[
  {
    "path": ".clang-format",
    "content": "---\nLanguage:        Cpp\n# BasedOnStyle:  LLVM\nAccessModifierOffset: -3\nAlignAfterOpenBracket: Align\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignEscapedNewlines: Right\nAlignOperands:   true\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: false\nAllowShortBlocksOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: None\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\n# AlwaysBreakAfterDefinitionReturnType: None\nAlwaysBreakAfterReturnType: TopLevel\nAlwaysBreakBeforeMultilineStrings: false\nAlwaysBreakTemplateDeclarations: true\nBinPackArguments: false\nBinPackParameters: false\nBraceWrapping:\n  AfterClass:      true\n  AfterControlStatement: false\n  AfterEnum:       true\n  AfterFunction:   true\n  AfterNamespace:  true\n  AfterObjCDeclaration: false\n  AfterStruct:     true\n  AfterUnion:      true\n  AfterExternBlock: true\n  BeforeCatch:     false\n  BeforeElse:      false\n  IndentBraces:    false\n  SplitEmptyFunction: true\n  SplitEmptyRecord: true\n  SplitEmptyNamespace: true\nBreakBeforeBinaryOperators: None\nBreakBeforeBraces: Custom\nBreakBeforeInheritanceComma: false\nBreakInheritanceList: AfterColon\nBreakBeforeTernaryOperators: true\n# BreakConstructorInitializersBeforeComma: false\nBreakConstructorInitializers: AfterColon\nBreakAfterJavaFieldAnnotations: false\nBreakStringLiterals: true\nColumnLimit:     0\nCommentPragmas:  '^ IWYU pragma:'\nCompactNamespaces: false\nConstructorInitializerAllOnOneLineOrOnePerLine: true\nConstructorInitializerIndentWidth: 3\nContinuationIndentWidth: 3\nCpp11BracedListStyle: false\nDerivePointerAlignment: false\nDisableFormat:   false\nExperimentalAutoDetectBinPacking: false\nFixNamespaceComments: true\nForEachMacros:\n  - foreach\n  - Q_FOREACH\n  - BOOST_FOREACH\nIncludeBlocks:   Regroup\nIncludeCategories:\n  - Regex:           '^\".*/.*'\n    Priority:        2\n  - Regex:           '^<.*'\n    Priority:        3\n  - Regex:           '.*'\n    Priority:        1\nIncludeIsMainRegex: 'IDontWantAmainInclude'\nIndentCaseLabels: false\nIndentPPDirectives: None\nIndentWidth:     3\nIndentWrappedFunctionNames: false\nJavaScriptQuotes: Leave\nJavaScriptWrapImports: true\nKeepEmptyLinesAtTheStartOfBlocks: true\nMacroBlockBegin: '^[A-Z]+_BEG'\nMacroBlockEnd:   '^[A-Z]+_END'\nMaxEmptyLinesToKeep: 2\nNamespaceIndentation: None\nObjCBinPackProtocolList: Auto\nObjCBlockIndentWidth: 2\nObjCSpaceAfterProperty: false\nObjCSpaceBeforeProtocolList: true\nPenaltyBreakAssignment: 2\nPenaltyBreakBeforeFirstCallParameter: 19\nPenaltyBreakComment: 300\nPenaltyBreakFirstLessLess: 120\nPenaltyBreakString: 1000\nPenaltyBreakTemplateDeclaration: 10\nPenaltyExcessCharacter: 1000000\nPenaltyReturnTypeOnItsOwnLine: 60\nPointerAlignment: Right\nReflowComments:  true\nSortIncludes:    true\nSortUsingDeclarations: true\nSpaceAfterCStyleCast: false\nSpaceAfterTemplateKeyword: false\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeCpp11BracedList: true\nSpaceBeforeCtorInitializerColon: true\nSpaceBeforeInheritanceColon: true\nSpaceBeforeParens: ControlStatements\nSpaceBeforeRangeBasedForLoopColon: true\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles:  false\nSpacesInContainerLiterals: true\nSpacesInCStyleCastParentheses: false\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard:        Cpp11\nTabWidth:        8\nUseTab:          Never\n...\n\n"
  },
  {
    "path": ".github/workflows/ccpp.yml",
    "content": "name: C/C++ CI\n\non: [push, pull_request]\n\njobs:\n  windows-build:\n    runs-on: windows-2019\n    strategy:\n      fail-fast: false\n\n    env:\n      VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite'\n      VCPKG_BUILD_TYPE: 'release'\n      VCPKG_ROOT: '${{github.workspace}}/libraries/vcpkg'\n      OS: windows-2019\n      MSVC: msvc2019_64\n      COMPILER: cl\n      VULKAN_VERSION: 1.3.211.0\n      QT_VERSION: 5.15.0\n\n    steps:\n    - uses: actions/checkout@v2\n      with:\n        submodules: true\n\n    - name: Initialise\n      run: mkdir build\n\n    - name: \"Fetch full history for vcpkg submodule\"\n      run: |\n        cd libraries/vcpkg\n        git fetch --unshallow\n        git pull --all\n\n    - name: 'Setup vcpkg'\n      run: ./libraries/vcpkg/bootstrap-vcpkg.bat\n\n    - name: 'Setup NuGet Credentials'\n      shell: 'bash'\n      run: >\n        cd build &&\n        `../libraries/vcpkg/vcpkg fetch nuget | tail -n 1`\n        sources add\n        -source \"https://nuget.pkg.github.com/decaf-emu/index.json\"\n        -storepasswordincleartext\n        -name \"GitHub\"\n        -username \"decaf-emu\"\n        -password \"${{ secrets.GITHUB_TOKEN }}\"\n        -Verbosity \"detailed\"\n\n    - name: Load Cached Vulkan SDK\n      id: cache-vulkan-windows\n      uses: actions/cache@v1\n      with:\n        path: C:/VulkanSDK/${{ env.VULKAN_VERSION }}\n        key: ${{ runner.os }}-${{ env.VULKAN_VERSION }}\n\n    - name: Install Vulkan SDK from web\n      if: steps.cache-vulkan-windows.outputs.cache-hit != 'true'\n      shell: powershell\n      run: |\n        mkdir \"C:\\\\VulkanSDK\"\n        cd \"C:\\\\VulkanSDK\"\n        Invoke-WebRequest \"https://sdk.lunarg.com/sdk/download/${{ env.VULKAN_VERSION }}/windows/VulkanSDK-${{ env.VULKAN_VERSION }}-Installer.exe?u=\" -OutFile \"VulkanSDK.exe\"\n        Start-Process -FilePath VulkanSDK.exe -Wait -PassThru -ArgumentList @(\"in --al --da --ao --confirm-command\");\n        cd \"C:\\\\VulkanSDK\\\\${{ env.VULKAN_VERSION }}\"\n        Remove-Item -Force -Recurse Demos\n        Remove-Item -Force -Recurse Templates\n        Remove-Item -Force -Recurse Tools\n        Remove-Item -Force maintenancetool.exe\n        Remove-Item -Force Bin\\\\VkLayer*\n        Remove-Item -Force Lib\\\\shaderc*\n        dir\n\n    - name: Load Cached Qt\n      id: cache-qt-windows\n      uses: actions/cache@v1\n      with:\n        path: C:/Qt/${{ env.QT_VERSION }}/${{ env.MSVC }}\n        key: ${{ runner.os }}-qt-${{ env.QT_VERSION }}-${{ env.MSVC }}\n\n    - name: Install Qt from web\n      if: steps.cache-qt-windows.outputs.cache-hit != 'true'\n      shell: powershell\n      run: |\n        mkdir \"C:\\\\Qt\"\n        cd \"C:\\\\Qt\"\n        pip install aqtinstall\n        cmd /c 'python 2>&1' -m aqt install ${{ env.QT_VERSION }} windows desktop win64_${{ env.MSVC }}\n        dir\n\n    - name: Setup Environment\n      shell: powershell\n      run: |\n        echo \"VULKAN_SDK=C:\\\\VulkanSDK\\\\${{ env.VULKAN_VERSION }}\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append\n        echo \"QTDIR=C:\\\\Qt\\\\${{ env.QT_VERSION }}\\\\${{ env.MSVC }}\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append\n\n    - name: Configure\n      shell: cmd\n      run: |\n        cd build\n        cmake -DCMAKE_TOOLCHAIN_FILE=../libraries/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-release -DVCPKG_HOST_TRIPLET=x64-windows-release -DCMAKE_BUILD_TYPE=Release -DDECAF_BUILD_TOOLS=ON -DDECAF_VULKAN=ON -DDECAF_QT=ON -DCMAKE_PREFIX_PATH=%QTDIR% -DCMAKE_INSTALL_PREFIX=install ..\n\n    - name: Build\n      run: |\n        cd build\n        cmake --build . --config Release -j 2 --target install\n\n    - uses: actions/upload-artifact@master\n      with:\n        name: decaf-emu-${{ env.OS }}\n        path: build/install\n\n  ubuntu-build:\n    runs-on: ubuntu-20.04\n    strategy:\n      fail-fast: false\n\n    env:\n      VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite'\n      VCPKG_BUILD_TYPE: 'release'\n      VCPKG_ROOT: '${{github.workspace}}/libraries/vcpkg'\n      OS: ubuntu-20.04\n      MSVC: msvc2019_64\n      COMPILER: gcc\n      VERSION: 10\n      QT_VERSION: 5.15.0\n\n    steps:\n    - uses: actions/checkout@v2\n      with:\n        submodules: true\n\n    - name: cache\n      uses: actions/cache@v2\n      with:\n        path: ~/.ccache\n        key: build-ccache-${{github.run_id}}\n        restore-keys: |\n          build-ccache\n\n    - name: Initialise\n      run: |\n        mkdir build\n\n    - name: Install Dependencies\n      run: |\n        cd build\n        wget -qO - http://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add -\n        sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-focal.list http://packages.lunarg.com/vulkan/lunarg-vulkan-focal.list\n        sudo apt-add-repository ppa:cginternals/ppa\n        sudo apt-get update\n        sudo apt-get install -y cmake ccache vulkan-sdk python3-setuptools mesa-common-dev libglu1-mesa-dev ninja-build libcurl4-openssl-dev libsdl2-dev libssl-dev zlib1g-dev libuv1-dev libc-ares-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev\n        if [ \"${{ env.COMPILER }}\" = \"gcc\" ]; then\n          sudo apt-get install -y g++-${{ env.VERSION }}\n        else\n          sudo apt-get install -y clang-${{ env.VERSION }}\n        fi\n        pip3 install wheel\n        pip3 install aqtinstall\n        python3 -m aqt install ${{ env.QT_VERSION }} linux desktop\n        PELFVER=0.12\n        curl -sSfLO https://github.com/NixOS/patchelf/releases/download/${PELFVER}/patchelf-${PELFVER}.tar.bz2\n        tar xvf patchelf-${PELFVER}.tar.bz2\n        cd patchelf-${PELFVER}*/\n        ./configure\n        make && sudo make install\n        cd ../\n\n    - name: Setup Environment\n      run: |\n        if [ \"${{ env.COMPILER }}\" = \"gcc\" ]; then\n          echo \"CC=gcc-${{ env.VERSION }}\" >> $GITHUB_ENV\n          echo \"CXX=g++-${{ env.VERSION }}\" >> $GITHUB_ENV\n        else\n          echo \"CC=clang-${{ env.VERSION }}\" >> $GITHUB_ENV\n          echo \"CXX=clang++-${{ env.VERSION}}\" >> $GITHUB_ENV\n        fi\n        echo \"QTDIR=$PWD/build/${{ env.QT_VERSION }}/gcc_64\" >> $GITHUB_ENV\n        echo \"VULKAN_SDK=$PWD/vulkan\" >> $GITHUB_ENV\n\n    - name: Configure\n      run: |\n        cd build\n        cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DDECAF_BUILD_TOOLS=ON -DDECAF_VULKAN=ON -DDECAF_QT=ON -DCMAKE_PREFIX_PATH=$QTDIR -DCMAKE_INSTALL_PREFIX=install ..\n\n    - name: Build\n      run: |\n        cd build\n        cmake --build . --config Release -j 2 --target install\n\n    - uses: actions/upload-artifact@master\n      with:\n        name: decaf-emu-${{ env.OS }}\n        path: build/install\n\n  create-release:\n    needs: [windows-build, ubuntu-build]\n    runs-on: \"ubuntu-20.04\"\n    if: github.ref == 'refs/heads/master'\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Download Artifacts\n        uses: actions/download-artifact@v2\n\n      - name: Upload\n        shell: bash\n        run: |\n          if [[ -e decaf-emu-appimage ]]; then\n            mv decaf-emu-appimage artifacts\n          else\n            mkdir artifacts\n          fi\n          files=$(find . -name \"decaf-emu-*\" ! -iname \"*.zip\" -type d)\n          for f in $files; do\n            echo \"Compressing $f\"\n            (cd $(basename $f) && zip -r ../artifacts/$(basename $f).zip *)\n          done\n          ls -al artifacts/\n          wget -c https://github.com/tcnksm/ghr/releases/download/v0.14.0/ghr_v0.14.0_linux_amd64.tar.gz\n          tar xfv ghr_v0.14.0_linux_amd64.tar.gz\n          ghr_v0.14.0_linux_amd64/ghr -u ${{ github.repository_owner }} -r decaf-emu -recreate -n 'decaf-emu CI builds' -b \"Corresponding commit: ${{ github.sha }}\" release artifacts/\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "build/\ndump/\nmlc/\nslc/\nhfio/\nsdcard/\ndocs/\n.vs/\n*.txt\n!CMakeLists.txt\n*.elf\n*.rpx\n*.o\n!tests/hle/bin/*.rpx\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"libraries/pugixml\"]\n\tpath = libraries/pugixml\n\turl = https://github.com/zeux/pugixml.git\n[submodule \"libraries/libbinrec\"]\n\tpath = libraries/libbinrec\n\turl = https://github.com/decaf-emu/libbinrec.git\n[submodule \"libraries/spdlog\"]\n\tpath = libraries/spdlog\n\turl = https://github.com/gabime/spdlog.git\n[submodule \"libraries/cereal\"]\n\tpath = libraries/cereal\n\turl = https://github.com/USCiLab/cereal.git\n[submodule \"libraries/ovsocket\"]\n\tpath = libraries/ovsocket\n\turl = https://github.com/exjam/ovsocket.git\n[submodule \"libraries/gsl-lite\"]\n\tpath = libraries/gsl-lite\n\turl = https://github.com/gsl-lite/gsl-lite\n[submodule \"libraries/addrlib\"]\n\tpath = libraries/addrlib\n\turl = https://github.com/decaf-emu/addrlib.git\n[submodule \"libraries/excmd\"]\n\tpath = libraries/excmd\n\turl = https://github.com/exjam/excmd\n[submodule \"libraries/imgui\"]\n\tpath = libraries/imgui\n\turl = https://github.com/ocornut/imgui.git\n[submodule \"libraries/cnl\"]\n\tpath = libraries/cnl\n\turl = https://github.com/johnmcfarlane/cnl\n[submodule \"libraries/catch\"]\n\tpath = libraries/catch\n\turl = https://github.com/philsquared/Catch.git\n[submodule \"libraries/cpp-peglib\"]\n\tpath = libraries/cpp-peglib\n\turl = https://github.com/yhirose/cpp-peglib.git\n[submodule \"libraries/fmt\"]\n\tpath = libraries/fmt\n\turl = https://github.com/fmtlib/fmt.git\n[submodule \"libraries/glslang\"]\n\tpath = libraries/glslang\n\turl = https://github.com/KhronosGroup/glslang.git\n[submodule \"libraries/qtads\"]\n\tpath = libraries/qtads\n\turl = https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System.git\n[submodule \"libraries/tomlplusplus\"]\n\tpath = libraries/tomlplusplus\n\turl = https://github.com/marzer/tomlplusplus.git\n[submodule \"libraries/vcpkg\"]\n\tpath = libraries/vcpkg\n\turl = https://github.com/microsoft/vcpkg.git\n"
  },
  {
    "path": "BUILDING.md",
    "content": "# Building decaf-emu from source\n- [Windows](#windows)\n- [Linux](#Linux)\n- [MacOS](#MacOS)\n- [CMake](#CMake)\n- [Troubleshooting](#Troubleshooting)\n\n## Windows\n\n### Dependencies\nRequired:\n- [Visual Studio 2019](https://visualstudio.microsoft.com/vs/community/)\n- [CMake](https://cmake.org/)\n- [Vulkan SDK](https://vulkan.lunarg.com/sdk/home)\n\nOptional:\n- [Qt 5.15+ / 6+] (https://www.qt.io/download-qt-installer), disable by using `-DDECAF_QT=OFF`\n\n### Building\n- `git clone --recursive https://github.com/decaf-emu/decaf-emu.git`\n\nUse cmake-gui to generate a VS project file:\n- Set `Where is the source code` to `[path to decaf-emu.git]`\n- Set `Where to build the binaries` to `[path to decaf-emu.git]/build`\n- Click `Add Entry` and set `Name: CMAKE_PREFIX_PATH`, `Type: PATH`, `Value` to a Qt5 or Qt6 installation directory, e.g. `Value: C:\\Qt\\5.15.2\\msvc2019_64`\n- Click `Configure`\n- Ensure `Specify the generator for this project` is set to a version of Visual Studio installed on your computer\n- Select `Specify toolchain for cross-compiling`\n- Click `Next`\n- Set `Specify the toolchain file` to `[path to decaf-emu.git]/libraries/vcpkg/scripts/buildsystems/vcpkg.cmake`\n- Click `Finish`\n- Configure will run, which may take a while as vcpkg acquires the dependencies, if all works the console should say `Configuring done`\n- Click `Generate`, if all works the console should say `Generating done`\n- Click `Open Project` to open the generated project in Visual sStudio where you can develop and build.\n\n## Linux\n\n### Dependencies\nRequired:\n- A modern C++17 friendly compiler such as g++9\n- CMake\n\nRequired dependencies which can be acquired from system or vcpkg:\n- c-ares\n- curl\n- ffmpeg\n- libuv\n- openssl\n- sdl2\n- zlib\n\nFor some systems, these can be installed with:\n- `apt install cmake libcurl4-openssl-dev libsdl2-dev libssl-dev zlib1g-dev libuv1-dev libc-ares-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev`\n\nOptional:\n- [Vulkan SDK](https://vulkan.lunarg.com/sdk/home), disable by using `-DDECAF_VULKAN=OFF`\n- [Qt 5.15+ / 6+] (https://www.qt.io/download-qt-installer), disable by using `-DDECAF_QT=OFF`\n\nFor some systems, Qt can be installed with:\n- `apt install qtbase5-dev qtbase5-private-dev libqt5svg5-dev libqt5x11extras5-dev mesa-common-dev libglu1-mesa-dev`\n\n### Building\n- `git clone --recursive https://github.com/decaf-emu/decaf-emu.git`\n- `cd decaf-emu`\n- `mkdir build`\n- `cd build`\n- `cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ../`\n- `make`\n\nYou might want to use `cmake -G Ninja <...>` and build with Ninja instead of Make for faster builds.\n\n## MacOS\nCurrently decaf-emu can build on MacOS using Xcode 11 although MoltenVK is missing crucial features which will prevent most games from rendering correctly, e.g. geometry shaders, transform feedback, logic op support, unrestricted depth range. This means the platform should be considered as unsupported.\n\n## CMake\nOptions interesting to users:\n- DECAF_FFMPEG - Build with ffmpeg which is used for decoding h264 videos\n- DECAF_QT - Build with Qt frontend.\n- DECAF_VULKAN - Build with Vulkan backend.\n\nOptions interesting to developers:\n- DECAF_BUILD_TESTS - Build tests.\n- DECAF_BUILD_TOOLS - Build tools.\n- DECAF_GIT_VERSION - Set this to OFF to disable generating a header with current git version to avoid rebuilding decaf_log.cpp when you do commits locally.\n- DECAF_PCH - Enable / disable pch (requires CMake v3.16)\n- DECAF_JIT_PROFILING - Build with JIT profiling support.\n- DECAF_VALGRIND - Build with Valgrind\n\n## Troubleshooting\n\ndecaf-emu builds on github actions CI - so a good reference on how to build is always the CI script itself [.github/workflows/ccpp.yml](https://github.com/decaf-emu/decaf-emu/blob/master/.github/workflows/ccpp.yml)\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.2)\n\nproject(decaf-emu C CXX)\nlist(APPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules\")\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\ninclude(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake OPTIONAL RESULT_VARIABLE USING_CONAN)\nif(USING_CONAN)\n    conan_basic_setup(NO_OUTPUT_DIRS)\nendif()\n\n# Disable PCH by default on older versions of CMake\nif(${CMAKE_VERSION} VERSION_LESS \"3.16.0\")\n    set(DECAF_PCH_DEFAULT OFF)\n    message(WARNING \"You are using a CMake which does not support PCH (<3.16).  This will adversely affect compile times.\")\nelse()\n    set(DECAF_PCH_DEFAULT ON)\nendif()\n\noption(DECAF_FFMPEG          \"Build with ffmpeg support\"                    ON)\noption(DECAF_VULKAN          \"Build with Vulkan rendering support\"          ON)\noption(DECAF_QT              \"Build with Qt support\"                        ON)\n\noption(DECAF_BUILD_TESTS     \"Build tests\"                                  OFF)\noption(DECAF_BUILD_TOOLS     \"Build tools\"                                  OFF)\n\noption(DECAF_GIT_VERSION     \"Generate a version header from git state\"     ON)\noption(DECAF_JIT_PROFILING   \"Build with JIT profiling support\"             OFF)\noption(DECAF_VALGRIND        \"Build with Valgrind support\"                  OFF)\noption(DECAF_PCH             \"Build with precompiled headers\"               ${DECAF_PCH_DEFAULT})\n\nset_property(GLOBAL PROPERTY USE_FOLDERS ON)\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/obj)\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/obj)\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/obj)\n\n# Setup install directories\ninclude(GNUInstallDirs)\nif(WIN32)\n    set(DECAF_INSTALL_BINDIR \"${CMAKE_INSTALL_PREFIX}\")\n    set(DECAF_INSTALL_DOCSDIR \"${CMAKE_INSTALL_PREFIX}\")\n    set(DECAF_INSTALL_RESOURCESDIR \"${CMAKE_INSTALL_PREFIX}/resources\")\nelse()\n    set(DECAF_INSTALL_BINDIR \"${CMAKE_INSTALL_BINDIR}\")\n    set(DECAF_INSTALL_DOCSDIR \"${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}\")\n    set(DECAF_INSTALL_RESOURCESDIR \"${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/resources\")\nendif()\n\nif(DECAF_JIT_PROFILING)\n    add_definitions(-DDECAF_JIT_ALLOW_PROFILING)\nendif()\n\nif(${CMAKE_SYSTEM_NAME} MATCHES \"Linux\")\n    find_package(XCB QUIET)\n    find_package(X11 QUIET)\n    find_package(WAYLAND QUIET)\n\n    if(${XCB_FOUND})\n        add_definitions(-DDECAF_PLATFORM_XCB)\n        set(DECAF_PLATFORM_XCB TRUE)\n    endif()\n\n    if(${X11_FOUND})\n        add_definitions(-DDECAF_PLATFORM_XLIB)\n        set(DECAF_PLATFORM_XLIB TRUE)\n    endif()\n\n    if(${WAYLAND_FOUND})\n        add_definitions(-DDECAF_PLATFORM_WAYLAND)\n        set(DECAF_PLATFORM_WAYLAND TRUE)\n    endif()\nendif()\n\nif(${CMAKE_SYSTEM_NAME} MATCHES \"Darwin\")\n    add_definitions(-DDECAF_PLATFORM_COCOA)\n    set(DECAF_PLATFORM_COCOA TRUE)\nendif()\n\nfind_package(Threads REQUIRED)\n\n\nif(VCPKG_TARGET_TRIPLET)\n    find_package(c-ares CONFIG REQUIRED)\n    find_package(CURL CONFIG REQUIRED)\n    find_package(OpenSSL REQUIRED)\n    find_package(SDL2 CONFIG REQUIRED)\n    find_package(libuv CONFIG REQUIRED)\n    find_package(ZLIB REQUIRED)\n\n    set(CARES_LIBRARY c-ares::cares)\n    set(CURL_LIBRARY CURL::libcurl)\n    set(LIBUV_LIBRARY libuv::uv)\n    set(OPENSSL_LIBRARY OpenSSL::SSL)\n    set(SDL2_LIBRARY SDL2::SDL2)\n    set(SDL2_MAIN_LIBRARY SDL2::SDL2main)\n    set(ZLIB_LIBRARY ZLIB::ZLIB)\nelse()\n    find_package(CARES REQUIRED)\n    find_package(CURL REQUIRED)\n    find_package(LibUV REQUIRED)\n    find_package(OpenSSL REQUIRED)\n    find_package(SDL2 REQUIRED)\n    find_package(ZLIB REQUIRED)\n\n    set(CARES_LIBRARY CARES::CARES)\n    set(CURL_LIBRARY CURL::libcurl)\n    set(LIBUV_LIBRARY LibUV::LibUV)\n    set(OPENSSL_LIBRARY OpenSSL::SSL)\n    set(SDL2_LIBRARY SDL2::SDL2)\n    set(SDL2_MAIN_LIBRARY SDL2::SDL2main)\n    set(ZLIB_LIBRARY ZLIB::ZLIB)\nendif()\n\nif(DECAF_FFMPEG)\n    find_package(FFMPEG REQUIRED)\n    set(FFMPEG_LIBRARY FFMPEG::AVCODEC FFMPEG::AVFILTER FFMPEG::AVUTIL FFMPEG::SWSCALE)\n    add_definitions(-DDECAF_FFMPEG)\nendif()\n\n# TODO: Remove this definitions as it is no longer optional\nadd_definitions(-DDECAF_SDL)\n\nif(DECAF_VULKAN)\n    find_package(Vulkan 1.1.106.0 REQUIRED) # Vulkan_INCLUDE_DIRS and Vulkan_LIBRARIES\n    add_library(vulkan INTERFACE IMPORTED)\n    set_target_properties(vulkan PROPERTIES\n        INTERFACE_INCLUDE_DIRECTORIES ${Vulkan_INCLUDE_DIRS}\n        INTERFACE_LINK_LIBRARIES ${Vulkan_LIBRARIES})\n    add_definitions(-DVULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL=0)\n\n    if(MSVC)\n        add_definitions(-DVK_USE_PLATFORM_WIN32_KHR)\n    endif()\n\n    if(DECAF_PLATFORM_XCB)\n        add_definitions(-DVK_USE_PLATFORM_XCB_KHR)\n    endif()\n\n    if(DECAF_PLATFORM_XLIB)\n        add_definitions(-DVK_USE_PLATFORM_XLIB_KHR)\n    endif()\n\n    if(DECAF_PLATFORM_WAYLAND)\n        add_definitions(-DVK_USE_PLATFORM_WAYLAND_KHR)\n    endif()\n\n    if(DECAF_PLATFORM_COCOA)\n        add_definitions(-DVK_USE_PLATFORM_MACOS_MVK)\n    endif()\n\n    add_definitions(-DDECAF_VULKAN)\nendif()\n\nif(DECAF_VALGRIND)\n    add_definitions(-DDECAF_VALGRIND)\nendif()\n\nif(DECAF_QT)\n    find_package(Qt6 COMPONENTS Core Concurrent Widgets Svg SvgWidgets Xml)\n    if(NOT Qt6_FOUND)\n        find_package(Qt5 5.15 COMPONENTS Core Concurrent Widgets Svg Xml REQUIRED)\n    endif()\n\n    set(QT_DIR Qt${QT_VERSION_MAJOR}_DIR)\n    add_definitions(-DDECAF_QT)\nendif()\n\n# Build third party libraries\nadd_subdirectory(\"libraries\")\n\n# Setup compile options\nif(MSVC)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /std:c++latest\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /permissive-\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /MP /FS\") # Parallel source builds\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /GA\") # Optimises TLS access\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /W4\") # Lets be specific about warnings\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /wd26812\") # Allow unscoped enums\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /wd4201\") # Allow the use of unnamed structs/unions\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /wd4100\") # Allow unreferenced formal parameters\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /wd4366\") # Allow unaligned uint64_t (permitted on x64)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /wd4324\") # Disable structure padding warnings\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /wd4297\") # Disable nothrow warning, VulkanSDK engineers dont know how to code\n\n    # Link time code generation\n    set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} /GL\")\n    set(CMAKE_STATIC_LINKER_FLAGS_RELEASE \"${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG\")\n    set(CMAKE_SHARED_LINKER_FLAGS_RELEASE \"${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG\")\n    set(CMAKE_EXE_LINKER_FLAGS_RELEASE \"${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG\")\n\n    add_definitions(-DNOMINMAX)\n    add_definitions(-DUNICODE -D_UNICODE)\n\n    # Disable warnings about using non-portable string function variants\n    add_definitions(-D_CRT_SECURE_NO_WARNINGS)\n\n    # Disable warnings about using deprecated std::wstring_convert\n    add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)\n\n    # Disable linker warnins about missing PDBs\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} /ignore:4099\")\n\n    # Set minimum windows version to enable newer APIs\n    add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600)\nelse()\n    add_definitions(-DDECAF_USE_STDLAYOUT_BITFIELD)\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++17\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof\")\n\n    if(APPLE)\n        add_definitions(-D_DARWIN_C_SOURCE)\n    else()\n        link_libraries(stdc++fs)\n    endif()\nendif()\n\n# Macro to map filters to folder structure for MSVC projects\nmacro(GroupSources groupname curdir)\n    if(MSVC)\n        file(GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/${curdir} ${PROJECT_SOURCE_DIR}/${curdir}/*)\n\n        foreach(child ${children})\n            if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child})\n                GroupSources(${rootname} ${groupname}/${child} ${curdir}/${child})\n            else()\n                string(REPLACE \"/\" \"\\\\\" safegroupname ${groupname})\n                source_group(${safegroupname} FILES ${PROJECT_SOURCE_DIR}/${rootdir}${curdir}/${child})\n            endif()\n        endforeach()\n    endif()\nendmacro()\n\nmacro(AutoGroupPCHFiles)\n    if(MSVC)\n        source_group(\"CMake PCH\" FILES\n            \"${PROJECT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}.dir/cmake_pch.hxx\"\n            \"${PROJECT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}.dir/cmake_pch.cxx\")\n    endif()\nendmacro()\n\nif(DECAF_GIT_VERSION)\n    # Generate build information\n    include(GetGitRevisionDescription)\n\n    function(get_timestamp _var)\n        string(TIMESTAMP timestamp UTC)\n        set(${_var} \"${timestamp}\" PARENT_SCOPE)\n    endfunction()\n\n    get_git_head_revision(GIT_REF_SPEC GIT_REV)\n    git_describe(GIT_DESC --always --long --dirty)\n    git_branch_name(GIT_BRANCH)\n    get_timestamp(BUILD_DATE)\n\n    set(BUILD_VERSION \"0\")\n    if ($ENV{CI})\n       if ($ENV{TRAVIS})\n          set(BUILD_TAG $ENV{TRAVIS_TAG})\n       elseif($ENV{APPVEYOR})\n          set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME})\n       endif()\n\n       if (BUILD_TAG)\n          string(REGEX MATCH \"${CMAKE_MATCH_1}-([0-9]+)\" OUTVAR ${BUILD_TAG})\n          if (${CMAKE_MATCH_COUNT} GREATER 0)\n              set(BUILD_VERSION ${CMAKE_MATCH_1})\n          endif()\n       endif()\n    endif()\nelse()\n    set(GIT_REV \"local\")\n    set(BUILD_NAME \"decaf-emu\")\n    set(BUILD_VERSION \"0\")\nendif()\n\nconfigure_file(\"${CMAKE_CURRENT_SOURCE_DIR}/src/decaf_buildinfo.h.in\"\n               \"${CMAKE_CURRENT_BINARY_DIR}/generated/decaf_buildinfo.h\" @ONLY)\ninclude_directories(\"${CMAKE_CURRENT_BINARY_DIR}/generated\")\n\nadd_subdirectory(\"src\")\nadd_subdirectory(\"resources\")\n\nif(DECAF_BUILD_TOOLS)\n    add_subdirectory(\"tools\")\nendif()\n\nif(DECAF_BUILD_TESTS)\n    enable_testing()\n    add_subdirectory(\"tests\")\nendif()\n"
  },
  {
    "path": "CMakeModules/FindCARES.cmake",
    "content": "# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying\n# file Copyright.txt or https://cmake.org/licensing for details.\n#\n# This is a slight edited version of FindLIBUV.cmake :)\n\n#[=======================================================================[.rst:\nFindCARES\n---------\n\nFind c-ares includes and library.\n\nImported Targets\n^^^^^^^^^^^^^^^^\n\nAn :ref:`imported target <Imported targets>` named\n``CARES::CARES`` is provided if c-ares has been found.\n\nResult Variables\n^^^^^^^^^^^^^^^^\n\nThis module defines the following variables:\n\n``CARES_FOUND``\n  True if c-ares was found, false otherwise.\n``CARES_INCLUDE_DIRS``\n  Include directories needed to include c-ares headers.\n``CARES_LIBRARIES``\n  Libraries needed to link to c-ares.\n``CARES_VERSION``\n  The version of c-ares found.\n``CARES_VERSION_MAJOR``\n  The major version of c-ares.\n``CARES_VERSION_MINOR``\n  The minor version of c-ares.\n``CARES_VERSION_PATCH``\n  The patch version of c-ares.\n\nCache Variables\n^^^^^^^^^^^^^^^\n\nThis module uses the following cache variables:\n\n``CARES_LIBRARY``\n  The location of the c-ares library file.\n``CARES_INCLUDE_DIR``\n  The location of the c-ares include directory containing ``ares.h``.\n\nThe cache variables should not be used by project code.\nThey may be set by end users to point at c-ares components.\n#]=======================================================================]\n\nset(CARES_NAMES ${CARES_NAMES} cares)\n\n#-----------------------------------------------------------------------------\nfind_library(CARES_LIBRARY\n  NAMES ${CARES_NAMES}\n  )\nmark_as_advanced(CARES_LIBRARY)\n\nfind_path(CARES_INCLUDE_DIR\n  NAMES ares.h\n  )\nmark_as_advanced(CARES_INCLUDE_DIR)\n\n#-----------------------------------------------------------------------------\n# Extract version number if possible.\nset(_CARES_H_REGEX \"#[ \\t]*define[ \\t]+ARES_VERSION_(MAJOR|MINOR|PATCH)[ \\t]+[0-9]+\")\nif(CARES_INCLUDE_DIR AND EXISTS \"${CARES_INCLUDE_DIR}/ares_version.h\")\n  file(STRINGS \"${CARES_INCLUDE_DIR}/ares_version.h\" _CARES_H REGEX \"${_CARES_H_REGEX}\")\nelse()\n  set(_CARES_H \"\")\nendif()\nforeach(c MAJOR MINOR PATCH)\n  if(_CARES_H MATCHES \"#[ \\t]*define[ \\t]+ARES_VERSION_${c}[ \\t]+([0-9]+)\")\n    set(_CARES_VERSION_${c} \"${CMAKE_MATCH_1}\")\n  else()\n    unset(_CARES_VERSION_${c})\n  endif()\nendforeach()\nif(DEFINED _CARES_VERSION_MAJOR AND DEFINED _CARES_VERSION_MINOR)\n  set(CARES_VERSION_MAJOR \"${_CARES_VERSION_MAJOR}\")\n  set(CARES_VERSION_MINOR \"${_CARES_VERSION_MINOR}\")\n  set(CARES_VERSION \"${CARES_VERSION_MAJOR}.${CARES_VERSION_MINOR}\")\n  if(DEFINED _CARES_VERSION_PATCH)\n    set(CARES_VERSION_PATCH \"${_CARES_VERSION_PATCH}\")\n    set(CARES_VERSION \"${CARES_VERSION}.${CARES_VERSION_PATCH}\")\n  else()\n    unset(CARES_VERSION_PATCH)\n  endif()\nelse()\n  set(CARES_VERSION_MAJOR \"\")\n  set(CARES_VERSION_MINOR \"\")\n  set(CARES_VERSION_PATCH \"\")\n  set(CARES_VERSION \"\")\nendif()\nunset(_CARES_VERSION_MAJOR)\nunset(_CARES_VERSION_MINOR)\nunset(_CARES_VERSION_PATCH)\nunset(_CARES_H_REGEX)\nunset(_CARES_H)\n\n#-----------------------------------------------------------------------------\ninclude(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(CARES\n  FOUND_VAR CARES_FOUND\n  REQUIRED_VARS CARES_LIBRARY CARES_INCLUDE_DIR\n  VERSION_VAR CARES_VERSION\n  )\nset(CARES_FOUND ${CARES_FOUND})\n\n#-----------------------------------------------------------------------------\n# Provide documented result variables and targets.\nif(CARES_FOUND)\n  set(CARES_INCLUDE_DIRS ${CARES_INCLUDE_DIR})\n  set(CARES_LIBRARIES ${CARES_LIBRARY})\n  if(NOT TARGET CARES::CARES)\n    add_library(CARES::CARES UNKNOWN IMPORTED)\n    set_target_properties(CARES::CARES PROPERTIES\n      IMPORTED_LOCATION \"${CARES_LIBRARY}\"\n      INTERFACE_INCLUDE_DIRECTORIES \"${CARES_INCLUDE_DIRS}\"\n      )\n  endif()\nendif()\n"
  },
  {
    "path": "CMakeModules/FindFFMPEG.cmake",
    "content": "macro(find_component COMPONENT LIBRARY HEADER)\n   find_path(${COMPONENT}_INCLUDE_DIR NAMES \"${HEADER}\" HINTS \"/usr/include/ffmpeg\")\n   find_library(${COMPONENT}_LIBRARY NAMES \"${LIBRARY}\")\n\n   if(${COMPONENT}_INCLUDE_DIR AND ${COMPONENT}_LIBRARY)\n      set(${COMPONENT}_FOUND TRUE)\n\n      if(NOT TARGET FFMPEG::${COMPONENT})\n        add_library(FFMPEG::${COMPONENT} UNKNOWN IMPORTED)\n        set_target_properties(FFMPEG::${COMPONENT} PROPERTIES\n           IMPORTED_LOCATION \"${${COMPONENT}_LIBRARY}\"\n           INTERFACE_INCLUDE_DIRECTORIES \"${${COMPONENT}_INCLUDE_DIR}\")\n      endif()\n   else()\n      set(${COMPONENT}_FOUND FALSE)\n   endif()\nendmacro()\n\nfind_component(AVCODEC \"avcodec\" \"libavcodec/avcodec.h\")\nfind_component(AVFILTER \"avfilter\" \"libavfilter/avfilter.h\")\nfind_component(AVUTIL \"avutil\" \"libavutil/avutil.h\")\nfind_component(SWSCALE \"swscale\" \"libswscale/swscale.h\")\n"
  },
  {
    "path": "CMakeModules/FindLibUV.cmake",
    "content": "# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying\n# file Copyright.txt or https://cmake.org/licensing for details.\n\n#[=======================================================================[.rst:\nFindLibUV\n---------\n\nFind libuv includes and library.\n\nImported Targets\n^^^^^^^^^^^^^^^^\n\nAn :ref:`imported target <Imported targets>` named\n``LibUV::LibUV`` is provided if libuv has been found.\n\nResult Variables\n^^^^^^^^^^^^^^^^\n\nThis module defines the following variables:\n\n``LibUV_FOUND``\n  True if libuv was found, false otherwise.\n``LibUV_INCLUDE_DIRS``\n  Include directories needed to include libuv headers.\n``LibUV_LIBRARIES``\n  Libraries needed to link to libuv.\n``LibUV_VERSION``\n  The version of libuv found.\n``LibUV_VERSION_MAJOR``\n  The major version of libuv.\n``LibUV_VERSION_MINOR``\n  The minor version of libuv.\n``LibUV_VERSION_PATCH``\n  The patch version of libuv.\n\nCache Variables\n^^^^^^^^^^^^^^^\n\nThis module uses the following cache variables:\n\n``LibUV_LIBRARY``\n  The location of the libuv library file.\n``LibUV_INCLUDE_DIR``\n  The location of the libuv include directory containing ``uv.h``.\n\nThe cache variables should not be used by project code.\nThey may be set by end users to point at libuv components.\n#]=======================================================================]\n\n#-----------------------------------------------------------------------------\nfind_library(LibUV_LIBRARY\n  NAMES uv uv_a libuv\n  )\nmark_as_advanced(LibUV_LIBRARY)\n\nfind_path(LibUV_INCLUDE_DIR\n  NAMES uv.h\n  )\nmark_as_advanced(LibUV_INCLUDE_DIR)\n\n#-----------------------------------------------------------------------------\n# Extract version number if possible.\nset(_LibUV_H_REGEX \"#[ \\t]*define[ \\t]+UV_VERSION_(MAJOR|MINOR|PATCH)[ \\t]+[0-9]+\")\nif(LibUV_INCLUDE_DIR AND EXISTS \"${LibUV_INCLUDE_DIR}/uv-version.h\")\n  file(STRINGS \"${LibUV_INCLUDE_DIR}/uv-version.h\" _LibUV_H REGEX \"${_LibUV_H_REGEX}\")\nelseif(LibUV_INCLUDE_DIR AND EXISTS \"${LibUV_INCLUDE_DIR}/uv/version.h\")\n  file(STRINGS \"${LibUV_INCLUDE_DIR}/uv/version.h\" _LibUV_H REGEX \"${_LibUV_H_REGEX}\")\nelseif(LibUV_INCLUDE_DIR AND EXISTS \"${LibUV_INCLUDE_DIR}/uv.h\")\n  file(STRINGS \"${LibUV_INCLUDE_DIR}/uv.h\" _LibUV_H REGEX \"${_LibUV_H_REGEX}\")\nelse()\n  set(_LibUV_H \"\")\nendif()\nforeach(c MAJOR MINOR PATCH)\n  if(_LibUV_H MATCHES \"#[ \\t]*define[ \\t]+UV_VERSION_${c}[ \\t]+([0-9]+)\")\n    set(_LibUV_VERSION_${c} \"${CMAKE_MATCH_1}\")\n  else()\n    unset(_LibUV_VERSION_${c})\n  endif()\nendforeach()\nif(DEFINED _LibUV_VERSION_MAJOR AND DEFINED _LibUV_VERSION_MINOR)\n  set(LibUV_VERSION_MAJOR \"${_LibUV_VERSION_MAJOR}\")\n  set(LibUV_VERSION_MINOR \"${_LibUV_VERSION_MINOR}\")\n  set(LibUV_VERSION \"${LibUV_VERSION_MAJOR}.${LibUV_VERSION_MINOR}\")\n  if(DEFINED _LibUV_VERSION_PATCH)\n    set(LibUV_VERSION_PATCH \"${_LibUV_VERSION_PATCH}\")\n    set(LibUV_VERSION \"${LibUV_VERSION}.${LibUV_VERSION_PATCH}\")\n  else()\n    unset(LibUV_VERSION_PATCH)\n  endif()\nelse()\n  set(LibUV_VERSION_MAJOR \"\")\n  set(LibUV_VERSION_MINOR \"\")\n  set(LibUV_VERSION_PATCH \"\")\n  set(LibUV_VERSION \"\")\nendif()\nunset(_LibUV_VERSION_MAJOR)\nunset(_LibUV_VERSION_MINOR)\nunset(_LibUV_VERSION_PATCH)\nunset(_LibUV_H_REGEX)\nunset(_LibUV_H)\n\n#-----------------------------------------------------------------------------\ninclude(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(LibUV\n  FOUND_VAR LibUV_FOUND\n  REQUIRED_VARS LibUV_LIBRARY LibUV_INCLUDE_DIR\n  VERSION_VAR LibUV_VERSION\n  )\nset(LIBUV_FOUND ${LibUV_FOUND})\n\n#-----------------------------------------------------------------------------\n# Provide documented result variables and targets.\nif(LibUV_FOUND)\n  set(LibUV_INCLUDE_DIRS ${LibUV_INCLUDE_DIR})\n  set(LibUV_LIBRARIES ${LibUV_LIBRARY})\n  if(NOT TARGET LibUV::LibUV)\n    add_library(LibUV::LibUV UNKNOWN IMPORTED)\n    set_target_properties(LibUV::LibUV PROPERTIES\n      IMPORTED_LOCATION \"${LibUV_LIBRARY}\"\n      INTERFACE_INCLUDE_DIRECTORIES \"${LibUV_INCLUDE_DIRS}\"\n      )\n  endif()\nendif()\n"
  },
  {
    "path": "CMakeModules/FindSDL2.cmake",
    "content": "# Distributed under the OSI-approved BSD 3-Clause License. See accompanying\n# file Copyright.txt or https://cmake.org/licensing for details.\n\n#.rst:\n# FindSDL2\n# -------\n#\n# Locate SDL2 library\n#\n# This module defines\n#\n# ::\n#\n# SDL2_LIBRARY, the name of the library to link against\n# SDL2_FOUND, if false, do not try to link to SDL\n# SDL2_INCLUDE_DIR, where to find SDL.h\n# SDL2_VERSION_STRING, human-readable string containing the version of SDL\n#\n#\n#\n# This module responds to the flag:\n#\n# ::\n#\n# SDL2_BUILDING_LIBRARY\n# If this is defined, then no SDL2_main will be linked in because\n# only applications need main().\n# Otherwise, it is assumed you are building an application and this\n# module will attempt to locate and set the proper link flags\n# as part of the returned SDL2_LIBRARY variable.\n#\n#\n#\n# Don't forget to include SDLmain.h and SDLmain.m your project for the\n# OS X framework based version. (Other versions link to -lSDLmain which\n# this module will try to find on your behalf.) Also for OS X, this\n# module will automatically add the -framework Cocoa on your behalf.\n#\n#\n#\n# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your\n# configuration and no SDL2_LIBRARY, it means CMake did not find your SDL\n# library (SDL.dll, libsdl.so, SDL.framework, etc). Set\n# SDL2_LIBRARY_TEMP to point to your SDL library, and configure again.\n# Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this\n# value as appropriate. These values are used to generate the final\n# SDL2_LIBRARY variable, but when these values are unset, SDL2_LIBRARY\n# does not get created.\n#\n#\n#\n# $SDLDIR is an environment variable that would correspond to the\n# ./configure --prefix=$SDLDIR used in building SDL. l.e.galup 9-20-02\n#\n# Modified by Eric Wing. Added code to assist with automated building\n# by using environmental variables and providing a more\n# controlled/consistent search behavior. Added new modifications to\n# recognize OS X frameworks and additional Unix paths (FreeBSD, etc).\n# Also corrected the header search path to follow \"proper\" SDL\n# guidelines. Added a search for SDLmain which is needed by some\n# platforms. Added a search for threads which is needed by some\n# platforms. Added needed compile switches for MinGW.\n#\n# On OSX, this will prefer the Framework version (if found) over others.\n# People will have to manually change the cache values of SDL2_LIBRARY to\n# override this selection or set the CMake environment\n# CMAKE_INCLUDE_PATH to modify the search paths.\n#\n# Note that the header path has changed from SDL/SDL.h to just SDL.h\n# This needed to change because \"proper\" SDL convention is #include\n# \"SDL.h\", not <SDL/SDL.h>. This is done for portability reasons\n# because not all systems place things in SDL/ (see FreeBSD).\n\nif(NOT SDL2_DIR)\n  set(SDL2_DIR \"\" CACHE PATH \"SDL2 directory\")\nendif()\n\nfind_path(SDL2_INCLUDE_DIR SDL.h\n  HINTS\n    ENV SDLDIR\n    ${SDL2_DIR}\n  PATH_SUFFIXES SDL2\n                # path suffixes to search inside ENV{SDLDIR}\n                include/SDL2 include\n)\n\nif(CMAKE_SIZEOF_VOID_P EQUAL 8)\n  set(VC_LIB_PATH_SUFFIX lib/x64)\nelse()\n  set(VC_LIB_PATH_SUFFIX lib/x86)\nendif()\n\n# SDL-1.1 is the name used by FreeBSD ports...\n# don't confuse it for the version number.\nfind_library(SDL2_LIBRARY_TEMP\n  NAMES SDL2\n  HINTS\n    ENV SDLDIR\n    ${SDL2_DIR}\n  PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}\n)\n\n# Hide this cache variable from the user, it's an internal implementation\n# detail. The documented library variable for the user is SDL2_LIBRARY\n# which is derived from SDL2_LIBRARY_TEMP further below.\nset_property(CACHE SDL2_LIBRARY_TEMP PROPERTY TYPE INTERNAL)\n\nif(NOT SDL2_BUILDING_LIBRARY)\n  if(NOT SDL2_INCLUDE_DIR MATCHES \".framework\")\n    # Non-OS X framework versions expect you to also dynamically link to\n    # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms\n    # seem to provide SDLmain for compatibility even though they don't\n    # necessarily need it.\n    find_library(SDL2MAIN_LIBRARY\n      NAMES SDL2main\n      HINTS\n        ENV SDLDIR\n        ${SDL2_DIR}\n      PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}\n      PATHS\n      /sw\n      /opt/local\n      /opt/csw\n      /opt\n    )\n  endif()\nendif()\n\n# SDL may require threads on your system.\n# The Apple build may not need an explicit flag because one of the\n# frameworks may already provide it.\n# But for non-OSX systems, I will use the CMake Threads package.\nif(NOT APPLE)\n  find_package(Threads)\nendif()\n\n# MinGW needs an additional link flag, -mwindows\n# It's total link flags should look like -lmingw32 -lSDLmain -lSDL -mwindows\nif(MINGW)\n  set(MINGW32_LIBRARY mingw32 \"-mwindows\" CACHE STRING \"link flags for MinGW\")\nendif()\n\nif(SDL2_LIBRARY_TEMP)\n  # For SDLmain\n  if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY)\n    list(FIND SDL2_LIBRARY_TEMP \"${SDL2MAIN_LIBRARY}\" _SDL2_MAIN_INDEX)\n    if(_SDL2_MAIN_INDEX EQUAL -1)\n      set(SDL2_LIBRARY_TEMP \"${SDL2MAIN_LIBRARY}\" ${SDL2_LIBRARY_TEMP})\n    endif()\n    unset(_SDL2_MAIN_INDEX)\n  endif()\n\n  # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa.\n  # CMake doesn't display the -framework Cocoa string in the UI even\n  # though it actually is there if I modify a pre-used variable.\n  # I think it has something to do with the CACHE STRING.\n  # So I use a temporary variable until the end so I can set the\n  # \"real\" variable in one-shot.\n  if(APPLE)\n    set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} \"-framework Cocoa\")\n  endif()\n\n  # For threads, as mentioned Apple doesn't need this.\n  # In fact, there seems to be a problem if I used the Threads package\n  # and try using this line, so I'm just skipping it entirely for OS X.\n  if(NOT APPLE)\n    set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT})\n  endif()\n\n  # For MinGW library\n  if(MINGW)\n    set(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP})\n  endif()\n\n  if(WIN32 AND NOT WINDOWS_STORE AND NOT WINDOWS_PHONE)\n    set(SDL2_LIBRARY_TEMP winmm imm32 version msimg32 ${SDL2_LIBRARY_TEMP})\n  endif(WIN32 AND NOT WINDOWS_STORE AND NOT WINDOWS_PHONE)\n\n  # Set the final string here so the GUI reflects the final state.\n  set(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING \"Where the SDL Library can be found\")\nendif()\n\nif(SDL2_INCLUDE_DIR AND EXISTS \"${SDL2_INCLUDE_DIR}/SDL2_version.h\")\n  file(STRINGS \"${SDL2_INCLUDE_DIR}/SDL2_version.h\" SDL2_VERSION_MAJOR_LINE REGEX \"^#define[ \\t]+SDL2_MAJOR_VERSION[ \\t]+[0-9]+$\")\n  file(STRINGS \"${SDL2_INCLUDE_DIR}/SDL2_version.h\" SDL2_VERSION_MINOR_LINE REGEX \"^#define[ \\t]+SDL2_MINOR_VERSION[ \\t]+[0-9]+$\")\n  file(STRINGS \"${SDL2_INCLUDE_DIR}/SDL2_version.h\" SDL2_VERSION_PATCH_LINE REGEX \"^#define[ \\t]+SDL2_PATCHLEVEL[ \\t]+[0-9]+$\")\n  string(REGEX REPLACE \"^#define[ \\t]+SDL2_MAJOR_VERSION[ \\t]+([0-9]+)$\" \"\\\\1\" SDL2_VERSION_MAJOR \"${SDL2_VERSION_MAJOR_LINE}\")\n  string(REGEX REPLACE \"^#define[ \\t]+SDL2_MINOR_VERSION[ \\t]+([0-9]+)$\" \"\\\\1\" SDL2_VERSION_MINOR \"${SDL2_VERSION_MINOR_LINE}\")\n  string(REGEX REPLACE \"^#define[ \\t]+SDL2_PATCHLEVEL[ \\t]+([0-9]+)$\" \"\\\\1\" SDL2_VERSION_PATCH \"${SDL2_VERSION_PATCH_LINE}\")\n  set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH})\n  unset(SDL2_VERSION_MAJOR_LINE)\n  unset(SDL2_VERSION_MINOR_LINE)\n  unset(SDL2_VERSION_PATCH_LINE)\n  unset(SDL2_VERSION_MAJOR)\n  unset(SDL2_VERSION_MINOR)\n  unset(SDL2_VERSION_PATCH)\nendif()\n\nset(SDL2_LIBRARIES ${SDL2_LIBRARY})\nset(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR})\n\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL\n                                  REQUIRED_VARS SDL2_LIBRARIES SDL2_INCLUDE_DIRS\n                                  VERSION_VAR SDL2_VERSION_STRING)\n\nmark_as_advanced(SDL2_LIBRARY SDL2_INCLUDE_DIR)\n"
  },
  {
    "path": "CMakeModules/FindWAYLAND.cmake",
    "content": "# Try to find Wayland on a Unix system\n#\n# This will define:\n#\n#   WAYLAND_FOUND       - True if Wayland is found\n#   WAYLAND_LIBRARIES   - Link these to use Wayland\n#   WAYLAND_INCLUDE_DIR - Include directory for Wayland\n#   WAYLAND_DEFINITIONS - Compiler flags for using Wayland\n#\n# In addition the following more fine grained variables will be defined:\n#\n#   WAYLAND_CLIENT_FOUND  WAYLAND_CLIENT_INCLUDE_DIR  WAYLAND_CLIENT_LIBRARIES\n#   WAYLAND_SERVER_FOUND  WAYLAND_SERVER_INCLUDE_DIR  WAYLAND_SERVER_LIBRARIES\n#   WAYLAND_EGL_FOUND     WAYLAND_EGL_INCLUDE_DIR     WAYLAND_EGL_LIBRARIES\n#\n# Copyright (c) 2013 Martin Gräßlin <mgraesslin@kde.org>\n#\n# Redistribution and use is allowed according to the terms of the BSD license.\n# For details see the accompanying COPYING-CMAKE-SCRIPTS file.\n\nIF (NOT WIN32)\n  IF (WAYLAND_INCLUDE_DIR AND WAYLAND_LIBRARIES)\n    # In the cache already\n    SET(WAYLAND_FIND_QUIETLY TRUE)\n  ENDIF ()\n\n  # Use pkg-config to get the directories and then use these values\n  # in the FIND_PATH() and FIND_LIBRARY() calls\n  FIND_PACKAGE(PkgConfig)\n  PKG_CHECK_MODULES(PKG_WAYLAND QUIET wayland-client wayland-server wayland-egl wayland-cursor)\n\n  SET(WAYLAND_DEFINITIONS ${PKG_WAYLAND_CFLAGS})\n\n  FIND_PATH(WAYLAND_CLIENT_INCLUDE_DIR  NAMES wayland-client.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS})\n  FIND_PATH(WAYLAND_SERVER_INCLUDE_DIR  NAMES wayland-server.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS})\n  FIND_PATH(WAYLAND_EGL_INCLUDE_DIR     NAMES wayland-egl.h    HINTS ${PKG_WAYLAND_INCLUDE_DIRS})\n  FIND_PATH(WAYLAND_CURSOR_INCLUDE_DIR  NAMES wayland-cursor.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS})\n\n  FIND_LIBRARY(WAYLAND_CLIENT_LIBRARIES NAMES wayland-client   HINTS ${PKG_WAYLAND_LIBRARY_DIRS})\n  FIND_LIBRARY(WAYLAND_SERVER_LIBRARIES NAMES wayland-server   HINTS ${PKG_WAYLAND_LIBRARY_DIRS})\n  FIND_LIBRARY(WAYLAND_EGL_LIBRARIES    NAMES wayland-egl      HINTS ${PKG_WAYLAND_LIBRARY_DIRS})\n  FIND_LIBRARY(WAYLAND_CURSOR_LIBRARIES NAMES wayland-cursor   HINTS ${PKG_WAYLAND_LIBRARY_DIRS})\n\n  set(WAYLAND_INCLUDE_DIR ${WAYLAND_CLIENT_INCLUDE_DIR} ${WAYLAND_SERVER_INCLUDE_DIR} ${WAYLAND_EGL_INCLUDE_DIR} ${WAYLAND_CURSOR_INCLUDE_DIR})\n\n  set(WAYLAND_LIBRARIES ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_SERVER_LIBRARIES} ${WAYLAND_EGL_LIBRARIES} ${WAYLAND_CURSOR_LIBRARIES})\n\n  list(REMOVE_DUPLICATES WAYLAND_INCLUDE_DIR)\n\n  include(FindPackageHandleStandardArgs)\n\n  FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_CLIENT  DEFAULT_MSG  WAYLAND_CLIENT_LIBRARIES  WAYLAND_CLIENT_INCLUDE_DIR)\n  FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_SERVER  DEFAULT_MSG  WAYLAND_SERVER_LIBRARIES  WAYLAND_SERVER_INCLUDE_DIR)\n  FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_EGL     DEFAULT_MSG  WAYLAND_EGL_LIBRARIES     WAYLAND_EGL_INCLUDE_DIR)\n  FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_CURSOR  DEFAULT_MSG  WAYLAND_CURSOR_LIBRARIES  WAYLAND_CURSOR_INCLUDE_DIR)\n  FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND         DEFAULT_MSG  WAYLAND_LIBRARIES         WAYLAND_INCLUDE_DIR)\n\n  MARK_AS_ADVANCED(\n        WAYLAND_INCLUDE_DIR         WAYLAND_LIBRARIES\n        WAYLAND_CLIENT_INCLUDE_DIR  WAYLAND_CLIENT_LIBRARIES\n        WAYLAND_SERVER_INCLUDE_DIR  WAYLAND_SERVER_LIBRARIES\n        WAYLAND_EGL_INCLUDE_DIR     WAYLAND_EGL_LIBRARIES\n        WAYLAND_CURSOR_INCLUDE_DIR  WAYLAND_CURSOR_LIBRARIES\n  )\n\nENDIF ()\n"
  },
  {
    "path": "CMakeModules/FindXCB.cmake",
    "content": "# - FindXCB\n#\n# Copyright (C) 2015 Valve Corporation\n\nfind_package(PkgConfig)\n\nif(NOT XCB_FIND_COMPONENTS)\n    set(XCB_FIND_COMPONENTS xcb)\nendif()\n\ninclude(FindPackageHandleStandardArgs)\nset(XCB_FOUND true)\nset(XCB_INCLUDE_DIRS \"\")\nset(XCB_LIBRARIES \"\")\nforeach(comp ${XCB_FIND_COMPONENTS})\n    # component name\n    string(TOUPPER ${comp} compname)\n    string(REPLACE \"-\" \"_\" compname ${compname})\n    # header name\n    string(REPLACE \"xcb-\" \"\" headername xcb/${comp}.h)\n    # library name\n    set(libname ${comp})\n\n    pkg_check_modules(PC_${comp} QUIET ${comp})\n\n    find_path(${compname}_INCLUDE_DIR NAMES ${headername}\n        HINTS\n        ${PC_${comp}_INCLUDEDIR}\n        ${PC_${comp}_INCLUDE_DIRS}\n        )\n\n    find_library(${compname}_LIBRARY NAMES ${libname}\n        HINTS\n        ${PC_${comp}_LIBDIR}\n        ${PC_${comp}_LIBRARY_DIRS}\n        )\n\n    find_package_handle_standard_args(${comp}\n        FOUND_VAR ${comp}_FOUND\n        REQUIRED_VARS ${compname}_INCLUDE_DIR ${compname}_LIBRARY)\n    mark_as_advanced(${compname}_INCLUDE_DIR ${compname}_LIBRARY)\n\n    list(APPEND XCB_INCLUDE_DIRS ${${compname}_INCLUDE_DIR})\n    list(APPEND XCB_LIBRARIES ${${compname}_LIBRARY})\n\n    if(NOT ${comp}_FOUND)\n        set(XCB_FOUND false)\n    endif()\nendforeach()\n\nlist(REMOVE_DUPLICATES XCB_INCLUDE_DIRS)\n"
  },
  {
    "path": "CMakeModules/GetGitRevisionDescription.cmake",
    "content": "# - Returns a version string from Git\n#\n# These functions force a re-configure on each git commit so that you can\n# trust the values of the variables in your build system.\n#\n#  get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])\n#\n# Returns the refspec and sha hash of the current head revision\n#\n#  git_describe(<var> [<additional arguments to git describe> ...])\n#\n# Returns the results of git describe on the source tree, and adjusting\n# the output so that it tests false if an error occurs.\n#\n#  git_get_exact_tag(<var> [<additional arguments to git describe> ...])\n#\n# Returns the results of git describe --exact-match on the source tree,\n# and adjusting the output so that it tests false if there was no exact\n# matching tag.\n#\n# Requires CMake 2.6 or newer (uses the 'function' command)\n#\n# Original Author:\n# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>\n# http://academic.cleardefinition.com\n# Iowa State University HCI Graduate Program/VRAC\n#\n# Copyright Iowa State University 2009-2010.\n# Distributed under the Boost Software License, Version 1.0.\n# (See accompanying file LICENSE_1_0.txt or copy at\n# http://www.boost.org/LICENSE_1_0.txt)\n\nif(__get_git_revision_description)\n\treturn()\nendif()\nset(__get_git_revision_description YES)\n\n# We must run the following at \"include\" time, not at function call time,\n# to find the path to this module rather than the path to a calling list file\nget_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)\n\nfunction(get_git_head_revision _refspecvar _hashvar)\n\tset(GIT_PARENT_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\")\n\tset(GIT_DIR \"${GIT_PARENT_DIR}/.git\")\n\twhile(NOT EXISTS \"${GIT_DIR}\")\t# .git dir not found, search parent directories\n\t\tset(GIT_PREVIOUS_PARENT \"${GIT_PARENT_DIR}\")\n\t\tget_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)\n\t\tif(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)\n\t\t\t# We have reached the root directory, we are not in git\n\t\t\tset(${_refspecvar} \"GITDIR-NOTFOUND\" PARENT_SCOPE)\n\t\t\tset(${_hashvar} \"GITDIR-NOTFOUND\" PARENT_SCOPE)\n\t\t\treturn()\n\t\tendif()\n\t\tset(GIT_DIR \"${GIT_PARENT_DIR}/.git\")\n\tendwhile()\n\t# check if this is a submodule\n\tif(NOT IS_DIRECTORY ${GIT_DIR})\n\t\tfile(READ ${GIT_DIR} submodule)\n\t\tstring(REGEX REPLACE \"gitdir: (.*)\\n$\" \"\\\\1\" GIT_DIR_RELATIVE ${submodule})\n\t\tget_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)\n\t\tget_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)\n\tendif()\n\tset(GIT_DATA \"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data\")\n\tif(NOT EXISTS \"${GIT_DATA}\")\n\t\tfile(MAKE_DIRECTORY \"${GIT_DATA}\")\n\tendif()\n\n\tif(NOT EXISTS \"${GIT_DIR}/HEAD\")\n\t\treturn()\n\tendif()\n\tset(HEAD_FILE \"${GIT_DATA}/HEAD\")\n\tconfigure_file(\"${GIT_DIR}/HEAD\" \"${HEAD_FILE}\" COPYONLY)\n\n\tconfigure_file(\"${_gitdescmoddir}/GetGitRevisionDescription.cmake.in\"\n\t\t\"${GIT_DATA}/grabRef.cmake\"\n\t\t@ONLY)\n\tinclude(\"${GIT_DATA}/grabRef.cmake\")\n\n\tset(${_refspecvar} \"${HEAD_REF}\" PARENT_SCOPE)\n\tset(${_hashvar} \"${HEAD_HASH}\" PARENT_SCOPE)\nendfunction()\n\nfunction(git_branch_name _var)\n\tif(NOT GIT_FOUND)\n\t\tfind_package(Git QUIET)\n\tendif()\n\n\tif(NOT GIT_FOUND)\n\t\tset(${_var} \"GIT-NOTFOUND\" PARENT_SCOPE)\n\t\treturn()\n\tendif()\n\n\texecute_process(COMMAND\n\t\t\"${GIT_EXECUTABLE}\"\n\t\trev-parse --abbrev-ref HEAD\n\t\tWORKING_DIRECTORY\n\t\t\"${CMAKE_SOURCE_DIR}\"\n\t\tRESULT_VARIABLE\n\t\tres\n\t\tOUTPUT_VARIABLE\n\t\tout\n\t\tERROR_QUIET\n\t\tOUTPUT_STRIP_TRAILING_WHITESPACE)\n\tif(NOT res EQUAL 0)\n\t\tset(out \"${out}-${res}-NOTFOUND\")\n\tendif()\n\n\tset(${_var} \"${out}\" PARENT_SCOPE)\nendfunction()\n\nfunction(git_describe _var)\n\tif(NOT GIT_FOUND)\n\t\tfind_package(Git QUIET)\n\tendif()\n\t#get_git_head_revision(refspec hash)\n\tif(NOT GIT_FOUND)\n\t\tset(${_var} \"GIT-NOTFOUND\" PARENT_SCOPE)\n\t\treturn()\n\tendif()\n\t#if(NOT hash)\n\t#\tset(${_var} \"HEAD-HASH-NOTFOUND\" PARENT_SCOPE)\n\t#\treturn()\n\t#endif()\n\n\t# TODO sanitize\n\t#if((${ARGN}\" MATCHES \"&&\") OR\n\t#\t(ARGN MATCHES \"||\") OR\n\t#\t(ARGN MATCHES \"\\\\;\"))\n\t#\tmessage(\"Please report the following error to the project!\")\n\t#\tmessage(FATAL_ERROR \"Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}\")\n\t#endif()\n\n\t#message(STATUS \"Arguments to execute_process: ${ARGN}\")\n\n\texecute_process(COMMAND\n\t\t\"${GIT_EXECUTABLE}\"\n\t\tdescribe\n\t\t${hash}\n\t\t${ARGN}\n\t\tWORKING_DIRECTORY\n\t\t\"${CMAKE_SOURCE_DIR}\"\n\t\tRESULT_VARIABLE\n\t\tres\n\t\tOUTPUT_VARIABLE\n\t\tout\n\t\tERROR_QUIET\n\t\tOUTPUT_STRIP_TRAILING_WHITESPACE)\n\tif(NOT res EQUAL 0)\n\t\tset(out \"${out}-${res}-NOTFOUND\")\n\tendif()\n\n\tset(${_var} \"${out}\" PARENT_SCOPE)\nendfunction()\n\nfunction(git_get_exact_tag _var)\n\tgit_describe(out --exact-match ${ARGN})\n\tset(${_var} \"${out}\" PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "CMakeModules/GetGitRevisionDescription.cmake.in",
    "content": "# \n# Internal file for GetGitRevisionDescription.cmake\n#\n# Requires CMake 2.6 or newer (uses the 'function' command)\n#\n# Original Author:\n# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>\n# http://academic.cleardefinition.com\n# Iowa State University HCI Graduate Program/VRAC\n#\n# Copyright Iowa State University 2009-2010.\n# Distributed under the Boost Software License, Version 1.0.\n# (See accompanying file LICENSE_1_0.txt or copy at\n# http://www.boost.org/LICENSE_1_0.txt)\n\nset(HEAD_HASH)\n\nfile(READ \"@HEAD_FILE@\" HEAD_CONTENTS LIMIT 1024)\n\nstring(STRIP \"${HEAD_CONTENTS}\" HEAD_CONTENTS)\nif(HEAD_CONTENTS MATCHES \"ref\")\n\t# named branch\n\tstring(REPLACE \"ref: \" \"\" HEAD_REF \"${HEAD_CONTENTS}\")\n\tif(EXISTS \"@GIT_DIR@/${HEAD_REF}\")\n\t\tconfigure_file(\"@GIT_DIR@/${HEAD_REF}\" \"@GIT_DATA@/head-ref\" COPYONLY)\n\telseif(EXISTS \"@GIT_DIR@/logs/${HEAD_REF}\")\n\t\tconfigure_file(\"@GIT_DIR@/logs/${HEAD_REF}\" \"@GIT_DATA@/head-ref\" COPYONLY)\n\t\tset(HEAD_HASH \"${HEAD_REF}\")\n\tendif()\nelse()\n\t# detached HEAD\n\tconfigure_file(\"@GIT_DIR@/HEAD\" \"@GIT_DATA@/head-ref\" COPYONLY)\nendif()\n\nif(NOT HEAD_HASH)\n\tif(EXISTS \"@GIT_DATA@/head-ref\")\n\t\tfile(READ \"@GIT_DATA@/head-ref\" HEAD_HASH LIMIT 1024)\n\t\tstring(STRIP \"${HEAD_HASH}\" HEAD_HASH)\n\telse()\n\t\tset(HEAD_HASH \"Unknown\")\n\tendif()\nendif()\n"
  },
  {
    "path": "CMakePresets.json",
    "content": "{\n  \"version\": 3,\n  \"cmakeMinimumRequired\": {\n    \"major\": 3,\n    \"minor\": 21,\n    \"patch\": 0\n  },\n  \"configurePresets\": [\n    {\n      \"name\": \"vcpkg\",\n      \"displayName\": \"vcpkg build\",\n      \"description\": \"Use vcpkg for dependencies\",\n      \"toolchainFile\": \"${sourceDir}/libraries/vcpkg/scripts/buildsystems/vcpkg.cmake\",\n      \"condition\": {\n        \"type\": \"equals\",\n        \"lhs\": \"${hostSystemName}\",\n        \"rhs\": \"Windows\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "LICENSE.md",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {one line to give the program's name and a brief idea of what it does.}\n    Copyright (C) {year}  {name of author}\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    {project}  Copyright (C) {year}  {fullname}\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "[![Build status](https://github.com/decaf-emu/decaf-emu/workflows/C%2FC%2B%2B%20CI/badge.svg)](https://github.com/decaf-emu/decaf-emu/actions?workflow=C%2FC%2B%2B+CI)\n\n# decaf-emu\nResearching Wii U emulation. Licensed under the terms of the GNU General Public License, version 3 or later (GPLv3+).\n\nYou can find us for developer discussion:\n- on discord using https://discord.gg/tPqFBnr\n\n<p float=\"left\">\n  <img src=\"https://user-images.githubusercontent.com/1302758/147675484-c0308d89-55a9-4927-8665-1826ee5d4771.png\" width=\"250\" />\n  <img src=\"https://user-images.githubusercontent.com/1302758/147674695-d8baf6ac-87e2-487c-8358-ef1588c5e5bf.png\" width=\"250\" />\n  <img src=\"https://user-images.githubusercontent.com/1302758/147674704-17767241-e0b4-497e-8841-aa968d14c8e3.png\" width=\"250\" />\n</p>\n\n## Support\nNone, this is an in-development project and user support is not provided.\n\n## Building from Source\nSee [BUILDING.md](BUILDING.md)\n\n## Binaries\nThe latest Windows and Linux binaries are available via [Actions artifacts](https://github.com/decaf-emu/decaf-emu/actions?query=branch%3Amaster+is%3Asuccess). You must be logged into GitHub in order to download the artifacts.\n\nMacOS builds are currently not provided due to complications with Vulkan.\n\n## Running\nRun the `decaf-qt` executable, it is recommended to run the emulator from the root git directory so that it is able to access `resources/fonts/*`.  Alternatively, set `resources_path` in the configuration file to point to the resources directory.\n\nConfiguration files can be found at:\n- Windows - `%APPDATA%\\decaf`\n- Linux - `~/.config/decaf`\n\nOn Linux, a \"Bus error\" crash usually indicates an out-of-space error in the temporary directory.  Set the `TMPDIR` environment variable to a directory on a filesystem with at least 2GB free.\n\nAdditionally there is an SDL command line application which can be used by `./decaf-sdl play <path to game>`\n\n"
  },
  {
    "path": "libraries/CMakeLists.txt",
    "content": "# addrlib\nadd_library(addrlib STATIC\n    \"addrlib/src/addrinterface.cpp\"\n    \"addrlib/src/core/addrelemlib.cpp\"\n    \"addrlib/src/core/addrlib.cpp\"\n    \"addrlib/src/core/addrobject.cpp\"\n    \"addrlib/src/r600/r600addrlib.cpp\")\nset_target_properties(addrlib PROPERTIES FOLDER libraries)\ntarget_include_directories(addrlib\n   PRIVATE \"addrlib/src\"\n   PUBLIC \"addrlib/include\")\n\n# libbinrec\nset(BINREC_ENABLE_RTL_DEBUG_OPTIMIZE FALSE CACHE BOOL \"Enable debug output from optimization passes\")\nset(BINREC_ENABLE_ASSERT FALSE CACHE BOOL \"Enable basic assertion checks\")\nadd_subdirectory(libbinrec)\nset_target_properties(binrec PROPERTIES FOLDER libraries)\n\n# catch\nadd_library(catch2 INTERFACE IMPORTED GLOBAL)\nset_target_properties(catch2 PROPERTIES\n    INTERFACE_COMPILE_DEFINITIONS \"CATCH_CONFIG_ENABLE_BENCHMARKING\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${CMAKE_CURRENT_SOURCE_DIR}/catch/single_include/catch2\")\n\n# cereal\nadd_library(cereal INTERFACE IMPORTED GLOBAL)\nset_target_properties(cereal PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${CMAKE_CURRENT_SOURCE_DIR}/cereal/include\")\n\n# cnl\nadd_library(cnl INTERFACE IMPORTED GLOBAL)\nset_target_properties(cnl PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${CMAKE_CURRENT_SOURCE_DIR}/cnl/include\")\n\n# excmd\nadd_library(excmd INTERFACE IMPORTED GLOBAL)\nset_target_properties(excmd PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${CMAKE_CURRENT_SOURCE_DIR}/excmd/src\")\n\n# fmt\nadd_subdirectory(fmt)\nset_target_properties(fmt PROPERTIES FOLDER libraries)\n\n# glslang\nif(DECAF_VULKAN OR DECAF_BUILD_TOOLS)\n    set(BUILD_SHARED_LIBS OFF CACHE BOOL \"glslang: BUILD_SHARED_LIBS\" FORCE)\n    set(BUILD_TESTING OFF CACHE BOOL \"glslang: BUILD_TESTING\" FORCE)\n    set(ENABLE_HLSL OFF CACHE BOOL \"glslang: ENABLE_HLSL\" FORCE)\n    set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL \"glslang: ENABLE_GLSLANG_BINARIES\" FORCE)\n    set(ENABLE_OPT ON CACHE BOOL \"glslang: ENABLE_OPT\" FORCE)\n    set(ENABLE_SPVREMAPPER OFF CACHE BOOL \"glslang: ENABLE_SPVREMAPPER\" FORCE)\n    set(SKIP_GLSLANG_INSTALL ON CACHE BOOL \"glslang: SKIP_GLSLANG_INSTALL\" FORCE)\n    add_subdirectory(glslang)\n\n    macro(remove_flag_from_target _target _flag)\n        get_target_property(_target_cxx_flags ${_target} COMPILE_OPTIONS)\n        if(_target_cxx_flags)\n            list(REMOVE_ITEM _target_cxx_flags ${_flag})\n            set_target_properties(${_target} PROPERTIES COMPILE_OPTIONS \"${_target_cxx_flags}\")\n        endif()\n    endmacro()\n\n    # As we inherit spv::Builder in libgpu, we must remove -fno-rtti\n    remove_flag_from_target(SPIRV -fno-rtti)\nendif()\n\n# gsl\nadd_library(gsl INTERFACE IMPORTED GLOBAL)\nset_target_properties(gsl PROPERTIES\n    INTERFACE_COMPILE_DEFINITIONS \"GSL_THROWS_FOR_TESTING\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${CMAKE_CURRENT_SOURCE_DIR}/gsl-lite/include\")\n\n# imgui\nadd_library(imgui STATIC\n    \"imgui/imgui.cpp\"\n    \"imgui/imgui_draw.cpp\")\nset_target_properties(imgui PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${CMAKE_CURRENT_SOURCE_DIR}/imgui\"\n    FOLDER libraries)\n\n# ovsocket\nadd_library(ovsocket INTERFACE IMPORTED GLOBAL)\nset_target_properties(ovsocket PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${CMAKE_CURRENT_SOURCE_DIR}/ovsocket/ovsocket\")\n\n# peglib\nadd_library(peglib INTERFACE IMPORTED GLOBAL)\nset_target_properties(peglib PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${CMAKE_CURRENT_SOURCE_DIR}/cpp-peglib\")\n\n# pugixml\nadd_library(pugixml STATIC\n    \"pugixml/src/pugixml.cpp\")\nset_target_properties(pugixml PROPERTIES\n    FOLDER libraries\n    INTERFACE_INCLUDE_DIRECTORIES \"${CMAKE_CURRENT_SOURCE_DIR}/pugixml/src\")\n\n# spdlog\nset(SPDLOG_FMT_EXTERNAL ON CACHE BOOL \"Use external fmt library instead of bundled\" FORCE)\nadd_subdirectory(spdlog)\nset_target_properties(spdlog PROPERTIES FOLDER libraries)\n\n# tomlplusplus\nadd_library(tomlplusplus INTERFACE IMPORTED GLOBAL)\nset_target_properties(tomlplusplus PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${CMAKE_CURRENT_SOURCE_DIR}/tomlplusplus/include\")\n\n# Qt Advanced Docking System\nif(DECAF_QT)\n    set(BUILD_STATIC TRUE CACHE BOOL \"ADS: Build the static library\")\n    set(BUILD_EXAMPLES FALSE CACHE BOOL \"ADS: Build the examples\")\n    set(ADS_VERSION \"3.8.1\" CACHE STRING \"ADS: Version\")\n    add_subdirectory(qtads EXCLUDE_FROM_ALL)\n    set_target_properties(qtadvanceddocking PROPERTIES\n        INTERFACE_COMPILE_DEFINITIONS \"ADS_STATIC\"\n        FOLDER libraries)\nendif()\n"
  },
  {
    "path": "libraries/bin2c.cmake",
    "content": "# https://github.com/wjakob/nanogui/blob/f9c3b7a/resources/bin2c.cmake\n# Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>, All rights reserved.\n# BSD-3-Clause-LBNL (https://github.com/wjakob/nanogui/blob/f9c3b7a/LICENSE.txt)\n\ncmake_minimum_required (VERSION 2.8.12)\n\n# Create header for C file\nfile(WRITE ${OUTPUT_C} \"/* Autogenerated by bin2c */\\n\\n\")\nfile(APPEND ${OUTPUT_C} \"#include <stdint.h>\\n\\n\")\n\n# Create header of H file\nfile(WRITE ${OUTPUT_H} \"/* Autogenerated by bin2c */\\n\\n\")\nfile(APPEND ${OUTPUT_H} \"#pragma once\\n\")\nfile(APPEND ${OUTPUT_H} \"#include <stdint.h>\\n\\n\")\n\nstring(REPLACE \",\" \";\" INPUT_LIST ${INPUT_FILES})\n\n\n# Iterate through binary files files\nforeach(bin ${INPUT_LIST})\n  # Get short filename\n  string(REGEX MATCH \"([^/]+)$\" filename ${bin})\n  # Replace filename spaces & extension separator for C compatibility\n  string(REGEX REPLACE \"\\\\.| |-\" \"_\" filename ${filename})\n  # Convert to lower case\n  string(TOLOWER ${filename} filename)\n  # Read hex data from file\n  file(READ ${bin} filedata HEX)\n  # Convert hex data for C compatibility\n  string(REGEX REPLACE \"([0-9a-f][0-9a-f])\" \"0x\\\\1,\" filedata ${filedata})\n  # Append data to c file\n  file(APPEND ${OUTPUT_C} \"uint8_t ${filename}[] = {${filedata}};\\n\\nuint32_t ${filename}_size = sizeof(${filename});\\n\\n\")\n  # Append extern definitions to h file\n  file(APPEND ${OUTPUT_H} \"extern uint8_t ${filename}[];\\n\\nextern uint32_t ${filename}_size;\\n\\n\")\nendforeach()"
  },
  {
    "path": "resources/CMakeLists.txt",
    "content": "project(resources)\n\nset(Fonts\n   fonts/CafeCn.ttf\n   fonts/CafeKr.ttf\n   fonts/CafeStd.ttf\n   fonts/CafeTw.ttf\n   fonts/DejaVuSansMono.ttf)\nset(Docs\n    fonts/DejaVuSansMono.LICENSE\n    fonts/NotoSansCJK.LICENSE\n    ../README.md\n    ../LICENSE.md)\nset(ResourceFiles ${Fonts})\n\nadd_custom_target(resources ALL SOURCES ${ResourceFiles})\n\nforeach(ResourceFile ${ResourceFiles})\n  add_custom_command(TARGET resources PRE_BUILD\n                     COMMAND ${CMAKE_COMMAND} -E\n                     copy ${CMAKE_SOURCE_DIR}/resources/${ResourceFile}\n                          $<TARGET_FILE_DIR:libdecaf>/resources/${ResourceFile})\nendforeach()\n\ninstall(FILES ${Fonts} DESTINATION \"${DECAF_INSTALL_RESOURCESDIR}/fonts\")\ninstall(FILES ${Docs} DESTINATION \"${DECAF_INSTALL_DOCSDIR}\")\n"
  },
  {
    "path": "resources/fonts/DejaVuSansMono.LICENSE",
    "content": "Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.\nGlyphs imported from Arev fonts are (c) Tavmjong Bah (see below)\n\n\nBitstream Vera Fonts Copyright\n------------------------------\n\nCopyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is\na trademark of Bitstream, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof the fonts accompanying this license (\"Fonts\") and associated\ndocumentation files (the \"Font Software\"), to reproduce and distribute the\nFont Software, including without limitation the rights to use, copy, merge,\npublish, distribute, and/or sell copies of the Font Software, and to permit\npersons to whom the Font Software is furnished to do so, subject to the\nfollowing conditions:\n\nThe above copyright and trademark notices and this permission notice shall\nbe included in all copies of one or more of the Font Software typefaces.\n\nThe Font Software may be modified, altered, or added to, and in particular\nthe designs of glyphs or characters in the Fonts may be modified and\nadditional glyphs or characters may be added to the Fonts, only if the fonts\nare renamed to names not containing either the words \"Bitstream\" or the word\n\"Vera\".\n\nThis License becomes null and void to the extent applicable to Fonts or Font\nSoftware that has been modified and is distributed under the \"Bitstream\nVera\" names.\n\nThe Font Software may be sold as part of a larger software package but no\ncopy of one or more of the Font Software typefaces may be sold by itself.\n\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,\nTRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME\nFOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING\nANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF\nTHE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE\nFONT SOFTWARE.\n\nExcept as contained in this notice, the names of Gnome, the Gnome\nFoundation, and Bitstream Inc., shall not be used in advertising or\notherwise to promote the sale, use or other dealings in this Font Software\nwithout prior written authorization from the Gnome Foundation or Bitstream\nInc., respectively. For further information, contact: fonts at gnome dot\norg.\n\nArev Fonts Copyright\n------------------------------\n\nCopyright (c) 2006 by Tavmjong Bah. All Rights Reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the fonts accompanying this license (\"Fonts\") and\nassociated documentation files (the \"Font Software\"), to reproduce\nand distribute the modifications to the Bitstream Vera Font Software,\nincluding without limitation the rights to use, copy, merge, publish,\ndistribute, and/or sell copies of the Font Software, and to permit\npersons to whom the Font Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright and trademark notices and this permission notice\nshall be included in all copies of one or more of the Font Software\ntypefaces.\n\nThe Font Software may be modified, altered, or added to, and in\nparticular the designs of glyphs or characters in the Fonts may be\nmodified and additional glyphs or characters may be added to the\nFonts, only if the fonts are renamed to names not containing either\nthe words \"Tavmjong Bah\" or the word \"Arev\".\n\nThis License becomes null and void to the extent applicable to Fonts\nor Font Software that has been modified and is distributed under the \n\"Tavmjong Bah Arev\" names.\n\nThe Font Software may be sold as part of a larger software package but\nno copy of one or more of the Font Software typefaces may be sold by\nitself.\n\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL\nTAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n\nExcept as contained in this notice, the name of Tavmjong Bah shall not\nbe used in advertising or otherwise to promote the sale, use or other\ndealings in this Font Software without prior written authorization\nfrom Tavmjong Bah. For further information, contact: tavmjong @ free\n. fr.\n\nTeX Gyre DJV Math\n-----------------\nFonts are (c) Bitstream (see below). DejaVu changes are in public domain.\n\nMath extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski\n(on behalf of TeX users groups) are in public domain.\n\nLetters imported from Euler Fraktur from AMSfonts are (c) American\nMathematical Society (see below).\nBitstream Vera Fonts Copyright\nCopyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera\nis a trademark of Bitstream, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof the fonts accompanying this license (“Fonts”) and associated\ndocumentation\nfiles (the “Font Software”), to reproduce and distribute the Font Software,\nincluding without limitation the rights to use, copy, merge, publish,\ndistribute,\nand/or sell copies of the Font Software, and to permit persons  to whom\nthe Font Software is furnished to do so, subject to the following\nconditions:\n\nThe above copyright and trademark notices and this permission notice\nshall be\nincluded in all copies of one or more of the Font Software typefaces.\n\nThe Font Software may be modified, altered, or added to, and in particular\nthe designs of glyphs or characters in the Fonts may be modified and\nadditional\nglyphs or characters may be added to the Fonts, only if the fonts are\nrenamed\nto names not containing either the words “Bitstream” or the word “Vera”.\n\nThis License becomes null and void to the extent applicable to Fonts or\nFont Software\nthat has been modified and is distributed under the “Bitstream Vera”\nnames.\n\nThe Font Software may be sold as part of a larger software package but\nno copy\nof one or more of the Font Software typefaces may be sold by itself.\n\nTHE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,\nTRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME\nFOUNDATION\nBE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,\nSPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN\nACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR\nINABILITY TO USE\nTHE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.\nExcept as contained in this notice, the names of GNOME, the GNOME\nFoundation,\nand Bitstream Inc., shall not be used in advertising or otherwise to promote\nthe sale, use or other dealings in this Font Software without prior written\nauthorization from the GNOME Foundation or Bitstream Inc., respectively.\nFor further information, contact: fonts at gnome dot org.\n\nAMSFonts (v. 2.2) copyright\n\nThe PostScript Type 1 implementation of the AMSFonts produced by and\npreviously distributed by Blue Sky Research and Y&Y, Inc. are now freely\navailable for general use. This has been accomplished through the\ncooperation\nof a consortium of scientific publishers with Blue Sky Research and Y&Y.\nMembers of this consortium include:\n\nElsevier Science IBM Corporation Society for Industrial and Applied\nMathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)\n\nIn order to assure the authenticity of these fonts, copyright will be\nheld by\nthe American Mathematical Society. This is not meant to restrict in any way\nthe legitimate use of the fonts, such as (but not limited to) electronic\ndistribution of documents containing these fonts, inclusion of these fonts\ninto other public domain or commercial font collections or computer\napplications, use of the outline data to create derivative fonts and/or\nfaces, etc. However, the AMS does require that the AMS copyright notice be\nremoved from any derivative versions of the fonts which have been altered in\nany way. In addition, to ensure the fidelity of TeX documents using Computer\nModern fonts, Professor Donald Knuth, creator of the Computer Modern faces,\nhas requested that any alterations which yield different font metrics be\ngiven a different name.\n\n$Id$\n"
  },
  {
    "path": "resources/fonts/NotoSansCJK.LICENSE",
    "content": "Copyright (c) <dates>, <Copyright Holder> (<URL|email>),\nwith Reserved Font Name <Reserved Font Name>.\nCopyright (c) <dates>, <additional Copyright Holder> (<URL|email>),\nwith Reserved Font Name <additional Reserved Font Name>.\nCopyright (c) <dates>, <additional Copyright Holder> (<URL|email>).\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at:\nhttp://scripts.sil.org/OFL\n\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded, \nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to, deleting,\nor substituting -- in part or in whole -- any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "resources/hidpi.manifest",
    "content": "<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\" xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\" >\n<asmv3:application>\n  <asmv3:windowsSettings>\n    <dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true/pm</dpiAware> <!-- legacy -->\n    <dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">permonitorv2,permonitor</dpiAwareness> <!-- falls back to pm if pmv2 is not available -->\n  </asmv3:windowsSettings>\n</asmv3:application>\n</assembly>\n"
  },
  {
    "path": "src/CMakeLists.txt",
    "content": "include_directories(\".\")\n\nadd_subdirectory(common)\nadd_subdirectory(libconfig)\nadd_subdirectory(libcpu)\nadd_subdirectory(libdecaf)\nadd_subdirectory(libgfd)\nadd_subdirectory(libgpu)\n\nadd_subdirectory(decaf-cli)\nadd_subdirectory(decaf-sdl)\n\nif(DECAF_QT)\n    add_subdirectory(decaf-qt)\nendif()\n"
  },
  {
    "path": "src/common/CMakeLists.txt",
    "content": "project(common)\n\ninclude_directories(\".\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h *.inl)\n\nadd_library(common STATIC ${SOURCE_FILES} ${HEADER_FILES})\nGroupSources(\"Source Files\" src)\n\ntarget_link_libraries(common\n    cnl\n    fmt::fmt\n    gsl\n    spdlog::spdlog)\n\nif(MSVC)\n    target_link_libraries(common Dbghelp)\nelseif(UNIX AND NOT APPLE)\n    target_link_libraries(common rt)\nendif()\n\nif(DECAF_PCH)\n    target_precompile_headers(common\n      PRIVATE\n        \"pch.h\"\n    )\n\n    AutoGroupPCHFiles()\nendif()\n"
  },
  {
    "path": "src/common/align.h",
    "content": "#pragma once\n#include <cstddef>\n\ntemplate<typename Type>\nconstexpr inline Type\nalign_up(Type value, size_t alignment)\n{\n   return static_cast<Type>((static_cast<size_t>(value) + (alignment - 1)) & ~(alignment - 1));\n}\n\ntemplate<typename Type>\nconstexpr inline Type\nalign_down(Type value, size_t alignment)\n{\n   return static_cast<Type>(static_cast<size_t>(value) & ~(alignment - 1));\n}\n\ntemplate<typename Type>\nconstexpr inline Type *\nalign_up(Type *value, size_t alignment)\n{\n   return reinterpret_cast<Type*>((reinterpret_cast<size_t>(value) + (alignment - 1)) & ~(alignment - 1));\n}\n\ntemplate<typename Type>\nconstexpr inline Type *\nalign_down(Type *value, size_t alignment)\n{\n   return reinterpret_cast<Type*>(reinterpret_cast<size_t>(value) & ~(alignment - 1));\n}\n\ntemplate<typename Type>\nconstexpr bool\nalign_check(Type *value, size_t alignment)\n{\n   return (reinterpret_cast<size_t>(value) & (alignment - 1)) == 0;\n}\n\ntemplate<typename Type>\nconstexpr bool\nalign_check(Type value, size_t alignment)\n{\n   return (static_cast<size_t>(value) & (alignment - 1)) == 0;\n}\n"
  },
  {
    "path": "src/common/atomicqueue.h",
    "content": "#pragma once\n#include <atomic>\n\n/**\n * Multi-producer multi-consumer queue.\n * This is unsafe and full of assumptions, should be very careful about using it.\n *\n * This is not a safe queue, it does not check if the queue is full before\n * pushing, or if it is empty before popping.\n *\n * Size must be a power of two so you don't need to be careful for wrapping of\n * read / write position.\n *\n * Empty when writePos = readPos.\n * Full when writePos + 1 == readPos.\n */\ntemplate<typename Type, std::size_t Size>\nclass alignas(64) AtomicQueue\n{\n   static_assert(Size && ((Size & (Size - 1)) == 0), \"N must be a power of two\");\n\npublic:\n   constexpr std::size_t capacity() const\n   {\n      return Size - 1;\n   }\n\n   bool wasFull() const\n   {\n      return (mWritePosition.load() + 1) == mReadPosition.load();\n   }\n\n   bool wasEmpty() const\n   {\n      return mWritePosition.load() == mReadPosition.load();\n   }\n\n   void push(Type value)\n   {\n      auto writePos = mWritePosition.fetch_add(1);\n      mBuffer[writePos % Size] = value;\n   }\n\n   Type pop()\n   {\n      auto readPos = mReadPosition.fetch_add(1);\n      return mBuffer[readPos % Size];\n   }\n\nprivate:\n   alignas(64) std::atomic<std::size_t> mWritePosition = 0;\n   Type mBuffer[Size];\n   alignas(64) std::atomic<std::size_t> mReadPosition = 0;\n};\n\n/**\n * Single producer, single consumer queue.\n * Much safer than MultiAtomicQueue, preferred when only one thread is writing\n * and one thread is reading.\n *\n * Safe, can detect whether queue is full on push, or empty on pop.\n *\n * Does not require size to be power of two.\n *\n * Empty when read == write\n * Full when (write + 1) == read\n */\ntemplate<typename Type, std::size_t Size>\nclass SingleAtomicQueue\n{\npublic:\n   constexpr std::size_t capacity() const\n   {\n      return Size - 1;\n   }\n\n   bool push(Type value)\n   {\n      const auto writePos = mWritePosition.load(std::memory_order_relaxed);\n      const auto nextWritePos = (writePos + 1) % Size;\n\n      if (nextWritePos == mReadPosition.load(std::memory_order_acquire)) {\n         // Queue is full!\n         return false;\n      }\n\n      mBuffer[writePos] = value;\n      mWritePosition.store(writePos, std::memory_order_release);\n      return true;\n   }\n\n   bool pop(Type &value)\n   {\n      const auto readPos = mReadPosition.load(std::memory_order_relaxed);\n      if (readPos == mWritePosition.load(std::memory_order_acquire)) {\n         // Queue is empty!\n         return false;\n      }\n\n      value = mBuffer[readPos];\n      mReadPosition.store((readPos + 1) % Size, std::memory_order_release);\n      return true;\n   }\n\nprivate:\n   alignas(64) std::atomic<std::size_t> mWritePosition = 0;\n   Type mBuffer[Size];\n   alignas(64) std::atomic<std::size_t> mReadPosition = 0;\n};\n"
  },
  {
    "path": "src/common/bit_cast.h",
    "content": "#pragma once\n#include <cstring>\n#include <memory>\n#include <type_traits>\n\n// reinterpret_cast for value types\ntemplate<typename DstType, typename SrcType>\ninline DstType\nbit_cast(const SrcType& src)\n{\n   static_assert(sizeof(SrcType) == sizeof(DstType), \"bit_cast must be between same sized types\");\n   static_assert(std::is_trivially_copyable<SrcType>::value, \"SrcType is not trivially copyable.\");\n   static_assert(std::is_trivially_copyable<DstType>::value, \"DstType is not trivially copyable.\");\n\n   DstType dst;\n   std::memcpy(std::addressof(dst), std::addressof(src), sizeof(SrcType));\n   return dst;\n}\n"
  },
  {
    "path": "src/common/bitfield.h",
    "content": "#pragma once\n#include \"bit_cast.h\"\n#include \"bitutils.h\"\n#include \"decaf_assert.h\"\n\n#include <common/type_traits.h>\n#include <fmt/core.h>\n\ntemplate<typename BitfieldType, typename ValueType, unsigned Position, unsigned Bits>\nstruct BitfieldHelper\n{\n   using underlying_type = typename safe_underlying_type<ValueType>::type;\n   using unsigned_underlying_type = typename std::make_unsigned<underlying_type>::type;\n   static const auto RelativeMask = static_cast<unsigned_underlying_type>((1ull << (Bits)) - 1);\n   static const auto AbsoluteMask = static_cast<typename BitfieldType::StorageType>(RelativeMask) << (Position);\n\n   static inline ValueType get(BitfieldType bitfield)\n   {\n      auto value = static_cast<unsigned_underlying_type>((bitfield.value & AbsoluteMask) >> (Position));\n\n      if (std::is_signed<underlying_type>::value) {\n         value = sign_extend<Bits>(value);\n      }\n\n      return bit_cast<ValueType>(value);\n   }\n\n   static inline BitfieldType set(BitfieldType bitfield,\n                                  ValueType value)\n   {\n      auto uValue = bit_cast<unsigned_underlying_type>(value);\n\n      if (std::is_signed<underlying_type>::value) {\n         uValue &= RelativeMask;\n      }\n\n      decaf_assert(uValue <= RelativeMask, fmt::format(\"{} <= {}\", uValue, static_cast<unsigned>(RelativeMask)));\n      bitfield.value &= ~AbsoluteMask;\n      bitfield.value |= static_cast<typename BitfieldType::StorageType>(uValue) << (Position);\n      return bitfield;\n   }\n};\n\n// Specialise for float because of errors using make_unsigned on float type\ntemplate<typename BitfieldType, unsigned Position, unsigned Bits>\nstruct BitfieldHelper<BitfieldType, float, Position, Bits>\n{\n   using ValueBitfield = BitfieldHelper<BitfieldType, uint32_t, Position, Bits>;\n\n   static float get(BitfieldType bitfield)\n   {\n      return bit_cast<float>(ValueBitfield::get(bitfield));\n   }\n\n   static inline BitfieldType set(BitfieldType bitfield, float floatValue)\n   {\n      return ValueBitfield::set(bitfield, bit_cast<uint32_t>(floatValue));\n   }\n};\n\n// Specialise for bool because of compiler warnings for static_cast<bool>(int)\ntemplate<typename BitfieldType, unsigned Position, unsigned Bits>\nstruct BitfieldHelper<BitfieldType, bool, Position, Bits>\n{\n   static const auto AbsoluteMask = (static_cast<typename BitfieldType::StorageType>((1ull << (Bits)) - 1)) << (Position);\n\n   static constexpr bool get(BitfieldType bitfield)\n   {\n      return !!(bitfield.value & AbsoluteMask);\n   }\n\n   static inline BitfieldType set(BitfieldType bitfield, bool value)\n   {\n      bitfield.value &= ~AbsoluteMask;\n      bitfield.value |= (static_cast<typename BitfieldType::StorageType>(value ? 1 : 0)) << (Position);\n      return bitfield;\n   }\n};\n\n#ifndef DECAF_USE_STDLAYOUT_BITFIELD\n\n#define BITFIELD_BEG(Name, Type)                                                  \\\n   union Name                                                                 \\\n   {                                                                          \\\n      using BitfieldType = Name;                                              \\\n      using StorageType = Type;                                               \\\n      Type value;                                                             \\\n      explicit operator StorageType() { return value; }                       \\\n      static inline Name get(Type v) {                                        \\\n         Name bitfield;                                                       \\\n         bitfield.value = v;                                                  \\\n         return bitfield;                                                     \\\n      }\n\n#define BITFIELD_ENTRY(Pos, Size, ValueType, Name)                            \\\n   private: struct { StorageType : Pos; StorageType _##Name : Size; };        \\\n   public: inline ValueType Name() const {                                    \\\n      return BitfieldHelper<BitfieldType, ValueType, Pos, Size>::get(*this);  \\\n   }                                                                          \\\n   inline BitfieldType Name(ValueType fieldValue) const {                     \\\n      return BitfieldHelper<BitfieldType, ValueType, Pos, Size>               \\\n            ::set(*this, fieldValue);                                         \\\n   }\n\n#else\n\n#define BITFIELD_BEG(Name, Type)                                                  \\\n   union Name                                                                 \\\n   {                                                                          \\\n      using BitfieldType = Name;                                              \\\n      using StorageType = Type;                                               \\\n      Type value;                                                             \\\n      explicit operator StorageType() { return value; }                       \\\n      static inline Name get(Type v) {                                        \\\n         Name bitfield;                                                       \\\n         bitfield.value = v;                                                  \\\n         return bitfield;                                                     \\\n      }\n\n#define BITFIELD_ENTRY(Pos, Size, ValueType, Name)                            \\\n   inline ValueType Name() const {                                            \\\n      return BitfieldHelper<BitfieldType, ValueType, Pos, Size>::get(*this);  \\\n   }                                                                          \\\n   inline BitfieldType Name(ValueType fieldValue) const {                     \\\n      return BitfieldHelper<BitfieldType, ValueType, Pos, Size>               \\\n         ::set(*this, fieldValue);                                            \\\n   }\n\n#endif\n\n#define BITFIELD_END                                                          \\\n   };\n"
  },
  {
    "path": "src/common/bitutils.h",
    "content": "#pragma once\n#include \"platform.h\"\n#include <climits>\n#include <cstdint>\n#include <cstddef>\n\n#ifdef PLATFORM_WINDOWS\n#include <intrin.h>\n#endif\n\n// Gets the value of a bit\ntemplate<typename Type>\nconstexpr Type\nget_bit(Type src, unsigned bit)\n{\n   return (src >> bit) & static_cast<Type>(1);\n}\n\ntemplate<unsigned bit, typename Type>\nconstexpr Type\nget_bit(Type src)\n{\n   return (src >> (bit)) & static_cast<Type>(1);\n}\n\n// Sets the value of a bit to 1\ntemplate<typename Type>\nconstexpr Type\nset_bit(Type src, unsigned bit)\n{\n   return src | (static_cast<Type>(1) << bit);\n}\n\ntemplate<unsigned bit, typename Type>\nconstexpr Type\nset_bit(Type src)\n{\n   return src | (static_cast<Type>(1) << (bit));\n}\n\n// Flips the value of a bit to 1\ntemplate<typename Type>\nconstexpr Type\nflip_bit(Type src, unsigned bit)\n{\n   return src ^ (static_cast<Type>(1) << bit);\n}\n\ntemplate<unsigned bit, typename Type>\nconstexpr Type\nflip_bit(Type src)\n{\n   return src ^ (static_cast<Type>(1) << (bit));\n}\n\n// Clears the value of a bit\ntemplate<typename Type>\nconstexpr Type\nclear_bit(Type src, unsigned bit)\n{\n   return src & ~(static_cast<Type>(1) << bit);\n}\n\ntemplate<unsigned bit, typename Type>\nconstexpr Type\nclear_bit(Type src)\n{\n   return src & ~(static_cast<Type>(1) << (bit));\n}\n\n// Sets the value of a bit to value\ntemplate<typename Type>\ninline Type\nset_bit_value(Type src, unsigned bit, Type value)\n{\n   src = clear_bit(src, bit);\n   return src | ((value & static_cast<Type>(1)) << bit);\n}\n\ntemplate<unsigned bit, typename Type>\ninline Type\nset_bit_value(Type src, Type value)\n{\n   src = clear_bit(src, bit);\n   return src | (value << bit);\n}\n\n// Create a bitmask for bits\ntemplate<typename Type>\nconstexpr Type\nmake_bitmask(Type bits)\n{\n   return static_cast<Type>((1ull << bits) - 1);\n}\n\ntemplate<unsigned bits, typename Type>\nconstexpr Type\nmake_bitmask()\n{\n   return static_cast<Type>((1ull << (bits)) - 1);\n}\n\ntemplate<>\nconstexpr uint32_t\nmake_bitmask<32, uint32_t>()\n{\n   return 0xffffffff;\n}\n\ntemplate<>\nconstexpr uint64_t\nmake_bitmask<64, uint64_t>()\n{\n   return 0xffffffffffffffffull;\n}\n\n// Creates a bitmask between begin and end\ntemplate<typename Type>\nconstexpr Type\nmake_bitmask(Type begin, Type end)\n{\n   return make_bitmask(end - begin + 1) << begin;\n}\n\ntemplate<unsigned begin, unsigned end, typename Type>\nconstexpr Type\nmake_bitmask()\n{\n   return make_bitmask<(end) - (begin) + 1, Type>() << (begin);\n}\n\n// Creates a bitmask between mb and me\ninline uint32_t\nmake_ppc_bitmask(int mb, int me)\n{\n   uint32_t begin, end, mask;\n   begin = 0xFFFFFFFF >> mb;\n   end = me < 31 ? (0xFFFFFFFF >> (me + 1)) : 0;\n   mask = begin ^ end;\n   return (me < mb) ? ~mask : mask;\n}\n\n// Sign extend bits to int32_t\ntemplate<typename Type>\ninline Type\nsign_extend(Type src, unsigned bits)\n{\n   auto mask = make_bitmask<Type>(bits);\n   src &= mask;\n\n   if (get_bit(src, bits)) {\n      return src | ~mask;\n   } else {\n      return src;\n   }\n}\n\ntemplate<unsigned bits, typename Type>\ninline Type\nsign_extend(Type src)\n{\n   auto mask = make_bitmask<bits, Type>();\n   src &= mask;\n\n   if (get_bit<(bits) - 1>(src)) {\n      return src | ~mask;\n   } else {\n      return src;\n   }\n}\n\n#ifdef PLATFORM_WINDOWS\ninline int\nclz(uint32_t bits)\n{\n   unsigned long a;\n   if (!_BitScanReverse(&a, bits)) {\n      return 32;\n   } else {\n      return 31 - a;\n   }\n}\n#else\n#define clz __builtin_clz\n#endif\n\n#ifdef PLATFORM_WINDOWS\ninline int\nclz64(uint64_t bits)\n{\n   unsigned long a;\n   if (!_BitScanReverse64(&a, bits)) {\n      return 64;\n   } else {\n      return 63 - a;\n   }\n}\n#else\n#define clz64 __builtin_clzll\n#endif\n\ninline bool\nbit_scan_reverse(unsigned long *out_position, uint32_t bits)\n{\n#ifdef PLATFORM_WINDOWS\n   return !!_BitScanReverse(out_position, bits);\n#elif defined(PLATFORM_POSIX)\n   if (bits == 0) {\n      return false;\n   }\n\n   *out_position = 31 - __builtin_clz(bits);\n   return true;\n#endif\n}\n\n#ifdef PLATFORM_WINDOWS\n#define bit_rotate_left _rotl\n#else\ninline uint32_t bit_rotate_left(uint32_t x, int shift)\n{\n   shift &= 31;\n\n   if (!shift) {\n     return x;\n   }\n\n   return (x << shift) | (x >> (32 - shift));\n}\n#endif\n\n#ifdef PLATFORM_WINDOWS\n#define bit_rotate_right _rotr\n#else\ninline uint32_t bit_rotate_right(uint32_t x, int shift)\n{\n   shift &= 31;\n\n   if (!shift) {\n      return x;\n   }\n\n   return (x >> shift) | (x << (32 - shift));\n}\n#endif\n\n// Return number of bits in type\ntemplate<typename Type>\nstruct bit_width\n{\n   static constexpr size_t value = sizeof(Type) * CHAR_BIT;\n\n   constexpr operator size_t() const\n   {\n      return value;\n   }\n};\n"
  },
  {
    "path": "src/common/byte_swap.h",
    "content": "#pragma once\n#include \"platform.h\"\n#include \"bit_cast.h\"\n#include <cstdint>\n\n#ifdef PLATFORM_LINUX\n#include <byteswap.h>\n#endif\n\n// Utility class to swap endian for types of size 1, 2, 4, 8\n// other type sizes are not supported\ntemplate<typename Type, unsigned Size = sizeof(Type)>\nstruct byte_swap_t;\n\ntemplate<typename Type>\nstruct byte_swap_t<Type, 1>\n{\n   static Type swap(Type src)\n   {\n      return src;\n   }\n};\n\ntemplate<typename Type>\nstruct byte_swap_t<Type, 2>\n{\n   static Type swap(Type src)\n   {\n#ifdef PLATFORM_WINDOWS\n      return bit_cast<Type>(_byteswap_ushort(bit_cast<uint16_t>(src)));\n#elif defined(PLATFORM_APPLE)\n      // Apple has no 16-bit byteswap intrinsic\n      const uint16_t data = bit_cast<uint16_t>(src);\n      return bit_cast<Type>((uint16_t)((data >> 8) | (data << 8)));\n#elif defined(PLATFORM_LINUX)\n      return bit_cast<Type>(bswap_16(bit_cast<uint16_t>(src)));\n#endif\n   }\n};\n\ntemplate<typename Type>\nstruct byte_swap_t<Type, 4>\n{\n   static Type swap(Type src)\n   {\n#ifdef PLATFORM_WINDOWS\n      return bit_cast<Type>(_byteswap_ulong(bit_cast<uint32_t>(src)));\n#elif defined(PLATFORM_APPLE)\n      return bit_cast<Type>(__builtin_bswap32(bit_cast<uint32_t>(src)));\n#elif defined(PLATFORM_LINUX)\n      return bit_cast<Type>(bswap_32(bit_cast<uint32_t>(src)));\n#endif\n   }\n};\n\ntemplate<typename Type>\nstruct byte_swap_t<Type, 8>\n{\n   static Type swap(Type src)\n   {\n#ifdef PLATFORM_WINDOWS\n      return bit_cast<Type>(_byteswap_uint64(bit_cast<uint64_t>(src)));\n#elif defined(PLATFORM_APPLE)\n      return bit_cast<Type>(__builtin_bswap64(bit_cast<uint64_t>(src)));\n#elif defined(PLATFORM_LINUX)\n      return bit_cast<Type>(bswap_64(bit_cast<uint64_t>(src)));\n#endif\n   }\n};\n\n// Swaps endian of src\ntemplate<typename Type>\ninline Type\nbyte_swap(Type src)\n{\n   return byte_swap_t<Type>::swap(src);\n}\n"
  },
  {
    "path": "src/common/byte_swap_array.h",
    "content": "#pragma once\n#include \"byte_swap.h\"\n#include \"platform.h\"\n#include \"platform_intrin.h\"\n#include <vector>\n\ntemplate<typename DataType>\nstatic inline void\nbyte_swap_unaligned(DataType *dst,\n                    const DataType *srcStart,\n                    const DataType *srcEnd)\n{\n   for (auto *src = srcStart; src < srcEnd; ) {\n      *dst++ = byte_swap(*src++);\n   }\n}\n\n#ifdef PLATFORM_HAS_SSE3\ntemplate<typename DataType>\nstatic inline void\nbyte_swap_aligned(DataType *dst,\n                  const DataType *srcStart,\n                  const DataType *srcEnd)\n{\n   auto sseDst = reinterpret_cast<__m128i *>(dst);\n   auto sseSrc = reinterpret_cast<const __m128i *>(srcStart);\n   auto sseSrcEnd = reinterpret_cast<const __m128i *>(srcEnd);\n\n   static_assert(sizeof(DataType) == 2 || sizeof(DataType) == 4,\n                 \"unexpected data type size for aligned byte swap\");\n\n   __m128i sseMask;\n   if constexpr (sizeof(DataType) == 2) {\n      sseMask = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);\n   } else if constexpr (sizeof(DataType) == 4) {\n      sseMask = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);\n   }\n\n   while (sseSrc < sseSrcEnd) {\n      _mm_storeu_si128(sseDst++,\n                       _mm_shuffle_epi8(_mm_loadu_si128(sseSrc++), sseMask));\n   }\n}\n#else\ntemplate<typename DataType>\nstatic inline void\nbyte_swap_aligned(DataType *dst, const DataType *srcStart, const DataType *srcEnd)\n{\n   byte_swap_unaligned<DataType>(dst, srcStart, srcEnd);\n}\n#endif\n\n#ifdef PLATFORM_HAS_SSE3\ntemplate<typename DataType>\nstatic inline void *\nbyte_swap_to_scratch(const void *data,\n                     uint32_t numBytes,\n                     std::vector<uint8_t> &scratch)\n{\n   // We pad the output buffer to guarentee we can align it to any source address.\n   scratch.resize(numBytes + 32);\n\n   // Calculate some information about the indices\n   auto swapSrc = reinterpret_cast<const DataType *>(data);\n   auto swapSrcEnd = swapSrc + (numBytes / sizeof(DataType));\n\n   // The source must be aligned at least to the swap boundary...\n   decaf_check(swapSrc == align_up(swapSrc, sizeof(DataType)));\n\n   // Align our destination exactly the same as the source\n   auto unalignedOffset = reinterpret_cast<uintptr_t>(swapSrc) & 0xF;\n   auto alignMatchedScratch = align_up(scratch.data(), 16) + unalignedOffset;\n   auto swapDest = reinterpret_cast<DataType *>(alignMatchedScratch);\n\n   // Calculate our aligned memory\n   auto alignedSwapDest = align_up(swapDest, 16);\n   auto alignedSwapSrc = align_up(swapSrc, 16);\n   auto alignedSwapSrcEnd = align_down(swapSrcEnd, 16);\n   auto alignedSize = alignedSwapSrcEnd - alignedSwapSrc;\n\n   // Do the unaligned before portion\n   byte_swap_unaligned<DataType>(swapDest, swapSrc, alignedSwapSrc);\n\n   // Do the aligned portion\n   byte_swap_aligned<DataType>(alignedSwapDest, alignedSwapSrc, alignedSwapSrcEnd);\n\n   // Do the unaligned after portion\n   byte_swap_unaligned<DataType>(alignedSwapDest + alignedSize,\n                                 alignedSwapSrc + alignedSize,\n                                 swapSrcEnd);\n\n   return alignMatchedScratch;\n}\n#else\ntemplate<typename DataType>\nstatic inline void *\nbyte_swap_to_scratch(const void *data, uint32_t numBytes, std::vector<uint8_t>& scratch)\n{\n   scratch.resize(numBytes);\n   auto swapDest = reinterpret_cast<DataType *>(scratch.data());\n   auto swapSrc = reinterpret_cast<const DataType *>(data);\n   auto swapSrcEnd = swapSrc + (numBytes / sizeof(DataType));\n   byte_swap_unaligned<DataType>(swapDest, swapSrc, swapSrcEnd);\n   return swapDest;\n}\n#endif\n"
  },
  {
    "path": "src/common/cbool.h",
    "content": "#pragma once\n#include <cstdint>\n\n#ifndef BOOL\nusing BOOL = int32_t;\n#endif\n\n#ifndef TRUE\n#define TRUE 1\n#endif\n\n#ifndef FALSE\n#define FALSE 0\n#endif\n"
  },
  {
    "path": "src/common/configstorage.h",
    "content": "#pragma once\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <vector>\n\ntemplate<typename SettingsType>\nclass ConfigStorage\n{\npublic:\n   using ChangeListener = std::function<void(const SettingsType &)>;\n   using Settings = SettingsType;\n\n   ConfigStorage() :\n      mStorage(std::make_shared<Settings>())\n   {\n   }\n\n   void set(std::shared_ptr<Settings> settings)\n   {\n      std::lock_guard<std::mutex> lock { mMutex };\n      mStorage = settings;\n\n      for (auto &listener : mListeners) {\n         listener(*settings);\n      }\n   }\n\n   std::shared_ptr<Settings> get()\n   {\n      std::lock_guard<std::mutex> lock { mMutex };\n      return mStorage;\n   }\n\n   void addListener(ChangeListener listener)\n   {\n      std::lock_guard<std::mutex> lock { mMutex };\n      mListeners.emplace_back(std::move(listener));\n   }\n\nprivate:\n   std::mutex mMutex;\n   std::shared_ptr<Settings> mStorage;\n   std::vector<ChangeListener> mListeners;\n};\n"
  },
  {
    "path": "src/common/count_of.h",
    "content": "#pragma once\n#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))\n"
  },
  {
    "path": "src/common/datahash.h",
    "content": "#pragma once\n\n#define XXH_INLINE_ALL\n#define XXH_CPU_LITTLE_ENDIAN 1\n#define XXH_STATIC_LINKING_ONLY\n#include \"xxhash.h\"\n#include <array>\n#include <functional>\n#include <random>\n\nclass DataHash\n{\npublic:\n   inline DataHash()\n   {\n   }\n\n   inline bool operator!=(const DataHash &rhs) const\n   {\n      return mHash != rhs.mHash;\n   }\n\n   inline bool operator==(const DataHash &rhs) const\n   {\n      return mHash == rhs.mHash;\n   }\n\n   inline DataHash& write(const void *data, size_t size)\n   {\n      mHash ^= XXH64(data, size, 0);\n      return *this;\n   }\n\n   template <typename T>\n   inline DataHash& write(const std::vector<T> &data)\n   {\n      static_assert(std::is_trivially_copyable<T>::value, \"Hashed types must be trivial\");\n#ifdef DECAF_USE_STDLAYOUT_BITFIELD\n      // On vs2019 this fails without stdlayout bitfield, but non-stdlayout bitfield is\n      // nicer for debugging in visual studio\n      static_assert(std::has_unique_object_representations<T>::value,\n                    \"Hashed types must have unique object representations\");\n#endif\n      return write(data.data(), data.size() * sizeof(T));\n   }\n\n   template <typename T>\n   inline DataHash& write(const T &data)\n   {\n      static_assert(std::is_trivially_copyable<T>::value, \"Hashed types must be trivial\");\n#ifdef DECAF_USE_STDLAYOUT_BITFIELD\n      // On vs2019 this fails without stdlayout bitfield, but non-stdlayout bitfield is\n      // nicer for debugging in visual studio\n      static_assert(std::has_unique_object_representations<T>::value,\n                    \"Hashed types must have unique object representations\");\n#endif\n      return write(&data, sizeof(T));\n   }\n\n   inline uint64_t value() const\n   {\n      return mHash;\n   }\n\n   static inline DataHash random()\n   {\n      static std::random_device r;\n      static std::mt19937 gen(r());\n      static std::uniform_int_distribution<uint64_t> unidist;\n\n      DataHash hash;\n      hash.mHash = unidist(gen);\n      return hash;\n   }\n\nprivate:\n   uint64_t mHash = 0;\n\n};\n\nnamespace std\n{\n\ntemplate <> struct hash<DataHash>\n{\n   uint64_t operator()(const DataHash& x) const\n   {\n      return x.value();\n   }\n};\n\n} // namespace std\n"
  },
  {
    "path": "src/common/decaf_assert.h",
    "content": "#pragma once\n#include <string>\n#include \"platform.h\"\n#include \"platform_compiler.h\"\n#include \"platform_stacktrace.h\"\n\n#ifdef PLATFORM_WINDOWS\n\n#define decaf_handle_assert(x, e, m) \\\n   if (!(x)) { \\\n      assertFailed(__FILE__, __LINE__, e, m); \\\n      __debugbreak(); \\\n      abort(); \\\n   }\n\n#define decaf_handle_warn_once_assert(x, e, m) \\\n   if (!(x)) { \\\n      static bool seen_this_error_before = false; \\\n      if (!seen_this_error_before) { \\\n         seen_this_error_before = true; \\\n         assertWarnFailed(__FILE__, __LINE__, e, m); \\\n      } \\\n   }\n\n#define decaf_host_fault(f, t) \\\n   hostFaultWithStackTrace(f, t); \\\n   __debugbreak(); \\\n   abort();\n\n#else\n\n#define decaf_handle_assert(x, e, m) \\\n   if (UNLIKELY(!(x))) { \\\n      assertFailed(__FILE__, __LINE__, e, m); \\\n      abort(); \\\n   }\n\n#define decaf_handle_warn_once_assert(x, e, m) \\\n   if (UNLIKELY(!(x))) { \\\n      static bool seen_this_error_before = false; \\\n      if (!seen_this_error_before) { \\\n         seen_this_error_before = true; \\\n         assertWarnFailed(__FILE__, __LINE__, e, m); \\\n      } \\\n   }\n\n#define decaf_host_fault(f, t) \\\n   hostFaultWithStackTrace(f, t); \\\n   abort();\n\n#endif\n\n#define decaf_assert(x, m) \\\n   decaf_handle_assert(x, #x, m)\n\n#define decaf_check(x) \\\n   decaf_handle_assert(x, #x, \"\")\n\n#define decaf_check_warn_once(x) \\\n   decaf_handle_warn_once_assert(x, #x, \"\")\n\n#define decaf_abort(m) \\\n   decaf_handle_assert(false, \"0\", m)\n\nvoid\nassertFailed(const char *file,\n             unsigned line,\n             const char *expression,\n             const std::string &message);\n\nvoid\nassertWarnFailed(const char *file,\n                 unsigned line,\n                 const char *expression,\n                 const std::string &message);\n\nvoid\nhostFaultWithStackTrace(const std::string &fault,\n                        platform::StackTrace *stackTrace);\n"
  },
  {
    "path": "src/common/enum_end.inl",
    "content": "\n#undef ENUM_BEG\n#undef ENUM_END\n#undef ENUM_VALUE\n\n#undef FLAGS_BEG\n#undef FLAGS_END\n#undef FLAGS_VALUE\n\n#undef ENUM_NAMESPACE_ENTER\n#undef ENUM_NAMESPACE_EXIT\n"
  },
  {
    "path": "src/common/enum_start.inl",
    "content": "\n#ifndef ENUM_BEG\n#include <cstdint>\n#include <type_traits>\n#define ENUM_BEG(name, type) namespace name##_ { enum Value : type {\n#endif\n\n#ifndef ENUM_END\n#define ENUM_END(name) }; }; using name = name##_::Value;\n#endif\n\n#ifndef ENUM_VALUE\n#define ENUM_VALUE(key, value) key = value,\n#endif\n\n#ifndef FLAGS_BEG\n#define FLAGS_BEG(name, type) namespace name##_ { enum Value : type {\n#endif\n\n#ifndef FLAGS_END\n#define FLAGS_END(E) }; \\\n   inline Value operator | (Value lhs, Value rhs) { return static_cast<Value>(static_cast<std::underlying_type_t<Value>>(lhs) | static_cast<std::underlying_type_t<Value>>(rhs)); } \\\n   inline Value operator & (Value lhs, Value rhs) { return static_cast<Value>(static_cast<std::underlying_type_t<Value>>(lhs) & static_cast<std::underlying_type_t<Value>>(rhs)); } \\\n   inline Value operator ^ (Value lhs, Value rhs) { return static_cast<Value>(static_cast<std::underlying_type_t<Value>>(lhs) ^ static_cast<std::underlying_type_t<Value>>(rhs)); } \\\n   inline Value operator ~ (Value lhs) { return static_cast<Value>(~static_cast<std::underlying_type_t<Value>>(lhs)); } \\\n   inline Value &operator |= (Value &lhs, Value rhs) { return (lhs = lhs | rhs); } \\\n   inline Value &operator &= (Value &lhs, Value rhs) { return (lhs = lhs & rhs); } \\\n   inline Value &operator ^= (Value &lhs, Value rhs) { return (lhs = lhs ^ rhs); } \\\n   }; using E = E##_::Value;\n#endif\n\n#ifndef FLAGS_VALUE\n#define FLAGS_VALUE(key, value) key = value,\n#endif\n\n#ifndef ENUM_NAMESPACE_ENTER\n#define ENUM_NAMESPACE_ENTER(name) namespace name {\n#endif\n\n#ifndef ENUM_NAMESPACE_EXIT\n#define ENUM_NAMESPACE_EXIT(name) }\n#endif\n"
  },
  {
    "path": "src/common/enum_string_declare.inl",
    "content": "\n#define ENUM_BEG(name, type) \\\n   std::string to_string(name enumValue);\n\n#define ENUM_VALUE(key, value)\n\n#define ENUM_END(name)\n\n#define FLAGS_BEG(name, type) \\\n   std::string to_string(name enumValue);\n\n#define FLAGS_VALUE(key, value)\n\n#define FLAGS_END(name)\n"
  },
  {
    "path": "src/common/enum_string_define.inl",
    "content": "\n#define ENUM_BEG(name, type) \\\n   std::string to_string(name enumValue) { \\\n      using namespace name##_; \\\n      switch (enumValue) {\n\n#define ENUM_VALUE(key, value) \\\n      case key: \\\n         return #key;\n\n#define ENUM_END(name) \\\n      default: \\\n         return std::to_string(static_cast<int>(enumValue)); \\\n      } \\\n   }\n\n#define FLAGS_BEG(name, type) \\\n   std::string to_string(name enumValue) { \\\n      using namespace name##_; \\\n      std::string out;\n\n#define FLAGS_VALUE(key, value) \\\n      if (enumValue & value) { \\\n         if (out.size()) { out += \" | \"; } \\\n         out += #key; \\\n      }\n\n#define FLAGS_END(name) \\\n      return out; \\\n   }\n"
  },
  {
    "path": "src/common/fastregionmap.h",
    "content": "#pragma once\n#include <atomic>\n#include <common/decaf_assert.h>\n\nstatic_assert(sizeof(std::atomic<void*>) == sizeof(void*), \"This class assumes std::atomic has no overhead\");\n\ntemplate<typename Type>\nclass FastRegionMap\n{\npublic:\n   FastRegionMap()\n      : mData(nullptr)\n   {\n      clear();\n   }\n\n   ~FastRegionMap()\n   {\n      // Currently we do not clean up due to atomics\n   }\n\n   // Note that there must be no readers or writers to call this\n   void clear()\n   {\n      if (mData) {\n         for (auto i = 0u; i < 0x100; ++i) {\n            auto level1 = mData[i].load();\n\n            if (level1) {\n               for (auto j = 0u; j < 0x100; ++j) {\n                  auto level2 = level1[j].load();\n\n                  if (level2) {\n                     delete[] level2;\n                  }\n               }\n\n               delete[] level1;\n            }\n         }\n      } else {\n         mData = new std::atomic<std::atomic<std::atomic<Type>*>*>[0x100];\n      }\n\n      memset(mData, 0, sizeof(void*) * 0x100);\n   }\n\n   Type find(uint32_t location)\n   {\n      auto index1 = (location & 0xFF000000) >> 24;\n      auto index2 = (location & 0x00FF0000) >> 16;\n      auto index3 = (location & 0x0000FFFC) >> 2;\n      auto level1 = mData[index1].load();\n\n      if (!level1) {\n         return nullptr;\n      }\n\n      auto level2 = level1[index2].load();\n\n      if (!level2) {\n         return nullptr;\n      }\n\n      return level2[index3].load();\n   }\n\n   void set(uint32_t location, Type data)\n   {\n      if (location & 0x3) {\n         decaf_abort(\"Location was not power-of-two.\");\n      }\n\n      auto index1 = (location & 0xFF000000) >> 24;\n      auto index2 = (location & 0x00FF0000) >> 16;\n      auto index3 = (location & 0x0000FFFC) >> 2;\n      auto level1 = mData[index1].load();\n\n      if (!level1) {\n         auto newTable = new std::atomic<std::atomic<Type>*>[0x100];\n         std::memset(newTable, 0, sizeof(void*) * 0x100);\n\n         if (mData[index1].compare_exchange_strong(level1, newTable)) {\n            level1 = newTable;\n         } else {\n            // compare_exchange updates level1 if we were pre-empted\n            delete[] newTable;\n         }\n      }\n\n      auto level2 = level1[index2].load();\n\n      if (!level2) {\n         auto newTable = new std::atomic<Type>[0x4000];\n         std::memset(newTable, 0, sizeof(void*) * 0x4000);\n\n         if (level1[index2].compare_exchange_strong(level2, newTable)) {\n            level2 = newTable;\n         } else {\n            // compare_exchange updates level2 if we were preempted\n            delete[] newTable;\n         }\n      }\n\n      level2[index3].store(data);\n   }\n\nprivate:\n   std::atomic<std::atomic<std::atomic<Type>*>*>*\n      mData;\n\n};"
  },
  {
    "path": "src/common/fixed.h",
    "content": "#pragma once\n#include \"bitfield.h\"\n#include <cnl/fixed_point.h>\n\nusing ufixed_16_16_t = cnl::fixed_point<uint32_t, -16>;\nusing sfixed_1_0_15_t = cnl::fixed_point<int16_t, -15>;\nusing ufixed_0_16_t = cnl::fixed_point<uint16_t, -16>;\nusing ufixed_1_15_t = cnl::fixed_point<uint16_t, -15>;\n\nusing ufixed_1_5_t = cnl::fixed_point<uint32_t, -5>;\nusing ufixed_4_6_t = cnl::fixed_point<uint32_t, -6>;\n\nusing sfixed_1_3_1_t = cnl::fixed_point<int8_t, -1>;\nusing sfixed_1_3_3_t = cnl::fixed_point<int8_t, -3>;\nusing sfixed_1_5_6_t = cnl::fixed_point<int16_t, -6>;\n\ntemplate<typename T>\nstruct UnwrapFixedPoint;\n\ntemplate<typename Rep, int Exponent, int Radix>\nstruct UnwrapFixedPoint<cnl::fixed_point<Rep, Exponent, Radix>>\n{\n   using rep = Rep;\n   static constexpr int exponent = Exponent;\n   static constexpr int radix = Radix;\n};\n\ntemplate<typename FixedPointType>\nstatic constexpr FixedPointType\nfixed_from_data(typename UnwrapFixedPoint<FixedPointType>::rep data)\n{\n   return cnl::from_rep<FixedPointType,\n                        typename UnwrapFixedPoint<FixedPointType>::rep> {}\n      (data);\n}\n\ntemplate<typename Rep, int Exponent, int Radix>\nstatic constexpr Rep\nfixed_to_data(cnl::fixed_point<Rep, Exponent, Radix> data)\n{\n   return cnl::to_rep<cnl::fixed_point<Rep, Exponent, Radix>>{}(data);\n}\n\n// Specialise of BitfieldHelper for fixed_point\ntemplate<typename BitfieldType, unsigned Position, unsigned Bits, class Rep, int Exponent, int Radix>\nstruct BitfieldHelper<BitfieldType, cnl::fixed_point<Rep, Exponent, Radix>, Position, Bits>\n{\n   using FixedType = cnl::fixed_point<Rep, Exponent, Radix>;\n   using ValueBitfield = BitfieldHelper<BitfieldType, Rep, Position, Bits>;\n\n   static FixedType get(BitfieldType bitfield)\n   {\n      return cnl::from_rep<FixedType, Rep>{}(ValueBitfield::get(bitfield));\n   }\n\n   static inline BitfieldType set(BitfieldType bitfield, FixedType fixedValue)\n   {\n      return ValueBitfield::set(bitfield, cnl::to_rep<FixedType>{}(fixedValue));\n   }\n};\n"
  },
  {
    "path": "src/common/floatutils.h",
    "content": "#pragma once\n#include \"bit_cast.h\"\n#include \"bitutils.h\"\n#include <numeric>\n\nunion FloatBitsSingle\n{\n   static const unsigned exponent_min = 0;\n   static const unsigned exponent_max = 0xff;\n\n   float v;\n   uint32_t uv;\n\n   struct\n   {\n      uint32_t mantissa : 23;\n      uint32_t exponent : 8;\n      uint32_t sign : 1;\n   };\n\n   struct\n   {\n      uint32_t : 22;\n      uint32_t quiet : 1;\n      uint32_t : 9;\n   };\n};\n\nunion FloatBitsDouble\n{\n   static const uint64_t exponent_min = 0;\n   static const uint64_t exponent_max = 0x7ff;\n\n   double v;\n   uint64_t uv;\n\n   struct\n   {\n      uint64_t mantissa : 52;\n      uint64_t exponent : 11;\n      uint64_t sign : 1;\n   };\n\n   struct\n   {\n      uint64_t : 51;\n      uint64_t quiet : 1;\n      uint64_t : 12;\n   };\n};\n\ninline FloatBitsSingle\nget_float_bits(float v)\n{\n   return { v };\n}\n\ninline FloatBitsDouble\nget_float_bits(double v)\n{\n   return { v };\n}\n\ntemplate<typename Type>\ninline bool\nis_negative(Type v)\n{\n   return get_float_bits(v).sign;\n}\n\ntemplate<typename Type>\ninline bool\nis_positive(Type v)\n{\n   return !is_negative(v);\n}\n\ntemplate<typename Type>\ninline bool\nis_zero(Type v)\n{\n   auto b = get_float_bits(v);\n   return b.exponent == 0 && b.mantissa == 0;\n}\n\ntemplate<typename Type>\ninline bool\nis_positive_zero(Type v)\n{\n   return is_positive(v) && is_zero(v);\n}\n\ntemplate<typename Type>\ninline bool\nis_negative_zero(Type v)\n{\n   return is_negative(v) && is_zero(v);\n}\n\ntemplate<typename Type>\ninline bool\nis_normal(Type v)\n{\n   auto d = get_float_bits(v);\n   return d.exponent > d.exponent_min\n       && d.exponent < d.exponent_max;\n}\n\ntemplate<typename Type>\ninline bool\nis_denormal(Type v)\n{\n   auto d = get_float_bits(v);\n   return d.exponent == d.exponent_min\n       && d.mantissa != 0;\n}\n\ntemplate<typename Type>\ninline bool\nis_infinity(Type v)\n{\n   auto d = get_float_bits(v);\n   return d.exponent == d.exponent_max\n       && d.mantissa == 0;\n}\n\ntemplate<typename Type>\ninline bool\nis_positive_infinity(Type v)\n{\n   return is_positive(v) && is_infinity(v);\n}\n\ntemplate<typename Type>\ninline bool\nis_negative_infinity(Type v)\n{\n   return is_negative(v) && is_infinity(v);\n}\n\ntemplate<typename Type>\ninline bool\nis_nan(Type v)\n{\n   auto d = get_float_bits(v);\n   return d.exponent == d.exponent_max\n       && d.mantissa != 0;\n}\n\ntemplate<typename Type>\ninline bool\nis_quiet(Type v)\n{\n   return !!get_float_bits(v).quiet;\n}\n\ntemplate<typename Type>\ninline bool\nis_quiet_nan(Type v)\n{\n   return is_nan(v) && is_quiet(v);\n}\n\ntemplate<typename Type>\ninline bool\nis_signalling_nan(Type v)\n{\n   return is_nan(v) && !is_quiet(v);\n}\n\ntemplate<typename Type>\nType\nmake_quiet(Type v)\n{\n   auto bits = get_float_bits(v);\n   bits.quiet = 1;\n   return bits.v;\n}\n\ntemplate<typename Type>\nType\nmake_nan()\n{\n   auto bits = get_float_bits(static_cast<Type>(0));\n   bits.exponent = bits.exponent_max;\n   bits.quiet = 1;\n   return bits.v;\n}\n\ninline uint64_t\nextend_float_nan_bits(uint32_t v)\n{\n   return ((uint64_t)(v & 0xC0000000) << 32\n           | (v & 0x40000000 ? UINT64_C(7) : UINT64_C(0)) << 59\n           | (uint64_t)(v & 0x3FFFFFFF) << 29);\n}\n\ninline double\nextend_float(float v)\n{\n   if (is_nan(v)) {\n       return bit_cast<double>(extend_float_nan_bits(bit_cast<uint32_t>(v)));\n   } else {\n       return static_cast<double>(v);\n   }\n}\n\ninline uint32_t\ntruncate_double_bits(uint64_t v)\n{\n   return (v>>32 & 0xC0000000) | (v>>29 & 0x3FFFFFFF);\n}\n\ninline float\ntruncate_double(double v)\n{\n   const FloatBitsDouble bits = get_float_bits(v);\n   if (bits.exponent <= 873) {\n      return bit_cast<float>(static_cast<uint32_t>(bits.sign)<<31);\n   } else if (bits.exponent <= 896) {\n      uint32_t mantissa = static_cast<uint32_t>(1<<23 | bits.mantissa>>29);\n      return bit_cast<float>(static_cast<uint32_t>(bits.sign)<<31 | (mantissa >> (897 - bits.exponent)));\n   } else if (bits.exponent >= 1151 && bits.exponent != 2047) {\n      return bit_cast<float>(static_cast<uint32_t>(bits.sign)<<31 | 0x7F800000);\n   } else {\n       return bit_cast<float>(truncate_double_bits(bits.uv));\n   }\n}\n"
  },
  {
    "path": "src/common/frameallocator.h",
    "content": "#pragma once\n#include <cstdint>\n#include \"align.h\"\n#include \"decaf_assert.h\"\n\nclass FrameAllocator\n{\npublic:\n   FrameAllocator() :\n      mBase(nullptr),\n      mSize(0),\n      mOffset(0)\n   {\n   }\n\n   FrameAllocator(void *base,\n                  size_t size) :\n      mBase(reinterpret_cast<uint8_t *>(base)),\n      mSize(size),\n      mOffset(0)\n   {\n   }\n\n   bool\n   empty() const\n   {\n      return mOffset == 0;\n   }\n\n   uint8_t *\n   top() const\n   {\n      return mBase + mOffset;\n   }\n\n   void *\n   allocate(size_t size,\n            size_t alignment = 4)\n   {\n      // Ensure section alignment\n      auto alignOffset = align_up(top(), alignment) - top();\n      size += alignOffset;\n\n      // Check we have enough size\n      decaf_check(mOffset + size <= mSize);\n\n      // Allocate data\n      auto result = top() + alignOffset;\n      mOffset += size;\n      return result;\n   }\n\n   void\n   reset()\n   {\n      mOffset = 0;\n   }\n\nprotected:\n   uint8_t *mBase;\n   size_t mSize;\n   size_t mOffset;\n};\n"
  },
  {
    "path": "src/common/log.h",
    "content": "#pragma once\n#include <fmt/core.h>\n#include <memory>\n#include <string_view>\n\n/**\n * Looks and acts like a spdlog logger but without including the spdlog header.\n */\nclass Logger\n{\npublic:\n   enum class Level\n   {\n      trace = 0,\n      debug = 1,\n      info = 2,\n      warn = 3,\n      err = 4,\n      critical = 5,\n      off = 6\n   };\n\npublic:\n   template<typename String, typename... Args>\n   inline void trace(const String &str, const Args & ... args)\n   {\n      log(Level::trace, fmt::format(str, args...));\n   }\n\n   template<typename String, typename... Args>\n   inline void debug(const String &str, const Args & ... args)\n   {\n      log(Level::debug, fmt::format(str, args...));\n   }\n\n   template<typename String, typename... Args>\n   inline void info(const String &str, const Args & ... args)\n   {\n      log(Level::info, fmt::format(str, args...));\n   }\n\n   template<typename String, typename... Args>\n   inline void warn(const String &str, const Args & ... args)\n   {\n      log(Level::warn, fmt::format(str, args...));\n   }\n\n   template<typename String, typename... Args>\n   inline void error(const String &str, const Args & ... args)\n   {\n      log(Level::err, fmt::format(str, args...));\n   }\n\n   template<typename String, typename... Args>\n   inline void critical(const String &str, const Args & ... args)\n   {\n      log(Level::critical, fmt::format(str, args...));\n   }\n\n   inline void trace(std::string_view msg)\n   {\n      log(Level::trace, msg);\n   }\n\n   inline void debug(std::string_view msg)\n   {\n      log(Level::debug, msg);\n   }\n\n   inline void info(std::string_view msg)\n   {\n      log(Level::info, msg);\n   }\n\n   inline void warn(std::string_view msg)\n   {\n      log(Level::warn, msg);\n   }\n\n   inline void error(std::string_view msg)\n   {\n      log(Level::err, msg);\n   }\n\n   inline void critical(std::string_view msg)\n   {\n      log(Level::critical, msg);\n   }\n\n   Logger &operator=(std::shared_ptr<void> logger)\n   {\n      mLogger = logger;\n      return *this;\n   }\n\n   Logger *operator->()\n   {\n      return this;\n   }\n\n   bool should_log(Level level);\n\nprivate:\n   void log(Level lvl, std::string_view msg);\n\nprivate:\n   std::shared_ptr<void> mLogger;\n};\n\nextern Logger\ngLog;\n"
  },
  {
    "path": "src/common/make_array.h",
    "content": "#pragma once\n#include <array>\n\ntemplate <class ArrayType, class... ValueTypes>\nconstexpr auto make_array(ValueTypes&&... values)\n   -> std::array<ArrayType, sizeof...(ValueTypes)>\n{\n   return { { std::forward<ValueTypes>(values)... } };\n}\n\ntemplate<size_t Size, typename Type>\nstatic auto make_filled_array(const Type &value)\n{\n   std::array<Type, Size> a;\n   a.fill(value);\n   return a;\n}\n"
  },
  {
    "path": "src/common/murmur3.h",
    "content": "//-----------------------------------------------------------------------------\n// MurmurHash3 was written by Austin Appleby, and is placed in the\n// public domain. The author hereby disclaims copyright to this source\n// code.\n\n#ifndef _MURMURHASH3_H_\n#define _MURMURHASH3_H_\n\n#include <cstdint>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n   //-----------------------------------------------------------------------------\n\n   void MurmurHash3_x86_32(const void *key, int len, uint32_t seed, void *out);\n\n   void MurmurHash3_x86_128(const void *key, int len, uint32_t seed, void *out);\n\n   void MurmurHash3_x64_128(const void *key, int len, uint32_t seed, void *out);\n\n   //-----------------------------------------------------------------------------\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // _MURMURHASH3_H_"
  },
  {
    "path": "src/common/pch.h",
    "content": "#pragma once\n\n#include <array>\n#include <atomic>\n#include <chrono>\n#include <condition_variable>\n#include <cstdint>\n#include <cstring>\n#include <deque>\n#include <fstream>\n#include <functional>\n#include <iostream>\n#include <list>\n#include <map>\n#include <memory>\n#include <mutex>\n#include <random>\n#include <sstream>\n#include <stack>\n#include <string>\n#include <string_view>\n#include <thread>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n#include <cnl/fixed_point.h>\n#include <fmt/core.h>\n#include <gsl/gsl-lite.hpp>\n"
  },
  {
    "path": "src/common/platform.h",
    "content": "#pragma once\n\n#if defined(WIN32) || defined(_WIN32) || defined(_MSC_VER)\n#define PLATFORM_WINDOWS\n#elif __APPLE__\n#define PLATFORM_APPLE\n#define PLATFORM_POSIX\n#define _XOPEN_SOURCE\n#elif __linux__\n#define PLATFORM_LINUX\n#define PLATFORM_POSIX\n#endif\n"
  },
  {
    "path": "src/common/platform_compiler.h",
    "content": "#pragma once\n\n// Macro to indicate that a branch is likely or unlikely to be taken.\n#ifdef __GNUC__  // Includes Clang.\n#define LIKELY(x) __builtin_expect(!!(x), 1)\n#define UNLIKELY(x) __builtin_expect(!!(x), 0)\n#else\n#define LIKELY(x) (x)\n#define UNLIKELY(x) (x)\n#endif\n\n// Macros to force a function to always or never be inlined.\n#ifdef __GNUC__\n#define ALWAYS_INLINE inline __attribute__((always_inline))\n#define NEVER_INLINE __attribute__((noinline))\n#elif defined(_MSC_VER)\n#define ALWAYS_INLINE __forceinline\n#define NEVER_INLINE __declspec(noinline)\n#else\n#define ALWAYS_INLINE //nothing\n#define NEVER_INLINE //nothing\n#endif\n\n// Macro to disable optimization when building with Clang on a function which\n//  both performs floating-point operations and checks exception flags that\n//  could be affected by those operations.  This is required because LLVM is\n//  unaware of the fact that floating-point operations can raise exceptions\n//  (see http://llvm.org/bugs/show_bug.cgi?id=6050).  Place this immediately\n//  before the opening brace for the function.\n#ifdef __clang__\n#define CLANG_FPU_BUG_WORKAROUND __attribute__((optnone))\n#else\n#define CLANG_FPU_BUG_WORKAROUND //nothing\n#endif\n"
  },
  {
    "path": "src/common/platform_debug.h",
    "content": "#pragma once\n#include <string>\n\nnamespace platform\n{\n\nvoid\ndebugBreak();\n\nvoid\ndebugLog(const std::string& message);\n\n} // namespace platform\n"
  },
  {
    "path": "src/common/platform_dir.h",
    "content": "#pragma once\n#include <string>\n\nnamespace platform\n{\n\nbool\ncreateDirectory(const std::string &path);\n\nbool\ncreateParentDirectories(const std::string &path);\n\nbool\nfileExists(const std::string &path);\n\nbool\nisFile(const std::string &path);\n\nbool\nisDirectory(const std::string &path);\n\nstd::string\ngetConfigDirectory();\n\n} // namespace platform\n"
  },
  {
    "path": "src/common/platform_exception.h",
    "content": "#pragma once\n#include <functional>\n\nnamespace platform\n{\n\nstruct Fiber;\n\nstruct Exception\n{\n   enum Type\n   {\n      AccessViolation = 1,\n      InvalidInstruction = 2,\n   };\n\n   Exception(Type type_) :\n      type(type_)\n   {\n   }\n\n   Type type;\n};\n\nstruct AccessViolationException : Exception\n{\n   AccessViolationException(uint64_t address_) :\n      Exception(Exception::AccessViolation),\n      address(address_)\n   {\n   }\n\n   uint64_t address;\n};\n\nstruct InvalidInstructionException : Exception\n{\n   InvalidInstructionException() :\n      Exception(Exception::InvalidInstruction)\n   {\n   }\n\n};\n\ntypedef void (*ExceptionResumeFunc)();\nusing ExceptionHandler = std::function<ExceptionResumeFunc(Exception *exception)>;\n\n// Can be returned from ExceptionHandler to indicate to resume\n// execute of current fiber.\nstatic ExceptionResumeFunc const\nHandledException = reinterpret_cast<ExceptionResumeFunc>(static_cast<uintptr_t>(-1));\n\nstatic ExceptionResumeFunc const\nUnhandledException = reinterpret_cast<ExceptionResumeFunc>(static_cast<uintptr_t>(0));\n\nbool\ninstallExceptionHandler(ExceptionHandler handler);\n\n} // namespace platform\n"
  },
  {
    "path": "src/common/platform_fiber.h",
    "content": "#pragma once\n#include <functional>\n\nnamespace platform\n{\n\nstruct Fiber;\n\nusing FiberEntryPoint = std::function<void(void *)>;\n\nFiber *\ngetThreadFiber();\n\nFiber *\ncreateFiber(FiberEntryPoint entry,\n            void *entryParam);\n\nvoid\ndestroyFiber(Fiber *fiber);\n\nvoid\nswapToFiber(Fiber *current,\n            Fiber *target);\n\n} // namespace platform\n"
  },
  {
    "path": "src/common/platform_intrin.h",
    "content": "#pragma once\n\n#if defined(_MSC_VER) || defined(__SSE3__)\n#define PLATFORM_HAS_SSE3\n#endif\n\n#if defined(_MSC_VER)\n#include <intrin.h>\n#else\n#include <x86intrin.h>\n#endif\n"
  },
  {
    "path": "src/common/platform_memory.h",
    "content": "#pragma once\n#include <cstddef>\n#include <cstdint>\n#include <string>\n\nnamespace platform\n{\n\nenum class ProtectFlags\n{\n   NoAccess,\n   ReadOnly,\n   ReadWrite,\n   ReadExecute,\n   ReadWriteExecute\n};\n\nusing MapFileHandle = intptr_t;\nstatic constexpr MapFileHandle InvalidMapFileHandle = -1;\n\nsize_t\ngetSystemPageSize();\n\nMapFileHandle\ncreateMemoryMappedFile(size_t size);\n\nMapFileHandle\nopenMemoryMappedFile(const std::string &path,\n                     ProtectFlags flags,\n                     size_t *outSize);\n\nbool\ncloseMemoryMappedFile(MapFileHandle handle);\n\nvoid *\nmapViewOfFile(MapFileHandle handle,\n              ProtectFlags flags,\n              size_t offset,\n              size_t size,\n              void *dst = nullptr);\n\nbool\nunmapViewOfFile(void *view,\n                size_t size);\n\nbool\nreserveMemory(uintptr_t address,\n              size_t size);\n\nbool\nfreeMemory(uintptr_t address,\n           size_t size);\n\nbool\ncommitMemory(uintptr_t address,\n             size_t size,\n             ProtectFlags flags = ProtectFlags::ReadWrite);\n\nbool\nuncommitMemory(uintptr_t address,\n               size_t size);\n\nbool\nprotectMemory(uintptr_t address,\n              size_t size,\n              ProtectFlags flags);\n\n} // namespace platform\n"
  },
  {
    "path": "src/common/platform_socket.h",
    "content": "#pragma once\n#include \"platform.h\"\n\n#ifdef PLATFORM_WINDOWS\n#define WIN32_LEAN_AND_MEAN\n#include <WinSock2.h>\n#include <Ws2tcpip.h>\n#else\n#include <fcntl.h>\n#include <netinet/in.h>\n#include <sys/socket.h>\n#include <sys/select.h>\n#include <sys/types.h>\n#endif\n\nnamespace platform\n{\n\n#ifdef PLATFORM_WINDOWS\nusing Socket = SOCKET;\n#else\nusing Socket = int;\n#endif\n\nbool socketWouldBlock(int result);\nint socketSetBlocking(Socket socket, bool blocking);\nint socketClose(Socket socket);\n\n} // namespace platform\n"
  },
  {
    "path": "src/common/platform_stacktrace.h",
    "content": "#pragma once\n#include <string>\n\nnamespace platform\n{\n\nstruct StackTrace;\n\nStackTrace *\ncaptureStackTrace();\n\nvoid\nfreeStackTrace(StackTrace *trace);\n\nstd::string\nformatStackTrace(StackTrace *trace);\n\nvoid\nprintStackTrace(StackTrace *trace);\n\n} // namespace platform\n"
  },
  {
    "path": "src/common/platform_thread.h",
    "content": "#pragma once\n#include <thread>\n#include <string>\n\nnamespace platform\n{\n\nvoid\nsetThreadName(std::thread *thread,\n\t\t\t  const std::string &name);\n\nvoid\nexitThread(int result);\n\n} // namespace platform\n"
  },
  {
    "path": "src/common/platform_time.h",
    "content": "#pragma once\n#include <ctime>\n\nnamespace platform\n{\n\ntm\nlocaltime(const std::time_t& time);\n\ntime_t\nmake_gm_time(std::tm time);\n\n} // namespace platform\n"
  },
  {
    "path": "src/common/platform_winapi_string.h",
    "content": "#pragma once\n#include \"platform.h\"\n\n#ifdef PLATFORM_WINDOWS\n#include <string>\n#include <string_view>\n\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\n\nnamespace platform\n{\n\n/**\n * Convert from UTF-8 string to UTF-16 string expected by Windows API.\n */\nstatic inline std::wstring\ntoWinApiString(const std::string_view &utf8)\n{\n   auto result = std::wstring { };\n   auto size = MultiByteToWideChar(CP_UTF8, 0,\n                                   utf8.data(), static_cast<int>(utf8.size()),\n                                   NULL, 0);\n   result.resize(size);\n   MultiByteToWideChar(CP_UTF8, 0,\n                       utf8.data(), static_cast<int>(utf8.size()),\n                       result.data(), static_cast<int>(result.size()));\n   return result;\n}\n\n/**\n * Convert to UTF-8 string from UTF-16 string returned by Windows API.\n */\nstatic inline std::string\nfromWinApiString(const std::wstring_view &utf16)\n{\n   auto result = std::string { };\n   auto size = WideCharToMultiByte(CP_UTF8, 0,\n                                   utf16.data(), static_cast<int>(utf16.size()),\n                                   NULL, 0,\n                                   NULL, NULL);\n   result.resize(size);\n   WideCharToMultiByte(CP_UTF8, 0,\n                       utf16.data(), static_cast<int>(utf16.size()),\n                       result.data(), static_cast<int>(result.size()),\n                       NULL, NULL);\n   return result;\n}\n\n} // namespace platform\n\n#endif // ifdef PLATFORM_WINDOWS\n"
  },
  {
    "path": "src/common/pow.h",
    "content": "#pragma once\n\ntemplate<typename Type>\ninline Type\nLog2(Type x)\n{\n   Type y = 0;\n\n   while (x > 1) {\n      x >>= 1;\n      y++;\n   }\n\n   return y;\n}\n"
  },
  {
    "path": "src/common/rangecombiner.h",
    "content": "#pragma once\n\n// void(_ObjType object, _OffsetType offset, _SizeType size)\ntemplate<typename _ObjType, typename _OffsetType, typename _SizeType, typename _FunctorType>\nclass RangeCombiner\n{\npublic:\n   RangeCombiner(_FunctorType functor)\n      : _functor(functor)\n   {\n   }\n\n   void push(_ObjType object, _OffsetType offset, _SizeType size)\n   {\n      if (_size > 0 && _object == object && _offset + _size == offset) {\n         _size += size;\n      } else {\n         flush();\n\n         _object = object;\n         _offset = offset;\n         _size = size;\n      }\n   }\n\n   void flush()\n   {\n      if (_size == 0) {\n         return;\n      }\n\n      _functor(_object, _offset, _size);\n\n      _object = {};\n      _offset = {};\n      _size = {};\n   }\n\nprotected:\n   _FunctorType _functor = nullptr;\n   _ObjType _object = {};\n   _OffsetType _offset = {};\n   _SizeType _size = {};\n};\n\ntemplate<typename _ObjType, typename _OffsetType, typename _SizeType, typename _FunctorType>\nstatic inline RangeCombiner<_ObjType, _OffsetType, _SizeType, _FunctorType>\nmakeRangeCombiner(_FunctorType functor)\n{\n   return RangeCombiner<_ObjType, _OffsetType, _SizeType, _FunctorType>(functor);\n}"
  },
  {
    "path": "src/common/src/assert.cpp",
    "content": "#include \"decaf_assert.h\"\n#include \"log.h\"\n#include \"platform.h\"\n#include \"platform_stacktrace.h\"\n\n#include <fmt/format.h>\n#include <iterator>\n#include <iostream>\n\n#ifdef PLATFORM_WINDOWS\n#include \"platform_winapi_string.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\n#undef NDEBUG\n#include <cassert>\n#endif\n\nvoid\nassertFailed(const char *file,\n             unsigned line,\n             const char *expression,\n             const std::string &message)\n{\n   auto stackTrace = platform::captureStackTrace();\n   auto trace = platform::formatStackTrace(stackTrace);\n   platform::freeStackTrace(stackTrace);\n\n   fmt::memory_buffer out;\n   fmt::format_to(std::back_inserter(out), \"Assertion failed:\\n\");\n   fmt::format_to(std::back_inserter(out), \"Expression: {}\\n\", expression);\n   fmt::format_to(std::back_inserter(out), \"File: {}\\n\", file);\n   fmt::format_to(std::back_inserter(out), \"Line: {}\\n\", line);\n\n   if (!message.empty()) {\n      fmt::format_to(std::back_inserter(out), \"Message: {}\\n\", message);\n   }\n\n   if (trace.size()) {\n      fmt::format_to(std::back_inserter(out), \"Stacktrace:\\n{}\\n\", trace);\n   }\n   out.push_back('\\0');\n\n   gLog->critical(\"{}\", out.data());\n   std::cerr << out.data() << std::endl;\n\n#ifdef PLATFORM_WINDOWS\n   if (IsDebuggerPresent()) {\n      OutputDebugStringW(platform::toWinApiString(out.data()).c_str());\n   } else {\n      auto wmsg = platform::toWinApiString(message);\n      auto expr = platform::toWinApiString(expression);\n\n      if (!wmsg.empty()) {\n         expr += L\"\\nMessage: \";\n         expr += wmsg;\n      }\n\n      _wassert(expr.c_str(), platform::toWinApiString(file).c_str(), line);\n   }\n#endif\n}\n\nvoid\nassertWarnFailed(const char *file,\n                 unsigned line,\n                 const char *expression,\n                 const std::string &message)\n{\n   gLog->warn(\"Asserted `{}` ({}) at {}:{}\", expression, message, file, line);\n}\n\nvoid\nhostFaultWithStackTrace(const std::string &fault,\n                        platform::StackTrace *stackTrace)\n{\n   auto trace = platform::formatStackTrace(stackTrace);\n\n   fmt::memory_buffer out;\n   fmt::format_to(std::back_inserter(out), \"Encountered host cpu fault:\\n\");\n   fmt::format_to(std::back_inserter(out), \"Fault: {}\\n\", fault);\n\n   if (trace.size()) {\n      fmt::format_to(std::back_inserter(out), \"Stacktrace:\\n{}\\n\", trace);\n   }\n   out.push_back('\\0');\n\n   gLog->critical(\"{}\", out.data());\n   std::cerr << out.data() << std::endl;\n\n#ifdef PLATFORM_WINDOWS\n   if (IsDebuggerPresent()) {\n      OutputDebugStringW(platform::toWinApiString(out.data()).c_str());\n   } else {\n      MessageBoxW(NULL,\n                  platform::toWinApiString(fault).c_str(),\n                  L\"Encountered host cpu fault\",\n                  MB_OK | MB_ICONERROR);\n   }\n#endif\n}\n"
  },
  {
    "path": "src/common/src/log.cpp",
    "content": "#include \"log.h\"\n#include <spdlog/spdlog.h>\n\nLogger gLog;\n\nvoid\nLogger::log(Level lvl, std::string_view msg)\n{\n   if (mLogger) {\n      auto logger = reinterpret_cast<spdlog::logger *>(mLogger.get());\n      logger->log(static_cast<spdlog::level::level_enum>(lvl), msg);\n   }\n}\n\nbool\nLogger::should_log(Level level)\n{\n   if (!mLogger) {\n      return false;\n   }\n\n   auto logger = reinterpret_cast<spdlog::logger *>(mLogger.get());\n   return logger->should_log(static_cast<spdlog::level::level_enum>(level));\n}\n"
  },
  {
    "path": "src/common/src/murmur3.cpp",
    "content": "//-----------------------------------------------------------------------------\n// MurmurHash3 was written by Austin Appleby, and is placed in the public\n// domain. The author hereby disclaims copyright to this source code.\n\n// Note - The x86 and x64 versions do _not_ produce the same results, as the\n// algorithms are optimized for their respective platforms. You can still\n// compile and run any of them on any platform, but your performance with the\n// non-native version will be less than optimal.\n\n#include \"murmur3.h\"\n\n//-----------------------------------------------------------------------------\n// Platform-specific functions and macros\n\n#ifdef __GNUC__\n#define FORCE_INLINE __attribute__((always_inline)) inline\n#else\n#define FORCE_INLINE inline\n#endif\n\nstatic FORCE_INLINE uint32_t rotl32(uint32_t x, int8_t r)\n{\n   return (x << r) | (x >> (32 - r));\n}\n\nstatic FORCE_INLINE uint64_t rotl64(uint64_t x, int8_t r)\n{\n   return (x << r) | (x >> (64 - r));\n}\n\n#define\tROTL32(x,y)\trotl32(x,y)\n#define ROTL64(x,y)\trotl64(x,y)\n\n#define BIG_CONSTANT(x) (x##LLU)\n\n//-----------------------------------------------------------------------------\n// Block read - if your platform needs to do endian-swapping or can only\n// handle aligned reads, do the conversion here\n\n#define getblock(p, i) (p[i])\n\n//-----------------------------------------------------------------------------\n// Finalization mix - force all bits of a hash block to avalanche\n\nstatic FORCE_INLINE uint32_t fmix32(uint32_t h)\n{\n   h ^= h >> 16;\n   h *= 0x85ebca6b;\n   h ^= h >> 13;\n   h *= 0xc2b2ae35;\n   h ^= h >> 16;\n\n   return h;\n}\n\n//----------\n\nstatic FORCE_INLINE uint64_t fmix64(uint64_t k)\n{\n   k ^= k >> 33;\n   k *= BIG_CONSTANT(0xff51afd7ed558ccd);\n   k ^= k >> 33;\n   k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);\n   k ^= k >> 33;\n\n   return k;\n}\n\n//-----------------------------------------------------------------------------\n\nvoid MurmurHash3_x86_32(const void * key, int len,\n   uint32_t seed, void * out)\n{\n   const uint8_t * data = (const uint8_t*)key;\n   const int nblocks = len / 4;\n   int i;\n\n   uint32_t h1 = seed;\n\n   uint32_t c1 = 0xcc9e2d51;\n   uint32_t c2 = 0x1b873593;\n\n   //----------\n   // body\n\n   const uint32_t * blocks = (const uint32_t *)(data + nblocks * 4);\n\n   for (i = -nblocks; i; i++)\n   {\n      uint32_t k1 = getblock(blocks, i);\n\n      k1 *= c1;\n      k1 = ROTL32(k1, 15);\n      k1 *= c2;\n\n      h1 ^= k1;\n      h1 = ROTL32(h1, 13);\n      h1 = h1 * 5 + 0xe6546b64;\n   }\n\n   //----------\n   // tail\n\n   const uint8_t * tail = (const uint8_t*)(data + nblocks * 4);\n\n   uint32_t k1 = 0;\n\n   switch (len & 3)\n   {\n   case 3: k1 ^= tail[2] << 16;\n   case 2: k1 ^= tail[1] << 8;\n   case 1: k1 ^= tail[0];\n      k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1;\n   };\n\n   //----------\n   // finalization\n\n   h1 ^= len;\n\n   h1 = fmix32(h1);\n\n   *(uint32_t*)out = h1;\n}\n\n//-----------------------------------------------------------------------------\n\nvoid MurmurHash3_x86_128(const void * key, const int len,\n   uint32_t seed, void * out)\n{\n   const uint8_t * data = (const uint8_t*)key;\n   const int nblocks = len / 16;\n   int i;\n\n   uint32_t h1 = seed;\n   uint32_t h2 = seed;\n   uint32_t h3 = seed;\n   uint32_t h4 = seed;\n\n   uint32_t c1 = 0x239b961b;\n   uint32_t c2 = 0xab0e9789;\n   uint32_t c3 = 0x38b34ae5;\n   uint32_t c4 = 0xa1e38b93;\n\n   //----------\n   // body\n\n   const uint32_t * blocks = (const uint32_t *)(data + nblocks * 16);\n\n   for (i = -nblocks; i; i++)\n   {\n      uint32_t k1 = getblock(blocks, i * 4 + 0);\n      uint32_t k2 = getblock(blocks, i * 4 + 1);\n      uint32_t k3 = getblock(blocks, i * 4 + 2);\n      uint32_t k4 = getblock(blocks, i * 4 + 3);\n\n      k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1;\n\n      h1 = ROTL32(h1, 19); h1 += h2; h1 = h1 * 5 + 0x561ccd1b;\n\n      k2 *= c2; k2 = ROTL32(k2, 16); k2 *= c3; h2 ^= k2;\n\n      h2 = ROTL32(h2, 17); h2 += h3; h2 = h2 * 5 + 0x0bcaa747;\n\n      k3 *= c3; k3 = ROTL32(k3, 17); k3 *= c4; h3 ^= k3;\n\n      h3 = ROTL32(h3, 15); h3 += h4; h3 = h3 * 5 + 0x96cd1c35;\n\n      k4 *= c4; k4 = ROTL32(k4, 18); k4 *= c1; h4 ^= k4;\n\n      h4 = ROTL32(h4, 13); h4 += h1; h4 = h4 * 5 + 0x32ac3b17;\n   }\n\n   //----------\n   // tail\n\n   const uint8_t * tail = (const uint8_t*)(data + nblocks * 16);\n\n   uint32_t k1 = 0;\n   uint32_t k2 = 0;\n   uint32_t k3 = 0;\n   uint32_t k4 = 0;\n\n   switch (len & 15)\n   {\n   case 15: k4 ^= tail[14] << 16;\n   case 14: k4 ^= tail[13] << 8;\n   case 13: k4 ^= tail[12] << 0;\n      k4 *= c4; k4 = ROTL32(k4, 18); k4 *= c1; h4 ^= k4;\n\n   case 12: k3 ^= tail[11] << 24;\n   case 11: k3 ^= tail[10] << 16;\n   case 10: k3 ^= tail[9] << 8;\n   case  9: k3 ^= tail[8] << 0;\n      k3 *= c3; k3 = ROTL32(k3, 17); k3 *= c4; h3 ^= k3;\n\n   case  8: k2 ^= tail[7] << 24;\n   case  7: k2 ^= tail[6] << 16;\n   case  6: k2 ^= tail[5] << 8;\n   case  5: k2 ^= tail[4] << 0;\n      k2 *= c2; k2 = ROTL32(k2, 16); k2 *= c3; h2 ^= k2;\n\n   case  4: k1 ^= tail[3] << 24;\n   case  3: k1 ^= tail[2] << 16;\n   case  2: k1 ^= tail[1] << 8;\n   case  1: k1 ^= tail[0] << 0;\n      k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1;\n   };\n\n   //----------\n   // finalization\n\n   h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len;\n\n   h1 += h2; h1 += h3; h1 += h4;\n   h2 += h1; h3 += h1; h4 += h1;\n\n   h1 = fmix32(h1);\n   h2 = fmix32(h2);\n   h3 = fmix32(h3);\n   h4 = fmix32(h4);\n\n   h1 += h2; h1 += h3; h1 += h4;\n   h2 += h1; h3 += h1; h4 += h1;\n\n   ((uint32_t*)out)[0] = h1;\n   ((uint32_t*)out)[1] = h2;\n   ((uint32_t*)out)[2] = h3;\n   ((uint32_t*)out)[3] = h4;\n}\n\n//-----------------------------------------------------------------------------\n\nvoid MurmurHash3_x64_128(const void * key, const int len,\n   const uint32_t seed, void * out)\n{\n   const uint8_t * data = (const uint8_t*)key;\n   const int nblocks = len / 16;\n   int i;\n\n   uint64_t h1 = seed;\n   uint64_t h2 = seed;\n\n   uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);\n   uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);\n\n   //----------\n   // body\n\n   const uint64_t * blocks = (const uint64_t *)(data);\n\n   for (i = 0; i < nblocks; i++)\n   {\n      uint64_t k1 = getblock(blocks, i * 2 + 0);\n      uint64_t k2 = getblock(blocks, i * 2 + 1);\n\n      k1 *= c1; k1 = ROTL64(k1, 31); k1 *= c2; h1 ^= k1;\n\n      h1 = ROTL64(h1, 27); h1 += h2; h1 = h1 * 5 + 0x52dce729;\n\n      k2 *= c2; k2 = ROTL64(k2, 33); k2 *= c1; h2 ^= k2;\n\n      h2 = ROTL64(h2, 31); h2 += h1; h2 = h2 * 5 + 0x38495ab5;\n   }\n\n   //----------\n   // tail\n\n   const uint8_t * tail = (const uint8_t*)(data + nblocks * 16);\n\n   uint64_t k1 = 0;\n   uint64_t k2 = 0;\n\n   switch (len & 15)\n   {\n   case 15: k2 ^= (uint64_t)(tail[14]) << 48;\n   case 14: k2 ^= (uint64_t)(tail[13]) << 40;\n   case 13: k2 ^= (uint64_t)(tail[12]) << 32;\n   case 12: k2 ^= (uint64_t)(tail[11]) << 24;\n   case 11: k2 ^= (uint64_t)(tail[10]) << 16;\n   case 10: k2 ^= (uint64_t)(tail[9]) << 8;\n   case  9: k2 ^= (uint64_t)(tail[8]) << 0;\n      k2 *= c2; k2 = ROTL64(k2, 33); k2 *= c1; h2 ^= k2;\n\n   case  8: k1 ^= (uint64_t)(tail[7]) << 56;\n   case  7: k1 ^= (uint64_t)(tail[6]) << 48;\n   case  6: k1 ^= (uint64_t)(tail[5]) << 40;\n   case  5: k1 ^= (uint64_t)(tail[4]) << 32;\n   case  4: k1 ^= (uint64_t)(tail[3]) << 24;\n   case  3: k1 ^= (uint64_t)(tail[2]) << 16;\n   case  2: k1 ^= (uint64_t)(tail[1]) << 8;\n   case  1: k1 ^= (uint64_t)(tail[0]) << 0;\n      k1 *= c1; k1 = ROTL64(k1, 31); k1 *= c2; h1 ^= k1;\n   };\n\n   //----------\n   // finalization\n\n   h1 ^= len; h2 ^= len;\n\n   h1 += h2;\n   h2 += h1;\n\n   h1 = fmix64(h1);\n   h2 = fmix64(h2);\n\n   h1 += h2;\n   h2 += h1;\n\n   ((uint64_t*)out)[0] = h1;\n   ((uint64_t*)out)[1] = h2;\n}\n\n//-----------------------------------------------------------------------------\n"
  },
  {
    "path": "src/common/src/platform_posix_debug.cpp",
    "content": "#include \"platform.h\"\n#include \"platform_debug.h\"\n\n#ifdef PLATFORM_POSIX\n\nnamespace platform\n{\n\nvoid\ndebugBreak()\n{\n   // TODO: Implement debug breaks for POSIX\n}\n\nvoid\ndebugLog(const std::string& message)\n{\n   // TODO: Implement IDE debug logging for POSIX\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_posix_dir.cpp",
    "content": "#include \"platform.h\"\n#include \"platform_dir.h\"\n\n#ifdef PLATFORM_POSIX\n#include <errno.h>\n#include <fmt/core.h>\n#include <stdlib.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\nnamespace platform\n{\n\nbool\ncreateDirectory(const std::string &path)\n{\n   if (!createParentDirectories(path)) {\n      return false;\n   }\n\n   return mkdir(path.c_str(), 0755) == 0\n      || (errno == EEXIST && isDirectory(path));\n}\n\nbool\ncreateParentDirectories(const std::string &path)\n{\n   auto slashPos = path.rfind('/');\n\n   if (slashPos == std::string::npos || slashPos == 0) {\n      return true;\n   }\n\n   return createDirectory(path.substr(0, slashPos));\n}\n\nbool\nfileExists(const std::string &path)\n{\n   return access(path.c_str(), F_OK) != -1;\n}\n\nbool\nisFile(const std::string &path)\n{\n   struct stat info;\n   auto result = stat(path.c_str(), &info);\n\n   if (result != 0) {\n      return false;\n   }\n\n   return S_ISREG(info.st_mode);\n}\n\nbool\nisDirectory(const std::string &path)\n{\n   struct stat info;\n   auto result = stat(path.c_str(), &info);\n\n   if (result != 0) {\n      return false;\n   }\n\n   return S_ISDIR(info.st_mode);\n}\n\nstd::string\ngetConfigDirectory()\n{\n   // TODO: will be different for Mac\n   const char *home = getenv(\"HOME\");\n   if (home && *home) {\n      return fmt::format(\"{}/.config\", home);\n   } else {\n      return \".\";\n   }\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_posix_exception.cpp",
    "content": "#include <vector>\n#include \"platform.h\"\n#include \"platform_exception.h\"\n#include \"platform_fiber.h\"\n#include \"log.h\"\n\n#ifdef PLATFORM_POSIX\n#ifndef _GNU_SOURCE\n#  define _GNU_SOURCE\n#endif\n#include <errno.h>\n#include <signal.h>\n#include <string.h>\n#include <ucontext.h>\n\nnamespace platform\n{\n\nstatic std::vector<ExceptionHandler>\nsExceptionHandlers;\n\nstatic struct sigaction\nsSegvHandler;\n\nstatic struct sigaction\nsSystemSegvHandler;\n\nstatic struct sigaction\nsIllHandler;\n\nstatic struct sigaction\nsSystemIllHandler;\n\nstatic void\ndispatchException(Exception *exception,\n                  void *context,\n                  int signum,\n                  const struct sigaction *ourHandler,\n                  const struct sigaction *sysHandler)\n{\n   // Reset to the original signal handler in case an exception handler\n   //  generates a signal of its own\n   sigaction(signum, sysHandler, nullptr);\n\n   static bool sInSignal = false;\n\n   // Avoid recursive signal handling (in case an exception handler looking\n   //  at a SIGILL causes a SIGSEGV, for example)\n   if (sInSignal) {\n      return;\n   }\n\n   sInSignal = true;\n\n   for (auto &handler : sExceptionHandlers) {\n      auto func = handler(exception);\n\n      if (func == UnhandledException) {\n         // Exception unhandled, try another handler\n         continue;\n      }\n\n      sInSignal = false;\n\n      // Reinstall our signal handler\n      sigaction(signum, ourHandler, nullptr);\n\n      if (func == HandledException) {\n         // Exception handled, resume execution\n         return;\n      } else {\n         // Exception handled, switch execution to target function\n         auto ctx = reinterpret_cast<ucontext_t *>(context);\n#ifdef PLATFORM_APPLE\n         ctx->uc_mcontext->__ss.__rip = reinterpret_cast<uint64_t>(func);\n#else\n         ctx->uc_mcontext.gregs[REG_RIP] = reinterpret_cast<uint64_t>(func);\n#endif\n         return;\n      }\n   }\n\n   // No exception handlers, found, so re-run the failing instruction to\n   //  call the original signal handler\n   return;\n}\n\nstatic void\nsegvHandler(int signum, siginfo_t *info, void *context)\n{\n   auto exception = AccessViolationException { reinterpret_cast<uint64_t>(info->si_addr) };\n   dispatchException(&exception, context, signum, &sSegvHandler, &sSystemSegvHandler);\n}\n\nstatic void\nillHandler(int signum, siginfo_t *info, void *context)\n{\n   auto exception = InvalidInstructionException { };\n   dispatchException(&exception, context, signum, &sIllHandler, &sSystemIllHandler);\n}\n\nbool\ninstallExceptionHandler(ExceptionHandler handler)\n{\n   static bool addedHandlers = false;\n\n   if (!addedHandlers) {\n      sigemptyset(&sSegvHandler.sa_mask);\n\n      // Set SA_RESETHAND so that a SEGV in the handler will terminate the\n      // program rather than going into an infinite loop.\n      sSegvHandler.sa_flags = SA_SIGINFO | SA_RESETHAND;\n\n      sSegvHandler.sa_sigaction = segvHandler;\n      if (sigaction(SIGSEGV, &sSegvHandler, &sSystemSegvHandler) != 0) {\n         gLog->error(\"sigaction(SIGSEGV) failed: {}\", strerror(errno));\n         return false;\n      }\n\n      sIllHandler = sSegvHandler;\n      sIllHandler.sa_sigaction = illHandler;\n      if (sigaction(SIGILL, &sIllHandler, &sSystemIllHandler) != 0) {\n         gLog->error(\"sigaction(SIGILL) failed: {}\", strerror(errno));\n         return false;\n      }\n\n      addedHandlers = true;\n   }\n\n   sExceptionHandlers.push_back(handler);\n   return true;\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_posix_fiber.cpp",
    "content": "#include \"platform.h\"\n#include \"platform_fiber.h\"\n#include \"log.h\"\n\n#ifdef PLATFORM_POSIX\n#include <array>\n#include <errno.h>\n#include <signal.h>\n#include <ucontext.h>\n\n#ifdef DECAF_VALGRIND\n   #include <valgrind/valgrind.h>\n#endif\n\nnamespace platform\n{\n\nstatic const size_t\nDefaultStackSize = 1024 * 1024;\n\nstruct Fiber\n{\n   ucontext_t context;\n   FiberEntryPoint entry = nullptr;\n   void *entryParam = nullptr;\n#ifdef DECAF_VALGRIND\n   unsigned int valgrindStackId;\n#endif\n   std::array<char, DefaultStackSize> stack;\n};\n\nFiber *\ngetThreadFiber()\n{\n   auto fiber = new Fiber();\n   return fiber;\n}\n\nstatic void\nfiberEntryPoint(Fiber *fiber)\n{\n   fiber->entry(fiber->entryParam);\n}\n\nFiber *\ncreateFiber(FiberEntryPoint entry, void *entryParam)\n{\n   auto fiber = new Fiber();\n   fiber->entry = entry;\n   fiber->entryParam = entryParam;\n\n#ifdef DECAF_VALGRIND\n   fiber->valgrindStackId = VALGRIND_STACK_REGISTER(&fiber->stack[0], &fiber->stack[fiber->stack.size() - 1]);\n#endif\n\n   getcontext(&fiber->context);\n   fiber->context.uc_stack.ss_sp = &fiber->stack[0];\n   fiber->context.uc_stack.ss_size = fiber->stack.size();\n   fiber->context.uc_link = nullptr;\n\n   makecontext(&fiber->context, reinterpret_cast<void(*)()>(&fiberEntryPoint), 1, fiber);\n   return fiber;\n}\n\nvoid\ndestroyFiber(Fiber *fiber)\n{\n#ifdef DECAF_VALGRIND\n   VALGRIND_STACK_DEREGISTER(fiber->valgrindStackId);\n#endif\n\n   delete fiber;\n}\n\nvoid\nswapToFiber(Fiber *current, Fiber *target)\n{\n   if (!current) {\n      setcontext(&target->context);\n   } else {\n      swapcontext(&current->context, &target->context);\n   }\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_posix_memory.cpp",
    "content": "#include \"platform.h\"\n#include \"platform_memory.h\"\n#include \"log.h\"\n\n#ifdef PLATFORM_POSIX\n#include <common/decaf_assert.h>\n#include <cstdlib>\n#include <errno.h>\n#include <fmt/core.h>\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <time.h>\n#include <unistd.h>\n\nnamespace platform\n{\n\nstatic int\nflagsToProt(ProtectFlags flags)\n{\n   switch (flags) {\n   case ProtectFlags::ReadOnly:\n      return PROT_READ;\n   case ProtectFlags::ReadWrite:\n      return PROT_READ | PROT_WRITE;\n   case ProtectFlags::ReadExecute:\n      return PROT_READ | PROT_EXEC;\n   case ProtectFlags::ReadWriteExecute:\n      return PROT_READ | PROT_WRITE | PROT_EXEC;\n   case ProtectFlags::NoAccess:\n   default:\n      return PROT_WRITE;\n   }\n}\n\nstatic int\nflagsToOpen(ProtectFlags flags)\n{\n   switch (flags) {\n   case ProtectFlags::ReadOnly:\n      return O_RDONLY;\n   case ProtectFlags::ReadWrite:\n      return O_RDWR;\n   case ProtectFlags::ReadExecute:\n      return O_RDONLY;\n   case ProtectFlags::ReadWriteExecute:\n      return O_RDWR;\n   case ProtectFlags::NoAccess:\n   default:\n      return PROT_WRITE;\n   }\n}\n\n\nsize_t\ngetSystemPageSize()\n{\n   return static_cast<size_t>(sysconf(_SC_PAGESIZE));\n}\n\n\nMapFileHandle\ncreateMemoryMappedFile(size_t size)\n{\n   const char *tmpdir = getenv(\"TMPDIR\");\n   if (!tmpdir || !*tmpdir) {\n      tmpdir = \"/tmp\";\n   }\n   const std::string pattern = fmt::format(\"{}/decafXXXXXX\", tmpdir);\n   char *path = strdup(pattern.c_str());  // Must be a modifiable char array.\n   int old_umask = umask(0077);\n   int fd = mkstemp(path);\n   if (fd == -1) {\n      gLog->error(\"createMemoryMappedFile({}) mkstemp failed with error: {}\",\n                  size, errno);\n      umask(old_umask);\n      return InvalidMapFileHandle;\n   }\n   umask(old_umask);\n\n   if (unlink(path) == -1) {\n      gLog->error(\"createMemoryMappedFile({}) unlink failed with error: {}\",\n                  size, errno);\n   }\n\n   free(path);\n\n#ifdef PLATFORM_APPLE\n   if (ftruncate(fd, size) == -1) {\n#else\n   if (ftruncate64(fd, size) == -1) {\n#endif\n      gLog->error(\"createMemoryMappedFile({}) ftruncate64 failed with error: {}\",\n                  size, errno);\n   }\n\n   return static_cast<MapFileHandle>(fd);\n}\n\n\nMapFileHandle\nopenMemoryMappedFile(const std::string &path,\n                     ProtectFlags flags,\n                     size_t *outSize)\n{\n   // Only support READ ONLY for now\n   decaf_check(flags == ProtectFlags::ReadOnly);\n   struct stat st;\n   if (stat(path.c_str(), &st) == -1) {\n      gLog->error(\"openMemoryMappedFile(\\\"{}\\\") stat failed with error: {}\",\n                  path, errno);\n      return InvalidMapFileHandle;\n   }\n\n   auto fd = open(path.c_str(), flagsToOpen(flags), 0);\n   if (fd == -1) {\n      gLog->error(\"openMemoryMappedFile(\\\"{}\\\") open failed with error: {}\",\n                  path, errno);\n      return InvalidMapFileHandle;\n   }\n\n   *outSize = st.st_size;\n   return static_cast<MapFileHandle>(fd);\n}\n\n\nbool\ncloseMemoryMappedFile(MapFileHandle handle)\n{\n   if (close(static_cast<int>(handle)) == -1) {\n      gLog->error(\"closeMemoryMappedFile({}) close failed with error: {}\",\n                  static_cast<int>(handle), errno);\n      return false;\n   }\n\n   return true;\n}\n\n\nvoid *\nmapViewOfFile(MapFileHandle handle,\n              ProtectFlags flags,\n              size_t offset,\n              size_t size,\n              void *dst)\n{\n   auto prot = flagsToProt(flags);\n   auto result = mmap(dst,\n                      size,\n                      prot,\n                      MAP_SHARED,\n                      static_cast<int>(handle),\n                      offset);\n\n   if (result == MAP_FAILED) {\n      gLog->error(\"mapViewOfFile(offset: 0x{:X}, size: 0x{:X}, dst: {}) mmap failed with error: {}\",\n                  offset, size, dst, errno);\n      return nullptr;\n   }\n\n   if (result != dst) {\n      gLog->error(\"mapViewOfFile(offset: 0x{:X}, size: 0x{:X}, dst: {}) mmap returned unexpected address: {}\",\n                  offset, size, dst, result);\n\n      munmap(result, size);\n      return nullptr;\n   }\n\n   return result;\n}\n\n\nbool\nunmapViewOfFile(void *view,\n                size_t size)\n{\n   if (munmap(view, size) == -1) {\n      gLog->error(\"unmapViewOfFile(view: 0x{:X}, size: 0x{:X}) munmap failed with error: {}\",\n                  view, size, errno);\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\nreserveMemory(uintptr_t address,\n              size_t size)\n{\n   // On *nix systems, regions mapped with mmap are reserved only by default\n   //  and become automatically commited on first use.  Because these pages\n   //  have no protection rights, they are forced to stay reserved.\n   auto baseAddress = reinterpret_cast<void *>(address);\n   auto result = mmap(baseAddress,\n                      size,\n                      PROT_NONE,\n                      MAP_PRIVATE | MAP_ANONYMOUS,\n                      -1,\n                      0);\n\n   if (result == MAP_FAILED) {\n      gLog->debug(\"reserveMemory(address: 0x{:08X}, size: 0x{:X}) mmap failed with error: {}\",\n                  address, size, errno);\n      return false;\n   }\n\n   if (result != baseAddress) {\n      gLog->debug(\"reserveMemory(address: 0x{:08X}, size: 0x{:X}) returned unexpected address: {}\",\n                  address, size, result);\n\n      munmap(result, size);\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\nfreeMemory(uintptr_t address,\n           size_t size)\n{\n   auto baseAddress = reinterpret_cast<void *>(address);\n   if (munmap(baseAddress, size) == -1) {\n      gLog->error(\"freeMemory(address: 0x{:08X}, size: 0x{:X}) munmap failed with error: {}\",\n                  address, size, errno);\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\ncommitMemory(uintptr_t address,\n             size_t size,\n             ProtectFlags flags)\n{\n   auto baseAddress = reinterpret_cast<void *>(address);\n\n   if (mprotect(baseAddress, size, flagsToProt(flags)) == -1) {\n      gLog->error(\"commitMemory(address: 0x{:08X}, size: 0x{:X}, flags: {}) mprotect failed with error: {}\",\n                  address, size, static_cast<int>(flags), errno);\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\nuncommitMemory(uintptr_t address,\n               size_t size)\n{\n   // On *nix systems, there is not really a way to forcibly uncommit\n   //   a particular region of code.  We just lock it out.\n   auto baseAddress = reinterpret_cast<void *>(address);\n\n   if (mprotect(baseAddress, size, PROT_NONE) == -1) {\n      gLog->error(\"uncommitMemory(address: {}, size: {}) mprotect failed with error: {}\",\n                  address, size, errno);\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\nprotectMemory(uintptr_t address,\n              size_t size,\n              ProtectFlags flags)\n{\n   auto baseAddress = reinterpret_cast<void *>(address);\n\n   if (mprotect(baseAddress, size, flagsToProt(flags)) == -1) {\n      gLog->error(\"protectMemory(address: {}, size: {}, flags: {}) mprotect failed with error: {}\",\n                  address, size, static_cast<int>(flags), errno);\n      return false;\n   }\n\n   return true;\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_posix_socket.cpp",
    "content": "#include \"platform_socket.h\"\n\n#ifdef PLATFORM_POSIX\n#include <errno.h>\n#include <unistd.h>\n\nnamespace platform\n{\n\nbool socketWouldBlock(int result)\n{\n   return (result == EWOULDBLOCK);\n}\n\nint socketSetBlocking(Socket socket, bool blocking)\n{\n   auto fl = fcntl(socket, F_GETFL, 0);\n\n   if (blocking) {\n      fl &= ~O_NONBLOCK;\n   } else {\n      fl |= O_NONBLOCK;\n   }\n\n   return fcntl(socket, F_SETFL, fl);\n}\n\nint socketClose(Socket socket)\n{\n   return close(socket);\n}\n\n} // namespace platform\n#endif\n"
  },
  {
    "path": "src/common/src/platform_posix_stacktrace.cpp",
    "content": "#include \"decaf_assert.h\"\n#include \"platform.h\"\n#include \"platform_stacktrace.h\"\n\n#ifdef PLATFORM_POSIX\n#include <stdexcept>\n\nnamespace platform\n{\n\nStackTrace * captureStackTrace()\n{\n   return nullptr;\n}\n\nvoid freeStackTrace(StackTrace *)\n{\n}\n\nstd::string\nformatStackTrace(StackTrace *trace)\n{\n   return { };\n}\n\nvoid printStackTrace(StackTrace *)\n{\n   decaf_abort(\"POSIX support for stack tracing is not implemented\");\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_posix_thread.cpp",
    "content": "#include \"platform.h\"\n#include \"platform_thread.h\"\n\n#ifdef PLATFORM_POSIX\n#include <cstdlib>\n#include <pthread.h>\n\nnamespace platform\n{\n\nvoid\nsetThreadName(std::thread *thread,\n              const std::string &name)\n{\n#ifndef PLATFORM_APPLE\n   auto handle = thread->native_handle();\n   pthread_setname_np(handle, name.c_str());\n#endif\n}\n\nvoid\nexitThread(int result)\n{\n   auto res = reinterpret_cast<int *>(malloc(sizeof(int)));\n   *res = result;\n   pthread_exit(res);\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_posix_time.cpp",
    "content": "#include \"platform.h\"\n#include \"platform_time.h\"\n\n#ifdef PLATFORM_POSIX\n#include <time.h>\n\nnamespace platform\n{\n\ntm\nlocaltime(const std::time_t& time)\n{\n   std::tm tm_snapshot;\n   localtime_r(&time, &tm_snapshot);\n   return tm_snapshot;\n}\n\ntime_t\nmake_gm_time(std::tm time)\n{\n   return timegm(&time);\n}\n\n}\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_win_debug.cpp",
    "content": "#include \"platform.h\"\n\n#ifdef PLATFORM_WINDOWS\n#include \"platform_debug.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\n\nnamespace platform\n{\n\nvoid\ndebugBreak()\n{\n   DebugBreak();\n}\n\nvoid\ndebugLog(const std::string& message)\n{\n   OutputDebugStringA(message.c_str());\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_win_dir.cpp",
    "content": "#include \"platform.h\"\n\n#ifdef PLATFORM_WINDOWS\n#include \"platform_dir.h\"\n#include \"platform_winapi_string.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <direct.h>\n#include <errno.h>\n#include <io.h>\n#include <locale>\n#include <sys/stat.h>\n#include <ShlObj.h>\n\nnamespace platform\n{\n\nstatic bool\nisDriveName(const std::string &path)\n{\n   return path.length() == 2\n      && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))\n      && path[1] == ':';\n}\n\nbool\ncreateDirectory(const std::string &path)\n{\n   if (!createParentDirectories(path)) {\n      return false;\n   }\n\n   auto winPath = platform::toWinApiString(path);\n\n   return _wmkdir(winPath.c_str()) == 0\n      || (errno == EEXIST && isDirectory(path));\n}\n\nbool\ncreateParentDirectories(const std::string &path)\n{\n   auto slashPos = path.find_last_of(\"/\\\\\");\n\n   if (slashPos == std::string::npos\n       || (slashPos == 2 && isDriveName(path.substr(0, 2)))\n       || (path.find_first_not_of(\"/\\\\\") == 2  // \"\\\\server\\path\" syntax\n           && path.find_first_of(\"/\\\\\", 2) == slashPos)) {\n      return true;\n   }\n\n   return createDirectory(path.substr(0, slashPos));\n}\n\nbool\nfileExists(const std::string &path)\n{\n   auto winPath = platform::toWinApiString(path);\n   return _waccess_s(winPath.c_str(), 0) == 0;\n}\n\nbool\nisFile(const std::string &path)\n{\n   auto winPath = platform::toWinApiString(path);\n   struct _stat64 info;\n\n   if (_wstat64(winPath.c_str(), &info)) {\n      return false;\n   }\n\n   return !!(info.st_mode & _S_IFREG);\n}\n\nbool\nisDirectory(const std::string &path)\n{\n   auto winPath = platform::toWinApiString(path);\n   struct _stat64 info;\n\n   if (_wstat64(winPath.c_str(), &info)) {\n      return false;\n   }\n\n   return !!(info.st_mode & _S_IFDIR);\n}\n\nstd::string\ngetConfigDirectory()\n{\n   PWSTR path;\n   auto result = std::string { \".\" };\n\n   if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &path))) {\n      result = platform::fromWinApiString(path);\n      CoTaskMemFree(path);\n   }\n\n   return result;\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_win_exception.cpp",
    "content": "#include \"platform.h\"\n\n#ifdef PLATFORM_WINDOWS\n#include \"platform_exception.h\"\n#include \"platform_fiber.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <vector>\n#include <Windows.h>\n\nnamespace platform\n{\n\nstatic std::vector<ExceptionHandler>\ngExceptionHandlers;\n\nLONG\ndispatchException(PEXCEPTION_POINTERS info, Exception *exception)\n{\n   for (auto &handler : gExceptionHandlers) {\n      auto func = handler(exception);\n\n      if (func == UnhandledException) {\n         // Exception unhandled, try another handler\n         continue;\n      } else if (func == HandledException) {\n         // Exception handled, resume execution\n         return EXCEPTION_CONTINUE_EXECUTION;\n      } else {\n         // Exception handled, jump to new function\n         info->ContextRecord->Rip = reinterpret_cast<DWORD64>(func);\n         return EXCEPTION_CONTINUE_EXECUTION;\n      }\n   }\n\n   return EXCEPTION_CONTINUE_SEARCH;\n}\n\nstatic LONG CALLBACK\nexceptionHandler(PEXCEPTION_POINTERS info)\n{\n   switch (info->ExceptionRecord->ExceptionCode) {\n   case STATUS_ACCESS_VIOLATION: {\n      auto address = info->ExceptionRecord->ExceptionInformation[1];\n      auto exception = AccessViolationException{ address };\n      return dispatchException(info, &exception);\n   } break;\n   case STATUS_ILLEGAL_INSTRUCTION: {\n      auto exception = InvalidInstructionException{ };\n      return dispatchException(info, &exception);\n   } break;\n   }\n\n   // Unhandled exception\n   return EXCEPTION_CONTINUE_SEARCH;\n}\n\nbool\ninstallExceptionHandler(ExceptionHandler handler)\n{\n   static bool addedHandler = false;\n\n   if (!addedHandler) {\n      AddVectoredExceptionHandler(0, exceptionHandler);\n      addedHandler = true;\n   }\n\n   gExceptionHandlers.push_back(handler);\n   return true;\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_win_fiber.cpp",
    "content": "#include \"platform.h\"\n\n#ifdef PLATFORM_WINDOWS\n#include \"platform_fiber.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\n\nnamespace platform\n{\n\nstruct Fiber\n{\n   LPVOID handle = nullptr;\n   FiberEntryPoint entry = nullptr;\n   void *entryParam = nullptr;\n};\n\nFiber *\ngetThreadFiber()\n{\n   auto fiber = new Fiber();\n   fiber->handle = ConvertThreadToFiber(NULL);\n   return fiber;\n}\n\nstatic void __stdcall\nfiberEntryPoint(LPVOID lpFiberParameter)\n{\n   auto fiber = reinterpret_cast<Fiber *>(lpFiberParameter);\n   fiber->entry(fiber->entryParam);\n}\n\nFiber *\ncreateFiber(FiberEntryPoint entry, void *entryParam)\n{\n   auto fiber = new Fiber();\n   fiber->handle = CreateFiber(0, &fiberEntryPoint, fiber);\n   fiber->entry = entry;\n   fiber->entryParam = entryParam;\n   return fiber;\n}\n\nvoid\ndestroyFiber(Fiber *fiber)\n{\n   DeleteFiber(fiber->handle);\n   delete fiber;\n}\n\nvoid\nswapToFiber(Fiber *current, Fiber *target)\n{\n   SwitchToFiber(target->handle);\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_win_memory.cpp",
    "content": "#include \"platform.h\"\n\n#ifdef PLATFORM_WINDOWS\n#include \"decaf_assert.h\"\n#include \"log.h\"\n#include \"platform_memory.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <map>\n#include <mutex>\n#include <Windows.h>\n\nnamespace platform\n{\n\nstruct WindowsMapFileHandle\n{\n   HANDLE fileHandle;\n   HANDLE mappingHandle;\n};\n\nstatic DWORD\nflagsToPageProtect(ProtectFlags flags)\n{\n   switch (flags) {\n   case ProtectFlags::ReadOnly:\n      return PAGE_READONLY;\n   case ProtectFlags::ReadWrite:\n      return PAGE_READWRITE;\n   case ProtectFlags::ReadExecute:\n      return PAGE_EXECUTE_READ;\n   case ProtectFlags::ReadWriteExecute:\n      return PAGE_EXECUTE_READWRITE;\n   case ProtectFlags::NoAccess:\n   default:\n      return PAGE_NOACCESS;\n   }\n}\n\n\nstatic DWORD\nflagsToFileMap(ProtectFlags flags)\n{\n   switch (flags) {\n   case ProtectFlags::ReadOnly:\n      return FILE_MAP_READ;\n   case ProtectFlags::ReadWrite:\n      return FILE_MAP_WRITE;\n   case ProtectFlags::ReadExecute:\n      return FILE_MAP_READ | FILE_MAP_EXECUTE;\n   case ProtectFlags::ReadWriteExecute:\n      return FILE_MAP_READ | FILE_MAP_WRITE | FILE_MAP_EXECUTE;\n   case ProtectFlags::NoAccess:\n   default:\n      return 0;\n   }\n}\n\n\nsize_t\ngetSystemPageSize()\n{\n   SYSTEM_INFO info;\n   GetSystemInfo(&info);\n   return static_cast<size_t>(info.dwAllocationGranularity);\n}\n\n\nMapFileHandle\ncreateMemoryMappedFile(size_t size)\n{\n   auto sizeLo = static_cast<DWORD>(size & 0xFFFFFFFFu);\n   auto sizeHi = static_cast<DWORD>((size >> 32) & 0xFFFFFFFFu);\n   auto handle = CreateFileMappingW(INVALID_HANDLE_VALUE,\n                                    NULL,\n                                    SEC_COMMIT | PAGE_READWRITE,\n                                    sizeHi,\n                                    sizeLo,\n                                    NULL);\n\n   if (!handle) {\n      gLog->error(\"createMemoryMappedFile(0x{:X}) failed with error: {}\",\n                  size, GetLastError());\n      return InvalidMapFileHandle;\n   }\n\n   auto windowsHandle = new WindowsMapFileHandle { };\n   windowsHandle->mappingHandle = handle;\n   windowsHandle->fileHandle = NULL;\n   return reinterpret_cast<MapFileHandle>(windowsHandle);\n}\n\n\nMapFileHandle\nopenMemoryMappedFile(const std::string &path,\n                     ProtectFlags flags,\n                     size_t *outSize)\n{\n   // Only support READ ONLY for now\n   decaf_check(flags == ProtectFlags::ReadOnly);\n   OFSTRUCT of;\n   auto fileHandle = reinterpret_cast<HANDLE>(static_cast<uintptr_t>(OpenFile(path.c_str(), &of, OF_READ)));\n   if (!fileHandle) {\n      gLog->error(\"openMemoryMappedFile(\\\"{}\\\") OpenFile failed with error: {}\",\n                  path, GetLastError());\n      return InvalidMapFileHandle;\n   }\n\n   auto mapHandle = CreateFileMappingW(fileHandle, NULL, PAGE_READONLY, 0, 0, NULL);\n   if (!mapHandle) {\n      CloseHandle(fileHandle);\n      gLog->error(\"openMemoryMappedFile(\\\"{}\\\") CreateFileMapping failed with error: {}\",\n                  path, GetLastError());\n      return InvalidMapFileHandle;\n   }\n\n   if (outSize) {\n      LARGE_INTEGER size;\n      GetFileSizeEx(fileHandle, &size);\n      *outSize = static_cast<size_t>(size.QuadPart);\n   }\n\n   auto windowsHandle = new WindowsMapFileHandle { };\n   windowsHandle->mappingHandle = mapHandle;\n   windowsHandle->fileHandle = fileHandle;\n   return reinterpret_cast<MapFileHandle>(windowsHandle);\n}\n\n\nbool\ncloseMemoryMappedFile(MapFileHandle handle)\n{\n   auto windowsHandle = reinterpret_cast<WindowsMapFileHandle *>(handle);\n\n   if (windowsHandle->fileHandle) {\n      if (!CloseHandle(windowsHandle->fileHandle)) {\n         gLog->error(\"closeMemoryMappedFile({}) close file handle failed with error: {}\",\n                     handle, GetLastError());\n      }\n\n      windowsHandle->fileHandle = NULL;\n   }\n\n   if (windowsHandle->mappingHandle) {\n      if (!CloseHandle(windowsHandle->mappingHandle)) {\n         gLog->error(\"closeMemoryMappedFile({}) close map handle failed with error: {}\",\n                     handle, GetLastError());\n         return false;\n      }\n\n      windowsHandle->mappingHandle = NULL;\n   }\n\n   delete windowsHandle;\n   return true;\n}\n\n\nvoid *\nmapViewOfFile(MapFileHandle handle,\n              ProtectFlags flags,\n              size_t offset,\n              size_t size,\n              void *dst)\n{\n   auto windowsHandle = reinterpret_cast<WindowsMapFileHandle *>(handle);\n   auto access = flagsToFileMap(flags);\n   auto offsetLo = static_cast<DWORD>(offset & 0xFFFFFFFFu);\n   auto offsetHi = static_cast<DWORD>((offset >> 32) & 0xFFFFFFFFu);\n   auto result = MapViewOfFileEx(reinterpret_cast<HANDLE>(windowsHandle->mappingHandle),\n                                 access,\n                                 offsetHi,\n                                 offsetLo,\n                                 size,\n                                 dst);\n\n   if (result == nullptr) {\n      gLog->error(\"mapViewOfFile(offset: 0x{:X}, size: 0x{:X}, dst: 0x{:X}) failed with error: {}\",\n                  offset, size, dst, GetLastError());\n   }\n\n   return result;\n}\n\n\nbool\nunmapViewOfFile(void *view,\n                size_t size)\n{\n   if (!UnmapViewOfFile(view)) {\n      gLog->error(\"unmapViewOfFile(view: 0x{:X}, size: 0x{:X}) failed with error: {}\",\n                  view, size, GetLastError());\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\nreserveMemory(uintptr_t address,\n              size_t size)\n{\n   auto baseAddress = reinterpret_cast<LPVOID>(address);\n   auto result = VirtualAlloc(baseAddress, size, MEM_RESERVE, PAGE_NOACCESS);\n\n   if (result != baseAddress) {\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\nfreeMemory(uintptr_t address,\n           size_t size)\n{\n   auto baseAddress = reinterpret_cast<LPVOID>(address);\n\n   if (!VirtualFree(baseAddress, 0, MEM_RELEASE)) {\n      gLog->error(\"freeMemory(address: {}, size: {}) failed with error: {}\",\n                  address, size, GetLastError());\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\ncommitMemory(uintptr_t address,\n             size_t size,\n             ProtectFlags flags)\n{\n   auto baseAddress = reinterpret_cast<LPVOID>(address);\n   auto result = VirtualAlloc(baseAddress,\n                              size,\n                              MEM_COMMIT,\n                              flagsToPageProtect(flags));\n\n   if (result != baseAddress) {\n      gLog->error(\"commitMemory(address: 0x{:X}, size: 0x{:X}, flags: {}) failed with error: {}\",\n                  address, size, static_cast<int>(flags), GetLastError());\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\nuncommitMemory(uintptr_t address,\n               size_t size)\n{\n   auto baseAddress = reinterpret_cast<LPVOID>(address);\n\n   if (!VirtualFree(baseAddress, size, MEM_DECOMMIT)) {\n      gLog->error(\"uncommitMemory(address: 0x{:X}, size: 0x{:X}) failed with error: {}\",\n                  address, size, GetLastError());\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\nprotectMemory(uintptr_t address,\n              size_t size,\n              ProtectFlags flags)\n{\n   auto baseAddress = reinterpret_cast<LPVOID>(address);\n   DWORD oldProtect;\n\n   if (!VirtualProtect(baseAddress, size, flagsToPageProtect(flags), &oldProtect)) {\n      gLog->error(\"protectMemory(address: 0x{:X}, size: 0x{:X}, flags: {}) failed with error: {}\",\n                  address, size, static_cast<int>(flags), GetLastError());\n      return false;\n   }\n\n   return true;\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_win_socket.cpp",
    "content": "#include \"platform_socket.h\"\n\n#ifdef PLATFORM_WINDOWS\nnamespace platform\n{\n\nbool socketWouldBlock(int result)\n{\n   return (result < 0 && WSAGetLastError() == WSAEWOULDBLOCK);\n}\n\nint socketSetBlocking(Socket socket, bool blocking)\n{\n   u_long iMode = blocking ? 0 : 1;\n   return ioctlsocket(socket, FIONBIO, &iMode);\n}\n\nint socketClose(Socket socket)\n{\n   return closesocket(socket);\n}\n\n} // namespace platform\n#endif\n"
  },
  {
    "path": "src/common/src/platform_win_stacktrace.cpp",
    "content": "#include \"platform.h\"\n\n#ifdef PLATFORM_WINDOWS\n#include \"platform_stacktrace.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <fmt/format.h>\n#include <iterator>\n#include <memory>\n#include <Windows.h>\n#include <Dbghelp.h>\n\nnamespace platform\n{\n\nstruct StackTrace\n{\n   void *data[0x100];\n   uint16_t frames;\n};\n\nstruct MySymbol : SYMBOL_INFO\n{\n   CHAR NameExt[MAX_SYM_NAME];\n};\n\nStackTrace *\ncaptureStackTrace()\n{\n   auto trace = new StackTrace();\n   trace->frames = CaptureStackBackTrace(0, 0x100, trace->data, NULL);\n   return trace;\n}\n\nvoid\nfreeStackTrace(StackTrace *trace)\n{\n   delete trace;\n}\n\nstd::string\nformatStackTrace(StackTrace *trace)\n{\n   auto process = GetCurrentProcess();\n   static bool symInitialise = false;\n\n   if (!symInitialise) {\n      SymInitialize(process, NULL, TRUE);\n      symInitialise = true;\n   }\n\n   auto symbol = std::make_unique<MySymbol>();\n   symbol->MaxNameLen = MAX_SYM_NAME;\n   symbol->SizeOfStruct = sizeof(SYMBOL_INFO);\n\n   fmt::memory_buffer out;\n\n   for (auto i = 0u; i < trace->frames; ++i) {\n      SymFromAddr(process, (DWORD64)trace->data[i], 0, symbol.get());\n      fmt::format_to(std::back_inserter(out), \"{}: {} - 0x{:X}x\\n\",\n                     trace->frames - i - 1,\n                     (const char*)symbol->Name,\n                     symbol->Address);\n   }\n\n   return out.data();\n}\n\nvoid\nprintStackTrace(StackTrace *trace)\n{\n   auto str = formatStackTrace(trace);\n   OutputDebugStringA(str.c_str());\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_win_thread.cpp",
    "content": "#include \"platform.h\"\n\n#ifdef PLATFORM_WINDOWS\n#include \"platform_thread.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\n\nstatic const DWORD MS_VC_EXCEPTION = 0x406D1388;\n\n#pragma pack(push, 8)\n\ntypedef struct tagTHREADNAME_INFO\n{\n   DWORD dwType;     // Must be 0x1000.\n   LPCSTR szName;    // Pointer to name (in user addr space).\n   DWORD dwThreadID; // Thread ID (-1=caller thread).\n   DWORD dwFlags;    // Reserved for future use, must be zero.\n} THREADNAME_INFO;\n\n#pragma pack(pop)\n\nnamespace platform\n{\n\nvoid\nsetThreadName(std::thread *thread,\n              const std::string &threadName)\n{\n   DWORD dwThreadID = ::GetThreadId(static_cast<HANDLE>(thread->native_handle()));\n\n   THREADNAME_INFO info;\n   info.dwType = 0x1000;\n   info.szName = threadName.c_str();\n   info.dwThreadID = dwThreadID;\n   info.dwFlags = 0;\n\n   __try {\n      RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);\n   } __except (EXCEPTION_EXECUTE_HANDLER)\n   {\n   }\n}\n\nvoid\nexitThread(int result)\n{\n   ExitThread(result);\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/src/platform_win_time.cpp",
    "content": "#include \"platform.h\"\n\n#ifdef PLATFORM_WINDOWS\n#include \"platform_time.h\"\n\nnamespace platform\n{\n\ntm\nlocaltime(const std::time_t& time)\n{\n   std::tm tm_snapshot;\n   localtime_s(&tm_snapshot, &time);\n   return tm_snapshot;\n}\n\ntime_t\nmake_gm_time(std::tm time)\n{\n   return _mkgmtime(&time);\n}\n\n} // namespace platform\n\n#endif\n"
  },
  {
    "path": "src/common/structsize.h",
    "content": "#pragma once\n#include <cstddef>\n#include \"platform.h\"\n\n// Workaround weird macro concat ## behaviour\n#define PP_CAT(a, b) PP_CAT_I(a, b)\n#define PP_CAT_I(a, b) PP_CAT_II(~, a ## b)\n#define PP_CAT_II(p, res) res\n\n// Ensure our structs are correct size & offsets to match WiiU\n#define CHECK_SIZE(Type, Size) \\\n   static_assert(sizeof(Type) == Size, \\\n                 #Type \" must be \" #Size \" bytes\")\n\n#define CHECK_OFFSET(Type, Offset, Field) \\\n   static_assert(offsetof(Type, Field) == Offset, \\\n                 #Type \"::\" #Field \" must be at offset \" #Offset)\n\n#define _CHECK_MEMBER_OFFSET_BEG \\\n   void PP_CAT(__verifyMemberOffsets, __COUNTER__) () {\n\n#define _CHECK_MEMBER_OFFSET_END \\\n   }\n\n#ifdef PLATFORM_WINDOWS\n#define CHECK_MEMBER_OFFSET_BEG _CHECK_MEMBER_OFFSET_BEG\n#define CHECK_MEMBER_OFFSET_END _CHECK_MEMBER_OFFSET_END\n#else\n#define CHECK_MEMBER_OFFSET_BEG \\\n   _Pragma(\"GCC diagnostic push\") \\\n   _Pragma(\"GCC diagnostic ignored \\\"-Winvalid-offsetof\\\"\") \\\n   _CHECK_MEMBER_OFFSET_BEG\n#define CHECK_MEMBER_OFFSET_END \\\n   _CHECK_MEMBER_OFFSET_END \\\n   _Pragma(\"GCC diagnostic pop\")\n#endif\n\n// TODO: Figure out how to implement this, might be impossible?\n#define CHECK_BIT_OFFSET(Type, Offset, Field)\n\n// Allow us to easily add UNKNOWN / PADDING bytes into our structs,\n// generates unique variable names using __COUNTER__\n#define UNKNOWN(Size) char PP_CAT(__unk, __COUNTER__) [Size]\n#define PADDING(Size) UNKNOWN(Size)\n\n#define UNKNOWN_ARGS void\n#define UNKNOWN_SIZE(x) //x\n"
  },
  {
    "path": "src/common/strutils.h",
    "content": "#pragma once\n#include \"platform.h\"\n\n#include <algorithm>\n#include <cstdarg>\n#include <cstdio>\n#include <cstring>\n#include <string>\n#include <string_view>\n#include <vector>\n\n#ifdef PLATFORM_WINDOWS\n   #define strdup _strdup\n#endif\n\n// Replace all occurences of a character in a string\ninline void\nreplace_all(std::string &source, char find, char replace)\n{\n   std::string::size_type offset = 0;\n\n   while ((offset = source.find(find, offset)) != std::string::npos) {\n      source[offset] = replace;\n      ++offset;\n   }\n}\n\n// Split a string by a character\ninline void\nsplit_string(const std::string_view &source,\n             char delimiter,\n             std::vector<std::string> &result)\n{\n   std::string_view::size_type offset = 0, last = 0;\n\n   if (source.at(0) == delimiter) {\n      offset = last = 1;\n   }\n\n   while ((offset = source.find(delimiter, offset)) != std::string::npos) {\n      if (offset - last > 0) {\n         result.push_back(std::string { source.substr(last, offset - last) });\n      }\n\n      last = ++offset;\n   }\n\n   result.push_back(std::string { source.substr(last, offset - last) });\n}\n\n// Joins a vector of strings into one string using a delimeter\ntemplate<typename IteratorType>\ninline void\njoin_string(IteratorType begin, IteratorType end, char delim, std::string &out)\n{\n   bool first = true;\n\n   for (auto itr = begin; itr != end; ++itr) {\n      if (first) {\n         out += *itr;\n         first = false;\n      } else {\n         out += delim + *itr;\n      }\n   }\n}\n\n// Returns true if source begins with prefix\ninline bool\nbegins_with(const std::string_view &source, const std::string_view &prefix)\n{\n   if (prefix.size() > source.size()) {\n      return false;\n   } else {\n      return std::equal(prefix.begin(), prefix.end(), source.begin());\n   }\n}\n\n// Returns true if source ends with suffix\ninline bool\nends_with(const std::string_view &source, const std::string_view &suffix)\n{\n   if (suffix.size() > source.size()) {\n      return false;\n   } else {\n      return std::equal(suffix.rbegin(), suffix.rend(), source.rbegin());\n   }\n}\n\n// Case insensitive string compare\ninline bool\niequals(const std::string_view &a, const std::string_view &b)\n{\n   return std::equal(a.begin(), a.end(),\n                     b.begin(), b.end(),\n                     [](char a, char b) {\n                        return tolower(a) == tolower(b);\n                     });\n}\n\n// A strncpy which does not warn on Windows\ninline void\nstring_copy(char *dst,\n            size_t dstSize,\n            const char *src,\n            size_t maxCount)\n{\n#ifdef PLATFORM_WINDOWS\n   strncpy_s(dst, dstSize, src, maxCount);\n#else\n   std::strncpy(dst, src, dstSize);\n#endif\n}\n\ninline void\nstring_copy(char *dst,\n            const char *src,\n            size_t maxCount)\n{\n   string_copy(dst, maxCount, src, maxCount);\n}\n\ntemplate<typename CharT, typename DstCharT, typename SrcCharT>\ninline void\nstring_copy(DstCharT *dst,\n            size_t dstSize,\n            const SrcCharT *src,\n            size_t maxCount)\n{\n   if (dstSize <= maxCount) {\n      maxCount = dstSize - 1;\n   }\n\n   auto i = size_t { 0 };\n   while (src[i] && i < maxCount) {\n      dst[i] = static_cast<DstCharT>(src[i]);\n      i++;\n   }\n\n   dst[i] = static_cast<CharT>('\\0');\n}\n\ninline std::string_view\ntrim_view(std::string_view str)\n{\n   while (!str.empty() && std::isspace(str[0])) {\n      str.remove_prefix(1);\n   }\n\n   while (!str.empty() && std::isspace(str.back())) {\n      str.remove_suffix(1);\n   }\n\n   return str;\n}\n\ninline std::string\ntrim(std::string_view str)\n{\n   while (!str.empty() && std::isspace(str[0])) {\n      str.remove_prefix(1);\n   }\n\n   while (!str.empty() && std::isspace(str.back())) {\n      str.remove_suffix(1);\n   }\n\n   return std::string { str };\n}\n"
  },
  {
    "path": "src/common/teenyheap.h",
    "content": "#pragma once\n#include \"align.h\"\n#include \"decaf_assert.h\"\n#include <algorithm>\n#include <map>\n#include <mutex>\n#include <vector>\n\nclass TeenyHeap\n{\nprivate:\n   struct MemoryBlock\n   {\n      uint8_t *start;\n      size_t size;\n   };\n\npublic:\n   TeenyHeap() :\n      mBuffer(nullptr),\n      mSize(0)\n   {\n   }\n\n   TeenyHeap(void *buffer, size_t size)\n   {\n      reset(buffer, size);\n   }\n\n   void\n   reset(void *buffer, size_t size)\n   {\n      mAllocatedBlocks.clear();\n      mFreeBlocks.clear();\n\n      mBuffer = static_cast<uint8_t *>(buffer);\n      mSize = size;\n      mFreeBlocks.emplace_back(MemoryBlock { mBuffer, mSize });\n   }\n\n   size_t\n   getLargestFreeSize()\n   {\n      auto largest = size_t { 0 };\n\n      for (auto &block : mFreeBlocks) {\n         largest = std::max(largest, block.size);\n      }\n\n      return largest;\n   }\n\n   size_t\n   getTotalFreeSize()\n   {\n      auto total = size_t { 0 };\n\n      for (auto &block : mFreeBlocks) {\n         total += block.size;\n      }\n\n      return total;\n   }\n\n   void *\n   alloc(size_t size, size_t alignment = 4)\n   {\n      std::unique_lock<std::mutex> lock(mMutex);\n      auto adjSize = size;\n      auto block = mFreeBlocks.begin();\n\n      for (block = mFreeBlocks.begin(); block != mFreeBlocks.end(); ++block) {\n         auto alignedDiff = align_up(block->start, alignment) - block->start;\n         if (block->size - alignedDiff >= adjSize) {\n            adjSize += alignedDiff;\n            break;\n         }\n      }\n\n      if (block == mFreeBlocks.end()) {\n         return nullptr;\n      }\n\n      auto start = block->start;\n      block->start += adjSize;\n      block->size -= adjSize;\n\n      // Erase block if it gets too small\n      if (block->size <= sizeof(MemoryBlock) + 4) {\n         adjSize += block->size;\n         mFreeBlocks.erase(block);\n      }\n\n      // Allocate block\n      auto alignedStart = align_up(start, alignment);\n      mAllocatedBlocks.emplace(alignedStart, MemoryBlock { start, adjSize });\n\n      return alignedStart;\n   }\n\n   void\n   free(void *ptr)\n   {\n      std::unique_lock<std::mutex> lock(mMutex);\n      auto ucptr = static_cast<uint8_t*>(ptr);\n      auto itr = mAllocatedBlocks.find(ucptr);\n      decaf_check(itr != mAllocatedBlocks.end());\n\n      releaseBlock(itr->second);\n      mAllocatedBlocks.erase(itr);\n   }\n\nprotected:\n   void\n   releaseBlock(MemoryBlock block)\n   {\n      auto blockStart = block.start;\n      auto blockEnd = block.start + block.size;\n\n      for (auto &free : mFreeBlocks) {\n         auto freeStart = free.start;\n         auto freeEnd = free.start + free.size;\n\n         if (blockStart == freeEnd) {\n            free.size += block.size;\n            return;\n         }\n\n         if (blockEnd == freeStart) {\n            free.start = blockStart;\n            free.size += block.size;\n            return;\n         }\n      }\n\n      mFreeBlocks.emplace_back(block);\n   }\n\n   uint8_t *mBuffer;\n   size_t mSize;\n   std::vector<MemoryBlock> mFreeBlocks;\n   std::map<uint8_t *, MemoryBlock> mAllocatedBlocks;\n   std::mutex mMutex;\n};\n"
  },
  {
    "path": "src/common/tga_encoder.cpp",
    "content": "#include \"log.h\"\n#include \"tga_encoder.h\"\n#include <fstream>\n\nnamespace tga\n{\n\nbool\nwriteFile(const std::string &filename,\n          uint32_t bpp,\n          uint32_t alphaBits,\n          uint32_t width,\n          uint32_t height,\n          void *data)\n{\n   std::ofstream out { filename, std::ofstream::binary };\n   if (!out.is_open()) {\n      gLog->error(\"Could not open {} for writing\", filename);\n      return false;\n   }\n\n   FileHeader header;\n   std::memset(&header, 0, sizeof(FileHeader));\n   header.imageType = FileHeader::TrueColour;\n   header.width = static_cast<uint16_t>(width);\n   header.height = static_cast<uint16_t>(height);\n   header.bpp = static_cast<uint8_t>(bpp);\n   header.descriptor = alphaBits & 0b1111;\n\n   out.write(reinterpret_cast<char *>(&header), sizeof(FileHeader));\n   out.write(reinterpret_cast<char *>(data), width * height * (bpp / 8));\n   return true;\n}\n\n} // namespace tga\n"
  },
  {
    "path": "src/common/tga_encoder.h",
    "content": "#pragma once\n#include <cstdint>\n#include <string>\n\nnamespace tga\n{\n\n#pragma pack(push, 1)\n\nstruct FileHeader\n{\n   enum ImageType : uint8_t\n   {\n      None = 0,\n      ColourMapped = 1,\n      TrueColour = 2,\n      Grayscale = 3,\n      ColourMappedRLE = 9,\n      TrueColourRLE = 10,\n      GrayscaleRLE = 11,\n   };\n\n   uint8_t id;\n   uint8_t colorMapType;\n   uint8_t imageType;\n\n   struct\n   {\n      uint16_t firstEntry;\n      uint16_t numEntries;\n      uint8_t entrySize;\n   } colorMap;\n\n   uint16_t x;\n   uint16_t y;\n   uint16_t width;\n   uint16_t height;\n   uint8_t bpp;\n   uint8_t descriptor;\n};\n\n#pragma pack(pop)\n\nbool\nwriteFile(const std::string &filename,\n          uint32_t bpp,\n          uint32_t alphaBits,\n          uint32_t width,\n          uint32_t height,\n          void *data);\n\n} // namespace tga\n"
  },
  {
    "path": "src/common/type_list.h",
    "content": "#pragma once\n\ntemplate<typename...>\nstruct type_list\n{\n};\n"
  },
  {
    "path": "src/common/type_traits.h",
    "content": "#pragma once\n#include <type_traits>\n\n// Same as std::underlying_type but works for non-enum Types\ntemplate<class T, bool = std::is_enum<T>::value>\nstruct safe_underlying_type : std::underlying_type<T> { };\n\ntemplate<class T>\nstruct safe_underlying_type<T, false>\n{\n   using type = T;\n};\n\n// Maps bool value to a std::bool_constant type\ntemplate<bool>\nstruct is_true;\n\ntemplate<>\nstruct is_true<false> : std::false_type { };\n\ntemplate<>\nstruct is_true<true> : std::true_type { };\n"
  },
  {
    "path": "src/common/vulkan_hpp.h",
    "content": "#pragma once\n\n#include <vulkan/vk_platform.h>\n\n#ifdef VK_USE_PLATFORM_XLIB_KHR\n#include <X11/Xlib.h>\n#include <X11/Xutil.h>\n#endif\n\n#include <vulkan/vulkan.hpp>\n\n#if defined(VK_USE_PLATFORM_XLIB_KHR)\n// Are you fucking serious X headers???\n#undef None\n#undef Status\n#undef True\n#undef False\n#undef Bool\n#undef RootWindow\n#undef CurrentTime\n#undef Success\n#undef DestroyAll\n#undef COUNT\n#undef CREATE\n#undef DeviceAdded\n#undef DeviceRemoved\n#endif\n"
  },
  {
    "path": "src/common/xxhash.c",
    "content": "/*\n*  xxHash - Fast Hash algorithm\n*  Copyright (C) 2012-2016, Yann Collet\n*\n*  BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)\n*\n*  Redistribution and use in source and binary forms, with or without\n*  modification, are permitted provided that the following conditions are\n*  met:\n*\n*  * Redistributions of source code must retain the above copyright\n*  notice, this list of conditions and the following disclaimer.\n*  * Redistributions in binary form must reproduce the above\n*  copyright notice, this list of conditions and the following disclaimer\n*  in the documentation and/or other materials provided with the\n*  distribution.\n*\n*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n*  \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n*  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n*  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n*  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n*  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n*  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n*  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n*  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*\n*  You can contact the author at :\n*  - xxHash homepage: http://www.xxhash.com\n*  - xxHash source repository : https://github.com/Cyan4973/xxHash\n*/\n\n\n/* *************************************\n*  Tuning parameters\n***************************************/\n/*!XXH_FORCE_MEMORY_ACCESS :\n * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable.\n * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal.\n * The below switch allow to select different access method for improved performance.\n * Method 0 (default) : use `memcpy()`. Safe and portable.\n * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable).\n *            This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`.\n * Method 2 : direct access. This method doesn't depend on compiler but violate C standard.\n *            It can generate buggy code on targets which do not support unaligned memory accesses.\n *            But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6)\n * See http://stackoverflow.com/a/32095106/646947 for details.\n * Prefer these methods in priority order (0 > 1 > 2)\n */\n#ifndef XXH_FORCE_MEMORY_ACCESS   /* can be defined externally, on command line for example */\n#  if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \\\n                        || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \\\n                        || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) )\n#    define XXH_FORCE_MEMORY_ACCESS 2\n#  elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || \\\n  (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \\\n                    || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \\\n                    || defined(__ARM_ARCH_7S__) ))\n#    define XXH_FORCE_MEMORY_ACCESS 1\n#  endif\n#endif\n\n /*!XXH_ACCEPT_NULL_INPUT_POINTER :\n  * If input pointer is NULL, xxHash default behavior is to dereference it, triggering a segfault.\n  * When this macro is enabled, xxHash actively checks input for null pointer.\n  * It it is, result for null input pointers is the same as a null-length input.\n  */\n#ifndef XXH_ACCEPT_NULL_INPUT_POINTER   /* can be defined externally */\n#  define XXH_ACCEPT_NULL_INPUT_POINTER 0\n#endif\n\n  /*!XXH_FORCE_NATIVE_FORMAT :\n   * By default, xxHash library provides endian-independent Hash values, based on little-endian convention.\n   * Results are therefore identical for little-endian and big-endian CPU.\n   * This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format.\n   * Should endian-independence be of no importance for your application, you may set the #define below to 1,\n   * to improve speed for Big-endian CPU.\n   * This option has no impact on Little_Endian CPU.\n   */\n#ifndef XXH_FORCE_NATIVE_FORMAT   /* can be defined externally */\n#  define XXH_FORCE_NATIVE_FORMAT 0\n#endif\n\n   /*!XXH_FORCE_ALIGN_CHECK :\n    * This is a minor performance trick, only useful with lots of very small keys.\n    * It means : check for aligned/unaligned input.\n    * The check costs one initial branch per hash;\n    * set it to 0 when the input is guaranteed to be aligned,\n    * or when alignment doesn't matter for performance.\n    */\n#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */\n#  if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64)\n#    define XXH_FORCE_ALIGN_CHECK 0\n#  else\n#    define XXH_FORCE_ALIGN_CHECK 1\n#  endif\n#endif\n\n\n    /* *************************************\n    *  Includes & Memory related functions\n    ***************************************/\n    /*! Modify the local functions below should you wish to use some other memory routines\n    *   for malloc(), free() */\n#include <stdlib.h>\nstatic void* XXH_malloc(size_t s) { return malloc(s); }\nstatic void  XXH_free(void* p) { free(p); }\n/*! and for memcpy() */\n#include <string.h>\nstatic void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest, src, size); }\n\n#include <assert.h>   /* assert */\n\n#define XXH_STATIC_LINKING_ONLY\n#include \"xxhash.h\"\n\n\n/* *************************************\n*  Compiler Specific Options\n***************************************/\n#ifdef _MSC_VER    /* Visual Studio */\n#  pragma warning(disable : 4127)      /* disable: C4127: conditional expression is constant */\n#  define FORCE_INLINE static __forceinline\n#else\n#  if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L   /* C99 */\n#    ifdef __GNUC__\n#      define FORCE_INLINE static inline __attribute__((always_inline))\n#    else\n#      define FORCE_INLINE static inline\n#    endif\n#  else\n#    define FORCE_INLINE static\n#  endif /* __STDC_VERSION__ */\n#endif\n\n\n/* *************************************\n*  Basic Types\n***************************************/\n#ifndef MEM_MODULE\n# if !defined (__VMS) \\\n  && (defined (__cplusplus) \\\n  || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )\n#   include <stdint.h>\ntypedef uint8_t  BYTE;\ntypedef uint16_t U16;\ntypedef uint32_t U32;\n# else\ntypedef unsigned char      BYTE;\ntypedef unsigned short     U16;\ntypedef unsigned int       U32;\n# endif\n#endif\n\n#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))\n\n/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */\nstatic U32 XXH_read32(const void* memPtr) { return *(const U32*)memPtr; }\n\n#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))\n\n/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */\n/* currently only defined for gcc and icc */\ntypedef union { U32 u32; } __attribute__((packed)) unalign;\nstatic U32 XXH_read32(const void* ptr) { return ((const unalign*)ptr)->u32; }\n\n#else\n\n/* portable and safe solution. Generally efficient.\n * see : http://stackoverflow.com/a/32095106/646947\n */\nstatic U32 XXH_read32(const void* memPtr)\n{\n   U32 val;\n   memcpy(&val, memPtr, sizeof(val));\n   return val;\n}\n\n#endif   /* XXH_FORCE_DIRECT_MEMORY_ACCESS */\n\n\n/* ****************************************\n*  Compiler-specific Functions and Macros\n******************************************/\n#define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)\n\n/* Note : although _rotl exists for minGW (GCC under windows), performance seems poor */\n#if defined(_MSC_VER)\n#  define XXH_rotl32(x,r) _rotl(x,r)\n#  define XXH_rotl64(x,r) _rotl64(x,r)\n#else\n#  define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r)))\n#  define XXH_rotl64(x,r) ((x << r) | (x >> (64 - r)))\n#endif\n\n#if defined(_MSC_VER)     /* Visual Studio */\n#  define XXH_swap32 _byteswap_ulong\n#elif XXH_GCC_VERSION >= 403\n#  define XXH_swap32 __builtin_bswap32\n#else\nstatic U32 XXH_swap32(U32 x)\n{\n   return  ((x << 24) & 0xff000000) |\n      ((x << 8) & 0x00ff0000) |\n      ((x >> 8) & 0x0000ff00) |\n      ((x >> 24) & 0x000000ff);\n}\n#endif\n\n\n/* *************************************\n*  Architecture Macros\n***************************************/\ntypedef enum { XXH_bigEndian = 0, XXH_littleEndian = 1 } XXH_endianess;\n\n/* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */\n#ifndef XXH_CPU_LITTLE_ENDIAN\nstatic int XXH_isLittleEndian(void)\n{\n   const union { U32 u; BYTE c[4]; } one = { 1 };   /* don't use static : performance detrimental  */\n   return one.c[0];\n}\n#   define XXH_CPU_LITTLE_ENDIAN   XXH_isLittleEndian()\n#endif\n\n\n/* ***************************\n*  Memory reads\n*****************************/\ntypedef enum { XXH_aligned, XXH_unaligned } XXH_alignment;\n\nFORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align)\n{\n   if (align == XXH_unaligned)\n      return endian == XXH_littleEndian ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr));\n   else\n      return endian == XXH_littleEndian ? *(const U32*)ptr : XXH_swap32(*(const U32*)ptr);\n}\n\nFORCE_INLINE U32 XXH_readLE32(const void* ptr, XXH_endianess endian)\n{\n   return XXH_readLE32_align(ptr, endian, XXH_unaligned);\n}\n\nstatic U32 XXH_readBE32(const void* ptr)\n{\n   return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr);\n}\n\n\n/* *************************************\n*  Macros\n***************************************/\n#define XXH_STATIC_ASSERT(c)  { enum { XXH_sa = 1/(int)(!!(c)) }; }  /* use after variable declarations */\nXXH_PUBLIC_API unsigned XXH_versionNumber(void) { return XXH_VERSION_NUMBER; }\n\n\n/* *******************************************************************\n*  32-bit hash functions\n*********************************************************************/\nstatic const U32 PRIME32_1 = 2654435761U;   /* 0b10011110001101110111100110110001 */\nstatic const U32 PRIME32_2 = 2246822519U;   /* 0b10000101111010111100101001110111 */\nstatic const U32 PRIME32_3 = 3266489917U;   /* 0b11000010101100101010111000111101 */\nstatic const U32 PRIME32_4 = 668265263U;   /* 0b00100111110101001110101100101111 */\nstatic const U32 PRIME32_5 = 374761393U;   /* 0b00010110010101100110011110110001 */\n\nstatic U32 XXH32_round(U32 seed, U32 input)\n{\n   seed += input * PRIME32_2;\n   seed = XXH_rotl32(seed, 13);\n   seed *= PRIME32_1;\n   return seed;\n}\n\n/* mix all bits */\nstatic U32 XXH32_avalanche(U32 h32)\n{\n   h32 ^= h32 >> 15;\n   h32 *= PRIME32_2;\n   h32 ^= h32 >> 13;\n   h32 *= PRIME32_3;\n   h32 ^= h32 >> 16;\n   return(h32);\n}\n\n#define XXH_get32bits(p) XXH_readLE32_align(p, endian, align)\n\nstatic U32\nXXH32_finalize(U32 h32, const void* ptr, size_t len,\n               XXH_endianess endian, XXH_alignment align)\n\n{\n   const BYTE* p = (const BYTE*)ptr;\n\n#define PROCESS1               \\\n    h32 += (*p++) * PRIME32_5; \\\n    h32 = XXH_rotl32(h32, 11) * PRIME32_1 ;\n\n#define PROCESS4                         \\\n    h32 += XXH_get32bits(p) * PRIME32_3; \\\n    p+=4;                                \\\n    h32  = XXH_rotl32(h32, 17) * PRIME32_4 ;\n\n   switch (len & 15)  /* or switch(bEnd - p) */\n   {\n   case 12:      PROCESS4;\n      /* fallthrough */\n   case 8:       PROCESS4;\n      /* fallthrough */\n   case 4:       PROCESS4;\n      return XXH32_avalanche(h32);\n\n   case 13:      PROCESS4;\n      /* fallthrough */\n   case 9:       PROCESS4;\n      /* fallthrough */\n   case 5:       PROCESS4;\n      PROCESS1;\n      return XXH32_avalanche(h32);\n\n   case 14:      PROCESS4;\n      /* fallthrough */\n   case 10:      PROCESS4;\n      /* fallthrough */\n   case 6:       PROCESS4;\n      PROCESS1;\n      PROCESS1;\n      return XXH32_avalanche(h32);\n\n   case 15:      PROCESS4;\n      /* fallthrough */\n   case 11:      PROCESS4;\n      /* fallthrough */\n   case 7:       PROCESS4;\n      /* fallthrough */\n   case 3:       PROCESS1;\n      /* fallthrough */\n   case 2:       PROCESS1;\n      /* fallthrough */\n   case 1:       PROCESS1;\n      /* fallthrough */\n   case 0:       return XXH32_avalanche(h32);\n   }\n   assert(0);\n   return h32;   /* reaching this point is deemed impossible */\n}\n\n\nFORCE_INLINE U32\nXXH32_endian_align(const void* input, size_t len, U32 seed,\n                   XXH_endianess endian, XXH_alignment align)\n{\n   const BYTE* p = (const BYTE*)input;\n   const BYTE* bEnd = p + len;\n   U32 h32;\n\n#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)\n   if (p == NULL) {\n      len = 0;\n      bEnd = p = (const BYTE*)(size_t)16;\n   }\n#endif\n\n   if (len >= 16) {\n      const BYTE* const limit = bEnd - 15;\n      U32 v1 = seed + PRIME32_1 + PRIME32_2;\n      U32 v2 = seed + PRIME32_2;\n      U32 v3 = seed + 0;\n      U32 v4 = seed - PRIME32_1;\n\n      do {\n         v1 = XXH32_round(v1, XXH_get32bits(p)); p += 4;\n         v2 = XXH32_round(v2, XXH_get32bits(p)); p += 4;\n         v3 = XXH32_round(v3, XXH_get32bits(p)); p += 4;\n         v4 = XXH32_round(v4, XXH_get32bits(p)); p += 4;\n      } while (p < limit);\n\n      h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7)\n         + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18);\n   } else {\n      h32 = seed + PRIME32_5;\n   }\n\n   h32 += (U32)len;\n\n   return XXH32_finalize(h32, p, len & 15, endian, align);\n}\n\n\nXXH_PUBLIC_API unsigned int XXH32(const void* input, size_t len, unsigned int seed)\n{\n#if 0\n   /* Simple version, good for code maintenance, but unfortunately slow for small inputs */\n   XXH32_state_t state;\n   XXH32_reset(&state, seed);\n   XXH32_update(&state, input, len);\n   return XXH32_digest(&state);\n#else\n   XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;\n\n   if (XXH_FORCE_ALIGN_CHECK) {\n      if ((((size_t)input) & 3) == 0) {   /* Input is 4-bytes aligned, leverage the speed benefit */\n         if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)\n            return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned);\n         else\n            return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned);\n      }\n   }\n\n   if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)\n      return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned);\n   else\n      return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned);\n#endif\n}\n\n\n\n/*======   Hash streaming   ======*/\n\nXXH_PUBLIC_API XXH32_state_t* XXH32_createState(void)\n{\n   return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t));\n}\nXXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr)\n{\n   XXH_free(statePtr);\n   return XXH_OK;\n}\n\nXXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState)\n{\n   memcpy(dstState, srcState, sizeof(*dstState));\n}\n\nXXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed)\n{\n   XXH32_state_t state;   /* using a local state to memcpy() in order to avoid strict-aliasing warnings */\n   memset(&state, 0, sizeof(state));\n   state.v1 = seed + PRIME32_1 + PRIME32_2;\n   state.v2 = seed + PRIME32_2;\n   state.v3 = seed + 0;\n   state.v4 = seed - PRIME32_1;\n   /* do not write into reserved, planned to be removed in a future version */\n   memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved));\n   return XXH_OK;\n}\n\n\nFORCE_INLINE XXH_errorcode\nXXH32_update_endian(XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian)\n{\n   if (input == NULL)\n#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)\n      return XXH_OK;\n#else\n      return XXH_ERROR;\n#endif\n\n   {\n      const BYTE* p = (const BYTE*)input;\n      const BYTE* const bEnd = p + len;\n\n      state->total_len_32 += (unsigned)len;\n      state->large_len |= (len >= 16) | (state->total_len_32 >= 16);\n\n      if (state->memsize + len < 16) {   /* fill in tmp buffer */\n         XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len);\n         state->memsize += (unsigned)len;\n         return XXH_OK;\n      }\n\n      if (state->memsize) {   /* some data left from previous update */\n         XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16 - state->memsize);\n         {\n            const U32* p32 = state->mem32;\n            state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++;\n            state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++;\n            state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++;\n            state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian));\n         }\n         p += 16 - state->memsize;\n         state->memsize = 0;\n      }\n\n      if (p <= bEnd - 16) {\n         const BYTE* const limit = bEnd - 16;\n         U32 v1 = state->v1;\n         U32 v2 = state->v2;\n         U32 v3 = state->v3;\n         U32 v4 = state->v4;\n\n         do {\n            v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p += 4;\n            v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p += 4;\n            v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p += 4;\n            v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p += 4;\n         } while (p <= limit);\n\n         state->v1 = v1;\n         state->v2 = v2;\n         state->v3 = v3;\n         state->v4 = v4;\n      }\n\n      if (p < bEnd) {\n         XXH_memcpy(state->mem32, p, (size_t)(bEnd - p));\n         state->memsize = (unsigned)(bEnd - p);\n      }\n   }\n\n   return XXH_OK;\n}\n\n\nXXH_PUBLIC_API XXH_errorcode XXH32_update(XXH32_state_t* state_in, const void* input, size_t len)\n{\n   XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;\n\n   if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)\n      return XXH32_update_endian(state_in, input, len, XXH_littleEndian);\n   else\n      return XXH32_update_endian(state_in, input, len, XXH_bigEndian);\n}\n\n\nFORCE_INLINE U32\nXXH32_digest_endian(const XXH32_state_t* state, XXH_endianess endian)\n{\n   U32 h32;\n\n   if (state->large_len) {\n      h32 = XXH_rotl32(state->v1, 1)\n         + XXH_rotl32(state->v2, 7)\n         + XXH_rotl32(state->v3, 12)\n         + XXH_rotl32(state->v4, 18);\n   } else {\n      h32 = state->v3 /* == seed */ + PRIME32_5;\n   }\n\n   h32 += state->total_len_32;\n\n   return XXH32_finalize(h32, state->mem32, state->memsize, endian, XXH_aligned);\n}\n\n\nXXH_PUBLIC_API unsigned int XXH32_digest(const XXH32_state_t* state_in)\n{\n   XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;\n\n   if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)\n      return XXH32_digest_endian(state_in, XXH_littleEndian);\n   else\n      return XXH32_digest_endian(state_in, XXH_bigEndian);\n}\n\n\n/*======   Canonical representation   ======*/\n\n/*! Default XXH result types are basic unsigned 32 and 64 bits.\n*   The canonical representation follows human-readable write convention, aka big-endian (large digits first).\n*   These functions allow transformation of hash result into and from its canonical format.\n*   This way, hash values can be written into a file or buffer, remaining comparable across different systems.\n*/\n\nXXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash)\n{\n   XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t));\n   if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash);\n   memcpy(dst, &hash, sizeof(*dst));\n}\n\nXXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src)\n{\n   return XXH_readBE32(src);\n}\n\n\n#ifndef XXH_NO_LONG_LONG\n\n/* *******************************************************************\n*  64-bit hash functions\n*********************************************************************/\n\n/*======   Memory access   ======*/\n\n#ifndef MEM_MODULE\n# define MEM_MODULE\n# if !defined (__VMS) \\\n  && (defined (__cplusplus) \\\n  || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )\n#   include <stdint.h>\ntypedef uint64_t U64;\n# else\n    /* if compiler doesn't support unsigned long long, replace by another 64-bit type */\ntypedef unsigned long long U64;\n# endif\n#endif\n\n\n#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))\n\n/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */\nstatic U64 XXH_read64(const void* memPtr) { return *(const U64*)memPtr; }\n\n#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))\n\n/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */\n/* currently only defined for gcc and icc */\ntypedef union { U32 u32; U64 u64; } __attribute__((packed)) unalign64;\nstatic U64 XXH_read64(const void* ptr) { return ((const unalign64*)ptr)->u64; }\n\n#else\n\n/* portable and safe solution. Generally efficient.\n * see : http://stackoverflow.com/a/32095106/646947\n */\n\nstatic U64 XXH_read64(const void* memPtr)\n{\n   U64 val;\n   memcpy(&val, memPtr, sizeof(val));\n   return val;\n}\n\n#endif   /* XXH_FORCE_DIRECT_MEMORY_ACCESS */\n\n#if defined(_MSC_VER)     /* Visual Studio */\n#  define XXH_swap64 _byteswap_uint64\n#elif XXH_GCC_VERSION >= 403\n#  define XXH_swap64 __builtin_bswap64\n#else\nstatic U64 XXH_swap64(U64 x)\n{\n   return  ((x << 56) & 0xff00000000000000ULL) |\n      ((x << 40) & 0x00ff000000000000ULL) |\n      ((x << 24) & 0x0000ff0000000000ULL) |\n      ((x << 8) & 0x000000ff00000000ULL) |\n      ((x >> 8) & 0x00000000ff000000ULL) |\n      ((x >> 24) & 0x0000000000ff0000ULL) |\n      ((x >> 40) & 0x000000000000ff00ULL) |\n      ((x >> 56) & 0x00000000000000ffULL);\n}\n#endif\n\nFORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align)\n{\n   if (align == XXH_unaligned)\n      return endian == XXH_littleEndian ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr));\n   else\n      return endian == XXH_littleEndian ? *(const U64*)ptr : XXH_swap64(*(const U64*)ptr);\n}\n\nFORCE_INLINE U64 XXH_readLE64(const void* ptr, XXH_endianess endian)\n{\n   return XXH_readLE64_align(ptr, endian, XXH_unaligned);\n}\n\nstatic U64 XXH_readBE64(const void* ptr)\n{\n   return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr);\n}\n\n\n/*======   xxh64   ======*/\n\nstatic const U64 PRIME64_1 = 11400714785074694791ULL;   /* 0b1001111000110111011110011011000110000101111010111100101010000111 */\nstatic const U64 PRIME64_2 = 14029467366897019727ULL;   /* 0b1100001010110010101011100011110100100111110101001110101101001111 */\nstatic const U64 PRIME64_3 = 1609587929392839161ULL;   /* 0b0001011001010110011001111011000110011110001101110111100111111001 */\nstatic const U64 PRIME64_4 = 9650029242287828579ULL;   /* 0b1000010111101011110010100111011111000010101100101010111001100011 */\nstatic const U64 PRIME64_5 = 2870177450012600261ULL;   /* 0b0010011111010100111010110010111100010110010101100110011111000101 */\n\nstatic U64 XXH64_round(U64 acc, U64 input)\n{\n   acc += input * PRIME64_2;\n   acc = XXH_rotl64(acc, 31);\n   acc *= PRIME64_1;\n   return acc;\n}\n\nstatic U64 XXH64_mergeRound(U64 acc, U64 val)\n{\n   val = XXH64_round(0, val);\n   acc ^= val;\n   acc = acc * PRIME64_1 + PRIME64_4;\n   return acc;\n}\n\nstatic U64 XXH64_avalanche(U64 h64)\n{\n   h64 ^= h64 >> 33;\n   h64 *= PRIME64_2;\n   h64 ^= h64 >> 29;\n   h64 *= PRIME64_3;\n   h64 ^= h64 >> 32;\n   return h64;\n}\n\n\n#define XXH_get64bits(p) XXH_readLE64_align(p, endian, align)\n\nstatic U64\nXXH64_finalize(U64 h64, const void* ptr, size_t len,\n               XXH_endianess endian, XXH_alignment align)\n{\n   const BYTE* p = (const BYTE*)ptr;\n\n#define PROCESS1_64            \\\n    h64 ^= (*p++) * PRIME64_5; \\\n    h64 = XXH_rotl64(h64, 11) * PRIME64_1;\n\n#define PROCESS4_64          \\\n    h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1; \\\n    p+=4;                    \\\n    h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3;\n\n#define PROCESS8_64 {        \\\n    U64 const k1 = XXH64_round(0, XXH_get64bits(p)); \\\n    p+=8;                    \\\n    h64 ^= k1;               \\\n    h64  = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; \\\n}\n\n   switch (len & 31) {\n   case 24: PROCESS8_64;\n      /* fallthrough */\n   case 16: PROCESS8_64;\n      /* fallthrough */\n   case  8: PROCESS8_64;\n      return XXH64_avalanche(h64);\n\n   case 28: PROCESS8_64;\n      /* fallthrough */\n   case 20: PROCESS8_64;\n      /* fallthrough */\n   case 12: PROCESS8_64;\n      /* fallthrough */\n   case  4: PROCESS4_64;\n      return XXH64_avalanche(h64);\n\n   case 25: PROCESS8_64;\n      /* fallthrough */\n   case 17: PROCESS8_64;\n      /* fallthrough */\n   case  9: PROCESS8_64;\n      PROCESS1_64;\n      return XXH64_avalanche(h64);\n\n   case 29: PROCESS8_64;\n      /* fallthrough */\n   case 21: PROCESS8_64;\n      /* fallthrough */\n   case 13: PROCESS8_64;\n      /* fallthrough */\n   case  5: PROCESS4_64;\n      PROCESS1_64;\n      return XXH64_avalanche(h64);\n\n   case 26: PROCESS8_64;\n      /* fallthrough */\n   case 18: PROCESS8_64;\n      /* fallthrough */\n   case 10: PROCESS8_64;\n      PROCESS1_64;\n      PROCESS1_64;\n      return XXH64_avalanche(h64);\n\n   case 30: PROCESS8_64;\n      /* fallthrough */\n   case 22: PROCESS8_64;\n      /* fallthrough */\n   case 14: PROCESS8_64;\n      /* fallthrough */\n   case  6: PROCESS4_64;\n      PROCESS1_64;\n      PROCESS1_64;\n      return XXH64_avalanche(h64);\n\n   case 27: PROCESS8_64;\n      /* fallthrough */\n   case 19: PROCESS8_64;\n      /* fallthrough */\n   case 11: PROCESS8_64;\n      PROCESS1_64;\n      PROCESS1_64;\n      PROCESS1_64;\n      return XXH64_avalanche(h64);\n\n   case 31: PROCESS8_64;\n      /* fallthrough */\n   case 23: PROCESS8_64;\n      /* fallthrough */\n   case 15: PROCESS8_64;\n      /* fallthrough */\n   case  7: PROCESS4_64;\n      /* fallthrough */\n   case  3: PROCESS1_64;\n      /* fallthrough */\n   case  2: PROCESS1_64;\n      /* fallthrough */\n   case  1: PROCESS1_64;\n      /* fallthrough */\n   case  0: return XXH64_avalanche(h64);\n   }\n\n   /* impossible to reach */\n   assert(0);\n   return 0;  /* unreachable, but some compilers complain without it */\n}\n\nFORCE_INLINE U64\nXXH64_endian_align(const void* input, size_t len, U64 seed,\n                   XXH_endianess endian, XXH_alignment align)\n{\n   const BYTE* p = (const BYTE*)input;\n   const BYTE* bEnd = p + len;\n   U64 h64;\n\n#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)\n   if (p == NULL) {\n      len = 0;\n      bEnd = p = (const BYTE*)(size_t)32;\n   }\n#endif\n\n   if (len >= 32) {\n      const BYTE* const limit = bEnd - 32;\n      U64 v1 = seed + PRIME64_1 + PRIME64_2;\n      U64 v2 = seed + PRIME64_2;\n      U64 v3 = seed + 0;\n      U64 v4 = seed - PRIME64_1;\n\n      do {\n         v1 = XXH64_round(v1, XXH_get64bits(p)); p += 8;\n         v2 = XXH64_round(v2, XXH_get64bits(p)); p += 8;\n         v3 = XXH64_round(v3, XXH_get64bits(p)); p += 8;\n         v4 = XXH64_round(v4, XXH_get64bits(p)); p += 8;\n      } while (p <= limit);\n\n      h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18);\n      h64 = XXH64_mergeRound(h64, v1);\n      h64 = XXH64_mergeRound(h64, v2);\n      h64 = XXH64_mergeRound(h64, v3);\n      h64 = XXH64_mergeRound(h64, v4);\n\n   } else {\n      h64 = seed + PRIME64_5;\n   }\n\n   h64 += (U64)len;\n\n   return XXH64_finalize(h64, p, len, endian, align);\n}\n\n\nXXH_PUBLIC_API unsigned long long XXH64(const void* input, size_t len, unsigned long long seed)\n{\n#if 0\n   /* Simple version, good for code maintenance, but unfortunately slow for small inputs */\n   XXH64_state_t state;\n   XXH64_reset(&state, seed);\n   XXH64_update(&state, input, len);\n   return XXH64_digest(&state);\n#else\n   XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;\n\n   if (XXH_FORCE_ALIGN_CHECK) {\n      if ((((size_t)input) & 7) == 0) {  /* Input is aligned, let's leverage the speed advantage */\n         if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)\n            return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned);\n         else\n            return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned);\n      }\n   }\n\n   if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)\n      return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned);\n   else\n      return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned);\n#endif\n}\n\n/*======   Hash Streaming   ======*/\n\nXXH_PUBLIC_API XXH64_state_t* XXH64_createState(void)\n{\n   return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t));\n}\nXXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr)\n{\n   XXH_free(statePtr);\n   return XXH_OK;\n}\n\nXXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState)\n{\n   memcpy(dstState, srcState, sizeof(*dstState));\n}\n\nXXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed)\n{\n   XXH64_state_t state;   /* using a local state to memcpy() in order to avoid strict-aliasing warnings */\n   memset(&state, 0, sizeof(state));\n   state.v1 = seed + PRIME64_1 + PRIME64_2;\n   state.v2 = seed + PRIME64_2;\n   state.v3 = seed + 0;\n   state.v4 = seed - PRIME64_1;\n   /* do not write into reserved, planned to be removed in a future version */\n   memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved));\n   return XXH_OK;\n}\n\nFORCE_INLINE XXH_errorcode\nXXH64_update_endian(XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian)\n{\n   if (input == NULL)\n#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)\n      return XXH_OK;\n#else\n      return XXH_ERROR;\n#endif\n\n   {\n      const BYTE* p = (const BYTE*)input;\n      const BYTE* const bEnd = p + len;\n\n      state->total_len += len;\n\n      if (state->memsize + len < 32) {  /* fill in tmp buffer */\n         XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len);\n         state->memsize += (U32)len;\n         return XXH_OK;\n      }\n\n      if (state->memsize) {   /* tmp buffer is full */\n         XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32 - state->memsize);\n         state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64 + 0, endian));\n         state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64 + 1, endian));\n         state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64 + 2, endian));\n         state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64 + 3, endian));\n         p += 32 - state->memsize;\n         state->memsize = 0;\n      }\n\n      if (p + 32 <= bEnd) {\n         const BYTE* const limit = bEnd - 32;\n         U64 v1 = state->v1;\n         U64 v2 = state->v2;\n         U64 v3 = state->v3;\n         U64 v4 = state->v4;\n\n         do {\n            v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p += 8;\n            v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p += 8;\n            v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p += 8;\n            v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p += 8;\n         } while (p <= limit);\n\n         state->v1 = v1;\n         state->v2 = v2;\n         state->v3 = v3;\n         state->v4 = v4;\n      }\n\n      if (p < bEnd) {\n         XXH_memcpy(state->mem64, p, (size_t)(bEnd - p));\n         state->memsize = (unsigned)(bEnd - p);\n      }\n   }\n\n   return XXH_OK;\n}\n\nXXH_PUBLIC_API XXH_errorcode XXH64_update(XXH64_state_t* state_in, const void* input, size_t len)\n{\n   XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;\n\n   if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)\n      return XXH64_update_endian(state_in, input, len, XXH_littleEndian);\n   else\n      return XXH64_update_endian(state_in, input, len, XXH_bigEndian);\n}\n\nFORCE_INLINE U64 XXH64_digest_endian(const XXH64_state_t* state, XXH_endianess endian)\n{\n   U64 h64;\n\n   if (state->total_len >= 32) {\n      U64 const v1 = state->v1;\n      U64 const v2 = state->v2;\n      U64 const v3 = state->v3;\n      U64 const v4 = state->v4;\n\n      h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18);\n      h64 = XXH64_mergeRound(h64, v1);\n      h64 = XXH64_mergeRound(h64, v2);\n      h64 = XXH64_mergeRound(h64, v3);\n      h64 = XXH64_mergeRound(h64, v4);\n   } else {\n      h64 = state->v3 /*seed*/ + PRIME64_5;\n   }\n\n   h64 += (U64)state->total_len;\n\n   return XXH64_finalize(h64, state->mem64, (size_t)state->total_len, endian, XXH_aligned);\n}\n\nXXH_PUBLIC_API unsigned long long XXH64_digest(const XXH64_state_t* state_in)\n{\n   XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;\n\n   if ((endian_detected == XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)\n      return XXH64_digest_endian(state_in, XXH_littleEndian);\n   else\n      return XXH64_digest_endian(state_in, XXH_bigEndian);\n}\n\n\n/*====== Canonical representation   ======*/\n\nXXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash)\n{\n   XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t));\n   if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash);\n   memcpy(dst, &hash, sizeof(*dst));\n}\n\nXXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src)\n{\n   return XXH_readBE64(src);\n}\n\n#endif  /* XXH_NO_LONG_LONG */"
  },
  {
    "path": "src/common/xxhash.h",
    "content": "/*\n   xxHash - Extremely Fast Hash algorithm\n   Header File\n   Copyright (C) 2012-2016, Yann Collet.\n\n   BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)\n\n   Redistribution and use in source and binary forms, with or without\n   modification, are permitted provided that the following conditions are\n   met:\n\n       * Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n       * Redistributions in binary form must reproduce the above\n   copyright notice, this list of conditions and the following disclaimer\n   in the documentation and/or other materials provided with the\n   distribution.\n\n   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n   \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n   You can contact the author at :\n   - xxHash source repository : https://github.com/Cyan4973/xxHash\n*/\n\n/* Notice extracted from xxHash homepage :\n\nxxHash is an extremely fast Hash algorithm, running at RAM speed limits.\nIt also successfully passes all tests from the SMHasher suite.\n\nComparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz)\n\nName            Speed       Q.Score   Author\nxxHash          5.4 GB/s     10\nCrapWow         3.2 GB/s      2       Andrew\nMumurHash 3a    2.7 GB/s     10       Austin Appleby\nSpookyHash      2.0 GB/s     10       Bob Jenkins\nSBox            1.4 GB/s      9       Bret Mulvey\nLookup3         1.2 GB/s      9       Bob Jenkins\nSuperFastHash   1.2 GB/s      1       Paul Hsieh\nCityHash64      1.05 GB/s    10       Pike & Alakuijala\nFNV             0.55 GB/s     5       Fowler, Noll, Vo\nCRC32           0.43 GB/s     9\nMD5-32          0.33 GB/s    10       Ronald L. Rivest\nSHA1-32         0.28 GB/s    10\n\nQ.Score is a measure of quality of the hash function.\nIt depends on successfully passing SMHasher test set.\n10 is a perfect score.\n\nA 64-bit version, named XXH64, is available since r35.\nIt offers much better speed, but for 64-bit applications only.\nName     Speed on 64 bits    Speed on 32 bits\nXXH64       13.8 GB/s            1.9 GB/s\nXXH32        6.8 GB/s            6.0 GB/s\n*/\n\n#ifndef XXHASH_H_5627135585666179\n#define XXHASH_H_5627135585666179 1\n\n#if defined (__cplusplus)\nextern \"C\" {\n#endif\n\n\n   /* ****************************\n   *  Definitions\n   ******************************/\n#include <stddef.h>   /* size_t */\n   typedef enum { XXH_OK = 0, XXH_ERROR } XXH_errorcode;\n\n\n   /* ****************************\n    *  API modifier\n    ******************************/\n    /** XXH_INLINE_ALL (and XXH_PRIVATE_API)\n     *  This is useful to include xxhash functions in `static` mode\n     *  in order to inline them, and remove their symbol from the public list.\n     *  Inlining can offer dramatic performance improvement on small keys.\n     *  Methodology :\n     *     #define XXH_INLINE_ALL\n     *     #include \"xxhash.h\"\n     * `xxhash.c` is automatically included.\n     *  It's not useful to compile and link it as a separate module.\n     */\n#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)\n#  ifndef XXH_STATIC_LINKING_ONLY\n#    define XXH_STATIC_LINKING_ONLY\n#  endif\n#  if defined(__GNUC__)\n#    define XXH_PUBLIC_API static __inline __attribute__((unused))\n#  elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)\n#    define XXH_PUBLIC_API static inline\n#  elif defined(_MSC_VER)\n#    define XXH_PUBLIC_API static __inline\n#  else\n     /* this version may generate warnings for unused static functions */\n#    define XXH_PUBLIC_API static\n#  endif\n#else\n#  define XXH_PUBLIC_API   /* do nothing */\n#endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */\n\n     /*! XXH_NAMESPACE, aka Namespace Emulation :\n      *\n      * If you want to include _and expose_ xxHash functions from within your own library,\n      * but also want to avoid symbol collisions with other libraries which may also include xxHash,\n      *\n      * you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library\n      * with the value of XXH_NAMESPACE (therefore, avoid NULL and numeric values).\n      *\n      * Note that no change is required within the calling program as long as it includes `xxhash.h` :\n      * regular symbol name will be automatically translated by this header.\n      */\n#ifdef XXH_NAMESPACE\n#  define XXH_CAT(A,B) A##B\n#  define XXH_NAME2(A,B) XXH_CAT(A,B)\n#  define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber)\n#  define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32)\n#  define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState)\n#  define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState)\n#  define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset)\n#  define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update)\n#  define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest)\n#  define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState)\n#  define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash)\n#  define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical)\n#  define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64)\n#  define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState)\n#  define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState)\n#  define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset)\n#  define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update)\n#  define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest)\n#  define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState)\n#  define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash)\n#  define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical)\n#endif\n\n\n      /* *************************************\n      *  Version\n      ***************************************/\n#define XXH_VERSION_MAJOR    0\n#define XXH_VERSION_MINOR    6\n#define XXH_VERSION_RELEASE  5\n#define XXH_VERSION_NUMBER  (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE)\n   XXH_PUBLIC_API unsigned XXH_versionNumber(void);\n\n\n   /*-**********************************************************************\n   *  32-bit hash\n   ************************************************************************/\n   typedef unsigned int XXH32_hash_t;\n\n   /*! XXH32() :\n       Calculate the 32-bit hash of sequence \"length\" bytes stored at memory address \"input\".\n       The memory between input & input+length must be valid (allocated and read-accessible).\n       \"seed\" can be used to alter the result predictably.\n       Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s */\n   XXH_PUBLIC_API XXH32_hash_t XXH32(const void* input, size_t length, unsigned int seed);\n\n   /*======   Streaming   ======*/\n   typedef struct XXH32_state_s XXH32_state_t;   /* incomplete type */\n   XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void);\n   XXH_PUBLIC_API XXH_errorcode  XXH32_freeState(XXH32_state_t* statePtr);\n   XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state);\n\n   XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed);\n   XXH_PUBLIC_API XXH_errorcode XXH32_update(XXH32_state_t* statePtr, const void* input, size_t length);\n   XXH_PUBLIC_API XXH32_hash_t  XXH32_digest(const XXH32_state_t* statePtr);\n\n   /*\n    * Streaming functions generate the xxHash of an input provided in multiple segments.\n    * Note that, for small input, they are slower than single-call functions, due to state management.\n    * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized.\n    *\n    * XXH state must first be allocated, using XXH*_createState() .\n    *\n    * Start a new hash by initializing state with a seed, using XXH*_reset().\n    *\n    * Then, feed the hash state by calling XXH*_update() as many times as necessary.\n    * The function returns an error code, with 0 meaning OK, and any other value meaning there is an error.\n    *\n    * Finally, a hash value can be produced anytime, by using XXH*_digest().\n    * This function returns the nn-bits hash as an int or long long.\n    *\n    * It's still possible to continue inserting input into the hash state after a digest,\n    * and generate some new hashes later on, by calling again XXH*_digest().\n    *\n    * When done, free XXH state space if it was allocated dynamically.\n    */\n\n    /*======   Canonical representation   ======*/\n\n   typedef struct { unsigned char digest[4]; } XXH32_canonical_t;\n   XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash);\n   XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src);\n\n   /* Default result type for XXH functions are primitive unsigned 32 and 64 bits.\n    * The canonical representation uses human-readable write convention, aka big-endian (large digits first).\n    * These functions allow transformation of hash result into and from its canonical format.\n    * This way, hash values can be written into a file / memory, and remain comparable on different systems and programs.\n    */\n\n\n#ifndef XXH_NO_LONG_LONG\n    /*-**********************************************************************\n    *  64-bit hash\n    ************************************************************************/\n   typedef unsigned long long XXH64_hash_t;\n\n   /*! XXH64() :\n       Calculate the 64-bit hash of sequence of length \"len\" stored at memory address \"input\".\n       \"seed\" can be used to alter the result predictably.\n       This function runs faster on 64-bit systems, but slower on 32-bit systems (see benchmark).\n   */\n   XXH_PUBLIC_API XXH64_hash_t XXH64(const void* input, size_t length, unsigned long long seed);\n\n   /*======   Streaming   ======*/\n   typedef struct XXH64_state_s XXH64_state_t;   /* incomplete type */\n   XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void);\n   XXH_PUBLIC_API XXH_errorcode  XXH64_freeState(XXH64_state_t* statePtr);\n   XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state);\n\n   XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed);\n   XXH_PUBLIC_API XXH_errorcode XXH64_update(XXH64_state_t* statePtr, const void* input, size_t length);\n   XXH_PUBLIC_API XXH64_hash_t  XXH64_digest(const XXH64_state_t* statePtr);\n\n   /*======   Canonical representation   ======*/\n   typedef struct { unsigned char digest[8]; } XXH64_canonical_t;\n   XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash);\n   XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src);\n#endif  /* XXH_NO_LONG_LONG */\n\n\n\n#ifdef XXH_STATIC_LINKING_ONLY\n\n   /* ================================================================================================\n      This section contains declarations which are not guaranteed to remain stable.\n      They may change in future versions, becoming incompatible with a different version of the library.\n      These declarations should only be used with static linking.\n      Never use them in association with dynamic linking !\n   =================================================================================================== */\n\n   /* These definitions are only present to allow\n    * static allocation of XXH state, on stack or in a struct for example.\n    * Never **ever** use members directly. */\n\n#if !defined (__VMS) \\\n  && (defined (__cplusplus) \\\n  || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )\n#   include <cstdint>\n\n   struct XXH32_state_s\n   {\n      uint32_t total_len_32;\n      uint32_t large_len;\n      uint32_t v1;\n      uint32_t v2;\n      uint32_t v3;\n      uint32_t v4;\n      uint32_t mem32[4];\n      uint32_t memsize;\n      uint32_t reserved;   /* never read nor write, might be removed in a future version */\n   };   /* typedef'd to XXH32_state_t */\n\n   struct XXH64_state_s\n   {\n      uint64_t total_len;\n      uint64_t v1;\n      uint64_t v2;\n      uint64_t v3;\n      uint64_t v4;\n      uint64_t mem64[4];\n      uint32_t memsize;\n      uint32_t reserved[2];          /* never read nor write, might be removed in a future version */\n   };   /* typedef'd to XXH64_state_t */\n\n# else\n\n   struct XXH32_state_s\n   {\n      unsigned total_len_32;\n      unsigned large_len;\n      unsigned v1;\n      unsigned v2;\n      unsigned v3;\n      unsigned v4;\n      unsigned mem32[4];\n      unsigned memsize;\n      unsigned reserved;   /* never read nor write, might be removed in a future version */\n   };   /* typedef'd to XXH32_state_t */\n\n#   ifndef XXH_NO_LONG_LONG  /* remove 64-bit support */\n   struct XXH64_state_s\n   {\n      unsigned long long total_len;\n      unsigned long long v1;\n      unsigned long long v2;\n      unsigned long long v3;\n      unsigned long long v4;\n      unsigned long long mem64[4];\n      unsigned memsize;\n      unsigned reserved[2];     /* never read nor write, might be removed in a future version */\n   };   /* typedef'd to XXH64_state_t */\n#    endif\n\n# endif\n\n\n#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)\n#  include \"xxhash.c\"   /* include xxhash function bodies as `static`, for inlining */\n#endif\n\n#endif /* XXH_STATIC_LINKING_ONLY */\n\n\n#if defined (__cplusplus)\n}\n#endif\n\n#endif /* XXHASH_H_5627135585666179 */"
  },
  {
    "path": "src/decaf-cli/CMakeLists.txt",
    "content": "project(decaf-cli)\n\ninclude_directories(\".\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nadd_executable(decaf-cli ${SOURCE_FILES} ${HEADER_FILES})\n\ntarget_link_libraries(decaf-cli\n    common\n    libconfig\n    libdecaf\n    excmd)\n\ninstall(TARGETS decaf-cli RUNTIME DESTINATION \"${DECAF_INSTALL_BINDIR}\")\n\nif(COMMAND x_vcpkg_install_local_dependencies)\n   x_vcpkg_install_local_dependencies(TARGETS decaf-cli DESTINATION \"${DECAF_INSTALL_BINDIR}\")\nendif()\n"
  },
  {
    "path": "src/decaf-cli/config.cpp",
    "content": "#include \"config.h\"\n\nnamespace config\n{\n\nnamespace system\n{\n\nint timeout_ms = 0;\n\n} // namespace system\n\nbool\nloadFrontendToml(const toml::table &config)\n{\n   readValue(config, \"system.timeout_ms\", system::timeout_ms);\n   return true;\n}\n\nbool\nsaveFrontendToml(toml::table &config)\n{\n   auto system = config.insert(\"system\", toml::table()).first->second.as_table();\n   system->insert_or_assign(\"timeout_ms\", system::timeout_ms);\n   return true;\n}\n\n} // namespace config\n"
  },
  {
    "path": "src/decaf-cli/config.h",
    "content": "#pragma once\n#include <libconfig/config_toml.h>\n#include <string>\n#include <toml++/toml.h>\n\nnamespace config\n{\n\nnamespace system\n{\n\nextern int timeout_ms;\n\n} // namespace system\n\nbool\nloadFrontendToml(const toml::table &config);\n\nbool\nsaveFrontendToml(toml::table &config);\n\n} // namespace config\n"
  },
  {
    "path": "src/decaf-cli/decafcli.cpp",
    "content": "#include \"decafcli.h\"\n#include \"config.h\"\n\n#include <chrono>\n#include <condition_variable>\n#include <libgpu/gpu_graphicsdriver.h>\n#include <libdecaf/decaf_nullinputdriver.h>\n#include <mutex>\n#include <thread>\n\nint\nDecafCLI::run(const std::string &gamePath)\n{\n   int result = 0;\n\n   // Setup drivers\n   decaf::setGraphicsDriver(gpu::createGraphicsDriver(gpu::GraphicsDriverType::Null));\n   decaf::setInputDriver(new decaf::NullInputDriver { });\n\n   // Initialise emulator\n   if (!decaf::initialise(gamePath)) {\n      return -1;\n   }\n\n   // Start graphics thread\n   auto graphicsThread = std::thread {\n      [this]() {\n         decaf::getGraphicsDriver()->run();\n      } };\n\n   // Setup timeout stuff\n   std::atomic_bool running { true };\n   std::atomic_bool timedOut { false };\n   std::thread timeoutThread;\n   std::condition_variable timeoutCV;\n\n   if (config::system::timeout_ms) {\n      auto timeout = std::chrono::system_clock::now() + std::chrono::milliseconds(config::system::timeout_ms);\n\n      timeoutThread = std::thread {\n         [&]() {\n            std::mutex timeoutMutex;\n            std::unique_lock<std::mutex> lock { timeoutMutex };\n            timeoutCV.wait_until(lock, timeout, [&]() { return !running.load(); });\n\n            if (running) {\n               timedOut.store(true);\n               decaf::shutdown();\n            }\n         } };\n   }\n\n   // Start emulator\n   decaf::start();\n\n   // Wait until program completes\n   result = decaf::waitForExit();\n\n   // If we didn't timeout, wakeup timeout thread\n   if (!timedOut.load()) {\n      running.store(false);\n      timeoutCV.notify_all();\n   } else {\n      gCliLog->error(\"Application exceeded maxmimum execution time of {}ms.\", config::system::timeout_ms);\n      result = -1;\n   }\n\n   // Wait for timeout thread to exit\n   if (timeoutThread.joinable()) {\n      timeoutThread.join();\n   }\n\n   // Wait for the GPU thread to exit\n   if (graphicsThread.joinable()) {\n      graphicsThread.join();\n   }\n\n   return result;\n}\n"
  },
  {
    "path": "src/decaf-cli/decafcli.h",
    "content": "#pragma once\n#include <libdecaf/decaf.h>\n#include <string>\n#include <thread>\n#include <spdlog/spdlog.h>\n\nusing namespace decaf::input;\n\nextern std::shared_ptr<spdlog::logger>\ngCliLog;\n\nclass DecafCLI\n{\npublic:\n   int run(const std::string &gamePath);\n\nprivate:\n};\n"
  },
  {
    "path": "src/decaf-cli/main.cpp",
    "content": "#include \"config.h\"\n#include \"decafcli.h\"\n\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <common/platform_dir.h>\n#include <excmd.h>\n#include <iostream>\n#include <libconfig/config_excmd.h>\n#include <libconfig/config_toml.h>\n#include <libdecaf/decaf.h>\n#include <libdecaf/decaf_log.h>\n#include <spdlog/sinks/stdout_sinks.h>\n#include <toml++/toml.h>\n\nstd::shared_ptr<spdlog::logger>\ngCliLog;\n\nusing namespace decaf::input;\n\nstatic excmd::parser\ngetCommandLineParser()\n{\n   excmd::parser parser;\n   using excmd::description;\n   using excmd::optional;\n   using excmd::default_value;\n   using excmd::allowed;\n   using excmd::value;\n\n   parser.global_options()\n      .add_option(\"v,version\",\n                  description { \"Show version.\" })\n      .add_option(\"h,help\",\n                  description { \"Show help.\" });\n\n   parser.add_command(\"help\")\n      .add_argument(\"help-command\",\n                    optional {},\n                    value<std::string> {});\n\n   auto cli_options = parser.add_option_group(\"Cli Options\")\n      .add_option(\"config\",\n                  description { \"Specify path to configuration file.\" },\n                  value<std::string> {})\n      .add_option(\"timeout_ms\",\n                  description { \"How long to execute the game for before quitting.\" },\n                  value<uint32_t> {});\n\n   auto config_options = config::getExcmdGroups(parser);\n\n   auto cmdPlay = parser.add_command(\"play\")\n      .add_option_group(cli_options)\n      .add_argument(\"game directory\", value<std::string> {});\n\n   for (auto group : config_options) {\n      cmdPlay.add_option_group(group);\n   }\n\n   return parser;\n}\n\nstatic std::string\ngetPathBasename(const std::string &path)\n{\n   auto pos = path.find_last_of(\"/\\\\\");\n\n   if (!pos) {\n      return path;\n   } else {\n      return path.substr(pos + 1);\n   }\n}\n\nint\nstart(excmd::parser &parser,\n      excmd::option_state &options)\n{\n   // Print version\n   if (options.has(\"version\")) {\n      // TODO: print git hash\n      std::cout << \"Decaf Emulator version 0.0.1\" << std::endl;\n      std::exit(0);\n   }\n\n   // Print help\n   if (options.empty() || options.has(\"help\")) {\n      if (options.has(\"help-command\")) {\n         std::cout << parser.format_help(\"decaf\", options.get<std::string>(\"help-command\")) << std::endl;\n      } else {\n         std::cout << parser.format_help(\"decaf\") << std::endl;\n      }\n\n      std::exit(0);\n   }\n\n   if (!options.has(\"play\")) {\n      return 0;\n   }\n\n   auto gamePath = options.get<std::string>(\"game directory\");\n\n   // Load config file\n   std::string configPath, configError;\n\n   if (options.has(\"config\")) {\n      configPath = options.get<std::string>(\"config\");\n   } else {\n      decaf::createConfigDirectory();\n      configPath = decaf::makeConfigPath(\"cli-config.toml\");\n   }\n\n   auto cpuSettings = cpu::Settings { };\n   auto gpuSettings = gpu::Settings { };\n   auto decafSettings = decaf::Settings { };\n\n   // If config file does not exist, create a default one.\n   if (!platform::fileExists(configPath)) {\n      auto toml = toml::table();\n      config::saveToTOML(toml, cpuSettings);\n      config::saveToTOML(toml, gpuSettings);\n      config::saveToTOML(toml, decafSettings);\n      config::saveFrontendToml(toml);\n      std::ofstream out { configPath };\n      out << toml;\n   }\n\n   try {\n      auto toml = toml::parse_file(configPath);\n      config::loadFromTOML(toml, cpuSettings);\n      config::loadFromTOML(toml, gpuSettings);\n      config::loadFromTOML(toml, decafSettings);\n      config::loadFrontendToml(toml);\n   } catch (const toml::parse_error &ex) {\n      configError = ex.what();\n   }\n\n   config::loadFromExcmd(options, cpuSettings);\n   config::loadFromExcmd(options, gpuSettings);\n   config::loadFromExcmd(options, decafSettings);\n\n   cpu::setConfig(cpuSettings);\n   gpu::setConfig(gpuSettings);\n   decaf::setConfig(decafSettings);\n\n   // Allow command line options to override config\n   if (options.has(\"timeout_ms\")) {\n      config::system::timeout_ms = options.get<uint32_t>(\"timeout_ms\");\n   }\n\n   // Initialise libdecaf logger\n   auto logFile = getPathBasename(gamePath);\n   decaf::initialiseLogging(logFile);\n\n   // Initialise frontend logger\n   if (!decafSettings.log.to_stdout) {\n      // Always do cli log to stdout\n      gCliLog = decaf::makeLogger(\"decaf-cli\",\n                                  { std::make_shared<spdlog::sinks::stdout_sink_mt>() });\n   } else {\n      gCliLog = decaf::makeLogger(\"decaf-cli\");\n   }\n\n   gCliLog->set_pattern(\"[%l] %v\");\n   gCliLog->info(\"Game path {}\", gamePath);\n\n   if (configError.empty()) {\n      gCliLog->info(\"Loaded config from {}\", configPath);\n   } else {\n      gCliLog->error(\"Failed to parse config {}: {}\", configPath, configError);\n   }\n\n   DecafCLI cli;\n   return cli.run(gamePath);\n}\n\nint main(int argc, char **argv)\n{\n   auto parser = getCommandLineParser();\n   excmd::option_state options;\n\n   try {\n      options = parser.parse(argc, argv);\n   } catch (excmd::exception ex) {\n      std::cout << \"Error parsing options: \" << ex.what() << std::endl;\n      std::exit(-1);\n   }\n\n   return start(parser, options);\n}\n"
  },
  {
    "path": "src/decaf-qt/CMakeLists.txt",
    "content": "project(decaf-qt)\nset(CMAKE_AUTOMOC ON)\nset(CMAKE_INCLUDE_CURRENT_DIR ON)\nset(CMAKE_CXX_STANDARD 17)\n\ninclude_directories(\"src\")\n\nfile(GLOB_RECURSE SOURCE_FILES src/*.cpp)\nfile(GLOB_RECURSE HEADER_FILES src/*.h)\nfile(GLOB_RECURSE UI_FILES ui/*.ui)\nfile(GLOB_RECURSE QRC_FILES resources/*.qrc)\n\nif(MSVC)\n   set(RESOURCE_FILES\n       ${CMAKE_SOURCE_DIR}/resources/decaf-sdl.rc\n       ${CMAKE_SOURCE_DIR}/resources/hidpi.manifest)\nelse()\n   set(RESOURCE_FILES \"\")\nendif()\n\nqt_wrap_ui(UIS_HDRS ${UI_FILES})\nqt_add_resources(QT_RESOURCES ${QRC_FILES})\n\nif(MSVC)\n   source_group(\"Generated Files\" FILES ${UIS_HDRS})\nendif()\n\nadd_executable(decaf-qt\n   ${SOURCE_FILES}\n   ${HEADER_FILES}\n   ${UIS_HDRS}\n   ${QT_RESOURCES}\n   ${RESOURCE_FILES}\n   ${QHEXVIEW_HEADERS}\n   ${QHEXVIEW_SOURCES})\nGroupSources(\"Source Files\" src)\nGroupSources(\"UI Files\" ui)\nGroupSources(\"Resource Files\" resources)\n\ntarget_include_directories(decaf-qt PRIVATE\n   ${SDL2_INCLUDE_DIRS})\n\ntarget_link_libraries(decaf-qt\n   common\n   libconfig\n   libcpu\n   libdecaf\n   ${EXCMD_LIBRARIES}\n   ${SDL2_LIBRARIES}\n   qtadvanceddocking\n   Qt::Core\n   Qt::Concurrent\n   Qt::Svg\n   Qt::Widgets\n   Qt::Xml)\n\nif(Qt5Gui_PRIVATE_INCLUDE_DIRS)\n   target_include_directories(decaf-qt PRIVATE\n      ${Qt5Gui_PRIVATE_INCLUDE_DIRS})\nendif()\n\nif(Qt6Gui_PRIVATE_INCLUDE_DIRS)\n   target_include_directories(decaf-qt PRIVATE\n      ${Qt6Gui_PRIVATE_INCLUDE_DIRS})\nendif()\n\nif(TARGET Qt::SvgWidgets)\n   target_link_libraries(decaf-qt\n      Qt::SvgWidgets)\nendif()\n\nif(MSVC)\n   set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /W3\") # Qt disapproves\n\n   target_link_libraries(decaf-qt\n      Setupapi)\n\n   if(TARGET Qt::WinMain)\n      target_link_libraries(decaf-qt\n         Qt::WinMain)\n   endif()\n\n   set_target_properties(decaf-qt PROPERTIES\n      LINK_FLAGS \"/SUBSYSTEM:WINDOWS\")\n\n   # Find windeployqt binary in the same directory as qmake\n   if(TARGET Qt::qmake AND NOT TARGET Qt::windeployqt)\n      get_target_property(_qt_qmake_location Qt::qmake IMPORTED_LOCATION)\n\n      execute_process(\n         COMMAND \"${_qt_qmake_location}\" -query QT_INSTALL_PREFIX\n         RESULT_VARIABLE return_code\n         OUTPUT_VARIABLE qt_install_prefix\n         OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n      set(imported_location \"${qt_install_prefix}/bin/windeployqt.exe\")\n\n      if(EXISTS ${imported_location})\n         add_executable(Qt::windeployqt IMPORTED)\n\n         set_target_properties(Qt::windeployqt PROPERTIES\n            IMPORTED_LOCATION ${imported_location})\n      endif()\n   endif()\n\n\n   if(Qt5Gui_PRIVATE_INCLUDE_DIRS)\n      # If Qt5\n      set(WINDEPLOYQT_OPTIONS --no-angle --no-opengl-sw --no-translations)\n   else()\n      # Else, assume Qt6\n      set(WINDEPLOYQT_OPTIONS --no-opengl-sw --no-translations)\n   endif()\n\n   # Windeployqt to the TARGET_FILE_DIR:decaf-qt\n   add_custom_command(TARGET decaf-qt\n      POST_BUILD\n      COMMAND set PATH=%PATH%$<SEMICOLON>${qt_install_prefix}/bin\n      COMMAND Qt::windeployqt\n         --dir \"$<TARGET_FILE_DIR:decaf-qt>\"\n         \"$<TARGET_FILE_DIR:decaf-qt>/$<TARGET_FILE_NAME:decaf-qt>\"\n         ${WINDEPLOYQT_OPTIONS})\n\n   # Windeployqt to temporary directory which we can copy in install\n   add_custom_command(TARGET decaf-qt\n      POST_BUILD\n      COMMAND ${CMAKE_COMMAND} -E remove_directory \"${CMAKE_CURRENT_BINARY_DIR}/windeployqt\"\n      COMMAND set PATH=%PATH%$<SEMICOLON>${qt_install_prefix}/bin\n      COMMAND Qt::windeployqt\n         --dir \"${CMAKE_CURRENT_BINARY_DIR}/windeployqt\"\n         \"$<TARGET_FILE_DIR:decaf-qt>/$<TARGET_FILE_NAME:decaf-qt>\"\n         ${WINDEPLOYQT_OPTIONS})\n\n   # copy deployment directory during installation\n   install(\n       DIRECTORY\n       \"${CMAKE_CURRENT_BINARY_DIR}/windeployqt/\"\n       DESTINATION ${DECAF_INSTALL_BINDIR})\nendif()\n\ninstall(TARGETS decaf-qt RUNTIME DESTINATION \"${DECAF_INSTALL_BINDIR}\")\n\nif(COMMAND x_vcpkg_install_local_dependencies)\n   x_vcpkg_install_local_dependencies(TARGETS decaf-qt DESTINATION \"${DECAF_INSTALL_BINDIR}\")\nendif()\n"
  },
  {
    "path": "src/decaf-qt/resources/resources.qrc",
    "content": "<RCC>\n  <qresource prefix=\"images\">\n    <file alias=\"logo\">../../../resources/logo.svg</file>\n    <file alias=\"icon\">../../../resources/decaf.ico</file>\n    <file alias=\"debugger-icon\">../../../resources/decaf-debugger.ico</file>\n  </qresource>\n  <qresource prefix=\"icons\">\n    <file alias=\"checkbox-blank-circle\">icons/checkbox-blank-circle.svg</file>\n    <file alias=\"arrow-left\">icons/arrow-left.svg</file>\n    <file alias=\"arrow-right\">icons/arrow-right.svg</file>\n    <file alias=\"debug-step-into\">icons/debug-step-into.svg</file>\n    <file alias=\"debug-step-out\">icons/debug-step-out.svg</file>\n    <file alias=\"debug-step-over\">icons/debug-step-over.svg</file>\n    <file alias=\"pause\">icons/pause.svg</file>\n    <file alias=\"play\">icons/play.svg</file>\n  </qresource>\n</RCC>\n"
  },
  {
    "path": "src/decaf-qt/src/aboutdialog.h",
    "content": "#pragma once\n#include \"ui_about.h\"\n\n#include <QDialog>\n#include <decaf_buildinfo.h>\n\nclass AboutDialog : public QDialog\n{\npublic:\n   AboutDialog(QWidget *parent = nullptr) :\n      QDialog(parent)\n   {\n      mUi.setupUi(this);\n      mUi.iconWidget->load(QString(\":/images/logo\"));\n      mUi.labelBuildInfo->setText(\n         mUi.labelBuildInfo->text().arg(BUILD_FULLNAME, GIT_BRANCH, GIT_DESC,\n                                        QString(BUILD_DATE).left(10)));\n   }\n\nprivate:\n   Ui::AboutDialog mUi;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/addresstextdocumentwidget.cpp",
    "content": "#include \"addresstextdocumentwidget.h\"\n\n#include <QApplication>\n#include <QAbstractTextDocumentLayout>\n#include <QClipboard>\n#include <QFontDatabase>\n#include <QScrollBar>\n#include <QTextBlock>\n#include <QTextCursor>\n#include <QTextDocument>\n#include <QTimer>\n#include <QMouseEvent>\n#include <QPainter>\n\n#include <libdecaf/decaf_debug_api.h>\n\nAddressTextDocumentWidget::AddressTextDocumentWidget(QWidget *parent) :\n   QAbstractScrollArea(parent)\n{\n   // Set to fixed width font\n   auto font = QFontDatabase::systemFont(QFontDatabase::FixedFont);\n   if (!(font.styleHint() & QFont::Monospace)) {\n      font.setFamily(\"Monospace\");\n      font.setStyleHint(QFont::TypeWriter);\n   }\n\n   setFont(font);\n   setMouseTracking(true);\n   setCursor(Qt::IBeamCursor);\n\n   mLineHeight = fontMetrics().height();\n   mCharacterWidth = fontMetrics().horizontalAdvance(' ');\n\n   // Setup text document\n   mTextDocument = new QTextDocument { this };\n   mTextDocument->setDefaultFont(font);\n   mTextDocument->setUndoRedoEnabled(false);\n   mTextDocument->setPlainText({});\n   mDocumentMargin = mTextDocument->documentMargin();\n\n   // Setup text formats\n   mTextFormatSelection = QTextCharFormat { };\n   mTextFormatSelection.setBackground(Qt::darkGray);\n   mTextFormatSelection.setForeground(Qt::white);\n\n   mTextFormatHighlightedWord = QTextCharFormat { };\n   mTextFormatHighlightedWord.setBackground(Qt::yellow);\n\n   // Setup cursor blink timer\n   mBlinkTimer = new QTimer { this };\n   mBlinkTimer->setInterval(qApp->cursorFlashTime());\n   connect(mBlinkTimer, &QTimer::timeout, this, [this] {\n      mBlinkCursorVisible = !mBlinkCursorVisible;\n      viewport()->update();\n   });\n}\n\nvoid\nAddressTextDocumentWidget::setBytesPerLine(int bytesPerLine)\n{\n   mBytesPerLine = bytesPerLine;\n   mNumLines = static_cast<int>((static_cast<int64_t>(mEndAddress - mStartAddress) + 1) / mBytesPerLine);\n   updateVerticalScrollBar();\n   viewport()->update();\n}\n\nvoid\nAddressTextDocumentWidget::setAddressRange(VirtualAddress start, VirtualAddress end)\n{\n   mStartAddress = start;\n   mEndAddress = end;\n   mNumLines = static_cast<int>((static_cast<int64_t>(mEndAddress - mStartAddress) + 1) / mBytesPerLine);\n   updateVerticalScrollBar();\n   viewport()->update();\n}\n\nvoid\nAddressTextDocumentWidget::navigateToAddress(VirtualAddress address)\n{\n   auto cursorAddress = cursorToAddress(getDocumentCursor());\n   mNavigationForwardStack = { };\n   if (mNavigationBackwardStack.empty() ||\n       mNavigationBackwardStack.back() != cursorAddress) {\n      mNavigationBackwardStack.push_back(cursorAddress);\n   }\n   showAddress(address);\n}\n\nvoid\nAddressTextDocumentWidget::navigateBackward()\n{\n   if (!mNavigationBackwardStack.empty()) {\n      auto address = mNavigationBackwardStack.back();\n      auto cursorAddress = cursorToAddress(getDocumentCursor());\n      mNavigationBackwardStack.pop_back();\n      mNavigationForwardStack.push_back(cursorAddress);\n      showAddress(address);\n   }\n}\n\nvoid\nAddressTextDocumentWidget::navigateForward()\n{\n   if (!mNavigationForwardStack.empty()) {\n      auto address = mNavigationForwardStack.back();\n      auto cursorAddress = cursorToAddress(getDocumentCursor());\n      mNavigationForwardStack.pop_back();\n      mNavigationBackwardStack.push_back(cursorAddress);\n      showAddress(address);\n   }\n}\n\nvoid\nAddressTextDocumentWidget::copySelection()\n{\n   auto selectionBegin = getDocumentSelectionBegin();\n   auto selectionEnd = getDocumentSelectionEnd();\n   auto selectionFirst = std::min(selectionBegin, selectionEnd);\n   auto selectionLast = std::max(selectionBegin, selectionEnd);\n\n   auto tempDocument = QTextDocument{ };\n   auto cursor = QTextCursor{ &tempDocument };\n   cursor.beginEditBlock();\n   updateTextDocument(cursor, selectionFirst.address, selectionLast.address, mBytesPerLine, false);\n   cursor.endEditBlock();\n\n   cursor.movePosition(QTextCursor::End);\n   cursor.movePosition(QTextCursor::StartOfLine);\n   cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, selectionLast.cursorPosition);\n   cursor.setPosition(selectionFirst.cursorPosition, QTextCursor::KeepAnchor);\n\n   qApp->clipboard()->setText(cursor.selectedText());\n}\n\nvoid\nAddressTextDocumentWidget::paintEvent(QPaintEvent *e)\n{\n   auto palette = qApp->palette();\n   auto painter = QPainter { viewport() };\n   painter.setFont(font());\n\n   if (mNumLines == 0) {\n      painter.fillRect(e->rect(), palette.brush(QPalette::Base));\n      return;\n   }\n\n   auto firstVisibleLine = verticalScrollBar()->value();\n   auto lastVisibleLine = firstVisibleLine + verticalScrollBar()->pageStep() - 1;\n\n   // Ensure text document is up to date\n   updateTextDocument(false);\n\n   // Get latest selections\n   auto customSelections = getCustomSelections(mTextDocument);\n\n   // Setup text document painting\n   auto textDocumentRect = viewport()->rect();\n   auto textDocumentOffsetX = horizontalScrollBar()->value();\n   painter.translate(-textDocumentOffsetX, 0.0);\n   textDocumentRect.moveTo({ textDocumentOffsetX, 0 });\n\n   auto paintContext = QAbstractTextDocumentLayout::PaintContext { };\n   paintContext.clip = textDocumentRect;\n   paintContext.selections = customSelections + mHighlightedWordSelections;\n   painter.setClipRect(textDocumentRect);\n\n   // Process text selection\n   auto selectionBegin = getDocumentSelectionBegin();\n   auto selectionEnd = getDocumentSelectionEnd();\n\n   if (selectionBegin != selectionEnd) {\n      auto selectionFirst = std::min(selectionBegin, selectionEnd);\n      auto selectionLast = std::max(selectionBegin, selectionEnd);\n\n      auto selectionFirstLine = static_cast<int>((selectionFirst.address - mStartAddress) / mBytesPerLine);\n      auto selectionLastLine = static_cast<int>((selectionLast.address - mStartAddress) / mBytesPerLine);\n\n      if (selectionLastLine >= firstVisibleLine && lastVisibleLine >= selectionFirstLine) {\n         QTextCursor selectionFirstCursor;\n         QTextCursor selectionLastCursor;\n\n         if (selectionFirstLine < firstVisibleLine) {\n            selectionFirstCursor = QTextCursor { mTextDocument->firstBlock() };\n            selectionFirstCursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);\n         } else {\n            selectionFirstCursor = QTextCursor { mTextDocument->findBlockByLineNumber(selectionFirstLine - firstVisibleLine) };\n            selectionFirstCursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, selectionFirst.cursorPosition);\n         }\n\n         if (selectionLastLine > lastVisibleLine) {\n            selectionLastCursor = QTextCursor { mTextDocument->lastBlock() };\n            selectionLastCursor.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);\n         } else {\n            selectionLastCursor = QTextCursor { mTextDocument->findBlockByLineNumber(selectionLastLine - firstVisibleLine) };\n            selectionLastCursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, selectionLast.cursorPosition);\n         }\n\n         auto selection = std::move(selectionFirstCursor);\n         selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,\n            selectionLastCursor.position() - selection.position());\n         paintContext.selections.push_back({ selection, mTextFormatSelection });\n      }\n   }\n\n   mTextDocument->documentLayout()->draw(&painter, paintContext);\n\n   // Draw cursor on top if there is no selection\n   if (mBlinkCursorVisible) {\n      auto cursor = getDocumentCursor();\n      auto cursorLine = static_cast<int>((cursor.address - mStartAddress) / mBytesPerLine);\n      if (cursorLine >= firstVisibleLine && cursorLine <= lastVisibleLine) {\n         auto cursorX = mDocumentMargin + cursor.cursorPosition * mCharacterWidth;\n         auto cursorY = mDocumentMargin + (cursorLine - firstVisibleLine) * mLineHeight;\n         painter.setCompositionMode(QPainter::CompositionMode_Difference);\n         painter.setPen(QPen{ Qt::GlobalColor::white, 2 });\n         painter.drawLine(cursorX, cursorY, cursorX, cursorY + mLineHeight);\n      }\n   }\n}\n\nvoid\nAddressTextDocumentWidget::resizeEvent(QResizeEvent *e)\n{\n   updateHorizontalScrollBar();\n   updateVerticalScrollBar();\n   QAbstractScrollArea::resizeEvent(e);\n}\n\nvoid\nAddressTextDocumentWidget::focusInEvent(QFocusEvent *e)\n{\n   QAbstractScrollArea::focusInEvent(e);\n   mBlinkTimer->start();\n   mBlinkCursorVisible = true;\n   viewport()->update();\n}\n\nvoid\nAddressTextDocumentWidget::focusOutEvent(QFocusEvent *e)\n{\n   QAbstractScrollArea::focusOutEvent(e);\n   mBlinkTimer->stop();\n   mBlinkCursorVisible = false;\n   viewport()->update();\n}\n\nstd::optional<AddressTextDocumentWidget::MouseHitTest>\nAddressTextDocumentWidget::mouseEventHitTest(QMouseEvent *e)\n{\n   // Translate mouse position to document cursor\n   auto documentLayout = mTextDocument->documentLayout();\n   auto documentOffset = QPoint { horizontalScrollBar()->value(), 0 };\n   auto cursorPos = documentLayout->hitTest(e->pos() + documentOffset, Qt::FuzzyHit);\n   if (cursorPos < 0) {\n      return { };\n   }\n\n   auto cursor = QTextCursor { mTextDocument };\n   cursor.setPosition(cursorPos);\n\n   // Translate mouse position to address\n   auto firstVisibleLine = verticalScrollBar()->value();\n   auto viewportLine = static_cast<int>(e->pos().y() - mDocumentMargin) / mLineHeight;\n   if (viewportLine >= mNumLines - firstVisibleLine) {\n      viewportLine = mNumLines - firstVisibleLine - 1;\n   } else if (firstVisibleLine + viewportLine < 0) {\n      viewportLine = -firstVisibleLine;\n   }\n\n   auto firstVisibleAddress = mStartAddress + static_cast<uint32_t>(firstVisibleLine) * mBytesPerLine;\n   auto address = firstVisibleAddress + static_cast<uint32_t>(viewportLine * mBytesPerLine);\n   return MouseHitTest { address, cursor };\n}\n\nvoid\nAddressTextDocumentWidget::mousePressEvent(QMouseEvent *e)\n{\n   auto handled = false;\n\n   if (e->buttons() == Qt::LeftButton || e->buttons() == Qt::RightButton) {\n      if (auto hit = mouseEventHitTest(e)) {\n         auto cursor = DocumentCursor { };\n         cursor.address = hit->lineAddress;\n         cursor.cursorPosition = hit->textCursor.positionInBlock();\n\n         if (e->buttons() == Qt::RightButton) {\n            auto selectionBegin = getDocumentSelectionBegin();\n            auto selectionEnd = getDocumentSelectionEnd();\n            if (selectionBegin != selectionEnd) {\n               auto selectionFirst = std::min(selectionBegin, selectionEnd);\n               auto selectionLast = std::max(selectionBegin, selectionEnd);\n\n               if (selectionFirst < cursor && cursor < selectionLast) {\n                  handled = true;\n               }\n            }\n         }\n\n         if (!handled) {\n            // Update cursors\n            setDocumentCursor(cursor);\n            setDocumentSelectionBegin(cursor);\n            setDocumentSelectionEnd(cursor);\n\n            // Start cursor blink timer\n            mBlinkTimer->start();\n            mBlinkCursorVisible = true;\n\n            // Update the currently highlighted word\n            auto character = mTextDocument->characterAt(hit->textCursor.position());\n            if (character.isSpace()) {\n               mHighlightedWord.clear();\n               updateHighlightedWord();\n            }\n            else {\n               hit->textCursor.select(QTextCursor::WordUnderCursor);\n               mHighlightedWord = hit->textCursor.selectedText();\n               updateHighlightedWord();\n            }\n\n            viewport()->update();\n            handled = true;\n         }\n      }\n   }\n\n   if (handled) {\n      e->accept();\n   } else {\n      QAbstractScrollArea::mousePressEvent(e);\n   }\n}\n\nvoid\nAddressTextDocumentWidget::mouseMoveEvent(QMouseEvent *e)\n{\n   auto handled = false;\n\n   if (e->buttons() & Qt::LeftButton) {\n      if (auto hit = mouseEventHitTest(e)) {\n         auto cursor = DocumentCursor { };\n         cursor.address = hit->lineAddress;\n         cursor.cursorPosition = hit->textCursor.positionInBlock();\n\n         // Update cursors\n         setDocumentCursor(cursor);\n         setDocumentSelectionEnd(cursor);\n\n         // Start cursor blink timer\n         mBlinkTimer->start();\n         mBlinkCursorVisible = true;\n\n         // Update the currently highlighted word\n         auto character = mTextDocument->characterAt(hit->textCursor.position());\n         if (character.isSpace()) {\n            mHighlightedWord.clear();\n            updateHighlightedWord();\n         } else {\n            hit->textCursor.select(QTextCursor::WordUnderCursor);\n            mHighlightedWord = hit->textCursor.selectedText();\n            updateHighlightedWord();\n         }\n\n         // Ensure cursor is visible\n         // TODO: Use a QTimer to rate limit this auto scrolling\n         auto line = static_cast<int>((cursor.address - mStartAddress) / mBytesPerLine);\n         if (line > verticalScrollBar()->maximum()) {\n            verticalScrollBar()->setValue(verticalScrollBar()->maximum());\n         } else if (line < 0) {\n            verticalScrollBar()->setValue(0);\n         } else {\n            auto firstLine = verticalScrollBar()->value();\n            auto lastLine = firstLine + verticalScrollBar()->pageStep();\n            if (line < firstLine) {\n               verticalScrollBar()->setValue(line);\n            } else if (line > lastLine) {\n               verticalScrollBar()->setValue(line - verticalScrollBar()->pageStep() + 1);\n            }\n         }\n\n         viewport()->update();\n         handled = true;\n      }\n   }\n\n   if (handled) {\n      e->accept();\n   } else {\n      QAbstractScrollArea::mouseMoveEvent(e);\n   }\n}\n\nvoid\nAddressTextDocumentWidget::mouseReleaseEvent(QMouseEvent *e)\n{\n   auto handled = false;\n   if (e->button() == Qt::BackButton) {\n      navigateBackward();\n      handled = true;\n   } else if (e->button() == Qt::ForwardButton) {\n      navigateForward();\n      handled = true;\n   } else if (e->button() == Qt::RightButton) {\n      showContextMenu(e);\n      handled = true;\n   }\n\n   if (handled) {\n      e->accept();\n   } else {\n      QAbstractScrollArea::mouseReleaseEvent(e);\n   }\n}\n\nvoid\nAddressTextDocumentWidget::mouseDoubleClickEvent(QMouseEvent *e)\n{\n   auto handled = false;\n   if (e->buttons() & Qt::LeftButton) {\n      if (auto hit = mouseEventHitTest(e)) {\n         auto blockPosition = hit->textCursor.block().position();\n         hit->textCursor.select(QTextCursor::WordUnderCursor);\n\n         setDocumentSelectionBegin({\n            hit->lineAddress,\n            hit->textCursor.selectionStart() - blockPosition\n         });\n         setDocumentSelectionEnd({\n            hit->lineAddress,\n            hit->textCursor.selectionEnd() - blockPosition\n         });\n         setDocumentCursor({\n            hit->lineAddress,\n            hit->textCursor.selectionEnd() - blockPosition\n         });\n\n         viewport()->update();\n         handled = true;\n      }\n   }\n\n   if (handled) {\n      e->accept();\n   } else {\n      QAbstractScrollArea::mouseDoubleClickEvent(e);\n   }\n}\n\nvoid\nAddressTextDocumentWidget::keyPressEvent(QKeyEvent *e)\n{\n   auto cursor = getDocumentCursor();\n   auto cursorLine = static_cast<int>((cursor.address - mStartAddress) / mBytesPerLine);\n   auto firstVisibleLine = verticalScrollBar()->value();\n   auto lastVisibleLine = firstVisibleLine + verticalScrollBar()->pageStep() - 1;\n\n   if (cursorLine < firstVisibleLine || cursorLine > lastVisibleLine) {\n      ensureCursorVisible(false);\n   }\n\n   auto moveToEndOfNewCursorLine = false;\n   auto moveAnchor = true;\n   auto moveAddressOffset = 0;\n   auto textCursor = QTextCursor { mTextDocument };\n   auto textBlock = mTextDocument->findBlockByLineNumber(cursorLine - firstVisibleLine);\n   auto endOfCurrentLine = textBlock.length();\n   textCursor.setPosition(textBlock.position() + cursor.cursorPosition);\n\n   if (e->matches(QKeySequence::MoveToPreviousChar) || e->matches(QKeySequence::SelectPreviousChar)) {\n      if (cursor.cursorPosition == 0) {\n         moveAddressOffset = -mBytesPerLine;\n         moveToEndOfNewCursorLine = true;\n      } else {\n         cursor.cursorPosition -= 1;\n      }\n\n      moveAnchor = e->matches(QKeySequence::MoveToPreviousChar);\n   } else if (e->matches(QKeySequence::MoveToNextChar) || e->matches(QKeySequence::SelectNextChar)) {\n      if (cursor.cursorPosition >= endOfCurrentLine) {\n         moveAddressOffset = mBytesPerLine;\n         cursor.cursorPosition = 0;\n      } else {\n         cursor.cursorPosition += 1;\n      }\n\n      moveAnchor = e->matches(QKeySequence::MoveToNextChar);\n   } else if (e->matches(QKeySequence::MoveToStartOfLine) || e->matches(QKeySequence::SelectStartOfLine)) {\n      cursor.cursorPosition = 0;\n      moveAnchor = e->matches(QKeySequence::MoveToStartOfLine);\n   } else if (e->matches(QKeySequence::MoveToEndOfLine) || e->matches(QKeySequence::SelectEndOfLine)) {\n      cursor.cursorPosition = endOfCurrentLine;\n      moveAnchor = e->matches(QKeySequence::MoveToEndOfLine);\n   } else if (e->matches(QKeySequence::MoveToPreviousLine) || e->matches(QKeySequence::SelectPreviousLine)) {\n      moveAddressOffset = -mBytesPerLine;\n      moveAnchor = e->matches(QKeySequence::MoveToPreviousLine);\n   } else if (e->matches(QKeySequence::MoveToNextLine) || e->matches(QKeySequence::SelectNextLine)) {\n      moveAddressOffset = mBytesPerLine;\n      moveAnchor = e->matches(QKeySequence::MoveToNextLine);\n   } else if (e->matches(QKeySequence::MoveToPreviousPage) || e->matches(QKeySequence::SelectPreviousPage)) {\n      moveAddressOffset = verticalScrollBar()->pageStep() * -mBytesPerLine;\n      moveAnchor = e->matches(QKeySequence::MoveToPreviousPage);\n   } else if (e->matches(QKeySequence::MoveToNextPage) || e->matches(QKeySequence::SelectNextPage)) {\n      moveAddressOffset = verticalScrollBar()->pageStep() * mBytesPerLine;\n      moveAnchor = e->matches(QKeySequence::MoveToNextPage);\n   } else if (e->matches(QKeySequence::MoveToStartOfDocument) || e->matches(QKeySequence::SelectStartOfDocument)) {\n      cursor.address = mStartAddress;\n      cursor.cursorPosition = 0;\n      moveAnchor = e->matches(QKeySequence::MoveToStartOfDocument);\n   } else if (e->matches(QKeySequence::MoveToEndOfDocument) || e->matches(QKeySequence::SelectEndOfDocument)) {\n      cursor.address = mEndAddress;\n      moveToEndOfNewCursorLine = true;\n      moveAnchor = e->matches(QKeySequence::MoveToEndOfDocument);\n   } else if (e->matches(QKeySequence::MoveToPreviousWord) || e->matches(QKeySequence::SelectPreviousWord)) {\n      if (!textCursor.movePosition(QTextCursor::PreviousWord)) {\n         cursor.cursorPosition = 0;\n      } else {\n         cursor.cursorPosition = textCursor.positionInBlock();\n      }\n      moveAnchor = e->matches(QKeySequence::MoveToPreviousWord);\n   } else if (e->matches(QKeySequence::MoveToNextWord) || e->matches(QKeySequence::SelectNextWord)) {\n      if (!textCursor.movePosition(QTextCursor::NextWord)) {\n         cursor.cursorPosition = endOfCurrentLine;\n      } else {\n         cursor.cursorPosition = textCursor.positionInBlock();\n      }\n      moveAnchor = e->matches(QKeySequence::MoveToNextWord);\n   } else if (e->matches(QKeySequence::SelectAll)) {\n      setDocumentSelectionBegin({ mStartAddress, 0 });\n      cursor.address = mEndAddress;\n      moveToEndOfNewCursorLine = true;\n      moveAnchor = false;\n   } else if (e->matches(QKeySequence::Deselect)) {\n      moveAnchor = true;\n   } else if (e->matches(QKeySequence::Back)) {\n      navigateBackward();\n      e->accept();\n      return;\n   } else if (e->matches(QKeySequence::Forward)) {\n      navigateForward();\n      e->accept();\n      return;\n   } else if (e->matches(QKeySequence::Copy)) {\n      copySelection();\n      e->accept();\n      return;\n   } else {\n      return QAbstractScrollArea::keyPressEvent(e);\n   }\n\n   if (moveAddressOffset) {\n      if (moveAddressOffset < 0) {\n         if (cursor.address - mStartAddress < static_cast<uint32_t>(-moveAddressOffset)) {\n            cursor.address = mStartAddress;\n            cursor.cursorPosition = 0;\n         } else {\n            cursor.address += moveAddressOffset;\n         }\n      } else {\n         if (mEndAddress - cursor.address < static_cast<uint32_t>(moveAddressOffset)) {\n            cursor.address = mEndAddress;\n            moveToEndOfNewCursorLine = true;\n         } else {\n            cursor.address += moveAddressOffset;\n         }\n      }\n   }\n\n   if (moveAnchor) {\n      setDocumentCursor(cursor);\n      setDocumentSelectionBegin(cursor);\n      setDocumentSelectionEnd(cursor);\n   } else {\n      setDocumentCursor(cursor);\n      setDocumentSelectionEnd(cursor);\n   }\n\n   ensureCursorVisible(false);\n\n   if (moveToEndOfNewCursorLine) {\n      cursorLine = static_cast<int>((cursor.address - mStartAddress) / mBytesPerLine);\n      firstVisibleLine = verticalScrollBar()->value();\n      textBlock = mTextDocument->findBlockByLineNumber(cursorLine - firstVisibleLine);\n      cursor.cursorPosition = textBlock.length();\n      setDocumentCursor(cursor);\n   }\n\n   mBlinkTimer->start();\n   mBlinkCursorVisible = true;\n   viewport()->update();\n   e->accept();\n}\n\nvoid\nAddressTextDocumentWidget::showAddress(VirtualAddress address)\n{\n   setDocumentCursor(cursorFromAddress(address));\n   setDocumentSelectionBegin({});\n   setDocumentSelectionEnd({});\n\n   mHighlightedWord = QString { \"%1\" }.arg(address, 8, 16, QLatin1Char{ '0' });\n   if (!ensureCursorVisible(true)) {\n      updateHighlightedWord();\n      viewport()->update();\n   }\n}\n\nbool\nAddressTextDocumentWidget::ensureCursorVisible(bool centerOnCursor)\n{\n   auto cursor = getDocumentCursor();\n   auto address = cursor.address;\n   auto targetLine = static_cast<int>((address - mStartAddress) / mBytesPerLine);\n   auto firstVisibleLine = verticalScrollBar()->value();\n   auto lastVisibleLine = firstVisibleLine + verticalScrollBar()->pageStep() - 1;\n\n   if (targetLine >= firstVisibleLine && targetLine <= lastVisibleLine) {\n      // Already visible\n      return false;\n   }\n\n   // Scroll so that the target line is 1/3rd of way down screen\n   if (centerOnCursor) {\n      targetLine -= verticalScrollBar()->pageStep() / 3;\n   } else if (targetLine > lastVisibleLine) {\n      targetLine -= verticalScrollBar()->pageStep() - 1;\n   }\n\n   if (targetLine < 0) {\n      verticalScrollBar()->setValue(0);\n   } else if (targetLine > verticalScrollBar()->maximum()) {\n      verticalScrollBar()->setValue(verticalScrollBar()->maximum());\n   } else {\n      verticalScrollBar()->setValue(targetLine);\n   }\n\n   updateTextDocument(false);\n   viewport()->update();\n   return true;\n}\n\nvoid\nAddressTextDocumentWidget::updateHorizontalScrollBar()\n{\n   auto viewportWidth = viewport()->width();\n   if (mMaxDocumentWidth < viewportWidth) {\n      horizontalScrollBar()->setMinimum(0);\n      horizontalScrollBar()->setMaximum(0);\n      horizontalScrollBar()->setValue(0);\n   } else {\n      horizontalScrollBar()->setMinimum(0);\n      horizontalScrollBar()->setMaximum(mMaxDocumentWidth - viewportWidth);\n      horizontalScrollBar()->setSingleStep(mCharacterWidth);\n      horizontalScrollBar()->setPageStep(viewportWidth);\n   }\n}\n\nvoid\nAddressTextDocumentWidget::updateVerticalScrollBar()\n{\n   if (mNumLines == 0) {\n      verticalScrollBar()->setMinimum(0);\n      verticalScrollBar()->setMaximum(0);\n      verticalScrollBar()->setValue(0);\n   } else {\n      auto margin = static_cast<int>(mDocumentMargin * 2);\n      auto pageLines = (viewport()->height() - margin) / mLineHeight;\n      verticalScrollBar()->setMinimum(0);\n      verticalScrollBar()->setMaximum(mNumLines - pageLines);\n      verticalScrollBar()->setSingleStep(1);\n      verticalScrollBar()->setPageStep(pageLines);\n   }\n}\n\nvoid\nAddressTextDocumentWidget::updateHighlightedWord()\n{\n   mHighlightedWordSelections.clear();\n\n   auto findCursor = QTextCursor { mTextDocument };\n   while (!findCursor.isNull() && !findCursor.atEnd()) {\n      findCursor = mTextDocument->find(mHighlightedWord, findCursor, QTextDocument::FindWholeWords);\n      if (!findCursor.isNull()) {\n         auto selection = QAbstractTextDocumentLayout::Selection { };\n         selection.cursor = findCursor;\n         selection.format = mTextFormatHighlightedWord;\n         mHighlightedWordSelections.push_back(selection);\n      }\n   }\n}\n\nvoid\nAddressTextDocumentWidget::updateTextDocument(bool forceUpdate)\n{\n   // Check if text document is representing current view\n   auto firstVisibleLine = verticalScrollBar()->value();\n   auto lastVisibleLine = firstVisibleLine + verticalScrollBar()->pageStep() - 1;\n   auto firstVisibleLineAddress = static_cast<VirtualAddress>(mStartAddress + (firstVisibleLine * mBytesPerLine));\n   auto lastVisibleLineAddress = static_cast<VirtualAddress>(mStartAddress + (lastVisibleLine * mBytesPerLine));\n\n   if (!forceUpdate) {\n      if (firstVisibleLineAddress == mTextDocumentFirstLineAddress &&\n         lastVisibleLineAddress == mTextDocumentLastLineAddress) {\n         return;\n      }\n   }\n\n   // Generate new text document\n   mTextDocument->clear();\n\n   auto cursor = QTextCursor { mTextDocument };\n   cursor.beginEditBlock();\n   updateTextDocument(cursor, firstVisibleLineAddress, lastVisibleLineAddress, mBytesPerLine, true);\n   cursor.endEditBlock();\n\n   // Update horizontal scroll\n   mMaxDocumentWidth = std::max(mMaxDocumentWidth, mTextDocument->documentLayout()->documentSize().width());\n   updateHorizontalScrollBar();\n\n   // Search text for highlighted word\n   updateHighlightedWord();\n\n   mTextDocumentFirstLineAddress = firstVisibleLineAddress;\n   mTextDocumentLastLineAddress = lastVisibleLineAddress;\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/addresstextdocumentwidget.h",
    "content": "#pragma once\n#include <QAbstractScrollArea>\n#include <QAbstractTextDocumentLayout>\n#include <QTextCharFormat>\n#include <QTextCursor>\n#include <QVector>\n#include <optional>\n\n#include <libdecaf/decaf_debug_api.h>\n\nclass QTextDocument;\n\nclass AddressTextDocumentWidget : public QAbstractScrollArea\n{\nprotected:\n   using VirtualAddress = decaf::debug::VirtualAddress;\n\n   struct DocumentCursor\n   {\n      VirtualAddress address;\n      int cursorPosition;\n\n      bool operator <(const DocumentCursor &rhs) const\n      {\n         if (address < rhs.address) {\n            return true;\n         } else if (address > rhs.address) {\n            return false;\n         }\n\n         return cursorPosition < rhs.cursorPosition;\n      }\n\n      bool operator !=(const DocumentCursor &rhs) const\n      {\n         return address != rhs.address || cursorPosition != rhs.cursorPosition;\n      }\n   };\n\n   struct MouseHitTest\n   {\n      VirtualAddress lineAddress;\n      QTextCursor textCursor;\n   };\n\npublic:\n   AddressTextDocumentWidget(QWidget *parent = nullptr);\n\n   void setAddressRange(VirtualAddress start, VirtualAddress end);\n   void setBytesPerLine(int bytesPerLine);\n\n   int getBytesPerLine() { return mBytesPerLine; };\n   VirtualAddress getStartAddress() { return mStartAddress; }\n   VirtualAddress getEndAddress() { return mEndAddress; }\n\n   void navigateToAddress(VirtualAddress address);\n   void navigateBackward();\n   void navigateForward();\n   void copySelection();\n\nprotected:\n   std::optional<MouseHitTest> mouseEventHitTest(QMouseEvent *e);\n   int characterWidth() { return mCharacterWidth; }\n   int documentMargin() { return mDocumentMargin; }\n   int lineHeight() { return mLineHeight; }\n   void updateTextDocument(bool forceUpdate);\n\nprotected:\n   void paintEvent(QPaintEvent *e) override;\n   void resizeEvent(QResizeEvent *) override;\n\n   void focusInEvent(QFocusEvent *e) override;\n   void focusOutEvent(QFocusEvent *e) override;\n\n   void mouseMoveEvent(QMouseEvent *e) override;\n   void mousePressEvent(QMouseEvent *e) override;\n   void mouseReleaseEvent(QMouseEvent *e) override;\n   void mouseDoubleClickEvent(QMouseEvent *e) override;\n\n   void keyPressEvent(QKeyEvent *e) override;\n\n   virtual void updateTextDocument(QTextCursor cursor,\n                                   VirtualAddress firstLineAddress,\n                                   VirtualAddress lastLineAddress,\n                                   int bytePerLine, bool forDisplay) = 0;\n\n   virtual QVector<QAbstractTextDocumentLayout::Selection>\n   getCustomSelections(QTextDocument *document) { return {}; }\n\n   // We provide a default implementation for cursor / selection tracking which\n   // works perfectly fine as long as bytesPerLine does not change.\n\n   virtual DocumentCursor cursorFromAddress(VirtualAddress address)\n   {\n      return DocumentCursor { address, 0 };\n   }\n\n   virtual VirtualAddress cursorToAddress(DocumentCursor cursor)\n   {\n      return cursor.address;\n   }\n\n   virtual DocumentCursor getDocumentCursor()\n   {\n      return mDefaultCursor;\n   }\n\n   virtual DocumentCursor getDocumentSelectionBegin()\n   {\n      return mDefaultSelectionBegin;\n   }\n\n   virtual DocumentCursor getDocumentSelectionEnd()\n   {\n      return mDefaultSelectionEnd;\n   }\n\n   virtual void setDocumentCursor(DocumentCursor cursor)\n   {\n      mDefaultCursor = cursor;\n   }\n\n   virtual void setDocumentSelectionBegin(DocumentCursor cursor)\n   {\n      mDefaultSelectionBegin = cursor;\n   }\n\n   virtual void setDocumentSelectionEnd(DocumentCursor cursor)\n   {\n      mDefaultSelectionEnd = cursor;\n   }\n\n   virtual void showContextMenu(QMouseEvent *e)\n   {\n   }\n\nprivate:\n   void showAddress(VirtualAddress address);\n   bool ensureCursorVisible(bool centerOnCursor);\n   void updateHighlightedWord();\n   void updateHorizontalScrollBar();\n   void updateVerticalScrollBar();\n\nprivate:\n   int mLineHeight = 16;\n   int mCharacterWidth = 16;\n   int mBytesPerLine = 16;\n   int mDocumentMargin = 0;\n   int mNumLines = 0;\n\n   // Address range\n   VirtualAddress mStartAddress = 0;\n   VirtualAddress mEndAddress = 0;\n\n   // Text document\n   QTextDocument *mTextDocument = nullptr;\n   VirtualAddress mTextDocumentFirstLineAddress = 0;\n   VirtualAddress mTextDocumentLastLineAddress = 0;\n   qreal mMaxDocumentWidth = 0.0;\n\n   // Text formats\n   QTextCharFormat mTextFormatSelection;\n   QTextCharFormat mTextFormatHighlightedWord;\n\n   // Cursor\n   bool mBlinkCursorVisible = false;\n   QTimer *mBlinkTimer = nullptr;\n\n   // Highlighted word\n   QString mHighlightedWord;\n   QVector<QAbstractTextDocumentLayout::Selection> mHighlightedWordSelections;\n\n   // Navigation\n   int mNavigationHistoryIndex = 0;\n   size_t mNavigationHistoryMaxSize = 128;\n   std::vector<VirtualAddress> mNavigationBackwardStack;\n   std::vector<VirtualAddress> mNavigationForwardStack;\n\n   // Default cursor implementation\n   DocumentCursor mDefaultCursor = { };\n   DocumentCursor mDefaultSelectionBegin = { };\n   DocumentCursor mDefaultSelectionEnd = { };\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/breakpointsmodel.h",
    "content": "#pragma once\n#include <QAbstractTableModel>\n#include <vector>\n\n#include \"debugdata.h\"\n\nclass BreakpointsModel : public QAbstractTableModel\n{\n   Q_OBJECT\n\n   static constexpr const char *ColumnNames[] = {\n      \"Address\",\n      \"Type\",\n   };\n\n   static constexpr int ColumnCount =\n      static_cast<int>(sizeof(ColumnNames) / sizeof(ColumnNames[0]));\n\n   using CpuBreakpoint = decaf::debug::CpuBreakpoint;\n\npublic:\n   enum UserRoles\n   {\n      AddressRole = Qt::UserRole,\n   };\n\n   BreakpointsModel(QObject *parent = nullptr) :\n      QAbstractTableModel(parent)\n   {\n   }\n\n   void setDebugData(DebugData *debugData)\n   {\n      mDebugData = debugData;\n      connect(mDebugData, &DebugData::dataChanged, this, &BreakpointsModel::debugDataChanged);\n   }\n\n   int rowCount(const QModelIndex &parent) const override\n   {\n      if (!mDebugData) {\n         return 0;\n      }\n\n      return static_cast<int>(mDebugData->breakpoints().size());\n   }\n\n   int columnCount(const QModelIndex &parent) const override\n   {\n      return ColumnCount;\n   }\n\n   static QString getTypeString(CpuBreakpoint::Type type)\n   {\n      switch (type) {\n      case CpuBreakpoint::Type::SingleFire:\n         return tr(\"Single Fire\");\n      case CpuBreakpoint::Type::MultiFire:\n         return tr(\"Multi Fire\");\n      default:\n         return tr(\"Unknown\");\n      }\n   }\n\n   QVariant data(const QModelIndex &index, int role) const override\n   {\n      if (!mDebugData || !index.isValid()) {\n         return QVariant { };\n      }\n\n      const auto &breakpoints = mDebugData->breakpoints();\n      if (index.row() >= breakpoints.size() || index.row() < 0) {\n         return QVariant { };\n      }\n\n      if (role == Qt::DisplayRole) {\n         const auto &breakpoint = breakpoints[index.row()];\n\n         switch (index.column()) {\n         case 0:\n            return QString(\"%1\").arg(static_cast<uint>(breakpoint.address), 8, 16, QChar { '0' });\n         case 1:\n            return getTypeString(breakpoint.type);\n         }\n      } else if (role == AddressRole) {\n         return breakpoints[index.row()].address;\n      }\n\n      return QVariant { };\n   }\n\n   QVariant headerData(int section, Qt::Orientation orientation, int role) const override\n   {\n      if (role != Qt::DisplayRole) {\n         return QVariant{ };\n      }\n\n      if (orientation == Qt::Horizontal) {\n         if (section < ColumnCount) {\n            return ColumnNames[section];\n         }\n      }\n\n      return QVariant { };\n   }\n\nprivate slots:\n   void debugDataChanged()\n   {\n      auto newSize = static_cast<int>(mDebugData->breakpoints().size());\n\n      if (newSize < mPreviousSize) {\n         beginRemoveRows({}, newSize, mPreviousSize - 1);\n         endRemoveRows();\n      } else if (newSize > mPreviousSize) {\n         beginInsertRows({}, mPreviousSize, newSize - 1);\n         endInsertRows();\n      }\n\n      mPreviousSize = newSize;\n   }\n\nprivate:\n   DebugData *mDebugData = nullptr;\n   int mPreviousSize = 0;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/breakpointswindow.cpp",
    "content": "#include \"breakpointswindow.h\"\n#include \"ui_breakpointswindow.h\"\n\n#include \"debugdata.h\"\n#include \"breakpointsmodel.h\"\n\n#include <libcpu/cpu_breakpoints.h>\n#include <QKeyEvent>\n\nBreakpointsWindow::BreakpointsWindow(QWidget *parent) :\n   QWidget(parent),\n   ui(new Ui::BreakpointsWindow { })\n{\n   ui->setupUi(this);\n   ui->tableView->installEventFilter(this);\n}\n\nBreakpointsWindow::~BreakpointsWindow()\n{\n   delete ui;\n}\n\nvoid\nBreakpointsWindow::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n   mBreakpointsModel = new BreakpointsModel { this };\n   mBreakpointsModel->setDebugData(debugData);\n\n   ui->tableView->setModel(mBreakpointsModel);\n\n   auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1;\n   ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);\n   ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2);\n\n   ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);\n   ui->tableView->update();\n}\n\nvoid\nBreakpointsWindow::breakpointsViewDoubleClicked(const QModelIndex &index)\n{\n   auto address = mBreakpointsModel->data(index, BreakpointsModel::AddressRole);\n   if (!address.isValid()) {\n      return;\n   }\n\n   navigateToTextAddress(address.value<uint32_t>());\n}\n\nbool\nBreakpointsWindow::eventFilter(QObject *obj, QEvent *event)\n{\n   if (event->type() == QEvent::KeyPress) {\n      QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);\n      if (keyEvent->key() == Qt::Key_Delete) {\n         auto index = ui->tableView->currentIndex();\n         if (index.isValid()) {\n            auto address = mBreakpointsModel->data(index, BreakpointsModel::AddressRole);\n            if (address.isValid()) {\n               cpu::removeBreakpoint(address.value<uint32_t>());\n               return true;\n            }\n         }\n      }\n   }\n\n   return QObject::eventFilter(obj, event);\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/breakpointswindow.h",
    "content": "#pragma once\n#include <QWidget>\n\nnamespace Ui\n{\nclass BreakpointsWindow;\n}\n\nclass DebugData;\nclass BreakpointsModel;\n\nclass BreakpointsWindow : public QWidget\n{\n   Q_OBJECT\n\npublic:\n   explicit BreakpointsWindow(QWidget *parent = nullptr);\n   ~BreakpointsWindow();\n\n   void setDebugData(DebugData *debugData);\n\nsignals:\n   void navigateToTextAddress(uint32_t address);\n\nprotected slots:\n   void breakpointsViewDoubleClicked(const QModelIndex &index);\n\nprotected:\n   bool eventFilter(QObject *obj, QEvent *event) override;\n\nprivate:\n   Ui::BreakpointsWindow *ui;\n\n   DebugData *mDebugData = nullptr;\n   BreakpointsModel *mBreakpointsModel = nullptr;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/debugdata.cpp",
    "content": "#include \"debugdata.h\"\n\n#include <libcpu/cpu_breakpoints.h>\n#include <libcpu/jit_stats.h>\n#include <libdecaf/decaf_debug_api.h>\n\nbool\nDebugData::update()\n{\n   if (!decaf::debug::ready()) {\n      return false;\n   }\n\n   mThreads.clear();\n   mSegments.clear();\n   mVoices.clear();\n\n   decaf::debug::sampleCafeThreads(mThreads);\n   decaf::debug::sampleCafeMemorySegments(mSegments);\n   decaf::debug::sampleCafeVoices(mVoices);\n   decaf::debug::sampleCpuBreakpoints(mBreakpoints);\n   cpu::jit::sampleStats(mJitStats);\n\n   if (!mEntryHit) {\n      if (decaf::debug::getLoadedModuleInfo(mLoadedModule)) {\n         // TODO: Spawn thread to do analysis\n         decaf::debug::analyseLoadedModules(mAnalyseDatabase);\n         decaf::debug::analyseCode(mAnalyseDatabase,\n                                   mLoadedModule.textAddr,\n                                   mLoadedModule.textAddr + mLoadedModule.textSize);\n      }\n\n      mEntryHit = true;\n      emit entry();\n   }\n\n   auto paused = decaf::debug::isPaused();\n   auto pauseInitiatorCoreId = 0;\n   auto pauseNia = VirtualAddress { 0 };\n   if (paused) {\n      pauseInitiatorCoreId = decaf::debug::getPauseInitiatorCoreId();\n      pauseNia = decaf::debug::getPausedContext(pauseInitiatorCoreId)->nia;\n   }\n\n   if ((paused != mPaused) ||\n       (pauseInitiatorCoreId != mPauseInitiatorCoreId) ||\n       (pauseNia != mPauseNia)) {\n      mPaused = paused;\n      mPauseNia = pauseNia;\n      mPauseInitiatorCoreId = pauseInitiatorCoreId;\n      emit executionStateChanged(paused, pauseInitiatorCoreId, pauseNia);\n   }\n\n   auto captureState = decaf::debug::pm4CaptureState();\n   if (captureState != mPm4CaptureState) {\n      mPm4CaptureState = captureState;\n      emit pm4CaptureStateChanged(captureState);\n   }\n\n   auto jitProfilingMask = cpu::jit::getProfilingMask();\n   if (jitProfilingMask != mJitProfilingMask) {\n      mJitProfilingMask = jitProfilingMask;\n      emit jitProfilingStateChanged(\n         jitProfilingMask & (1 << 0),\n         jitProfilingMask & (1 << 1),\n         jitProfilingMask & (1 << 2));\n   }\n\n   emit dataChanged();\n   return true;\n}\n\nvoid\nDebugData::setActiveThreadIndex(int index)\n{\n   mActiveThreadIndex = index;\n   emit activeThreadIndexChanged();\n}\n\nvoid\nDebugData::setJitProfilingState(bool core0, bool core1, bool core2)\n{\n   cpu::jit::setProfilingMask(((core0 ? 1 : 0) << 0) |\n                              ((core1 ? 1 : 0) << 1) |\n                              ((core2 ? 1 : 0) << 2));\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/debugdata.h",
    "content": "#pragma once\n#include <libcpu/cpu_breakpoints.h>\n#include <libcpu/jit_stats.h>\n#include <libdecaf/decaf_debug_api.h>\n#include <vector>\n#include <QObject>\n\nclass DebugData : public QObject\n{\n   Q_OBJECT\n\npublic:\n   using AnalyseDatabase = decaf::debug::AnalyseDatabase;\n   using CafeThread = decaf::debug::CafeThread;\n   using CafeMemorySegment = decaf::debug::CafeMemorySegment;\n   using CafeModuleInfo = decaf::debug::CafeModuleInfo;\n   using CafeVoice = decaf::debug::CafeVoice;\n   using CpuBreakpoint = decaf::debug::CpuBreakpoint;\n   using JitStats = cpu::jit::JitStats;\n   using Pm4CaptureState = decaf::debug::Pm4CaptureState;\n   using VirtualAddress = decaf::debug::VirtualAddress;\n\n   DebugData(QObject *parent = nullptr) :\n      QObject(parent)\n   {\n   }\n\n   int activeThreadIndex() const\n   {\n      return mActiveThreadIndex;\n   }\n\n   const CafeThread *activeThread() const\n   {\n      if (mActiveThreadIndex < 0 || mActiveThreadIndex >= mThreads.size()) {\n         return nullptr;\n      }\n\n      return &mThreads[mActiveThreadIndex];\n   }\n\n   const CafeModuleInfo &loadedModule() const\n   {\n      return mLoadedModule;\n   }\n\n   const AnalyseDatabase &analyseDatabase() const\n   {\n      return mAnalyseDatabase;\n   }\n\n   const JitStats &jitStats() const\n   {\n      return mJitStats;\n   }\n\n   const std::vector<CpuBreakpoint> breakpoints() const\n   {\n      return mBreakpoints;\n   }\n\n   const CpuBreakpoint *getBreakpoint(VirtualAddress address) const\n   {\n      for (auto &breakpoint : mBreakpoints) {\n         if (breakpoint.address == address) {\n            return &breakpoint;\n         }\n      }\n\n      return nullptr;\n   }\n\n   const std::vector<CafeThread> &threads() const\n   {\n      return mThreads;\n   }\n\n   const std::vector<CafeMemorySegment> &segments() const\n   {\n      return mSegments;\n   }\n\n   const std::vector<CafeVoice> &voices() const\n   {\n      return mVoices;\n   }\n\n   const CafeMemorySegment *segmentForAddress(VirtualAddress address) const\n   {\n      for (auto &segment : mSegments) {\n         if (address >= segment.address && address - segment.address < segment.size) {\n            return &segment;\n         }\n      }\n\n      return nullptr;\n   }\n\n   bool paused() const\n   {\n      return mPaused;\n   }\n\n   bool update();\n   void setActiveThreadIndex(int index);\n\n   void setJitProfilingState(bool core0, bool core1, bool core2);\n\nsignals:\n   void entry();\n   void dataChanged();\n   void activeThreadIndexChanged();\n\n   void pm4CaptureStateChanged(Pm4CaptureState state);\n   void executionStateChanged(bool paused, int pauseInitiatorCoreId,\n                              VirtualAddress pauseNia);\n   void jitProfilingStateChanged(bool core0, bool core1, bool core2);\n\nprivate:\n   bool mEntryHit = false;\n   int mActiveThreadIndex = -1;\n\n   AnalyseDatabase mAnalyseDatabase = { };\n   CafeModuleInfo mLoadedModule = { };\n\n   std::vector<CafeThread> mThreads;\n   std::vector<CafeMemorySegment> mSegments;\n   std::vector<CafeVoice> mVoices;\n\n   std::vector<CpuBreakpoint> mBreakpoints;\n   cpu::jit::JitStats mJitStats = { };\n\n   bool mPaused = false;\n   int mPauseInitiatorCoreId = -1;\n   VirtualAddress mPauseNia = 0u;\n\n   Pm4CaptureState mPm4CaptureState = Pm4CaptureState::Disabled;\n\n   unsigned mJitProfilingMask = 0u;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/debuggershortcuts.h",
    "content": "#pragma once\n#include <QAction>\n\nstruct DebuggerShortcuts\n{\n   QAction *toggleBreakpoint;\n   QAction *navigateForward;\n   QAction *navigateBackward;\n   QAction *navigateToAddress;\n   QAction *navigateToOperand;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/debuggerwindow.cpp",
    "content": "#include \"debuggerwindow.h\"\n#include \"ui_debuggerwindow.h\"\n\n#include \"debugdata.h\"\n\n#include \"breakpointswindow.h\"\n#include \"disassemblywindow.h\"\n#include \"functionswindow.h\"\n#include \"jitprofilingwindow.h\"\n#include \"memorywindow.h\"\n#include \"registerswindow.h\"\n#include \"segmentswindow.h\"\n#include \"stackwindow.h\"\n#include \"threadswindow.h\"\n#include \"voiceswindow.h\"\n\n#include <DockAreaWidget.h>\n#include <DockWidgetTab.h>\n#include <QInputDialog>\n#include <QMessageBox>\n#include <QModelIndex>\n#include <QTimer>\n\n#include <libdecaf/decaf.h>\n#include <libdecaf/decaf_config.h>\n#include <libdecaf/decaf_debug_api.h>\n#include <libgpu/gpu_config.h>\n\nDebuggerWindow::DebuggerWindow(QWidget *parent) :\n   QMainWindow(parent),\n   ui(new Ui::DebuggerWindow { })\n{\n   connect(qApp, &QApplication::focusChanged, this, &DebuggerWindow::focusChanged);\n   ui->setupUi(this);\n\n   mDebuggerShortcuts.navigateBackward = ui->actionNavigateBackward;\n   mDebuggerShortcuts.navigateForward = ui->actionNavigateForward;\n   mDebuggerShortcuts.navigateToAddress = ui->actionNavigateToAddress;\n   mDebuggerShortcuts.navigateToOperand = ui->actionNavigateToOperand;\n   mDebuggerShortcuts.toggleBreakpoint = ui->actionToggleBreakpoint;\n\n   mDockManager = new ads::CDockManager { this };\n\n   mDebugData = new DebugData { this };\n   connect(mDebugData, &DebugData::entry, this, &DebuggerWindow::onEntry);\n   connect(mDebugData, &DebugData::pm4CaptureStateChanged, this, &DebuggerWindow::pm4CaptureStateChanged);\n   connect(mDebugData, &DebugData::executionStateChanged, this, &DebuggerWindow::executionStateChanged);\n   connect(mDebugData, &DebugData::activeThreadIndexChanged, this, &DebuggerWindow::activeThreadChanged);\n\n   mBreakpointsDockWidget = new ads::CDockWidget{ tr(\"Breakpoints\") };\n   mBreakpointsWindow = new BreakpointsWindow { mBreakpointsDockWidget };\n   mBreakpointsWindow->setDebugData(mDebugData);\n   mBreakpointsDockWidget->setWidget(mBreakpointsWindow, ads::CDockWidget::ForceNoScrollArea);\n\n   mDisassemblyDockWidget = new ads::CDockWidget { tr(\"Disassembly\") };\n   mDisassemblyWindow = new DisassemblyWindow { &mDebuggerShortcuts, mDisassemblyDockWidget };\n   mDisassemblyWindow->setDebugData(mDebugData);\n   mDisassemblyDockWidget->setWidget(mDisassemblyWindow, ads::CDockWidget::ForceNoScrollArea);\n\n   mFunctionsDockWidget = new ads::CDockWidget { tr(\"Functions\") };\n   mFunctionsWindow = new FunctionsWindow { mFunctionsDockWidget };\n   mFunctionsWindow->setDebugData(mDebugData);\n   mFunctionsDockWidget->setWidget(mFunctionsWindow, ads::CDockWidget::ForceNoScrollArea);\n\n   mJitProfilingDockWidget = new ads::CDockWidget { tr(\"JIT Profiling\") };\n   mJitProfilingWindow = new JitProfilingWindow { mJitProfilingDockWidget };\n   mJitProfilingWindow->setDebugData(mDebugData);\n   mJitProfilingDockWidget->setWidget(mJitProfilingWindow, ads::CDockWidget::ForceNoScrollArea);\n\n   mMemoryDockWidget = new ads::CDockWidget { tr(\"Memory\") };\n   mMemoryWindow = new MemoryWindow { &mDebuggerShortcuts, mRegistersDockWidget };\n   mMemoryDockWidget->setWidget(mMemoryWindow, ads::CDockWidget::ForceNoScrollArea);\n\n   mRegistersDockWidget = new ads::CDockWidget { tr(\"Registers\") };\n   mRegistersWindow = new RegistersWindow { mRegistersDockWidget };\n   mRegistersWindow->setDebugData(mDebugData);\n   mRegistersDockWidget->setWidget(mRegistersWindow, ads::CDockWidget::ForceScrollArea);\n\n   mSegmentsDockWidget = new ads::CDockWidget { tr(\"Segments\") };\n   mSegmentsWindow = new SegmentsWindow { mSegmentsDockWidget };\n   mSegmentsWindow->setDebugData(mDebugData);\n   mSegmentsDockWidget->setWidget(mSegmentsWindow, ads::CDockWidget::ForceNoScrollArea);\n\n   mStackDockWidget = new ads::CDockWidget { tr(\"Stack\") };\n   mStackWindow = new StackWindow { &mDebuggerShortcuts, mStackDockWidget };\n   mStackWindow->setDebugData(mDebugData);\n   mStackDockWidget->setWidget(mStackWindow, ads::CDockWidget::ForceNoScrollArea);\n\n   mThreadsDockWidget = new ads::CDockWidget { tr(\"Threads\") };\n   mThreadsWindow = new ThreadsWindow { mThreadsDockWidget };\n   mThreadsWindow->setDebugData(mDebugData);\n   mThreadsDockWidget->setWidget(mThreadsWindow, ads::CDockWidget::ForceNoScrollArea);\n\n   mVoicesDockWidget = new ads::CDockWidget { tr(\"Voices\") };\n   mVoicesWindow = new VoicesWindow { mVoicesDockWidget };\n   mVoicesWindow->setDebugData(mDebugData);\n   mVoicesDockWidget->setWidget(mVoicesWindow, ads::CDockWidget::ForceNoScrollArea);\n\n   connect(mBreakpointsWindow, &BreakpointsWindow::navigateToTextAddress, this, &DebuggerWindow::gotoTextAddress);\n   connect(mFunctionsWindow, &FunctionsWindow::navigateToTextAddress, this, &DebuggerWindow::gotoTextAddress);\n   connect(mJitProfilingWindow, &JitProfilingWindow::navigateToTextAddress, this, &DebuggerWindow::gotoTextAddress);\n   connect(mSegmentsWindow, &SegmentsWindow::navigateToDataAddress, this, &DebuggerWindow::gotoDataAddress);\n   connect(mSegmentsWindow, &SegmentsWindow::navigateToTextAddress, this, &DebuggerWindow::gotoTextAddress);\n   connect(mStackWindow, &StackWindow::navigateToTextAddress, this, &DebuggerWindow::gotoTextAddress);\n\n   // Setup default dock layout\n   {\n      auto mainDockArea = mDockManager->addDockWidgetTab(ads::CenterDockWidgetArea, mDisassemblyDockWidget);\n      mDockManager->addDockWidgetTabToArea(mMemoryDockWidget, mainDockArea);\n      mDockManager->addDockWidgetTabToArea(mThreadsDockWidget, mainDockArea);\n      mDockManager->addDockWidgetTabToArea(mSegmentsDockWidget, mainDockArea);\n      mDockManager->addDockWidgetTabToArea(mVoicesDockWidget, mainDockArea);\n      mDockManager->addDockWidgetTabToArea(mJitProfilingDockWidget, mainDockArea);\n      mDockManager->addDockWidgetTabToArea(mBreakpointsDockWidget, mainDockArea);\n      mDisassemblyDockWidget->dockAreaWidget()->setCurrentDockWidget(mDisassemblyDockWidget);\n\n      auto sizePolicy = mainDockArea->sizePolicy();\n      sizePolicy.setHorizontalStretch(1);\n      sizePolicy.setVerticalStretch(1);\n      mainDockArea->setSizePolicy(sizePolicy);\n   }\n\n   {\n      auto leftDockArea = mDockManager->addDockWidget(ads::LeftDockWidgetArea, mFunctionsDockWidget);\n   }\n\n   {\n      auto rightDockArea = mDockManager->addDockWidget(ads::RightDockWidgetArea, mRegistersDockWidget);\n      mDockManager->addDockWidget(ads::BottomDockWidgetArea, mStackDockWidget, rightDockArea);\n   }\n\n   // Setup shortcuts\n   mBreakpointsDockWidget->toggleViewAction()->setShortcut(tr(\"Ctrl+b\"));\n   mDisassemblyDockWidget->toggleViewAction()->setShortcut(tr(\"Ctrl+i\"));\n   mMemoryDockWidget->toggleViewAction()->setShortcut(tr(\"Ctrl+m\"));\n   mRegistersDockWidget->toggleViewAction()->setShortcut(tr(\"Ctrl+r\"));\n   mSegmentsDockWidget->toggleViewAction()->setShortcut(tr(\"Ctrl+s\"));\n   mStackDockWidget->toggleViewAction()->setShortcut(tr(\"Ctrl+e\"));\n   mThreadsDockWidget->toggleViewAction()->setShortcut(tr(\"Ctrl+t\"));\n   mVoicesDockWidget->toggleViewAction()->setShortcut(tr(\"Ctrl+p\"));\n   mJitProfilingDockWidget->toggleViewAction()->setShortcut(tr(\"Ctrl+j\"));\n\n   // Add view toggles to menu\n   ui->menuView->addAction(mBreakpointsDockWidget->toggleViewAction());\n   ui->menuView->addAction(mDisassemblyDockWidget->toggleViewAction());\n   ui->menuView->addAction(mFunctionsDockWidget->toggleViewAction());\n   ui->menuView->addAction(mThreadsDockWidget->toggleViewAction());\n   ui->menuView->addAction(mSegmentsDockWidget->toggleViewAction());\n   ui->menuView->addAction(mVoicesDockWidget->toggleViewAction());\n   ui->menuView->addAction(mRegistersDockWidget->toggleViewAction());\n   ui->menuView->addAction(mStackDockWidget->toggleViewAction());\n   ui->menuView->addAction(mMemoryDockWidget->toggleViewAction());\n   ui->menuView->addAction(mJitProfilingDockWidget->toggleViewAction());\n\n   // Create a timer to poll debug data from decaf\n   mUpdateModelTimer = new QTimer { this };\n   connect(mUpdateModelTimer, SIGNAL(timeout()), this, SLOT(updateModel()));\n   mUpdateModelTimer->start(100);\n}\n\nDebuggerWindow::~DebuggerWindow()\n{\n   delete ui;\n}\n\nvoid\nDebuggerWindow::updateModel()\n{\n   if (!mDebugData->update()) {\n      return;\n   }\n}\n\nvoid\nDebuggerWindow::gotoTextAddress(DebugData::VirtualAddress address)\n{\n   mDisassemblyWindow->navigateToAddress(address);\n   mDisassemblyDockWidget->dockAreaWidget()->setCurrentDockWidget(mDisassemblyDockWidget);\n}\n\nvoid\nDebuggerWindow::gotoDataAddress(DebugData::VirtualAddress address)\n{\n   mMemoryWindow->navigateToAddress(address);\n   mMemoryDockWidget->dockAreaWidget()->setCurrentDockWidget(mMemoryDockWidget);\n}\n\nvoid\nDebuggerWindow::onEntry()\n{\n   auto textStartAddress = 0x02000000u;\n   auto dataStartAddress = 0x10000000u;\n\n   if (mDebugData->loadedModule().textAddr) {\n      textStartAddress = mDebugData->loadedModule().textAddr;\n   }\n\n   if (mDebugData->loadedModule().dataAddr) {\n      dataStartAddress = mDebugData->loadedModule().dataAddr;\n   }\n\n   if (!mDebugData->paused()) {\n      gotoDataAddress(dataStartAddress);\n      gotoTextAddress(textStartAddress);\n   }\n}\n\nvoid\nDebuggerWindow::debugPause()\n{\n   decaf::debug::pause();\n}\n\nvoid\nDebuggerWindow::debugResume()\n{\n   decaf::debug::resume();\n}\n\nvoid\nDebuggerWindow::debugStepOver()\n{\n   if (auto activeThread = mDebugData->activeThread()) {\n      decaf::debug::stepOver(activeThread->coreId);\n   }\n}\n\nvoid\nDebuggerWindow::debugStepInto()\n{\n   if (auto activeThread = mDebugData->activeThread()) {\n      decaf::debug::stepInto(activeThread->coreId);\n   }\n}\n\nvoid\nDebuggerWindow::debugToggleBreakpoint()\n{\n   mDisassemblyWindow->toggleBreakpointUnderCursor();\n}\n\nvoid\nDebuggerWindow::setHleTraceEnabled(bool enabled)\n{\n   auto config = *decaf::config();\n   config.log.hle_trace = enabled;\n   decaf::setConfig(config);\n}\n\nvoid\nDebuggerWindow::setGpuShaderBinaryDumpOnly(bool enabled)\n{\n   auto config = *gpu::config();\n   config.debug.dump_shader_binaries_only = enabled;\n   gpu::setConfig(config);\n}\n\nvoid\nDebuggerWindow::setGpuShaderDumpEnabled(bool enabled)\n{\n   auto config = *gpu::config();\n   config.debug.dump_shaders = enabled;\n   gpu::setConfig(config);\n}\n\nvoid\nDebuggerWindow::setGx2ShaderDumpEnabled(bool enabled)\n{\n   auto config = *decaf::config();\n   config.gx2.dump_shaders = enabled;\n   decaf::setConfig(config);\n}\n\nvoid\nDebuggerWindow::setGx2TextureDumpEnabled(bool enabled)\n{\n   auto config = *decaf::config();\n   config.gx2.dump_textures = enabled;\n   decaf::setConfig(config);\n}\n\nvoid\nDebuggerWindow::setPm4TraceEnabled(bool enabled)\n{\n   if (enabled) {\n      decaf::debug::pm4CaptureBegin();\n      ui->actionPm4CaptureNextFrame->setEnabled(false);\n      ui->actionPm4TraceEnabled->setEnabled(false);\n   } else {\n      decaf::debug::pm4CaptureEnd();\n   }\n}\n\nvoid\nDebuggerWindow::pm4CaptureNextFrame()\n{\n   decaf::debug::pm4CaptureNextFrame();\n   ui->actionPm4CaptureNextFrame->setEnabled(false);\n   ui->actionPm4TraceEnabled->setEnabled(false);\n}\n\nvoid\nDebuggerWindow::pm4CaptureStateChanged(decaf::debug::Pm4CaptureState state)\n{\n   if (state == decaf::debug::Pm4CaptureState::Disabled) {\n      ui->actionPm4CaptureNextFrame->setEnabled(true);\n      ui->actionPm4TraceEnabled->setEnabled(true);\n      ui->actionPm4TraceEnabled->setChecked(false);\n   } else if (state == decaf::debug::Pm4CaptureState::Enabled) {\n      ui->actionPm4CaptureNextFrame->setEnabled(false);\n      ui->actionPm4TraceEnabled->setEnabled(true);\n      ui->actionPm4TraceEnabled->setChecked(true);\n   } else {\n      ui->actionPm4CaptureNextFrame->setEnabled(false);\n      ui->actionPm4TraceEnabled->setEnabled(false);\n      ui->actionPm4TraceEnabled->setChecked(true);\n   }\n}\n\nvoid\nDebuggerWindow::activeThreadChanged()\n{\n   auto activeThread = mDebugData->activeThread();\n   if (!activeThread) {\n      return;\n   }\n\n   if (mDebugData->paused()) {\n      gotoTextAddress(activeThread->nia);\n   }\n}\n\nvoid\nDebuggerWindow::executionStateChanged(bool paused,\n                                      int pauseInitiatorCoreId,\n                                      decaf::debug::VirtualAddress pauseNia)\n{\n   if (!paused) {\n      ui->actionResume->setEnabled(false);\n      ui->actionPause->setEnabled(true);\n      return;\n   }\n\n   ui->actionPause->setEnabled(false);\n\n   auto canResume = !decaf::stopping();\n   ui->actionResume->setEnabled(canResume);\n   ui->actionStepInto->setEnabled(canResume);\n   ui->actionStepOver->setEnabled(canResume);\n\n   // Set active thread to the one that initiated pause - this will lead to\n   // activeThreadChanged being called and thus follow pauseNia implicitly\n   auto &threads = mDebugData->threads();\n   for (auto i = 0u; i < threads.size(); ++i) {\n      if (threads[i].coreId == pauseInitiatorCoreId) {\n         mDebugData->setActiveThreadIndex(i);\n      }\n   }\n}\n\nvoid\nDebuggerWindow::navigateBackward()\n{\n   if (isDockWidgetFocused(mDisassemblyWindow)) {\n      mDisassemblyWindow->navigateBackward();\n   } else if (isDockWidgetFocused(mMemoryWindow)) {\n      mMemoryWindow->navigateBackward();\n   } else if (isDockWidgetFocused(mStackWindow)) {\n      mStackWindow->navigateBackward();\n   }\n}\n\nvoid\nDebuggerWindow::navigateForward()\n{\n   if (isDockWidgetFocused(mDisassemblyWindow)) {\n      mDisassemblyWindow->navigateForward();\n   } else if (isDockWidgetFocused(mMemoryWindow)) {\n      mMemoryWindow->navigateForward();\n   } else if (isDockWidgetFocused(mStackWindow)) {\n      mStackWindow->navigateForward();\n   }\n}\n\nvoid\nDebuggerWindow::navigateAddress()\n{\n   auto address = 0u;\n   while (true) {\n      auto ok = false;\n      auto text = QInputDialog::getText(this, tr(\"Navigate to Address\"),\n                                        tr(\"Address:\"), QLineEdit::Normal, {},\n                                        &ok);\n      if (!ok) {\n         return; // Cancelled\n      }\n\n      address = text.toUInt(&ok, 16);\n      if (!ok) {\n         QMessageBox::warning(this, \"Navigate to address\", \"Invalid address\");\n         continue; // Try again\n      }\n\n      break;\n   }\n\n   if (isDockWidgetFocused(mDisassemblyWindow)) {\n      mDisassemblyWindow->navigateToAddress(address);\n   } else if (isDockWidgetFocused(mMemoryWindow)) {\n      mMemoryWindow->navigateToAddress(address);\n   } else if (isDockWidgetFocused(mStackWindow)) {\n      mStackWindow->navigateToAddress(address);\n   }\n}\n\nvoid\nDebuggerWindow::navigateOperand()\n{\n   if (isDockWidgetFocused(mDisassemblyWindow)) {\n      mDisassemblyWindow->navigateOperand();\n   } else if (isDockWidgetFocused(mStackWindow)) {\n      mStackWindow->navigateOperand();\n   }\n}\n\nvoid\nDebuggerWindow::focusChanged(QWidget *old, QWidget *now)\n{\n   const auto updateActionState =\n      [](QAction *action) {\n         for (auto widget : action->associatedWidgets()) {\n            if (widget->hasFocus()) {\n               action->setEnabled(true);\n               return;\n            }\n         }\n\n         action->setEnabled(false);\n       };\n\n   updateActionState(ui->actionToggleBreakpoint);\n   updateActionState(ui->actionNavigateForward);\n   updateActionState(ui->actionNavigateBackward);\n   updateActionState(ui->actionNavigateToAddress);\n   updateActionState(ui->actionNavigateToOperand);\n}\n\nbool\nDebuggerWindow::isDockWidgetFocused(QWidget *dockWidget)\n{\n   auto widget = QApplication::focusWidget();\n   while (widget) {\n      if (widget == dockWidget) {\n         return true;\n      }\n\n      widget = widget->parentWidget();\n   }\n\n   return false;\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/debuggerwindow.h",
    "content": "#pragma once\n#include \"debuggershortcuts.h\"\n\n#include <QMainWindow>\n#include <DockManager.h>\n\n#include <libdecaf/decaf_debug_api.h>\n\nnamespace Ui\n{\nclass DebuggerWindow;\n}\n\nclass DebugData;\nclass QTimer;\n\nclass BreakpointsWindow;\nclass DisassemblyWindow;\nclass FunctionsWindow;\nclass JitProfilingWindow;\nclass SegmentsWindow;\nclass ThreadsWindow;\nclass VoicesWindow;\nclass RegistersWindow;\nclass MemoryWindow;\nclass StackWindow;\n\nclass DebuggerWindow : public QMainWindow\n{\n   Q_OBJECT\n\npublic:\n   explicit DebuggerWindow(QWidget *parent = 0);\n   ~DebuggerWindow();\n\n   void gotoTextAddress(decaf::debug::VirtualAddress address);\n   void gotoDataAddress(decaf::debug::VirtualAddress address);\n\npublic slots:\n   void updateModel();\n   void onEntry();\n\n   void debugPause();\n   void debugResume();\n   void debugStepOver();\n   void debugStepInto();\n   void debugToggleBreakpoint();\n\n   void setHleTraceEnabled(bool enabled);\n\n   void setGpuShaderBinaryDumpOnly(bool enabled);\n   void setGpuShaderDumpEnabled(bool enabled);\n   void setGx2ShaderDumpEnabled(bool enabled);\n   void setGx2TextureDumpEnabled(bool enabled);\n\n   void setPm4TraceEnabled(bool enabled);\n   void pm4CaptureNextFrame();\n   void pm4CaptureStateChanged(decaf::debug::Pm4CaptureState state);\n\n   void activeThreadChanged();\n   void executionStateChanged(bool paused, int pauseInitiatorCoreId,\n                              decaf::debug::VirtualAddress pauseNia);\n\n   void focusChanged(QWidget *old, QWidget *now);\n\n   void navigateBackward();\n   void navigateForward();\n   void navigateAddress();\n   void navigateOperand();\n\nprivate:\n   bool isDockWidgetFocused(QWidget *widget);\n\nprivate:\n   Ui::DebuggerWindow *ui;\n   ads::CDockManager *mDockManager;\n\n   DebuggerShortcuts mDebuggerShortcuts;\n\n   DebugData *mDebugData;\n   QTimer *mUpdateModelTimer;\n\n   ads::CDockWidget *mBreakpointsDockWidget = nullptr;\n   BreakpointsWindow *mBreakpointsWindow = nullptr;\n\n   ads::CDockWidget *mDisassemblyDockWidget = nullptr;\n   DisassemblyWindow *mDisassemblyWindow = nullptr;\n\n   ads::CDockWidget *mFunctionsDockWidget = nullptr;\n   FunctionsWindow *mFunctionsWindow = nullptr;\n\n   ads::CDockWidget *mJitProfilingDockWidget = nullptr;\n   JitProfilingWindow *mJitProfilingWindow = nullptr;\n\n   ads::CDockWidget *mMemoryDockWidget = nullptr;\n   MemoryWindow *mMemoryWindow = nullptr;\n\n   ads::CDockWidget *mRegistersDockWidget = nullptr;\n   RegistersWindow *mRegistersWindow = nullptr;\n\n   ads::CDockWidget *mSegmentsDockWidget = nullptr;\n   SegmentsWindow *mSegmentsWindow = nullptr;\n\n   ads::CDockWidget *mStackDockWidget = nullptr;\n   StackWindow *mStackWindow = nullptr;\n\n   ads::CDockWidget *mThreadsDockWidget = nullptr;\n   ThreadsWindow *mThreadsWindow = nullptr;\n\n   ads::CDockWidget *mVoicesDockWidget = nullptr;\n   VoicesWindow *mVoicesWindow = nullptr;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/disassemblywidget.cpp",
    "content": "#include \"disassemblywidget.h\"\n\n#include <QtEndian>\n#include <QPainter>\n#include <QScrollBar>\n#include <QTextBlock>\n\n#include <libcpu/espresso/espresso_instructionset.h>\n#include <libdecaf/decaf_debug_api.h>\n\nDisassemblyWidget::DisassemblyWidget(QWidget *parent) :\n   AddressTextDocumentWidget(parent)\n{\n   setBytesPerLine(4);\n   setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);\n   setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);\n\n   mTextFormats.breakpoint = QTextCharFormat { };\n   mTextFormats.breakpoint.setBackground(QColor { Qt::red }.lighter());\n\n   mTextFormats.currentInstruction = QTextCharFormat { };\n   mTextFormats.currentInstruction.setBackground(QColor { Qt::green }.lighter());\n\n   mTextFormats.lineAddress = QTextCharFormat { };\n   mTextFormats.lineAddress.setForeground(Qt::black);\n\n   mTextFormats.instructionData = QTextCharFormat{ };\n   mTextFormats.instructionData.setForeground(Qt::gray);\n\n   mTextFormats.instructionName = QTextCharFormat { };\n   mTextFormats.instructionName.setForeground(Qt::darkBlue);\n\n   mTextFormats.registerName = QTextCharFormat { };\n   mTextFormats.registerName.setForeground(Qt::darkBlue);\n\n   mTextFormats.punctuation = QTextCharFormat { };\n   mTextFormats.punctuation.setForeground(Qt::darkBlue);\n\n   mTextFormats.branchAddress = QTextCharFormat { };\n   mTextFormats.branchAddress.setForeground(Qt::blue);\n\n   mTextFormats.symbolName = QTextCharFormat { };\n   mTextFormats.symbolName.setForeground(Qt::blue);\n\n   mTextFormats.numericValue = QTextCharFormat { };\n   mTextFormats.numericValue.setForeground(Qt::darkGreen);\n\n   mTextFormats.invalid = QTextCharFormat { };\n   mTextFormats.invalid.setForeground(Qt::darkGray);\n\n   mTextFormats.comment = QTextCharFormat{ };\n   mTextFormats.comment.setForeground(Qt::gray);\n\n   mTextFormats.functionOutline = Qt::black;\n\n   mTextFormats.branchDirectionArrow = Qt::darkBlue;\n   mTextFormats.branchOutlineTrue = Qt::darkGreen;\n   mTextFormats.branchOutlineFalse = Qt::darkRed;\n\n   auto arrowSize = characterWidth() - 1;\n   mTextFormats.branchDownArrowPath = QPainterPath { };\n   mTextFormats.branchDownArrowPath.moveTo(0, 0);\n   mTextFormats.branchDownArrowPath.lineTo(arrowSize, 0);\n   mTextFormats.branchDownArrowPath.lineTo(arrowSize / 2.0, arrowSize);\n   mTextFormats.branchDownArrowPath.lineTo(0, 0);\n   mTextFormats.branchDownArrowPath.translate(-arrowSize / 2.0, -arrowSize / 2.0);\n\n   mTextFormats.branchUpArrowPath = QPainterPath { };\n   mTextFormats.branchUpArrowPath.moveTo(0, arrowSize);\n   mTextFormats.branchUpArrowPath.lineTo(arrowSize, arrowSize);\n   mTextFormats.branchUpArrowPath.lineTo(arrowSize / 2.0, 0);\n   mTextFormats.branchUpArrowPath.lineTo(0, arrowSize);\n   mTextFormats.branchUpArrowPath.translate(-arrowSize / 2.0, -arrowSize / 2.0);\n}\n\nvoid\nDisassemblyWidget::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n}\n\nvoid\nDisassemblyWidget::followSymbolUnderCursor()\n{\n   followSymbolAtCursor(getDocumentCursor());\n}\n\nvoid\nDisassemblyWidget::toggleBreakpointUnderCursor()\n{\n   auto address = getDocumentCursor().address;\n   if (decaf::debug::hasBreakpoint(address)) {\n      decaf::debug::removeBreakpoint(address);\n   } else {\n      decaf::debug::addBreakpoint(address);\n   }\n\n   AddressTextDocumentWidget::updateTextDocument(true);\n}\n\nvoid\nDisassemblyWidget::followSymbolAtCursor(DocumentCursor cursor)\n{\n   auto cacheIndex = (cursor.address - mCacheStartAddress) / 4;\n   if (cacheIndex >= mTextCursorPositionCache.size()) {\n      return;\n   }\n\n   auto &cursorPositionCache = mTextCursorPositionCache[cacheIndex];\n   auto &disassemblyCache = mDisassemblyCache[cacheIndex];\n   auto inRange = [](int cursorPosition, std::pair<int, int> range) {\n      return cursorPosition >= range.first && cursorPosition < range.second;\n   };\n\n   for (auto i = 0u; i < cursorPositionCache.instructionArgs.size(); ++i) {\n      auto argCursorPositions = cursorPositionCache.instructionArgs[i];\n      if (!inRange(cursor.cursorPosition, argCursorPositions)) {\n         continue;\n      }\n\n      auto arg = disassemblyCache.disassembly.args[i];\n      if (arg.type == espresso::Disassembly::Argument::Address) {\n         navigateToAddress(arg.address);\n      }\n   }\n\n   if (inRange(cursor.cursorPosition, cursorPositionCache.referencedSymbol)) {\n      navigateToAddress(disassemblyCache.referenceLookup->start);\n   }\n}\n\nvoid\nDisassemblyWidget::paintEvent(QPaintEvent *e)\n{\n   AddressTextDocumentWidget::paintEvent(e);\n\n   auto painter = QPainter { viewport() };\n   painter.translate(QPoint { -horizontalScrollBar()->value(), 0 });\n\n   if (mVisibleColumns.functionOutline) {\n      auto offset = documentMargin();\n      if (mVisibleColumns.lineAddress) {\n         offset += (mTextCursorPositionCache[0].lineAddress.second + 1) * characterWidth();\n      }\n\n      auto functionLineStartY = -1;\n      auto functionLineX1 = offset + (characterWidth() / 2);\n      auto functionLineX2 = functionLineX1 + characterWidth();\n      auto lineY = documentMargin() + lineHeight() / 2;\n\n      painter.setPen(QPen { mTextFormats.functionOutline });\n      for (auto &item : mDisassemblyCache) {\n         if (item.addressLookup.function) {\n            if (item.addressLookup.function->start == item.address &&\n                item.addressLookup.function->end != 0xFFFFFFFF) {\n               // Start of function\n               painter.drawLine(functionLineX1, lineY, functionLineX2, lineY);\n               functionLineStartY = lineY;\n            } else if (item.addressLookup.function->end == item.address + 4) {\n               // End of function\n               painter.drawLine(functionLineX1, functionLineStartY, functionLineX1, lineY);\n               painter.drawLine(functionLineX1, lineY, functionLineX2, lineY);\n               functionLineStartY = -1;\n            } else {\n               // Inside function\n               if (functionLineStartY == -1) {\n                  functionLineStartY = 0;\n               }\n            }\n         }\n\n         lineY += lineHeight();\n      }\n\n      if (functionLineStartY != -1) {\n         painter.drawLine(functionLineX1, functionLineStartY, functionLineX1, viewport()->height());\n      }\n   }\n\n   if (mVisibleColumns.branchOutline) {\n      auto offset = documentMargin();\n      if (mVisibleColumns.instructionData) {\n         offset += (mTextCursorPositionCache[0].instructionData.second + 1) * characterWidth();\n      } else {\n         if (mVisibleColumns.lineAddress) {\n            offset += (mTextCursorPositionCache[0].lineAddress.second + 1) * characterWidth();\n         }\n\n         if (mVisibleColumns.functionOutline) {\n            offset += (mPunctuation.functionOutline.size() + 1) * characterWidth();\n         }\n      }\n\n      auto lineY = documentMargin() + lineHeight() / 2;\n      auto outlineX = offset + characterWidth() / 2;\n      auto arrowLeftX = offset + characterWidth() * 2 + 2;\n      auto cursor = getDocumentCursor();\n      auto ctr = 0u;\n      auto cr = 0u;\n      auto lr = 0u;\n\n      if (auto activeThread = mDebugData->activeThread()) {\n         ctr = activeThread->ctr;\n         cr = activeThread->cr;\n         lr = activeThread->lr;\n      }\n\n      for (auto &item : mDisassemblyCache) {\n         if (item.disassembly.instruction &&\n             espresso::isBranchInstruction(item.disassembly.instruction->id)) {\n            auto instr = qFromBigEndian<uint32_t>(item.data.data());\n            auto info =\n               espresso::disassembleBranchInfo(item.disassembly.instruction->id,\n                                               instr, item.address, ctr, cr, lr);\n            if (!info.isVariable && !info.isCall) {\n               if (info.target > item.address) {\n                  painter.fillPath(\n                     mTextFormats.branchDownArrowPath.translated(arrowLeftX, lineY + 2),\n                     mTextFormats.branchDirectionArrow);\n               } else {\n                  painter.fillPath(\n                     mTextFormats.branchUpArrowPath.translated(arrowLeftX, lineY + 2),\n                     mTextFormats.branchDirectionArrow);\n               }\n\n               if (cursor.address == item.address) {\n                  painter.setPen(QPen {\n                     info.conditionSatisfied ?\n                        mTextFormats.branchOutlineTrue :\n                        mTextFormats.branchOutlineFalse });\n                  painter.drawLine(outlineX, lineY, outlineX + characterWidth(), lineY);\n                  if (info.target < mCacheStartAddress) {\n                     painter.drawLine(outlineX, 0, outlineX, lineY);\n                  } else {\n                     auto index = (info.target - mCacheStartAddress) / 4;\n                     if (index >= mDisassemblyCache.size()) {\n                        painter.drawLine(outlineX, lineY, outlineX, viewport()->height());\n                     } else {\n                        auto targetLineY = documentMargin() + lineHeight() / 2 + lineHeight() * index;\n                        painter.drawLine(outlineX, lineY, outlineX, targetLineY);\n                        painter.drawLine(outlineX, targetLineY, outlineX + characterWidth(), targetLineY);\n                     }\n                  }\n               }\n            }\n         }\n\n         lineY += lineHeight();\n      }\n   }\n}\n\nvoid\nDisassemblyWidget::mouseReleaseEvent(QMouseEvent *e)\n{\n   auto handled = false;\n\n   if ((e->modifiers() & Qt::ControlModifier) && e->button() == Qt::LeftButton) {\n      if (auto hit = mouseEventHitTest(e)) {\n         followSymbolAtCursor({ hit->lineAddress, hit->textCursor.positionInBlock() });\n         handled = true;\n      }\n   }\n\n   if (!handled) {\n      AddressTextDocumentWidget::mouseReleaseEvent(e);\n   }\n}\n\nvoid\nDisassemblyWidget::keyPressEvent(QKeyEvent *e)\n{\n   auto handled = false;\n\n   if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {\n      followSymbolUnderCursor();\n      handled = true;\n   }\n\n   if (!handled) {\n      AddressTextDocumentWidget::keyPressEvent(e);\n   } else {\n      e->accept();\n   }\n}\n\nQVector<QAbstractTextDocumentLayout::Selection>\nDisassemblyWidget::getCustomSelections(QTextDocument *document)\n{\n   auto line = 0;\n   auto activeThread = mDebugData->activeThread();\n   mCustomSelectionsBuffer.clear();\n\n   for (auto &item : mDisassemblyCache) {\n      if (activeThread && activeThread->nia == item.address) {\n         auto selection = QAbstractTextDocumentLayout::Selection { };\n         selection.cursor = QTextCursor { document->findBlockByLineNumber(line) };\n         selection.cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);\n         selection.format = mTextFormats.currentInstruction;\n         mCustomSelectionsBuffer.push_back(selection);\n      } else if (mDebugData->getBreakpoint(item.address)) {\n         auto selection = QAbstractTextDocumentLayout::Selection { };\n         selection.cursor = QTextCursor { document->findBlockByLineNumber(line) };\n         selection.cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);\n         selection.format = mTextFormats.breakpoint;\n         mCustomSelectionsBuffer.push_back(selection);\n      }\n\n      ++line;\n   }\n\n   return mCustomSelectionsBuffer;\n}\n\nvoid\nDisassemblyWidget::updateTextDocument(QTextCursor cursor,\n                                      VirtualAddress firstLineAddress,\n                                      VirtualAddress lastLineAddress,\n                                      int bytesPerLine,\n                                      bool forDisplay)\n{\n   mCacheStartAddress = firstLineAddress;\n   mTextCursorPositionCache.clear();\n   mDisassemblyCache.clear();\n\n   for (auto address = static_cast<int64_t>(firstLineAddress);\n        address <= lastLineAddress; address += bytesPerLine) {\n      auto &cursorPositionCache = mTextCursorPositionCache.emplace_back();\n      auto &item = mDisassemblyCache.emplace_back();\n      item.address = static_cast<VirtualAddress>(address);\n      item.valid = decaf::debug::readMemory(item.address, item.data.data(), bytesPerLine) == bytesPerLine;\n\n      auto breakpoint = mDebugData->getBreakpoint(item.address);\n      if (breakpoint) {\n         item.data[3] = (breakpoint->savedCode >> 0) & 0xFF;\n         item.data[2] = (breakpoint->savedCode >> 8) & 0xFF;\n         item.data[1] = (breakpoint->savedCode >> 16) & 0xFF;\n         item.data[0] = (breakpoint->savedCode >> 24) & 0xFF;\n      }\n\n      if (item.address != firstLineAddress) {\n         cursor.insertBlock();\n      }\n\n      if (mVisibleColumns.lineAddress) {\n         cursorPositionCache.lineAddress.first = cursor.positionInBlock();\n         cursor.insertText(\n            QString { \"%1\" }.arg(item.address, 8, 16, QLatin1Char { '0' }),\n            !item.valid ?\n               mTextFormats.invalid :\n               mTextFormats.lineAddress);\n         cursorPositionCache.lineAddress.second = cursor.positionInBlock();\n         cursor.insertText(mPunctuation.afterLineAddress, mTextFormats.punctuation);\n      }\n\n      if (!item.valid) {\n         continue;\n      }\n\n      if (mVisibleColumns.functionOutline) {\n         cursor.insertText(mPunctuation.functionOutline, mTextFormats.punctuation);\n      }\n\n      if (mVisibleColumns.instructionData) {\n         cursorPositionCache.instructionData.first = cursor.positionInBlock();\n         cursor.insertText(QString { \"%1 %2 %3 %4\" }\n            .arg(item.data[0], 2, 16, QLatin1Char { '0' })\n            .arg(item.data[1], 2, 16, QLatin1Char { '0' })\n            .arg(item.data[2], 2, 16, QLatin1Char { '0' })\n            .arg(item.data[3], 2, 16, QLatin1Char { '0' })\n            .toUpper(), mTextFormats.instructionData);\n         cursorPositionCache.instructionData.second = cursor.positionInBlock();\n         cursor.insertText(mPunctuation.afterInstructionData, mTextFormats.punctuation);\n      }\n\n      auto disassemblyValid = espresso::disassemble(qFromBigEndian<uint32_t>(item.data.data()), item.disassembly, item.address);\n\n      if (mVisibleColumns.branchOutline) {\n         cursor.insertText(mPunctuation.branchOutline, mTextFormats.punctuation);\n      }\n\n      if (mVisibleColumns.instructionName) {\n         cursorPositionCache.instructionName.first = cursor.positionInBlock();\n         if (!disassemblyValid) {\n            cursor.insertText(\"???\", mTextFormats.invalid);\n         } else {\n            cursor.insertText(QString::fromStdString(item.disassembly.name),\n                              mTextFormats.instructionName);\n         }\n         cursorPositionCache.instructionName.second = cursor.positionInBlock();\n\n         cursor.insertText(\n            QString { ' ' }.repeated(\n               std::max(0, mPunctuation.instructionNameWidth -\n                           static_cast<int>(item.disassembly.name.size()))),\n            mTextFormats.punctuation);\n      }\n\n      if (mVisibleColumns.instructionArgs) {\n         auto beforeArgsPosition = cursor.position();\n         auto firstArg = true;\n         for (const auto &arg : item.disassembly.args) {\n            if (!firstArg) {\n               cursor.insertText(QString { \", \" }, mTextFormats.punctuation);\n            }\n            firstArg = false;\n\n            auto &argCursorPosition = cursorPositionCache.instructionArgs.emplace_back();\n            argCursorPosition.first = cursor.positionInBlock();\n            switch (arg.type) {\n            case espresso::Disassembly::Argument::Address:\n            {\n               auto lookup =\n                  decaf::debug::analyseLookupFunction(mDebugData->analyseDatabase(), arg.address);\n               if (lookup && lookup->start == arg.address && !lookup->name.empty()) {\n                  item.referenceLookup = lookup;\n               }\n\n               cursor.insertText(\n                  QString { \"@%1\" }.arg(arg.address, 8, 16, QLatin1Char { '0' }),\n                  mTextFormats.branchAddress);\n               break;\n            }\n            case espresso::Disassembly::Argument::Register:\n               cursor.insertText(\n                  QString::fromStdString(arg.registerName),\n                  mTextFormats.registerName);\n               break;\n            case espresso::Disassembly::Argument::ValueUnsigned:\n               if (arg.valueUnsigned > 9) {\n                  cursor.insertText(\n                     QString { \"0x%1\" }.arg(arg.valueUnsigned, 0, 16),\n                     mTextFormats.numericValue);\n               } else {\n                  cursor.insertText(\n                     QString { \"%1\" }.arg(arg.valueUnsigned),\n                     mTextFormats.numericValue);\n               }\n               break;\n            case espresso::Disassembly::Argument::ValueSigned:\n               if (arg.valueSigned < -9) {\n                  cursor.insertText(\n                     QString { \"-0x%1\" }.arg(-arg.valueSigned, 0, 16),\n                     mTextFormats.numericValue);\n               } else if (arg.valueSigned > 9) {\n                  cursor.insertText(\n                     QString { \"0x%1\" }.arg(arg.valueSigned, 0, 16),\n                     mTextFormats.numericValue);\n               } else {\n                  cursor.insertText(\n                     QString { \"%1\" }.arg(arg.valueSigned),\n                     mTextFormats.numericValue);\n               }\n               break;\n            case espresso::Disassembly::Argument::ConstantUnsigned:\n               cursor.insertText(\n                  QString { \"%1\" }.arg(arg.constantUnsigned),\n                  mTextFormats.numericValue);\n               break;\n            case espresso::Disassembly::Argument::ConstantSigned:\n               cursor.insertText(\n                  QString { \"%1\" }.arg(arg.constantSigned),\n                  mTextFormats.numericValue);\n               break;\n            default:\n               cursor.insertText(\n                  QString { '?' },\n                  mTextFormats.invalid);\n               break;\n            }\n            argCursorPosition.second = cursor.positionInBlock();\n         }\n\n         auto afterArgsPosition = cursor.position();\n         cursor.insertText(\n            QString { ' ' }.repeated(std::max(0, mPunctuation.instructionArgsWidth - (afterArgsPosition - beforeArgsPosition))),\n            mTextFormats.punctuation);\n      }\n\n      if (mVisibleColumns.referencedSymbol && item.referenceLookup) {\n         cursorPositionCache.referencedSymbol.first = cursor.positionInBlock();\n         cursor.insertText(\n            QString { \"@%1\" }.arg(QString::fromStdString(item.referenceLookup->name)),\n            mTextFormats.symbolName);\n         cursorPositionCache.referencedSymbol.second = cursor.positionInBlock();\n         cursor.insertText(mPunctuation.afterReferencedSymbol, mTextFormats.punctuation);\n      }\n\n      item.addressLookup = decaf::debug::analyseLookupAddress(mDebugData->analyseDatabase(), item.address);\n      if (item.addressLookup.function &&\n          item.addressLookup.function->start == item.address &&\n          !item.addressLookup.function->name.empty()) {\n         cursor.setCharFormat(mTextFormats.comment);\n         cursor.insertText(mPunctuation.beforeComment);\n         cursorPositionCache.commentFunctionName.first = cursor.positionInBlock();\n         cursor.insertText(QString::fromStdString(item.addressLookup.function->name));\n         cursorPositionCache.commentFunctionName.second = cursor.positionInBlock();\n      }\n   }\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/disassemblywidget.h",
    "content": "#pragma once\n#include \"addresstextdocumentwidget.h\"\n#include \"debugdata.h\"\n\n#ifndef Q_MOC_RUN\n// moc struggles parsing espresso_disassembler.h on some platforms\n#include <libcpu/espresso/espresso_disassembler.h>\n#endif\n\n#include <array>\n#include <vector>\n#include <QColor>\n#include <QPainterPath>\n#include <QString>\n#include <QTextCharFormat>\n#include <QVector>\n\nclass QTextDocument;\n\nclass DisassemblyWidget : public AddressTextDocumentWidget\n{\n   Q_OBJECT\n\n   using VirtualAddress = DebugData::VirtualAddress;\n\npublic:\n   DisassemblyWidget(QWidget *parent = nullptr);\n\n   void setDebugData(DebugData *debugData);\n\npublic slots:\n   void followSymbolUnderCursor();\n   void toggleBreakpointUnderCursor();\n\nprotected:\n   void followSymbolAtCursor(DocumentCursor cursor);\n\n   void paintEvent(QPaintEvent *e) override;\n   void mouseReleaseEvent(QMouseEvent *e) override;\n   void keyPressEvent(QKeyEvent *e) override;\n\n   QVector<QAbstractTextDocumentLayout::Selection>\n   getCustomSelections(QTextDocument *document) override;\n\n   void updateTextDocument(QTextCursor cursor, VirtualAddress firstLineAddress,\n                           VirtualAddress lastLineAddress,\n                           int bytePerLine, bool forDisplay) override;\n\nprivate:\n   DebugData *mDebugData;\n\n   VirtualAddress mCacheStartAddress;\n   QVector<QAbstractTextDocumentLayout::Selection> mCustomSelectionsBuffer;\n\n   // Cached disassembly information of current visible instructions\n   struct DisassemblyCacheItem\n   {\n      bool valid = false;\n      VirtualAddress address;\n      std::array<uint8_t, 4> data;\n#ifndef Q_MOC_RUN\n      espresso::Disassembly disassembly;\n#endif\n      DebugData::AnalyseDatabase::Lookup addressLookup;\n      const DebugData::AnalyseDatabase::Function *referenceLookup;\n   };\n   std::vector<DisassemblyCacheItem> mDisassemblyCache;\n\n   // Cache of the cursor positions of current visible instructions\n   struct TextCursorPositionCache\n   {\n      std::pair<int, int> lineAddress;\n      std::pair<int, int> instructionData;\n      std::pair<int, int> instructionName;\n      std::vector<std::pair<int, int>> instructionArgs;\n      std::pair<int, int> referencedSymbol;\n      std::pair<int, int> commentFunctionName;\n   };\n   std::vector<TextCursorPositionCache> mTextCursorPositionCache;\n\n   // Visible columns in disassembly\n   struct {\n      bool lineAddress = true;\n      bool functionOutline = true;\n      bool instructionData = true;\n      bool branchOutline = true;\n      bool instructionName = true;\n      bool instructionArgs = true;\n      bool referencedSymbol = true;\n      bool functionName = true;\n   } mVisibleColumns;\n\n   // Punctuation between columns\n   struct {\n      QString afterLineAddress = \"  \";\n      QString functionOutline = \"  \";\n      QString afterInstructionData = \"  \";\n      QString branchOutline = \"  \";\n      int instructionNameWidth = 12;\n      int instructionArgsWidth = 32;\n      QString afterReferencedSymbol = \"  \";\n      QString beforeComment = \"# \";\n   } mPunctuation;\n\n   // Formatting for each data type\n   struct {\n      QTextCharFormat breakpoint;\n      QTextCharFormat currentInstruction;\n      QTextCharFormat lineAddress;\n      QTextCharFormat instructionData;\n      QTextCharFormat instructionName;\n      QTextCharFormat registerName;\n      QTextCharFormat punctuation;\n      QTextCharFormat branchAddress;\n      QTextCharFormat symbolName;\n      QTextCharFormat numericValue;\n      QTextCharFormat invalid;\n      QTextCharFormat comment;\n      QColor functionOutline;\n      QColor branchOutlineTrue;\n      QColor branchOutlineFalse;\n      QColor branchDirectionArrow;\n      QPainterPath branchUpArrowPath;\n      QPainterPath branchDownArrowPath;\n   } mTextFormats;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/disassemblywindow.cpp",
    "content": "#include \"debugdata.h\"\n#include \"debuggershortcuts.h\"\n#include \"disassemblywindow.h\"\n#include \"ui_disassemblywindow.h\"\n\nDisassemblyWindow::DisassemblyWindow(DebuggerShortcuts *debuggerShortcuts,\n                                     QWidget *parent) :\n   QWidget(parent),\n   ui(new Ui::DisassemblyWindow { })\n{\n   ui->setupUi(this);\n\n   ui->disassemblyWidget->addAction(debuggerShortcuts->toggleBreakpoint);\n   ui->disassemblyWidget->addAction(debuggerShortcuts->navigateBackward);\n   ui->disassemblyWidget->addAction(debuggerShortcuts->navigateForward);\n   ui->disassemblyWidget->addAction(debuggerShortcuts->navigateToAddress);\n   ui->disassemblyWidget->addAction(debuggerShortcuts->navigateToOperand);\n}\n\nDisassemblyWindow::~DisassemblyWindow()\n{\n   delete ui;\n}\n\nvoid\nDisassemblyWindow::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n   ui->disassemblyWidget->setDebugData(debugData);\n   ui->disassemblyWidget->setAddressRange(0, 0xFFFFFFFF);\n   ui->disassemblyWidget->navigateToAddress(0x02000000);\n}\n\nvoid\nDisassemblyWindow::navigateToAddress(uint32_t address)\n{\n   ui->disassemblyWidget->navigateToAddress(address);\n}\n\nvoid\nDisassemblyWindow::navigateForward()\n{\n   ui->disassemblyWidget->navigateForward();\n}\n\nvoid\nDisassemblyWindow::navigateBackward()\n{\n   ui->disassemblyWidget->navigateBackward();\n}\n\nvoid\nDisassemblyWindow::navigateOperand()\n{\n   ui->disassemblyWidget->followSymbolUnderCursor();\n}\n\nvoid\nDisassemblyWindow::toggleBreakpointUnderCursor()\n{\n   ui->disassemblyWidget->toggleBreakpointUnderCursor();\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/disassemblywindow.h",
    "content": "#pragma once\n#include <QWidget>\n\nnamespace Ui\n{\nclass DisassemblyWindow;\n}\n\nclass DebugData;\nstruct DebuggerShortcuts;\n\nclass DisassemblyWindow : public QWidget\n{\n   Q_OBJECT\n\npublic:\n   explicit DisassemblyWindow(DebuggerShortcuts *debuggerShortcuts, QWidget *parent = nullptr);\n   ~DisassemblyWindow();\n\n   void setDebugData(DebugData *debugData);\n\npublic slots:\n   void navigateToAddress(uint32_t address);\n   void navigateForward();\n   void navigateBackward();\n   void navigateOperand();\n   void toggleBreakpointUnderCursor();\n\nprivate:\n   Ui::DisassemblyWindow *ui;\n\n   DebugData *mDebugData = nullptr;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/functionsmodel.h",
    "content": "#pragma once\n#include <QAbstractTableModel>\n\n#include \"debugdata.h\"\n\nclass FunctionsModel : public QAbstractTableModel\n{\n   Q_OBJECT\n\n   enum Columns\n   {\n      Name,\n      Start,\n      Length,\n      Segment,\n      NumColumns,\n   };\n\n   using AnalyseDatabase = DebugData::AnalyseDatabase;\n\npublic:\n   enum UserRoles\n   {\n      StartAddressRole = Qt::UserRole,\n   };\n\n   FunctionsModel(QObject *parent = nullptr) :\n      QAbstractTableModel(parent)\n   {\n   }\n\n   void setDebugData(DebugData *debugData)\n   {\n      mDebugData = debugData;\n      connect(mDebugData, &DebugData::dataChanged, this, &FunctionsModel::debugDataChanged);\n   }\n\n   int rowCount(const QModelIndex &parent) const override\n   {\n      if (!mDebugData) {\n         return 0;\n      }\n\n      return static_cast<int>(mDebugData->analyseDatabase().functions.size());\n   }\n\n   int columnCount(const QModelIndex &parent) const override\n   {\n      return NumColumns;\n   }\n\n   QVariant data(const QModelIndex &index, int role) const override\n   {\n      if (!mDebugData || !index.isValid()) {\n         return QVariant{ };\n      }\n\n      const auto &functions = mDebugData->analyseDatabase().functions;\n      if (index.row() >= functions.size() || index.row() < 0) {\n         return QVariant{ };\n      }\n\n      if (role == Qt::DisplayRole) {\n         const auto &func = functions[index.row()];\n\n         switch (index.column()) {\n         case Columns::Name:\n            return QString::fromStdString(func.name);\n         case Columns::Start:\n            return QString { \"%1\" }.arg(func.start, 8, 16, QChar { '0' }).toUpper();\n         case Columns::Length:\n            return QString { \"%1\" }.arg(func.end - func.start, 8, 16, QChar { '0' }).toUpper();\n         case Columns::Segment:\n         {\n            auto segment = mDebugData->segmentForAddress(func.start);\n            if (!segment) {\n               return QString { \"?\" };\n            }\n\n            return QString::fromStdString(segment->name);\n         }\n         }\n      } else if (role == StartAddressRole) {\n         return functions[index.row()].start;\n      }\n\n      return QVariant { };\n   }\n\n   QVariant headerData(int section, Qt::Orientation orientation, int role) const override\n   {\n      if (role != Qt::DisplayRole) {\n         return QVariant{ };\n      }\n\n      if (orientation == Qt::Horizontal) {\n         switch (section) {\n         case Columns::Name:\n            return tr(\"Name\");\n         case Columns::Start:\n            return tr(\"Start\");\n         case Columns::Length:\n            return tr(\"Length\");\n         case Columns::Segment:\n            return tr(\"Segment\");\n         }\n      }\n\n      return QVariant { };\n   }\n\npublic slots:\n   void debugDataChanged()\n   {\n      auto newSize = static_cast<int>(mDebugData->analyseDatabase().functions.size());\n\n      if (newSize < mPreviousSize) {\n         beginRemoveRows({}, newSize, mPreviousSize - 1);\n         endRemoveRows();\n      } else if (newSize > mPreviousSize) {\n         beginInsertRows({}, mPreviousSize, newSize - 1);\n         endInsertRows();\n      }\n\n      mPreviousSize = newSize;\n   }\n\nprivate:\n   DebugData *mDebugData = nullptr;\n   int mPreviousSize = 0;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/functionswindow.cpp",
    "content": "#include \"functionswindow.h\"\n#include \"functionsmodel.h\"\n#include \"debugdata.h\"\n\n#include \"ui_functionswindow.h\"\n\n#include <QSortFilterProxyModel>\n\nFunctionsWindow::FunctionsWindow(QWidget *parent) :\n   QWidget(parent),\n   ui(new Ui::FunctionsWindow { })\n{\n   ui->setupUi(this);\n}\n\nFunctionsWindow::~FunctionsWindow()\n{\n   delete ui;\n}\n\nvoid\nFunctionsWindow::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n   mFunctionsModel = new FunctionsModel { this };\n   mFunctionsModel->setDebugData(mDebugData);\n\n   mSortModel = new QSortFilterProxyModel { this };\n   mSortModel->setSourceModel(mFunctionsModel);\n   mSortModel->setSortCaseSensitivity(Qt::CaseInsensitive);\n   mSortModel->setFilterCaseSensitivity(Qt::CaseInsensitive);\n   mSortModel->setFilterKeyColumn(0);\n\n   ui->tableView->setModel(mSortModel);\n\n   auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1;\n   ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);\n   ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2);\n\n   ui->tableView->setSortingEnabled(true);\n   ui->tableView->update();\n\n   connect(mDebugData, &DebugData::entry, [&]{\n      mFunctionsModel->debugDataChanged();\n      ui->tableView->sortByColumn(1, Qt::AscendingOrder);\n      ui->tableView->resizeColumnToContents(1);\n      ui->tableView->resizeColumnToContents(2);\n      ui->tableView->resizeColumnToContents(3);\n   });\n}\n\nvoid\nFunctionsWindow::filterChanged(QString value)\n{\n   mSortModel->setFilterFixedString(value);\n}\n\nvoid\nFunctionsWindow::functionsViewDoubleClicked(const QModelIndex &index)\n{\n   auto functionIndex = mSortModel->mapToSource(index);\n   auto startAddress = mFunctionsModel->data(functionIndex, FunctionsModel::StartAddressRole);\n   if (!startAddress.isValid()) {\n      return;\n   }\n\n   navigateToTextAddress(startAddress.value<uint32_t>());\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/functionswindow.h",
    "content": "#pragma once\n#include <QWidget>\n\nnamespace Ui\n{\nclass FunctionsWindow;\n}\n\nclass DebugData;\nclass FunctionsModel;\nclass QSortFilterProxyModel;\n\nclass FunctionsWindow : public QWidget\n{\n   Q_OBJECT\n\npublic:\n   explicit FunctionsWindow(QWidget *parent = nullptr);\n   ~FunctionsWindow();\n\n   void setDebugData(DebugData *debugData);\n\nsignals:\n   void navigateToTextAddress(uint32_t address);\n\npublic slots:\n   void filterChanged(QString value);\n   void functionsViewDoubleClicked(const QModelIndex &index);\n\nprivate:\n   Ui::FunctionsWindow *ui;\n\n   DebugData *mDebugData = nullptr;\n   FunctionsModel *mFunctionsModel = nullptr;\n   QSortFilterProxyModel *mSortModel = nullptr;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/jitprofilingmodel.h",
    "content": "#pragma once\n#include <QAbstractTableModel>\n\n#include \"debugdata.h\"\n\nclass JitProfilingModel : public QAbstractTableModel\n{\n   Q_OBJECT\n\n   static constexpr const char *ColumnNames[] = {\n      \"Address\",\n      \"Native Code\",\n      \"Time %\",\n      \"Total Cycles\",\n      \"Call Count\",\n      \"Cycles/Call\",\n   };\n\n   static constexpr int ColumnCount =\n      static_cast<int>(sizeof(ColumnNames) / sizeof(ColumnNames[0]));\n\npublic:\n   enum UserRole\n   {\n      SortRole = Qt::UserRole,\n   };\n\n   JitProfilingModel(QObject *parent = nullptr) :\n      QAbstractTableModel(parent)\n   {\n   }\n\n   void setDebugData(DebugData *debugData)\n   {\n      mDebugData = debugData;\n      connect(mDebugData, &DebugData::dataChanged, this, &JitProfilingModel::debugDataChanged);\n   }\n\n   int rowCount(const QModelIndex &parent) const override\n   {\n      if (!mDebugData) {\n         return 0;\n      }\n\n      return static_cast<int>(mDebugData->jitStats().compiledBlocks.size());\n   }\n\n   int columnCount(const QModelIndex &parent) const override\n   {\n      return ColumnCount;\n   }\n\n   QVariant data(const QModelIndex &index, int role) const override\n   {\n      if (!mDebugData || !index.isValid()) {\n         return QVariant { };\n      }\n\n      const auto &jitStats = mDebugData->jitStats();\n      if (index.row() >= jitStats.compiledBlocks.size() || index.row() < 0) {\n         return QVariant { };\n      }\n\n      const auto totalTime = static_cast<double>(jitStats.totalTimeInCodeBlocks);\n      const auto &stats = jitStats.compiledBlocks[index.row()];\n      if (role == Qt::DisplayRole) {\n         switch (index.column()) {\n         case 0:\n            return QString { \"%1\" }.arg(static_cast<uint>(stats.address), 8, 16, QChar { '0' });\n         case 1:\n            return QString { \"%1\" }.arg((quintptr)stats.code, QT_POINTER_SIZE * 2, 16, QChar{ '0' });\n         case 2:\n            if (totalTime == 0) {\n               return QLatin1String { \"0%\" };\n            } else {\n               return QString{ \"%1%\" }.arg(100.0 * stats.profileData.time.load() / totalTime, 0, 'f', 2);\n            }\n         case 3:\n            return QString { \"%1\" }.arg(stats.profileData.time.load());\n         case 4:\n            return QString { \"%1\" }.arg(stats.profileData.count.load());\n         case 5:\n         {\n            auto count = stats.profileData.count.load();\n            auto time = stats.profileData.time.load();\n            return QString { \"%1\" }.arg(count ? (time + count / 2) / count : 0);\n         }\n         }\n      } else if (role == SortRole) {\n         switch (index.column()) {\n         case 0:\n            return stats.address;\n         case 1:\n            return reinterpret_cast<quintptr>(stats.code);\n         case 2:\n         case 3:\n            return static_cast<qulonglong>(stats.profileData.time.load());\n         case 4:\n            return static_cast<qulonglong>(stats.profileData.count.load());\n         case 5:\n         {\n            auto count = stats.profileData.count.load();\n            auto time = stats.profileData.time.load();\n            return count ? (time + count / 2) / count : 0ull;\n         }\n         }\n      }\n\n      return QVariant { };\n   }\n\n   QVariant headerData(int section, Qt::Orientation orientation, int role) const override\n   {\n      if (role != Qt::DisplayRole) {\n         return QVariant { };\n      }\n\n      if (orientation == Qt::Horizontal) {\n         if (section < ColumnCount) {\n            return ColumnNames[section];\n         }\n      }\n\n      return QVariant { };\n   }\n\nprivate slots:\n   void debugDataChanged()\n   {\n      auto newSize = static_cast<int>(mDebugData->jitStats().compiledBlocks.size());\n\n      if (newSize < mPreviousSize) {\n         beginRemoveRows({}, newSize, mPreviousSize - 1);\n         endRemoveRows();\n      } else if (newSize > mPreviousSize) {\n         beginInsertRows({}, mPreviousSize, newSize - 1);\n         endInsertRows();\n      }\n\n      dataChanged(index(0, 0), index(newSize, ColumnCount));\n      mPreviousSize = newSize;\n   }\n\nprivate:\n   DebugData *mDebugData = nullptr;\n   int mPreviousSize = 0;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/jitprofilingwindow.cpp",
    "content": "#include \"jitprofilingwindow.h\"\n#include \"ui_jitprofilingwindow.h\"\n\n#include \"debugdata.h\"\n#include \"jitprofilingmodel.h\"\n#include <libcpu/jit_stats.h>\n\n#include <QSortFilterProxyModel>\n\nJitProfilingWindow::JitProfilingWindow(QWidget *parent) :\n   QWidget(parent),\n   ui(new Ui::JitProfilingWindow { })\n{\n   ui->setupUi(this);\n}\n\nJitProfilingWindow::~JitProfilingWindow()\n{\n   delete ui;\n}\n\nvoid\nJitProfilingWindow::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n   connect(mDebugData, &DebugData::dataChanged, this, [this]() {\n      const auto &stats = mDebugData->jitStats();\n      ui->labelJitCodeSize->setText(QString{ \"%1 mb\" }.arg(stats.usedCodeCacheSize / 1.0e6, 0, 'f', 2));\n      ui->labelJitDataSize->setText(QString{ \"%1 mb\" }.arg(stats.usedDataCacheSize / 1.0e6, 0, 'f', 2));\n   });\n\n   mJitProfilingModel = new JitProfilingModel { this };\n   mJitProfilingModel->setDebugData(debugData);\n\n   mSortModel = new QSortFilterProxyModel { this };\n   mSortModel->setSourceModel(mJitProfilingModel);\n   mSortModel->setSortRole(JitProfilingModel::SortRole);\n\n   ui->tableView->setModel(mSortModel);\n\n   auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1;\n   ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);\n   ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2);\n\n   ui->tableView->setSortingEnabled(true);\n   ui->tableView->update();\n\n   connect(mDebugData, &DebugData::entry, [&]{\n      ui->tableView->sortByColumn(3, Qt::AscendingOrder);\n      ui->tableView->resizeColumnToContents(0);\n      ui->tableView->resizeColumnToContents(1);\n      ui->tableView->resizeColumnToContents(2);\n      ui->tableView->resizeColumnToContents(3);\n      ui->tableView->resizeColumnToContents(4);\n      ui->tableView->resizeColumnToContents(5);\n   });\n}\n\nvoid\nJitProfilingWindow::clearProfileData()\n{\n   cpu::jit::resetProfileStats();\n}\n\nvoid\nJitProfilingWindow::setProfilingEnabled(bool enabled)\n{\n   if (enabled) {\n      mDebugData->setJitProfilingState(\n         ui->checkBoxCore0->isChecked(),\n         ui->checkBoxCore1->isChecked(),\n         ui->checkBoxCore2->isChecked());\n      ui->pushButtonStartStop->setText(tr(\"Stop\"));\n   } else {\n      mDebugData->setJitProfilingState(false, false, false);\n      ui->pushButtonStartStop->setText(tr(\"Start\"));\n   }\n}\n\nvoid\nJitProfilingWindow::setCore0Mask(bool enabled)\n{\n   if (ui->pushButtonStartStop->isChecked()) {\n      mDebugData->setJitProfilingState(\n         ui->checkBoxCore0->isChecked(),\n         ui->checkBoxCore1->isChecked(),\n         ui->checkBoxCore2->isChecked());\n   }\n}\n\nvoid\nJitProfilingWindow::setCore1Mask(bool enabled)\n{\n   if (ui->pushButtonStartStop->isChecked()) {\n      mDebugData->setJitProfilingState(\n         ui->checkBoxCore0->isChecked(),\n         ui->checkBoxCore1->isChecked(),\n         ui->checkBoxCore2->isChecked());\n   }\n}\n\nvoid\nJitProfilingWindow::setCore2Mask(bool enabled)\n{\n   if (ui->pushButtonStartStop->isChecked()) {\n      mDebugData->setJitProfilingState(\n         ui->checkBoxCore0->isChecked(),\n         ui->checkBoxCore1->isChecked(),\n         ui->checkBoxCore2->isChecked());\n   }\n}\n\nvoid\nJitProfilingWindow::tableViewDoubleClicked(QModelIndex index)\n{\n   auto profileIndex = mSortModel->mapToSource(index);\n   auto startAddress = mJitProfilingModel->data(profileIndex.siblingAtColumn(0), JitProfilingModel::SortRole);\n   if (!startAddress.isValid()) {\n      return;\n   }\n\n   navigateToTextAddress(startAddress.value<uint32_t>());\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/jitprofilingwindow.h",
    "content": "#pragma once\n#include <QWidget>\n\nnamespace Ui\n{\nclass JitProfilingWindow;\n}\n\nclass DebugData;\nclass JitProfilingModel;\nclass QSortFilterProxyModel;\n\nclass JitProfilingWindow : public QWidget\n{\n   Q_OBJECT\n\npublic:\n   JitProfilingWindow(QWidget *parent = nullptr);\n   ~JitProfilingWindow();\n\n   void setDebugData(DebugData *debugData);\n\nsignals:\n   void navigateToTextAddress(uint32_t address);\n\npublic slots:\n   void clearProfileData();\n   void setProfilingEnabled(bool enabled);\n   void setCore0Mask(bool enabled);\n   void setCore1Mask(bool enabled);\n   void setCore2Mask(bool enabled);\n   void tableViewDoubleClicked(QModelIndex index);\n\nprivate:\n   Ui::JitProfilingWindow *ui;\n   DebugData *mDebugData = nullptr;\n   JitProfilingModel *mJitProfilingModel = nullptr;\n   QSortFilterProxyModel *mSortModel = nullptr;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/memorywidget.cpp",
    "content": "#include \"memorywidget.h\"\n\n#include <cctype>\n#include <libdecaf/decaf_debug_api.h>\n\n#include <QAction>\n#include <QApplication>\n#include <QClipboard>\n#include <QMenu>\n\nMemoryWidget::MemoryWidget(QWidget *parent) :\n   AddressTextDocumentWidget(parent)\n{\n   setBytesPerLine(16);\n   setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);\n   setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);\n\n   mTextFormats.lineAddress = QTextCharFormat { };\n   mTextFormats.lineAddress.setForeground(Qt::darkGray);\n\n   mTextFormats.hexData = QTextCharFormat { };\n   mTextFormats.hexData.setForeground(QColor { 0, 0x80, 0x40 });\n\n   mTextFormats.textData = QTextCharFormat { };\n   mTextFormats.textData.setForeground(Qt::blue);\n\n   mTextFormats.punctuation = QTextCharFormat { };\n   mTextFormats.punctuation.setForeground(Qt::darkBlue);\n\n   mCopyAction = new QAction(tr(\"&Copy\"), this);\n   mCopyHexAction = new QAction(tr(\"Copy data as hex\"), this);\n   mCopyTextAction = new QAction(tr(\"Copy data as text\"), this);\n   connect(mCopyAction, &QAction::triggered, this, &MemoryWidget::copySelection);\n   connect(mCopyHexAction, &QAction::triggered, this, &MemoryWidget::copySelectionAsHex);\n   connect(mCopyTextAction, &QAction::triggered, this, &MemoryWidget::copySelectionAsText);\n}\n\nvoid\nMemoryWidget::setBytesPerLine(int bytesPerLine)\n{\n   mAutoBytesPerLine = false;\n   AddressTextDocumentWidget::setBytesPerLine(bytesPerLine);\n}\n\nvoid\nMemoryWidget::setAutoBytesPerLine(bool enabled)\n{\n   mAutoBytesPerLine = enabled;\n   updateAutoBytesPerLine();\n}\n\nbool\nMemoryWidget::autoBytesPerLine()\n{\n   return mAutoBytesPerLine;\n}\n\nvoid\nMemoryWidget::updateAutoBytesPerLine()\n{\n   auto maxWidth = viewport()->width() - documentMargin() * 2;\n   auto charactersPerByte = 0;\n   auto charactersTotalOffset = 0;\n   auto charWidth = characterWidth();\n\n   if (mVisibleColumns.lineAddress) {\n      maxWidth -= 8 * charWidth;\n      maxWidth -= mPunctuation.afterLineAddress.size() * charWidth;\n   }\n\n   if (mVisibleColumns.hexData) {\n      charactersTotalOffset -= 1;\n      charactersPerByte += 3;\n      maxWidth -= mPunctuation.afterHexData.size() * charWidth;\n   }\n\n   if (mVisibleColumns.textData) {\n      charactersPerByte++;\n   }\n\n   auto maxCharacters = maxWidth / characterWidth();\n   auto bytesPerLine = (maxCharacters - charactersTotalOffset) / charactersPerByte;\n   AddressTextDocumentWidget::setBytesPerLine(std::max(4, bytesPerLine));\n}\n\nvoid\nMemoryWidget::resizeEvent(QResizeEvent *e)\n{\n   if (mAutoBytesPerLine) {\n      updateAutoBytesPerLine();\n   }\n\n   AddressTextDocumentWidget::resizeEvent(e);\n}\n\nvoid\nMemoryWidget::copySelectionAsHex()\n{\n   if (mSelectionBegin != mSelectionEnd) {\n      auto startAddress = std::min(mSelectionBegin.address, mSelectionEnd.address);\n      auto endAddress = std::max(mSelectionBegin.address, mSelectionEnd.address);\n      auto count = endAddress - startAddress;\n\n      // Limit copy to 4 kb\n      if (count <= 4 * 1024) {\n         auto buffer = QByteArray { };\n         buffer.resize(count);\n         buffer.resize(static_cast<int>(\n            decaf::debug::readMemory(startAddress, buffer.data(),\n               buffer.size())));\n         qApp->clipboard()->setText(QString { buffer.toHex() });\n      }\n   }\n}\n\nvoid\nMemoryWidget::copySelectionAsText()\n{\n   if (mSelectionBegin != mSelectionEnd) {\n      auto startAddress = std::min(mSelectionBegin.address, mSelectionEnd.address);\n      auto endAddress = std::max(mSelectionBegin.address, mSelectionEnd.address);\n      auto count = endAddress - startAddress;\n\n      // Limit copy to 4 kb\n      if (count <= 4 * 1024) {\n         auto buffer = QByteArray { };\n         buffer.resize(count);\n         buffer.resize(static_cast<int>(\n            decaf::debug::readMemory(startAddress, buffer.data(),\n               buffer.size())));\n\n         auto textString = QString { };\n         for (auto c : buffer) {\n            if (std::isprint(c)) {\n               textString += c;\n            } else {\n               textString += '?';\n            }\n         }\n\n         qApp->clipboard()->setText(textString);\n      }\n   }\n}\n\nvoid\nMemoryWidget::keyPressEvent(QKeyEvent *e)\n{\n   return AddressTextDocumentWidget::keyPressEvent(e);\n}\n\nvoid\nMemoryWidget::showContextMenu(QMouseEvent *e)\n{\n   auto menu = QMenu { this };\n   menu.addAction(mCopyAction);\n   menu.addAction(mCopyHexAction);\n   menu.addAction(mCopyTextAction);\n#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)\n   menu.exec(e->globalPosition().toPoint());\n#else\n   menu.exec(e->globalPos());\n#endif\n}\n\nMemoryWidget::MemoryCursor\nMemoryWidget::convertCursor(const DocumentCursor documentCursor)\n{\n   auto memoryCursor = MemoryCursor { };\n   auto bytesPerLine = getBytesPerLine();\n   auto endAddress = getEndAddress();\n\n   // Convert document cursor into a non-document dependent memory cursor\n   if (mVisibleColumns.hexData) {\n      if (documentCursor.cursorPosition < mHexDataStartPosition) {\n         memoryCursor.address = documentCursor.address;\n         memoryCursor.type = MemoryCursor::Hex;\n         memoryCursor.nibble = 0;\n      } else if (documentCursor.cursorPosition >= mHexDataStartPosition &&\n                 documentCursor.cursorPosition < mHexDataEndPosition) {\n         auto byte = (documentCursor.cursorPosition - mHexDataStartPosition) / 3;\n         if (byte >= bytesPerLine) {\n            byte = bytesPerLine - 1;\n            memoryCursor.nibble = 2;\n         } else {\n            memoryCursor.nibble = (documentCursor.cursorPosition - mHexDataStartPosition) % 3;\n         }\n\n         memoryCursor.address = documentCursor.address + byte;\n         memoryCursor.type = MemoryCursor::Hex;\n      } else if (documentCursor.cursorPosition >= mHexDataEndPosition &&\n                 documentCursor.cursorPosition < mTextDataStartPosition) {\n         memoryCursor.address = documentCursor.address + (bytesPerLine - 1);\n         memoryCursor.type = MemoryCursor::Hex;\n         memoryCursor.nibble = 2;\n      }\n   }\n\n   if (mVisibleColumns.textData) {\n      if (documentCursor.cursorPosition >= mTextDataStartPosition) {\n         auto byte = documentCursor.cursorPosition - mTextDataStartPosition;\n         if (byte >= bytesPerLine) {\n            byte = bytesPerLine - 1;\n            memoryCursor.nibble = 2;\n         }\n\n         if (static_cast<int64_t>(endAddress) - static_cast<int64_t>(documentCursor.address) < byte) {\n            memoryCursor.address = endAddress;\n            memoryCursor.nibble = 2;\n         } else {\n            memoryCursor.address = documentCursor.address + byte;\n         }\n\n         memoryCursor.type = MemoryCursor::Text;\n      }\n   }\n\n   return memoryCursor;\n}\n\nMemoryWidget::DocumentCursor\nMemoryWidget::convertCursor(const MemoryWidget::MemoryCursor memoryCursor)\n{\n   auto documentCursor = DocumentCursor { };\n   auto bytesPerLine = getBytesPerLine();\n   auto startAddress = getStartAddress();\n   auto lineStartAddress = startAddress + ((memoryCursor.address - startAddress) / bytesPerLine) * bytesPerLine;\n   auto lineByte = memoryCursor.address - lineStartAddress;\n   documentCursor.address = lineStartAddress;\n\n   if (memoryCursor.type == MemoryCursor::Hex) {\n      documentCursor.cursorPosition = mHexDataStartPosition + 3 * lineByte + memoryCursor.nibble;\n   } else if (memoryCursor.type == MemoryCursor::Text) {\n      documentCursor.cursorPosition = mTextDataStartPosition + lineByte;\n      if (memoryCursor.nibble == 2) {\n         documentCursor.cursorPosition++;\n      }\n   }\n\n   return documentCursor;\n}\n\nMemoryWidget::MemoryCursor\nMemoryWidget::moveMemoryCursor(MemoryCursor memoryCursor, int byteOffset)\n{\n   auto startAddress = getStartAddress();\n   auto endAddress = getEndAddress();\n\n   if (byteOffset < 0 && mCursor.address < startAddress - byteOffset) {\n      memoryCursor.address = startAddress;\n      memoryCursor.nibble = 0;\n   } else if (byteOffset > 0 && mCursor.address > endAddress - byteOffset) {\n      memoryCursor.address = endAddress;\n      memoryCursor.nibble = 2;\n   } else {\n      memoryCursor.address += byteOffset;\n   }\n\n   return memoryCursor;\n}\n\nMemoryWidget::DocumentCursor\nMemoryWidget::cursorFromAddress(VirtualAddress address)\n{\n   auto memoryCursor = MemoryCursor { };\n   if (mVisibleColumns.hexData) {\n      memoryCursor.type = MemoryCursor::Hex;\n   } else if (mVisibleColumns.textData) {\n      memoryCursor.type = MemoryCursor::Text;\n   }\n\n   memoryCursor.address = address;\n   return convertCursor(memoryCursor);\n}\n\nMemoryWidget::VirtualAddress\nMemoryWidget::cursorToAddress(DocumentCursor cursor)\n{\n   return convertCursor(cursor).address;\n}\n\nMemoryWidget::DocumentCursor\nMemoryWidget::getDocumentCursor()\n{\n   return convertCursor(mCursor);\n}\n\nMemoryWidget::DocumentCursor\nMemoryWidget::getDocumentSelectionBegin()\n{\n   return convertCursor(mSelectionBegin);\n}\n\nMemoryWidget::DocumentCursor\nMemoryWidget::getDocumentSelectionEnd()\n{\n   return convertCursor(mSelectionEnd);\n}\n\nvoid\nMemoryWidget::setDocumentCursor(DocumentCursor cursor)\n{\n   mCursor = convertCursor(cursor);\n}\n\nvoid\nMemoryWidget::setDocumentSelectionBegin(DocumentCursor cursor)\n{\n   mSelectionBegin = convertCursor(cursor);\n}\n\nvoid\nMemoryWidget::setDocumentSelectionEnd(DocumentCursor cursor)\n{\n   mSelectionEnd = convertCursor(cursor);\n}\n\nstatic constexpr inline QChar\ntoHexUpper(uint value)\n{\n   return \"0123456789ABCDEF\"[value & 0xF];\n}\n\nvoid\nMemoryWidget::updateTextDocument(QTextCursor cursor,\n                                  VirtualAddress firstLineAddress,\n                                  VirtualAddress lastLineAddress,\n                                  int bytesPerLine,\n                                  bool forDisplay)\n{\n   // TODO: Cache line cursor position for Address, Hex, Text\n   auto buffer = std::vector<uint8_t> { };\n   buffer.resize(bytesPerLine, 0);\n\n   auto hexString = QString { };\n   hexString.resize(bytesPerLine * 3 - 1, ' ');\n\n   auto textString = QString { };\n   textString.resize(bytesPerLine, '.');\n\n   auto pageSize = decaf::debug::getMemoryPageSize();\n   auto pageMask = ~(pageSize - 1);\n\n   auto beginAddress = static_cast<int64_t>(firstLineAddress) ;\n   auto endAddress = static_cast<int64_t>(lastLineAddress) + bytesPerLine;\n   auto address = beginAddress;\n\n   auto currentPage = beginAddress & pageMask;\n   auto nextPage = currentPage + pageSize;\n   auto currentPageValid = decaf::debug::isValidVirtualAddress(static_cast<VirtualAddress>(currentPage));\n   auto nextPageValid = decaf::debug::isValidVirtualAddress(static_cast<VirtualAddress>(nextPage));\n\n   // This code assumes you could only ever have 2 pages on a single line\n   assert(pageSize > bytesPerLine);\n\n   for (auto address = beginAddress; address < endAddress; address += bytesPerLine) {\n      auto firstValidByte = -1;\n      auto lastValidByte = -1;\n      auto lineCrossesPage = (nextPage - address) < bytesPerLine;\n\n      if (address != beginAddress) {\n         cursor.insertBlock();\n      }\n\n      if (mVisibleColumns.lineAddress) {\n         mAddressStartPosition = cursor.positionInBlock();\n         cursor.insertText(\n            QString { \"%1\" }.arg(address, 8, 16, QLatin1Char { '0' }),\n            mTextFormats.lineAddress);\n         mAddressEndPosition = cursor.positionInBlock();\n         cursor.insertText(mPunctuation.afterLineAddress, mTextFormats.punctuation);\n      }\n\n      if (currentPageValid) {\n         // Current page is valid, so first byte is valid\n         firstValidByte = 0;\n      } else if (lineCrossesPage && nextPageValid) {\n         // Current page invalid, next page valid\n         firstValidByte = static_cast<int>(nextPage - address);\n      }\n\n      if (!lineCrossesPage) {\n         if (currentPageValid) {\n            // Whole line is on current valid page\n            lastValidByte = bytesPerLine;\n         }\n      } else {\n         if (nextPageValid) {\n            // Next page is valid so assume we can finish the current line\n            lastValidByte = bytesPerLine;\n         } else if (currentPageValid) {\n            // Current page is valid, next page is invalid\n            lastValidByte = std::min(static_cast<int>(nextPage - address), bytesPerLine);\n         }\n      }\n\n      if (lineCrossesPage) {\n         currentPage = nextPage;\n         currentPageValid = nextPageValid;\n\n         nextPage = currentPage + pageSize;\n         nextPageValid = decaf::debug::isValidVirtualAddress(static_cast<VirtualAddress>(nextPage));\n      }\n\n      if (firstValidByte < 0 || lastValidByte < 0) {\n         // Whole line is invalid\n         hexString.fill(' ');\n         textString.fill(' ');\n      } else {\n         decaf::debug::readMemory(address + firstValidByte, buffer.data() + firstValidByte, lastValidByte - firstValidByte);\n\n         for (auto i = 0; i < firstValidByte; ++i) {\n            hexString[i * 3 + 0] = ' ';\n            hexString[i * 3 + 1] = ' ';\n            textString[i] = ' ';\n         }\n\n         for (auto i = firstValidByte; i < lastValidByte; ++i) {\n            hexString[i * 3 + 0] = toHexUpper(buffer[i] >> 4);\n            hexString[i * 3 + 1] = toHexUpper(buffer[i] & 0xf);\n\n            if (std::isprint(buffer[i])) {\n               textString[i] = QChar { buffer[i] };\n            } else {\n               textString[i] = '.';\n            }\n         }\n\n         for (auto i = lastValidByte; i < bytesPerLine; ++i) {\n            hexString[i * 3 + 0] = ' ';\n            hexString[i * 3 + 1] = ' ';\n            textString[i] = ' ';\n         }\n      }\n\n      if (mVisibleColumns.hexData) {\n         mHexDataStartPosition = cursor.positionInBlock();\n         cursor.insertText(hexString, mTextFormats.hexData);\n         mHexDataEndPosition = cursor.positionInBlock();\n         cursor.insertText(mPunctuation.afterLineAddress, mTextFormats.punctuation);\n      }\n\n      if (mVisibleColumns.textData) {\n         mTextDataStartPosition = cursor.positionInBlock();\n         cursor.insertText(textString, mTextFormats.textData);\n         mTextDataEndPosition = cursor.positionInBlock();\n      }\n   }\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/memorywidget.h",
    "content": "#pragma once\n#include \"addresstextdocumentwidget.h\"\n#include <QAbstractScrollArea>\n#include <QAbstractTextDocumentLayout>\n#include <QTextCursor>\n#include <QTextDocument>\n\n#include <cstddef>\n#include <vector>\n#include <optional>\n\nclass QAction;\nclass QTextDocument;\n\nclass MemoryWidget : public AddressTextDocumentWidget\n{\n   using VirtualAddress = uint32_t;\n\n   struct MemoryCursor\n   {\n      enum Type\n      {\n         Invalid,\n         Hex,\n         Text,\n      };\n\n      Type type = Invalid;\n      VirtualAddress address = 0;\n      int nibble = 0;\n\n      bool operator !=(const MemoryCursor &rhs) const\n      {\n         return type != rhs.type || address != rhs.address || nibble != rhs.nibble;\n      }\n\n      bool operator <(const MemoryCursor &rhs) const\n      {\n         if (address > rhs.address) {\n            return false;\n         } else if (address < rhs.address) {\n            return true;\n         } else {\n            return nibble < rhs.nibble;\n         }\n      }\n   };\n\npublic:\n   MemoryWidget(QWidget *parent = nullptr);\n\n   void setBytesPerLine(int bytesPerLine);\n\n   void setAutoBytesPerLine(bool enabled);\n   bool autoBytesPerLine();\n\n   void copySelectionAsHex();\n   void copySelectionAsText();\n\nprivate:\n   void updateAutoBytesPerLine();\n\n   void resizeEvent(QResizeEvent *e) override;\n   void keyPressEvent(QKeyEvent *e) override;\n\n   MemoryCursor convertCursor(const DocumentCursor documentCursor);\n   DocumentCursor convertCursor(const MemoryCursor memoryCursor);\n   MemoryCursor moveMemoryCursor(MemoryCursor memoryCursor, int byteOffset);\n\n   DocumentCursor cursorFromAddress(VirtualAddress address) override;\n   VirtualAddress cursorToAddress(DocumentCursor cursor) override;\n   DocumentCursor getDocumentCursor() override;\n   DocumentCursor getDocumentSelectionBegin() override;\n   DocumentCursor getDocumentSelectionEnd() override;\n   void setDocumentCursor(DocumentCursor cursor) override;\n   void setDocumentSelectionBegin(DocumentCursor cursor) override;\n   void setDocumentSelectionEnd(DocumentCursor cursor) override;\n\n   void showContextMenu(QMouseEvent *e) override;\n\n   void updateTextDocument(QTextCursor cursor, VirtualAddress startAddress,\n                           VirtualAddress endAddress, int bytesPerLine,\n                           bool forDisplay) override;\n\nprivate:\n   bool mAutoBytesPerLine = false;\n\n   MemoryCursor mCursor = { };\n   MemoryCursor mSelectionBegin = { };\n   MemoryCursor mSelectionEnd = { };\n\n   int mAddressStartPosition = 0;\n   int mAddressEndPosition = 0;\n\n   int mHexDataStartPosition = 0;\n   int mHexDataEndPosition = 0;\n\n   int mTextDataStartPosition = 0;\n   int mTextDataEndPosition = 0;\n\n   struct {\n      QTextCharFormat lineAddress;\n      QTextCharFormat punctuation;\n      QTextCharFormat hexData;\n      QTextCharFormat textData;\n   } mTextFormats;\n\n   struct {\n      QString afterLineAddress = \"  \";\n      QString afterHexData = \"  \";\n   } mPunctuation;\n\n   struct {\n      bool lineAddress = true;\n      bool hexData = true;\n      bool textData = true;\n   } mVisibleColumns;\n\n   QAction *mCopyAction;\n   QAction *mCopyHexAction;\n   QAction *mCopyTextAction;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/memorywindow.cpp",
    "content": "#include \"debuggershortcuts.h\"\n#include \"memorywindow.h\"\n#include \"memorywidget.h\"\n\n#include \"ui_memorywindow.h\"\n\nMemoryWindow::MemoryWindow(DebuggerShortcuts *debuggerShortcuts,\n                           QWidget *parent) :\n   QWidget(parent),\n   ui(new Ui::MemoryWindow { })\n{\n   ui->setupUi(this);\n   ui->widget->setAddressRange(0, 0xFFFFFFFF);\n   ui->widget->navigateToAddress(0x02000000);\n   ui->widget->setBytesPerLine(16);\n\n   ui->widget->addAction(debuggerShortcuts->navigateBackward);\n   ui->widget->addAction(debuggerShortcuts->navigateForward);\n   ui->widget->addAction(debuggerShortcuts->navigateToAddress);\n}\n\nMemoryWindow::~MemoryWindow()\n{\n   delete ui;\n}\n\nvoid\nMemoryWindow::navigateToAddress(uint32_t address)\n{\n   ui->widget->navigateToAddress(address);\n}\n\nvoid\nMemoryWindow::navigateForward()\n{\n   ui->widget->navigateForward();\n}\n\nvoid\nMemoryWindow::navigateBackward()\n{\n   ui->widget->navigateBackward();\n}\n\nvoid\nMemoryWindow::addressChanged()\n{\n   auto text = ui->lineEditAddress->text();\n   auto value = 0ull;\n   auto valid = false;\n\n   if (text.startsWith(\"0x\")) {\n      value = text.toULongLong(&valid, 0);\n   } else {\n      value = text.toULongLong(&valid, 16);\n   }\n\n   if (valid) {\n      ui->widget->navigateToAddress(value);\n      ui->widget->setFocus();\n   }\n}\n\nvoid\nMemoryWindow::columnsChanged()\n{\n   if (ui->comboBoxColumns->currentIndex() == 0) {\n      ui->widget->setAutoBytesPerLine(true);\n   } else {\n      auto valid = false;\n      auto value = ui->comboBoxColumns->currentText().toInt(&valid, 0);\n      if (valid) {\n         ui->widget->setBytesPerLine(value);\n      }\n   }\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/memorywindow.h",
    "content": "#pragma once\n#include <QWidget>\n\nnamespace Ui\n{\nclass MemoryWindow;\n}\n\nstruct DebuggerShortcuts;\n\nclass MemoryWindow : public QWidget\n{\n   Q_OBJECT\n\npublic:\n   MemoryWindow(DebuggerShortcuts *debuggerShortcuts, QWidget *parent = nullptr);\n   ~MemoryWindow();\n\npublic slots:\n   void navigateToAddress(uint32_t address);\n   void navigateForward();\n   void navigateBackward();\n\nprotected slots:\n   void addressChanged();\n   void columnsChanged();\n\nprivate:\n   Ui::MemoryWindow *ui;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/registerswindow.cpp",
    "content": "#include \"registerswindow.h\"\n\n#include <QFontDatabase>\n#include <QTextBlock>\n#include <QTextDocument>\n#include <QVBoxLayout>\n#include <QScrollBar>\n\nRegistersWindow::RegistersWindow(QWidget *parent) :\n   QPlainTextEdit(parent)\n{\n   // Set to fixed width font\n   auto font = QFontDatabase::systemFont(QFontDatabase::FixedFont);\n   if (!(font.styleHint() & QFont::Monospace)) {\n      font.setFamily(\"Monospace\");\n      font.setStyleHint(QFont::TypeWriter);\n   }\n   setFont(font);\n   setReadOnly(true);\n   setWordWrapMode(QTextOption::NoWrap);\n\n   mTextFormats.registerName = QTextCharFormat { };\n   mTextFormats.registerName.setForeground(Qt::darkBlue);\n\n   mTextFormats.punctuation = QTextCharFormat { };\n   mTextFormats.punctuation.setForeground(Qt::darkBlue);\n\n   mTextFormats.changedValue = QTextCharFormat { };\n   mTextFormats.changedValue.setForeground(Qt::red);\n\n   generateDocument();\n   verticalScrollBar()->triggerAction(QScrollBar::SliderToMinimum);\n   horizontalScrollBar()->triggerAction(QScrollBar::SliderToMinimum);\n}\n\nvoid\nRegistersWindow::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n   connect(mDebugData, &DebugData::dataChanged, this, &RegistersWindow::debugDataChanged);\n}\n\ntemplate<typename T>\nstatic QString toHexString(T value, int width)\n{\n   return QString { \"%1\" }.arg(value, width / 4, 16, QLatin1Char { '0' }).toUpper();\n}\n\nvoid\nRegistersWindow::generateDocument()\n{\n   auto cursor = QTextCursor { document() };\n   cursor.beginEditBlock();\n   document()->clear();\n\n   auto registerNameWidth = 10;\n   for (auto i = 0; i < 32; ++i) {\n      if (i != 0) {\n         cursor.insertBlock();\n      }\n\n      cursor.insertText(QString { \"R%1\" }.arg(i, 2, 10, QLatin1Char { '0' }), mTextFormats.registerName);\n      cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock()));\n\n      mRegisterCursors.gpr[i] = { cursor.block(), cursor.positionInBlock() };\n      cursor.insertText(toHexString(0, 32), mTextFormats.value);\n      mRegisterCursors.gpr[i].end = cursor.positionInBlock();\n   }\n\n   cursor.insertBlock();\n\n   cursor.insertBlock();\n   cursor.insertText(QString { \"LR\" }, mTextFormats.registerName);\n   cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock()));\n   mRegisterCursors.lr = { cursor.block(), cursor.positionInBlock() };\n   cursor.insertText(toHexString(0, 32), mTextFormats.value);\n   mRegisterCursors.lr.end = cursor.positionInBlock();\n\n   cursor.insertBlock();\n   cursor.insertText(QString { \"CTR\" }, mTextFormats.registerName);\n   cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock()));\n   mRegisterCursors.ctr = { cursor.block(), cursor.positionInBlock() };\n   cursor.insertText(toHexString(0, 32), mTextFormats.value);\n   mRegisterCursors.ctr.end = cursor.positionInBlock();\n\n   cursor.insertBlock();\n   cursor.insertText(QString { \"XER\" }, mTextFormats.registerName);\n   cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock()));\n   mRegisterCursors.xer = { cursor.block(), cursor.positionInBlock() };\n   cursor.insertText(toHexString(0, 32), mTextFormats.value);\n   mRegisterCursors.xer.end = cursor.positionInBlock();\n\n   cursor.insertBlock();\n   cursor.insertText(QString { \"MSR\" }, mTextFormats.registerName);\n   cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock()));\n   mRegisterCursors.msr = { cursor.block(), cursor.positionInBlock() };\n   cursor.insertText(toHexString(0, 32), mTextFormats.value);\n   mRegisterCursors.msr.end = cursor.positionInBlock();\n\n   cursor.insertBlock();\n\n   cursor.insertBlock();\n   cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock()));\n   cursor.insertText(QString { \"O Z + -\" });\n\n   for (auto i = 0; i < 8; ++i) {\n      cursor.insertBlock();\n      cursor.insertText(QString { \"CRF%1\" }.arg(i), mTextFormats.registerName);\n      cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock()));\n      mRegisterCursors.crf[i] = { cursor.block(), cursor.positionInBlock() };\n      cursor.insertText(\"0 0 0 0\", mTextFormats.value);\n      mRegisterCursors.crf[i].end = cursor.positionInBlock();\n   }\n\n   cursor.insertBlock();\n\n   for (auto i = 0; i < 32; ++i) {\n      cursor.insertBlock();\n      cursor.insertText(QString { \"F%1\" }.arg(i, 2, 10, QLatin1Char { '0' }), mTextFormats.registerName);\n      cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock()));\n\n      mRegisterCursors.fpr[i] = { cursor.block(), cursor.positionInBlock() };\n      cursor.insertText(toHexString(0, 64) + QString { \" 0\" }, mTextFormats.value);\n      mRegisterCursors.fpr[i].end = cursor.positionInBlock();\n   }\n\n   for (auto i = 0; i < 32; ++i) {\n      cursor.insertBlock();\n      cursor.insertText(QString { \"P%1\" }.arg(i, 2, 10, QLatin1Char { '0' }), mTextFormats.registerName);\n      cursor.insertText(QString { ' ' }.repeated(registerNameWidth - cursor.positionInBlock()));\n\n      mRegisterCursors.psf[i] = { cursor.block(), cursor.positionInBlock() };\n      cursor.insertText(toHexString(0, 64) + QString { \" 0\" }, mTextFormats.value);\n      mRegisterCursors.psf[i].end = cursor.positionInBlock();\n   }\n\n   cursor.endEditBlock();\n}\n\nvoid\nRegistersWindow::debugDataChanged()\n{\n   auto thread = mDebugData->activeThread();\n   if (!thread) {\n      return;\n   }\n\n   auto clearChangedHighlight = !mDebugData->paused() || mLastThreadState.nia != thread->nia;\n   auto cursor = QTextCursor { document() };\n   cursor.beginEditBlock();\n\n   auto updateRegisterText =\n      [&](RegisterCursor &reg, auto lastValue, auto currentValue, auto toString) {\n         cursor.setPosition(reg.block.position() + reg.start);\n         cursor.setPosition(reg.block.position() + reg.end, QTextCursor::KeepAnchor);\n\n         auto wasChangedValue = cursor.charFormat().foreground().color() == mTextFormats.changedValue.foreground().color();\n\n         if (lastValue != currentValue) {\n            cursor.insertText(toString(currentValue), mTextFormats.changedValue);\n            reg.end = cursor.selectionEnd() - reg.block.position();\n         } else if (clearChangedHighlight) {\n            if (cursor.charFormat().foreground().color() == mTextFormats.changedValue.foreground().color()) {\n               cursor.setCharFormat(mTextFormats.value);\n            }\n         }\n      };\n\n   for (auto i = 0u; i < 32; ++i) {\n      updateRegisterText(mRegisterCursors.gpr[i], mLastThreadState.gpr[i], thread->gpr[i],\n                         [](auto value) { return toHexString(value, 32); });\n   }\n\n   updateRegisterText(mRegisterCursors.lr, mLastThreadState.lr, thread->lr,\n                      [](auto value) { return toHexString(value, 32); });\n   updateRegisterText(mRegisterCursors.ctr, mLastThreadState.ctr, thread->ctr,\n                      [](auto value) { return toHexString(value, 32); });\n   updateRegisterText(mRegisterCursors.xer, mLastThreadState.xer, thread->xer,\n                      [](auto value) { return toHexString(value, 32); });\n   updateRegisterText(mRegisterCursors.msr, mLastThreadState.msr, thread->msr,\n                      [](auto value) { return toHexString(value, 32); });\n\n   for (auto i = 0; i < 8; ++i) {\n      auto lastValue = (mLastThreadState.cr >> ((7 - i) * 4)) & 0xF;\n      auto value = (thread->cr >> ((7 - i) * 4)) & 0xF;\n      updateRegisterText(mRegisterCursors.crf[i], lastValue, value,\n         [](auto value) {\n            return QString { \"%1 %2 %3 %4\" }\n               .arg((value >> 0) & 1)\n               .arg((value >> 1) & 1)\n               .arg((value >> 2) & 1)\n               .arg((value >> 3) & 1);\n         });\n   }\n\n   for (auto i = 0u; i < 32; ++i) {\n      updateRegisterText(mRegisterCursors.fpr[i], mLastThreadState.fpr[i], thread->fpr[i],\n         [](auto value) {\n            return QString { \"%1 %2\" }\n               .arg(toHexString(*reinterpret_cast<uint64_t *>(&value), 64).toUpper())\n               .arg(value);\n         });\n   }\n\n   for (auto i = 0u; i < 32; ++i) {\n      updateRegisterText(mRegisterCursors.psf[i], mLastThreadState.ps1[i], thread->ps1[i],\n         [](auto value) {\n            return QString { \"%1 %2\" }\n               .arg(toHexString(*reinterpret_cast<uint64_t *>(&value), 64).toUpper())\n               .arg(value);\n         });\n   }\n\n   cursor.endEditBlock();\n   mLastThreadState = *thread;\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/registerswindow.h",
    "content": "#pragma once\n#include <array>\n#include <cstdint>\n#include <QWidget>\n#include <QPlainTextEdit>\n#include <QTextBlock>\n\n#include \"debugdata.h\"\n\nclass RegistersWindow : public QPlainTextEdit\n{\n   Q_OBJECT\n\n   struct RegisterCursor\n   {\n      QTextBlock block;\n      int start;\n      int end;\n   };\n\npublic:\n   RegistersWindow(QWidget *parent = nullptr);\n\n   void setDebugData(DebugData *debugData);\n\nprivate slots:\n   void debugDataChanged();\n\nprivate:\n   void generateDocument();\n\nprivate:\n   DebugData *mDebugData = nullptr;\n\n   decaf::debug::CafeThread mLastThreadState = { };\n\n   struct RegisterCursors\n   {\n      std::array<RegisterCursor, 32> gpr;\n      RegisterCursor lr;\n      RegisterCursor ctr;\n      RegisterCursor xer;\n      RegisterCursor msr;\n      std::array<RegisterCursor, 8> crf;\n      std::array<RegisterCursor, 32> fpr;\n      std::array<RegisterCursor, 32> psf;\n   } mRegisterCursors;\n\n   struct {\n      QTextCharFormat registerName;\n      QTextCharFormat punctuation;\n      QTextCharFormat value;\n      QTextCharFormat changedValue;\n   } mTextFormats;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/segmentsmodel.h",
    "content": "#pragma once\n#include <QAbstractTableModel>\n#include <vector>\n\n#include \"debugdata.h\"\n\nclass SegmentsModel : public QAbstractTableModel\n{\n   Q_OBJECT\n\n   static constexpr const char *ColumnNames[] = {\n      \"Name\",\n      \"Start\",\n      \"End\",\n      \"R\",\n      \"W\",\n      \"X\",\n      \"Align\",\n   };\n\n   static constexpr int ColumnCount =\n      static_cast<int>(sizeof(ColumnNames) / sizeof(ColumnNames[0]));\n\n   using CafeMemorySegment = DebugData::CafeMemorySegment;\n\npublic:\n   enum UserRoles\n   {\n      AddressRole = Qt::UserRole,\n      ExecuteRole,\n   };\n\n   SegmentsModel(QObject *parent = nullptr) :\n      QAbstractTableModel(parent)\n   {\n   }\n\n   void setDebugData(DebugData *debugData)\n   {\n      mDebugData = debugData;\n      connect(mDebugData, &DebugData::dataChanged, this, &SegmentsModel::debugDataChanged);\n   }\n\n   int rowCount(const QModelIndex &parent) const override\n   {\n      if (!mDebugData) {\n         return 0;\n      }\n\n      return static_cast<int>(mDebugData->segments().size());\n   }\n\n   int columnCount(const QModelIndex &parent) const override\n   {\n      return ColumnCount;\n   }\n\n   QVariant data(const QModelIndex &index, int role) const override\n   {\n      if (!mDebugData || !index.isValid()) {\n         return QVariant{ };\n      }\n\n      const auto &segments = mDebugData->segments();\n      if (index.row() >= segments.size() || index.row() < 0) {\n         return QVariant { };\n      }\n\n      if (role == Qt::DisplayRole) {\n         const auto &segment = segments[index.row()];\n\n         switch (index.column()) {\n         case 0:\n            return QString::fromStdString(segment.name);\n         case 1:\n            return QString(\"%1\").arg(static_cast<uint>(segment.address), 8, 16, QChar { '0' });\n         case 2:\n            return QString(\"%1\").arg(static_cast<uint>(segment.address + segment.size), 8, 16, QChar { '0' });\n         case 3:\n            return segment.read ? \"R\" : \".\";\n         case 4:\n            return segment.write ? \"W\" : \".\";\n         case 5:\n            return segment.execute ? \"X\" : \".\";\n         case 6:\n            return QString(\"0x%1\").arg(static_cast<uint>(segment.align), 0, 16, QChar { '0' });\n         }\n      } else if (role == AddressRole) {\n         return segments[index.row()].address;\n      } else if (role == ExecuteRole) {\n         return segments[index.row()].execute;\n      }\n\n      return QVariant { };\n   }\n\n   QVariant headerData(int section, Qt::Orientation orientation, int role) const override\n   {\n      if (role != Qt::DisplayRole) {\n         return QVariant{ };\n      }\n\n      if (orientation == Qt::Horizontal) {\n         if (section < ColumnCount) {\n            return ColumnNames[section];\n         }\n      }\n\n      return QVariant { };\n   }\n\nprivate slots:\n   void debugDataChanged()\n   {\n      auto newSize = static_cast<int>(mDebugData->segments().size());\n\n      if (newSize < mPreviousSize) {\n         beginRemoveRows({}, newSize, mPreviousSize - 1);\n         endRemoveRows();\n      } else if (newSize > mPreviousSize) {\n         beginInsertRows({}, mPreviousSize, newSize - 1);\n         endInsertRows();\n      }\n\n      mPreviousSize = newSize;\n   }\n\nprivate:\n   DebugData *mDebugData = nullptr;\n   int mPreviousSize = 0;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/segmentswindow.cpp",
    "content": "#include \"segmentswindow.h\"\n#include \"ui_segmentswindow.h\"\n\n#include \"debugdata.h\"\n#include \"segmentsmodel.h\"\n\nSegmentsWindow::SegmentsWindow(QWidget *parent) :\n   QWidget(parent),\n   ui(new Ui::SegmentsWindow { })\n{\n   ui->setupUi(this);\n}\n\nSegmentsWindow::~SegmentsWindow()\n{\n   delete ui;\n}\n\nvoid\nSegmentsWindow::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n   mSegmentsModel = new SegmentsModel { this };\n   mSegmentsModel->setDebugData(debugData);\n\n   ui->tableView->setModel(mSegmentsModel);\n\n   auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1;\n   ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);\n   ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2);\n\n   ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(4, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(5, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(6, QHeaderView::ResizeToContents);\n   ui->tableView->update();\n}\n\nvoid\nSegmentsWindow::segmentsViewDoubleClicked(const QModelIndex &index)\n{\n   auto address = mSegmentsModel->data(index, SegmentsModel::AddressRole);\n   auto executable = mSegmentsModel->data(index, SegmentsModel::ExecuteRole);\n   if (!address.isValid() || !executable.isValid()) {\n      return;\n   }\n\n   if (executable.toBool()) {\n      navigateToTextAddress(address.value<uint32_t>());\n   } else {\n      navigateToDataAddress(address.value<uint32_t>());\n   }\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/segmentswindow.h",
    "content": "#pragma once\n#include <QWidget>\n#include <memory>\n\nnamespace Ui\n{\nclass SegmentsWindow;\n}\n\nclass DebugData;\nclass SegmentsModel;\n\nclass SegmentsWindow : public QWidget\n{\n   Q_OBJECT\n\npublic:\n   explicit SegmentsWindow(QWidget *parent = nullptr);\n   ~SegmentsWindow();\n\n   void setDebugData(DebugData *debugData);\n\nsignals:\n   void navigateToDataAddress(uint32_t address);\n   void navigateToTextAddress(uint32_t address);\n\nprotected slots:\n   void segmentsViewDoubleClicked(const QModelIndex &index);\n\nprivate:\n   Ui::SegmentsWindow *ui;\n\n   DebugData *mDebugData = nullptr;\n   SegmentsModel *mSegmentsModel = nullptr;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/stackwidget.cpp",
    "content": "#include \"stackwidget.h\"\n\n#include <QtEndian>\n#include <QPainter>\n#include <QScrollBar>\n#include <QTextBlock>\n#include <QTextCursor>\n\n#include <libdecaf/decaf_debug_api.h>\n\nStackWidget::StackWidget(QWidget *parent) :\n   AddressTextDocumentWidget(parent)\n{\n   setBytesPerLine(4);\n   setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);\n\n   mTextFormats.invalid = QTextCharFormat { };\n   mTextFormats.invalid.setForeground(Qt::gray);\n\n   mTextFormats.lineAddress = QTextCharFormat { };\n   mTextFormats.lineAddress.setForeground(Qt::black);\n\n   mTextFormats.data = QTextCharFormat { };\n   mTextFormats.data.setForeground(Qt::gray);\n\n   mTextFormats.symbolName = QTextCharFormat { };\n   mTextFormats.symbolName.setForeground(Qt::blue);\n\n   mTextFormats.referencedAscii = QTextCharFormat { };\n   mTextFormats.referencedAscii.setForeground(Qt::gray);\n\n   mTextFormats.currentLine = QTextCharFormat { };\n   mTextFormats.currentLine.setBackground(QColor { Qt::green }.lighter());\n\n   mTextFormats.stackOutline = Qt::darkBlue;\n   mTextFormats.backchainOutline = Qt::black;\n}\n\nvoid\nStackWidget::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n   connect(mDebugData, &DebugData::dataChanged, this, &StackWidget::dataChanged);\n   connect(mDebugData, &DebugData::activeThreadIndexChanged, this, &StackWidget::activeThreadChanged);\n}\n\nvoid\nStackWidget::dataChanged()\n{\n   AddressTextDocumentWidget::updateTextDocument(true);\n   viewport()->update();\n}\n\nvoid\nStackWidget::activeThreadChanged()\n{\n   if (auto activeThread = mDebugData->activeThread()) {\n      setAddressRange(activeThread->stackEnd, activeThread->stackStart);\n      navigateToAddress(activeThread->gpr[1]);\n      AddressTextDocumentWidget::updateTextDocument(true);\n      viewport()->update();\n   }\n}\n\nvoid\nStackWidget::navigateOperand()\n{\n   auto address = getDocumentCursor().address;\n   auto data = std::array<uint8_t, 4> { };\n   auto symbolNameBuffer = std::array<char, 256> { };\n   auto moduleNameBuffer = std::array<char, 256> { };\n\n   auto valid = decaf::debug::readMemory(address, data.data(), data.size()) == data.size();\n   auto value = qFromBigEndian<VirtualAddress>(data.data());\n   if (value > 0 && value < 0x10000000) {\n      auto symbolDistance = uint32_t{ 0 };\n      auto symbolFound =\n         decaf::debug::findClosestSymbol(\n            value,\n            &symbolDistance,\n            symbolNameBuffer.data(),\n            static_cast<uint32_t>(symbolNameBuffer.size()),\n            moduleNameBuffer.data(),\n            static_cast<uint32_t>(moduleNameBuffer.size()));\n\n      if (symbolFound && moduleNameBuffer[0]) {\n         emit navigateToTextAddress(value);\n      }\n   }\n}\n\nvoid\nStackWidget::paintEvent(QPaintEvent *e)\n{\n   AddressTextDocumentWidget::paintEvent(e);\n   auto painter = QPainter { viewport() };\n   painter.translate(QPoint { -horizontalScrollBar()->value(), 0 });\n\n   if (mVisibleColumns.outline) {\n      auto offset = documentMargin();\n      if (mVisibleColumns.lineAddress) {\n         offset += 9 * characterWidth();\n      }\n\n      auto startAddress = getStartAddress();\n      auto firstVisibleLine = verticalScrollBar()->value();\n      auto lastVisibleLine = firstVisibleLine + (verticalScrollBar()->pageStep() - 1);\n\n      auto currentLineStartY = -1;\n      auto currentLineIsBackchain = false;\n      auto lineX1 = offset + (characterWidth() / 2);\n      auto lineX2 = lineX1 + characterWidth();\n      auto lineY = documentMargin() + lineHeight() / 2;\n\n      auto itr = mStackFrames.lower_bound(startAddress + firstVisibleLine * 4);\n      if (itr != mStackFrames.end()) {\n         for (auto i = firstVisibleLine; i < lastVisibleLine; ++i) {\n            auto address = startAddress + i * 4;\n\n            if (address >= itr->second.end) {\n               ++itr;\n            }\n\n            if (itr == mStackFrames.end()) {\n               break;\n            }\n\n            if (address == itr->second.start) {\n               // Start of frame\n               painter.setPen(mTextFormats.stackOutline);\n               painter.drawLine(lineX1, lineY, lineX2, lineY);\n               currentLineStartY = lineY;\n               currentLineIsBackchain = false;\n            } else if (address == itr->second.end - 12) {\n               // End of frame\n               painter.setPen(mTextFormats.stackOutline);\n               painter.drawLine(lineX1, currentLineStartY, lineX1, lineY);\n               painter.drawLine(lineX1, lineY, lineX2, lineY);\n               currentLineStartY = lineY;\n               currentLineIsBackchain = true;\n            } else if (address == itr->second.end - 4) {\n               // End of back chain\n               painter.setPen(mTextFormats.backchainOutline);\n               painter.drawLine(lineX1, currentLineStartY, lineX1, lineY);\n               painter.drawLine(lineX1, lineY, lineX2, lineY);\n               currentLineStartY = -1;\n            } else if (address > itr->second.start && address < itr->second.end) {\n               // Inside stack frame\n               if (currentLineStartY == -1) {\n                  currentLineStartY = 0;\n                  currentLineIsBackchain = false;\n               }\n            }\n\n            lineY += lineHeight();\n         }\n      }\n\n      if (currentLineStartY != -1) {\n         painter.setPen(QPen {\n            currentLineIsBackchain ?\n               mTextFormats.backchainOutline :\n               mTextFormats.stackOutline });\n         painter.drawLine(lineX1, currentLineStartY, lineX1, viewport()->height());\n      }\n   }\n}\n\nvoid\nStackWidget::keyPressEvent(QKeyEvent *e)\n{\n   auto handled = false;\n\n   if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {\n      navigateOperand();\n      handled = true;\n   }\n\n   if (!handled) {\n      AddressTextDocumentWidget::keyPressEvent(e);\n   } else {\n      e->accept();\n   }\n}\n\nQVector<QAbstractTextDocumentLayout::Selection>\nStackWidget::getCustomSelections(QTextDocument *document)\n{\n   if (auto activeThread = mDebugData->activeThread()) {\n      mStackCurrentAddress = activeThread->gpr[1];\n   }\n\n   mCustomSelectionsBuffer.clear();\n\n   // Essentially we want to select the line with mStackCurrentAddress on\n   if (mStackCurrentAddress >= mStackFirstVisibleAddress &&\n       mStackCurrentAddress < mStackLastVisibleAddress) {\n      auto line = (mStackCurrentAddress - mStackFirstVisibleAddress) / 4;\n      auto block = document->findBlockByLineNumber(line);\n      auto selection = QAbstractTextDocumentLayout::Selection{ };\n      selection.cursor = QTextCursor{ document->findBlockByLineNumber(line) };\n      selection.cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);\n      selection.format = mTextFormats.currentLine;\n      mCustomSelectionsBuffer.push_back(selection);\n   }\n\n   return mCustomSelectionsBuffer;\n}\n\nvoid\nStackWidget::updateStackFrames()\n{\n   if (auto activeThread = mDebugData->activeThread()) {\n      mStackCurrentAddress = activeThread->gpr[1];\n   }\n\n   auto frame = StackFrame { };\n   auto address = mStackCurrentAddress + 8;\n   auto startAddress = getEndAddress();\n   auto frameEnd = std::array<uint8_t, 4> { };\n   mStackFrames.clear();\n\n   while (true) {\n      if (address < mStackCurrentAddress ||\n         !decaf::debug::isValidVirtualAddress(address)) {\n         // If we somehow jump outside of our valid range, lets stop\n         break;\n      }\n\n      decaf::debug::readMemory(address - 8, frameEnd.data(), 4);\n\n      frame.start = address;\n      frame.end = qFromBigEndian<uint32_t>(frameEnd.data()) + 8;\n\n      if (mStackFrames.find(frame.end) != mStackFrames.end()) {\n         // Prevent an infinite loop with a buggy stack!\n         break;\n      }\n\n      if (frame.end > startAddress) {\n         // Stop when we go outside of the stack\n         break;\n      }\n\n      mStackFrames.emplace(frame.end, frame);\n      address = frame.end;\n   }\n}\n\nvoid\nStackWidget::updateTextDocument(QTextCursor cursor,\n                                VirtualAddress firstLineAddress,\n                                VirtualAddress lastLineAddress,\n                                int bytesPerLine,\n                                bool forDisplay)\n{\n   auto data = std::array<uint8_t, 4> { };\n   auto symbolNameBuffer = std::array<char, 256> { };\n   auto moduleNameBuffer = std::array<char, 256> { };\n\n   if (forDisplay) {\n      mStackFirstVisibleAddress = firstLineAddress;\n      mStackLastVisibleAddress = lastLineAddress;\n      updateStackFrames();\n   }\n\n   for (auto address = static_cast<int64_t>(firstLineAddress);\n        address <= lastLineAddress; address += bytesPerLine) {\n      auto valid = decaf::debug::readMemory(address, data.data(), data.size()) == data.size();\n\n      if (address != firstLineAddress) {\n         cursor.insertBlock();\n      }\n\n      if (mVisibleColumns.lineAddress) {\n         cursor.insertText(\n            QString { \"%1\" }.arg(address, 8, 16, QLatin1Char{ '0' }),\n            valid ? mTextFormats.lineAddress : mTextFormats.invalid);\n         cursor.insertText(mPunctuation.afterLineAddress, mTextFormats.punctuation);\n      }\n\n      if (mVisibleColumns.outline) {\n         cursor.insertText(mPunctuation.outline, mTextFormats.punctuation);\n      }\n\n      if (mVisibleColumns.data) {\n         cursor.insertText(QString { \"%1 %2 %3 %4\" }\n            .arg(data[0], 2, 16, QLatin1Char { '0' })\n            .arg(data[1], 2, 16, QLatin1Char { '0' })\n            .arg(data[2], 2, 16, QLatin1Char { '0' })\n            .arg(data[3], 2, 16, QLatin1Char { '0' })\n            .toUpper(), mTextFormats.data);\n         cursor.insertText(mPunctuation.afterData, mTextFormats.punctuation);\n      }\n\n      if (mVisibleColumns.dataReference) {\n         auto value = qFromBigEndian<VirtualAddress>(data.data());\n         if (value > 0 && value < 0x10000000) {\n            auto symbolDistance = uint32_t { 0 };\n            auto symbolFound =\n               decaf::debug::findClosestSymbol(\n                  value,\n                  &symbolDistance,\n                  symbolNameBuffer.data(),\n                  static_cast<uint32_t>(symbolNameBuffer.size()),\n                  moduleNameBuffer.data(),\n                  static_cast<uint32_t>(moduleNameBuffer.size()));\n\n            if (symbolFound && moduleNameBuffer[0]) {\n               cursor.insertText(\n                  QString { \"%1::%2+0x%3\" }\n                  .arg(moduleNameBuffer.data())\n                  .arg(symbolNameBuffer.data())\n                  .arg(symbolDistance, 0, 16), mTextFormats.symbolName);\n            }\n         } else if (value >= 0x10000000) {\n            // TODO: Try read ASCII ?\n         }\n      }\n   }\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/stackwidget.h",
    "content": "#pragma once\n#include \"addresstextdocumentwidget.h\"\n#include \"debugdata.h\"\n\n#include <map>\n#include <QString>\n#include <QTextCharFormat>\n\nclass QTextDocument;\n\nclass StackWidget : public AddressTextDocumentWidget\n{\n   Q_OBJECT\n\n   using VirtualAddress = DebugData::VirtualAddress;\n\n   struct StackFrame\n   {\n      VirtualAddress start;\n      VirtualAddress end;\n   };\n\npublic:\n   StackWidget(QWidget *parent = nullptr);\n\n   void setDebugData(DebugData *debugData);\n\npublic slots:\n   void navigateOperand();\n\nsignals:\n   void navigateToTextAddress(uint32_t address);\n\nprotected:\n   void paintEvent(QPaintEvent *e) override;\n   void keyPressEvent(QKeyEvent *e) override;\n\n   QVector<QAbstractTextDocumentLayout::Selection>\n   getCustomSelections(QTextDocument *document) override;\n   void updateStackFrames();\n   void updateTextDocument(QTextCursor cursor, VirtualAddress firstLineAddress,\n                           VirtualAddress lastLineAddress,\n                           int bytePerLine,\n                           bool forDisplay) override;\n\nprivate slots:\n   void activeThreadChanged();\n   void dataChanged();\n\nprivate:\n   DebugData *mDebugData;\n\n   VirtualAddress mStackCurrentAddress;\n   VirtualAddress mStackFirstVisibleAddress;\n   VirtualAddress mStackLastVisibleAddress;\n   QVector<QAbstractTextDocumentLayout::Selection> mCustomSelectionsBuffer;\n\n   // Cached stack frames\n   std::map<VirtualAddress, StackFrame> mStackFrames;\n\n   // Visible columns in disassembly\n   struct {\n      bool lineAddress = true;\n      bool outline = true;\n      bool data = true;\n      bool dataReference = true;\n   } mVisibleColumns;\n\n   // Punctuation between columns\n   struct {\n      QString afterLineAddress = \"  \";\n      QString outline = \"  \";\n      QString afterData = \"  \";\n   } mPunctuation;\n\n   // Formatting for each data type\n   struct {\n      QTextCharFormat invalid;\n      QTextCharFormat punctuation;\n      QTextCharFormat lineAddress;\n      QTextCharFormat currentLine;\n      QTextCharFormat data;\n      QTextCharFormat symbolName;\n      QTextCharFormat referencedAscii;\n      QColor stackOutline;\n      QColor backchainOutline;\n   } mTextFormats;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/stackwindow.cpp",
    "content": "#include \"debuggershortcuts.h\"\n#include \"stackwindow.h\"\n#include \"ui_stackwindow.h\"\n\nStackWindow::StackWindow(DebuggerShortcuts *debuggerShortcuts,\n                         QWidget *parent) :\n   QWidget(parent),\n   ui(new Ui::StackWindow { })\n{\n   ui->setupUi(this);\n\n   ui->stackWidget->addAction(debuggerShortcuts->navigateBackward);\n   ui->stackWidget->addAction(debuggerShortcuts->navigateForward);\n   ui->stackWidget->addAction(debuggerShortcuts->navigateToAddress);\n   ui->stackWidget->addAction(debuggerShortcuts->navigateToOperand);\n\n   connect(ui->stackWidget, &StackWidget::navigateToTextAddress, this, &StackWindow::navigateToTextAddress);\n}\n\nStackWindow::~StackWindow()\n{\n   delete ui;\n}\n\nvoid\nStackWindow::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n   ui->stackWidget->setDebugData(mDebugData);\n   connect(mDebugData, &DebugData::dataChanged, this, &StackWindow::updateStatus);\n   connect(mDebugData, &DebugData::activeThreadIndexChanged, this, &StackWindow::updateStatus);\n}\n\nvoid\nStackWindow::navigateToAddress(uint32_t address)\n{\n   ui->stackWidget->navigateToAddress(address);\n}\n\nvoid\nStackWindow::navigateForward()\n{\n   ui->stackWidget->navigateForward();\n}\n\nvoid\nStackWindow::navigateBackward()\n{\n   ui->stackWidget->navigateBackward();\n}\n\nvoid\nStackWindow::navigateOperand()\n{\n   ui->stackWidget->navigateOperand();\n}\n\nvoid\nStackWindow::updateStatus()\n{\n   auto start = DebugData::VirtualAddress { 0 };\n   auto end = DebugData::VirtualAddress { 0 };\n   auto current = DebugData::VirtualAddress { 0 };\n\n   if (auto activeThread = mDebugData->activeThread()) {\n      start = activeThread->stackStart;\n      end = activeThread->stackEnd;\n      current = activeThread->gpr[1];\n   }\n\n   ui->labelStatus->setText(\n      QString { \"Showing stack from %1 to %2. Current %3\" }\n      .arg(start, 8, 16, QLatin1Char { '0' })\n      .arg(end, 8, 16, QLatin1Char { '0' })\n      .arg(current, 8, 16, QLatin1Char{ '0' }));\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/stackwindow.h",
    "content": "#pragma once\n#include <QWidget>\n\n#include \"debugdata.h\"\n\nnamespace Ui\n{\nclass StackWindow;\n}\n\nclass QAbstractItemModel;\nstruct DebuggerShortcuts;\n\nclass StackWindow : public QWidget\n{\n   Q_OBJECT\n\npublic:\n   StackWindow(DebuggerShortcuts *debuggerShortcuts, QWidget *parent = nullptr);\n   ~StackWindow();\n\n   void setDebugData(DebugData *debugData);\n\nsignals:\n   void navigateToTextAddress(uint32_t address);\n\npublic slots:\n   void navigateToAddress(uint32_t address);\n   void navigateForward();\n   void navigateBackward();\n   void navigateOperand();\n\nprotected slots:\n   void updateStatus();\n\nprivate:\n   Ui::StackWindow *ui;\n\n   DebugData *mDebugData = nullptr;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/threadsmodel.h",
    "content": "#pragma once\n#include <QAbstractTableModel>\n\n#include \"debugdata.h\"\n\nclass ThreadsModel : public QAbstractTableModel\n{\n   Q_OBJECT\n\n   static constexpr const char *ColumnNames[] = {\n      \"ID\",\n      \"Name\",\n      \"NIA\",\n      \"State\",\n      \"Priority\",\n      \"Affinity\",\n      \"Core\",\n      \"Core Time\"\n   };\n\n   static constexpr int ColumnCount =\n      static_cast<int>(sizeof(ColumnNames) / sizeof(ColumnNames[0]));\n\n   using CafeThread = DebugData::CafeThread;\n\npublic:\n   enum UserRoles\n   {\n      IdRole = Qt::UserRole,\n   };\n\n   ThreadsModel(QObject *parent = nullptr) :\n      QAbstractTableModel(parent)\n   {\n   }\n\n   void setDebugData(DebugData *debugData)\n   {\n      mDebugData = debugData;\n      connect(mDebugData, &DebugData::dataChanged, this, &ThreadsModel::debugDataChanged);\n   }\n\n   int rowCount(const QModelIndex &parent) const override\n   {\n      if (!mDebugData) {\n         return 0;\n      }\n\n      return static_cast<int>(mDebugData->threads().size());\n   }\n\n   int columnCount(const QModelIndex &parent) const override\n   {\n      return ColumnCount;\n   }\n\n   static QString getThreadStateName(CafeThread::ThreadState state)\n   {\n      switch (state) {\n      case CafeThread::Inactive:\n         return tr(\"Inactive\");\n      case CafeThread::Ready:\n         return tr(\"Ready\");\n      case CafeThread::Running:\n         return tr(\"Running\");\n      case CafeThread::Waiting:\n         return tr(\"Waiting\");\n      case CafeThread::Moribund:\n         return tr(\"Moribund\");\n      default:\n         return tr(\"Invalid\");\n      }\n   }\n\n   QVariant data(const QModelIndex &index, int role) const override\n   {\n      if (!mDebugData || !index.isValid()) {\n         return QVariant { };\n      }\n\n      auto &threads = mDebugData->threads();\n      if (index.row() >= threads.size() || index.row() < 0) {\n         return QVariant { };\n      }\n\n      const auto &threadInfo = threads[index.row()];\n      if (role == Qt::DisplayRole) {\n         switch (index.column()) {\n         case 0:\n            return QString(\"%1\").arg(threadInfo.id);\n         case 1:\n            return QString::fromStdString(threadInfo.name);\n         case 2:\n            return QString(\"%1\").arg(static_cast<uint>(threadInfo.nia), 8, 16, QChar{ '0' });\n         case 3:\n            return getThreadStateName(threadInfo.state);\n         case 4:\n            return QString(\"%1\").arg(threadInfo.priority);\n         case 5:\n            return QString(\"%1\").arg(threadInfo.affinity, 3, 2, QChar{ '0' });\n         case 6:\n            if (threadInfo.coreId == -1) {\n               return QString();\n            } else {\n               return QString(\"%1\").arg(threadInfo.coreId);\n            }\n         case 7:\n            return QString(\"%1\").arg(threadInfo.executionTime.count());\n         }\n      } else if (role == IdRole) {\n         return threadInfo.id;\n      }\n\n      return QVariant { };\n   }\n\n   QVariant headerData(int section, Qt::Orientation orientation, int role) const override\n   {\n      if (role != Qt::DisplayRole) {\n         return QVariant { };\n      }\n\n      if (orientation == Qt::Horizontal) {\n         if (section < ColumnCount) {\n            return ColumnNames[section];\n         }\n      }\n\n      return QVariant { };\n   }\n\nprivate slots:\n   void debugDataChanged()\n   {\n      auto newSize = static_cast<int>(mDebugData->threads().size());\n\n      if (newSize < mPreviousSize) {\n         beginRemoveRows({}, newSize, mPreviousSize - 1);\n         endRemoveRows();\n      } else if (newSize > mPreviousSize) {\n         beginInsertRows({}, mPreviousSize, newSize - 1);\n         endInsertRows();\n      }\n\n      dataChanged(index(0, 0), index(newSize, ColumnCount));\n      mPreviousSize = newSize;\n   }\n\nprivate:\n   DebugData *mDebugData = nullptr;\n   int mPreviousSize = 0;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/threadswindow.cpp",
    "content": "#include \"threadswindow.h\"\n#include \"ui_threadswindow.h\"\n\n#include \"debugdata.h\"\n#include \"threadsmodel.h\"\n\nThreadsWindow::ThreadsWindow(QWidget *parent) :\n   QWidget(parent),\n   ui(new Ui::ThreadsWindow { })\n{\n   ui->setupUi(this);\n}\n\nThreadsWindow::~ThreadsWindow()\n{\n   delete ui;\n}\n\nvoid\nThreadsWindow::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n   mThreadsModel = new ThreadsModel { this };\n   mThreadsModel->setDebugData(debugData);\n\n   ui->tableView->setModel(mThreadsModel);\n\n   auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1;\n   ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);\n   ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2);\n\n   ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(4, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(5, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(6, QHeaderView::ResizeToContents);\n   ui->tableView->horizontalHeader()->setSectionResizeMode(7, QHeaderView::ResizeToContents);\n   ui->tableView->update();\n}\n\nvoid\nThreadsWindow::threadsViewDoubleClicked(const QModelIndex &index)\n{\n   if (index.isValid()) {\n      mDebugData->setActiveThreadIndex(index.row());\n   }\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/threadswindow.h",
    "content": "#pragma once\n#include <QWidget>\n\nnamespace Ui\n{\nclass ThreadsWindow;\n}\n\nclass DebugData;\nclass ThreadsModel;\n\nclass ThreadsWindow : public QWidget\n{\n   Q_OBJECT\n\npublic:\n   explicit ThreadsWindow(QWidget *parent = nullptr);\n   ~ThreadsWindow();\n\n   void setDebugData(DebugData *debugData);\n\nprotected slots:\n   void threadsViewDoubleClicked(const QModelIndex &index);\n\nprivate:\n   Ui::ThreadsWindow *ui;\n\n   DebugData *mDebugData = nullptr;\n   ThreadsModel *mThreadsModel = nullptr;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/voicesmodel.h",
    "content": "#pragma once\n#include <QAbstractTableModel>\n#include <vector>\n\n#include \"debugdata.h\"\n\nclass VoicesModel : public QAbstractTableModel\n{\n   Q_OBJECT\n\n   static constexpr const char *ColumnNames[] = {\n      \"ID\",\n      \"State\",\n      \"Format\",\n      \"Stream\",\n      \"Address\",\n      \"Current Offset\",\n      \"End Offset\",\n      \"Loop Offset\",\n      \"Loop Mode\",\n   };\n\n   static constexpr int ColumnCount =\n      static_cast<int>(sizeof(ColumnNames) / sizeof(ColumnNames[0]));\n\n   using CafeVoice = DebugData::CafeVoice;\n\npublic:\n   VoicesModel(QObject *parent = nullptr) :\n      QAbstractTableModel(parent)\n   {\n   }\n\n   void setDebugData(DebugData *debugData)\n   {\n      mDebugData = debugData;\n      connect(mDebugData, &DebugData::dataChanged, this, &VoicesModel::debugDataChanged);\n   }\n\n   int rowCount(const QModelIndex &parent) const override\n   {\n      if (!mDebugData) {\n         return 0;\n      }\n\n      return static_cast<int>(mDebugData->voices().size());\n   }\n\n   int columnCount(const QModelIndex &parent) const override\n   {\n      return ColumnCount;\n   }\n\n   static QString getStateString(CafeVoice::State state)\n   {\n      switch (state) {\n      case CafeVoice::State::Stopped:\n         return tr(\"Stopped\");\n      case CafeVoice::State::Playing:\n         return tr(\"Playing\");\n      default:\n         return tr(\"Unknown\");\n      }\n   }\n\n   static QString getFormatString(CafeVoice::Format format)\n   {\n      switch (format) {\n      case CafeVoice::Format::ADPCM:\n         return tr(\"ADPCM\");\n      case CafeVoice::Format::LPCM16:\n         return tr(\"LPCM16\");\n      case CafeVoice::Format::LPCM8:\n         return tr(\"LPCM8\");\n      default:\n         return tr(\"Unknown\");\n      }\n   }\n\n   static QString getVoiceTypeString(CafeVoice::VoiceType type)\n   {\n      switch (type) {\n      case CafeVoice::VoiceType::Default:\n         return tr(\"Default\");\n      case CafeVoice::VoiceType::Streaming:\n         return tr(\"Streaming\");\n      default:\n         return tr(\"Unknown\");\n      }\n   }\n\n   QVariant data(const QModelIndex &index, int role) const override\n   {\n      if (!mDebugData || !index.isValid()) {\n         return QVariant{ };\n      }\n\n      const auto &voices = mDebugData->voices();\n      if (index.row() >= voices.size() || index.row() < 0) {\n         return QVariant{ };\n      }\n\n      if (role == Qt::DisplayRole) {\n         const auto &voice = voices.at(index.row());\n\n         switch (index.column()) {\n         case 0:\n            return QString(\"%1\").arg(voice.index);\n         case 1:\n            return getStateString(voice.state);\n         case 2:\n            return getFormatString(voice.format);\n         case 3:\n            return getVoiceTypeString(voice.type);\n         case 4:\n            return QString(\"%1\").arg(static_cast<uint>(voice.data), 8, 16, QChar{ '0' });\n         case 5:\n            return QString(\"%1\").arg(voice.currentOffset);\n         case 6:\n            return QString(\"%1\").arg(voice.endOffset);\n         case 7:\n            return QString(\"%1\").arg(voice.loopOffset);\n         case 8:\n            return voice.loopingEnabled ? tr(\"Looping\") : QString(\"\");\n         }\n      }\n\n      return QVariant { };\n   }\n\n   QVariant headerData(int section, Qt::Orientation orientation, int role) const override\n   {\n      if (role != Qt::DisplayRole) {\n         return QVariant{ };\n      }\n\n      if (orientation == Qt::Horizontal) {\n         if (section < ColumnCount) {\n            return ColumnNames[section];\n         }\n      }\n\n      return QVariant { };\n   }\n\nprivate:\n   void debugDataChanged()\n   {\n      auto newSize = static_cast<int>(mDebugData->voices().size());\n\n      if (newSize < mPreviousSize) {\n         beginRemoveRows({}, newSize, mPreviousSize - 1);\n         endRemoveRows();\n      } else if (newSize > mPreviousSize) {\n         beginInsertRows({}, mPreviousSize, newSize - 1);\n         endInsertRows();\n      }\n\n      dataChanged(index(0, 0), index(newSize, ColumnCount));\n      mPreviousSize = newSize;\n   }\n\nprivate:\n   DebugData *mDebugData = nullptr;\n   int mPreviousSize = 0;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/voiceswindow.cpp",
    "content": "#include \"voiceswindow.h\"\n#include \"ui_voiceswindow.h\"\n\n#include \"debugdata.h\"\n#include \"voicesmodel.h\"\n\nVoicesWindow::VoicesWindow(QWidget *parent) :\n   QWidget(parent),\n   ui(new Ui::VoicesWindow { })\n{\n   ui->setupUi(this);\n}\n\nVoicesWindow::~VoicesWindow()\n{\n   delete ui;\n}\n\nvoid\nVoicesWindow::setDebugData(DebugData *debugData)\n{\n   mDebugData = debugData;\n   mVoicesModel = new VoicesModel { this };\n   mVoicesModel->setDebugData(debugData);\n\n   ui->tableView->setModel(mVoicesModel);\n\n   auto textMargin = ui->tableView->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, ui->tableView) + 1;\n   ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);\n   ui->tableView->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + textMargin * 2);\n\n   ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);\n   for (int i = 1; i < ui->tableView->horizontalHeader()->count(); ++i) {\n      ui->tableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Stretch);\n   }\n   ui->tableView->update();\n}\n"
  },
  {
    "path": "src/decaf-qt/src/debugger/voiceswindow.h",
    "content": "#pragma once\n#include <QWidget>\n\nnamespace Ui\n{\nclass VoicesWindow;\n}\n\nclass DebugData;\nclass VoicesModel;\n\nclass VoicesWindow : public QWidget\n{\n   Q_OBJECT\n\npublic:\n   explicit VoicesWindow(QWidget *parent = nullptr);\n   ~VoicesWindow();\n\n   void setDebugData(DebugData *debugData);\n\nprivate:\n   Ui::VoicesWindow *ui;\n\n   DebugData *mDebugData = nullptr;\n   VoicesModel *mVoicesModel = nullptr;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/decafinterface.cpp",
    "content": "#include \"decafinterface.h\"\n#include \"inputdriver.h\"\n#include \"sounddriver.h\"\n#include \"settings.h\"\n\n#include <QCoreApplication>\n#include <common/log.h>\n#include <libconfig/config_toml.h>\n#include <libdecaf/decaf.h>\n#include <libdecaf/decaf_debug_api.h>\n#include <libdecaf/decaf_log.h>\n#include <libdecaf/decaf_nullinputdriver.h>\n\nDecafInterface::DecafInterface(SettingsStorage *settingsStorage,\n                               InputDriver *inputDriver,\n                               SoundDriver *soundDriver) :\n   mInputDriver(inputDriver),\n   mSettingsStorage(settingsStorage),\n   mSoundDriver(soundDriver)\n{\n   decaf::addEventListener(this);\n   decaf::debug::setPauseCallback([&]() {\n      this->debugInterrupt();\n   });\n\n   QObject::connect(mSettingsStorage, &SettingsStorage::settingsChanged,\n                    this, &DecafInterface::settingsChanged);\n   settingsChanged();\n}\n\nvoid\nDecafInterface::settingsChanged()\n{\n   auto settings = mSettingsStorage->get();\n   decaf::setConfig(settings->decaf);\n   gpu::setConfig(settings->gpu);\n   cpu::setConfig(settings->cpu);\n}\n\nvoid\nDecafInterface::startLogging()\n{\n   decaf::initialiseLogging(\"decaf-qt\");\n}\n\nvoid\nDecafInterface::startGame(QString path)\n{\n   mStarted = true;\n\n   decaf::setInputDriver(mInputDriver);\n   decaf::setSoundDriver(mSoundDriver);\n   gLog->info(\"Config path {}\", mSettingsStorage->path());\n\n   decaf::initialise(path.toLocal8Bit().constData());\n   decaf::start();\n}\n\nvoid\nDecafInterface::shutdown()\n{\n   if (mStarted) {\n      decaf::shutdown();\n      mStarted = false;\n   }\n}\n\nvoid\nDecafInterface::onGameLoaded(const decaf::GameInfo &info)\n{\n   emit titleLoaded(info.titleId, QString::fromStdString(info.executable));\n}\n"
  },
  {
    "path": "src/decaf-qt/src/decafinterface.h",
    "content": "#pragma once\n#include \"settings.h\"\n\n#include <libdecaf/decaf_eventlistener.h>\n#include <libdecaf/decaf_graphics.h>\n#include <libdecaf/decaf_sound.h>\n\n#include <QObject>\n#include <QString>\n\nclass InputDriver;\nclass SettingsStorage;\nclass SoundDriver;\n\nclass DecafInterface : public QObject, public decaf::EventListener\n{\n   Q_OBJECT\n\npublic:\n   DecafInterface(SettingsStorage *settingsStorage, InputDriver *inputDriver,\n                  SoundDriver *soundDriver);\n\npublic slots:\n   void startLogging();\n   void startGame(QString path);\n   void shutdown();\n   void settingsChanged();\n\nsignals:\n   void titleLoaded(quint64 id, const QString &name);\n   void debugInterrupt();\n\nprotected:\n   void onGameLoaded(const decaf::GameInfo &info) override;\n\nprivate:\n   bool mStarted = false;\n   InputDriver *mInputDriver = nullptr;\n   SettingsStorage *mSettingsStorage = nullptr;\n   SoundDriver *mSoundDriver = nullptr;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/erreuladriver.cpp",
    "content": "#include \"erreuladriver.h\"\n\nErrEulaDriver::ErrEulaDriver(QObject *parent) :\n   QObject(parent)\n{\n}\n\nvoid\nErrEulaDriver::onOpenErrorCode(int32_t errorCode)\n{\n   emit openWithErrorCode(errorCode);\n}\n\nvoid\nErrEulaDriver::onOpenErrorMessage(std::u16string message,\n                                  std::u16string button1,\n                                  std::u16string button2)\n{\n   emit openWithMessage(QString::fromStdU16String(message),\n                        QString::fromStdU16String(button1),\n                        QString::fromStdU16String(button2));\n}\n\nvoid\nErrEulaDriver::onClose()\n{\n   emit close();\n}\n"
  },
  {
    "path": "src/decaf-qt/src/erreuladriver.h",
    "content": "#pragma once\n#include <libdecaf/decaf_erreula.h>\n#include <QObject>\n\nclass QInputDialog;\nclass QWidget;\n\nclass ErrEulaDriver : public QObject, public decaf::ErrEulaDriver\n{\n   Q_OBJECT\n\npublic:\n   ErrEulaDriver(QObject *parent);\n\nsignals:\n   void openWithErrorCode(int32_t errorCode);\n   void openWithMessage(QString message, QString button1, QString button2);\n   void close();\n\nprivate:\n   void onOpenErrorCode(int32_t errorCode) override;\n   void onOpenErrorMessage(std::u16string message,\n                           std::u16string button1,\n                           std::u16string button2) override;\n   void onClose() override;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/inputdriver.cpp",
    "content": "#include \"inputdriver.h\"\n\n#include <QKeyEvent>\n#include <QTimer>\n#include <SDL.h>\n#include <SDL_gamecontroller.h>\n\nInputDriver::InputDriver(SettingsStorage *settingsStorage,\n                         QObject *parent) :\n   QObject(parent),\n   mSettingsStorage(settingsStorage)\n{\n   QObject::connect(mSettingsStorage, &SettingsStorage::settingsChanged,\n                    this, &InputDriver::settingsChanged);\n   settingsChanged();\n\n   // Startup SDL\n   SDL_Init(SDL_INIT_HAPTIC | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER);\n\n   // Start a timer for polling for SDL events\n   QTimer::singleShot(100, Qt::PreciseTimer, this, SLOT(update()));\n\n   // Clear keyboard state\n   mKeyboardState.fill(false);\n}\n\nInputDriver::~InputDriver()\n{\n   SDL_Quit();\n}\n\nstatic inline float\ntranslateAxisValue(Sint16 value)\n{\n   if (value >= 0) {\n      return value / static_cast<float>(SDL_JOYSTICK_AXIS_MAX);\n   } else {\n      return value / static_cast<float>(-(SDL_JOYSTICK_AXIS_MIN));\n   }\n}\n\nvoid\nInputDriver::update()\n{\n   SDL_Event event;\n\n   while (SDL_PollEvent(&event)) {\n      switch (event.type) {\n      case SDL_JOYDEVICEADDED:\n      {\n         auto joystick = SDL_JoystickOpen(event.jdevice.which);\n         auto guid = SDL_JoystickGetGUID(joystick);\n         auto instanceId = SDL_JoystickInstanceID(joystick);\n\n         {\n            std::unique_lock lock { mConfigurationMutex };\n            auto connected = ConnectedJoystick { };\n            connected.joystick = joystick;\n            connected.guid = guid;\n            connected.instanceId = instanceId;\n            connected.duplicateId = 0;\n\n            for (auto &others : mJoysticks) {\n               if (others.guid == connected.guid) {\n                  connected.duplicateId = std::max(connected.duplicateId, others.duplicateId + 1);\n               }\n            }\n\n            for (auto &controller : mConfiguration.controllers) {\n               for (auto &input : controller.inputs) {\n                  if (input.joystickGuid == guid && input.joystickDuplicateId == connected.duplicateId) {\n                     input.joystick = joystick;\n                     input.joystickInstanceId = instanceId;\n                  }\n               }\n            }\n\n            mJoysticks.push_back(connected);\n         }\n\n         emit joystickConnected(instanceId, guid, SDL_JoystickName(joystick));\n         break;\n      }\n      case SDL_JOYDEVICEREMOVED:\n      {\n         auto joystick = SDL_JoystickFromInstanceID(event.jdevice.which);\n         if (joystick) {\n            joystickDisconnected(SDL_JoystickInstanceID(joystick), SDL_JoystickGetGUID(joystick));\n\n            {\n               std::unique_lock lock { mConfigurationMutex };\n               for (auto itr = mJoysticks.begin(); itr != mJoysticks.end(); ++itr) {\n                  if (itr->joystick == joystick) {\n                     itr = mJoysticks.erase(itr);\n                  }\n               }\n\n               for (auto &controller : mConfiguration.controllers) {\n                  for (auto &input : controller.inputs) {\n                     if (input.joystick == joystick) {\n                        input.joystick = nullptr;\n                     }\n                  }\n               }\n            }\n\n            SDL_JoystickClose(joystick);\n         }\n         break;\n      }\n      case SDL_JOYBUTTONDOWN:\n      {\n         if (mButtonEventsEnabled) {\n            auto joystick = SDL_JoystickFromInstanceID(event.jdevice.which);\n            if (joystick) {\n               joystickButtonDown(SDL_JoystickInstanceID(joystick), SDL_JoystickGetGUID(joystick), event.jbutton.button);\n            }\n         }\n         break;\n      }\n      case SDL_JOYAXISMOTION:\n      {\n         if (mButtonEventsEnabled) {\n            auto joystick = SDL_JoystickFromInstanceID(event.jdevice.which);\n            if (joystick) {\n               joystickAxisMotion(SDL_JoystickInstanceID(joystick), SDL_JoystickGetGUID(joystick), event.jaxis.axis, translateAxisValue(event.jaxis.value));\n            }\n         }\n         break;\n      }\n      case SDL_JOYHATMOTION:\n      {\n         if (mButtonEventsEnabled) {\n            auto joystick = SDL_JoystickFromInstanceID(event.jdevice.which);\n            if (joystick) {\n               joystickHatMotion(SDL_JoystickInstanceID(joystick), SDL_JoystickGetGUID(joystick), event.jhat.hat, event.jhat.value);\n            }\n         }\n         break;\n      }\n\n      // I don't think we actually care about game controllers?\n      case SDL_CONTROLLERDEVICEADDED:\n         qDebug(\"SDL_CONTROLLERDEVICEADDED\");\n         break;\n      case SDL_CONTROLLERDEVICEREMAPPED:\n         qDebug(\"SDL_CONTROLLERDEVICEREMAPPED\");\n         break;\n      case SDL_CONTROLLERDEVICEREMOVED:\n         qDebug(\"SDL_CONTROLLERDEVICEREMOVED\");\n         break;\n      case SDL_CONTROLLERBUTTONDOWN:\n         qDebug(\"SDL_CONTROLLERBUTTONDOWN\");\n         break;\n      }\n   }\n\n   QTimer::singleShot(10, Qt::PreciseTimer, this, SLOT(update()));\n}\n\nconst char *\ngetConfigButtonName(ButtonType type)\n{\n   switch (type) {\n   case ButtonType::A:\n      return \"button_a\";\n   case ButtonType::B:\n      return \"button_b\";\n   case ButtonType::X:\n      return \"button_x\";\n   case ButtonType::Y:\n      return \"button_y\";\n   case ButtonType::R:\n      return \"button_r\";\n   case ButtonType::L:\n      return \"button_l\";\n   case ButtonType::ZR:\n      return \"button_zr\";\n   case ButtonType::ZL:\n      return \"button_zl\";\n   case ButtonType::Plus:\n      return \"button_plus\";\n   case ButtonType::Minus:\n      return \"button_minus\";\n   case ButtonType::Home:\n      return \"button_home\";\n   case ButtonType::Sync:\n      return \"button_sync\";\n   case ButtonType::DpadUp:\n      return \"dpad_up\";\n   case ButtonType::DpadDown:\n      return \"dpad_down\";\n   case ButtonType::DpadLeft:\n      return \"dpad_left\";\n   case ButtonType::DpadRight:\n      return \"dpad_right\";\n   case ButtonType::LeftStickPress:\n      return \"left_stick_press\";\n   case ButtonType::LeftStickUp:\n      return \"left_stick_up\";\n   case ButtonType::LeftStickDown:\n      return \"left_stick_down\";\n   case ButtonType::LeftStickLeft:\n      return \"left_stick_left\";\n   case ButtonType::LeftStickRight:\n      return \"left_stick_right\";\n   case ButtonType::RightStickPress:\n      return \"right_stick_press\";\n   case ButtonType::RightStickUp:\n      return \"right_stick_up\";\n   case ButtonType::RightStickDown:\n      return \"right_stick_down\";\n   case ButtonType::RightStickLeft:\n      return \"right_stick_left\";\n   case ButtonType::RightStickRight:\n      return \"right_stick_right\";\n   default:\n      return \"\";\n   }\n}\n\nvoid\nInputDriver::keyPressEvent(int key)\n{\n   std::unique_lock<std::mutex> lock { mConfigurationMutex };\n   mKeyboardState[key & 0xFF] = true;\n}\n\nvoid\nInputDriver::keyReleaseEvent(int key)\n{\n   std::unique_lock<std::mutex> lock { mConfigurationMutex };\n   mKeyboardState[key & 0xFF] = false;\n}\n\nvoid\nInputDriver::gamepadTouchEvent(bool down, float x, float y)\n{\n   std::unique_lock<std::mutex> lock { mConfigurationMutex };\n   mTouchDown = down;\n   mTouchX = x;\n   mTouchY = y;\n}\n\nvoid\nInputDriver::settingsChanged()\n{\n   auto settings = mSettingsStorage->get();\n   std::unique_lock<std::mutex> lock { mConfigurationMutex };\n   mConfiguration = settings->input;\n\n   // Map to correct SDL joystick\n   for (auto &controller : mConfiguration.controllers) {\n      for (auto &input : controller.inputs) {\n         if (input.source == InputConfiguration::Input::JoystickAxis ||\n             input.source == InputConfiguration::Input::JoystickButton ||\n             input.source == InputConfiguration::Input::JoystickHat) {\n\n            for (auto &connected : mJoysticks) {\n               if (input.joystickInstanceId != -1) {\n                  if (connected.instanceId == input.joystickInstanceId) {\n                     input.joystick = connected.joystick;\n                     input.joystickDuplicateId = connected.duplicateId;\n                  }\n               } else if (connected.guid == input.joystickGuid &&\n                          connected.duplicateId == input.joystickDuplicateId) {\n                  input.joystick = connected.joystick;\n                  input.joystickInstanceId = connected.instanceId;\n               }\n            }\n         }\n      }\n   }\n\n   // TODO: Should probably allow a way to remember what VPAD / WPAD index is\n   // assigned to each controller.\n\n   // Update VPAD / WPAD assignment\n   auto vpad = 0;\n   auto wpad = 0;\n\n   for (auto &controller : mConfiguration.controllers) {\n      if (controller.type == ControllerType::Gamepad) {\n         if (vpad < mConfiguration.vpad.size()) {\n            mConfiguration.vpad[vpad++] = &controller;\n         }\n      } else if (controller.type != ControllerType::Invalid) {\n         if (wpad < mConfiguration.wpad.size()) {\n            mConfiguration.wpad[wpad++] = &controller;\n         }\n      }\n   }\n}\n\nvoid\nInputDriver::sampleVpadController(int channel, decaf::input::vpad::Status &status)\n{\n   std::unique_lock<std::mutex> lock { mConfigurationMutex };\n   if (channel < 0 || channel > mConfiguration.vpad.size()) {\n      status.connected = false;\n      return;\n   }\n\n   auto getButtonState = [&](InputConfiguration::Controller *controller, ButtonType type) -> uint32_t\n   {\n      auto &input = controller->inputs[static_cast<size_t>(type)];\n      if (input.source == InputConfiguration::Input::KeyboardKey) {\n         if (input.id < 0) {\n            return 0;\n         }\n\n         return !!mKeyboardState[input.id & 0xFF];\n      } else if (input.source == InputConfiguration::Input::JoystickButton) {\n         if (!input.joystick) {\n            return 0;\n         }\n\n         return !!SDL_JoystickGetButton(input.joystick, input.id);\n      } else if (input.source == InputConfiguration::Input::JoystickHat) {\n         if (!input.joystick) {\n            return 0;\n         }\n\n         return !!(SDL_JoystickGetHat(input.joystick, input.id) & input.hatValue);\n      } else if (input.source == InputConfiguration::Input::JoystickAxis) {\n         if (!input.joystick) {\n            return 0;\n         }\n\n         // Button is considered pressed if axis value is >= 0.5f or <= -0.5f\n         auto value = SDL_JoystickGetAxis(input.joystick, input.id);\n         if (input.invert && value >= SDL_JOYSTICK_AXIS_MAX / 2) {\n            return 1;\n         } else if (!input.invert && value < SDL_JOYSTICK_AXIS_MIN / 2) {\n            return 1;\n         } else {\n            return 0;\n         }\n      }\n\n      return 0;\n   };\n\n   auto getAxisValue = [&](InputConfiguration::Controller *controller, ButtonType type) -> float\n   {\n      auto &input = controller->inputs[static_cast<size_t>(type)];\n      if (input.source == InputConfiguration::Input::KeyboardKey) {\n         if (input.id < 0) {\n            return 0.0f;\n         }\n\n         return mKeyboardState[input.id & 0xFF] ? 1.0f : 0.0f;\n      } else if (input.source == InputConfiguration::Input::JoystickButton) {\n         if (!input.joystick) {\n            return 0.0f;\n         }\n\n         return SDL_JoystickGetButton(input.joystick, input.id) ? 1.0f : 0.0f;\n      } else if (input.source == InputConfiguration::Input::JoystickHat) {\n         if (!input.joystick) {\n            return 0.0f;\n         }\n\n         return (SDL_JoystickGetHat(input.joystick, input.id) & input.hatValue) ? 1.0f : 0.0f;\n      } else if (input.source == InputConfiguration::Input::JoystickAxis) {\n         if (!input.joystick) {\n            return 0.0f;\n         }\n\n         auto value = SDL_JoystickGetAxis(input.joystick, input.id);\n         if (!input.invert && value > 0) {\n            return static_cast<float>(value) / static_cast<float>(SDL_JOYSTICK_AXIS_MAX);\n         } else if (input.invert && value < 0) {\n            return static_cast<float>(value) / static_cast<float>(SDL_JOYSTICK_AXIS_MIN);\n         } else {\n            return 0.0f;\n         }\n      }\n\n      return 0.0f;\n   };\n\n   auto controller = mConfiguration.vpad[channel];\n\n   if (!controller) {\n      status.connected = false;\n      return;\n   }\n\n   status.connected = true;\n   status.buttons.sync = getButtonState(controller, ButtonType::Sync);\n   status.buttons.home = getButtonState(controller, ButtonType::Home);\n   status.buttons.minus = getButtonState(controller, ButtonType::Minus);\n   status.buttons.plus = getButtonState(controller, ButtonType::Plus);\n   status.buttons.r = getButtonState(controller, ButtonType::R);\n   status.buttons.l = getButtonState(controller, ButtonType::L);\n   status.buttons.zr = getButtonState(controller, ButtonType::ZR);\n   status.buttons.zl = getButtonState(controller, ButtonType::ZL);\n   status.buttons.down = getButtonState(controller, ButtonType::DpadDown);\n   status.buttons.up = getButtonState(controller, ButtonType::DpadUp);\n   status.buttons.right = getButtonState(controller, ButtonType::DpadRight);\n   status.buttons.left = getButtonState(controller, ButtonType::DpadLeft);\n   status.buttons.x = getButtonState(controller, ButtonType::X);\n   status.buttons.y = getButtonState(controller, ButtonType::Y);\n   status.buttons.a = getButtonState(controller, ButtonType::A);\n   status.buttons.b = getButtonState(controller, ButtonType::B);\n   status.buttons.stickR = getButtonState(controller, ButtonType::RightStickDown);\n   status.buttons.stickL = getButtonState(controller, ButtonType::LeftStickDown);\n\n   status.leftStickX = getAxisValue(controller, ButtonType::LeftStickRight);\n   status.leftStickX -= getAxisValue(controller, ButtonType::LeftStickLeft);\n   status.leftStickY = getAxisValue(controller, ButtonType::LeftStickUp);\n   status.leftStickY -= getAxisValue(controller, ButtonType::LeftStickDown);\n\n   status.rightStickX = getAxisValue(controller, ButtonType::RightStickRight);\n   status.rightStickX -= getAxisValue(controller, ButtonType::RightStickLeft);\n   status.rightStickY = getAxisValue(controller, ButtonType::RightStickUp);\n   status.rightStickY -= getAxisValue(controller, ButtonType::RightStickDown);\n\n   status.touch.down = mTouchDown;\n   status.touch.x = mTouchX;\n   status.touch.y = mTouchY;\n}\n\nvoid\nInputDriver::sampleWpadController(int channel, decaf::input::wpad::Status &status)\n{\n\n}\n"
  },
  {
    "path": "src/decaf-qt/src/inputdriver.h",
    "content": "#pragma once\n#include \"settings.h\"\n\n#include <QObject>\n#include <QVector>\n\n#include <array>\n#include <libdecaf/decaf_input.h>\n#include <mutex>\n\nclass QKeyEvent;\nclass SettingsStorage;\n\nstruct ConnectedJoystick\n{\n   SDL_Joystick *joystick = nullptr;\n   SDL_JoystickGUID guid;\n   SDL_JoystickID instanceId;\n\n   // This is for when we have multiple controllers with the same guid\n   int duplicateId = 0;\n};\n\nstatic inline bool operator ==(const SDL_JoystickGUID &lhs, const SDL_JoystickGUID &rhs)\n{\n   return memcmp(&lhs, &rhs, sizeof(SDL_JoystickGUID)) == 0;\n}\n\nclass InputDriver : public QObject, public decaf::InputDriver\n{\n   Q_OBJECT\n\npublic:\n   InputDriver(SettingsStorage *settingsStorage, QObject *parent = nullptr);\n   ~InputDriver();\n\n   void enableButtonEvents() { mButtonEventsEnabled = true; }\n   void disableButtonEvents() { mButtonEventsEnabled = false; }\n\n   void keyPressEvent(int key);\n   void keyReleaseEvent(int key);\n   void gamepadTouchEvent(bool down, float x, float y);\n\nsignals:\n   void joystickConnected(SDL_JoystickID id, SDL_JoystickGUID guid, const char *name);\n   void joystickDisconnected(SDL_JoystickID id, SDL_JoystickGUID guid);\n   void joystickButtonDown(SDL_JoystickID id, SDL_JoystickGUID guid, int button);\n   void joystickAxisMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int axis, float value);\n   void joystickHatMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int hat, int value);\n   void configurationUpdated();\n\nprivate slots:\n   void update();\n   void settingsChanged();\n\nprivate:\n   void sampleVpadController(int channel, decaf::input::vpad::Status &status) override;\n   void sampleWpadController(int channel, decaf::input::wpad::Status &status) override;\n\nprivate:\n   SettingsStorage *mSettingsStorage;\n\n   bool mButtonEventsEnabled = false;\n   std::mutex mConfigurationMutex;\n   InputConfiguration mConfiguration;\n   std::vector<ConnectedJoystick> mJoysticks;\n\n   bool mTouchDown = false;\n   float mTouchX = 0.0f;\n   float mTouchY = 0.0f;\n   std::array<bool, 0x100> mKeyboardState;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/inputeventfilter.h",
    "content": "#pragma once\n#include <QObject>\n#include <QEvent>\n#include <QKeyEvent>\n\nclass InputEventFilter : public QObject\n{\n   Q_OBJECT\n\npublic:\n   InputEventFilter(QObject *parent) :\n      QObject(parent)\n   {\n   }\n\n   bool enabled()\n   {\n      return mEnabled;\n   }\n\n   void enable()\n   {\n      mEnabled = true;\n   }\n\n   void disable()\n   {\n      mEnabled = false;\n   }\n\nsignals:\n   void caughtKeyPress(int key);\n\nprotected:\n   bool eventFilter(QObject *obj, QEvent *event)\n   {\n      if (mEnabled) {\n         if (event->type() == QEvent::KeyPress) {\n            QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);\n            emit caughtKeyPress(keyEvent->key());\n            return true;\n         }\n      }\n\n      return QObject::eventFilter(obj, event);\n   }\n\nprivate:\n   bool mEnabled = false;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/main.cpp",
    "content": "#include \"mainwindow.h\"\n#include \"decafinterface.h\"\n#include \"inputdriver.h\"\n#include \"sounddriver.h\"\n\n#include <libconfig/config_toml.h>\n#include <libdecaf/decaf.h>\n#include <libdecaf/decaf_input.h>\n#include <libdecaf/decaf_nullinputdriver.h>\n#include <libdecaf/decaf_log.h>\n\n#include <SDL2/SDL.h>\n\n#include <QApplication>\n\n#include <common/platform_debug.h>\n\nint main(int argc, char *argv[])\n{\n   QCoreApplication::setOrganizationName(\"decaf-emu\");\n   QCoreApplication::setOrganizationDomain(\"decaf-emu.com\");\n   QCoreApplication::setApplicationName(\"decaf-qt\");\n\n   auto app = QApplication { argc, argv };\n\n   decaf::createConfigDirectory();\n   auto settings = SettingsStorage { decaf::makeConfigPath(\"config.toml\") };\n\n   auto inputDriver = InputDriver { &settings };\n   auto soundDriver = SoundDriver { &settings };\n   auto decafInterface = DecafInterface { &settings, &inputDriver, &soundDriver };\n\n   auto mainWindow = MainWindow { &settings, &decafInterface, &inputDriver };\n   mainWindow.show();\n\n   return app.exec();\n}\n"
  },
  {
    "path": "src/decaf-qt/src/mainwindow.cpp",
    "content": "#include \"mainwindow.h\"\n\n#include \"aboutdialog.h\"\n#include \"debugger/debuggerwindow.h\"\n#include \"decafinterface.h\"\n#include \"erreuladriver.h\"\n#include \"renderwidget.h\"\n#include \"softwarekeyboarddriver.h\"\n#include \"settings/settingsdialog.h\"\n#include \"titlelistwidget.h\"\n\n#include <QAbstractButton>\n#include <QCloseEvent>\n#include <QMessageBox>\n#include <QFileDialog>\n#include <QFileInfo>\n#include <QInputDialog>\n#include <QSettings>\n#include <QShortcut>\n#include <QTimer>\n\n#include <libdecaf/decaf.h>\n\nMainWindow::MainWindow(SettingsStorage *settingsStorage,\n                       DecafInterface *decafInterface,\n                       InputDriver *inputDriver,\n                       QWidget *parent) :\n   QMainWindow(parent),\n   mSettingsStorage(settingsStorage),\n   mDecafInterface(decafInterface),\n   mInputDriver(inputDriver),\n   mErrEulaDriver(new ErrEulaDriver { this }),\n   mErrEulaDialog(new QMessageBox { this }),\n   mSoftwareKeyboardDriver(new SoftwareKeyboardDriver { this }),\n   mSoftwareKeyboardInputDialog(new QInputDialog { this })\n{\n   // Setup UI\n   mUi.setupUi(this);\n\n   mTitleListWiget = new TitleListWidget { mSettingsStorage, this };\n   setCentralWidget(mTitleListWiget);\n   connect(mTitleListWiget, &TitleListWidget::launchTitle,\n           this, &MainWindow::loadFile);\n   connect(mTitleListWiget, &TitleListWidget::statusMessage,\n           this, &MainWindow::showStatusMessage);\n\n   connect(decafInterface, &DecafInterface::titleLoaded,\n           this, &MainWindow::titleLoaded);\n   connect(decafInterface, &DecafInterface::debugInterrupt,\n           this, &MainWindow::debugInterrupt);\n\n   // Setup status bar\n   mStatusTimer = new QTimer(this);\n   mUi.statusBar->addPermanentWidget(mStatusFrameRate = new QLabel());\n   mUi.statusBar->addPermanentWidget(mStatusFrameTime = new QLabel());\n   connect(mStatusTimer, SIGNAL(timeout()), this, SLOT(updateStatusBar()));\n\n   // Setup settings\n   connect(mSettingsStorage, &SettingsStorage::settingsChanged,\n           this, &MainWindow::settingsChanged);\n   settingsChanged();\n\n   QShortcut *shortcut = new QShortcut(QKeySequence(\"F11\"), this);\n   connect(shortcut, &QShortcut::activated,\n           this, &MainWindow::toggleFullScreen);\n\n   connect(mErrEulaDriver, &ErrEulaDriver::openWithErrorCode,\n           this, &MainWindow::erreulaOpenWithErrorCode);\n   connect(mErrEulaDriver, &ErrEulaDriver::openWithMessage,\n           this, &MainWindow::erreulaOpenWithMessage);\n   connect(mErrEulaDriver, &ErrEulaDriver::close,\n           this, &MainWindow::erreulaClose);\n   decaf::setErrEulaDriver(mErrEulaDriver);\n\n   connect(mSoftwareKeyboardDriver, &SoftwareKeyboardDriver::open,\n           this, &MainWindow::softwareKeyboardOpen);\n   connect(mSoftwareKeyboardDriver, &SoftwareKeyboardDriver::close,\n           this, &MainWindow::softwareKeyboardClose);\n   connect(mSoftwareKeyboardDriver, &SoftwareKeyboardDriver::inputStringChanged,\n           this, &MainWindow::softwareKeyboardInputStringChanged);\n   connect(mSoftwareKeyboardInputDialog, &QInputDialog::finished,\n           this, &MainWindow::softwareKeyboardInputFinished);\n   decaf::setSoftwareKeyboardDriver(mSoftwareKeyboardDriver);\n\n   // Create recent file actions\n   mRecentFilesSeparator = mUi.menuFile->insertSeparator(mUi.actionExit);\n   mRecentFilesSeparator->setVisible(false);\n\n   for (auto i = 0u; i < mRecentFileActions.size(); ++i) {\n      mRecentFileActions[i] = new QAction(this);\n      mRecentFileActions[i]->setVisible(false);\n      QObject::connect(mRecentFileActions[i], &QAction::triggered,\n                       this, &MainWindow::openRecentFile);\n      mUi.menuFile->insertAction(mRecentFilesSeparator, mRecentFileActions[i]);\n   }\n\n   updateRecentFileActions();\n}\n\nvoid\nMainWindow::softwareKeyboardOpen(QString defaultText)\n{\n   mSoftwareKeyboardInputDialog->setLabelText(\"Software Keyboard Input\");\n   mSoftwareKeyboardInputDialog->show();\n}\n\nvoid\nMainWindow::softwareKeyboardClose()\n{\n   mSoftwareKeyboardInputDialog->close();\n}\n\nvoid\nMainWindow::softwareKeyboardInputStringChanged(QString text)\n{\n   mSoftwareKeyboardInputDialog->setTextValue(text);\n}\n\nvoid\nMainWindow::softwareKeyboardInputFinished(int result)\n{\n   if (result == QDialog::Accepted) {\n      mSoftwareKeyboardDriver->acceptInput(mSoftwareKeyboardInputDialog->textValue());\n   } else {\n      mSoftwareKeyboardDriver->rejectInput();\n   }\n}\n\nvoid\nMainWindow::erreulaOpenWithErrorCode(int32_t errorCode)\n{\n   mErrEulaDialog->setWindowTitle(\"ErrEula\");\n   mErrEulaDialog->setIcon(QMessageBox::Critical);\n   mErrEulaDialog->setText(QString(\"Error code: %1\").arg(errorCode));\n\n   mErrEulaButton1 = nullptr;\n   mErrEulaButton2 = nullptr;\n   for (auto button : mErrEulaDialog->buttons()) {\n      mErrEulaDialog->removeButton(button);\n      delete button;\n   }\n\n   mErrEulaDialog->setStandardButtons(QMessageBox::Ok);\n   mErrEulaDialog->show();\n}\n\nvoid\nMainWindow::erreulaOpenWithMessage(QString message,\n                                   QString button1,\n                                   QString button2)\n{\n   mErrEulaDialog->setWindowTitle(\"ErrEula\");\n   mErrEulaDialog->setIcon(QMessageBox::Critical);\n   mErrEulaDialog->setText(QString(\"Error message: %1\").arg(message));\n\n   mErrEulaButton1 = nullptr;\n   mErrEulaButton2 = nullptr;\n   for (auto button : mErrEulaDialog->buttons()) {\n      mErrEulaDialog->removeButton(button);\n      delete button;\n   }\n\n   if (button2.isEmpty()) {\n      if (button1.isEmpty()) {\n         mErrEulaDialog->setStandardButtons(QMessageBox::Ok);\n      } else {\n         mErrEulaButton1 = reinterpret_cast<QAbstractButton *>(\n            mErrEulaDialog->addButton(button1, QMessageBox::AcceptRole));\n      }\n   } else {\n      mErrEulaButton1 = reinterpret_cast<QAbstractButton *>(\n         mErrEulaDialog->addButton(button1, QMessageBox::AcceptRole));\n      mErrEulaButton2 = reinterpret_cast<QAbstractButton *>(\n         mErrEulaDialog->addButton(button2, QMessageBox::RejectRole));\n   }\n\n   mErrEulaDialog->show();\n}\n\nvoid\nMainWindow::erreulaClose()\n{\n   mErrEulaDialog->close();\n}\n\nvoid\nMainWindow::erreulaButtonClicked(QAbstractButton *button)\n{\n   if (button == mErrEulaButton1) {\n      mErrEulaDriver->button1Clicked();\n   } else if (button == mErrEulaButton2) {\n      mErrEulaDriver->button2Clicked();\n   } else {\n      mErrEulaDriver->buttonClicked();\n   }\n}\n\nvoid\nMainWindow::settingsChanged()\n{\n   auto settings = mSettingsStorage->get();\n   if (settings->gpu.display.viewMode == gpu::DisplaySettings::Split) {\n      mUi.actionViewSplit->setChecked(true);\n   } else if (settings->gpu.display.viewMode == gpu::DisplaySettings::TV) {\n      mUi.actionViewTV->setChecked(true);\n   } else if (settings->gpu.display.viewMode == gpu::DisplaySettings::Gamepad1) {\n      mUi.actionViewGamepad1->setChecked(true);\n   } else if (settings->gpu.display.viewMode == gpu::DisplaySettings::Gamepad2) {\n      mUi.actionViewGamepad2->setChecked(true);\n   }\n\n   if (settings->ui.titleListMode == UiSettings::TitleList) {\n      mUi.actionViewTitleList->setChecked(true);\n   } else if(settings->ui.titleListMode == UiSettings::TitleGrid) {\n      mUi.actionViewTitleGrid->setChecked(true);\n   }\n}\n\nvoid\nMainWindow::titleLoaded(quint64 id, const QString &name)\n{\n   setWindowTitle(QString(\"decaf-qt - %1\").arg(name));\n}\n\nvoid\nMainWindow::debugInterrupt()\n{\n   openDebugger();\n}\n\nbool\nMainWindow::loadFile(QString path)\n{\n   // Ensure file exists before trying to load\n   if (!QFileInfo { path }.exists()) {\n      QMessageBox::warning(this, \"File not found\",\n                           \"Could not find selected file: \" + path);\n\n      // Delete path from recent files\n      auto settings = QSettings {};\n      auto files = settings.value(\"recentFileList\").toStringList();\n      files.removeAll(path);\n      settings.setValue(\"recentFileList\", files);\n      updateRecentFileActions();\n      return false;\n   }\n\n   // You only get one chance to run a game out here buddy.\n   mUi.actionOpen->setDisabled(true);\n   for (auto i = 0u; i < mRecentFileActions.size(); ++i) {\n      mRecentFileActions[i]->setDisabled(true);\n   }\n\n   // Change main widget to render widget\n   mTitleListWiget->deleteLater();\n   mTitleListWiget = nullptr;\n\n   mRenderWidget = new RenderWidget { mInputDriver, this };\n   setCentralWidget(mRenderWidget);\n\n   // Update status bar\n   mUi.statusBar->clearMessage();\n   mStatusTimer->start(500);\n\n   // Start the game\n   mDecafInterface->startLogging();\n   mRenderWidget->startGraphicsDriver();\n   mDecafInterface->startGame(path);\n\n   // Update recent files list\n   {\n      auto settings = QSettings { };\n      auto files = settings.value(\"recentFileList\").toStringList();\n      files.removeAll(path);\n      files.prepend(path);\n      while (files.size() > MaxRecentFiles) {\n         files.removeLast();\n      }\n\n      settings.setValue(\"recentFileList\", files);\n   }\n\n   updateRecentFileActions();\n   return true;\n}\n\nvoid\nMainWindow::openFile()\n{\n   auto fileName =\n      QFileDialog::getOpenFileName(this,\n                                   tr(\"Open Application\"), \"\",\n                                   tr(\"RPX Files (*.rpx);;cos.xml (cos.xml);;\"));\n   if (!fileName.isEmpty()) {\n      loadFile(fileName);\n   }\n}\n\nvoid\nMainWindow::openRecentFile()\n{\n   auto action = qobject_cast<QAction *>(sender());\n   if (action) {\n      loadFile(action->data().toString());\n   }\n}\n\nvoid\nMainWindow::updateRecentFileActions()\n{\n   auto settings = QSettings { };\n   auto files = settings.value(\"recentFileList\").toStringList();\n   auto numRecentFiles = qMin(files.size(), MaxRecentFiles);\n\n   for (int i = 0; i < numRecentFiles; ++i) {\n      auto text = QString { \"&%1 %2\" }.arg(i + 1).arg(QFileInfo { files[i] }.fileName());\n      mRecentFileActions[i]->setText(text);\n      mRecentFileActions[i]->setData(files[i]);\n      mRecentFileActions[i]->setVisible(true);\n   }\n\n   for (int j = numRecentFiles; j < MaxRecentFiles; ++j) {\n      mRecentFileActions[j]->setVisible(false);\n   }\n\n   mRecentFilesSeparator->setVisible(numRecentFiles > 0);\n}\n\nvoid\nMainWindow::exit()\n{\n   close();\n}\n\nvoid\nMainWindow::setViewModeSplit()\n{\n   auto settings = *mSettingsStorage->get();\n   settings.gpu.display.viewMode = gpu::DisplaySettings::Split;\n   mSettingsStorage->set(settings);\n}\n\nvoid\nMainWindow::setViewModeTV()\n{\n   auto settings = *mSettingsStorage->get();\n   settings.gpu.display.viewMode = gpu::DisplaySettings::TV;\n   mSettingsStorage->set(settings);\n}\n\nvoid\nMainWindow::setViewModeGamepad1()\n{\n   auto settings = *mSettingsStorage->get();\n   settings.gpu.display.viewMode = gpu::DisplaySettings::Gamepad1;\n   mSettingsStorage->set(settings);\n}\n\nvoid\nMainWindow::setTitleListModeList()\n{\n   auto settings = *mSettingsStorage->get();\n   settings.ui.titleListMode= UiSettings::TitleList;\n   mSettingsStorage->set(settings);\n}\n\nvoid\nMainWindow::setTitleListModeGrid()\n{\n   auto settings = *mSettingsStorage->get();\n   settings.ui.titleListMode = UiSettings::TitleGrid;\n   mSettingsStorage->set(settings);\n}\n\nvoid\nMainWindow::toggleFullScreen()\n{\n   if (mUi.menuBar->isVisible()) {\n      mUi.menuBar->hide();\n      mUi.statusBar->hide();\n      showFullScreen();\n   } else {\n      mUi.menuBar->show();\n      mUi.statusBar->show();\n      showNormal();\n   }\n}\n\nvoid\nMainWindow::openDebugger()\n{\n   if (mDebuggerWindow) {\n      mDebuggerWindow->show();\n      return;\n   }\n\n   mDebuggerWindow = new DebuggerWindow { };\n   mDebuggerWindow->setAttribute(Qt::WA_DeleteOnClose);\n   mDebuggerWindow->show();\n   connect(mDebuggerWindow, &DebuggerWindow::destroyed, [this]() {\n      mDebuggerWindow = nullptr;\n   });\n}\n\nvoid\nMainWindow::openSettings()\n{\n   SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Default, this };\n   dialog.exec();\n}\n\nvoid\nMainWindow::openDebugSettings()\n{\n   SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Debug, this };\n   dialog.exec();\n}\n\nvoid\nMainWindow::openInputSettings()\n{\n   SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Input, this };\n   dialog.exec();\n}\n\nvoid\nMainWindow::openLoggingSettings()\n{\n   SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Logging, this };\n   dialog.exec();\n}\n\nvoid\nMainWindow::openSystemSettings()\n{\n   SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::System, this };\n   dialog.exec();\n}\n\nvoid\nMainWindow::openAudioSettings()\n{\n   SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Audio, this };\n   dialog.exec();\n}\n\nvoid\nMainWindow::openDisplaySettings()\n{\n   SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Display, this };\n   dialog.exec();\n}\n\nvoid\nMainWindow::openContentSettings()\n{\n   SettingsDialog dialog { mSettingsStorage, mInputDriver, SettingsTab::Content, this };\n   dialog.exec();\n}\n\nvoid\nMainWindow::openAboutDialog()\n{\n   AboutDialog dialog { this };\n   dialog.exec();\n}\n\nvoid\nMainWindow::closeEvent(QCloseEvent *event)\n{\n#if 0\n   QMessageBox::StandardButton resBtn = QMessageBox::question(this, \"Are you sure?\",\n                                                              tr(\"A game is running, are you sure you want to exit?\\n\"),\n                                                              QMessageBox::Cancel | QMessageBox::No | QMessageBox::Yes,\n                                                              QMessageBox::Yes);\n   if (resBtn != QMessageBox::Yes) {\n      event->ignore();\n      return;\n   }\n#endif\n   if (mDebuggerWindow) {\n      mDebuggerWindow->close();\n   }\n\n   mDecafInterface->shutdown();\n   event->accept();\n}\n\nvoid\nMainWindow::updateStatusBar()\n{\n   if (auto gpuDriver = decaf::getGraphicsDriver()) {\n      auto debugInfo = gpuDriver->getDebugInfo();\n      mStatusFrameRate->setText(\n         QString(\"FPS: %1\")\n         .arg(debugInfo->averageFps, 2, 'f', 0));\n\n      mStatusFrameTime->setText(\n         QString(\"Frametime: %1ms\")\n         .arg(debugInfo->averageFrameTimeMS, 7, 'f', 3));\n   } else {\n      mStatusFrameRate->setText(\"\");\n      mStatusFrameTime->setText(\"\");\n   }\n}\n\nvoid\nMainWindow::showStatusMessage(QString message, int timeout)\n{\n   mUi.statusBar->showMessage(message, timeout);\n}\n"
  },
  {
    "path": "src/decaf-qt/src/mainwindow.h",
    "content": "#pragma once\n#include \"ui_mainwindow.h\"\n\n#include <array>\n#include <QMainWindow>\n\nclass DebuggerWindow;\nclass DecafInterface;\nclass ErrEulaDriver;\nclass InputDriver;\nclass RenderWidget;\nclass SettingsStorage;\nclass SoftwareKeyboardDriver;\nclass TitleListWidget;\n\nclass QAbstractButton;\nclass QAction;\nclass QInputDialog;\nclass QLabel;\nclass QMessageBox;\nclass QTimer;\n\nclass MainWindow : public QMainWindow\n{\n   Q_OBJECT\n\n   static constexpr int MaxRecentFiles = 5;\n\npublic:\n   explicit MainWindow(SettingsStorage *settingsStorage,\n                       DecafInterface *decafInterface,\n                       InputDriver *inputDriver,\n                       QWidget* parent = nullptr);\n\nprivate slots:\n   void openFile();\n   void exit();\n\n   void setViewModeGamepad1();\n   void setViewModeSplit();\n   void setViewModeTV();\n\n   void setTitleListModeList();\n   void setTitleListModeGrid();\n\n   void toggleFullScreen();\n\n   void openDebugger();\n\n   void openSettings();\n   void openDebugSettings();\n   void openInputSettings();\n   void openLoggingSettings();\n   void openRecentFile();\n   void openSystemSettings();\n   void openAudioSettings();\n   void openDisplaySettings();\n   void openContentSettings();\n\n   void openAboutDialog();\n\n   void debugInterrupt();\n   void titleLoaded(quint64 id, const QString &name);\n\n   void settingsChanged();\n\n   void softwareKeyboardOpen(QString defaultText);\n   void softwareKeyboardClose();\n   void softwareKeyboardInputStringChanged(QString text);\n   void softwareKeyboardInputFinished(int result);\n\n   void erreulaOpenWithErrorCode(int32_t errorCode);\n   void erreulaOpenWithMessage(QString message, QString button1, QString button2);\n   void erreulaClose();\n   void erreulaButtonClicked(QAbstractButton *button);\n\n   void updateStatusBar();\n   void showStatusMessage(QString message, int timeout);\n\n   bool loadFile(QString path);\n\nprotected:\n   void closeEvent(QCloseEvent *event) override;\n   void updateRecentFileActions();\n\nprivate:\n   SettingsStorage *mSettingsStorage = nullptr;\n   RenderWidget *mRenderWidget = nullptr;\n   TitleListWidget *mTitleListWiget = nullptr;\n   DecafInterface *mDecafInterface = nullptr;\n   InputDriver *mInputDriver = nullptr;\n   ErrEulaDriver *mErrEulaDriver = nullptr;\n   QMessageBox *mErrEulaDialog = nullptr;\n   QAbstractButton *mErrEulaButton1 = nullptr;\n   QAbstractButton *mErrEulaButton2 = nullptr;\n   SoftwareKeyboardDriver *mSoftwareKeyboardDriver = nullptr;\n   QInputDialog *mSoftwareKeyboardInputDialog = nullptr;\n   DebuggerWindow *mDebuggerWindow = nullptr;\n\n   Ui::MainWindow mUi;\n   QTimer *mStatusTimer = nullptr;\n   QLabel *mStatusFrameRate = nullptr;\n   QLabel *mStatusFrameTime = nullptr;\n\n   QAction *mRecentFilesSeparator = nullptr;\n   std::array<QAction *, MaxRecentFiles> mRecentFileActions;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/renderwidget.cpp",
    "content": "#include \"inputdriver.h\"\n#include \"renderwidget.h\"\n\n#include <QApplication>\n#include <QEvent>\n#include <QResizeEvent>\n#include <QMouseEvent>\n#include <QKeyEvent>\n#include <QWidget>\n#include <QWindow>\n#include <thread>\n\n#include <libdecaf/decaf_graphics.h>\n#include <libgpu/gpu_graphicsdriver.h>\n#include <libgpu/gpu7_displaylayout.h>\n\n#include <qpa/qplatformnativeinterface.h>\n\nRenderWidget::RenderWidget(InputDriver *inputDriver, QWidget *parent) :\n   mInputDriver(inputDriver),\n   QWidget(parent)\n{\n   setAutoFillBackground(false);\n   setAttribute(Qt::WA_PaintOnScreen);\n   setAttribute(Qt::WA_OpaquePaintEvent);\n   setAttribute(Qt::WA_NoSystemBackground);\n   setAttribute(Qt::WA_NativeWindow);\n   setFocusPolicy(Qt::StrongFocus);\n   setFocus();\n}\n\nRenderWidget::~RenderWidget()\n{\n   if (mGraphicsDriver) {\n      mRenderThread.join();\n      delete mGraphicsDriver;\n   }\n}\n\nvoid\nRenderWidget::startGraphicsDriver()\n{\n   auto wsi = gpu::WindowSystemInfo { };\n   auto platformName = QGuiApplication::platformName();\n   if (platformName == QStringLiteral(\"windows\")) {\n      wsi.type = gpu::WindowSystemType::Windows;\n   } else if (platformName == QStringLiteral(\"cocoa\")) {\n      wsi.type = gpu::WindowSystemType::Cocoa;\n   } else if (platformName == QStringLiteral(\"xcb\")) {\n      wsi.type = gpu::WindowSystemType::X11;\n   } else if (platformName == QStringLiteral(\"wayland\")) {\n      wsi.type = gpu::WindowSystemType::Wayland;\n   }\n\n   auto window = windowHandle();\n#if defined(WIN32) || defined(__APPLE__)\n   wsi.renderSurface = reinterpret_cast<void *>(window->winId());\n#else\n   auto pni = QGuiApplication::platformNativeInterface();\n   wsi.displayConnection = pni->nativeResourceForWindow(\"display\", window);\n   if (wsi.type == gpu::WindowSystemType::Wayland) {\n      wsi.renderSurface = pni->nativeResourceForWindow(\"surface\", window);\n   } else {\n      wsi.renderSurface = reinterpret_cast<void *>(window->winId());\n   }\n#endif\n   wsi.renderSurfaceScale = window->devicePixelRatio();\n\n   mGraphicsDriver = gpu::createGraphicsDriver();\n   mGraphicsDriver->setWindowSystemInfo(wsi);\n\n   mRenderThread = std::thread { [this]() { mGraphicsDriver->run(); } };\n   decaf::setGraphicsDriver(mGraphicsDriver);\n}\n\nQPaintEngine *\nRenderWidget::paintEngine() const\n{\n   return nullptr;\n}\n\nbool\nRenderWidget::event(QEvent *event)\n{\n   switch (event->type()) {\n   case QEvent::Paint:\n      return false;\n   case QEvent::WinIdChange:\n      if (mGraphicsDriver) {\n         mGraphicsDriver->windowHandleChanged(reinterpret_cast<void *>(winId()));\n      }\n      break;\n   case QEvent::KeyPress:\n   {\n      auto keyEvent = static_cast<QKeyEvent *>(event);\n      mInputDriver->keyPressEvent(keyEvent->key());\n      return true;\n   }\n   case QEvent::KeyRelease:\n   {\n      auto keyEvent = static_cast<QKeyEvent *>(event);\n      mInputDriver->keyReleaseEvent(keyEvent->key());\n      return true;\n   }\n   case QEvent::MouseMove:\n   case QEvent::MouseButtonPress:\n   case QEvent::MouseButtonRelease:\n   {\n      auto mouseEvent = static_cast<QMouseEvent *>(event);\n#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)\n      auto mousePosition = mouseEvent->position();\n      auto mouseX = mousePosition.x();\n      auto mouseY = mousePosition.y();\n#else\n      auto mouseX = static_cast<float>(mouseEvent->x());\n      auto mouseY = static_cast<float>(mouseEvent->y());\n#endif\n      auto layout = gpu7::getDisplayLayout(static_cast<float>(width()),\n                                           static_cast<float>(height()));\n      auto touchEvent = gpu7::translateDisplayTouch(layout, mouseX, mouseY);\n\n      if (touchEvent.screen != gpu7::DisplayTouchEvent::None) {\n         mInputDriver->gamepadTouchEvent(\n            !!(mouseEvent->buttons() & Qt::LeftButton),\n            touchEvent.x, touchEvent.y);\n      }\n      return true;\n   }\n   case QEvent::Resize:\n   {\n      auto resizeEvent = static_cast<QResizeEvent *>(event);\n      auto newSize = resizeEvent->size();\n      auto ratio = devicePixelRatio();\n      if (mGraphicsDriver) {\n         mGraphicsDriver->windowSizeChanged(newSize.width() * ratio,\n                                            newSize.height() * ratio);\n      }\n      break;\n   }\n   }\n\n   return QWidget::event(event);\n}\n"
  },
  {
    "path": "src/decaf-qt/src/renderwidget.h",
    "content": "#pragma once\n#include <QWidget>\n#include <thread>\n\nnamespace gpu\n{\nclass GraphicsDriver;\n}\n\nclass InputDriver;\n\nclass RenderWidget : public QWidget\n{\npublic:\n   RenderWidget(InputDriver *inputDriver, QWidget *parent = nullptr);\n   ~RenderWidget();\n\n   void startGraphicsDriver();\n\nprotected:\n   QPaintEngine *paintEngine() const override;\n   bool event(QEvent *event) override;\n\nprivate:\n   gpu::GraphicsDriver *mGraphicsDriver = nullptr;\n   InputDriver *mInputDriver = nullptr;\n   std::thread mRenderThread;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings/audiosettingswidget.cpp",
    "content": "#include \"audiosettingswidget.h\"\n\nAudioSettingsWidget::AudioSettingsWidget(QWidget *parent, Qt::WindowFlags f) :\n   SettingsWidget(parent, f)\n{\n   mUi.setupUi(this);\n}\n\nvoid\nAudioSettingsWidget::loadSettings(const Settings &settings)\n{\n   mUi.checkBoxPlaybackEnabled->setChecked(settings.sound.playbackEnabled);\n}\n\nvoid\nAudioSettingsWidget::saveSettings(Settings &settings)\n{\n   settings.sound.playbackEnabled = mUi.checkBoxPlaybackEnabled->isChecked();\n}\n"
  },
  {
    "path": "src/decaf-qt/src/settings/audiosettingswidget.h",
    "content": "#pragma once\n#include \"ui_audiosettings.h\"\n#include \"settingswidget.h\"\n\nclass AudioSettingsWidget : public SettingsWidget\n{\n   Q_OBJECT\n\npublic:\n   AudioSettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());\n   ~AudioSettingsWidget() = default;\n\n   void loadSettings(const Settings &settings) override;\n   void saveSettings(Settings &settings) override;\n\nprivate:\n   Ui::AudioSettingsWidget mUi;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings/colourlineedit.h",
    "content": "#pragma once\n#include <QColorDialog>\n#include <QEvent>\n#include <QLineEdit>\n\nclass ColourLineEdit : public QLineEdit\n{\npublic:\n   ColourLineEdit(QWidget *parent = nullptr) :\n      QLineEdit(parent)\n   {\n      installEventFilter(this);\n      setReadOnly(true);\n      setColour(QColor { Qt::black });\n   }\n\n   bool eventFilter(QObject*, QEvent* ev)\n   {\n      if (ev->type() == QEvent::MouseButtonPress) {\n         auto color = QColorDialog::getColor(mColour, this, tr(\"Select color\"));\n\n         if (color.isValid()) {\n            setColour(color);\n         }\n\n         return false;\n      }\n\n      return false;\n   }\n\n   void setColour(const QColor &colour)\n   {\n      mColour = colour;\n\n      auto palette = QPalette { };\n      palette.setColor(QPalette::Base, mColour);\n      palette.setColor(QPalette::Text, getTextColor());\n      setPalette(palette);\n      setText(mColour.name());\n   }\n\n   QColor getColour() const\n   {\n      return mColour;\n   }\n\nprivate:\n   QColor getTextColor()\n   {\n      double luminance = 1 - (0.299 * mColour.red() + 0.587 * mColour.green() + 0.114 * mColour.blue()) / 255;\n\n      if (luminance < 0.5) {\n         return QColor { Qt::black };\n      } else {\n         return QColor { Qt::white };\n      }\n   }\n\nprivate:\n   QColor mColour;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings/contentsettingswidget.cpp",
    "content": "#include \"contentsettingswidget.h\"\n\n#include <QFileDialog>\n\nContentSettingsWidget::ContentSettingsWidget(QWidget *parent,\n                                             Qt::WindowFlags f) :\n   SettingsWidget(parent, f)\n{\n   mUi.setupUi(this);\n}\n\nvoid\nContentSettingsWidget::loadSettings(const Settings &settings)\n{\n   mUi.lineEditMlcPath->setText(QString::fromStdString(settings.decaf.system.mlc_path));\n   mUi.lineEditSlcPath->setText(QString::fromStdString(settings.decaf.system.slc_path));\n   mUi.lineEditSdcardPath->setText(QString::fromStdString(settings.decaf.system.sdcard_path));\n   mUi.lineEditHfioPath->setText(QString::fromStdString(settings.decaf.system.hfio_path));\n   mUi.lineEditResourcesPath->setText(QString::fromStdString(settings.decaf.system.resources_path));\n   mUi.lineEditOtpPath->setText(QString::fromStdString(settings.decaf.system.otp_path));\n\n   mUi.listWidgetTitleDirectories->clear();\n   for (const auto &path : settings.decaf.system.title_directories) {\n      mUi.listWidgetTitleDirectories->addItem(QString::fromStdString(path));\n   }\n\n   mUi.lineEditMlcPath->setCursorPosition(0);\n   mUi.lineEditSlcPath->setCursorPosition(0);\n   mUi.lineEditSdcardPath->setCursorPosition(0);\n   mUi.lineEditHfioPath->setCursorPosition(0);\n   mUi.lineEditResourcesPath->setCursorPosition(0);\n   mUi.lineEditOtpPath->setCursorPosition(0);\n   mUi.listWidgetTitleDirectories->setCurrentRow(0);\n}\n\nvoid\nContentSettingsWidget::saveSettings(Settings &settings)\n{\n   settings.decaf.system.mlc_path = mUi.lineEditMlcPath->text().toStdString();\n   settings.decaf.system.slc_path = mUi.lineEditSlcPath->text().toStdString();\n   settings.decaf.system.sdcard_path = mUi.lineEditSdcardPath->text().toStdString();\n   settings.decaf.system.hfio_path = mUi.lineEditHfioPath->text().toStdString();\n   settings.decaf.system.resources_path = mUi.lineEditResourcesPath->text().toStdString();\n   settings.decaf.system.otp_path = mUi.lineEditOtpPath->text().toStdString();\n\n   settings.decaf.system.title_directories.clear();\n   for (auto i = 0; i < mUi.listWidgetTitleDirectories->count(); ++i) {\n      settings.decaf.system.title_directories.emplace_back(\n         mUi.listWidgetTitleDirectories->item(i)->text().toStdString());\n   }\n}\n\nvoid\nContentSettingsWidget::browseHfioPath()\n{\n   auto path = QFileDialog::getExistingDirectory(this, tr(\"Open Directory\"),\n                                                 mUi.lineEditHfioPath->text());\n   if (!path.isEmpty()) {\n      mUi.lineEditHfioPath->setText(path);\n   }\n}\n\nvoid\nContentSettingsWidget::browseMlcPath()\n{\n   auto path = QFileDialog::getExistingDirectory(this, tr(\"Open Directory\"),\n                                                 mUi.lineEditMlcPath->text());\n   if (!path.isEmpty()) {\n      mUi.lineEditMlcPath->setText(path);\n   }\n}\n\nvoid\nContentSettingsWidget::browseOtpPath()\n{\n\n   auto path = QFileDialog::getOpenFileName(this, tr(\"Open otp.bin\"),\n                                            mUi.lineEditOtpPath->text());\n   if (!path.isEmpty()) {\n      mUi.lineEditOtpPath->setText(path);\n   }\n}\n\nvoid\nContentSettingsWidget::browseResourcesPath()\n{\n   auto path = QFileDialog::getExistingDirectory(this, tr(\"Open Directory\"),\n                                                 mUi.lineEditResourcesPath->text());\n   if (!path.isEmpty()) {\n      mUi.lineEditResourcesPath->setText(path);\n   }\n}\n\nvoid\nContentSettingsWidget::browseSdcardPath()\n{\n   auto path = QFileDialog::getExistingDirectory(this, tr(\"Open Directory\"),\n                                                 mUi.lineEditSdcardPath->text());\n   if (!path.isEmpty()) {\n      mUi.lineEditSdcardPath->setText(path);\n   }\n}\n\nvoid\nContentSettingsWidget::browseSlcPath()\n{\n   auto path = QFileDialog::getExistingDirectory(this, tr(\"Open Directory\"),\n                                                 mUi.lineEditSlcPath->text());\n   if (!path.isEmpty()) {\n      mUi.lineEditSlcPath->setText(path);\n   }\n}\n\nvoid\nContentSettingsWidget::addTitleDirectory()\n{\n   auto path = QFileDialog::getExistingDirectory(this, tr(\"Open Directory\"));\n   if (!path.isEmpty()) {\n      mUi.listWidgetTitleDirectories->addItem(path);\n   }\n}\n\nvoid\nContentSettingsWidget::removeTitleDirectory()\n{\n   qDeleteAll(mUi.listWidgetTitleDirectories->selectedItems());\n}\n"
  },
  {
    "path": "src/decaf-qt/src/settings/contentsettingswidget.h",
    "content": "#pragma once\n#include \"ui_contentsettings.h\"\n#include \"settingswidget.h\"\n\nclass ContentSettingsWidget : public SettingsWidget\n{\n   Q_OBJECT\n\npublic:\n   ContentSettingsWidget(QWidget *parent = nullptr,\n                         Qt::WindowFlags f = Qt::WindowFlags());\n   ~ContentSettingsWidget() = default;\n\n   void loadSettings(const Settings &settings) override;\n   void saveSettings(Settings &settings) override;\n\nprivate slots:\n   void browseHfioPath();\n   void browseMlcPath();\n   void browseOtpPath();\n   void browseResourcesPath();\n   void browseSdcardPath();\n   void browseSlcPath();\n   void addTitleDirectory();\n   void removeTitleDirectory();\n\nprivate:\n   Ui::ContentSettingsWidget mUi;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings/debugsettingswidget.cpp",
    "content": "#include \"debugsettingswidget.h\"\n\n#include <QIntValidator>\n\nDebugSettingsWidget::DebugSettingsWidget(QWidget *parent,\n                                         Qt::WindowFlags f) :\n   SettingsWidget(parent, f)\n{\n   mUi.setupUi(this);\n\n   mUi.lineEditGdbServerPort->setValidator(new QIntValidator(0, 65535));\n}\n\nvoid\nDebugSettingsWidget::loadSettings(const Settings &settings)\n{\n   mUi.checkBoxBreakEntry->setChecked(settings.decaf.debugger.break_on_entry);\n   mUi.checkBoxBreakExit->setChecked(settings.decaf.debugger.break_on_exit);\n   mUi.checkBoxGdbServerEnabled->setChecked(settings.decaf.debugger.gdb_stub);\n   mUi.lineEditGdbServerPort->setText(QString(\"%1\").arg(settings.decaf.debugger.gdb_stub_port));\n}\n\nvoid\nDebugSettingsWidget::saveSettings(Settings &settings)\n{\n   settings.decaf.debugger.break_on_entry = mUi.checkBoxBreakEntry->isChecked();\n   settings.decaf.debugger.break_on_exit = mUi.checkBoxBreakExit->isChecked();\n   settings.decaf.debugger.gdb_stub = mUi.checkBoxGdbServerEnabled->isChecked();\n   settings.decaf.debugger.gdb_stub_port = mUi.lineEditGdbServerPort->text().toInt();\n}\n"
  },
  {
    "path": "src/decaf-qt/src/settings/debugsettingswidget.h",
    "content": "#pragma once\n#include \"ui_debugsettings.h\"\n#include \"settingswidget.h\"\n\nclass DebugSettingsWidget : public SettingsWidget\n{\n   Q_OBJECT\n\npublic:\n   DebugSettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());\n   ~DebugSettingsWidget() = default;\n\n   void loadSettings(const Settings &settings) override;\n   void saveSettings(Settings &settings) override;\n\nprivate:\n   Ui::DebugSettingsWidget mUi;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings/displaysettingswidget.cpp",
    "content": "#include \"displaysettingswidget.h\"\n\n#include <QDoubleValidator>\n\nDisplaySettingsWidget::DisplaySettingsWidget(QWidget *parent,\n                                             Qt::WindowFlags f) :\n   SettingsWidget(parent, f)\n{\n   mUi.setupUi(this);\n\n   mUi.comboBoxTitleListMode->addItem(tr(\"Title List\"), static_cast<int>(UiSettings::TitleList));\n   mUi.comboBoxTitleListMode->addItem(tr(\"Title Grid\"), static_cast<int>(UiSettings::TitleGrid));\n\n   mUi.comboBoxViewMode->addItem(tr(\"Split\"), static_cast<int>(gpu::DisplaySettings::Split));\n   mUi.comboBoxViewMode->addItem(tr(\"TV\"), static_cast<int>(gpu::DisplaySettings::TV));\n   mUi.comboBoxViewMode->addItem(tr(\"Gamepad 1\"), static_cast<int>(gpu::DisplaySettings::Gamepad1));\n   mUi.comboBoxViewMode->addItem(tr(\"Gamepad 2\"), static_cast<int>(gpu::DisplaySettings::Gamepad2));\n\n   mUi.lineEditSplitSeparation->setValidator(new QDoubleValidator { });\n}\n\nvoid\nDisplaySettingsWidget::loadSettings(const Settings &settings)\n{\n   auto index = mUi.comboBoxTitleListMode->findData(static_cast<int>(settings.ui.titleListMode));\n   if (index != -1) {\n      mUi.comboBoxTitleListMode->setCurrentIndex(index);\n   } else {\n      mUi.comboBoxTitleListMode->setCurrentIndex(0);\n   }\n\n   index = mUi.comboBoxViewMode->findData(static_cast<int>(settings.gpu.display.viewMode));\n   if (index != -1) {\n      mUi.comboBoxViewMode->setCurrentIndex(index);\n   } else {\n      mUi.comboBoxViewMode->setCurrentIndex(2);\n   }\n\n   mUi.checkBoxMaintainAspectRatio->setChecked(\n      settings.gpu.display.maintainAspectRatio);\n\n   mUi.lineEditSplitSeparation->setText(\n      QString { \"%1\" }.arg(settings.gpu.display.splitSeperation, 0, 'g', 2));\n\n   mUi.lineEditBackgroundColour->setColour(QColor {\n      settings.gpu.display.backgroundColour[0],\n      settings.gpu.display.backgroundColour[1],\n      settings.gpu.display.backgroundColour[2],\n   });\n}\n\nvoid\nDisplaySettingsWidget::saveSettings(Settings &settings)\n{\n   settings.ui.titleListMode =\n      static_cast<UiSettings::TitleListMode>(mUi.comboBoxTitleListMode->currentData().toInt());\n\n   settings.gpu.display.viewMode =\n      static_cast<gpu::DisplaySettings::ViewMode>(mUi.comboBoxViewMode->currentData().toInt());\n\n   settings.gpu.display.maintainAspectRatio =\n      mUi.checkBoxMaintainAspectRatio->isChecked();\n\n   settings.gpu.display.splitSeperation =\n      mUi.lineEditSplitSeparation->text().toDouble();\n\n   auto colour = mUi.lineEditBackgroundColour->getColour();\n   settings.gpu.display.backgroundColour[0] = colour.red();\n   settings.gpu.display.backgroundColour[1] = colour.green();\n   settings.gpu.display.backgroundColour[2] = colour.blue();\n}\n"
  },
  {
    "path": "src/decaf-qt/src/settings/displaysettingswidget.h",
    "content": "#pragma once\n#include \"ui_displaysettings.h\"\n#include \"settingswidget.h\"\n\nclass DisplaySettingsWidget : public SettingsWidget\n{\n   Q_OBJECT\n\npublic:\n   DisplaySettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());\n   ~DisplaySettingsWidget() = default;\n\n   void loadSettings(const Settings &settings) override;\n   void saveSettings(Settings &settings) override;\n\nprivate:\n   Ui::DisplaySettingsWidget mUi;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings/inputsettingswidget.cpp",
    "content": "#include \"inputsettingswidget.h\"\n#include \"inputeventfilter.h\"\n#include \"inputdriver.h\"\n\n#include <QStandardItemModel>\n\n#include <libdecaf/decaf_input.h>\n#include <functional>\n\nstatic const char *\ngetButtonName(ButtonType type)\n{\n   switch (type) {\n   case ButtonType::A:\n      return \"A\";\n   case ButtonType::B:\n      return \"B\";\n   case ButtonType::X:\n      return \"X\";\n   case ButtonType::Y:\n      return \"Y\";\n   case ButtonType::R:\n      return \"R\";\n   case ButtonType::L:\n      return \"L\";\n   case ButtonType::ZR:\n      return \"ZR\";\n   case ButtonType::ZL:\n      return \"ZL\";\n   case ButtonType::Plus:\n      return \"Plus\";\n   case ButtonType::Minus:\n      return \"Minus\";\n   case ButtonType::Home:\n      return \"Home\";\n   case ButtonType::Sync:\n      return \"Sync\";\n   case ButtonType::DpadUp:\n      return \"Dpad Up\";\n   case ButtonType::DpadDown:\n      return \"Dpad Down\";\n   case ButtonType::DpadLeft:\n      return \"Dpad Left\";\n   case ButtonType::DpadRight:\n      return \"Dpad Right\";\n   case ButtonType::LeftStickPress:\n      return \"Left Stick Press\";\n   case ButtonType::LeftStickUp:\n      return \"Left Stick Up\";\n   case ButtonType::LeftStickDown:\n      return \"Left Stick Down\";\n   case ButtonType::LeftStickLeft:\n      return \"Left Stick Left\";\n   case ButtonType::LeftStickRight:\n      return \"Left Stick Right\";\n   case ButtonType::RightStickPress:\n      return \"Right Stick Press\";\n   case ButtonType::RightStickUp:\n      return \"Right Stick Up\";\n   case ButtonType::RightStickDown:\n      return \"Right Stick Down\";\n   case ButtonType::RightStickLeft:\n      return \"Right Stick Left\";\n   case ButtonType::RightStickRight:\n      return \"Right Stick Right\";\n   default:\n      return \"\";\n   }\n}\n\nInputSettingsWidget::InputSettingsWidget(InputDriver *inputDriver, QWidget *parent, Qt::WindowFlags f) :\n   SettingsWidget(parent, f),\n   mInputDriver(inputDriver)\n{\n   mUi.setupUi(this);\n\n   // Setup filter so we can catch keyboard input\n   mInputEventFilter = new InputEventFilter(this);\n   qApp->installEventFilter(mInputEventFilter);\n   connect(mInputEventFilter, &InputEventFilter::caughtKeyPress, this, &InputSettingsWidget::caughtKeyPress);\n\n   connect(mInputDriver, &InputDriver::joystickConnected, this, &InputSettingsWidget::joystickConnected);\n   connect(mInputDriver, &InputDriver::joystickDisconnected, this, &InputSettingsWidget::joystickDisconnected);\n   connect(mInputDriver, &InputDriver::joystickButtonDown, this, &InputSettingsWidget::joystickButton);\n   connect(mInputDriver, &InputDriver::joystickAxisMotion, this, &InputSettingsWidget::joystickAxisMotion);\n   connect(mInputDriver, &InputDriver::joystickHatMotion, this, &InputSettingsWidget::joystickHatMotion);\n}\n\nInputSettingsWidget::~InputSettingsWidget()\n{\n   qApp->removeEventFilter(mInputEventFilter);\n   delete mInputEventFilter;\n}\n\nvoid InputSettingsWidget::loadSettings(const Settings &settings)\n{\n   mInputConfiguration = settings.input;\n   mUi.controllerList->setUpdatesEnabled(false);\n   mUi.controllerList->clear();\n\n   auto index = 0;\n   auto gamepad = 0;\n   auto wiimote = 0;\n   auto pro = 0;\n   auto classic = 0;\n\n   for (auto &controller : mInputConfiguration.controllers) {\n      switch (controller.type) {\n      case ControllerType::Invalid:\n         mUi.controllerList->addItem(QString(\"New Controller %1\").arg(index));\n         break;\n      case ControllerType::Gamepad:\n         mUi.controllerList->addItem(QString(\"Gamepad %1\").arg(gamepad++));\n         break;\n      case ControllerType::WiiMote:\n         mUi.controllerList->addItem(QString(\"Wiimote %1\").arg(wiimote++));\n         break;\n      case ControllerType::ProController:\n         mUi.controllerList->addItem(QString(\"Pro Controller %1\").arg(pro++));\n         break;\n      case ControllerType::ClassicController:\n         mUi.controllerList->addItem(QString(\"Classic Controller %1\").arg(classic++));\n         break;\n      }\n\n      ++index;\n   }\n\n   mUi.controllerList->setUpdatesEnabled(true);\n   mUi.controllerList->setCurrentRow(0);\n}\n\nvoid InputSettingsWidget::saveSettings(Settings &settings)\n{\n   settings.input = mInputConfiguration;\n}\n\nvoid InputSettingsWidget::addController()\n{\n   auto id = mInputConfiguration.controllers.size();\n   mInputConfiguration.controllers.push_back({});\n\n   auto item = new QListWidgetItem(QString(\"New Controller %1\").arg(id));\n   mUi.controllerList->addItem(item);\n   mUi.controllerList->setCurrentItem(item);\n}\n\nvoid InputSettingsWidget::removeController()\n{\n   mInputConfiguration.controllers.erase(mInputConfiguration.controllers.begin() + mUi.controllerList->currentRow());\n   delete mUi.controllerList->takeItem(mUi.controllerList->currentRow());\n}\n\nvoid InputSettingsWidget::editController(int index)\n{\n   if (index == -1) {\n      mUi.controllerType->clear();\n      return;\n   }\n\n   auto type = mInputConfiguration.controllers[index].type;\n   mUi.controllerType->setUpdatesEnabled(false);\n   mUi.controllerType->clear();\n   mUi.controllerType->addItem(\"Unconfigured\", QVariant::fromValue(static_cast<int>(ControllerType::Invalid)));\n   mUi.controllerType->addItem(\"Wii U Gamepad\", QVariant::fromValue(static_cast<int>(ControllerType::Gamepad)));\n   mUi.controllerType->addItem(\"Wiimote\", QVariant::fromValue(static_cast<int>(ControllerType::WiiMote)));\n   mUi.controllerType->addItem(\"Pro Controller\", QVariant::fromValue(static_cast<int>(ControllerType::ProController)));\n   mUi.controllerType->addItem(\"Classic Controller\", QVariant::fromValue(static_cast<int>(ControllerType::ClassicController)));\n   mUi.controllerType->setCurrentIndex(mUi.controllerType->findData(QVariant::fromValue(static_cast<int>(type))));\n   mUi.controllerType->setUpdatesEnabled(true);\n}\n\nvoid InputSettingsWidget::assignButton(QPushButton *button, ButtonType type)\n{\n   if (button->isChecked()) {\n      if (mAssignButton && mAssignButton != button) {\n         mAssignButton->setChecked(false);\n      }\n\n      mInputEventFilter->enable();\n      mInputDriver->enableButtonEvents();\n      mAssignButtonType = type;\n      mAssignButton = button;\n   } else if (mAssignButton == button) {\n      mAssignButtonType = type;\n      mAssignButton = nullptr;\n      mInputEventFilter->disable();\n      mInputDriver->disableButtonEvents();\n   }\n}\n\nstatic QString\ninputToText(const InputConfiguration::Input &input)\n{\n   switch (input.source) {\n   case InputConfiguration::Input::KeyboardKey:\n      return QString(\"Keyboard key %1\")\n         .arg(QKeySequence(input.id).toString());\n   case InputConfiguration::Input::JoystickButton:\n      return QString(\"Joystick %1 button %2\")\n         .arg(input.joystickInstanceId)\n         .arg(input.id);\n   case InputConfiguration::Input::JoystickAxis:\n      return QString(\"Joystick %1 axis %2 %3\")\n         .arg(input.joystickInstanceId)\n         .arg(input.id)\n         .arg(input.invert ? \"negative\" : \"positive\");\n   case InputConfiguration::Input::JoystickHat:\n   {\n      const char *direction = \"\";\n      if (input.hatValue == SDL_HAT_UP) {\n         direction = \"up\";\n      } else if (input.hatValue == SDL_HAT_LEFT) {\n         direction = \"left\";\n      } else if (input.hatValue == SDL_HAT_RIGHT) {\n         direction = \"right\";\n      } else if (input.hatValue == SDL_HAT_DOWN) {\n         direction = \"down\";\n      }\n\n      return QString(\"Joystick %1 Hat %2 %3\")\n         .arg(input.joystickInstanceId)\n         .arg(input.id)\n         .arg(direction);\n   }\n   default:\n      return {};\n   }\n}\n\nvoid InputSettingsWidget::caughtKeyPress(int key)\n{\n   // TODO: Assign mAssignButtonType to key!\n   if (mAssignButton) {\n      auto index = mUi.controllerList->currentIndex().row();\n      auto &controller = mInputConfiguration.controllers[index];\n      auto &input = controller.inputs[static_cast<size_t>(mAssignButtonType)];\n      input.source = InputConfiguration::Input::KeyboardKey;\n      input.id = key;\n\n      mAssignButton->setText(inputToText(input));\n      mAssignButton->setChecked(false);\n   }\n}\n\nvoid InputSettingsWidget::joystickConnected(SDL_JoystickID id, SDL_JoystickGUID guid, const char *name)\n{\n   mJoysticks.push_back({ id, guid, name });\n}\n\nvoid InputSettingsWidget::joystickDisconnected(SDL_JoystickID id, SDL_JoystickGUID guid)\n{\n}\n\nvoid InputSettingsWidget::joystickButton(SDL_JoystickID id, SDL_JoystickGUID guid, int button)\n{\n   if (mAssignButton) {\n      auto index = mUi.controllerList->currentIndex().row();\n      auto &controller = mInputConfiguration.controllers[index];\n      auto &input = controller.inputs[static_cast<size_t>(mAssignButtonType)];\n      input.source = InputConfiguration::Input::JoystickButton;\n      input.id = button;\n      input.joystickInstanceId = id;\n      input.joystickGuid = guid;\n\n      mAssignButton->setText(inputToText(input));\n      mAssignButton->setChecked(false);\n   }\n}\n\nvoid InputSettingsWidget::joystickAxisMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int axis, float value)\n{\n   if (value > -0.5f && value < 0.5) {\n      // Ignore until axis has a decent value\n      return;\n   }\n\n   if (mAssignButton) {\n      auto index = mUi.controllerList->currentIndex().row();\n      auto &controller = mInputConfiguration.controllers[index];\n      auto &input = controller.inputs[static_cast<size_t>(mAssignButtonType)];\n      input.source = InputConfiguration::Input::JoystickAxis;\n      input.id = axis;\n      input.joystickInstanceId = id;\n      input.joystickGuid = guid;\n      input.invert = (value < 0);\n\n      mAssignButton->setText(inputToText(input));\n      mAssignButton->setChecked(false);\n   }\n}\n\nvoid InputSettingsWidget::joystickHatMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int hat, int value)\n{\n   const char *direction = nullptr;\n   if (value == SDL_HAT_UP) {\n      direction = \"up\";\n   } else if (value == SDL_HAT_LEFT) {\n      direction = \"left\";\n   } else if (value == SDL_HAT_RIGHT) {\n      direction = \"right\";\n   } else if (value == SDL_HAT_DOWN) {\n      direction = \"down\";\n   } else {\n      // Only allow one direction\n      return;\n   }\n\n   if (mAssignButton) {\n      auto index = mUi.controllerList->currentIndex().row();\n      auto &controller = mInputConfiguration.controllers[index];\n      auto &input = controller.inputs[static_cast<size_t>(mAssignButtonType)];\n      input.source = InputConfiguration::Input::JoystickHat;\n      input.id = hat;\n      input.hatValue = value;\n      input.joystickInstanceId = id;\n      input.joystickGuid = guid;\n\n      mAssignButton->setText(inputToText(input));\n      mAssignButton->setChecked(false);\n   }\n}\n\nvoid InputSettingsWidget::controllerTypeChanged(int typeIndex)\n{\n   mUi.buttonList->setUpdatesEnabled(false);\n   qDeleteAll(mUi.buttonList->findChildren<QWidget*>(\"\", Qt::FindDirectChildrenOnly));\n   mUi.buttonList->setUpdatesEnabled(true);\n\n   if (typeIndex == -1) {\n      return;\n   }\n\n   auto type = static_cast<ControllerType>(mUi.controllerType->itemData(typeIndex).toInt());\n   auto index = mUi.controllerList->currentIndex().row();\n   auto &controller = mInputConfiguration.controllers[index];\n   controller.type = type;\n   mUi.buttonList->setUpdatesEnabled(false);\n\n   if (type == ControllerType::Gamepad) {\n      auto buttonLayout = reinterpret_cast<QFormLayout *>(mUi.buttonList->layout());\n      auto addButton = [&](ButtonType type)\n      {\n         auto &input = controller.inputs[static_cast<size_t>(type)];\n         auto button = new QPushButton(inputToText(input));\n         button->setCheckable(true);\n         connect(button, &QPushButton::toggled, std::bind(&InputSettingsWidget::assignButton, this, button, type));\n         buttonLayout->addRow(getButtonName(type), button);\n      };\n\n      addButton(ButtonType::A);\n      addButton(ButtonType::B);\n      addButton(ButtonType::X);\n      addButton(ButtonType::Y);\n      addButton(ButtonType::R);\n      addButton(ButtonType::L);\n      addButton(ButtonType::ZR);\n      addButton(ButtonType::ZL);\n      addButton(ButtonType::Plus);\n      addButton(ButtonType::Minus);\n      addButton(ButtonType::Home);\n      addButton(ButtonType::Sync);\n      addButton(ButtonType::DpadUp);\n      addButton(ButtonType::DpadDown);\n      addButton(ButtonType::DpadLeft);\n      addButton(ButtonType::DpadRight);\n      addButton(ButtonType::LeftStickPress);\n      addButton(ButtonType::LeftStickUp);\n      addButton(ButtonType::LeftStickDown);\n      addButton(ButtonType::LeftStickLeft);\n      addButton(ButtonType::LeftStickRight);\n      addButton(ButtonType::RightStickPress);\n      addButton(ButtonType::RightStickUp);\n      addButton(ButtonType::RightStickDown);\n      addButton(ButtonType::RightStickLeft);\n      addButton(ButtonType::RightStickRight);\n   }\n\n   mUi.buttonList->setUpdatesEnabled(true);\n}\n"
  },
  {
    "path": "src/decaf-qt/src/settings/inputsettingswidget.h",
    "content": "#pragma once\n#include \"ui_inputsettings.h\"\n#include \"inputdriver.h\"\n#include \"settingswidget.h\"\n\n#include <SDL_joystick.h>\n#include <QDialog>\n#include <QVector>\n#include <QMap>\n\nclass InputEventFilter;\nclass SdlEventLoop;\n\nstruct JoystickInfo\n{\n   SDL_JoystickID id = -1;\n   SDL_JoystickGUID guid;\n   const char *name = nullptr;\n};\n\nclass InputSettingsWidget : public SettingsWidget\n{\n   Q_OBJECT\n\npublic:\n   InputSettingsWidget(InputDriver *inputDriver, QWidget *parent = nullptr,\n                       Qt::WindowFlags f = Qt::WindowFlags());\n   ~InputSettingsWidget();\n\n   void loadSettings(const Settings &settings) override;\n   void saveSettings(Settings &settings) override;\n\nprivate slots:\n   void addController();\n   void removeController();\n   void editController(int index);\n   void controllerTypeChanged(int index);\n\n   void assignButton(QPushButton *button, ButtonType type);\n   void caughtKeyPress(int key);\n\n   void joystickConnected(SDL_JoystickID id, SDL_JoystickGUID guid, const char *name);\n   void joystickDisconnected(SDL_JoystickID id, SDL_JoystickGUID guid);\n   void joystickButton(SDL_JoystickID id, SDL_JoystickGUID guid, int key);\n   void joystickAxisMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int axis, float value);\n   void joystickHatMotion(SDL_JoystickID id, SDL_JoystickGUID guid, int hat, int value);\n\nprivate:\n   Ui::InputSettingsWidget mUi;\n   QVector<JoystickInfo> mJoysticks;\n   InputEventFilter *mInputEventFilter;\n\n   ButtonType mAssignButtonType;\n   QPushButton *mAssignButton = nullptr;\n   InputDriver *mInputDriver = nullptr;\n\n   InputConfiguration mInputConfiguration;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings/loggingsettingswidget.cpp",
    "content": "#include \"loggingsettingswidget.h\"\n\n#include <QFileDialog>\n\nstatic const char *LogLevels[] = {\n   \"trace\",\n   \"debug\",\n   \"info\",\n   \"notice\",\n   \"warning\",\n   \"error\",\n   \"critical\",\n   \"alert\",\n   \"emerg\",\n   \"off\"\n};\n\nLoggingSettingsWidget::LoggingSettingsWidget(QWidget *parent,\n                                             Qt::WindowFlags f) :\n   SettingsWidget(parent, f)\n{\n   mUi.setupUi(this);\n\n   for (auto &level : LogLevels) {\n      mUi.comboBoxLogLevel->addItem(level);\n   }\n}\n\nvoid\nLoggingSettingsWidget::loadSettings(const Settings &settings)\n{\n   mUi.checkBoxAsynchronous->setChecked(settings.decaf.log.async);\n   mUi.checkBoxBranchTracing->setChecked(settings.decaf.log.branch_trace);\n   mUi.checkBoxHleTrace->setChecked(settings.decaf.log.hle_trace);\n   mUi.checkBoxHleTraceReturnValue->setChecked(settings.decaf.log.hle_trace_res);\n   mUi.checkBoxOutputFile->setChecked(settings.decaf.log.to_file);\n   mUi.checkBoxOutputStdout->setChecked(settings.decaf.log.to_stdout);\n   mUi.lineEditLogDirectory->setText(QString::fromStdString(settings.decaf.log.directory));\n   mUi.lineEditLogDirectory->setCursorPosition(0);\n\n   int index = mUi.comboBoxLogLevel->findText(QString::fromStdString(settings.decaf.log.level));\n   if (index != -1) {\n      mUi.comboBoxLogLevel->setCurrentIndex(index);\n   } else {\n      mUi.comboBoxLogLevel->setCurrentIndex(1);\n   }\n\n   mUi.listWidgetHleTraceFilters->clear();\n   for (auto &filter : settings.decaf.log.hle_trace_filters) {\n      auto item = new QListWidgetItem(QString::fromStdString(filter));\n      item->setFlags(item->flags() | Qt::ItemIsEditable);\n      mUi.listWidgetHleTraceFilters->addItem(item);\n   }\n}\n\nvoid\nLoggingSettingsWidget::saveSettings(Settings &settings)\n{\n   settings.decaf.log.async = mUi.checkBoxAsynchronous->isChecked();\n   settings.decaf.log.branch_trace = mUi.checkBoxBranchTracing->isChecked();\n   settings.decaf.log.hle_trace = mUi.checkBoxHleTrace->isChecked();\n   settings.decaf.log.hle_trace_res = mUi.checkBoxHleTraceReturnValue->isChecked();\n   settings.decaf.log.to_file = mUi.checkBoxOutputFile->isChecked();\n   settings.decaf.log.to_stdout = mUi.checkBoxOutputStdout->isChecked();\n   settings.decaf.log.directory = mUi.lineEditLogDirectory->text().toStdString();\n   settings.decaf.log.level = mUi.comboBoxLogLevel->currentText().toStdString();\n\n   settings.decaf.log.hle_trace_filters.clear();\n   for (auto i = 0; i < mUi.listWidgetHleTraceFilters->count(); ++i) {\n      settings.decaf.log.hle_trace_filters.emplace_back(mUi.listWidgetHleTraceFilters->item(i)->text().toStdString());\n   }\n}\n\nvoid\nLoggingSettingsWidget::addTraceFilter()\n{\n   auto item = new QListWidgetItem(QString::fromStdString(\"+.*\"));\n   item->setFlags(item->flags() | Qt::ItemIsEditable);\n   mUi.listWidgetHleTraceFilters->addItem(item);\n   mUi.listWidgetHleTraceFilters->editItem(item);\n}\n\nvoid\nLoggingSettingsWidget::removeTraceFilter()\n{\n   delete mUi.listWidgetHleTraceFilters->currentItem();\n}\n\nvoid\nLoggingSettingsWidget::browseLogPath()\n{\n   auto path = QFileDialog::getExistingDirectory(this, tr(\"Open Directory\"), mUi.lineEditLogDirectory->text());\n   if (!path.isEmpty()) {\n      mUi.lineEditLogDirectory->setText(path);\n   }\n}\n"
  },
  {
    "path": "src/decaf-qt/src/settings/loggingsettingswidget.h",
    "content": "#pragma once\n#include \"ui_loggingsettings.h\"\n#include \"settingswidget.h\"\n\nclass LoggingSettingsWidget : public SettingsWidget\n{\n   Q_OBJECT\n\npublic:\n   LoggingSettingsWidget(QWidget *parent = nullptr,\n                         Qt::WindowFlags f = Qt::WindowFlags());\n   ~LoggingSettingsWidget() = default;\n\n   void loadSettings(const Settings &settings) override;\n   void saveSettings(Settings &settings) override;\n\nprivate slots:\n   void addTraceFilter();\n   void removeTraceFilter();\n   void browseLogPath();\n\nprivate:\n   Ui::LoggingSettingsWidget mUi;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings/settingsdialog.cpp",
    "content": "#include \"settingsdialog.h\"\n#include \"settings.h\"\n\n#include \"audiosettingswidget.h\"\n#include \"contentsettingswidget.h\"\n#include \"debugsettingswidget.h\"\n#include \"displaysettingswidget.h\"\n#include \"inputsettingswidget.h\"\n#include \"loggingsettingswidget.h\"\n#include \"systemsettingswidget.h\"\n\nSettingsDialog::SettingsDialog(SettingsStorage *settingsStorage,\n                               InputDriver *inputDriver,\n                               SettingsTab tab,\n                               QWidget *parent,\n                               Qt::WindowFlags f) :\n   QDialog(parent, f),\n   mSettingsStorage(settingsStorage)\n{\n   mUi.setupUi(this);\n\n   auto inputSettings = new InputSettingsWidget { inputDriver };\n   mUi.tabWidget->addTab(inputSettings, tr(\"Input\"));\n   mSettings.push_back(inputSettings);\n\n   auto contentSettings = new ContentSettingsWidget { };\n   mUi.tabWidget->addTab(contentSettings, tr(\"Content\"));\n   mSettings.push_back(contentSettings);\n\n   auto systemSettings = new SystemSettingsWidget { };\n   mUi.tabWidget->addTab(systemSettings, tr(\"System\"));\n   mSettings.push_back(systemSettings);\n\n   auto displaySettings = new DisplaySettingsWidget { };\n   mUi.tabWidget->addTab(displaySettings, tr(\"Display\"));\n   mSettings.push_back(displaySettings);\n\n   auto audioSettings = new AudioSettingsWidget { };\n   mUi.tabWidget->addTab(audioSettings, tr(\"Audio\"));\n   mSettings.push_back(audioSettings);\n\n   auto loggingSettings = new LoggingSettingsWidget { };\n   mUi.tabWidget->addTab(loggingSettings, tr(\"Logging\"));\n   mSettings.push_back(loggingSettings);\n\n   auto debugSettings = new DebugSettingsWidget { };\n   mUi.tabWidget->addTab(debugSettings, tr(\"Debug\"));\n   mSettings.push_back(debugSettings);\n\n   // Load all the settings\n   auto settings = mSettingsStorage->get();\n   for (auto &settingsWidget : mSettings) {\n      settingsWidget->loadSettings(*settings);\n   }\n\n   if (tab == SettingsTab::Default || tab == SettingsTab::Input) {\n      mUi.tabWidget->setCurrentWidget(inputSettings);\n   } else if (tab == SettingsTab::Content) {\n      mUi.tabWidget->setCurrentWidget(contentSettings);\n   } else if (tab == SettingsTab::System) {\n      mUi.tabWidget->setCurrentWidget(systemSettings);\n   } else if (tab == SettingsTab::Display) {\n      mUi.tabWidget->setCurrentWidget(displaySettings);\n   } else if (tab == SettingsTab::Audio) {\n      mUi.tabWidget->setCurrentWidget(audioSettings);\n   } else if (tab == SettingsTab::Logging) {\n      mUi.tabWidget->setCurrentWidget(loggingSettings);\n   } else if (tab == SettingsTab::Debug) {\n      mUi.tabWidget->setCurrentWidget(debugSettings);\n   }\n}\n\nvoid\nSettingsDialog::buttonBoxClicked(QAbstractButton *button)\n{\n   auto role = mUi.buttonBox->buttonRole(button);\n   auto settings = mSettingsStorage->get();\n\n   // On Reset we should load settings\n   if (role == QDialogButtonBox::ResetRole) {\n      for (auto &settingsWidget : mSettings) {\n         settingsWidget->loadSettings(*settings);\n      }\n   }\n\n   // On Accept or Apply we should save settings\n   if (role == QDialogButtonBox::AcceptRole ||\n       role == QDialogButtonBox::ApplyRole) {\n      // Update config with settings\n      auto updatedSettings = *settings;\n      for (auto &settingsWidget : mSettings) {\n         settingsWidget->saveSettings(updatedSettings);\n      }\n\n      mSettingsStorage->set(updatedSettings);\n   }\n\n   if (role == QDialogButtonBox::AcceptRole) {\n      accept();\n   } else if (role == QDialogButtonBox::RejectRole) {\n      reject();\n   }\n}\n"
  },
  {
    "path": "src/decaf-qt/src/settings/settingsdialog.h",
    "content": "#pragma once\n#include \"ui_settings.h\"\n#include \"settingswidget.h\"\n\n#include <QDialog>\n#include <QVector>\n\nclass DecafInterface;\nclass InputDriver;\nclass SettingsWidget;\nclass SettingsStorage;\n\nclass QAbstractButton;\n\nenum class SettingsTab\n{\n   Default = 0,\n   System,\n   Content,\n   Input,\n   Display,\n   Audio,\n   Logging,\n   Debug,\n};\n\nclass SettingsDialog : public QDialog\n{\n   Q_OBJECT\n\npublic:\n   SettingsDialog(SettingsStorage *settingsStorage,\n                  InputDriver *inputDriver,\n                  SettingsTab openTab = SettingsTab::Default,\n                  QWidget *parent = nullptr,\n                  Qt::WindowFlags f = Qt::WindowFlags());\n   ~SettingsDialog() = default;\n\nprivate slots:\n   void buttonBoxClicked(QAbstractButton *button);\n\nprivate:\n   Ui::SettingsDialog mUi;\n   QVector<SettingsWidget *> mSettings;\n   SettingsStorage *mSettingsStorage;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings/settingswidget.h",
    "content": "#pragma once\n#include \"settings.h\"\n\n#include <QWidget>\n\nclass SettingsWidget : public QWidget\n{\npublic:\n   SettingsWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) : QWidget(parent, f) {}\n   virtual ~SettingsWidget() = default;\n\n   virtual void loadSettings(const Settings &settings) = 0;\n   virtual void saveSettings(Settings &settings) = 0;\n\nprivate:\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings/systemsettingswidget.cpp",
    "content": "#include \"systemsettingswidget.h\"\n\n#include <QFileDialog>\n\nstatic constexpr const char *LibraryList[] = {\n   \"avm.rpl\",\n   \"camera.rpl\",\n   \"coreinit.rpl\",\n   \"dc.rpl\",\n   \"dmae.rpl\",\n   \"drmapp.rpl\",\n   \"erreula.rpl\",\n   \"gx2.rpl\",\n   \"h264.rpl\",\n   \"lzma920.rpl\",\n   \"mic.rpl\",\n   \"nfc.rpl\",\n   \"nio_prof.rpl\",\n   \"nlibcurl.rpl\",\n   \"nlibnss2.rpl\",\n   \"nlibnss.rpl\",\n   \"nn_ac.rpl\",\n   \"nn_acp.rpl\",\n   \"nn_act.rpl\",\n   \"nn_aoc.rpl\",\n   \"nn_boss.rpl\",\n   \"nn_ccr.rpl\",\n   \"nn_cmpt.rpl\",\n   \"nn_dlp.rpl\",\n   \"nn_ec.rpl\",\n   \"nn_fp.rpl\",\n   \"nn_hai.rpl\",\n   \"nn_hpad.rpl\",\n   \"nn_idbe.rpl\",\n   \"nn_ndm.rpl\",\n   \"nn_nets2.rpl\",\n   \"nn_nfp.rpl\",\n   \"nn_nim.rpl\",\n   \"nn_olv.rpl\",\n   \"nn_pdm.rpl\",\n   \"nn_save.rpl\",\n   \"nn_sl.rpl\",\n   \"nn_spm.rpl\",\n   \"nn_temp.rpl\",\n   \"nn_uds.rpl\",\n   \"nn_vctl.rpl\",\n   \"nsysccr.rpl\",\n   \"nsyshid.rpl\",\n   \"nsyskbd.rpl\",\n   \"nsysnet.rpl\",\n   \"nsysuhs.rpl\",\n   \"nsysuvd.rpl\",\n   \"ntag.rpl\",\n   \"padscore.rpl\",\n   \"proc_ui.rpl\",\n   \"sndcore2.rpl\",\n   \"snd_core.rpl\",\n   \"snduser2.rpl\",\n   \"snd_user.rpl\",\n   \"swkbd.rpl\",\n   \"sysapp.rpl\",\n   \"tcl.rpl\",\n   \"tve.rpl\",\n   \"uac.rpl\",\n   \"uac_rpl.rpl\",\n   \"usb_mic.rpl\",\n   \"uvc.rpl\",\n   \"uvd.rpl\",\n   \"vpadbase.rpl\",\n   \"vpad.rpl\",\n   \"zlib125.rpl\",\n};\n\nstatic constexpr const char *DisabledLibraryList[] = {\n   \"coreinit.rpl\",\n   \"gx2.rpl\",\n   \"tcl.rpl\",\n};\n\nusing SystemRegion = decaf::SystemRegion;\n\nSystemSettingsWidget::SystemSettingsWidget(QWidget *parent,\n                                           Qt::WindowFlags f) :\n   SettingsWidget(parent, f)\n{\n   mUi.setupUi(this);\n\n   for (auto library : LibraryList) {\n      auto item = new QListWidgetItem(library);\n      item->setFlags(item->flags() | Qt::ItemIsUserCheckable);\n      item->setCheckState(Qt::Unchecked);\n\n      if (std::find(std::begin(DisabledLibraryList), std::end(DisabledLibraryList), library) != std::end(DisabledLibraryList)) {\n         item->setFlags(item->flags() & ~Qt::ItemIsEnabled);\n      }\n\n      mUi.listWidgetLleWhitelist->addItem(item);\n   }\n\n   mUi.comboBoxRegion->addItem(tr(\"Japan\"), static_cast<int>(SystemRegion::Japan));\n   mUi.comboBoxRegion->addItem(tr(\"USA\"), static_cast<int>(SystemRegion::USA));\n   mUi.comboBoxRegion->addItem(tr(\"Europe\"), static_cast<int>(SystemRegion::Europe));\n   mUi.comboBoxRegion->addItem(tr(\"Unknown8\"), static_cast<int>(SystemRegion::Unknown8));\n   mUi.comboBoxRegion->addItem(tr(\"China\"), static_cast<int>(SystemRegion::China));\n   mUi.comboBoxRegion->addItem(tr(\"Korea\"), static_cast<int>(SystemRegion::Korea));\n   mUi.comboBoxRegion->addItem(tr(\"Taiwan\"), static_cast<int>(SystemRegion::Taiwan));\n\n   mUi.lineEditTimeScale->setValidator(new QDoubleValidator(0.01, 100, 2, this));\n}\n\nvoid\nSystemSettingsWidget::loadSettings(const Settings &settings)\n{\n   int index = mUi.comboBoxRegion->findData(static_cast<int>(settings.decaf.system.region));\n   if (index != -1) {\n      mUi.comboBoxRegion->setCurrentIndex(index);\n   } else {\n      mUi.comboBoxRegion->setCurrentIndex(2);\n   }\n\n   for (auto i = 0; i < mUi.listWidgetLleWhitelist->count(); ++i) {\n      auto item = mUi.listWidgetLleWhitelist->item(i);\n      auto name = item->text().toStdString();\n\n      if (std::find(settings.decaf.system.lle_modules.begin(), settings.decaf.system.lle_modules.end(), name) != settings.decaf.system.lle_modules.end()) {\n         item->setCheckState(Qt::Checked);\n      } else {\n         item->setCheckState(Qt::Unchecked);\n      }\n   }\n}\n\nvoid\nSystemSettingsWidget::saveSettings(Settings &settings)\n{\n   settings.decaf.system.time_scale = mUi.lineEditTimeScale->text().toDouble();\n   settings.decaf.system.region = static_cast<SystemRegion>(mUi.comboBoxRegion->currentData().toInt());\n\n   settings.decaf.system.lle_modules.clear();\n   for (auto i = 0; i < mUi.listWidgetLleWhitelist->count(); ++i) {\n      auto item = mUi.listWidgetLleWhitelist->item(i);\n      if (item->checkState() == Qt::Checked) {\n         settings.decaf.system.lle_modules.emplace_back(item->text().toStdString());\n      }\n   }\n}\n"
  },
  {
    "path": "src/decaf-qt/src/settings/systemsettingswidget.h",
    "content": "#pragma once\n#include \"ui_systemsettings.h\"\n#include \"settingswidget.h\"\n\nclass SystemSettingsWidget : public SettingsWidget\n{\n   Q_OBJECT\n\npublic:\n   SystemSettingsWidget(QWidget *parent = nullptr,\n                        Qt::WindowFlags f = Qt::WindowFlags());\n   ~SystemSettingsWidget() = default;\n\n   void loadSettings(const Settings &settings) override;\n   void saveSettings(Settings &settings) override;\n\nprivate:\n   Ui::SystemSettingsWidget mUi;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/settings.cpp",
    "content": "#include \"settings.h\"\n#include \"inputdriver.h\"\n\n#include <libconfig/config_toml.h>\n#include <optional>\n#include <toml++/toml.h>\n\nstatic bool loadFromTOML(const toml::table &config,\n                         InputConfiguration &inputConfiguration);\nstatic bool saveToTOML(toml::table &config,\n                       const InputConfiguration &inputConfiguration);\n\nstatic bool loadFromTOML(const toml::table &config,\n                         SoundSettings &soundSettings);\nstatic bool saveToTOML(toml::table &config,\n                       const SoundSettings &soundSettings);\n\nstatic bool loadFromTOML(const toml::table &config,\n                         UiSettings &uiSettings);\nstatic bool saveToTOML(toml::table &config,\n                       const UiSettings &uiSettings);\n\nbool\nloadSettings(const std::string &path,\n             Settings &settings)\n{\n   try {\n      toml::table toml = toml::parse_file(path);\n      config::loadFromTOML(toml, settings.cpu);\n      config::loadFromTOML(toml, settings.decaf);\n      config::loadFromTOML(toml, settings.gpu);\n      loadFromTOML(toml, settings.input);\n      loadFromTOML(toml, settings.sound);\n      loadFromTOML(toml, settings.ui);\n      return true;\n   } catch (const toml::parse_error &) {\n      return false;\n   }\n}\n\nbool\nsaveSettings(const std::string &path,\n             const Settings &settings)\n{\n   toml::table toml;\n   try {\n      // Read current file and modify that\n      toml = toml::parse_file(path);\n   } catch (const toml::parse_error &) {\n   }\n\n   // Update it\n   config::saveToTOML(toml, settings.decaf);\n   config::saveToTOML(toml, settings.cpu);\n   config::saveToTOML(toml, settings.gpu);\n   saveToTOML(toml, settings.input);\n   saveToTOML(toml, settings.sound);\n   saveToTOML(toml, settings.ui);\n\n   // Write to file\n   std::ofstream out { path };\n   if (!out.is_open()) {\n      return false;\n   }\n\n   out << toml;\n   return true;\n}\n\nstatic const char *\ngetConfigButtonName(ButtonType type)\n{\n   switch (type) {\n   case ButtonType::A:\n      return \"button_a\";\n   case ButtonType::B:\n      return \"button_b\";\n   case ButtonType::X:\n      return \"button_x\";\n   case ButtonType::Y:\n      return \"button_y\";\n   case ButtonType::R:\n      return \"button_r\";\n   case ButtonType::L:\n      return \"button_l\";\n   case ButtonType::ZR:\n      return \"button_zr\";\n   case ButtonType::ZL:\n      return \"button_zl\";\n   case ButtonType::Plus:\n      return \"button_plus\";\n   case ButtonType::Minus:\n      return \"button_minus\";\n   case ButtonType::Home:\n      return \"button_home\";\n   case ButtonType::Sync:\n      return \"button_sync\";\n   case ButtonType::DpadUp:\n      return \"dpad_up\";\n   case ButtonType::DpadDown:\n      return \"dpad_down\";\n   case ButtonType::DpadLeft:\n      return \"dpad_left\";\n   case ButtonType::DpadRight:\n      return \"dpad_right\";\n   case ButtonType::LeftStickPress:\n      return \"left_stick_press\";\n   case ButtonType::LeftStickUp:\n      return \"left_stick_up\";\n   case ButtonType::LeftStickDown:\n      return \"left_stick_down\";\n   case ButtonType::LeftStickLeft:\n      return \"left_stick_left\";\n   case ButtonType::LeftStickRight:\n      return \"left_stick_right\";\n   case ButtonType::RightStickPress:\n      return \"right_stick_press\";\n   case ButtonType::RightStickUp:\n      return \"right_stick_up\";\n   case ButtonType::RightStickDown:\n      return \"right_stick_down\";\n   case ButtonType::RightStickLeft:\n      return \"right_stick_left\";\n   case ButtonType::RightStickRight:\n      return \"right_stick_right\";\n   default:\n      return \"\";\n   }\n}\n\nbool\nloadFromTOML(const toml::table &config,\n             InputConfiguration &inputConfiguration)\n{\n   auto controllers = config.at_path(\"input.controller\").as_array();\n   if (!controllers) {\n      return true;\n   }\n\n   for (const auto &controllerConfig : *controllers) {\n      auto controllerConfigTable = controllerConfig.as_table();\n      if (!controllerConfigTable) {\n         continue;\n      }\n      auto &controller = inputConfiguration.controllers.emplace_back();\n\n      auto controllerType = controllerConfigTable->get_as<std::string>(\"type\");\n      if (!controllerType) {\n         continue;\n      } else if (**controllerType == \"gamepad\") {\n         controller.type = ControllerType::Gamepad;\n      } else if (**controllerType == \"wiimote\") {\n         controller.type = ControllerType::WiiMote;\n      } else if (**controllerType == \"pro\") {\n         controller.type = ControllerType::ProController;\n      } else if (**controllerType == \"classic\") {\n         controller.type = ControllerType::ClassicController;\n      } else {\n         continue;\n      }\n\n      auto readInputConfig =\n         [](const toml::table &controllerConfig,\n            InputConfiguration::Controller &controller,\n            ButtonType buttonType)\n         {\n            auto &input = controller.inputs[static_cast<size_t>(buttonType)];\n            auto buttonConfig = controllerConfig.get_as<toml::table>(getConfigButtonName(buttonType));\n            if (buttonConfig) {\n               if (auto guid = buttonConfig->get_as<std::string>(\"sdl_joystick_guid\"); guid) {\n                  input.joystickGuid = SDL_JoystickGetGUIDFromString(guid->get().c_str());\n               }\n\n               if (auto id = buttonConfig->get_as<int64_t>(\"sdl_joystick_duplicate_id\"); id) {\n                  input.joystickDuplicateId = id->get();\n               }\n\n               if (auto key = buttonConfig->get_as<int64_t>(\"key\"); key) {\n                  input.source = InputConfiguration::Input::KeyboardKey;\n                  input.id = key->get();\n               }\n\n               if (auto button = buttonConfig->get_as<int64_t>(\"button\"); button) {\n                  input.source = InputConfiguration::Input::JoystickButton;\n                  input.id = button->get();\n               }\n\n               if (auto axis = buttonConfig->get_as<int64_t>(\"axis\"); axis) {\n                  input.source = InputConfiguration::Input::JoystickAxis;\n                  input.id = axis->get();\n               }\n\n               if (auto hat = buttonConfig->get_as<int64_t>(\"hat\"); hat) {\n                  input.source = InputConfiguration::Input::JoystickHat;\n                  input.id = hat->get();\n                  input.hatValue = 0;\n               }\n\n               if (auto hatValue = buttonConfig->get_as<int64_t>(\"hat_value\"); hatValue) {\n                  input.hatValue = hatValue->get();\n               }\n\n               if (auto invert = buttonConfig->get_as<bool>(\"invert\"); invert) {\n                  input.invert = invert->get();\n               }\n            }\n         };\n\n      for (auto i = 0u; i < static_cast<size_t>(ButtonType::MaxButtonType); ++i) {\n         readInputConfig(*controllerConfigTable, controller, static_cast<ButtonType>(i));\n      }\n   }\n   return true;\n}\n\nbool\nsaveToTOML(toml::table &config,\n           const InputConfiguration &inputConfiguration)\n{\n   auto input = config.insert(\"input\", toml::table()).first->second.as_table();\n\n   auto controllers = toml::array();\n   if (input->contains(\"controller\")) {\n      input->erase(\"controller\");\n   }\n\n   for (auto &controller : inputConfiguration.controllers) {\n      auto controllerConfig = toml::table();\n      if (controller.type == ControllerType::Gamepad) {\n         controllerConfig.insert_or_assign(\"type\", \"gamepad\");\n      } else if (controller.type == ControllerType::WiiMote) {\n         controllerConfig.insert_or_assign(\"type\", \"wiimote\");\n      } else if (controller.type == ControllerType::ProController) {\n         controllerConfig.insert_or_assign(\"type\", \"pro\");\n      } else if (controller.type == ControllerType::ClassicController) {\n         controllerConfig.insert_or_assign(\"type\", \"classic\");\n      } else {\n         continue;\n      }\n\n      for (auto i = 0u; i < static_cast<size_t>(ButtonType::MaxButtonType); ++i) {\n         auto buttonType = static_cast<ButtonType>(i);\n         auto &input = controller.inputs[i];\n         auto inputConfig = toml::table();\n         if (input.source == InputConfiguration::Input::Unassigned) {\n            continue;\n         } else if (input.source == InputConfiguration::Input::KeyboardKey) {\n            inputConfig.insert_or_assign(\"key\", input.id);\n         } else if (input.source == InputConfiguration::Input::JoystickAxis) {\n            char guidBuffer[33];\n            SDL_JoystickGetGUIDString(input.joystickGuid, guidBuffer, 33);\n            inputConfig.insert_or_assign(\"sdl_joystick_guid\", guidBuffer);\n            inputConfig.insert_or_assign(\"sdl_joystick_duplicate_id\", input.joystickDuplicateId);\n            inputConfig.insert_or_assign(\"axis\", input.id);\n            inputConfig.insert_or_assign(\"invert\", input.invert);\n         } else if (input.source == InputConfiguration::Input::JoystickButton) {\n            char guidBuffer[33];\n            SDL_JoystickGetGUIDString(input.joystickGuid, guidBuffer, 33);\n            inputConfig.insert_or_assign(\"sdl_joystick_guid\", guidBuffer);\n            inputConfig.insert_or_assign(\"sdl_joystick_duplicate_id\", input.joystickDuplicateId);\n            inputConfig.insert_or_assign(\"button\", input.id);\n         } else if (input.source == InputConfiguration::Input::JoystickHat) {\n            char guidBuffer[33];\n            SDL_JoystickGetGUIDString(input.joystickGuid, guidBuffer, 33);\n            inputConfig.insert_or_assign(\"sdl_joystick_guid\", guidBuffer);\n            inputConfig.insert_or_assign(\"sdl_joystick_duplicate_id\", input.joystickDuplicateId);\n            inputConfig.insert_or_assign(\"hat\", input.id);\n            inputConfig.insert_or_assign(\"hat_value\", input.hatValue);\n         }\n\n         controllerConfig.insert_or_assign(getConfigButtonName(buttonType), inputConfig);\n      }\n\n      controllers.push_back(std::move(controllerConfig));\n   }\n\n   input->insert_or_assign(\"controller\", std::move(controllers));\n   return true;\n}\n\nbool\nloadFromTOML(const toml::table &config,\n             SoundSettings &soundSettings)\n{\n   config::readValue(config, \"sound.playback_enabled\", soundSettings.playbackEnabled);\n   return true;\n}\n\nbool\nsaveToTOML(toml::table &config,\n           const SoundSettings &soundSettings)\n{\n   auto sound = config.insert(\"sound\", toml::table()).first->second.as_table();\n   sound->insert_or_assign(\"playback_enabled\", soundSettings.playbackEnabled);\n   return true;\n}\n\n\nstatic const char *\ntranslateTitleListMode(UiSettings::TitleListMode mode)\n{\n   if (mode == UiSettings::TitleListMode::TitleList) {\n      return \"list\";\n   } else if (mode == UiSettings::TitleListMode::TitleGrid) {\n      return \"grid\";\n   }\n\n   return \"\";\n}\n\nstatic std::optional<UiSettings::TitleListMode>\ntranslateTitleListMode(const std::string &text)\n{\n   if (text == \"list\") {\n      return UiSettings::TitleListMode::TitleList;\n   } else if (text == \"grid\") {\n      return UiSettings::TitleListMode::TitleGrid;\n   }\n\n   return { };\n}\n\nbool\nloadFromTOML(const toml::table &config,\n             UiSettings &uiSettings)\n{\n   std::string titleListModeText;\n   config::readValue(config, \"ui.title_list_mode\", titleListModeText);\n   if (auto mode = translateTitleListMode(titleListModeText); mode) {\n      uiSettings.titleListMode = *mode;\n   }\n\n   return true;\n}\n\nbool\nsaveToTOML(toml::table &config,\n           const UiSettings &uiSettings)\n{\n   auto ui = config.insert(\"ui\", toml::table()).first->second.as_table();\n\n   if (auto text = translateTitleListMode(uiSettings.titleListMode); text) {\n      ui->insert_or_assign(\"title_list_mode\", text);\n   }\n\n   return true;\n}\n"
  },
  {
    "path": "src/decaf-qt/src/settings.h",
    "content": "#pragma once\n#include <array>\n#include <memory>\n#include <mutex>\n#include <vector>\n#include <libcpu/cpu_config.h>\n#include <libdecaf/decaf_config.h>\n#include <libgpu/gpu_config.h>\n#include <SDL_joystick.h>\n\n#include <QColor>\n#include <QObject>\n\nenum class ButtonType\n{\n   A,\n   B,\n   X,\n   Y,\n   R,\n   L,\n   ZR,\n   ZL,\n   Plus,\n   Minus,\n   Home,\n   Sync,\n   DpadUp,\n   DpadDown,\n   DpadLeft,\n   DpadRight,\n   LeftStickPress,\n   LeftStickUp,\n   LeftStickDown,\n   LeftStickLeft,\n   LeftStickRight,\n   RightStickPress,\n   RightStickUp,\n   RightStickDown,\n   RightStickLeft,\n   RightStickRight,\n   MaxButtonType,\n};\n\nenum class ControllerType\n{\n   Invalid,\n   Gamepad,\n   WiiMote,\n   ProController,\n   ClassicController,\n};\n\nstruct UiSettings\n{\n   enum TitleListMode\n   {\n      TitleList,\n      TitleGrid,\n   };\n\n   TitleListMode titleListMode = TitleList;\n};\n\nstruct InputConfiguration\n{\n   struct Input\n   {\n      enum Source\n      {\n         Unassigned,\n         KeyboardKey,\n         JoystickButton,\n         JoystickAxis,\n         JoystickHat,\n      };\n\n      Source source = Source::Unassigned;\n      int id = -1;\n\n      // Only valid for Source==Joystick*\n      SDL_JoystickGUID joystickGuid;\n      int joystickDuplicateId = 0; // For when there are multiple controllers with the same GUID\n\n      // Only valid for Type==JoystickAxis\n      bool invert = false; // TODO: Do we want to allow invert on everything?\n      // TODO: deadzone? etc\n\n      // Only valid for Source==JoystickHat\n      int hatValue = -1;\n\n      // Not loaded from config file, assigned on controller connect!\n      SDL_Joystick *joystick = nullptr;\n      SDL_JoystickID joystickInstanceId = -1;\n   };\n\n   struct Controller\n   {\n      ControllerType type = ControllerType::Invalid;\n      std::array<Input, static_cast<size_t>(ButtonType::MaxButtonType)> inputs { };\n   };\n\n   std::vector<Controller> controllers;\n   std::array<Controller *, 4> wpad = { nullptr, nullptr, nullptr, nullptr };\n   std::array<Controller *, 2> vpad = { nullptr, nullptr };\n};\n\nstruct SoundSettings\n{\n   //! Whether audio playback is enabled.\n   bool playbackEnabled = false;\n};\n\nstruct Settings\n{\n   decaf::Settings decaf = { };\n   cpu::Settings cpu = { };\n   gpu::Settings gpu = { };\n\n   UiSettings ui = { };\n   InputConfiguration input = { };\n   SoundSettings sound = { };\n};\n\nbool loadSettings(const std::string &path, Settings &settings);\nbool saveSettings(const std::string &path, const Settings &settings);\n\nclass SettingsStorage : public QObject\n{\n   Q_OBJECT\npublic:\n   SettingsStorage(std::string path) :\n      mSettingsStorage(std::make_shared<Settings>()),\n      mPath(std::move(path))\n   {\n      loadSettings(mPath, *mSettingsStorage);\n   }\n\n   std::string path()\n   {\n      std::lock_guard<std::mutex> lock { mMutex };\n      return mPath;\n   }\n\n   std::shared_ptr<const Settings> get()\n   {\n      std::lock_guard<std::mutex> lock { mMutex };\n      return mSettingsStorage;\n   }\n\n   void set(const Settings &settings)\n   {\n      mMutex.lock();\n      mSettingsStorage = std::make_shared<Settings>(settings);\n      saveSettings(mPath, settings);\n      mMutex.unlock();\n\n      emit settingsChanged();\n   }\n\nsignals:\n   void settingsChanged();\n\nprivate:\n   std::mutex mMutex;\n   std::string mPath;\n   std::shared_ptr<Settings> mSettingsStorage;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/softwarekeyboarddriver.cpp",
    "content": "#include \"softwarekeyboarddriver.h\"\n\nSoftwareKeyboardDriver::SoftwareKeyboardDriver(QObject *parent) :\n   QObject(parent)\n{\n}\n\nvoid\nSoftwareKeyboardDriver::acceptInput(QString text)\n{\n   decaf::SoftwareKeyboardDriver::setInputString(text.toStdU16String());\n   decaf::SoftwareKeyboardDriver::accept();\n}\n\nvoid\nSoftwareKeyboardDriver::rejectInput()\n{\n   decaf::SoftwareKeyboardDriver::reject();\n}\n\nvoid\nSoftwareKeyboardDriver::onOpen(std::u16string defaultText)\n{\n   emit open(QString::fromStdU16String(defaultText));\n}\n\nvoid\nSoftwareKeyboardDriver::onClose()\n{\n   emit close();\n}\n\nvoid\nSoftwareKeyboardDriver::onInputStringChanged(std::u16string text)\n{\n   emit inputStringChanged(QString::fromStdU16String(text));\n}\n"
  },
  {
    "path": "src/decaf-qt/src/softwarekeyboarddriver.h",
    "content": "#pragma once\n#include <libdecaf/decaf_softwarekeyboard.h>\n#include <QObject>\n\nclass QInputDialog;\nclass QWidget;\n\nclass SoftwareKeyboardDriver : public QObject, public decaf::SoftwareKeyboardDriver\n{\n   Q_OBJECT\n\npublic:\n   SoftwareKeyboardDriver(QObject *parent);\n\n   void acceptInput(QString text);\n   void rejectInput();\n\nsignals:\n   void open(QString text);\n   void close();\n   void inputStringChanged(QString text);\n\nprivate:\n   void onOpen(std::u16string defaultText) override;\n   void onClose() override;\n   void onInputStringChanged(std::u16string text) override;\n};\n"
  },
  {
    "path": "src/decaf-qt/src/sounddriver.cpp",
    "content": "#include \"sounddriver.h\"\n\n#ifdef QT_MULTIMEDIA_LIB\nbool\nSoundDriver::start(unsigned outputRate, unsigned numChannels)\n{\n   QAudioFormat format;\n   format.setChannelCount(numChannels);\n   format.setCodec(\"audio/pcm\");\n   format.setSampleRate(outputRate);\n   format.setSampleSize(16);\n   format.setSampleType(QAudioFormat::SignedInt);\n   format.setByteOrder(QAudioFormat::LittleEndian);\n\n   QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());\n   if (!info.isFormatSupported(format)) {\n      qWarning() << \"Raw audio format not supported by backend, cannot play audio.\";\n      return false;\n   }\n\n   mAudioOutput = new QAudioOutput(format, this);\n   mAudioIo = mAudioOutput->start();\n   return true;\n}\n\nvoid\nSoundDriver::output(int16_t *samples, unsigned numSamples)\n{\n   mAudioIo->write(reinterpret_cast<const char *>(samples), numSamples * sizeof(int16_t));\n}\n\nvoid\nSoundDriver::stop()\n{\n   mAudioOutput->stop();\n   delete mAudioOutput;\n\n   mAudioOutput = nullptr;\n   mAudioIo = nullptr;\n}\n#else\n#include <common/decaf_assert.h>\n#include <SDL.h>\n#include <libdecaf/decaf_log.h>\n\nSoundDriver::SoundDriver(SettingsStorage *settingsStorage) :\n   mSettingsStorage(settingsStorage)\n{\n   QObject::connect(mSettingsStorage, &SettingsStorage::settingsChanged,\n                    this, &SoundDriver::settingsChanged);\n   settingsChanged();\n}\n\nSoundDriver::~SoundDriver()\n{\n   SDL_CloseAudio();\n}\n\nvoid\nSoundDriver::settingsChanged()\n{\n   auto settings = mSettingsStorage->get();\n   if (mPlaybackEnabled != settings->sound.playbackEnabled) {\n      mPlaybackEnabled = settings->sound.playbackEnabled;\n\n      if (settings->sound.playbackEnabled) {\n         SDL_PauseAudio(0);\n      } else {\n         SDL_PauseAudio(1);\n      }\n   }\n}\n\nbool\nSoundDriver::start(unsigned outputRate,\n                   unsigned numChannels)\n{\n   auto mFrameLength = 30; // TODO: Config\n   mNumChannelsIn = numChannels;\n   mNumChannelsOut = std::min(numChannels, 2u);  // TODO: support surround output\n   mOutputFrameLen = mFrameLength * (outputRate / 1000);\n\n   // Set up the ring buffer with enough space for 3 output frames of audio\n   mOutputBuffer.resize(mOutputFrameLen * mNumChannelsOut * 3);\n   mBufferWritePos = 0;\n   mBufferReadPos = 0;\n\n   SDL_AudioSpec audiospec;\n   audiospec.format = AUDIO_S16LSB;\n   audiospec.freq = outputRate;\n   audiospec.channels = static_cast<Uint8>(mNumChannelsOut);\n   audiospec.samples = static_cast<Uint16>(mOutputFrameLen);\n   audiospec.callback = sdlCallback;\n   audiospec.userdata = this;\n\n   if (SDL_OpenAudio(&audiospec, nullptr) != 0) {\n      return false;\n   }\n\n   SDL_PauseAudio(0);\n   return true;\n}\n\nvoid\nSoundDriver::output(int16_t *samples, unsigned numSamples)\n{\n   if (!mPlaybackEnabled) {\n      return;\n   }\n\n   // Discard channels from the input if necessary.\n   if (mNumChannelsIn != mNumChannelsOut) {\n      decaf_check(mNumChannelsOut < mNumChannelsIn);\n\n      for (auto sample = 1u; sample < numSamples; ++sample) {\n         for (auto channel = 0u; channel < mNumChannelsOut; channel++) {\n            samples[sample * mNumChannelsOut + channel] = samples[sample * mNumChannelsIn + channel];\n         }\n      }\n   }\n\n   // Copy to the output buffer, ignoring the possibility of overrun\n   //  (which should never happen anyway).\n   auto numSamplesOut = static_cast<size_t>(numSamples * mNumChannelsOut);\n\n   while (mBufferWritePos + numSamplesOut >= mOutputBuffer.size()) {\n      auto samplesToCopy = mOutputBuffer.size() - mBufferWritePos;\n      std::memcpy(&mOutputBuffer[mBufferWritePos], samples, samplesToCopy * 2);\n\n      mBufferWritePos = 0;\n      samples += samplesToCopy;\n      numSamplesOut -= samplesToCopy;\n   }\n\n   std::memcpy(&mOutputBuffer[mBufferWritePos], samples, numSamplesOut * 2);\n   mBufferWritePos += numSamplesOut;\n}\n\nvoid\nSoundDriver::stop()\n{\n   SDL_CloseAudio();\n}\n\nvoid\nSoundDriver::sdlCallback(void *instance_, Uint8 *stream_, int size)\n{\n   SoundDriver *instance = reinterpret_cast<SoundDriver *>(instance_);\n   int16_t *stream = reinterpret_cast<int16_t *>(stream_);\n   decaf_check(size >= 0);\n   decaf_check(size % (2 * instance->mNumChannelsOut) == 0);\n   auto numSamples = static_cast<size_t>(size) / 2;\n   auto samplesAvail = (instance->mBufferWritePos + instance->mOutputBuffer.size() - instance->mBufferReadPos) % instance->mOutputBuffer.size();\n\n   if (samplesAvail < numSamples) {\n      // Rather than outputting the partial frame, output a full frame of\n      //  silence to give audio generation a chance to catch up.\n      std::memset(stream, 0, size);\n   } else {\n      decaf_check(instance->mBufferReadPos + numSamples <= instance->mOutputBuffer.size());\n      std::memcpy(stream, &instance->mOutputBuffer[instance->mBufferReadPos], size);\n      instance->mBufferReadPos = (instance->mBufferReadPos + numSamples) % instance->mOutputBuffer.size();\n   }\n}\n#endif\n"
  },
  {
    "path": "src/decaf-qt/src/sounddriver.h",
    "content": "#pragma once\n#include \"settings.h\"\n\n#ifdef QT_MULTIMEDIA_LIB\n#include <QAudioOutput>\n#include <QObject>\n#include <QDebug>\n\n#include <libdecaf/decaf_sound.h>\n\nclass SoundDriver : public QObject, public decaf::SoundDriver\n{\npublic:\n   ~SoundDriver() override = default;\n\n   bool start(unsigned outputRate, unsigned numChannels) override;\n   void output(int16_t *samples, unsigned numSamples) override;\n   void stop() override;\n\nprivate:\n   QAudioOutput *mAudioOutput;\n   QIODevice *mAudioIo;\n};\n#else\n#include <atomic>\n#include <libdecaf/decaf_sound.h>\n#include <SDL.h>\n#include <spdlog/spdlog.h>\n#include <vector>\n\n#include <QObject>\n\nclass SettingsStorage;\n\nclass SoundDriver : public QObject, public decaf::SoundDriver\n{\n   Q_OBJECT\n\npublic:\n   SoundDriver(SettingsStorage *settingsStorage);\n   ~SoundDriver() override;\n\nprivate:\n   bool start(unsigned outputRate, unsigned numChannels) override;\n   void output(int16_t *samples, unsigned numSamples) override;\n   void stop() override;\n\nprivate slots:\n   void settingsChanged();\n\nprivate:\n   SettingsStorage *mSettingsStorage = nullptr;\n   std::atomic<bool> mPlaybackEnabled { true };\n\n   // Number of channels of data we receive in output()\n   unsigned mNumChannelsIn = 0;\n\n   // Number of channels we send to the audio device\n   unsigned mNumChannelsOut = 0;\n\n   // Number of samples (per channel) in an output frame\n   unsigned mOutputFrameLen = 0;\n\n   // Output buffer (ring buffer): written by output(), read by SDL callback\n   std::vector<int16_t> mOutputBuffer = { };\n\n   // Index of next sample (array element) to write\n   size_t mBufferWritePos = 0u;\n\n   // Index of next sample (array element) to read\n   size_t mBufferReadPos = 0u;\n\n   static void sdlCallback(void *instance, Uint8 *stream, int size);\n};\n#endif\n"
  },
  {
    "path": "src/decaf-qt/src/tgahandler.cpp",
    "content": "/* This file is part of the KDE project\n   Copyright (C) 2003 Dominik Seichter <domseichter@web.de>\n   Copyright (C) 2004 Ignacio Castao <castano@ludicon.com>\n\n   This program is free software; you can redistribute it and/or\n   modify it under the terms of the Lesser GNU General Public\n   License as published by the Free Software Foundation; either\n   version 2 of the License, or (at your option) any later version.\n*/\n\n/* this code supports:\n * reading:\n *     uncompressed and run length encoded indexed, grey and color tga files.\n *     image types 1, 2, 3, 9, 10 and 11.\n *     only RGB color maps with no more than 256 colors.\n *     pixel formats 8, 16, 24 and 32.\n * writing:\n *     uncompressed true color tga files\n */\n\n#include \"tgahandler.h\"\n\n#include <assert.h>\n\n#include <QImage>\n#include <QDataStream>\n#include <QDebug>\n\ntypedef quint32 uint;\ntypedef quint16 ushort;\ntypedef quint8 uchar;\n\nnamespace   // Private.\n{\n\n// Header format of saved files.\nuchar targaMagic[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 };\n\nenum TGAType {\n    TGA_TYPE_INDEXED        = 1,\n    TGA_TYPE_RGB            = 2,\n    TGA_TYPE_GREY           = 3,\n    TGA_TYPE_RLE_INDEXED    = 9,\n    TGA_TYPE_RLE_RGB        = 10,\n    TGA_TYPE_RLE_GREY       = 11\n};\n\n#define TGA_INTERLEAVE_MASK 0xc0\n#define TGA_INTERLEAVE_NONE 0x00\n#define TGA_INTERLEAVE_2WAY 0x40\n#define TGA_INTERLEAVE_4WAY 0x80\n\n#define TGA_ORIGIN_MASK     0x30\n#define TGA_ORIGIN_LEFT     0x00\n#define TGA_ORIGIN_RIGHT    0x10\n#define TGA_ORIGIN_LOWER    0x00\n#define TGA_ORIGIN_UPPER    0x20\n\n/** Tga Header. */\nstruct TgaHeader {\n    uchar id_length;\n    uchar colormap_type;\n    uchar image_type;\n    ushort colormap_index;\n    ushort colormap_length;\n    uchar colormap_size;\n    ushort x_origin;\n    ushort y_origin;\n    ushort width;\n    ushort height;\n    uchar pixel_size;\n    uchar flags;\n\n    enum { SIZE = 18 }; // const static int SIZE = 18;\n};\n\nstatic QDataStream &operator>> (QDataStream &s, TgaHeader &head)\n{\n    s >> head.id_length;\n    s >> head.colormap_type;\n    s >> head.image_type;\n    s >> head.colormap_index;\n    s >> head.colormap_length;\n    s >> head.colormap_size;\n    s >> head.x_origin;\n    s >> head.y_origin;\n    s >> head.width;\n    s >> head.height;\n    s >> head.pixel_size;\n    s >> head.flags;\n    /*qDebug() << \"id_length: \" << head.id_length << \" - colormap_type: \" << head.colormap_type << \" - image_type: \" << head.image_type;\n    qDebug() << \"colormap_index: \" << head.colormap_index << \" - colormap_length: \" << head.colormap_length << \" - colormap_size: \" << head.colormap_size;\n    qDebug() << \"x_origin: \" << head.x_origin << \" - y_origin: \" << head.y_origin << \" - width:\" << head.width << \" - height:\" << head.height << \" - pixelsize: \" << head.pixel_size << \" - flags: \" << head.flags;*/\n    return s;\n}\n\nstatic bool IsSupported(const TgaHeader &head)\n{\n    if (head.image_type != TGA_TYPE_INDEXED &&\n            head.image_type != TGA_TYPE_RGB &&\n            head.image_type != TGA_TYPE_GREY &&\n            head.image_type != TGA_TYPE_RLE_INDEXED &&\n            head.image_type != TGA_TYPE_RLE_RGB &&\n            head.image_type != TGA_TYPE_RLE_GREY) {\n        return false;\n    }\n    if (head.image_type == TGA_TYPE_INDEXED ||\n            head.image_type == TGA_TYPE_RLE_INDEXED) {\n        if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) {\n            return false;\n        }\n    }\n    if (head.image_type == TGA_TYPE_RGB ||\n            head.image_type == TGA_TYPE_GREY ||\n            head.image_type == TGA_TYPE_RLE_RGB ||\n            head.image_type == TGA_TYPE_RLE_GREY) {\n        if (head.colormap_type != 0) {\n            return false;\n        }\n    }\n    if (head.width == 0 || head.height == 0) {\n        return false;\n    }\n    if (head.pixel_size != 8 && head.pixel_size != 16 &&\n            head.pixel_size != 24 && head.pixel_size != 32) {\n        return false;\n    }\n    return true;\n}\n\nstruct Color555 {\n    ushort b : 5;\n    ushort g : 5;\n    ushort r : 5;\n};\n\nstruct TgaHeaderInfo {\n    bool rle;\n    bool pal;\n    bool rgb;\n    bool grey;\n\n    TgaHeaderInfo(const TgaHeader &tga) : rle(false), pal(false), rgb(false), grey(false)\n    {\n        switch (tga.image_type) {\n        case TGA_TYPE_RLE_INDEXED:\n            rle = true;\n        Q_FALLTHROUGH();\n        // no break is intended!\n        case TGA_TYPE_INDEXED:\n            pal = true;\n            break;\n\n        case TGA_TYPE_RLE_RGB:\n            rle = true;\n        Q_FALLTHROUGH();\n        // no break is intended!\n        case TGA_TYPE_RGB:\n            rgb = true;\n            break;\n\n        case TGA_TYPE_RLE_GREY:\n            rle = true;\n        Q_FALLTHROUGH();\n        // no break is intended!\n        case TGA_TYPE_GREY:\n            grey = true;\n            break;\n\n        default:\n            // Error, unknown image type.\n            break;\n        }\n    }\n};\n\nstatic bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)\n{\n    // Create image.\n    img = QImage(tga.width, tga.height, QImage::Format_RGB32);\n\n    TgaHeaderInfo info(tga);\n\n    // Bits 0-3 are the numbers of alpha bits (can be zero!)\n    const int numAlphaBits = tga.flags & 0xf;\n    // However alpha exists only in the 32 bit format.\n    if ((tga.pixel_size == 32) && (tga.flags & 0xf)) {\n        img = QImage(tga.width, tga.height, QImage::Format_ARGB32);\n\n        if (numAlphaBits > 8) {\n            return false;\n        }\n    }\n\n    uint pixel_size = (tga.pixel_size / 8);\n    qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;\n\n    if (size < 1) {\n//          qDebug() << \"This TGA file is broken with size \" << size;\n        return false;\n    }\n\n    // Read palette.\n    static const int max_palette_size = 768;\n    char palette[max_palette_size];\n    if (info.pal) {\n        // @todo Support palettes in other formats!\n        const int palette_size = 3 * tga.colormap_length;\n        if (palette_size > max_palette_size) {\n            return false;\n        }\n        const int dataRead = s.readRawData(palette, palette_size);\n        if (dataRead < 0) {\n            return false;\n        }\n        if (dataRead < max_palette_size) {\n            memset(&palette[dataRead], 0, max_palette_size - dataRead);\n        }\n    }\n\n    // Allocate image.\n    uchar *const image = reinterpret_cast<uchar*>(malloc(size));\n    if (!image) {\n        return false;\n    }\n\n    bool valid = true;\n\n    if (info.rle) {\n        // Decode image.\n        char *dst = (char *)image;\n        qint64 num = size;\n\n        while (num > 0) {\n            if (s.atEnd()) {\n                valid = false;\n                break;\n            }\n\n            // Get packet header.\n            uchar c;\n            s >> c;\n\n            uint count = (c & 0x7f) + 1;\n            num -= count * pixel_size;\n            if (num < 0) {\n                valid = false;\n                break;\n            }\n\n            if (c & 0x80) {\n                // RLE pixels.\n                assert(pixel_size <= 8);\n                char pixel[8];\n                const int dataRead = s.readRawData(pixel, pixel_size);\n                if (dataRead < (int)pixel_size) {\n                    memset(&pixel[dataRead], 0, pixel_size - dataRead);\n                }\n                do {\n                    memcpy(dst, pixel, pixel_size);\n                    dst += pixel_size;\n                } while (--count);\n            } else {\n                // Raw pixels.\n                count *= pixel_size;\n                const int dataRead = s.readRawData(dst, count);\n                if (dataRead < 0) {\n                    free(image);\n                    return false;\n                }\n                if ((uint)dataRead < count) {\n                    memset(&dst[dataRead], 0, count - dataRead);\n                }\n                dst += count;\n            }\n        }\n    } else {\n        // Read raw image.\n        const int dataRead = s.readRawData((char *)image, size);\n        if (dataRead < 0) {\n            free(image);\n            return false;\n        }\n        if (dataRead < size) {\n            memset(&image[dataRead], 0, size - dataRead);\n        }\n    }\n\n    if (!valid) {\n        free(image);\n        return false;\n    }\n\n    // Convert image to internal format.\n    int y_start, y_step, y_end;\n    if (tga.flags & TGA_ORIGIN_UPPER) {\n        y_start = 0;\n        y_step = 1;\n        y_end = tga.height;\n    } else {\n        y_start = tga.height - 1;\n        y_step = -1;\n        y_end = -1;\n    }\n\n    uchar *src = image;\n\n    for (int y = y_start; y != y_end; y += y_step) {\n        QRgb *scanline = (QRgb *) img.scanLine(y);\n\n        if (info.pal) {\n            // Paletted.\n            for (int x = 0; x < tga.width; x++) {\n                uchar idx = *src++;\n                scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]);\n            }\n        } else if (info.grey) {\n            // Greyscale.\n            for (int x = 0; x < tga.width; x++) {\n                scanline[x] = qRgb(*src, *src, *src);\n                src++;\n            }\n        } else {\n            // True Color.\n            if (tga.pixel_size == 16) {\n                for (int x = 0; x < tga.width; x++) {\n                    Color555 c = *reinterpret_cast<Color555 *>(src);\n                    scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2));\n                    src += 2;\n                }\n            } else if (tga.pixel_size == 24) {\n                for (int x = 0; x < tga.width; x++) {\n                    scanline[x] = qRgb(src[2], src[1], src[0]);\n                    src += 3;\n                }\n            } else if (tga.pixel_size == 32) {\n                for (int x = 0; x < tga.width; x++) {\n                    // ### TODO: verify with images having really some alpha data\n                    const uchar alpha = (src[3] << (8 - numAlphaBits));\n                    scanline[x] = qRgba(src[2], src[1], src[0], alpha);\n                    src += 4;\n                }\n            }\n        }\n    }\n\n    // Free image.\n    free(image);\n\n    return true;\n}\n\n} // namespace\n\nTGAHandler::TGAHandler()\n{\n}\n\nbool TGAHandler::canRead() const\n{\n    if (canRead(device())) {\n        setFormat(\"tga\");\n        return true;\n    }\n    return false;\n}\n\nbool TGAHandler::read(QImage *outImage)\n{\n    //qDebug() << \"Loading TGA file!\";\n\n    QDataStream s(device());\n    s.setByteOrder(QDataStream::LittleEndian);\n\n    // Read image header.\n    TgaHeader tga;\n    s >> tga;\n    s.device()->seek(TgaHeader::SIZE + tga.id_length);\n\n    // Check image file format.\n    if (s.atEnd()) {\n//         qDebug() << \"This TGA file is not valid.\";\n        return false;\n    }\n\n    // Check supported file types.\n    if (!IsSupported(tga)) {\n//         qDebug() << \"This TGA file is not supported.\";\n        return false;\n    }\n\n    QImage img;\n    bool result = LoadTGA(s, tga, img);\n\n    if (result == false) {\n//         qDebug() << \"Error loading TGA file.\";\n        return false;\n    }\n\n    *outImage = img;\n    return true;\n}\n\nbool TGAHandler::write(const QImage &image)\n{\n    QDataStream s(device());\n    s.setByteOrder(QDataStream::LittleEndian);\n\n    const QImage &img = image;\n    const bool hasAlpha = (img.format() == QImage::Format_ARGB32);\n    for (int i = 0; i < 12; i++) {\n        s << targaMagic[i];\n    }\n\n    // write header\n    s << quint16(img.width());   // width\n    s << quint16(img.height());   // height\n    s << quint8(hasAlpha ? 32 : 24);   // depth (24 bit RGB + 8 bit alpha)\n    s << quint8(hasAlpha ? 0x24 : 0x20);   // top left image (0x20) + 8 bit alpha (0x4)\n\n    for (int y = 0; y < img.height(); y++)\n        for (int x = 0; x < img.width(); x++) {\n            const QRgb color = img.pixel(x, y);\n            s << quint8(qBlue(color));\n            s << quint8(qGreen(color));\n            s << quint8(qRed(color));\n            if (hasAlpha) {\n                s << quint8(qAlpha(color));\n            }\n        }\n\n    return true;\n}\n\nbool TGAHandler::canRead(QIODevice *device)\n{\n    if (!device) {\n        qWarning(\"TGAHandler::canRead() called with no device\");\n        return false;\n    }\n\n    qint64 oldPos = device->pos();\n    QByteArray head = device->read(TgaHeader::SIZE);\n    int readBytes = head.size();\n\n    if (device->isSequential()) {\n        for (int pos = readBytes - 1; pos >= 0; --pos) {\n            device->ungetChar(head[pos]);\n        }\n    } else {\n        device->seek(oldPos);\n    }\n\n    if (readBytes < TgaHeader::SIZE) {\n        return false;\n    }\n\n    QDataStream stream(head);\n    stream.setByteOrder(QDataStream::LittleEndian);\n    TgaHeader tga;\n    stream >> tga;\n    return IsSupported(tga);\n}\n"
  },
  {
    "path": "src/decaf-qt/src/tgahandler.h",
    "content": "/* This file is part of the KDE project\n   Copyright (C) 2003 Dominik Seichter <domseichter@web.de>\n   This program is free software; you can redistribute it and/or\n   modify it under the terms of the Lesser GNU General Public\n   License as published by the Free Software Foundation; either\n   version 2 of the License, or (at your option) any later version.\n*/\n\n#ifndef KIMG_TGA_P_H\n#define KIMG_TGA_P_H\n\n#include <QImageIOPlugin>\n\nclass TGAHandler : public QImageIOHandler\n{\npublic:\n    TGAHandler();\n\n    bool canRead() const override;\n    bool read(QImage *image) override;\n    bool write(const QImage &image) override;\n\n    static bool canRead(QIODevice *device);\n};\n\n#endif // KIMG_TGA_P_H\n"
  },
  {
    "path": "src/decaf-qt/src/titlelistmodel.h",
    "content": "#pragma once\n#include <libdecaf/decaf_content.h>\n\n#include <QAbstractTableModel>\n#include <QDir>\n#include <QString>\n#include <QPixmap>\n\nstruct TitleInfo\n{\n   QString code_path;\n   QString argstr;\n   qulonglong title_id;\n   int title_version;\n   QString longname_en;\n   QString publisher_en;\n   QPixmap icon;\n};\n\nclass TitleListModel : public QAbstractTableModel\n{\n   Q_OBJECT\n\npublic:\n   static constexpr auto TitleTypeRole = Qt::UserRole;\n   static constexpr auto TitlePathRole = Qt::UserRole + 1;\n   static constexpr auto TitleIdRole = Qt::UserRole + 2;\n\n   TitleListModel(QObject *parent = nullptr) :\n      QAbstractTableModel(parent)\n   {\n   }\n\n   ~TitleListModel()\n   {\n      qDeleteAll(mTitles);\n   }\n\n   void clear()\n   {\n      beginResetModel();\n      qDeleteAll(mTitles);\n      mTitles.clear();\n      endResetModel();\n   }\n\n   int rowCount(const QModelIndex &parent) const override\n   {\n      if (parent.isValid()) {\n         return 0;\n      }\n\n      return mTitles.size();\n   }\n\n   int columnCount(const QModelIndex &parent) const override\n   {\n      return 4;\n   }\n\n   static QString\n   getTypeString(qulonglong titleId)\n   {\n      switch (decaf::getTitleTypeFromID(titleId)) {\n      case decaf::TitleType::Application:\n         return tr(\"Application\");\n      case decaf::TitleType::Demo:\n         return tr(\"Demo\");\n      case decaf::TitleType::Data:\n         return tr(\"Data\");\n      case decaf::TitleType::DLC:\n         return tr(\"DLC\");\n      case decaf::TitleType::Update:\n         return tr(\"Update\");\n      default:\n         return tr(\"Unknown\");\n      }\n   }\n\n   QVariant data(const QModelIndex &index, int role) const override\n   {\n      if (!index.isValid()) {\n         return QVariant { };\n      }\n\n      if (index.row() >= mTitles.size() || index.row() < 0) {\n         return QVariant{ };\n      }\n\n      auto &title = *mTitles[index.row()];\n      if (role == Qt::DisplayRole) {\n         switch (index.column()) {\n         case 0:\n            return title.longname_en;\n         case 1:\n            return title.publisher_en;\n         case 2:\n            return getTypeString(title.title_id);\n         case 3:\n            return QDir { title.code_path }.filePath(title.argstr);\n         default:\n            return QVariant { };\n         }\n      } else if (role == Qt::DecorationRole) {\n         if (index.column() == 0) {\n            return title.icon;\n         }\n      } else if (role == TitleTypeRole) {\n         return getTypeString(title.title_id);\n      } else if (role == TitlePathRole) {\n         return QDir { title.code_path }.filePath(title.argstr);\n      } else if (role == TitleIdRole) {\n         return title.title_id;\n      }\n\n      return QVariant { };\n   }\n\n   QVariant headerData(int section, Qt::Orientation orientation, int role) const override\n   {\n      if (role != Qt::DisplayRole) {\n         return QVariant{ };\n      }\n\n      if (orientation == Qt::Horizontal) {\n         switch (section) {\n         case 0:\n            return tr(\"Name\");\n         case 1:\n            return tr(\"Publisher\");\n         case 2:\n            return tr(\"Type\");\n         case 3:\n            return tr(\"Path\");\n         }\n      }\n\n      return QVariant { };\n   }\n\npublic slots:\n   void addTitle(TitleInfo *info)\n   {\n      beginInsertRows({}, mTitles.size(), mTitles.size());\n      mTitles.push_back(info);\n      endInsertRows();\n   }\n\nprivate:\n   QVector<TitleInfo *> mTitles;\n};"
  },
  {
    "path": "src/decaf-qt/src/titlelistscanner.h",
    "content": "#pragma once\n#include <atomic>\n#include <QDirIterator>\n#include <QDomDocument>\n#include <QPixmap>\n#include <QObject>\n\n#include \"titlelistmodel.h\"\n#include \"tgahandler.h\"\n\nclass TitleScanner : public QObject\n{\n   Q_OBJECT\n\npublic:\n   void cancel()\n   {\n      mCancel.store(true);\n   }\n\n   void scanDirectoryList(QStringList directories)\n   {\n      for (auto scanDirectory : directories) {\n         auto itr = QDirIterator {\n            scanDirectory,\n            { \"*.rpx\" },\n            QDir::Files | QDir::Readable, QDirIterator::Subdirectories\n         };\n\n         if (mCancel.load()) {\n            break;\n         }\n\n         while (itr.hasNext() && !mCancel.load()) {\n            auto titleInfo = new TitleInfo { };\n            auto rpx = QFileInfo { itr.next() };\n            auto codeDirectory = QDir { rpx.path() };\n            titleInfo->code_path = codeDirectory.path();\n            titleInfo->argstr = rpx.fileName();\n\n            auto appXml = QFile { codeDirectory.filePath(\"app.xml\") };\n            if (appXml.open(QIODevice::ReadOnly)) {\n               auto document = QDomDocument { };\n               if (document.setContent(&appXml)) {\n                  auto app = document.documentElement();\n                  if (app.tagName() == \"app\") {\n                     titleInfo->title_id = app.firstChildElement(\"title_id\").text().trimmed().toULongLong(nullptr, 16);\n                     titleInfo->title_version = app.firstChildElement(\"title_version\").text().trimmed().toInt(nullptr, 16);\n                  }\n               }\n            }\n\n            auto cosXml = QFile { codeDirectory.filePath(\"cos.xml\") };\n            if (cosXml.open(QIODevice::ReadOnly)) {\n               auto document = QDomDocument { };\n               if (document.setContent(&cosXml)) {\n                  auto cos = document.documentElement();\n                  if (cos.tagName() == \"app\") {\n                     titleInfo->argstr = cos.firstChildElement(\"argstr\").text().trimmed();\n                  }\n               }\n            }\n\n            auto metaDirectory = QDir { codeDirectory.filePath(\"../meta\") };\n            if (metaDirectory.exists()) {\n               auto metaXml = QFile { metaDirectory.filePath(\"meta.xml\") };\n               if (metaXml.open(QIODevice::ReadOnly)) {\n                  auto document = QDomDocument { };\n                  if (document.setContent(&metaXml)) {\n                     auto meta = document.documentElement();\n                     if (meta.tagName() == \"menu\") {\n                        titleInfo->longname_en = meta.firstChildElement(\"longname_en\").text().trimmed();\n                        titleInfo->publisher_en = meta.firstChildElement(\"publisher_en\").text().trimmed();\n                     }\n                  }\n               }\n\n               auto iconTex = QFile { metaDirectory.filePath(\"iconTex.tga\") };\n               if (iconTex.open(QIODevice::ReadOnly)) {\n                  auto tgaHandler = TGAHandler { };\n                  auto image = QImage { };\n                  tgaHandler.setDevice(&iconTex);\n                  if (tgaHandler.read(&image)) {\n                     titleInfo->icon = QPixmap::fromImage(image);\n                  }\n               }\n            }\n\n            emit titleFound(titleInfo);\n         }\n      }\n\n      mCancel = false;\n      emit scanFinished();\n   }\n\nsignals:\n   void titleFound(TitleInfo *info);\n   void scanFinished();\n\nprivate:\n   std::atomic_bool mCancel { false };\n};\n"
  },
  {
    "path": "src/decaf-qt/src/titlelistwidget.cpp",
    "content": "#include \"settings.h\"\n#include \"titlelistwidget.h\"\n#include \"titlelistmodel.h\"\n#include \"titlelistscanner.h\"\n#include \"ui_titlelist.h\"\n\n#include <libdecaf/decaf_content.h>\n#include <QAction>\n#include <QApplication>\n#include <QClipboard>\n#include <QSortFilterProxyModel>\n#include <QStackedLayout>\n#include <QTreeView>\n#include <QListView>\n\nclass TitleSortFilterProxyModel : public QSortFilterProxyModel\n{\npublic:\n   TitleSortFilterProxyModel(QObject *parent = nullptr) :\n      QSortFilterProxyModel(parent)\n   {\n   }\n\n   bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override\n   {\n      auto titleId = sourceModel()->data(\n         sourceModel()->index(sourceRow, 0, sourceParent),\n         TitleListModel::TitleIdRole).toULongLong();\n\n      if (decaf::isSystemTitle(titleId)) {\n         if (!showSystemTitles) {\n            return false;\n         }\n      } else {\n         if (!showNonSystemTitles) {\n            return false;\n         }\n      }\n\n      switch (decaf::getTitleTypeFromID(titleId)) {\n      case decaf::TitleType::Application:\n         return showApplications;\n      case decaf::TitleType::Demo:\n         return showDemos;\n      case decaf::TitleType::Data:\n         return showData;\n      case decaf::TitleType::DLC:\n         return showDLC;\n      case decaf::TitleType::Update:\n         return showUpdates;\n      default:\n         return showUnknown;\n      }\n   }\n\n   bool showApplications = true;\n   bool showDemos = true;\n   bool showData = false;\n   bool showDLC = false;\n   bool showUpdates = false;\n   bool showNonSystemTitles = true;\n   bool showSystemTitles = true;\n   bool showUnknown = false;\n};\n\nTitleListWidget::TitleListWidget(SettingsStorage *settingsStorage,\n                                 QWidget *parent) :\n   QWidget(parent),\n   mSettingsStorage(settingsStorage),\n   mStackedLayout(new QStackedLayout { this })\n{\n   mStackedLayout->setContentsMargins(0, 0, 0, 0);\n\n   mTitleScanner = new TitleScanner();\n   mTitleScanner->moveToThread(&mScanThread);\n   mScanThread.start();\n\n   mProxyModel = new TitleSortFilterProxyModel { this };\n   mTitleListModel = new TitleListModel { this };\n   mProxyModel->setSourceModel(mTitleListModel);\n\n   mTitleList = new QTreeView { };\n   mTitleList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);\n   mTitleList->setSortingEnabled(true);\n   mTitleList->setModel(mProxyModel);\n   mTitleList->header()->setSortIndicator(0, Qt::AscendingOrder);\n   mTitleList->header()->setStretchLastSection(false);\n   mTitleList->header()->setSectionResizeMode(QHeaderView::ResizeToContents);\n\n   mTitleGrid = new QListView { };\n   mTitleGrid->setViewMode(QListView::IconMode);\n   mTitleGrid->setModel(mProxyModel);\n   mTitleGrid->setWrapping(true);\n   mTitleGrid->setWordWrap(true);\n   mTitleGrid->setResizeMode(QListView::Adjust);\n\n   mStackedLayout->addWidget(mTitleList);\n   mStackedLayout->addWidget(mTitleGrid);\n\n   mCopyPathAction = new QAction(tr(\"Copy title path\"), this);\n   connect(mCopyPathAction, &QAction::triggered, [&]() {\n      auto data = QVariant { };\n      if (mStackedLayout->currentIndex() == 0) {\n         data = mTitleList->model()->data(mTitleList->currentIndex(), TitleListModel::TitlePathRole);\n      } else if (mStackedLayout->currentIndex() == 1) {\n         data = mTitleGrid->model()->data(mTitleGrid->currentIndex(), TitleListModel::TitlePathRole);\n      }\n\n      if (data.isValid()) {\n         auto path = data.toString();\n         if (!path.isEmpty()) {\n            qApp->clipboard()->setText(path);\n         }\n      }\n   });\n\n   for (auto widget : { (QWidget *)mTitleGrid , (QWidget *)mTitleList }) {\n      widget->addAction(mCopyPathAction);\n      widget->setContextMenuPolicy(Qt::ActionsContextMenu);\n   }\n\n   connect(&mScanThread, &QThread::finished, mTitleScanner, &QObject::deleteLater);\n   connect(this, &TitleListWidget::scanDirectoryList, mTitleScanner, &TitleScanner::scanDirectoryList);\n   connect(mTitleScanner, &TitleScanner::titleFound, mTitleListModel, &TitleListModel::addTitle);\n   connect(mTitleScanner, &TitleScanner::scanFinished, this, &TitleListWidget::titleScanFinished);\n\n   connect(mTitleList, &QListView::doubleClicked, [&](const QModelIndex &index) {\n      auto data = mTitleList->model()->data(index, TitleListModel::TitlePathRole);\n      if (data.isValid()) {\n         auto path = data.toString();\n         if (!path.isEmpty()) {\n            launchTitle(path);\n         }\n      }\n   });\n\n   connect(mTitleGrid, &QListView::doubleClicked, [&](const QModelIndex &index) {\n      auto data = mTitleGrid->model()->data(index, TitleListModel::TitlePathRole);\n      if (data.isValid()) {\n         auto path = data.toString();\n         if (!path.isEmpty()) {\n            launchTitle(path);\n         }\n      }\n   });\n\n   connect(mSettingsStorage, &SettingsStorage::settingsChanged,\n           this, &TitleListWidget::settingsChanged);\n   settingsChanged();\n}\n\nTitleListWidget::~TitleListWidget()\n{\n   mScanThread.quit();\n   mScanThread.wait();\n}\n\nvoid\nTitleListWidget::settingsChanged()\n{\n   if (mSettingsStorage->get()->ui.titleListMode == UiSettings::TitleGrid) {\n      mStackedLayout->setCurrentIndex(1);\n   } else {\n      mStackedLayout->setCurrentIndex(0);\n   }\n\n   startTitleScan();\n}\n\nvoid\nTitleListWidget::startTitleScan()\n{\n   auto settings = mSettingsStorage->get();\n   auto directoryList = QStringList { };\n\n   if (!settings->decaf.system.mlc_path.empty()) {\n      directoryList.push_back(QString::fromStdString(settings->decaf.system.mlc_path) + \"/sys/title\");\n      directoryList.push_back(QString::fromStdString(settings->decaf.system.mlc_path) + \"/usr/title\");\n   }\n\n   for (const auto &dir : settings->decaf.system.title_directories) {\n      directoryList.push_back(QString::fromStdString(dir));\n   }\n\n   if (mCurrentDirectoryList == directoryList) {\n      return;\n   }\n\n   if (mScanRunning) {\n      if (!mScanRequested) {\n         mTitleScanner->cancel();\n         mScanRequested = true;\n      }\n\n      return;\n   }\n\n   mScanRunning = true;\n   mScanRequested = false;\n   mCurrentDirectoryList = directoryList;\n   mTitleListModel->clear();\n\n   emit statusMessage(\"Scanning for titles...\", 0);\n   scanDirectoryList(mCurrentDirectoryList);\n}\n\nvoid\nTitleListWidget::titleScanFinished()\n{\n   mScanRunning = false;\n\n   if (mScanRequested) {\n      startTitleScan();\n   }\n\n   emit statusMessage(\n      QString(\"Scanning complete, found %1 titles\")\n         .arg(mTitleListModel->rowCount({})), 0);\n}\n"
  },
  {
    "path": "src/decaf-qt/src/titlelistwidget.h",
    "content": "#pragma once\n#include <QStringList>\n#include <QThread>\n#include <QWidget>\n\nclass SettingsStorage;\nclass TitleScanner;\nclass TitleListModel;\nclass TitleSortFilterProxyModel;\n\nclass QStackedLayout;\nclass QTreeView;\nclass QListView;\n\nclass TitleListWidget : public QWidget\n{\n   Q_OBJECT\n\npublic:\n   TitleListWidget(SettingsStorage *settingsStorage, QWidget *parent = nullptr);\n   ~TitleListWidget();\n\n   void startTitleScan();\n\nsignals:\n   void scanDirectoryList(QStringList directories);\n   void launchTitle(QString path);\n   void statusMessage(QString message, int timeout);\n\nprotected slots:\n   void settingsChanged();\n   void titleScanFinished();\n\nprivate:\n   QStackedLayout *mStackedLayout = nullptr;\n   QTreeView *mTitleList = nullptr;\n   QListView *mTitleGrid = nullptr;\n\n   SettingsStorage *mSettingsStorage = nullptr;\n   QThread mScanThread;\n   TitleScanner *mTitleScanner = nullptr;\n   TitleListModel *mTitleListModel = nullptr;\n   TitleSortFilterProxyModel *mProxyModel = nullptr;\n\n   bool mScanRunning = false;\n   bool mScanRequested = false;\n   QStringList mCurrentDirectoryList;\n\n   QAction *mCopyPathAction;\n};\n"
  },
  {
    "path": "src/decaf-qt/ui/about.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>AboutDialog</class>\n <widget class=\"QDialog\" name=\"AboutDialog\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>501</width>\n    <height>150</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>About decaf-emu</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <layout class=\"QGridLayout\" name=\"gridLayout\">\n     <item row=\"1\" column=\"1\" alignment=\"Qt::AlignHCenter\">\n      <widget class=\"QLabel\" name=\"labelBuildInfo\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Minimum\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"minimumSize\">\n        <size>\n         <width>0</width>\n         <height>22</height>\n        </size>\n       </property>\n       <property name=\"text\">\n        <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4) &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n       </property>\n       <property name=\"scaledContents\">\n        <bool>false</bool>\n       </property>\n       <property name=\"alignment\">\n        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n       </property>\n       <property name=\"margin\">\n        <number>-4</number>\n       </property>\n      </widget>\n     </item>\n     <item row=\"0\" column=\"1\">\n      <widget class=\"QLabel\" name=\"label\">\n       <property name=\"text\">\n        <string>&lt;h1&gt;decaf-emu&lt;/h1&gt;\n&lt;a href=&quot;https://github.com/decaf-emu&quot;&gt;github&lt;/a&gt; | &lt;a href=&quot;https://github.com/decaf-emu/decaf-emu/blob/master/LICENSE.md&quot;&gt;license&lt;/a&gt;</string>\n       </property>\n       <property name=\"textFormat\">\n        <enum>Qt::RichText</enum>\n       </property>\n       <property name=\"alignment\">\n        <set>Qt::AlignCenter</set>\n       </property>\n       <property name=\"openExternalLinks\">\n        <bool>true</bool>\n       </property>\n      </widget>\n     </item>\n     <item row=\"0\" column=\"0\" rowspan=\"2\">\n      <widget class=\"QSvgWidget\" name=\"iconWidget\" native=\"true\"/>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n     <property name=\"orientation\">\n      <enum>Qt::Horizontal</enum>\n     </property>\n     <property name=\"standardButtons\">\n      <set>QDialogButtonBox::Ok</set>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>QSvgWidget</class>\n   <extends>QWidget</extends>\n   <header>qsvgwidget.h</header>\n   <container>1</container>\n  </customwidget>\n </customwidgets>\n <resources>\n  <include location=\"resources.qrc\"/>\n </resources>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>accepted()</signal>\n   <receiver>AboutDialog</receiver>\n   <slot>accept()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>248</x>\n     <y>254</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>157</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>rejected()</signal>\n   <receiver>AboutDialog</receiver>\n   <slot>reject()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>316</x>\n     <y>260</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>286</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/audiosettings.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>AudioSettingsWidget</class>\n <widget class=\"QWidget\" name=\"AudioSettingsWidget\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>300</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox\">\n     <property name=\"title\">\n      <string>Playback</string>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n      <item>\n       <widget class=\"QCheckBox\" name=\"checkBoxPlaybackEnabled\">\n        <property name=\"text\">\n         <string>Audio playback enabled</string>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <spacer name=\"verticalSpacer\">\n     <property name=\"orientation\">\n      <enum>Qt::Vertical</enum>\n     </property>\n     <property name=\"sizeHint\" stdset=\"0\">\n      <size>\n       <width>20</width>\n       <height>40</height>\n      </size>\n     </property>\n    </spacer>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/contentsettings.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ContentSettingsWidget</class>\n <widget class=\"QWidget\" name=\"ContentSettingsWidget\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>453</width>\n    <height>374</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox_2\">\n     <property name=\"title\">\n      <string>System Directories</string>\n     </property>\n     <layout class=\"QGridLayout\" name=\"gridLayout\">\n      <item row=\"0\" column=\"2\">\n       <widget class=\"QPushButton\" name=\"pushButtonResourcesPath\">\n        <property name=\"text\">\n         <string>...</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"5\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_4\">\n        <property name=\"text\">\n         <string>/dev/hfio01</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"5\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditHfioPath\"/>\n      </item>\n      <item row=\"3\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditSlcPath\"/>\n      </item>\n      <item row=\"4\" column=\"2\">\n       <widget class=\"QPushButton\" name=\"pushButtonSdcardPath\">\n        <property name=\"text\">\n         <string>...</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"6\" column=\"2\">\n       <widget class=\"QPushButton\" name=\"pushButtonOtpPath\">\n        <property name=\"text\">\n         <string>...</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"0\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_5\">\n        <property name=\"text\">\n         <string>Resources</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"4\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditSdcardPath\"/>\n      </item>\n      <item row=\"5\" column=\"2\">\n       <widget class=\"QPushButton\" name=\"pushButtonHfioPath\">\n        <property name=\"text\">\n         <string>...</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"6\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_6\">\n        <property name=\"text\">\n         <string>otp.bin</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"6\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditOtpPath\"/>\n      </item>\n      <item row=\"2\" column=\"2\">\n       <widget class=\"QPushButton\" name=\"pushButtonMlcPath\">\n        <property name=\"text\">\n         <string>...</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"0\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditResourcesPath\"/>\n      </item>\n      <item row=\"4\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_3\">\n        <property name=\"text\">\n         <string>/dev/sdcard01</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"2\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditMlcPath\"/>\n      </item>\n      <item row=\"2\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label\">\n        <property name=\"text\">\n         <string>/dev/mlc01</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"3\" column=\"2\">\n       <widget class=\"QPushButton\" name=\"pushButtonSlcPath\">\n        <property name=\"text\">\n         <string>...</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"3\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_2\">\n        <property name=\"text\">\n         <string>/dev/slc01</string>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox\">\n     <property name=\"title\">\n      <string>Title Search Directores</string>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n      <item>\n       <widget class=\"QListWidget\" name=\"listWidgetTitleDirectories\"/>\n      </item>\n      <item>\n       <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n        <item>\n         <widget class=\"QPushButton\" name=\"pushButtonAddTitleDirectory\">\n          <property name=\"text\">\n           <string>Add</string>\n          </property>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QPushButton\" name=\"pushButtonRemoveTitleDirectory\">\n          <property name=\"text\">\n           <string>Remove</string>\n          </property>\n         </widget>\n        </item>\n       </layout>\n      </item>\n     </layout>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>pushButtonAddTitleDirectory</sender>\n   <signal>clicked()</signal>\n   <receiver>ContentSettingsWidget</receiver>\n   <slot>addTitleDirectory()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>119</x>\n     <y>351</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>226</x>\n     <y>186</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonOtpPath</sender>\n   <signal>clicked()</signal>\n   <receiver>ContentSettingsWidget</receiver>\n   <slot>browseOtpPath()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>411</x>\n     <y>136</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>226</x>\n     <y>186</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonHfioPath</sender>\n   <signal>clicked()</signal>\n   <receiver>ContentSettingsWidget</receiver>\n   <slot>browseHfioPath()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>411</x>\n     <y>115</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>226</x>\n     <y>186</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonMlcPath</sender>\n   <signal>clicked()</signal>\n   <receiver>ContentSettingsWidget</receiver>\n   <slot>browseMlcPath()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>411</x>\n     <y>52</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>226</x>\n     <y>186</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonRemoveTitleDirectory</sender>\n   <signal>clicked()</signal>\n   <receiver>ContentSettingsWidget</receiver>\n   <slot>removeTitleDirectory()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>333</x>\n     <y>351</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>226</x>\n     <y>186</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonSdcardPath</sender>\n   <signal>clicked()</signal>\n   <receiver>ContentSettingsWidget</receiver>\n   <slot>browseSdcardPath()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>411</x>\n     <y>94</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>226</x>\n     <y>186</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonResourcesPath</sender>\n   <signal>clicked()</signal>\n   <receiver>ContentSettingsWidget</receiver>\n   <slot>browseResourcesPath()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>411</x>\n     <y>31</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>226</x>\n     <y>186</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonSlcPath</sender>\n   <signal>clicked()</signal>\n   <receiver>ContentSettingsWidget</receiver>\n   <slot>browseSlcPath()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>411</x>\n     <y>73</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>226</x>\n     <y>186</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>browseResourcesPath()</slot>\n  <slot>browseMlcPath()</slot>\n  <slot>browseSlcPath()</slot>\n  <slot>browseSdcardPath()</slot>\n  <slot>browseHfioPath()</slot>\n  <slot>browseOtpPath()</slot>\n  <slot>addTitleDirectory()</slot>\n  <slot>removeTitleDirectory()</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugger/breakpointswindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>BreakpointsWindow</class>\n <widget class=\"QWidget\" name=\"BreakpointsWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>512</width>\n    <height>440</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"spacing\">\n    <number>0</number>\n   </property>\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"QTableView\" name=\"tableView\">\n     <property name=\"selectionBehavior\">\n      <enum>QAbstractItemView::SelectRows</enum>\n     </property>\n     <property name=\"verticalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n     <property name=\"horizontalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <property name=\"leftMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>3</number>\n     </property>\n     <item>\n      <widget class=\"QLabel\" name=\"labelStatus\">\n       <property name=\"text\">\n        <string/>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>tableView</sender>\n   <signal>doubleClicked(QModelIndex)</signal>\n   <receiver>BreakpointsWindow</receiver>\n   <slot>breakpointsViewDoubleClicked(QModelIndex)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>255</x>\n     <y>211</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>255</x>\n     <y>219</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>breakpointsViewDoubleClicked(QModelIndex)</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugger/debuggerwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DebuggerWindow</class>\n <widget class=\"QMainWindow\" name=\"DebuggerWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>800</width>\n    <height>600</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>decaf-emu Debugger</string>\n  </property>\n  <property name=\"windowIcon\">\n   <iconset resource=\"../../resources/resources.qrc\">\n    <normaloff>:/images/debugger-icon</normaloff>:/images/debugger-icon</iconset>\n  </property>\n  <widget class=\"QWidget\" name=\"centralwidget\"/>\n  <widget class=\"QMenuBar\" name=\"menubar\">\n   <property name=\"geometry\">\n    <rect>\n     <x>0</x>\n     <y>0</y>\n     <width>800</width>\n     <height>18</height>\n    </rect>\n   </property>\n   <widget class=\"QMenu\" name=\"menuFile\">\n    <property name=\"title\">\n     <string>&amp;File</string>\n    </property>\n    <addaction name=\"actionClose\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuView\">\n    <property name=\"title\">\n     <string>&amp;View</string>\n    </property>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuDebug\">\n    <property name=\"title\">\n     <string>&amp;Debug</string>\n    </property>\n    <addaction name=\"actionPause\"/>\n    <addaction name=\"actionResume\"/>\n    <addaction name=\"actionStepOver\"/>\n    <addaction name=\"actionStepInto\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionToggleBreakpoint\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionHleTraceEnabled\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuGraphics\">\n    <property name=\"title\">\n     <string>Graphics</string>\n    </property>\n    <addaction name=\"actionPm4CaptureNextFrame\"/>\n    <addaction name=\"actionPm4TraceEnabled\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionGx2TextureDumpEnabled\"/>\n    <addaction name=\"actionGx2ShaderDumpEnabled\"/>\n    <addaction name=\"actionGpuShaderDumpEnabled\"/>\n    <addaction name=\"actionGpuShaderBinaryDumpOnly\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuNavigate\">\n    <property name=\"title\">\n     <string>Navigate</string>\n    </property>\n    <addaction name=\"actionNavigateBackward\"/>\n    <addaction name=\"actionNavigateForward\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionNavigateToOperand\"/>\n    <addaction name=\"actionNavigateToAddress\"/>\n   </widget>\n   <addaction name=\"menuFile\"/>\n   <addaction name=\"menuDebug\"/>\n   <addaction name=\"menuNavigate\"/>\n   <addaction name=\"menuGraphics\"/>\n   <addaction name=\"menuView\"/>\n  </widget>\n  <widget class=\"QStatusBar\" name=\"statusbar\"/>\n  <widget class=\"QToolBar\" name=\"toolBarNavigate\">\n   <property name=\"windowTitle\">\n    <string>toolBar_3</string>\n   </property>\n   <attribute name=\"toolBarArea\">\n    <enum>TopToolBarArea</enum>\n   </attribute>\n   <attribute name=\"toolBarBreak\">\n    <bool>false</bool>\n   </attribute>\n   <addaction name=\"actionNavigateBackward\"/>\n   <addaction name=\"actionNavigateForward\"/>\n  </widget>\n  <widget class=\"QToolBar\" name=\"toolBarDebugActions\">\n   <property name=\"windowTitle\">\n    <string>toolBar</string>\n   </property>\n   <attribute name=\"toolBarArea\">\n    <enum>TopToolBarArea</enum>\n   </attribute>\n   <attribute name=\"toolBarBreak\">\n    <bool>false</bool>\n   </attribute>\n   <addaction name=\"actionPause\"/>\n   <addaction name=\"actionResume\"/>\n   <addaction name=\"actionStepOver\"/>\n   <addaction name=\"actionStepInto\"/>\n  </widget>\n  <widget class=\"QToolBar\" name=\"toolBarBreakpoints\">\n   <property name=\"windowTitle\">\n    <string>toolBar_2</string>\n   </property>\n   <attribute name=\"toolBarArea\">\n    <enum>TopToolBarArea</enum>\n   </attribute>\n   <attribute name=\"toolBarBreak\">\n    <bool>false</bool>\n   </attribute>\n   <addaction name=\"actionToggleBreakpoint\"/>\n  </widget>\n  <action name=\"actionClose\">\n   <property name=\"text\">\n    <string>&amp;Close</string>\n   </property>\n   <property name=\"shortcutContext\">\n    <enum>Qt::WidgetWithChildrenShortcut</enum>\n   </property>\n  </action>\n  <action name=\"actionPause\">\n   <property name=\"icon\">\n    <iconset resource=\"../../resources/resources.qrc\">\n     <normaloff>:/icons/pause</normaloff>:/icons/pause</iconset>\n   </property>\n   <property name=\"text\">\n    <string>&amp;Pause</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Ctrl+Cancel</string>\n   </property>\n   <property name=\"shortcutVisibleInContextMenu\">\n    <bool>true</bool>\n   </property>\n  </action>\n  <action name=\"actionResume\">\n   <property name=\"enabled\">\n    <bool>false</bool>\n   </property>\n   <property name=\"icon\">\n    <iconset resource=\"../../resources/resources.qrc\">\n     <normaloff>:/icons/play</normaloff>:/icons/play</iconset>\n   </property>\n   <property name=\"text\">\n    <string>&amp;Resume</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>F5</string>\n   </property>\n  </action>\n  <action name=\"actionStepOver\">\n   <property name=\"enabled\">\n    <bool>false</bool>\n   </property>\n   <property name=\"icon\">\n    <iconset resource=\"../../resources/resources.qrc\">\n     <normaloff>:/icons/debug-step-over</normaloff>:/icons/debug-step-over</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Step &amp;Over</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>F10</string>\n   </property>\n  </action>\n  <action name=\"actionStepInto\">\n   <property name=\"enabled\">\n    <bool>false</bool>\n   </property>\n   <property name=\"icon\">\n    <iconset resource=\"../../resources/resources.qrc\">\n     <normaloff>:/icons/debug-step-into</normaloff>:/icons/debug-step-into</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Step &amp;Into</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>F11</string>\n   </property>\n  </action>\n  <action name=\"actionHleTraceEnabled\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>&amp;HLE Trace Enabled</string>\n   </property>\n  </action>\n  <action name=\"actionPm4CaptureNextFrame\">\n   <property name=\"text\">\n    <string>PM4 &amp;Capture Next Frame</string>\n   </property>\n  </action>\n  <action name=\"actionPm4TraceEnabled\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>PM4 Trace Enabled</string>\n   </property>\n  </action>\n  <action name=\"actionGx2TextureDumpEnabled\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>GX2 Texture Dump Enabled</string>\n   </property>\n  </action>\n  <action name=\"actionGx2ShaderDumpEnabled\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>GX2 Shader Dump Enabled</string>\n   </property>\n  </action>\n  <action name=\"actionGpuShaderDumpEnabled\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>GPU Shader Dump Enabled</string>\n   </property>\n  </action>\n  <action name=\"actionGpuShaderBinaryDumpOnly\">\n   <property name=\"checkable\">\n    <bool>true</bool>\n   </property>\n   <property name=\"text\">\n    <string>GPU Shader Binary Dump Only</string>\n   </property>\n  </action>\n  <action name=\"actionToggleBreakpoint\">\n   <property name=\"icon\">\n    <iconset resource=\"../../resources/resources.qrc\">\n     <normaloff>:/icons/checkbox-blank-circle</normaloff>:/icons/checkbox-blank-circle</iconset>\n   </property>\n   <property name=\"text\">\n    <string>To&amp;ggle Breakpoint</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>F9</string>\n   </property>\n   <property name=\"shortcutContext\">\n    <enum>Qt::WidgetWithChildrenShortcut</enum>\n   </property>\n  </action>\n  <action name=\"actionNavigateForward\">\n   <property name=\"icon\">\n    <iconset resource=\"../../resources/resources.qrc\">\n     <normaloff>:/icons/arrow-right</normaloff>:/icons/arrow-right</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Navigate forward</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Alt+Left</string>\n   </property>\n   <property name=\"shortcutContext\">\n    <enum>Qt::WidgetWithChildrenShortcut</enum>\n   </property>\n  </action>\n  <action name=\"actionNavigateBackward\">\n   <property name=\"icon\">\n    <iconset resource=\"../../resources/resources.qrc\">\n     <normaloff>:/icons/arrow-left</normaloff>:/icons/arrow-left</iconset>\n   </property>\n   <property name=\"text\">\n    <string>Navigate backward</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Alt+Right</string>\n   </property>\n   <property name=\"shortcutContext\">\n    <enum>Qt::WidgetWithChildrenShortcut</enum>\n   </property>\n  </action>\n  <action name=\"actionNavigateToAddress\">\n   <property name=\"text\">\n    <string>Navigate to address</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>G</string>\n   </property>\n   <property name=\"shortcutContext\">\n    <enum>Qt::WidgetWithChildrenShortcut</enum>\n   </property>\n  </action>\n  <action name=\"actionNavigateToOperand\">\n   <property name=\"text\">\n    <string>Navigate to operand</string>\n   </property>\n   <property name=\"shortcut\">\n    <string>Return</string>\n   </property>\n   <property name=\"shortcutContext\">\n    <enum>Qt::WidgetWithChildrenShortcut</enum>\n   </property>\n  </action>\n </widget>\n <resources>\n  <include location=\"../../resources/resources.qrc\"/>\n </resources>\n <connections>\n  <connection>\n   <sender>actionClose</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>close()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionGpuShaderBinaryDumpOnly</sender>\n   <signal>triggered(bool)</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>setGpuShaderBinaryDumpOnly(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionGpuShaderDumpEnabled</sender>\n   <signal>triggered(bool)</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>setGpuShaderDumpEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionGx2ShaderDumpEnabled</sender>\n   <signal>triggered(bool)</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>setGx2ShaderDumpEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionGx2TextureDumpEnabled</sender>\n   <signal>triggered(bool)</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>setGx2TextureDumpEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionHleTraceEnabled</sender>\n   <signal>triggered(bool)</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>setHleTraceEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionPause</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>debugPause()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionResume</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>debugResume()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionPm4CaptureNextFrame</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>pm4CaptureNextFrame()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionPm4TraceEnabled</sender>\n   <signal>triggered(bool)</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>setPm4TraceEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionStepInto</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>debugStepInto()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionStepOver</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>debugStepOver()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionToggleBreakpoint</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>debugToggleBreakpoint()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionNavigateBackward</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>navigateBackward()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionNavigateForward</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>navigateForward()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionNavigateToAddress</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>navigateAddress()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionNavigateToOperand</sender>\n   <signal>triggered()</signal>\n   <receiver>DebuggerWindow</receiver>\n   <slot>navigateOperand()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>399</x>\n     <y>299</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>debugPause()</slot>\n  <slot>debugResume()</slot>\n  <slot>debugStepOver()</slot>\n  <slot>debugStepInto()</slot>\n  <slot>setHleTraceEnabled(bool)</slot>\n  <slot>pm4CaptureNextFrame()</slot>\n  <slot>setPm4TraceEnabled(bool)</slot>\n  <slot>setGx2TextureDumpEnabled(bool)</slot>\n  <slot>setGx2ShaderDumpEnabled(bool)</slot>\n  <slot>setGpuShaderDumpEnabled(bool)</slot>\n  <slot>setGpuShaderBinaryDumpOnly(bool)</slot>\n  <slot>debugToggleBreakpoint()</slot>\n  <slot>navigateBackward()</slot>\n  <slot>navigateForward()</slot>\n  <slot>navigateAddress()</slot>\n  <slot>navigateOperand()</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugger/disassemblywindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DisassemblyWindow</class>\n <widget class=\"QWidget\" name=\"DisassemblyWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>557</width>\n    <height>442</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"spacing\">\n    <number>0</number>\n   </property>\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"DisassemblyWidget\" name=\"disassemblyWidget\" native=\"true\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n       <horstretch>0</horstretch>\n       <verstretch>1</verstretch>\n      </sizepolicy>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <property name=\"leftMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>3</number>\n     </property>\n     <item>\n      <widget class=\"QLabel\" name=\"labelStatus\">\n       <property name=\"text\">\n        <string/>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>DisassemblyWidget</class>\n   <extends>QWidget</extends>\n   <header>debugger/disassemblywidget.h</header>\n   <container>1</container>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugger/functionswindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>FunctionsWindow</class>\n <widget class=\"QWidget\" name=\"FunctionsWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>416</width>\n    <height>339</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Functions</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"spacing\">\n    <number>0</number>\n   </property>\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"QTableView\" name=\"tableView\">\n     <property name=\"selectionBehavior\">\n      <enum>QAbstractItemView::SelectRows</enum>\n     </property>\n     <property name=\"verticalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n     <property name=\"horizontalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n     <property name=\"showGrid\">\n      <bool>false</bool>\n     </property>\n     <property name=\"gridStyle\">\n      <enum>Qt::NoPen</enum>\n     </property>\n     <attribute name=\"horizontalHeaderShowSortIndicator\" stdset=\"0\">\n      <bool>true</bool>\n     </attribute>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <property name=\"leftMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>3</number>\n     </property>\n     <item>\n      <widget class=\"QLabel\" name=\"labelFilter\">\n       <property name=\"text\">\n        <string>Filter:</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QLineEdit\" name=\"lineEditFilter\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n         <horstretch>1</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>lineEditFilter</sender>\n   <signal>textChanged(QString)</signal>\n   <receiver>FunctionsWindow</receiver>\n   <slot>filterChanged(QString)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>217</x>\n     <y>286</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>199</x>\n     <y>149</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>tableView</sender>\n   <signal>doubleClicked(QModelIndex)</signal>\n   <receiver>FunctionsWindow</receiver>\n   <slot>functionsViewDoubleClicked(QModelIndex)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>207</x>\n     <y>156</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>207</x>\n     <y>169</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>filterChanged(QString)</slot>\n  <slot>functionsViewDoubleClicked(QModelIndex)</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugger/jitprofilingwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>JitProfilingWindow</class>\n <widget class=\"QWidget\" name=\"JitProfilingWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>565</width>\n    <height>441</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>JIT Profiling</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n     <item>\n      <widget class=\"QCheckBox\" name=\"checkBoxCore0\">\n       <property name=\"text\">\n        <string>Core 0</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QCheckBox\" name=\"checkBoxCore1\">\n       <property name=\"text\">\n        <string>Core 1</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QCheckBox\" name=\"checkBoxCore2\">\n       <property name=\"text\">\n        <string>Core 2</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <spacer name=\"horizontalSpacer_2\">\n       <property name=\"orientation\">\n        <enum>Qt::Horizontal</enum>\n       </property>\n       <property name=\"sizeHint\" stdset=\"0\">\n        <size>\n         <width>40</width>\n         <height>20</height>\n        </size>\n       </property>\n      </spacer>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"pushButtonStartStop\">\n       <property name=\"text\">\n        <string>Start</string>\n       </property>\n       <property name=\"checkable\">\n        <bool>true</bool>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"pushButtonClear\">\n       <property name=\"text\">\n        <string>Clear</string>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <layout class=\"QFormLayout\" name=\"formLayout\">\n     <item row=\"2\" column=\"0\">\n      <widget class=\"QLabel\" name=\"label\">\n       <property name=\"text\">\n        <string>Total JIT Data Size:</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"1\" column=\"0\">\n      <widget class=\"QLabel\" name=\"label_4\">\n       <property name=\"text\">\n        <string>Total JIT Code Size:</string>\n       </property>\n      </widget>\n     </item>\n     <item row=\"1\" column=\"1\">\n      <widget class=\"QLabel\" name=\"labelJitCodeSize\">\n       <property name=\"cursor\">\n        <cursorShape>IBeamCursor</cursorShape>\n       </property>\n       <property name=\"text\">\n        <string>0.00 mb</string>\n       </property>\n       <property name=\"textInteractionFlags\">\n        <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>\n       </property>\n      </widget>\n     </item>\n     <item row=\"2\" column=\"1\">\n      <widget class=\"QLabel\" name=\"labelJitDataSize\">\n       <property name=\"cursor\">\n        <cursorShape>IBeamCursor</cursorShape>\n       </property>\n       <property name=\"text\">\n        <string>0.00 mb</string>\n       </property>\n       <property name=\"textInteractionFlags\">\n        <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <widget class=\"QTableView\" name=\"tableView\">\n     <property name=\"selectionMode\">\n      <enum>QAbstractItemView::SingleSelection</enum>\n     </property>\n     <property name=\"selectionBehavior\">\n      <enum>QAbstractItemView::SelectRows</enum>\n     </property>\n     <property name=\"verticalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n     <property name=\"horizontalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>checkBoxCore0</sender>\n   <signal>clicked(bool)</signal>\n   <receiver>JitProfilingWindow</receiver>\n   <slot>setCore0Mask(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>37</x>\n     <y>20</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>281</x>\n     <y>245</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>checkBoxCore1</sender>\n   <signal>clicked(bool)</signal>\n   <receiver>JitProfilingWindow</receiver>\n   <slot>setCore1Mask(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>98</x>\n     <y>20</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>281</x>\n     <y>245</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>checkBoxCore2</sender>\n   <signal>clicked(bool)</signal>\n   <receiver>JitProfilingWindow</receiver>\n   <slot>setCore2Mask(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>159</x>\n     <y>20</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>281</x>\n     <y>245</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonClear</sender>\n   <signal>clicked()</signal>\n   <receiver>JitProfilingWindow</receiver>\n   <slot>clearProfileData()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>516</x>\n     <y>21</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>281</x>\n     <y>245</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonStartStop</sender>\n   <signal>clicked(bool)</signal>\n   <receiver>JitProfilingWindow</receiver>\n   <slot>setProfilingEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>435</x>\n     <y>21</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>281</x>\n     <y>245</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>tableView</sender>\n   <signal>doubleClicked(QModelIndex)</signal>\n   <receiver>JitProfilingWindow</receiver>\n   <slot>tableViewDoubleClicked(QModelIndex)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>282</x>\n     <y>335</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>282</x>\n     <y>220</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>setProfilingEnabled(bool)</slot>\n  <slot>clearProfileData()</slot>\n  <slot>setCore0Mask(bool)</slot>\n  <slot>setCore1Mask(bool)</slot>\n  <slot>setCore2Mask(bool)</slot>\n  <slot>tableViewDoubleClicked(QModelIndex)</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugger/memorywindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>MemoryWindow</class>\n <widget class=\"QWidget\" name=\"MemoryWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>646</width>\n    <height>442</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Memory</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <property name=\"leftMargin\">\n      <number>9</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>9</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>9</number>\n     </property>\n     <item>\n      <widget class=\"QLabel\" name=\"label\">\n       <property name=\"text\">\n        <string>Address:</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QLineEdit\" name=\"lineEditAddress\"/>\n     </item>\n     <item>\n      <widget class=\"QLabel\" name=\"label_2\">\n       <property name=\"text\">\n        <string>Columns:</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QComboBox\" name=\"comboBoxColumns\">\n       <property name=\"minimumSize\">\n        <size>\n         <width>100</width>\n         <height>0</height>\n        </size>\n       </property>\n       <property name=\"editable\">\n        <bool>true</bool>\n       </property>\n       <property name=\"currentIndex\">\n        <number>3</number>\n       </property>\n       <item>\n        <property name=\"text\">\n         <string>Auto</string>\n        </property>\n       </item>\n       <item>\n        <property name=\"text\">\n         <string>4</string>\n        </property>\n       </item>\n       <item>\n        <property name=\"text\">\n         <string>8</string>\n        </property>\n       </item>\n       <item>\n        <property name=\"text\">\n         <string>16</string>\n        </property>\n       </item>\n       <item>\n        <property name=\"text\">\n         <string>32</string>\n        </property>\n       </item>\n       <item>\n        <property name=\"text\">\n         <string>64</string>\n        </property>\n       </item>\n      </widget>\n     </item>\n    </layout>\n   </item>\n   <item>\n    <widget class=\"MemoryWidget\" name=\"widget\" native=\"true\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n       <horstretch>0</horstretch>\n       <verstretch>1</verstretch>\n      </sizepolicy>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>MemoryWidget</class>\n   <extends>QWidget</extends>\n   <header>debugger/memorywidget.h</header>\n   <container>1</container>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections>\n  <connection>\n   <sender>comboBoxColumns</sender>\n   <signal>currentTextChanged(QString)</signal>\n   <receiver>MemoryWindow</receiver>\n   <slot>columnsChanged()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>585</x>\n     <y>19</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>322</x>\n     <y>220</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>lineEditAddress</sender>\n   <signal>returnPressed()</signal>\n   <receiver>MemoryWindow</receiver>\n   <slot>addressChanged()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>268</x>\n     <y>19</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>322</x>\n     <y>220</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>columnsChanged()</slot>\n  <slot>addressChanged()</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugger/segmentswindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>SegmentsWindow</class>\n <widget class=\"QWidget\" name=\"SegmentsWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>512</width>\n    <height>439</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Segments</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"spacing\">\n    <number>0</number>\n   </property>\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"QTableView\" name=\"tableView\">\n     <property name=\"selectionBehavior\">\n      <enum>QAbstractItemView::SelectRows</enum>\n     </property>\n     <property name=\"verticalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n     <property name=\"horizontalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <property name=\"leftMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>3</number>\n     </property>\n     <item>\n      <widget class=\"QLabel\" name=\"labelStatus\">\n       <property name=\"text\">\n        <string/>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>tableView</sender>\n   <signal>doubleClicked(QModelIndex)</signal>\n   <receiver>SegmentsWindow</receiver>\n   <slot>segmentsViewDoubleClicked(QModelIndex)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>255</x>\n     <y>210</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>255</x>\n     <y>219</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>segmentsViewDoubleClicked(QModelIndex)</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugger/stackwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>StackWindow</class>\n <widget class=\"QWidget\" name=\"StackWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>658</width>\n    <height>601</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Stack</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"spacing\">\n    <number>6</number>\n   </property>\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"StackWidget\" name=\"stackWidget\" native=\"true\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n       <horstretch>0</horstretch>\n       <verstretch>1</verstretch>\n      </sizepolicy>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <property name=\"spacing\">\n      <number>6</number>\n     </property>\n     <property name=\"leftMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>3</number>\n     </property>\n     <item>\n      <widget class=\"QLabel\" name=\"labelStatus\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n         <horstretch>1</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string/>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>StackWidget</class>\n   <extends>QWidget</extends>\n   <header>debugger/stackwidget.h</header>\n   <container>1</container>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections/>\n <slots>\n  <slot>threadChanged(int)</slot>\n  <slot>displayModeChanged(int)</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugger/threadswindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ThreadsWindow</class>\n <widget class=\"QWidget\" name=\"ThreadsWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>512</width>\n    <height>440</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Threads</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"spacing\">\n    <number>0</number>\n   </property>\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"QTableView\" name=\"tableView\">\n     <property name=\"selectionBehavior\">\n      <enum>QAbstractItemView::SelectRows</enum>\n     </property>\n     <property name=\"verticalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n     <property name=\"horizontalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <property name=\"leftMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>3</number>\n     </property>\n     <item>\n      <widget class=\"QLabel\" name=\"labelStatus\">\n       <property name=\"text\">\n        <string/>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>tableView</sender>\n   <signal>doubleClicked(QModelIndex)</signal>\n   <receiver>ThreadsWindow</receiver>\n   <slot>threadsViewDoubleClicked(QModelIndex)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>255</x>\n     <y>210</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>255</x>\n     <y>219</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>threadsViewDoubleClicked(QModelIndex)</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugger/voiceswindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>VoicesWindow</class>\n <widget class=\"QWidget\" name=\"VoicesWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>512</width>\n    <height>440</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"spacing\">\n    <number>0</number>\n   </property>\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"QTableView\" name=\"tableView\">\n     <property name=\"selectionBehavior\">\n      <enum>QAbstractItemView::SelectRows</enum>\n     </property>\n     <property name=\"verticalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n     <property name=\"horizontalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <property name=\"leftMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>3</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>3</number>\n     </property>\n     <item>\n      <widget class=\"QLabel\" name=\"labelStatus\">\n       <property name=\"text\">\n        <string/>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/debugsettings.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DebugSettingsWidget</class>\n <widget class=\"QWidget\" name=\"DebugSettingsWidget\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>398</width>\n    <height>293</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox\">\n     <property name=\"title\">\n      <string>Debug Options</string>\n     </property>\n     <layout class=\"QGridLayout\" name=\"gridLayout_3\">\n      <item row=\"5\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditGdbServerPort\"/>\n      </item>\n      <item row=\"5\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label\">\n        <property name=\"text\">\n         <string>GDB Server Port</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"0\" column=\"0\" colspan=\"2\">\n       <widget class=\"QCheckBox\" name=\"checkBoxBreakEntry\">\n        <property name=\"text\">\n         <string>Break on main entry point</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"3\" column=\"0\">\n       <widget class=\"QCheckBox\" name=\"checkBoxBreakExit\">\n        <property name=\"text\">\n         <string>Break on exit</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"4\" column=\"0\">\n       <widget class=\"QCheckBox\" name=\"checkBoxGdbServerEnabled\">\n        <property name=\"text\">\n         <string>GDB server enabled</string>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <spacer name=\"verticalSpacer\">\n     <property name=\"orientation\">\n      <enum>Qt::Vertical</enum>\n     </property>\n     <property name=\"sizeHint\" stdset=\"0\">\n      <size>\n       <width>20</width>\n       <height>40</height>\n      </size>\n     </property>\n    </spacer>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/displaysettings.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>DisplaySettingsWidget</class>\n <widget class=\"QWidget\" name=\"DisplaySettingsWidget\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>428</width>\n    <height>305</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox_2\">\n     <property name=\"title\">\n      <string>Title List</string>\n     </property>\n     <layout class=\"QGridLayout\" name=\"gridLayout\" columnstretch=\"0,1\">\n      <item row=\"0\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_4\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"text\">\n         <string>View Mode</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"0\" column=\"1\">\n       <widget class=\"QComboBox\" name=\"comboBoxTitleListMode\"/>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox\">\n     <property name=\"title\">\n      <string>Game</string>\n     </property>\n     <layout class=\"QGridLayout\" name=\"gridLayout_2\" columnstretch=\"0,1\">\n      <item row=\"1\" column=\"1\">\n       <widget class=\"QCheckBox\" name=\"checkBoxMaintainAspectRatio\">\n        <property name=\"text\">\n         <string>Maintain Aspect Ratio</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"2\" column=\"1\">\n       <widget class=\"ColourLineEdit\" name=\"lineEditBackgroundColour\"/>\n      </item>\n      <item row=\"2\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_3\">\n        <property name=\"text\">\n         <string>Background Colour</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"0\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"text\">\n         <string>View Mode</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"0\" column=\"1\">\n       <widget class=\"QComboBox\" name=\"comboBoxViewMode\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n       </widget>\n      </item>\n      <item row=\"3\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_2\">\n        <property name=\"text\">\n         <string>Split Separation</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"3\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditSplitSeparation\"/>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <spacer name=\"verticalSpacer\">\n     <property name=\"orientation\">\n      <enum>Qt::Vertical</enum>\n     </property>\n     <property name=\"sizeHint\" stdset=\"0\">\n      <size>\n       <width>20</width>\n       <height>140</height>\n      </size>\n     </property>\n    </spacer>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>ColourLineEdit</class>\n   <extends>QLineEdit</extends>\n   <header>settings/colourlineedit.h</header>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/inputsettings.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>InputSettingsWidget</class>\n <widget class=\"QWidget\" name=\"InputSettingsWidget\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>636</width>\n    <height>357</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n   <item>\n    <widget class=\"QWidget\" name=\"verticalWidget\" native=\"true\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"MinimumExpanding\" vsizetype=\"Preferred\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <property name=\"minimumSize\">\n      <size>\n       <width>0</width>\n       <height>0</height>\n      </size>\n     </property>\n     <property name=\"maximumSize\">\n      <size>\n       <width>200</width>\n       <height>16777215</height>\n      </size>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n      <item>\n       <widget class=\"QListWidget\" name=\"controllerList\">\n        <property name=\"minimumSize\">\n         <size>\n          <width>0</width>\n          <height>0</height>\n         </size>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\" stretch=\"0,0\">\n        <property name=\"spacing\">\n         <number>0</number>\n        </property>\n        <item>\n         <widget class=\"QPushButton\" name=\"addController\">\n          <property name=\"sizePolicy\">\n           <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Fixed\">\n            <horstretch>0</horstretch>\n            <verstretch>0</verstretch>\n           </sizepolicy>\n          </property>\n          <property name=\"text\">\n           <string>Add</string>\n          </property>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QPushButton\" name=\"removeController\">\n          <property name=\"sizePolicy\">\n           <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Fixed\">\n            <horstretch>0</horstretch>\n            <verstretch>0</verstretch>\n           </sizepolicy>\n          </property>\n          <property name=\"text\">\n           <string>Remove</string>\n          </property>\n         </widget>\n        </item>\n       </layout>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QWidget\" name=\"widget\" native=\"true\">\n     <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n      <item>\n       <widget class=\"QWidget\" name=\"formWidget\" native=\"true\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Minimum\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <layout class=\"QFormLayout\" name=\"formLayout\">\n         <property name=\"sizeConstraint\">\n          <enum>QLayout::SetDefaultConstraint</enum>\n         </property>\n         <item row=\"0\" column=\"0\">\n          <widget class=\"QLabel\" name=\"label\">\n           <property name=\"text\">\n            <string>Controller Type</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"0\" column=\"1\">\n          <widget class=\"QComboBox\" name=\"controllerType\"/>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QScrollArea\" name=\"scrollArea\">\n        <property name=\"frameShape\">\n         <enum>QFrame::StyledPanel</enum>\n        </property>\n        <property name=\"frameShadow\">\n         <enum>QFrame::Sunken</enum>\n        </property>\n        <property name=\"widgetResizable\">\n         <bool>true</bool>\n        </property>\n        <widget class=\"QWidget\" name=\"buttonList\">\n         <property name=\"geometry\">\n          <rect>\n           <x>0</x>\n           <y>0</y>\n           <width>406</width>\n           <height>296</height>\n          </rect>\n         </property>\n         <layout class=\"QFormLayout\" name=\"formLayout_2\"/>\n        </widget>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>addController</sender>\n   <signal>clicked()</signal>\n   <receiver>InputSettingsWidget</receiver>\n   <slot>addController()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>60</x>\n     <y>444</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>393</x>\n     <y>243</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>removeController</sender>\n   <signal>clicked()</signal>\n   <receiver>InputSettingsWidget</receiver>\n   <slot>removeController()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>153</x>\n     <y>444</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>393</x>\n     <y>243</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>controllerType</sender>\n   <signal>currentIndexChanged(int)</signal>\n   <receiver>InputSettingsWidget</receiver>\n   <slot>controllerTypeChanged(int)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>524</x>\n     <y>28</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>393</x>\n     <y>243</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>controllerList</sender>\n   <signal>currentRowChanged(int)</signal>\n   <receiver>InputSettingsWidget</receiver>\n   <slot>editController(int)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>106</x>\n     <y>221</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>393</x>\n     <y>243</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>addController()</slot>\n  <slot>removeController()</slot>\n  <slot>controllerTypeChanged(int)</slot>\n  <slot>editController(int)</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/loggingsettings.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>LoggingSettingsWidget</class>\n <widget class=\"QWidget\" name=\"LoggingSettingsWidget\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>393</width>\n    <height>400</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox\">\n     <property name=\"title\">\n      <string>Log Options</string>\n     </property>\n     <layout class=\"QGridLayout\" name=\"gridLayout\">\n      <item row=\"3\" column=\"0\" colspan=\"4\">\n       <widget class=\"QCheckBox\" name=\"checkBoxOutputFile\">\n        <property name=\"text\">\n         <string>Output to file</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"0\" column=\"0\" colspan=\"4\">\n       <widget class=\"QCheckBox\" name=\"checkBoxAsynchronous\">\n        <property name=\"text\">\n         <string>Asynchronous</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"4\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label\">\n        <property name=\"text\">\n         <string>Log File Directory</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"4\" column=\"2\">\n       <widget class=\"QLineEdit\" name=\"lineEditLogDirectory\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Expanding\" vsizetype=\"Fixed\">\n          <horstretch>2</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n       </widget>\n      </item>\n      <item row=\"4\" column=\"3\">\n       <widget class=\"QPushButton\" name=\"pushButtonLogDirectory\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Fixed\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"text\">\n         <string>...</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"1\" column=\"0\" colspan=\"4\">\n       <widget class=\"QCheckBox\" name=\"checkBoxBranchTracing\">\n        <property name=\"text\">\n         <string>Branch tracing</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"2\" column=\"0\" colspan=\"4\">\n       <widget class=\"QCheckBox\" name=\"checkBoxOutputStdout\">\n        <property name=\"text\">\n         <string>Output to stdout</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"5\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_2\">\n        <property name=\"text\">\n         <string>Log Level</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"5\" column=\"2\" colspan=\"2\">\n       <widget class=\"QComboBox\" name=\"comboBoxLogLevel\"/>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox_2\">\n     <property name=\"title\">\n      <string>CafeOS HLE Function Call Tracing</string>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n      <item>\n       <widget class=\"QCheckBox\" name=\"checkBoxHleTrace\">\n        <property name=\"text\">\n         <string>Enabled</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QCheckBox\" name=\"checkBoxHleTraceReturnValue\">\n        <property name=\"text\">\n         <string>Trace return value</string>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QListWidget\" name=\"listWidgetHleTraceFilters\"/>\n      </item>\n      <item>\n       <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n        <item>\n         <widget class=\"QPushButton\" name=\"pushButtonAddTraceFilter\">\n          <property name=\"text\">\n           <string>Add</string>\n          </property>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QPushButton\" name=\"pushButtonRemoveTraceFilter\">\n          <property name=\"text\">\n           <string>Remove</string>\n          </property>\n         </widget>\n        </item>\n       </layout>\n      </item>\n     </layout>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>pushButtonAddTraceFilter</sender>\n   <signal>clicked()</signal>\n   <receiver>LoggingSettingsWidget</receiver>\n   <slot>addTraceFilter()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>103</x>\n     <y>370</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>195</x>\n     <y>196</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonRemoveTraceFilter</sender>\n   <signal>clicked()</signal>\n   <receiver>LoggingSettingsWidget</receiver>\n   <slot>removeTraceFilter()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>287</x>\n     <y>370</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>195</x>\n     <y>196</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>pushButtonLogDirectory</sender>\n   <signal>clicked()</signal>\n   <receiver>LoggingSettingsWidget</receiver>\n   <slot>browseLogPath()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>349</x>\n     <y>103</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>195</x>\n     <y>196</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>browseLogPath()</slot>\n  <slot>addTraceFilter()</slot>\n  <slot>removeTraceFilter()</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/mainwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>MainWindow</class>\n <widget class=\"QMainWindow\" name=\"MainWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>675</width>\n    <height>465</height>\n   </rect>\n  </property>\n  <property name=\"minimumSize\">\n   <size>\n    <width>100</width>\n    <height>100</height>\n   </size>\n  </property>\n  <property name=\"windowTitle\">\n   <string>decaf-qt</string>\n  </property>\n  <property name=\"windowIcon\">\n   <iconset resource=\"../resources/resources.qrc\">\n    <normaloff>:/images/icon</normaloff>:/images/icon</iconset>\n  </property>\n  <widget class=\"QMenuBar\" name=\"menuBar\">\n   <property name=\"geometry\">\n    <rect>\n     <x>0</x>\n     <y>0</y>\n     <width>675</width>\n     <height>22</height>\n    </rect>\n   </property>\n   <widget class=\"QMenu\" name=\"menuFile\">\n    <property name=\"title\">\n     <string>&amp;File</string>\n    </property>\n    <addaction name=\"actionOpen\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionExit\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuOptions\">\n    <property name=\"title\">\n     <string>&amp;Options</string>\n    </property>\n    <addaction name=\"actionSettings\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionSystemSettings\"/>\n    <addaction name=\"actionContentSettings\"/>\n    <addaction name=\"actionInputSettings\"/>\n    <addaction name=\"actionLogSettings\"/>\n    <addaction name=\"actionDebugSettings\"/>\n    <addaction name=\"actionAudioSettings\"/>\n    <addaction name=\"actionDisplaySettings\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuHelp\">\n    <property name=\"title\">\n     <string>&amp;Help</string>\n    </property>\n    <addaction name=\"actionAbout\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuView\">\n    <property name=\"title\">\n     <string>&amp;View</string>\n    </property>\n    <addaction name=\"actionViewFullScreen\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionViewSplit\"/>\n    <addaction name=\"actionViewTV\"/>\n    <addaction name=\"actionViewGamepad1\"/>\n    <addaction name=\"actionViewGamepad2\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionViewTitleList\"/>\n    <addaction name=\"actionViewTitleGrid\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuTools\">\n    <property name=\"title\">\n     <string>&amp;Tools</string>\n    </property>\n    <addaction name=\"actionDebugger\"/>\n   </widget>\n   <addaction name=\"menuFile\"/>\n   <addaction name=\"menuView\"/>\n   <addaction name=\"menuTools\"/>\n   <addaction name=\"menuOptions\"/>\n   <addaction name=\"menuHelp\"/>\n  </widget>\n  <widget class=\"QStatusBar\" name=\"statusBar\"/>\n  <action name=\"actionOpen\">\n   <property name=\"text\">\n    <string>&amp;Open</string>\n   </property>\n  </action>\n  <action name=\"actionExit\">\n   <property name=\"text\">\n    <string>E&amp;xit</string>\n   </property>\n  </action>\n  <action name=\"actionInputSettings\">\n   <property name=\"text\">\n    <string>&amp;Input Settings</string>\n   </property>\n  </action>\n  <action name=\"actionAbout\">\n   <property name=\"text\">\n    <string>&amp;About</string>\n   </property>\n  </action>\n  <action name=\"actionSettings\">\n   <property name=\"text\">\n    <string>&amp;Settings</string>\n   </property>\n  </action>\n  <action name=\"actionSystemSettings\">\n   <property name=\"text\">\n    <string>S&amp;ystem Settings</string>\n   </property>\n  </action>\n  <action name=\"actionDebugSettings\">\n   <property name=\"text\">\n    <string>&amp;Debug Settings</string>\n   </property>\n  </action>\n  <action name=\"actionLogSettings\">\n   <property name=\"text\">\n    <string>&amp;Log Settings</string>\n   </property>\n  </action>\n  <action name=\"actionDebugger\">\n   <property name=\"text\">\n    <string>&amp;Debugger</string>\n   </property>\n  </action>\n  <action name=\"actionAudioSettings\">\n   <property name=\"text\">\n    <string>Audio Settings</string>\n   </property>\n  </action>\n  <action name=\"actionDisplaySettings\">\n   <property name=\"text\">\n    <string>Display Settings</string>\n   </property>\n  </action>\n  <action name=\"actionContentSettings\">\n   <property name=\"text\">\n    <string>Content Settings</string>\n   </property>\n  </action>\n  <actiongroup name=\"titleViewActionGroup\">\n   <action name=\"actionViewTitleList\">\n    <property name=\"checkable\">\n     <bool>true</bool>\n    </property>\n    <property name=\"text\">\n     <string>Title List</string>\n    </property>\n   </action>\n   <action name=\"actionViewTitleGrid\">\n    <property name=\"checkable\">\n     <bool>true</bool>\n    </property>\n    <property name=\"text\">\n     <string>Title Grid</string>\n    </property>\n   </action>\n  </actiongroup>\n  <actiongroup name=\"viewActionGroup\">\n   <action name=\"actionViewFullScreen\">\n    <property name=\"text\">\n     <string>Full Screen</string>\n    </property>\n   </action>\n   <action name=\"actionViewSplit\">\n    <property name=\"checkable\">\n     <bool>true</bool>\n    </property>\n    <property name=\"checked\">\n     <bool>true</bool>\n    </property>\n    <property name=\"text\">\n     <string>Split</string>\n    </property>\n   </action>\n   <action name=\"actionViewTV\">\n    <property name=\"checkable\">\n     <bool>true</bool>\n    </property>\n    <property name=\"text\">\n     <string>TV</string>\n    </property>\n   </action>\n   <action name=\"actionViewGamepad1\">\n    <property name=\"checkable\">\n     <bool>true</bool>\n    </property>\n    <property name=\"text\">\n     <string>Gamepad 1</string>\n    </property>\n   </action>\n   <action name=\"actionViewGamepad2\">\n    <property name=\"checkable\">\n     <bool>true</bool>\n    </property>\n    <property name=\"enabled\">\n     <bool>false</bool>\n    </property>\n    <property name=\"text\">\n     <string>Gamepad 2</string>\n    </property>\n   </action>\n   <property name=\"exclusive\" stdset=\"0\">\n    <bool>true</bool>\n   </property>\n  </actiongroup>\n </widget>\n <resources>\n  <include location=\"../resources/resources.qrc\"/>\n </resources>\n <connections>\n  <connection>\n   <sender>actionInputSettings</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openInputSettings()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionOpen</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openFile()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionAbout</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openAboutDialog()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionExit</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>exit()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionViewGamepad1</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>setViewModeGamepad1()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionViewFullScreen</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>toggleFullScreen()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionViewSplit</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>setViewModeSplit()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionViewTV</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>setViewModeTV()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionLogSettings</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openLoggingSettings()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionDebugSettings</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openDebugSettings()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionSettings</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openSettings()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionSystemSettings</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openSystemSettings()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionDebugger</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openDebugger()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionContentSettings</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openContentSettings()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionAudioSettings</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openAudioSettings()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionDisplaySettings</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>openDisplaySettings()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionViewTitleList</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>setTitleListModeList()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>actionViewTitleGrid</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>setTitleListModeGrid()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>337</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>openInputSettings()</slot>\n  <slot>openFile()</slot>\n  <slot>openAboutDialog()</slot>\n  <slot>exit()</slot>\n  <slot>setViewModeSplit()</slot>\n  <slot>setViewModeTV()</slot>\n  <slot>setViewModeGamepad1()</slot>\n  <slot>openLoggingSettings()</slot>\n  <slot>openSettings()</slot>\n  <slot>openDebugSettings()</slot>\n  <slot>openSystemSettings()</slot>\n  <slot>openDebugger()</slot>\n  <slot>openDisplaySettings()</slot>\n  <slot>openAudioSettings()</slot>\n  <slot>openContentSettings()</slot>\n  <slot>setTitleListModeList()</slot>\n  <slot>setTitleListModeGrid()</slot>\n  <slot>toggleFullScreen()</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/settings.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>SettingsDialog</class>\n <widget class=\"QDialog\" name=\"SettingsDialog\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>697</width>\n    <height>493</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Settings</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QTabWidget\" name=\"tabWidget\">\n     <property name=\"currentIndex\">\n      <number>-1</number>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QDialogButtonBox\" name=\"buttonBox\">\n     <property name=\"orientation\">\n      <enum>Qt::Horizontal</enum>\n     </property>\n     <property name=\"standardButtons\">\n      <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>\n     </property>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections>\n  <connection>\n   <sender>buttonBox</sender>\n   <signal>clicked(QAbstractButton*)</signal>\n   <receiver>SettingsDialog</receiver>\n   <slot>buttonBoxClicked(QAbstractButton*)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>248</x>\n     <y>254</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>157</x>\n     <y>274</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>buttonBoxClicked(QAbstractButton*)</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/systemsettings.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>SystemSettingsWidget</class>\n <widget class=\"QWidget\" name=\"SystemSettingsWidget\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>454</width>\n    <height>320</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox_3\">\n     <property name=\"title\">\n      <string>Options</string>\n     </property>\n     <layout class=\"QFormLayout\" name=\"formLayout\">\n      <item row=\"0\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_7\">\n        <property name=\"text\">\n         <string>Region</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"0\" column=\"1\">\n       <widget class=\"QComboBox\" name=\"comboBoxRegion\"/>\n      </item>\n      <item row=\"1\" column=\"0\">\n       <widget class=\"QLabel\" name=\"label_8\">\n        <property name=\"text\">\n         <string>Time Scale</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"1\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditTimeScale\">\n        <property name=\"inputMask\">\n         <string/>\n        </property>\n        <property name=\"text\">\n         <string/>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBox_2\">\n     <property name=\"title\">\n      <string>CafeOS LLE Whitelist</string>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n      <item>\n       <widget class=\"QListWidget\" name=\"listWidgetLleWhitelist\"/>\n      </item>\n     </layout>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "src/decaf-qt/ui/titlelist.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>TitleListWidget</class>\n <widget class=\"QWidget\" name=\"TitleListWidget\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>609</width>\n    <height>447</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Form</string>\n  </property>\n  <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"QTreeView\" name=\"treeViewTitleList\">\n     <property name=\"showDropIndicator\" stdset=\"0\">\n      <bool>false</bool>\n     </property>\n     <property name=\"verticalScrollMode\">\n      <enum>QAbstractItemView::ScrollPerPixel</enum>\n     </property>\n     <property name=\"sortingEnabled\">\n      <bool>true</bool>\n     </property>\n     <attribute name=\"headerStretchLastSection\">\n      <bool>false</bool>\n     </attribute>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "src/decaf-sdl/CMakeLists.txt",
    "content": "project(decaf-sdl)\n\ninclude_directories(\".\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nif(MSVC)\n    set(RESOURCE_FILES\n        ${CMAKE_SOURCE_DIR}/resources/decaf-sdl.rc\n        ${CMAKE_SOURCE_DIR}/resources/hidpi.manifest)\nelse()\n    set(RESOURCE_FILES \"\")\nendif()\n\nadd_executable(decaf-sdl\n    ${SOURCE_FILES}\n    ${HEADER_FILES}\n    ${RESOURCE_FILES})\n\ntarget_include_directories(decaf-sdl PRIVATE\n    ${SDL2_INCLUDE_DIRS})\n\ntarget_link_libraries(decaf-sdl\n    common\n    libconfig\n    libdecaf\n    excmd\n    ${SDL2_LIBRARIES})\n\nif(MSVC)\n    set_target_properties(decaf-sdl PROPERTIES\n        LINK_FLAGS \"/SUBSYSTEM:WINDOWS\")\n    target_link_libraries(decaf-sdl Setupapi)\nendif()\n\nif(DECAF_PCH)\n    target_precompile_headers(decaf-sdl\n      PRIVATE\n        <SDL.h>\n    )\n\n    AutoGroupPCHFiles()\nendif()\n\ninstall(TARGETS decaf-sdl RUNTIME DESTINATION \"${DECAF_INSTALL_BINDIR}\")\n\nif(COMMAND x_vcpkg_install_local_dependencies)\n   x_vcpkg_install_local_dependencies(TARGETS decaf-sdl DESTINATION \"${DECAF_INSTALL_BINDIR}\")\nendif()\n"
  },
  {
    "path": "src/decaf-sdl/clilog.h",
    "content": "#pragma once\n#include <spdlog/spdlog.h>\n\nextern std::shared_ptr<spdlog::logger>\ngCliLog;\n"
  },
  {
    "path": "src/decaf-sdl/config.cpp",
    "content": "#include \"config.h\"\n\n#include <SDL_keycode.h>\n\nnamespace config\n{\n\nnamespace input\n{\n\nstd::vector<InputDevice> devices;\nstd::string vpad0 = \"default_keyboard\";\n\n} // namespace input\n\nnamespace sound\n{\n\nunsigned frame_length = 30;\n\n} // namespace sound\n\nnamespace test\n{\n\n//! Maximum time to run for before termination in milliseconds.\nint timeout_ms = -1;\n\n//! Maximum number of frames to render before termination.\nint timeout_frames = -1;\n\n//! Whether to dump rendered DRC frames to file;\nbool dump_drc_frames = false;\n\n//! Whether to dump rendered TV frames to file;\nbool dump_tv_frames = false;\n\n//! What directory to place dumped frames in.\nstd::string dump_frames_dir = \"frames\";\n\n} // namespace test\n\nvoid\nsetupDefaultInputDevices()\n{\n   auto foundKeyboardDevice = false;\n   auto foundJoystickDevice = false;\n\n   for (auto &device : input::devices) {\n      if (device.type == input::ControllerType::Keyboard) {\n         foundKeyboardDevice = true;\n      } else if (device.type == input::ControllerType::Joystick) {\n         foundJoystickDevice = true;\n      }\n   }\n\n   // Setup default keyboard device\n   if (!foundKeyboardDevice) {\n      input::InputDevice device;\n      device.id = \"default_keyboard\";\n      device.type = input::Keyboard;\n      device.device_name = \"\";\n      device.button_up = SDL_SCANCODE_UP;\n      device.button_down = SDL_SCANCODE_DOWN;\n      device.button_left = SDL_SCANCODE_LEFT;\n      device.button_right = SDL_SCANCODE_RIGHT;\n      device.button_a = SDL_SCANCODE_X;\n      device.button_b = SDL_SCANCODE_Z;\n      device.button_x = SDL_SCANCODE_S;\n      device.button_y = SDL_SCANCODE_A;\n      device.button_trigger_r = SDL_SCANCODE_E;\n      device.button_trigger_l = SDL_SCANCODE_W;\n      device.button_trigger_zr = SDL_SCANCODE_R;\n      device.button_trigger_zl = SDL_SCANCODE_Q;\n      device.button_stick_l = SDL_SCANCODE_KP_0;\n      device.button_stick_r = SDL_SCANCODE_KP_ENTER;\n      device.button_plus = SDL_SCANCODE_1;\n      device.button_minus = SDL_SCANCODE_2;\n      device.button_home = SDL_SCANCODE_3;\n      device.button_sync = SDL_SCANCODE_4;\n\n      input::InputDeviceKeyboard keyboard;\n      keyboard.left_stick_up = SDL_SCANCODE_KP_8;\n      keyboard.left_stick_down = SDL_SCANCODE_KP_2;\n      keyboard.left_stick_left = SDL_SCANCODE_KP_4;\n      keyboard.left_stick_right = SDL_SCANCODE_KP_6;\n      keyboard.right_stick_up = -1;\n      keyboard.right_stick_down = -1;\n      keyboard.right_stick_left = -1;\n      keyboard.right_stick_right = -1;\n      device.typeExtra = keyboard;\n\n      input::devices.push_back(device);\n   }\n\n   // Setup default joystick device\n   if (!foundJoystickDevice) {\n      input::InputDevice device;\n      device.id = \"default_joystick\";\n      device.device_name = \"\";\n      device.type = input::Joystick;\n      device.button_up = -2;\n      device.button_down = -2;\n      device.button_left = -2;\n      device.button_right = -2;\n      device.button_a = -2;\n      device.button_b = -2;\n      device.button_x = -2;\n      device.button_y = -2;\n      device.button_trigger_r = -2;\n      device.button_trigger_l = -2;\n      device.button_trigger_zr = -2;\n      device.button_trigger_zl = -2;\n      device.button_stick_l = -2;\n      device.button_stick_r = -2;\n      device.button_plus = -2;\n      device.button_minus = -2;\n      device.button_home = -2;\n      device.button_sync = -2;\n\n      input::InputDeviceJoystick joystick;\n      joystick.left_stick_x = -2;\n      joystick.left_stick_x_invert = false;\n      joystick.left_stick_y = -2;\n      joystick.left_stick_y_invert = false;\n      joystick.right_stick_x = -2;\n      joystick.right_stick_x_invert = false;\n      joystick.right_stick_y = -2;\n      joystick.right_stick_y_invert = false;\n      device.typeExtra = joystick;\n\n      input::devices.push_back(device);\n   }\n}\n\nbool\nloadFrontendToml(const toml::table &config)\n{\n   // input\n   auto devices = config.at_path(\"input.devices\").as_array();\n   input::devices.clear();\n\n   if (devices) {\n      for (auto itr = devices->begin(); itr != devices->end(); ++itr) {\n         auto cfgDevice = itr->as_table();\n         config::input::InputDevice device;\n\n         auto type = cfgDevice->get_as<std::string>(\"type\");\n         if (!type) {\n            continue;\n         } else if (type->get() == \"keyboard\") {\n            device.type = config::input::ControllerType::Keyboard;\n         } else if (type->get() == \"joystick\") {\n            device.type = config::input::ControllerType::Joystick;\n         } else {\n            continue;\n         }\n\n         readValue(*cfgDevice, \"id\", device.id);\n         readValue(*cfgDevice, \"device_name\", device.device_name);\n         readValue(*cfgDevice, \"button_up\", device.button_up);\n         readValue(*cfgDevice, \"button_down\", device.button_down);\n         readValue(*cfgDevice, \"button_left\", device.button_left);\n         readValue(*cfgDevice, \"button_right\", device.button_right);\n         readValue(*cfgDevice, \"button_a\", device.button_a);\n         readValue(*cfgDevice, \"button_b\", device.button_b);\n         readValue(*cfgDevice, \"button_x\", device.button_x);\n         readValue(*cfgDevice, \"button_y\", device.button_y);\n         readValue(*cfgDevice, \"button_trigger_r\", device.button_trigger_r);\n         readValue(*cfgDevice, \"button_trigger_l\", device.button_trigger_l);\n         readValue(*cfgDevice, \"button_trigger_zr\", device.button_trigger_zr);\n         readValue(*cfgDevice, \"button_trigger_zl\", device.button_trigger_zl);\n         readValue(*cfgDevice, \"button_stick_l\", device.button_stick_l);\n         readValue(*cfgDevice, \"button_stick_r\", device.button_stick_r);\n         readValue(*cfgDevice, \"button_plus\", device.button_plus);\n         readValue(*cfgDevice, \"button_minus\", device.button_minus);\n         readValue(*cfgDevice, \"button_home\", device.button_home);\n         readValue(*cfgDevice, \"button_sync\", device.button_sync);\n\n         if (device.type == config::input::ControllerType::Keyboard) {\n            auto keyboard = config::input::InputDeviceKeyboard {};\n            readValue(*cfgDevice, \"left_stick_up\", keyboard.left_stick_up);\n            readValue(*cfgDevice, \"left_stick_down\", keyboard.left_stick_down);\n            readValue(*cfgDevice, \"left_stick_left\", keyboard.left_stick_left);\n            readValue(*cfgDevice, \"left_stick_right\", keyboard.left_stick_right);\n\n            readValue(*cfgDevice, \"right_stick_up\", keyboard.right_stick_up);\n            readValue(*cfgDevice, \"right_stick_down\", keyboard.right_stick_down);\n            readValue(*cfgDevice, \"right_stick_left\", keyboard.right_stick_left);\n            readValue(*cfgDevice, \"right_stick_right\", keyboard.right_stick_right);\n            device.typeExtra = keyboard;\n         } else if (device.type == config::input::ControllerType::Joystick) {\n            auto joystick = config::input::InputDeviceJoystick {};\n            readValue(*cfgDevice, \"left_stick_x\", joystick.left_stick_x);\n            readValue(*cfgDevice, \"left_stick_x_invert\", joystick.left_stick_x_invert);\n            readValue(*cfgDevice, \"left_stick_y\", joystick.left_stick_y);\n            readValue(*cfgDevice, \"left_stick_y_invert\", joystick.left_stick_y_invert);\n\n            readValue(*cfgDevice, \"right_stick_x\", joystick.right_stick_x);\n            readValue(*cfgDevice, \"right_stick_x_invert\", joystick.right_stick_x_invert);\n            readValue(*cfgDevice, \"right_stick_y\", joystick.right_stick_y);\n            readValue(*cfgDevice, \"right_stick_y_invert\", joystick.right_stick_y_invert);\n            device.typeExtra = joystick;\n         }\n\n         config::input::devices.push_back(device);\n      }\n   }\n\n   readValue(config, \"input.vpad0\", config::input::vpad0);\n   setupDefaultInputDevices();\n\n   // sound\n   readValue(config, \"sound.frame_length\", config::sound::frame_length);\n\n   // test\n   readValue(config, \"test.timeout_ms\", config::test::timeout_ms);\n   readValue(config, \"test.timeout_frames\", config::test::timeout_frames);\n   readValue(config, \"test.dump_drc_frames\", config::test::dump_drc_frames);\n   readValue(config, \"test.dump_tv_frames\", config::test::dump_tv_frames);\n   readValue(config, \"test.dump_frames_dir\", config::test::dump_frames_dir);\n   return true;\n}\n\nbool\nsaveFrontendToml(toml::table &config)\n{\n   setupDefaultInputDevices();\n\n   // input\n   auto input = config.insert(\"input\", toml::table()).first->second.as_table();\n   input->insert_or_assign(\"vpad0\", config::input::vpad0);\n\n   auto devices = config.insert(\"devices\", toml::array()).first->second.as_array();\n   for (const auto &device : config::input::devices) {\n      auto cfgDevice = toml::table();\n\n      if (device.type == config::input::ControllerType::Joystick) {\n         cfgDevice.insert_or_assign(\"type\", \"joystick\");\n      } else if (device.type == config::input::ControllerType::Keyboard) {\n         cfgDevice.insert_or_assign(\"type\", \"keyboard\");\n      } else {\n         cfgDevice.insert_or_assign(\"type\", \"unknown\");\n      }\n\n      cfgDevice.insert_or_assign(\"id\", device.id);\n      cfgDevice.insert_or_assign(\"device_name\", device.device_name);\n      cfgDevice.insert_or_assign(\"button_up\", device.button_up);\n      cfgDevice.insert_or_assign(\"button_down\", device.button_down);\n      cfgDevice.insert_or_assign(\"button_left\", device.button_left);\n      cfgDevice.insert_or_assign(\"button_right\", device.button_right);\n      cfgDevice.insert_or_assign(\"button_a\", device.button_a);\n      cfgDevice.insert_or_assign(\"button_b\", device.button_b);\n      cfgDevice.insert_or_assign(\"button_x\", device.button_x);\n      cfgDevice.insert_or_assign(\"button_y\", device.button_y);\n      cfgDevice.insert_or_assign(\"button_trigger_r\", device.button_trigger_r);\n      cfgDevice.insert_or_assign(\"button_trigger_l\", device.button_trigger_l);\n      cfgDevice.insert_or_assign(\"button_trigger_zr\", device.button_trigger_zr);\n      cfgDevice.insert_or_assign(\"button_trigger_zl\", device.button_trigger_zl);\n      cfgDevice.insert_or_assign(\"button_stick_l\", device.button_stick_l);\n      cfgDevice.insert_or_assign(\"button_stick_r\", device.button_stick_r);\n      cfgDevice.insert_or_assign(\"button_plus\", device.button_plus);\n      cfgDevice.insert_or_assign(\"button_minus\", device.button_minus);\n      cfgDevice.insert_or_assign(\"button_home\", device.button_home);\n      cfgDevice.insert_or_assign(\"button_sync\", device.button_sync);\n\n      if (device.type == config::input::ControllerType::Keyboard) {\n         const auto &keyboard =\n            std::get<config::input::InputDeviceKeyboard>(device.typeExtra);\n         cfgDevice.insert_or_assign(\"left_stick_up\", keyboard.left_stick_up);\n         cfgDevice.insert_or_assign(\"left_stick_down\", keyboard.left_stick_down);\n         cfgDevice.insert_or_assign(\"left_stick_left\", keyboard.left_stick_left);\n         cfgDevice.insert_or_assign(\"left_stick_right\", keyboard.left_stick_right);\n         cfgDevice.insert_or_assign(\"right_stick_up\", keyboard.right_stick_up);\n         cfgDevice.insert_or_assign(\"right_stick_down\", keyboard.right_stick_down);\n         cfgDevice.insert_or_assign(\"right_stick_left\", keyboard.right_stick_left);\n         cfgDevice.insert_or_assign(\"right_stick_right\", keyboard.right_stick_right);\n      } else if (device.type == config::input::ControllerType::Joystick) {\n         const auto &joystick =\n            std::get<config::input::InputDeviceJoystick>(device.typeExtra);\n         cfgDevice.insert_or_assign(\"left_stick_x\", joystick.left_stick_x);\n         cfgDevice.insert_or_assign(\"left_stick_x_invert\", joystick.left_stick_x_invert);\n         cfgDevice.insert_or_assign(\"left_stick_y\", joystick.left_stick_y);\n         cfgDevice.insert_or_assign(\"left_stick_y_invert\", joystick.left_stick_y_invert);\n         cfgDevice.insert_or_assign(\"right_stick_x\", joystick.right_stick_x);\n         cfgDevice.insert_or_assign(\"right_stick_x_invert\", joystick.right_stick_x_invert);\n         cfgDevice.insert_or_assign(\"right_stick_y\", joystick.right_stick_y);\n         cfgDevice.insert_or_assign(\"right_stick_y_invert\", joystick.right_stick_y_invert);\n      }\n\n      devices->push_back(cfgDevice);\n   }\n\n   // sound\n   auto sound = config.insert(\"sound\", toml::table()).first->second.as_table();\n   sound->insert_or_assign(\"frame_length\", config::sound::frame_length);\n\n   // test\n   auto test = config.insert(\"test\", toml::table()).first->second.as_table();\n   test->insert_or_assign(\"timeout_ms\", config::test::timeout_ms);\n   test->insert_or_assign(\"timeout_frames\", config::test::timeout_frames);\n   test->insert_or_assign(\"dump_drc_frames\", config::test::dump_drc_frames);\n   test->insert_or_assign(\"dump_tv_frames\", config::test::dump_tv_frames);\n   test->insert_or_assign(\"dump_frames_dir\", config::test::dump_frames_dir);\n   return true;\n}\n\n} // namespace config\n"
  },
  {
    "path": "src/decaf-sdl/config.h",
    "content": "#pragma once\n#include <libconfig/config_toml.h>\n#include <string>\n#include <variant>\n#include <vector>\n\nnamespace config\n{\n\nnamespace input\n{\n\nenum ControllerType\n{\n   None,\n   Keyboard,\n   Joystick,\n};\n\n/*\n * For keyboard input, each entry is an SDL_SCANCODE_ *constant; for\n * joystick input, each entry is the button number, or -2 to let SDL\n * choose an appropriate button.  In both cases, -1 means nothing is\n * assigned.\n */\n\nstruct InputDeviceKeyboard\n{\n   int left_stick_up = -1;\n   int left_stick_down = -1;\n   int left_stick_left = -1;\n   int left_stick_right = -1;\n\n   int right_stick_up = -1;\n   int right_stick_down = -1;\n   int right_stick_left = -1;\n   int right_stick_right = -1;\n};\n\nstruct InputDeviceJoystick\n{\n   int left_stick_x = -1;\n   bool left_stick_x_invert = false;\n\n   int left_stick_y = -1;\n   bool left_stick_y_invert = false;\n\n   int right_stick_x = -1;\n   bool right_stick_x_invert = false;\n\n   int right_stick_y = -1;\n   bool right_stick_y_invert = false;\n};\n\nstruct InputDevice\n{\n   ControllerType type;\n   std::string id;\n   std::string device_name;\n\n   int button_up = -1;\n   int button_down = -1;\n   int button_left = -1;\n   int button_right = -1;\n   int button_a = -1;\n   int button_b = -1;\n   int button_x = -1;\n   int button_y = -1;\n   int button_trigger_r = -1;\n   int button_trigger_l = -1;\n   int button_trigger_zr = -1;\n   int button_trigger_zl = -1;\n   int button_stick_l = -1;\n   int button_stick_r = -1;\n   int button_plus = -1;\n   int button_minus = -1;\n   int button_home = -1;\n   int button_sync = -1;\n\n   std::variant<InputDeviceKeyboard, InputDeviceJoystick> typeExtra;\n};\n\nextern std::vector<InputDevice> devices;\nextern std::string vpad0;\n\n} // namespace input\n\nnamespace sound\n{\n\n// Frame length factor for audio data.\n// Default is 30 (x 48 = 1440 for 48kHz)\nextern unsigned frame_length;\n\n} // namespace sound\n\nnamespace test\n{\n\n//! Maximum time to run for before termination in milliseconds.\nextern int timeout_ms;\n\n//! Maximum number of frames to render before termination.\nextern int timeout_frames;\n\n//! Whether to dump rendered DRC frames to file;\nextern bool dump_drc_frames;\n\n//! Whether to dump rendered TV frames to file;\nextern bool dump_tv_frames;\n\n//! What directory to place dumped frames in.\nextern std::string dump_frames_dir;\n\n} // namespace test\n\nvoid\nsetupDefaultInputDevices();\n\nbool\nloadFrontendToml(const toml::table &config);\n\nbool\nsaveFrontendToml(toml::table &config);\n\n} // namespace config\n"
  },
  {
    "path": "src/decaf-sdl/decafsdl.cpp",
    "content": "#include \"clilog.h\"\n#include \"config.h\"\n#include \"decafsdl.h\"\n\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n#include <libgpu/gpu_graphicsdriver.h>\n#include <SDL_syswm.h>\n\n#include <iterator>\n#include <thread>\n\nstatic std::string\nsActiveGfx = \"NOGFX\";\n\nvoid setWindowIcon(SDL_Window *window);\n\nDecafSDL::~DecafSDL()\n{\n}\n\nbool\nDecafSDL::initCore()\n{\n   if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0) {\n      gCliLog->error(\"Failed to initialize SDL: {}\", SDL_GetError());\n      return false;\n   }\n\n   return true;\n}\n\nbool\nDecafSDL::initGraphics()\n{\n   auto videoInitialised = false;\n\n#ifdef SDL_VIDEO_DRIVER_X11\n   if (!videoInitialised) {\n      videoInitialised = SDL_VideoInit(\"x11\") == 0;\n      if (!videoInitialised) {\n         gCliLog->error(\"Failed to initialize SDL Video with x11: {}\", SDL_GetError());\n      }\n   }\n#endif\n\n#ifdef SDL_VIDEO_DRIVER_WAYLAND\n   if (!videoInitialised) {\n      videoInitialised = SDL_VideoInit(\"wayland\") == 0;\n      if (!videoInitialised) {\n         gCliLog->error(\"Failed to initialize SDL Video with wayland: {}\", SDL_GetError());\n      }\n   }\n#endif\n\n#ifdef SDL_VIDEO_DRIVER_COCOA\n   if (!videoInitialised) {\n      videoInitialised = SDL_VideoInit(\"cocoa\") == 0;\n      if (!videoInitialised) {\n         gCliLog->error(\"Failed to initialize SDL Video with cocoa: {}\", SDL_GetError());\n      }\n   }\n#endif\n\n   if (!videoInitialised) {\n      if (SDL_VideoInit(NULL) != 0) {\n         gCliLog->error(\"Failed to initialize SDL Video: {}\", SDL_GetError());\n         return false;\n      }\n   }\n\n   gCliLog->info(\"Using SDL video driver {}\", SDL_GetCurrentVideoDriver());\n\n   mGraphicsDriver = gpu::createGraphicsDriver();\n   if (!mGraphicsDriver) {\n      return false;\n   }\n\n   switch (mGraphicsDriver->type()) {\n   case gpu::GraphicsDriverType::Vulkan:\n      sActiveGfx = \"Vulkan\";\n      break;\n   case gpu::GraphicsDriverType::Null:\n      sActiveGfx = \"Null\";\n      break;\n   default:\n      sActiveGfx = \"Unknown\";\n   }\n\n   return true;\n}\n\nbool\nDecafSDL::initSound()\n{\n   if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {\n      gCliLog->error(\"Failed to initialize SDL audio: {}\", SDL_GetError());\n      return false;\n   }\n\n   mSoundDriver = new DecafSDLSound;\n   return true;\n}\n\nstatic Uint32\nwindowTitleTimerCallback(Uint32 interval, void *param)\n{\n   auto event = SDL_Event { 0 };\n   event.type = static_cast<Uint32>(reinterpret_cast<uintptr_t>(param));\n   event.user.code = 0;\n   event.user.data1 = nullptr;\n   event.user.data2 = nullptr;\n   SDL_PushEvent(&event);\n   return interval;\n}\n\nbool\nDecafSDL::run(const std::string &gamePath)\n{\n   auto shouldQuit = false;\n\n   // Setup some basic window stuff\n   mWindow =\n      SDL_CreateWindow(\"Decaf\",\n                       SDL_WINDOWPOS_UNDEFINED,\n                       SDL_WINDOWPOS_UNDEFINED,\n                       WindowWidth, WindowHeight,\n                       SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE);\n   setWindowIcon(mWindow);\n\n   if (gpu::config()->display.screenMode == gpu::DisplaySettings::Fullscreen) {\n      SDL_SetWindowFullscreen(mWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);\n   }\n\n   mDecafEventId = SDL_RegisterEvents(2);\n   if (mDecafEventId != -1) {\n      mUpdateWindowTitleEventId = mDecafEventId + 1;\n      mWindowTitleTimerId = SDL_AddTimer(100, &windowTitleTimerCallback,\n         reinterpret_cast<void *>(static_cast<uintptr_t>(mUpdateWindowTitleEventId)));\n   }\n\n   // Setup graphics driver\n   auto wsi = gpu::WindowSystemInfo { };\n   auto sysWmInfo = SDL_SysWMinfo { };\n   SDL_VERSION(&sysWmInfo.version);\n   if (!SDL_GetWindowWMInfo(mWindow, &sysWmInfo)) {\n      gCliLog->error(\"SDL_GetWindowWMInfo failed: {}\", SDL_GetError());\n   }\n\n   switch (sysWmInfo.subsystem) {\n#ifdef SDL_VIDEO_DRIVER_WINDOWS\n   case SDL_SYSWM_WINDOWS:\n      wsi.type = gpu::WindowSystemType::Windows;\n      wsi.renderSurface = static_cast<void *>(sysWmInfo.info.win.window);\n      break;\n#endif\n#ifdef SDL_VIDEO_DRIVER_X11\n   case SDL_SYSWM_X11:\n      wsi.type = gpu::WindowSystemType::X11;\n      wsi.renderSurface = reinterpret_cast<void *>(sysWmInfo.info.x11.window);\n      wsi.displayConnection = static_cast<void *>(sysWmInfo.info.x11.display);\n      break;\n#endif\n#ifdef SDL_VIDEO_METAL\n   case SDL_SYSWM_COCOA:\n      wsi.type = gpu::WindowSystemType::Cocoa;\n      wsi.renderSurface =  static_cast<void *>(SDL_Metal_CreateView(mWindow));\n      break;\n#endif\n#ifdef SDL_VIDEO_DRIVER_WAYLAND\n   case SDL_SYSWM_WAYLAND:\n      wsi.type = gpu::WindowSystemType::Wayland;\n      wsi.renderSurface = static_cast<void *>(sysWmInfo.info.wl.surface);\n      wsi.displayConnection = static_cast<void *>(sysWmInfo.info.wl.display);\n      break;\n#endif\n   default:\n      decaf_abort(fmt::format(\"Unsupported SDL window subsystem {}\", sysWmInfo.subsystem));\n   }\n\n   mGraphicsDriver->setWindowSystemInfo(wsi);\n   mGraphicsThread = std::thread { [this]() { mGraphicsDriver->run(); } };\n   decaf::setGraphicsDriver(mGraphicsDriver);\n\n   // Set input provider\n   decaf::setInputDriver(this);\n   decaf::addEventListener(this);\n   openInputDevices();\n\n   // Set sound driver\n   decaf::setSoundDriver(mSoundDriver);\n\n   // Initialise emulator\n   if (!decaf::initialise(gamePath)) {\n      return false;\n   }\n\n   // Start emulator\n   decaf::start();\n\n   auto event = SDL_Event { };\n   while (!shouldQuit && !decaf::stopping() && SDL_WaitEvent(&event)) {\n      switch (event.type) {\n      case SDL_WINDOWEVENT:\n         if (event.window.event == SDL_WINDOWEVENT_CLOSE) {\n            shouldQuit = true;\n         } else if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {\n            mGraphicsDriver->windowSizeChanged(event.window.data1, event.window.data2);\n         }\n\n         break;\n      case SDL_KEYUP:\n         if (event.key.keysym.sym == SDLK_TAB) {\n            mToggleDRC = !mToggleDRC;\n         }\n\n         if (event.key.keysym.sym == SDLK_ESCAPE) {\n            shouldQuit = true;\n         }\n         break;\n      case SDL_QUIT:\n         shouldQuit = true;\n         break;\n      }\n\n      if (event.type == mDecafEventId) {\n         auto eventType = static_cast<decaf::EventType>(event.user.code);\n         if (eventType == decaf::EventType::GameLoaded) {\n            auto info = reinterpret_cast<decaf::GameInfo *>(event.user.data1);\n            mGameInfo = *info;\n            delete info;\n         }\n      } else if (event.type == mUpdateWindowTitleEventId) {\n         fmt::memory_buffer title;\n         fmt::format_to(std::back_inserter(title), \"decaf-sdl -\");\n\n         if (mGameInfo.titleId) {\n            fmt::format_to(std::back_inserter(title), \" {:08X}-{:08X}\",\n                           mGameInfo.titleId >> 32,\n                           mGameInfo.titleId & 0xFFFFFFFF);\n         }\n\n         if (!mGameInfo.executable.empty()) {\n            fmt::format_to(std::back_inserter(title), \" {}\", mGameInfo.executable);\n         }\n\n         fmt::format_to(std::back_inserter(title), \" ({} {} fps)\", sActiveGfx,\n                        static_cast<int>(mGraphicsDriver->getDebugInfo()->averageFps));\n         auto titleStr = std::string { title.data(), title.size() };\n         SDL_SetWindowTitle(mWindow, titleStr.c_str());\n      }\n   }\n\n   // Shut down decaf\n   decaf::shutdown();\n\n   // Shut down graphics\n   mGraphicsDriver->stop();\n   mGraphicsThread.join();\n\n   return true;\n}\n\nvoid\nDecafSDL::onGameLoaded(const decaf::GameInfo &info)\n{\n   if (mDecafEventId != -1) {\n      auto event = SDL_Event { 0 };\n      event.type = mDecafEventId;\n      event.user.code = static_cast<Sint32>(decaf::EventType::GameLoaded);\n      event.user.data1 = new decaf::GameInfo(info);\n      SDL_PushEvent(&event);\n   }\n}\n"
  },
  {
    "path": "src/decaf-sdl/decafsdl.h",
    "content": "#pragma once\n#include \"decafsdl_sound.h\"\n\n#include <libdecaf/decaf.h>\n#include <SDL.h>\n#include <thread>\n\nusing namespace decaf::input;\n\nnamespace gpu { class GraphicsDriver; }\n\nnamespace config::input\n{\n\nstruct InputDevice;\n\n} // namespace config::input\n\nclass DecafSDL : public decaf::InputDriver, public decaf::EventListener\n{\n   static const auto WindowWidth = 1420;\n   static const auto WindowHeight = 768;\n\npublic:\n   ~DecafSDL();\n\n   bool initCore();\n   bool initGraphics();\n   bool initSound();\n\n   bool run(const std::string &gamePath);\n\nprivate:\n   void openInputDevices();\n\n   void sampleVpadController(int channel, vpad::Status &status) override;\n   void sampleWpadController(int channel, wpad::Status &status) override;\n\n   // Events\n   virtual void onGameLoaded(const decaf::GameInfo &info) override;\n\nprotected:\n   DecafSDLSound *mSoundDriver = nullptr;\n\n   SDL_Window *mWindow = nullptr;\n   gpu::GraphicsDriver *mGraphicsDriver = nullptr;\n   std::thread mGraphicsThread;\n\n   const config::input::InputDevice *mVpad0Config = nullptr;\n   SDL_GameController *mVpad0Controller = nullptr;\n\n   bool mToggleDRC = false;\n\n   Uint32 mDecafEventId = static_cast<Uint32>(-1);\n   Uint32 mUpdateWindowTitleEventId = static_cast<Uint32>(-1);\n   SDL_TimerID mWindowTitleTimerId = -1;\n\n   decaf::GameInfo mGameInfo;\n};\n"
  },
  {
    "path": "src/decaf-sdl/decafsdl_input.cpp",
    "content": "#include \"decafsdl.h\"\n\n#include \"clilog.h\"\n#include \"config.h\"\n\n#include <common/decaf_assert.h>\n#include <libgpu/gpu7_displaylayout.h>\n\nvoid\nDecafSDL::openInputDevices()\n{\n   mVpad0Config = nullptr;\n   mVpad0Controller = nullptr;\n\n   for (const auto &device : config::input::devices) {\n      if (config::input::vpad0.compare(device.id) != 0) {\n         continue;\n      }\n\n      if (device.type == config::input::Joystick) {\n         auto numJoysticks = SDL_NumJoysticks();\n\n         for (int i = 0; i < numJoysticks; ++i) {\n            if (!SDL_IsGameController(i)) {\n               continue;\n            }\n\n            auto controller = SDL_GameControllerOpen(i);\n\n            if (!controller) {\n               gCliLog->error(\"Failed to open game controller {}: {}\", i, SDL_GetError());\n               continue;\n            }\n\n            auto name = SDL_GameControllerName(controller);\n\n            if (!device.device_name.empty() && device.device_name.compare(name) != 0) {\n               SDL_GameControllerClose(controller);\n               continue;\n            }\n\n            mVpad0Controller = controller;\n            break;\n         }\n\n         if (!mVpad0Controller) {\n            continue;\n         }\n      }\n\n      mVpad0Config = &device;\n      break;\n   }\n\n   if (!mVpad0Config) {\n      gCliLog->warn(\"No input device found for gamepad (VPAD0)\");\n   }\n}\n\nvoid\nDecafSDL::sampleVpadController(int channel, vpad::Status &status)\n{\n   auto device = mVpad0Config;\n   if (!device || channel > 0 || channel < 0) {\n      status.connected = false;\n      return;\n   }\n\n   if (device->type == config::input::Keyboard) {\n      auto numKeys = 0;\n      const auto keyboardState = SDL_GetKeyboardState(&numKeys);\n      const auto getState =\n         [keyboardState, numKeys](int scancode) -> uint32_t {\n            if (scancode < 0 || scancode > numKeys) {\n               return 0;\n            } else {\n               return keyboardState[scancode] ? 1 : 0;\n            }\n         };\n\n      status.connected = true;\n      status.buttons.sync = getState(device->button_sync);\n      status.buttons.home = getState(device->button_home);\n      status.buttons.minus = getState(device->button_minus);\n      status.buttons.plus = getState(device->button_plus);\n      status.buttons.r = getState(device->button_trigger_r);\n      status.buttons.l = getState(device->button_trigger_l);\n      status.buttons.zr = getState(device->button_trigger_zr);\n      status.buttons.zl = getState(device->button_trigger_zl);\n      status.buttons.down = getState(device->button_down);\n      status.buttons.up = getState(device->button_up);\n      status.buttons.right = getState(device->button_right);\n      status.buttons.left = getState(device->button_left);\n      status.buttons.y = getState(device->button_y);\n      status.buttons.x = getState(device->button_x);\n      status.buttons.b = getState(device->button_b);\n      status.buttons.a = getState(device->button_a);\n      status.buttons.stickR = getState(device->button_stick_r);\n      status.buttons.stickL = getState(device->button_stick_l);\n\n      status.leftStickX = 0.0f;\n      status.leftStickY = 0.0f;\n\n      status.rightStickX = 0.0f;\n      status.rightStickY = 0.0f;\n\n      auto &deviceKeyboard = std::get<config::input::InputDeviceKeyboard>(device->typeExtra);\n      if (getState(deviceKeyboard.left_stick_up)) {\n         status.leftStickY += 1.0f;\n      }\n\n      if (getState(deviceKeyboard.left_stick_down)) {\n         status.leftStickY -= 1.0f;\n      }\n\n      if (getState(deviceKeyboard.left_stick_left)) {\n         status.leftStickX -= 1.0f;\n      }\n\n      if (getState(deviceKeyboard.left_stick_right)) {\n         status.leftStickX += 1.0f;\n      }\n\n      if (getState(deviceKeyboard.right_stick_up)) {\n         status.rightStickY += 1.0f;\n      }\n\n      if (getState(deviceKeyboard.right_stick_down)) {\n         status.rightStickY -= 1.0f;\n      }\n\n      if (getState(deviceKeyboard.right_stick_left)) {\n         status.rightStickX -= 1.0f;\n      }\n\n      if (getState(deviceKeyboard.right_stick_right)) {\n         status.rightStickX += 1.0f;\n      }\n\n      status.touch.down = false;\n\n      int mouseX, mouseY;\n      if (SDL_GetMouseState(&mouseX, &mouseY) & SDL_BUTTON(SDL_BUTTON_LEFT)) {\n         // Calculate screen position\n         auto displayLayout = gpu7::DisplayLayout { };\n         auto windowWidth = 0, windowHeight = 0;\n         SDL_GetWindowSize(mWindow, &windowWidth, &windowHeight);\n         gpu7::updateDisplayLayout(displayLayout,\n                                   static_cast<float>(windowWidth),\n                                   static_cast<float>(windowHeight));\n\n         // Check that mouse is inside DRC screen\n         if (displayLayout.drc.visible) {\n            auto drcLeft = displayLayout.drc.x;\n            auto drcTop = displayLayout.drc.y;\n            auto drcRight = drcLeft + displayLayout.drc.width;\n            auto drcBottom = drcTop + displayLayout.drc.height;\n\n            if (mouseX >= drcLeft && mouseX <= drcRight &&\n                mouseY >= drcTop && mouseY <= drcBottom) {\n               status.touch.down = true;\n               status.touch.x = (mouseX - drcLeft) / displayLayout.drc.width;\n               status.touch.y = (mouseY - drcTop) / displayLayout.drc.height;\n            }\n         }\n      }\n   } else if (mVpad0Controller && device->type == config::input::Joystick) {\n      auto controller = mVpad0Controller;\n      auto joystick = SDL_GameControllerGetJoystick(controller);\n      const auto getState =\n         [controller, joystick](int button, SDL_GameControllerButton name) -> uint32_t {\n            if (button >= 0) {\n               return SDL_JoystickGetButton(joystick, button) ? 1 : 0;\n            } else if (button == -2 && name != SDL_CONTROLLER_BUTTON_INVALID) {\n               return SDL_GameControllerGetButton(controller, name);\n            } else {\n               return 0;\n            }\n         };\n      const auto getAxis =\n         [controller, joystick](int index, SDL_GameControllerAxis name) -> float {\n            auto value = 0;\n            if (index >= 0) {\n               value = SDL_JoystickGetAxis(joystick, index);\n            } else if (index == -2 && name != SDL_CONTROLLER_AXIS_INVALID) {\n               value = SDL_GameControllerGetAxis(controller, name);\n            }\n\n            if (value < 0) {\n               return value / 32768.0f;\n            } else {\n               return value / 32767.0f;\n            }\n         };\n\n      status.connected = true;\n      status.buttons.sync = getState(device->button_sync, SDL_CONTROLLER_BUTTON_INVALID);\n      status.buttons.home = getState(device->button_home, SDL_CONTROLLER_BUTTON_GUIDE);\n      status.buttons.minus = getState(device->button_minus, SDL_CONTROLLER_BUTTON_BACK);\n      status.buttons.plus = getState(device->button_plus, SDL_CONTROLLER_BUTTON_START);\n      status.buttons.r = getState(device->button_trigger_r, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);\n      status.buttons.l = getState(device->button_trigger_l, SDL_CONTROLLER_BUTTON_LEFTSHOULDER);\n      status.buttons.zr = getAxis(device->button_trigger_zr, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) > 0.5f;\n      status.buttons.zl = getAxis(device->button_trigger_zl, SDL_CONTROLLER_AXIS_TRIGGERLEFT) > 0.5f;\n      status.buttons.down = getState(device->button_down, SDL_CONTROLLER_BUTTON_DPAD_DOWN);\n      status.buttons.up = getState(device->button_up, SDL_CONTROLLER_BUTTON_DPAD_UP);\n      status.buttons.right = getState(device->button_right, SDL_CONTROLLER_BUTTON_DPAD_RIGHT);\n      status.buttons.left = getState(device->button_left, SDL_CONTROLLER_BUTTON_DPAD_LEFT);\n      status.buttons.y = getState(device->button_y, SDL_CONTROLLER_BUTTON_Y);\n      status.buttons.x = getState(device->button_x, SDL_CONTROLLER_BUTTON_X);\n      status.buttons.stickR = getState(device->button_stick_r, SDL_CONTROLLER_BUTTON_RIGHTSTICK);\n      status.buttons.stickL = getState(device->button_stick_l, SDL_CONTROLLER_BUTTON_LEFTSTICK);\n\n      // Yes A and B are purposefully swapped.\n      status.buttons.b = getState(device->button_b, SDL_CONTROLLER_BUTTON_A);\n      status.buttons.a = getState(device->button_a, SDL_CONTROLLER_BUTTON_B);\n\n      auto &deviceJoystick = std::get<config::input::InputDeviceJoystick>(device->typeExtra);\n      status.leftStickX = getAxis(deviceJoystick.left_stick_x, SDL_CONTROLLER_AXIS_LEFTX);\n      status.leftStickY = getAxis(deviceJoystick.left_stick_y, SDL_CONTROLLER_AXIS_LEFTY);\n\n      if (deviceJoystick.left_stick_x_invert) {\n         status.leftStickX = -status.leftStickX;\n      }\n\n      if (deviceJoystick.left_stick_y_invert) {\n         status.leftStickY = -status.leftStickY;\n      }\n\n      status.rightStickX = getAxis(deviceJoystick.right_stick_x, SDL_CONTROLLER_AXIS_RIGHTX);\n      status.rightStickY = getAxis(deviceJoystick.right_stick_y, SDL_CONTROLLER_AXIS_RIGHTY);\n\n      if (deviceJoystick.right_stick_x_invert) {\n         status.rightStickX = -status.rightStickX;\n      }\n\n      if (deviceJoystick.right_stick_y_invert) {\n         status.rightStickY = -status.rightStickY;\n      }\n   } else {\n      status.connected = false;\n   }\n}\n\nvoid\nDecafSDL::sampleWpadController(int channel, wpad::Status &status)\n{\n   status.type = wpad::BaseControllerType::Disconnected;\n}\n"
  },
  {
    "path": "src/decaf-sdl/decafsdl_posix.cpp",
    "content": "#include <common/platform.h>\n\n#ifdef PLATFORM_POSIX\n\n#include <SDL.h>\n\nvoid\nsetWindowIcon(SDL_Window *window)\n{\n}\n\n#endif\n"
  },
  {
    "path": "src/decaf-sdl/decafsdl_sound.cpp",
    "content": "#include \"decafsdl_sound.h\"\n#include \"config.h\"\n\n#include <common/decaf_assert.h>\n#include <SDL.h>\n#include <libdecaf/decaf_log.h>\n\nDecafSDLSound::DecafSDLSound()\n{\n}\n\nDecafSDLSound::~DecafSDLSound()\n{\n   SDL_CloseAudio();\n}\n\nbool\nDecafSDLSound::start(unsigned outputRate,\n                     unsigned numChannels)\n{\n   if (!mLog) {\n      mLog = decaf::makeLogger(\"sdl-sound\");\n   }\n\n   mNumChannelsIn = numChannels;\n   mNumChannelsOut = std::min(numChannels, 2u);  // TODO: support surround output\n   mOutputFrameLen = config::sound::frame_length * (outputRate / 1000);\n\n   // Set up the ring buffer with enough space for 3 output frames of audio\n   mOutputBuffer.resize(mOutputFrameLen * mNumChannelsOut * 3);\n   mBufferWritePos = 0;\n   mBufferReadPos = 0;\n\n   SDL_AudioSpec audiospec;\n   audiospec.format = AUDIO_S16LSB;\n   audiospec.freq = outputRate;\n   audiospec.channels = static_cast<Uint8>(mNumChannelsOut);\n   audiospec.samples = static_cast<Uint16>(mOutputFrameLen);\n   audiospec.callback = sdlCallback;\n   audiospec.userdata = this;\n\n   if (SDL_OpenAudio(&audiospec, nullptr) != 0) {\n      mLog->error(\"Failed to open audio device: {}\", SDL_GetError());\n      return false;\n   }\n\n   SDL_PauseAudio(0);\n   return true;\n}\n\nvoid\nDecafSDLSound::output(int16_t *samples, unsigned numSamples)\n{\n   // Discard channels from the input if necessary.\n   if (mNumChannelsIn != mNumChannelsOut) {\n      decaf_check(mNumChannelsOut < mNumChannelsIn);\n\n      for (auto sample = 1u; sample < numSamples; ++sample) {\n         for (auto channel = 0u; channel < mNumChannelsOut; channel++) {\n            samples[sample * mNumChannelsOut + channel] = samples[sample * mNumChannelsIn + channel];\n         }\n      }\n   }\n\n   // Copy to the output buffer, ignoring the possibility of overrun\n   //  (which should never happen anyway).\n   auto numSamplesOut = static_cast<size_t>(numSamples * mNumChannelsOut);\n\n   while (mBufferWritePos + numSamplesOut >= mOutputBuffer.size()) {\n      auto samplesToCopy = mOutputBuffer.size() - mBufferWritePos;\n      std::memcpy(&mOutputBuffer[mBufferWritePos], samples, samplesToCopy * 2);\n\n      mBufferWritePos = 0;\n      samples += samplesToCopy;\n      numSamplesOut -= samplesToCopy;\n   }\n\n   std::memcpy(&mOutputBuffer[mBufferWritePos], samples, numSamplesOut * 2);\n   mBufferWritePos += numSamplesOut;\n}\n\nvoid\nDecafSDLSound::stop()\n{\n   SDL_CloseAudio();\n}\n\nvoid\nDecafSDLSound::sdlCallback(void *instance_, Uint8 *stream_, int size)\n{\n   DecafSDLSound *instance = reinterpret_cast<DecafSDLSound *>(instance_);\n   int16_t *stream = reinterpret_cast<int16_t *>(stream_);\n   decaf_check(size >= 0);\n   decaf_check(size % (2 * instance->mNumChannelsOut) == 0);\n   auto numSamples = static_cast<size_t>(size) / 2;\n   auto samplesAvail = (instance->mBufferWritePos + instance->mOutputBuffer.size() - instance->mBufferReadPos) % instance->mOutputBuffer.size();\n\n   if (samplesAvail < numSamples) {\n      // Rather than outputting the partial frame, output a full frame of\n      //  silence to give audio generation a chance to catch up.\n      std::memset(stream, 0, size);\n   } else {\n      decaf_check(instance->mBufferReadPos + numSamples <= instance->mOutputBuffer.size());\n      std::memcpy(stream, &instance->mOutputBuffer[instance->mBufferReadPos], size);\n      instance->mBufferReadPos = (instance->mBufferReadPos + numSamples) % instance->mOutputBuffer.size();\n   }\n}\n"
  },
  {
    "path": "src/decaf-sdl/decafsdl_sound.h",
    "content": "#pragma once\n#include <libdecaf/decaf_sound.h>\n#include <SDL.h>\n#include <spdlog/spdlog.h>\n#include <vector>\n\nclass DecafSDLSound : public decaf::SoundDriver\n{\npublic:\n   DecafSDLSound();\n   ~DecafSDLSound() override;\n\n   bool start(unsigned outputRate, unsigned numChannels) override;\n   void output(int16_t *samples, unsigned numSamples) override;\n   void stop() override;\n\nprivate:\n   std::shared_ptr<spdlog::logger> mLog;\n\n   unsigned mNumChannelsIn;  // Number of channels of data we receive in output()\n   unsigned mNumChannelsOut; // Number of channels we send to the audio device\n   unsigned mOutputFrameLen; // Number of samples (per channel) in an output frame\n\n   // Output buffer (ring buffer): written by output(), read by SDL callback\n   std::vector<int16_t> mOutputBuffer;\n   size_t mBufferWritePos; // Index of next sample (array element) to write\n   size_t mBufferReadPos;  // Index of next sample (array element) to read\n\n   static void sdlCallback(void *instance, Uint8 *stream, int size);\n};\n"
  },
  {
    "path": "src/decaf-sdl/decafsdl_win.cpp",
    "content": "#include <common/platform.h>\n\n#ifdef PLATFORM_WINDOWS\n\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\n#include <SDL.h>\n#include <SDL_syswm.h>\n\nextern \"C\"\n{\n__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;\n__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;\n}\n\nvoid\nsetWindowIcon(SDL_Window *window)\n{\n   auto handle = ::GetModuleHandle(nullptr);\n   auto icon = ::LoadIcon(handle, L\"IDI_MAIN_ICON\");\n\n   if (icon != nullptr) {\n      SDL_SysWMinfo wminfo;\n      SDL_VERSION(&wminfo.version);\n\n      if (SDL_GetWindowWMInfo(window, &wminfo) == 1) {\n         auto hwnd = wminfo.info.win.window;\n         ::SetClassLongPtr(hwnd, GCLP_HICON, reinterpret_cast<LONG_PTR>(icon));\n      }\n   }\n}\n\n#endif\n"
  },
  {
    "path": "src/decaf-sdl/main.cpp",
    "content": "#include \"clilog.h\"\n#include \"config.h\"\n#include \"decafsdl.h\"\n\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <common/platform_dir.h>\n#include <excmd.h>\n#include <fmt/core.h>\n#include <iostream>\n#include <libconfig/config_excmd.h>\n#include <libconfig/config_toml.h>\n#include <libdecaf/decaf.h>\n#include <libdecaf/decaf_log.h>\n#include <spdlog/sinks/stdout_sinks.h>\n\nstd::shared_ptr<spdlog::logger>\ngCliLog;\n\nusing namespace decaf::input;\n\nstatic excmd::parser\ngetCommandLineParser()\n{\n   excmd::parser parser;\n   using excmd::description;\n   using excmd::optional;\n   using excmd::default_value;\n   using excmd::allowed;\n   using excmd::value;\n   using excmd::make_default_value;\n\n   parser.global_options()\n      .add_option(\"v,version\",\n                  description { \"Show version.\" })\n      .add_option(\"h,help\",\n                  description { \"Show help.\" });\n\n   parser.add_command(\"help\")\n      .add_argument(\"help-command\",\n                    optional {},\n                    value<std::string> {});\n\n   auto frontend_options = parser.add_option_group(\"Frontend Options\")\n      .add_option(\"config\",\n                  description { \"Specify path to configuration file.\" },\n                  value<std::string> {})\n      .add_option(\"force-sync\",\n                  description { \"Force rendering to sync with gpu flips.\" })\n      .add_option(\"display-layout\",\n                  description { \"Set the window display layout.\" },\n                  default_value<std::string> { \"split\" },\n                  allowed<std::string> { {\n                     \"split\", \"toggle\"\n                  } })\n      .add_option(\"display-mode\",\n                  description { \"Set the window display mode.\" },\n                  default_value<std::string> { \"windowed\" },\n                  allowed<std::string> { {\n                     \"windowed\", \"fullscreen\"\n                  } })\n      .add_option(\"display-stretch\",\n                  description { \"Enable display stretching, aspect ratio will not be maintained.\" })\n      .add_option(\"sound\",\n                  description { \"Enable sound output.\" });\n\n   auto input_options = parser.add_option_group(\"Input Options\")\n      .add_option(\"vpad0\",\n                  description { \"Select the input device for VPAD0.\" },\n                  make_default_value(config::input::vpad0));\n\n   auto test_options = parser.add_option_group(\"Test Options\")\n      .add_option(\"timeout-ms\",\n                  description { \"Maximum time to run for before termination in milliseconds.\" },\n                  make_default_value(config::test::timeout_ms))\n      .add_option(\"timeout-frames\",\n                  description { \"Maximum number of frames to render before termination.\" },\n                  make_default_value(config::test::timeout_frames))\n      .add_option(\"dump-drc-frames\",\n                  description { \"Dump rendered DRC frames to file.\" })\n      .add_option(\"dump-tv-frames\",\n                  description { \"Dump rendered TV frames to file.\" })\n      .add_option(\"dump-frames-dir\",\n                  description { \"Folder to place dumped frames in\" },\n                  make_default_value(config::test::dump_frames_dir));\n\n   auto config_options = config::getExcmdGroups(parser);\n\n   auto cmdPlay = parser.add_command(\"play\")\n      .add_option_group(frontend_options)\n      .add_option_group(input_options)\n      .add_argument(\"target\", value<std::string> {});\n\n   auto cmdTest = parser.add_command(\"test\")\n      .add_option_group(frontend_options)\n      .add_option_group(test_options)\n      .add_argument(\"target\", value<std::string> {});\n\n   for (auto group : config_options) {\n      cmdPlay.add_option_group(group);\n      cmdTest.add_option_group(group);\n   }\n\n   return parser;\n}\n\nstatic std::string\ngetPathBasename(const std::string &path)\n{\n   auto pos = path.find_last_of(\"/\\\\\");\n\n   if (!pos) {\n      return path;\n   } else {\n      return path.substr(pos + 1);\n   }\n}\n\nstatic int\nstart(excmd::parser &parser,\n      excmd::option_state &options)\n{\n   // Print version\n   if (options.has(\"version\")) {\n      // TODO: print git hash\n      std::cout << \"Decaf Emulator version 0.0.1\" << std::endl;\n      std::exit(0);\n   }\n\n   // Print help\n   if (options.empty() || options.has(\"help\")) {\n      if (options.has(\"help-command\")) {\n         std::cout << parser.format_help(\"decaf\", options.get<std::string>(\"help-command\")) << std::endl;\n      } else {\n         std::cout << parser.format_help(\"decaf\") << std::endl;\n      }\n\n      std::exit(0);\n   }\n\n   auto target = options.get<std::string>(\"target\");\n\n   // Load config file\n   std::string configPath, configError;\n\n   if (options.has(\"config\")) {\n      configPath = options.get<std::string>(\"config\");\n   } else {\n      decaf::createConfigDirectory();\n      configPath = decaf::makeConfigPath(\"config.toml\");\n   }\n\n   auto cpuSettings = cpu::Settings { };\n   auto gpuSettings = gpu::Settings { };\n   auto decafSettings = decaf::Settings { };\n\n   // If config file does not exist, create a default one.\n   if (!platform::fileExists(configPath)) {\n      auto toml = toml::table();\n      config::saveToTOML(toml, cpuSettings);\n      config::saveToTOML(toml, gpuSettings);\n      config::saveToTOML(toml, decafSettings);\n      config::saveFrontendToml(toml);\n      std::ofstream out { configPath };\n      out << toml;\n   }\n\n   try {\n      auto toml = toml::parse_file(configPath);\n      config::loadFromTOML(toml, cpuSettings);\n      config::loadFromTOML(toml, gpuSettings);\n      config::loadFromTOML(toml, decafSettings);\n      config::loadFrontendToml(toml);\n   } catch (const toml::parse_error &ex) {\n      configError = ex.what();\n   }\n\n   config::loadFromExcmd(options, cpuSettings);\n   config::loadFromExcmd(options, gpuSettings);\n   config::loadFromExcmd(options, decafSettings);\n\n   cpu::setConfig(cpuSettings);\n   decaf::setConfig(decafSettings);\n   gpu::setConfig(gpuSettings);\n\n   // Now handle frontend config options\n   if (options.has(\"vpad0\")) {\n      config::input::vpad0 = options.get<std::string>(\"vpad0\");\n   }\n\n   if (options.has(\"timeout-ms\")) {\n      config::test::timeout_ms = options.get<int>(\"timeout-ms\");\n   }\n\n   if (options.has(\"timeout-frames\")) {\n      config::test::timeout_frames = options.get<int>(\"timeout-frames\");\n   }\n\n   if (options.has(\"dump-drc-frames\")) {\n      config::test::dump_drc_frames = true;\n   }\n\n   if (options.has(\"dump-tv-frames\")) {\n      config::test::dump_tv_frames = true;\n   }\n\n   if (options.has(\"dump-frames-dir\")) {\n      config::test::dump_frames_dir = options.get<std::string>(\"dump-frames-dir\");\n   }\n\n   // Initialise libdecaf logger\n   auto logFile = getPathBasename(target);\n   decaf::initialiseLogging(logFile);\n\n   // Initialise frontend logger\n   if (!decafSettings.log.to_stdout) {\n      // Always do cli log to stdout\n      gCliLog = decaf::makeLogger(\"decaf-cli\",\n                                  { std::make_shared<spdlog::sinks::stdout_sink_mt>() });\n   } else {\n      gCliLog = decaf::makeLogger(\"decaf-cli\");\n   }\n\n   gCliLog->set_pattern(\"[%l] %v\");\n   gCliLog->info(\"Target {}\", target);\n\n   if (configError.empty()) {\n      gCliLog->info(\"Loaded config from {}\", configPath);\n   } else {\n      gCliLog->error(\"Failed to parse config {}: {}\", configPath, configError);\n   }\n\n   DecafSDL sdl;\n\n   if (!sdl.initCore()) {\n      gCliLog->error(\"Failed to initialise SDL\");\n      return -1;\n   }\n\n   if (!sdl.initGraphics()) {\n      gCliLog->error(\"Failed to initialise graphics backend.\");\n      return -1;\n   }\n\n   if (options.has(\"sound\") && !sdl.initSound()) {\n      gCliLog->error(\"Failed to initialise SDL sound\");\n      return -1;\n   }\n\n   if (!sdl.run(target)) {\n      gCliLog->error(\"Failed to start game\");\n      return -1;\n   }\n\n   return 0;\n}\n\n#ifdef _WIN32\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\nint WINAPI\nwWinMain(_In_ HINSTANCE hInstance,\n         _In_opt_ HINSTANCE hPrevInstance,\n         _In_ LPWSTR lpCmdLine,\n         _In_ int nShowCmd)\n{\n   auto parser = getCommandLineParser();\n   excmd::option_state options;\n\n   if (AttachConsole(ATTACH_PARENT_PROCESS)) {\n      FILE *dumbFuck;\n      freopen_s(&dumbFuck, \"CONOUT$\", \"w\", stdout);\n      freopen_s(&dumbFuck, \"CONOUT$\", \"w\", stderr);\n   }\n\n   try {\n      options = parser.parse(lpCmdLine);\n   } catch (excmd::exception ex) {\n      std::cout << \"Error parsing options: \" << ex.what() << std::endl;\n      std::exit(-1);\n   }\n\n   return start(parser, options);\n}\n#else\nint main(int argc, char **argv) {\n   auto parser = getCommandLineParser();\n   excmd::option_state options;\n\n   try {\n      options = parser.parse(argc, argv);\n   } catch (excmd::exception ex) {\n      std::cout << \"Error parsing options: \" << ex.what() << std::endl;\n      std::exit(-1);\n   }\n\n   return start(parser, options);\n}\n#endif\n"
  },
  {
    "path": "src/decaf_buildinfo.h.in",
    "content": "#pragma once\n\n#define GIT_REV      \"@GIT_REV@\"\n#define GIT_BRANCH   \"@GIT_BRANCH@\"\n#define GIT_DESC     \"@GIT_DESC@\"\n\n#define BUILD_NAME   \"@REPO_NAME@\"\n#define BUILD_DATE   \"@BUILD_DATE@\"\n#define BUILD_FULLNAME \"@BUILD_FULLNAME@\"\n#define BUILD_VERSION \"@BUILD_VERSION@\"\n"
  },
  {
    "path": "src/decaf_game.h",
    "content": "#pragma once\n#include <cstdint>\n#include <string>\n\nnamespace decaf\n{\n\nstruct GameInfo\n{\n   std::string executable;\n   uint64_t titleId;\n};\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libconfig/CMakeLists.txt",
    "content": "project(libconfig)\n\ninclude_directories(\".\")\ninclude_directories(\"src\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nadd_library(libconfig STATIC ${SOURCE_FILES} ${HEADER_FILES})\nGroupSources(\"Source Files\" src)\n\ntarget_link_libraries(libconfig\n    common\n    libcpu\n    libdecaf\n    libgpu\n    tomlplusplus\n    excmd)\n"
  },
  {
    "path": "src/libconfig/config_excmd.h",
    "content": "#pragma once\n#include <excmd.h>\n#include <libcpu/cpu_config.h>\n#include <libdecaf/decaf_config.h>\n#include <libgpu/gpu_config.h>\n#include <vector>\n\nnamespace config\n{\n\nstd::vector<excmd::option_group *>\ngetExcmdGroups(excmd::parser &parser);\n\nbool\nloadFromExcmd(excmd::option_state &options,\n              cpu::Settings &cpuSettings);\n\nbool\nloadFromExcmd(excmd::option_state &options,\n              decaf::Settings &decafSettings);\n\nbool\nloadFromExcmd(excmd::option_state &options,\n              gpu::Settings &gpuSettings);\n\n} // namespace config\n"
  },
  {
    "path": "src/libconfig/config_toml.h",
    "content": "#pragma once\n#include <common/type_traits.h>\n#include <libcpu/cpu_config.h>\n#include <libdecaf/decaf_config.h>\n#include <libgpu/gpu_config.h>\n#include <string>\n#include <toml++/toml.h>\n#include <memory>\n#include <type_traits>\n\nnamespace config\n{\n\nbool\nloadFromTOML(const toml::table &config,\n             cpu::Settings &cpuSettings);\n\nbool\nloadFromTOML(const toml::table &config,\n             decaf::Settings &decafSettings);\n\nbool\nloadFromTOML(const toml::table &config,\n             gpu::Settings &gpuSettings);\n\nbool\nsaveToTOML(toml::table &config,\n           const cpu::Settings &cpuSettings);\n\nbool\nsaveToTOML(toml::table &config,\n           const decaf::Settings &decafSettings);\n\nbool\nsaveToTOML(toml::table &config,\n           const gpu::Settings &gpuSettings);\n\n\ntemplate<typename Type>\nvoid\nreadValue(const toml::table &config,\n          const char *name,\n          Type &value)\n{\n   auto node = config.at_path(name);\n   if constexpr (std::is_same_v<Type, bool>) {\n      if (auto ptr = node.as_boolean()) {\n         value = ptr->get();\n      }\n   } else if constexpr (std::is_integral_v<typename safe_underlying_type<Type>::type>) {\n      if (auto ptr = node.as_integer()) {\n         value = static_cast<Type>(ptr->get());\n      }\n   } else if constexpr (std::is_floating_point_v<typename safe_underlying_type<Type>::type>) {\n      if (auto ptr = node.as_floating_point()) {\n         value = static_cast<Type>(ptr->get());\n      }\n   } else if constexpr (std::is_same_v<Type, std::string>) {\n      if (auto ptr = node.as_string()) {\n         value = static_cast<Type>(ptr->get());\n      }\n   } else {\n      static_assert(\n         std::is_same_v<Type, bool> ||\n         std::is_integral_v<typename safe_underlying_type<Type>::type> ||\n         std::is_floating_point_v<typename safe_underlying_type<Type>::type> ||\n         std::is_same_v<Type, std::string>,\n         \"Invalid type passed to readValue\");\n   }\n}\n\n} // namespace config\n"
  },
  {
    "path": "src/libconfig/src/loader_excmd.cpp",
    "content": "#include \"config_excmd.h\"\n\n#include <common/strutils.h>\n#include <excmd.h>\n#include <libcpu/cpu_config.h>\n#include <libdecaf/decaf_config.h>\n#include <libgpu/gpu_config.h>\n#include <string>\n\nnamespace config\n{\n\nstd::vector<excmd::option_group *>\ngetExcmdGroups(excmd::parser &parser)\n{\n   std::vector<excmd::option_group *> groups;\n   using excmd::description;\n   using excmd::optional;\n   using excmd::default_value;\n   using excmd::allowed;\n   using excmd::value;\n\n   auto gpu_options = parser.add_option_group(\"GPU Options\")\n      .add_option(\"gpu-debug\",\n                  description { \"Enable extra gpu debug info.\" });\n   groups.push_back(gpu_options.group);\n\n   auto display_options = parser.add_option_group(\"Display Options\")\n      .add_option(\"background-colour\",\n                  description { \"Background colour.\" },\n                  default_value<std::string> { \"153,51,51\" })\n      .add_option(\"display-backend\",\n                  description { \"Which display backend to use.\" },\n                  default_value<std::string> { \"vulkan\" },\n                  allowed<std::string> { {\n                     \"vulkan\", \"null\",\n                  } })\n      .add_option(\"screen-mode\",\n                  description { \"Screen display mode.\" },\n                  default_value<std::string> { \"windowed\" },\n                  allowed<std::string> { {\n                     \"windowed\", \"fullscreen\"\n                  } })\n      .add_option(\"split-separation\",\n                  default_value<double> { 5.0 },\n                  description { \"Gap between screens when view-mode is split.\" })\n      .add_option(\"stretch-screen\",\n                  description { \"Stretch the screen to fill window rather than maintaining aspect ratio.\" })\n      .add_option(\"view-mode\",\n                  description { \"View display mode.\" },\n                  default_value<std::string> { \"split\" },\n                  allowed<std::string> { {\n                     \"split\", \"tv\", \"gamepad1\", \"gamepad2\"\n                  } });\n   groups.push_back(display_options.group);\n\n   auto jit_options = parser.add_option_group(\"JIT Options\")\n      .add_option(\"jit\",\n                  description { \"Enable the JIT engine.\" })\n      .add_option(\"no-jit\",\n                  description { \"Disable the JIT engine.\" })\n      .add_option(\"jit-fast-math\",\n                  description { \"Enable JIT floating-point optimizations which may not exactly match PowerPC behavior.  May not work for all games.\" },\n                  default_value<std::string> { \"full\" },\n                  allowed<std::string> { {\n                     \"full\", \"no-fpscr\"\n                  } })\n      .add_option(\"jit-opt-level\",\n                  description { \"Set the JIT optimization level.  Higher levels give better performance but may cause longer translation delays.  Level 3 may not work for all games.\" },\n                  default_value<int> { 1 },\n                  allowed<int> { { 0, 1, 2, 3 } })\n      .add_option(\"jit-verify\",\n                  description { \"Verify JIT implementation against interpreter.\" })\n      .add_option(\"jit-verify-addr\",\n                  description { \"Select single code block for JIT verification.\" },\n                  default_value<uint32_t> { 0 });\n   groups.push_back(jit_options.group);\n\n   auto log_options = parser.add_option_group(\"Log Options\")\n      .add_option(\"log-async\",\n                  description { \"Enable asynchronous logging.\" })\n      .add_option(\"log-dir\",\n                  description{ \"Directory where log file will be written.\" },\n                  value<std::string> {})\n      .add_option(\"log-file\",\n                  description { \"Enable logging to file.\" })\n      .add_option(\"log-stdout\",\n                  description { \"Enable logging to stdout.\" })\n      .add_option(\"log-level\",\n                  description { \"Only display logs with severity equal to or greater than this level.\" },\n                  default_value<std::string> { \"debug\" },\n                  allowed<std::string> { {\n                     \"trace\", \"debug\", \"info\", \"notice\", \"warning\",\n                     \"error\", \"critical\", \"alert\", \"emerg\", \"off\"\n                  } });\n   groups.push_back(log_options.group);\n\n   auto sys_options = parser.add_option_group(\"System Options\")\n      .add_option(\"region\",\n                  description { \"Set the system region.\" },\n                  default_value<std::string> { \"US\" },\n                  allowed<std::string> { {\n                     \"EUR\", \"JAP\", \"US\"\n                  } })\n      .add_option(\"resources-path\",\n                  description { \"Path to Decaf resource files.\" },\n                  value<std::string> {})\n      .add_option(\"content-path\",\n                  description { \"Sets which path to mount to /vol/content, only used for standalone rpx files.\" },\n                  value<std::string> {})\n      .add_option(\"hfio-path\",\n                  description { \"Sets which path to mount to /dev/hfio01.\" },\n                  value<std::string> {})\n      .add_option(\"mlc-path\",\n                  description { \"Sets which path to mount to /dev/mlc01.\" },\n                  value<std::string> {})\n      .add_option(\"otp-path\",\n                  description { \"Path to otp.bin.\" },\n                  value<std::string> {})\n      .add_option(\"sdcard-path\",\n                  description { \"Sets which path to mount to /dev/sdcard01.\" },\n                  value<std::string> {})\n      .add_option(\"slc-path\",\n                  description { \"Sets which path to mount to /dev/slc01.\" },\n                  value<std::string> {})\n      .add_option(\"time-scale\",\n                  description { \"Time scale factor for emulated clock.\" },\n                  default_value<double> { 1.0 });\n   groups.push_back(sys_options.group);\n\n   return groups;\n}\n\nbool\nloadFromExcmd(excmd::option_state &options,\n              decaf::Settings &decafSettings)\n{\n\n   if (options.has(\"log-stdout\")) {\n      decafSettings.log.to_stdout = true;\n   }\n\n   if (options.has(\"log-file\")) {\n      decafSettings.log.to_file = true;\n   }\n\n   if (options.has(\"log-dir\")) {\n      decafSettings.log.directory = options.get<std::string>(\"log-dir\");\n   }\n\n   if (options.has(\"log-async\")) {\n      decafSettings.log.async = true;\n   }\n\n   if (options.has(\"log-level\")) {\n      decafSettings.log.level = options.get<std::string>(\"log-level\");\n   }\n\n   if (options.has(\"region\")) {\n      auto region = options.get<std::string>(\"region\");\n\n      if (iequals(region, \"japan\") == 0) {\n         decafSettings.system.region = decaf::SystemRegion::Japan;\n      } else if (iequals(region, \"usa\") == 0) {\n         decafSettings.system.region = decaf::SystemRegion::USA;\n      } else if (iequals(region, \"europe\") == 0) {\n         decafSettings.system.region = decaf::SystemRegion::Europe;\n      } else if (iequals(region, \"china\") == 0) {\n         decafSettings.system.region = decaf::SystemRegion::China;\n      } else if (iequals(region, \"korea\") == 0) {\n         decafSettings.system.region = decaf::SystemRegion::Korea;\n      } else if (iequals(region, \"taiwan\") == 0) {\n         decafSettings.system.region = decaf::SystemRegion::Taiwan;\n      }\n   }\n\n   if (options.has(\"resources-path\")) {\n      decafSettings.system.resources_path = options.get<std::string>(\"resources-path\");\n   }\n\n   if (options.has(\"content-path\")) {\n      decafSettings.system.content_path = options.get<std::string>(\"content-path\");\n   }\n\n   if (options.has(\"hfio-path\")) {\n      decafSettings.system.hfio_path = options.get<std::string>(\"hfio-path\");\n   }\n\n   if (options.has(\"mlc-path\")) {\n      decafSettings.system.mlc_path = options.get<std::string>(\"mlc-path\");\n   }\n\n   if (options.has(\"sdcard-path\")) {\n      decafSettings.system.sdcard_path = options.get<std::string>(\"sdcard-path\");\n   }\n\n   if (options.has(\"slc-path\")) {\n      decafSettings.system.slc_path = options.get<std::string>(\"slc-path\");\n   }\n\n   if (options.has(\"otp-path\")) {\n      decafSettings.system.otp_path = options.get<std::string>(\"otp-path\");\n   }\n\n   if (options.has(\"time-scale\")) {\n      decafSettings.system.time_scale = options.get<double>(\"time-scale\");\n   }\n\n   return true;\n}\n\nbool\nloadFromExcmd(excmd::option_state &options,\n              cpu::Settings &cpuSettings)\n{\n   if (options.has(\"jit\")) {\n      cpuSettings.jit.enabled = true;\n   } else if (options.has(\"no-jit\")) {\n      cpuSettings.jit.enabled = false;\n   }\n\n   if (options.has(\"jit-verify\")) {\n      cpuSettings.jit.verify = true;\n   }\n\n   if (options.has(\"jit-verify-addr\")) {\n      cpuSettings.jit.verifyAddress = options.get<uint32_t>(\"jit-verify-addr\");\n   }\n\n   if (options.has(\"jit-opt-level\")) {\n      auto level = options.get<int>(\"jit-opt-level\");\n\n      cpuSettings.jit.optimisationFlags.clear();\n\n      if (level >= 1) {\n         cpuSettings.jit.optimisationFlags.push_back(\"BASIC\");\n         cpuSettings.jit.optimisationFlags.push_back(\"DECONDITION\");\n         cpuSettings.jit.optimisationFlags.push_back(\"DSE\");\n         cpuSettings.jit.optimisationFlags.push_back(\"FOLD_CONSTANTS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_FORWARD_LOADS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_PAIRED_LWARX_STWCX\");\n         cpuSettings.jit.optimisationFlags.push_back(\"X86_BRANCH_ALIGNMENT\");\n         cpuSettings.jit.optimisationFlags.push_back(\"X86_CONDITION_CODES\");\n         cpuSettings.jit.optimisationFlags.push_back(\"X86_FIXED_REGS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"X86_FORWARD_CONDITIONS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"X86_STORE_IMMEDIATE\");\n      }\n\n      if (level >= 2) {\n         cpuSettings.jit.optimisationFlags.push_back(\"CHAIN\");\n         // Skip DEEP_DATA_FLOW if verifying because its whole purpose is\n         //  to eliminate dead stores to registers.\n         if (!cpuSettings.jit.verify) {\n            cpuSettings.jit.optimisationFlags.push_back(\"DEEP_DATA_FLOW\");\n         }\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_TRIM_CR_STORES\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_USE_SPLIT_FIELDS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"X86_ADDRESS_OPERANDS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"X86_MERGE_REGS\");\n      }\n\n      if (level >= 3) {\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_CONSTANT_GQRS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_DETECT_FCFI_EMUL\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_FAST_FCTIW\");\n      }\n   }\n\n   if (options.has(\"jit-fast-math\")) {\n      // PPC_NO_FPSCR_STATE by itself (--jit-fast-math=no-fpscr) is safe to\n      //  use with --jit-verify as long as the game doesn't actually look\n      //  at FPSCR status bits.  Other optimization flags may result in\n      //  verification differences even if the generated code is correct.\n      cpuSettings.jit.optimisationFlags.push_back(\"PPC_NO_FPSCR_STATE\");\n      if (options.get<std::string>(\"jit-fast-math\").compare(\"full\") == 0) {\n         cpuSettings.jit.optimisationFlags.push_back(\"DSE_FP\");\n         cpuSettings.jit.optimisationFlags.push_back(\"FOLD_FP_CONSTANTS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"NATIVE_IEEE_NAN\");\n         cpuSettings.jit.optimisationFlags.push_back(\"NATIVE_IEEE_UNDERFLOW\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_ASSUME_NO_SNAN\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_FAST_FMADDS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_FAST_FMULS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_FAST_STFS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_FNMADD_ZERO_SIGN\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_IGNORE_FPSCR_VXFOO\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_NATIVE_RECIPROCAL\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_PS_STORE_DENORMALS\");\n         cpuSettings.jit.optimisationFlags.push_back(\"PPC_SINGLE_PREC_INPUTS\");\n      }\n   }\n\n   return true;\n}\n\nbool\nloadFromExcmd(excmd::option_state &options,\n              gpu::Settings &gpuSettings)\n{\n   if (options.has(\"gpu-debug\")) {\n      gpuSettings.debug.debug_enabled = true;\n   }\n\n   if (options.has(\"background-colour\")) {\n      auto colour = std::vector<std::string> { };\n      split_string(options.get<std::string>(\"background-colour\"), ',', colour);\n      if (colour.size() == 3) {\n         gpuSettings.display.backgroundColour[0] = std::atoi(colour[0].c_str());\n         gpuSettings.display.backgroundColour[1] = std::atoi(colour[1].c_str());\n         gpuSettings.display.backgroundColour[2] = std::atoi(colour[2].c_str());\n      }\n   }\n\n   if (options.has(\"display-backend\")) {\n      auto mode = options.get<std::string>(\"screen-mode\");\n      if (mode.compare(\"vulkan\") == 0) {\n         gpuSettings.display.backend = gpu::DisplaySettings::Vulkan;\n      } else if (mode.compare(\"null\") == 0) {\n         gpuSettings.display.backend = gpu::DisplaySettings::Null;\n      }\n   }\n\n   if (options.has(\"screen-mode\")) {\n      auto mode = options.get<std::string>(\"screen-mode\");\n      if (mode.compare(\"windowed\") == 0) {\n         gpuSettings.display.screenMode = gpu::DisplaySettings::Windowed;\n      } else if (mode.compare(\"fullscreen\") == 0) {\n         gpuSettings.display.screenMode = gpu::DisplaySettings::Fullscreen;\n      }\n   }\n\n   if (options.has(\"stretch-screen\")) {\n      gpuSettings.display.maintainAspectRatio = false;\n   }\n\n   if (options.has(\"split-separation\")) {\n      gpuSettings.display.splitSeperation = options.get<double>(\"split-separation\");\n   }\n\n   if (options.has(\"view-mode\")) {\n      auto layout = options.get<std::string>(\"view-mode\");\n      if (layout.compare(\"split\") == 0) {\n         gpuSettings.display.viewMode = gpu::DisplaySettings::Split;\n      } else if (layout.compare(\"tv\") == 0)  {\n         gpuSettings.display.viewMode = gpu::DisplaySettings::TV;\n      } else if (layout.compare(\"gamepad1\") == 0) {\n         gpuSettings.display.viewMode = gpu::DisplaySettings::Gamepad1;\n      } else if (layout.compare(\"gamepad2\") == 0) {\n         gpuSettings.display.viewMode = gpu::DisplaySettings::Gamepad2;\n      }\n   }\n\n   return true;\n}\n\n} // namespace config\n"
  },
  {
    "path": "src/libconfig/src/loader_toml.cpp",
    "content": "#include \"config_toml.h\"\n\n#include <string>\n#include <optional>\n\nnamespace config\n{\n\ntemplate<typename Type>\ninline void\nreadArray(const toml::table &config, const char *name, std::vector<Type> &value)\n{\n   auto ptr = config.at_path(name).as_array();\n   if (ptr) {\n      value.clear();\n      for (auto itr = ptr->begin(); itr != ptr->end(); ++itr) {\n         value.push_back(itr->template as<Type>()->get());\n      }\n   }\n}\n\nstatic const char *\ntranslateDisplayBackend(gpu::DisplaySettings::Backend backend)\n{\n   if (backend == gpu::DisplaySettings::Null) {\n      return \"null\";\n   } else if (backend == gpu::DisplaySettings::Vulkan) {\n      return \"vulkan\";\n   }\n\n   return \"\";\n}\n\nstatic std::optional<gpu::DisplaySettings::Backend>\ntranslateDisplayBackend(const std::string &text)\n{\n   if (text == \"null\") {\n      return gpu::DisplaySettings::Null;\n   } else if (text == \"vulkan\") {\n      return gpu::DisplaySettings::Vulkan;\n   }\n\n   return { };\n}\n\nstatic const char *\ntranslateScreenMode(gpu::DisplaySettings::ScreenMode mode)\n{\n   if (mode == gpu::DisplaySettings::Windowed) {\n      return \"windowed\";\n   } else if (mode == gpu::DisplaySettings::Fullscreen) {\n      return \"fullscreen\";\n   }\n\n   return \"\";\n}\n\nstatic std::optional<gpu::DisplaySettings::ScreenMode>\ntranslateScreenMode(const std::string &text)\n{\n   if (text == \"windowed\") {\n      return gpu::DisplaySettings::Windowed;\n   } else if (text == \"fullscreen\") {\n      return gpu::DisplaySettings::Fullscreen;\n   }\n\n   return { };\n}\n\nstatic const char *\ntranslateViewMode(gpu::DisplaySettings::ViewMode mode)\n{\n   if (mode == gpu::DisplaySettings::TV) {\n      return \"tv\";\n   } else if (mode == gpu::DisplaySettings::Gamepad1) {\n      return \"gamepad1\";\n   } else if (mode == gpu::DisplaySettings::Gamepad2) {\n      return \"gamepad2\";\n   } else if (mode == gpu::DisplaySettings::Split) {\n      return \"split\";\n   }\n\n   return \"\";\n}\n\nstatic std::optional<gpu::DisplaySettings::ViewMode>\ntranslateViewMode(const std::string &text)\n{\n   if (text == \"tv\") {\n      return gpu::DisplaySettings::TV;\n   } else if (text == \"gamepad1\") {\n      return gpu::DisplaySettings::Gamepad1;\n   } else if (text == \"gamepad2\") {\n      return gpu::DisplaySettings::Gamepad2;\n   } else if (text == \"split\") {\n      return gpu::DisplaySettings::Split;\n   }\n\n   return { };\n}\n\nbool\nloadFromTOML(const toml::table &config,\n             cpu::Settings &cpuSettings)\n{\n   readValue(config, \"mem.writetrack\", cpuSettings.memory.writeTrackEnabled);\n\n   readValue(config, \"jit.enabled\", cpuSettings.jit.enabled);\n   readValue(config, \"jit.verify\", cpuSettings.jit.verify);\n   readValue(config, \"jit.verify_addr\", cpuSettings.jit.verifyAddress);\n   readValue(config, \"jit.code_cache_size_mb\", cpuSettings.jit.codeCacheSizeMB);\n   readValue(config, \"jit.data_cache_size_mb\", cpuSettings.jit.dataCacheSizeMB);\n   readArray(config, \"jit.opt_flags\", cpuSettings.jit.optimisationFlags);\n   readValue(config, \"jit.rodata_read_only\", cpuSettings.jit.rodataReadOnly);\n   return true;\n}\n\nbool\nloadFromTOML(const toml::table &config,\n             decaf::Settings &decafSettings)\n{\n   readValue(config, \"debugger.enabled\", decafSettings.debugger.enabled);\n   readValue(config, \"debugger.break_on_entry\", decafSettings.debugger.break_on_entry);\n   readValue(config, \"debugger.break_on_exit\", decafSettings.debugger.break_on_exit);\n   readValue(config, \"debugger.gdb_stub\", decafSettings.debugger.gdb_stub);\n   readValue(config, \"debugger.gdb_stub_port\", decafSettings.debugger.gdb_stub_port);\n\n   readValue(config, \"gx2.dump_textures\", decafSettings.gx2.dump_textures);\n   readValue(config, \"gx2.dump_shaders\", decafSettings.gx2.dump_shaders);\n\n   readValue(config, \"log.async\", decafSettings.log.async);\n   readValue(config, \"log.branch_trace\", decafSettings.log.branch_trace);\n   readValue(config, \"log.directory\", decafSettings.log.directory);\n   readValue(config, \"log.hle_trace\", decafSettings.log.hle_trace);\n   readValue(config, \"log.hle_trace_res\", decafSettings.log.hle_trace_res);\n   readArray(config, \"log.hle_trace_filters\", decafSettings.log.hle_trace_filters);\n   readValue(config, \"log.level\", decafSettings.log.level);\n   readValue(config, \"log.to_file\", decafSettings.log.to_file);\n   readValue(config, \"log.to_stdout\", decafSettings.log.to_stdout);\n\n   auto logLevels = config.at_path(\"log.levels\").as_table();\n   if (logLevels) {\n      decafSettings.log.levels.clear();\n      for (auto item : *logLevels) {\n         if (auto level = item.second.as_string()) {\n            decafSettings.log.levels.emplace_back(item.first, **level);\n         }\n      }\n   }\n\n   readValue(config, \"sound.dump_sounds\", decafSettings.sound.dump_sounds);\n\n   readValue(config, \"system.region\", decafSettings.system.region);\n   readValue(config, \"system.hfio_path\", decafSettings.system.hfio_path);\n   readValue(config, \"system.mlc_path\", decafSettings.system.mlc_path);\n   readValue(config, \"system.otp_path\", decafSettings.system.otp_path);\n   readValue(config, \"system.resources_path\", decafSettings.system.resources_path);\n   readValue(config, \"system.sdcard_path\", decafSettings.system.sdcard_path);\n   readValue(config, \"system.slc_path\", decafSettings.system.slc_path);\n   readValue(config, \"system.content_path\", decafSettings.system.content_path);\n   readValue(config, \"system.time_scale\", decafSettings.system.time_scale);\n   readArray(config, \"system.lle_modules\", decafSettings.system.lle_modules);\n   readValue(config, \"system.dump_hle_rpl\", decafSettings.system.dump_hle_rpl);\n   readArray(config, \"system.title_directories\", decafSettings.system.title_directories);\n   return true;\n}\n\nbool\nloadFromTOML(const toml::table &config,\n             gpu::Settings &gpuSettings)\n{\n   readValue(config, \"gpu.debug\", gpuSettings.debug.debug_enabled);\n   readValue(config, \"gpu.dump_shaders\", gpuSettings.debug.dump_shaders);\n   readValue(config, \"gpu.dump_shader_binaries_only\", gpuSettings.debug.dump_shader_binaries_only);\n\n   auto display = config.get_as<toml::table>(\"display\");\n   if (display) {\n      if (auto text = display->get_as<std::string>(\"backend\"); text) {\n         if (auto backend = translateDisplayBackend(**text); backend) {\n            gpuSettings.display.backend = *backend;\n         }\n      }\n\n      if (auto text = display->get_as<std::string>(\"screen_mode\"); text) {\n         if (auto screenMode = translateScreenMode(**text); screenMode) {\n            gpuSettings.display.screenMode = *screenMode;\n         }\n      }\n\n      if (auto text = display->get_as<std::string>(\"view_mode\"); text) {\n         if (auto viewMode = translateViewMode(**text); viewMode) {\n            gpuSettings.display.viewMode = *viewMode;\n         }\n      }\n\n      if (auto maintainAspectRatio = display->get_as<bool>(\"maintain_aspect_ratio\"); maintainAspectRatio) {\n         gpuSettings.display.maintainAspectRatio = **maintainAspectRatio;\n      }\n\n      if (auto splitSeperation = display->get_as<double>(\"split_seperation\"); splitSeperation) {\n         gpuSettings.display.splitSeperation = **splitSeperation;\n      }\n\n      if (auto backgroundColour = display->get_as<toml::array>(\"background_colour\");\n          backgroundColour && backgroundColour->size() >= 3) {\n         gpuSettings.display.backgroundColour = {\n               static_cast<int>(**backgroundColour->at(0).as_integer()),\n               static_cast<int>(**backgroundColour->at(1).as_integer()),\n               static_cast<int>(**backgroundColour->at(2).as_integer())\n            };\n      }\n   }\n\n   return true;\n}\n\nbool\nsaveToTOML(toml::table &config,\n           const cpu::Settings &cpuSettings)\n{\n   auto jit = config.insert(\"jit\", toml::table()).first->second.as_table();\n   jit->insert_or_assign(\"enabled\", cpuSettings.jit.enabled);\n   jit->insert_or_assign(\"verify\", cpuSettings.jit.verify);\n   jit->insert_or_assign(\"verify_addr\", cpuSettings.jit.verifyAddress);\n   jit->insert_or_assign(\"code_cache_size_mb\", cpuSettings.jit.codeCacheSizeMB);\n   jit->insert_or_assign(\"data_cache_size_mb\", cpuSettings.jit.dataCacheSizeMB);\n   jit->insert_or_assign(\"rodata_read_only\", cpuSettings.jit.rodataReadOnly);\n\n   auto opt_flags = toml::array();\n   for (auto &flag : cpuSettings.jit.optimisationFlags) {\n      opt_flags.push_back(flag);\n   }\n\n   jit->insert_or_assign(\"opt_flags\", opt_flags);\n   return true;\n}\n\nbool\nsaveToTOML(toml::table &config,\n           const decaf::Settings &decafSettings)\n{\n   // debugger\n   auto debugger = config.insert(\"debugger\", toml::table()).first->second.as_table();\n   debugger->insert_or_assign(\"enabled\", decafSettings.debugger.enabled);\n   debugger->insert_or_assign(\"break_on_entry\", decafSettings.debugger.break_on_entry);\n   debugger->insert_or_assign(\"break_on_exit\", decafSettings.debugger.break_on_exit);\n   debugger->insert_or_assign(\"gdb_stub\", decafSettings.debugger.gdb_stub);\n   debugger->insert_or_assign(\"gdb_stub_port\", decafSettings.debugger.gdb_stub_port);\n\n   // gx2\n   auto gx2 = config.insert(\"gx2\", toml::table()).first->second.as_table();\n   gx2->insert_or_assign(\"dump_textures\", decafSettings.gx2.dump_textures);\n   gx2->insert_or_assign(\"dump_shaders\", decafSettings.gx2.dump_shaders);\n\n   // log\n   auto log = config.insert(\"log\", toml::table()).first->second.as_table();\n   log->insert_or_assign(\"async\", decafSettings.log.async);\n   log->insert_or_assign(\"branch_trace\", decafSettings.log.branch_trace);\n   log->insert_or_assign(\"directory\", decafSettings.log.directory);\n   log->insert_or_assign(\"hle_trace\", decafSettings.log.hle_trace);\n   log->insert_or_assign(\"hle_trace_res\", decafSettings.log.hle_trace_res);\n   log->insert_or_assign(\"level\", decafSettings.log.level);\n   log->insert_or_assign(\"to_file\", decafSettings.log.to_file);\n   log->insert_or_assign(\"to_stdout\", decafSettings.log.to_stdout);\n\n   auto levels = toml::table();\n   for (auto item : decafSettings.log.levels) {\n      levels.insert_or_assign(item.first, item.second);\n   }\n   log->insert_or_assign(\"levels\", std::move(levels));\n\n   auto hle_trace_filters = toml::array();\n   for (auto &filter : decafSettings.log.hle_trace_filters) {\n      hle_trace_filters.push_back(filter);\n   }\n\n   log->insert_or_assign(\"hle_trace_filters\", std::move(hle_trace_filters));\n\n   // sound\n   auto sound = config.insert(\"sound\", toml::table()).first->second.as_table();\n   sound->insert_or_assign(\"dump_sounds\", decafSettings.sound.dump_sounds);\n\n   // system\n   auto system = config.insert(\"system\", toml::table()).first->second.as_table();\n   system->insert_or_assign(\"region\", static_cast<int>(decafSettings.system.region));\n   system->insert_or_assign(\"hfio_path\", decafSettings.system.hfio_path);\n   system->insert_or_assign(\"mlc_path\", decafSettings.system.mlc_path);\n   system->insert_or_assign(\"otp_path\", decafSettings.system.otp_path);\n   system->insert_or_assign(\"resources_path\", decafSettings.system.resources_path);\n   system->insert_or_assign(\"sdcard_path\", decafSettings.system.sdcard_path);\n   system->insert_or_assign(\"slc_path\", decafSettings.system.slc_path);\n   system->insert_or_assign(\"content_path\", decafSettings.system.content_path);\n   system->insert_or_assign(\"time_scale\", decafSettings.system.time_scale);\n\n   auto lle_modules = toml::array();\n   for (auto &name : decafSettings.system.lle_modules) {\n      lle_modules.push_back(name);\n   }\n   system->insert_or_assign(\"lle_modules\", std::move(lle_modules));\n\n   auto title_directories = toml::array();\n   for (auto &name : decafSettings.system.title_directories) {\n      title_directories.push_back(name);\n   }\n   system->insert_or_assign(\"title_directories\", std::move(title_directories));\n   return true;\n}\n\nbool\nsaveToTOML(toml::table &config,\n           const gpu::Settings &gpuSettings)\n{\n   // gpu\n   auto gpu = config.insert(\"gpu\", toml::table()).first->second.as_table();\n   gpu->insert_or_assign(\"debug\", gpuSettings.debug.debug_enabled);\n   gpu->insert_or_assign(\"dump_shaders\", gpuSettings.debug.dump_shaders);\n   gpu->insert_or_assign(\"dump_shader_binaries_only\", gpuSettings.debug.dump_shader_binaries_only);\n\n   // display\n   auto display = config.insert(\"display\", toml::table()).first->second.as_table();\n   display->insert_or_assign(\"backend\", translateDisplayBackend(gpuSettings.display.backend));\n   display->insert_or_assign(\"screen_mode\", translateScreenMode(gpuSettings.display.screenMode));\n   display->insert_or_assign(\"view_mode\", translateViewMode(gpuSettings.display.viewMode));\n   display->insert_or_assign(\"maintain_aspect_ratio\", gpuSettings.display.maintainAspectRatio);\n   display->insert_or_assign(\"split_seperation\", gpuSettings.display.splitSeperation);\n\n   auto backgroundColour = toml::array();\n   backgroundColour.push_back(gpuSettings.display.backgroundColour[0]);\n   backgroundColour.push_back(gpuSettings.display.backgroundColour[1]);\n   backgroundColour.push_back(gpuSettings.display.backgroundColour[2]);\n   display->insert_or_assign(\"background_colour\", std::move(backgroundColour));\n   return true;\n}\n\n} // namespace config\n"
  },
  {
    "path": "src/libcpu/CMakeLists.txt",
    "content": "project(libcpu)\n\ninclude_directories(\".\")\ninclude_directories(\"src\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nif(MSVC)\n    add_library(libcpu STATIC ${SOURCE_FILES} ${HEADER_FILES} \"cpu.natvis\")\nelse()\n    add_library(libcpu STATIC ${SOURCE_FILES} ${HEADER_FILES})\nendif()\n\nGroupSources(\"Header Files/expresso\" espresso)\nGroupSources(\"Source Files\" src)\n\ntarget_link_libraries(libcpu\n    common\n    binrec)\n\nif(MSVC)\n    target_link_libraries(libcpu\n        ntdll)\nelse()\n    target_link_libraries(libcpu\n        m\n        ${CMAKE_THREAD_LIBS_INIT})\nendif()\n\nif(DECAF_PCH)\n    target_precompile_headers(libcpu\n      PRIVATE\n        <common/pch.h>\n    )\n\n    AutoGroupPCHFiles()\nendif()\n"
  },
  {
    "path": "src/libcpu/address.h",
    "content": "#pragma once\n#include <cstdint>\n#include <common/align.h>\n#include <type_traits>\n\nnamespace cpu\n{\n\ntemplate<class Type>\nclass Address\n{\npublic:\n   using StorageType = uint32_t;\n\n   constexpr Address() = default;\n\n   constexpr explicit Address(StorageType address) :\n      mAddress(address)\n   {\n   }\n\n   explicit operator bool() const\n   {\n      return !!mAddress;\n   }\n\n   template<typename OtherType,\n            typename = typename std::enable_if<std::is_integral<OtherType>::value>::type>\n   explicit operator OtherType() const\n   {\n      return static_cast<OtherType>(mAddress);\n   }\n\n   constexpr bool operator == (const Address &other) const\n   {\n      return mAddress == other.mAddress;\n   }\n\n   constexpr bool operator != (const Address &other) const\n   {\n      return mAddress != other.mAddress;\n   }\n\n   constexpr bool operator >= (const Address &other) const\n   {\n      return mAddress >= other.mAddress;\n   }\n\n   constexpr bool operator <= (const Address &other) const\n   {\n      return mAddress <= other.mAddress;\n   }\n\n   constexpr bool operator > (const Address &other) const\n   {\n      return mAddress > other.mAddress;\n   }\n\n   constexpr bool operator < (const Address &other) const\n   {\n      return mAddress < other.mAddress;\n   }\n\n   constexpr Address &operator += (ptrdiff_t value)\n   {\n      mAddress = static_cast<StorageType>(mAddress + value);\n      return *this;\n   }\n\n   constexpr Address &operator -= (ptrdiff_t value)\n   {\n      mAddress = static_cast<StorageType>(mAddress - value);\n      return *this;\n   }\n\n   constexpr Address &operator &= (StorageType value)\n   {\n      mAddress = mAddress & value;\n      return *this;\n   }\n\n   constexpr Address &operator ^= (StorageType value)\n   {\n      mAddress = mAddress ^ value;\n      return *this;\n   }\n\n   constexpr Address operator + (ptrdiff_t value) const\n   {\n      return Address { static_cast<StorageType>(mAddress + value) };\n   }\n\n   constexpr Address operator - (ptrdiff_t value) const\n   {\n      return Address { static_cast<StorageType>(mAddress - value) };\n   }\n\n   constexpr ptrdiff_t operator - (const Address &other) const\n   {\n      return mAddress - other.mAddress;\n   }\n\n   constexpr Address operator & (StorageType value) const\n   {\n      return Address { static_cast<StorageType>(mAddress & value) };\n   }\n\n   constexpr Address operator ^ (StorageType value) const\n   {\n      return Address { static_cast<StorageType>(mAddress ^ value) };\n   }\n\n   constexpr StorageType operator % (StorageType value) const\n   {\n      return mAddress % value;\n   }\n\n   constexpr StorageType operator / (StorageType value) const\n   {\n      return mAddress / value;\n   }\n\n   constexpr StorageType operator << (StorageType value) const\n   {\n      return mAddress << value;\n   }\n\n   constexpr StorageType operator >> (StorageType value) const\n   {\n      return mAddress >> value;\n   }\n\n   constexpr StorageType getAddress() const\n   {\n      return mAddress;\n   }\n\nprivate:\n   StorageType mAddress = 0;\n};\n\ntemplate<typename Type>\nstruct AddressRange\n{\n   using address_type = Address<Type>;\n\n   AddressRange(address_type start,\n                uint32_t size) :\n      start(start),\n      size(size)\n   {\n   }\n\n   AddressRange(address_type start,\n                address_type end) :\n      start(start),\n      size(static_cast<uint32_t>(end - start + 1))\n   {\n   }\n\n   constexpr bool\n   contains(AddressRange &other) const\n   {\n      return (start <= other.start && start + size >= other.start + other.size);\n   }\n\n   address_type start;\n   uint32_t size;\n};\n\nclass Physical;\nclass Virtual;\n\nusing PhysicalAddress = Address<Physical>;\nusing VirtualAddress = Address<Virtual>;\n\nusing PhysicalAddressRange = AddressRange<Physical>;\nusing VirtualAddressRange = AddressRange<Virtual>;\n\n} // namespace cpu\n\ntemplate<typename Type>\nconstexpr inline cpu::Address<Type>\nalign_up(cpu::Address<Type> value, size_t alignment)\n{\n   return cpu::Address<Type> { align_up(value.getAddress(), alignment) };\n}\n\ntemplate<typename Type>\nconstexpr inline cpu::Address<Type>\nalign_down(cpu::Address<Type> value, size_t alignment)\n{\n   return cpu::Address<Type> { align_down(value.getAddress(), alignment) };\n}\n\n// Custom formatters for fmtlib\nnamespace fmt\n{\n\ninline namespace v8\n{\ntemplate<typename Type, typename Char, typename Enabled>\nstruct formatter;\n}\n\ntemplate<typename AddressType, typename Char>\nstruct formatter<cpu::Address<AddressType>, Char, void>;\n\n} // namespace fmt\n"
  },
  {
    "path": "src/libcpu/be2_array.h",
    "content": "#pragma once\n#include <algorithm>\n#include <cstdint>\n#include <initializer_list>\n#include <stdexcept>\n#include <type_traits>\n#include <string_view>\n\n#include \"be2_val.h\"\n#include \"pointer.h\"\n#include \"common/fixed.h\"\n\ntemplate<typename Type>\nstruct be2_struct;\n\n// Equivalent to std:true_type if type T is a virt_ptr or phys_ptr or virt_func_ptr.\ntemplate<typename>\nstruct is_cpu_ptr : std::false_type { };\n\ntemplate<typename T, typename A>\nstruct is_cpu_ptr<cpu::Pointer<T, A>> : std::true_type { };\n\ntemplate<typename T, typename A>\nstruct is_cpu_ptr<cpu::FunctionPointer<T, A>> : std::true_type { };\n\n// Equivalent to std:true_type if type T is a cnl::fixed_point\ntemplate<typename>\nstruct is_fixed_point : std::false_type { };\n\ntemplate<typename Rep, int Exponent, int Radix>\nstruct is_fixed_point<cnl::fixed_point<Rep, Exponent, Radix>> : std::true_type { };\n\n// Detects the actual type to be used for be2_array members.\ntemplate <typename T, typename = void>\nstruct be2_array_item_type;\n\ntemplate <typename T>\nstruct be2_array_item_type<T, typename std::enable_if<std::is_arithmetic<T>::value\n                                                   || std::is_enum<T>::value\n                                                   || is_cpu_ptr<std::remove_cv_t<T>>::value\n                                                   || is_fixed_point<std::remove_cv_t<T>>::value>::type>\n{\n   using type = be2_val<T>;\n};\n\ntemplate <typename T>\nstruct be2_array_item_type<T, typename std::enable_if<!is_cpu_ptr<std::remove_cv_t<T>>::value\n                                                   && !is_fixed_point<std::remove_cv_t<T>>::value\n                                                   && std::is_class<T>::value\n                                                   && !std::is_union<T>::value>::type>\n{\n   using type = be2_struct<T>;\n};\n\ntemplate <typename T>\nstruct be2_array_item_type<T, typename std::enable_if<std::is_union<T>::value>::type>\n{\n   using type = be2_val<T>;\n};\n\n\ntemplate<typename Type, uint32_t Size>\nclass be2_array_iterator;\n\ntemplate<typename Type, uint32_t Size>\nclass be2_array_const_iterator;\n\n// BigEndianValue but for arrays.\ntemplate<typename Type, uint32_t Size>\nclass be2_array\n{\npublic:\n   using raw_value_type = Type;\n   using be2_value_type = typename be2_array_item_type<Type>::type;\n   using size_type = uint32_t;\n   using index_type = size_t;\n   using difference_type = std::ptrdiff_t;\n   using iterator = be2_array_iterator<Type, Size>;\n   using const_iterator = be2_array_const_iterator<Type, Size>;\n\n   be2_array() = default;\n\n   template<size_t N>\n   be2_array(const char (&src)[N],\n             typename std::enable_if<Size >= N && std::is_same<char, Type>::value>::type * = 0)\n   {\n      std::copy_n(src, N, mValues);\n   }\n\n   template<typename U = Type>\n   be2_array(std::string_view src,\n             typename std::enable_if<std::is_same<char, U>::value>::type * = 0)\n   {\n      auto count = (src.size() < Size) ? src.size() : Size - 1;\n      std::copy_n(src.begin(), count, mValues);\n      mValues[src.size()] = char { 0 };\n   }\n\n   be2_array(std::initializer_list<Type> list)\n   {\n      if (list.size() > Size) {\n         throw std::out_of_range(\"invalid be2_array<T, N> initializer_list\");\n      }\n\n      auto i = 0u;\n      for (auto &item : list) {\n         mValues[i] = item;\n      }\n   }\n\n   be2_array(const std::array<Type, Size> &other)\n   {\n      for (auto i = 0u; i < Size; ++i) {\n         mValues[i] = other[i];\n      }\n   }\n\n   template<size_t N,\n            typename = typename std::enable_if<Size >= N>::type>\n   be2_array &operator =(const Type (&src)[N])\n   {\n      std::copy_n(src, N, mValues);\n      return *this;\n   }\n\n   template<typename U = Type,\n            typename = typename std::enable_if<std::is_same<char, U>::value>::type>\n   be2_array &operator =(const std::string_view &src)\n   {\n      auto count = (src.size() < Size) ? src.size() : Size - 1;\n      std::copy_n(src.begin(), count, mValues);\n      mValues[count] = char { 0 };\n      return *this;\n   }\n\n   constexpr auto &at(index_type pos)\n   {\n      if (pos >= Size) {\n         throw std::out_of_range(\"invalid be2_array<T, N> subscript\");\n      }\n\n      return mValues[pos];\n   }\n\n   constexpr const auto &at(index_type pos) const\n   {\n      if (pos >= Size) {\n         throw std::out_of_range(\"invalid be2_array<T, N> subscript\");\n      }\n\n      return mValues[pos];\n   }\n\n   constexpr auto &operator[](index_type pos)\n   {\n      return mValues[pos];\n   }\n\n   constexpr const auto &operator[](index_type pos) const\n   {\n      return mValues[pos];\n   }\n\n   constexpr auto &front()\n   {\n      return mValues[0];\n   }\n\n   constexpr const auto &front() const\n   {\n      return mValues[0];\n   }\n\n   constexpr auto &back()\n   {\n      return mValues[Size - 1];\n   }\n\n   constexpr const auto &back() const\n   {\n      return mValues[Size - 1];\n   }\n\n   constexpr bool empty() const\n   {\n      return Size > 0;\n   }\n\n   constexpr size_type size() const\n   {\n      return Size;\n   }\n\n   constexpr size_type max_size() const\n   {\n      return Size;\n   }\n\n   constexpr void fill(const raw_value_type &value)\n   {\n      for (auto i = 0u; i < Size; ++i) {\n         mValues[i] = value;\n      }\n   }\n\n   constexpr auto begin()\n   {\n      return iterator { *this, 0 };\n   }\n\n   constexpr auto end()\n   {\n      return iterator { *this, Size };\n   }\n\n   constexpr auto cbegin()\n   {\n      return const_iterator { *this, 0 };\n   }\n\n   constexpr auto cend()\n   {\n      return const_iterator { *this, Size };\n   }\n\n   std::array<Type, Size> value() const\n   {\n      std::array<Type, Size> result;\n\n      for (auto i = 0u; i < Size; ++i) {\n         result[i] = mValues[i];\n      }\n\n      return result;\n   }\n\n   operator std::array<Type, Size>() const\n   {\n      return value();\n   }\n\n   // Please use virt_addrof or phys_addrof instead\n   auto operator &() = delete;\n\nprotected:\n   be2_value_type mValues[Size];\n};\n\ntemplate<typename Type, uint32_t Size>\nclass be2_array_iterator\n{\n   using array_type = be2_array<Type, Size>;\n   using size_type = typename array_type::size_type;\n\npublic:\n   be2_array_iterator(be2_array<Type, Size> &arr, size_type index) :\n      mIndex(index),\n      mArray(arr)\n   {\n   }\n\n   constexpr auto &operator*()\n   {\n      return mArray[mIndex];\n   }\n\n   be2_array_iterator &operator++()\n   {\n      mIndex++;\n      return *this;\n   }\n\n   bool operator !=(be2_array_iterator &other) const\n   {\n      return mIndex != other.mIndex;\n   }\n\nprivate:\n   size_type mIndex;\n   array_type &mArray;\n};\n\ntemplate<typename Type, uint32_t Size>\nclass be2_const_array_iterator\n{\n   using array_type = const be2_array<Type, Size>;\n   using size_type = typename array_type::size_type;\n\npublic:\n   be2_const_array_iterator(array_type &arr, size_type index) :\n      mArray(arr),\n      mIndex(index)\n   {\n   }\n\n   constexpr const auto &operator*()\n   {\n      return mArray[mIndex];\n   }\n\n   be2_const_array_iterator &operator++()\n   {\n      mIndex++;\n      return *this;\n   }\n\n   bool operator !=(be2_const_array_iterator &other) const\n   {\n      return mIndex != other.mIndex;\n   }\n\nprivate:\n   size_type mIndex;\n   array_type &mArray;\n};\n"
  },
  {
    "path": "src/libcpu/be2_atomic.h",
    "content": "#pragma once\n#include <atomic>\n#include <common/byte_swap.h>\n#include <common/type_traits.h>\n\ntemplate<typename Type>\nclass be2_atomic\n{\npublic:\n   static_assert(std::atomic<Type>::is_always_lock_free);\n   static_assert(sizeof(std::atomic<Type>) == sizeof(Type));\n   static_assert(sizeof(Type) == 1 || sizeof(Type) == 2 || sizeof(Type) == 4 || sizeof(Type) == 8);\n\n   using value_type = Type;\n\n   be2_atomic() = default;\n\n   value_type\n   load(std::memory_order order = std::memory_order_seq_cst) const\n   {\n      return byte_swap(mStorage.load(order));\n   }\n\n   void\n   store(value_type value,\n         std::memory_order order = std::memory_order_seq_cst)\n   {\n      mStorage.store(byte_swap(value), order);\n   }\n\n   value_type\n   exchange(value_type value,\n            std::memory_order order = std::memory_order_seq_cst)\n   {\n      return byte_swap(mStorage.exchange(byte_swap(value), order));\n   }\n\n   bool\n   compare_exchange_weak(value_type &expected,\n                         value_type desired,\n                         std::memory_order order = std::memory_order_seq_cst)\n   {\n      auto expectedValue = byte_swap(expected);\n      auto result = mStorage.compare_exchange_weak(expectedValue,\n                                                   byte_swap(desired),\n                                                   order);\n      expected = byte_swap(expectedValue);\n      return result;\n   }\n\n   bool\n   compare_exchange_weak(value_type &expected,\n                         value_type desired,\n                         std::memory_order success,\n                         std::memory_order failure)\n   {\n      auto expectedValue = byte_swap(expected);\n      auto result = mStorage.compare_exchange_weak(expectedValue,\n                                                   byte_swap(desired),\n                                                   success,\n                                                   failure);\n      expected = byte_swap(expectedValue);\n      return result;\n   }\n\n   bool\n   compare_exchange_strong(value_type &expected,\n                           value_type desired,\n                           std::memory_order order = std::memory_order_seq_cst)\n   {\n      auto expectedValue = byte_swap(expected);\n      auto result = mStorage.compare_exchange_strong(expectedValue,\n                                                     byte_swap(desired),\n                                                     order);\n      expected = byte_swap(expectedValue);\n      return result;\n   }\n\n   bool\n   compare_exchange_strong(value_type &expected,\n                           value_type desired,\n                           std::memory_order success,\n                           std::memory_order failure)\n   {\n      auto expectedValue = byte_swap(expected);\n      auto result = mStorage.compare_exchange_strong(expectedValue,\n                                                     byte_swap(desired),\n                                                     success,\n                                                     failure);\n      expected = byte_swap(expectedValue);\n      return result;\n   }\n\n   value_type\n   fetch_add(value_type addValue)\n   {\n      auto oldValue = load(std::memory_order_relaxed);\n      auto newValue = oldValue + addValue;\n\n      while (!compare_exchange_weak(oldValue, newValue,\n                                    std::memory_order_release,\n                                    std::memory_order_relaxed)) {\n         newValue = oldValue + addValue;\n      }\n\n      return oldValue;\n   }\n\n   value_type\n   fetch_sub(value_type subValue)\n   {\n      auto oldValue = load(std::memory_order_relaxed);\n      auto newValue = oldValue - subValue;\n\n      while (!compare_exchange_weak(oldValue, newValue,\n                                    std::memory_order_release,\n                                    std::memory_order_relaxed)) {\n         newValue = oldValue - subValue;\n      }\n\n      return oldValue;\n   }\n\n   value_type\n   fetch_and(value_type subValue)\n   {\n      auto oldValue = load(std::memory_order_relaxed);\n      auto newValue = oldValue & subValue;\n\n      while (!compare_exchange_weak(oldValue, newValue,\n                                    std::memory_order_release,\n                                    std::memory_order_relaxed)) {\n         newValue = oldValue & subValue;\n      }\n\n      return oldValue;\n   }\n\n   value_type\n   fetch_or(value_type subValue)\n   {\n      auto oldValue = load(std::memory_order_relaxed);\n      auto newValue = oldValue | subValue;\n\n      while (!compare_exchange_weak(oldValue, newValue,\n                                    std::memory_order_release,\n                                    std::memory_order_relaxed)) {\n         newValue = oldValue | subValue;\n      }\n\n      return oldValue;\n   }\n\n   value_type\n   fetch_xor(value_type subValue)\n   {\n      auto oldValue = load(std::memory_order_relaxed);\n      auto newValue = oldValue ^ subValue;\n\n      while (!compare_exchange_weak(oldValue, newValue,\n                                    std::memory_order_release,\n                                    std::memory_order_relaxed)) {\n         newValue = oldValue ^ subValue;\n      }\n\n      return oldValue;\n   }\n\nprivate:\n   std::atomic<value_type> mStorage;\n};\n"
  },
  {
    "path": "src/libcpu/be2_struct.h",
    "content": "#pragma once\n#include \"address.h\"\n#include \"be2_array.h\"\n#include \"be2_val.h\"\n#include \"functionpointer.h\"\n#include \"pointer.h\"\n#include \"mmu.h\"\n\n#include <cstdint>\n#include <cstdlib>\n#include <common/cbool.h>\n#include <common/structsize.h>\n\nusing virt_addr = cpu::VirtualAddress;\nusing phys_addr = cpu::PhysicalAddress;\n\nusing virt_addr_range = cpu::VirtualAddressRange;\nusing phys_addr_range = cpu::PhysicalAddressRange;\n\ntemplate<typename T>\nusing virt_ptr = cpu::Pointer<T, virt_addr>;\n\ntemplate<typename T>\nusing phys_ptr = cpu::Pointer<T, phys_addr>;\n\ntemplate<typename Ft>\nusing virt_func_ptr = cpu::FunctionPointer<virt_addr, Ft>;\n\ntemplate<typename Ft>\nusing phys_func_ptr = cpu::FunctionPointer<phys_addr, Ft>;\n\ntemplate<typename Type>\nusing be2_ptr = be2_val<virt_ptr<Type>>;\n\ntemplate<typename Type>\nusing be2_virt_ptr = be2_ptr<Type>;\n\ntemplate<typename Type>\nusing be2_phys_ptr = be2_val<phys_ptr<Type>>;\n\ntemplate<typename Ft>\nusing be2_virt_func_ptr = be2_val<virt_func_ptr<Ft>>;\n\ntemplate<typename Ft>\nusing be2_phys_func_ptr = be2_val<phys_func_ptr<Ft>>;\n\n/*\n* be2_struct is a wrapper intended to be used around struct value type members\n* in structs which reside in PPC memory. This is so we can use members with\n* virt_addrof or phys_addrof.\n*/\ntemplate<typename Type>\nstruct be2_struct : public Type\n{\n   using value_type = std::remove_cv_t<Type>;\n   using value_type::value_type;\n   using value_type::operator =;\n\n   // Please use virt_addrof or phys_addrof instead\n   auto operator &() = delete;\n};\n\n// reinterpret_cast for virt_addr to virt_ptr<X>\ntemplate<typename DstType,\n         typename = typename std::enable_if<std::is_pointer<DstType>::value>::type>\ninline auto virt_cast(virt_addr src)\n{\n   return cpu::pointer_cast_impl<cpu::VirtualAddress, virt_addr, DstType>::cast(src);\n}\n\n// reinterpret_cast for virt_ptr<X> to virt_ptr<Y> or virt_addr\ntemplate<typename DstType, typename SrcType,\n         typename = typename std::enable_if<std::is_pointer<DstType>::value ||\n                                            std::is_same<DstType, virt_addr>::value>::type>\ninline auto virt_cast(const virt_ptr<SrcType> &src)\n{\n   return cpu::pointer_cast_impl<cpu::VirtualAddress, SrcType *, DstType>::cast(src);\n}\n\n// reinterpret_cast for be2_ptr<X> to virt_ptr<Y> or virt_addr\ntemplate<typename DstType, typename SrcType,\n         typename = typename std::enable_if<std::is_pointer<DstType>::value ||\n                                            std::is_same<DstType, virt_addr>::value>::type>\ninline auto virt_cast(const be2_virt_ptr<SrcType> &src)\n{\n   return cpu::pointer_cast_impl<cpu::VirtualAddress, SrcType *, DstType>::cast(src);\n}\n\n// reinterpret_cast for phys_addr to phys_ptr<X>\ntemplate<typename DstType,\n         typename = typename std::enable_if<std::is_pointer<DstType>::value>::type>\ninline auto phys_cast(phys_addr src)\n{\n   return cpu::pointer_cast_impl<cpu::PhysicalAddress, phys_addr, DstType>::cast(src);\n}\n\n// reinterpret_cast for phys_ptr<X> to phys_ptr<Y> or phys_addr\ntemplate<typename DstType, typename SrcType,\n         typename = typename std::enable_if<std::is_pointer<DstType>::value ||\n                                            std::is_same<DstType, phys_addr>::value>::type>\ninline auto phys_cast(const phys_ptr<SrcType> &src)\n{\n   return cpu::pointer_cast_impl<cpu::PhysicalAddress, SrcType *, DstType>::cast(src);\n}\n\n// reinterpret_cast for be2_ptr<X> to phys_ptr<Y> or phys_addr\ntemplate<typename DstType, typename SrcType,\n         typename = typename std::enable_if<std::is_pointer<DstType>::value ||\n                                            std::is_same<DstType, phys_addr>::value>::type>\ninline auto phys_cast(const be2_phys_ptr<SrcType> &src)\n{\n   return cpu::pointer_cast_impl<cpu::PhysicalAddress, SrcType *, DstType>::cast(src);\n}\n\n// reinterpret_cast for virt_addr to virt_func_ptr<X>\ntemplate<typename FunctionType>\ninline auto virt_func_cast(virt_addr src)\n{\n   return cpu::func_pointer_cast_impl<cpu::VirtualAddress, FunctionType>::cast(src);\n}\n\n// reinterpret_cast for virt_func_ptr<X> to virt_addr\ntemplate<typename DstType, typename FunctionType,\n         typename = typename std::enable_if<std::is_same<DstType, virt_addr>::value>::type>\ninline auto virt_func_cast(virt_func_ptr<FunctionType> src)\n{\n   return cpu::func_pointer_cast_impl<cpu::VirtualAddress, FunctionType>::cast(src);\n}\n\n// reinterpret_cast for be2_virt_func_ptr<X> to virt_addr\ntemplate<typename DstType, typename FunctionType,\n         typename = typename std::enable_if<std::is_same<DstType, virt_addr>::value>::type>\ninline auto virt_func_cast(be2_virt_func_ptr<FunctionType> src)\n{\n   return cpu::func_pointer_cast_impl<cpu::VirtualAddress, FunctionType>::cast(src);\n}\n\n// reinterpret_cast for phys_addr to phys_func_ptr<X>\ntemplate<typename FunctionType>\ninline auto phys_func_cast(phys_addr src)\n{\n   return cpu::func_pointer_cast_impl<cpu::PhysicalAddress, FunctionType>::cast(src);\n}\n\n// reinterpret_cast for phys_func_ptr<X> to phys_addr\ntemplate<typename DstType, typename FunctionType,\n         typename = typename std::enable_if<std::is_same<DstType, phys_addr>::value>::type>\ninline auto phys_func_cast(phys_func_ptr<FunctionType> src)\n{\n   return cpu::func_pointer_cast_impl<cpu::PhysicalAddress, FunctionType>::cast(src);\n}\n\n// reinterpret_cast for be2_phys_func_ptr<X> to phys_addr\ntemplate<typename DstType, typename FunctionType,\n         typename = typename std::enable_if<std::is_same<DstType, phys_addr>::value>::type>\ninline auto phys_func_cast(be2_phys_func_ptr<FunctionType> src)\n{\n   return cpu::func_pointer_cast_impl<cpu::PhysicalAddress, FunctionType>::cast(src);\n}\n\n/**\n * Returns a virt_ptr to a big endian value type.\n *\n * The address of the object should be in virtual memory,\n * as in virtualMemoryBase < &ref < virtualMemoryEnd.\n */\ntemplate<typename Type>\ninline virt_ptr<Type> virt_addrof(const be2_struct<Type> &ref)\n{\n   return virt_cast<Type *>(cpu::translate(std::addressof(ref)));\n}\n\ntemplate<typename Type>\ninline virt_ptr<Type> virt_addrof(const be2_val<Type> &ref)\n{\n   return virt_cast<Type *>(cpu::translate(std::addressof(ref)));\n}\n\ntemplate<typename Type, uint32_t Size>\nvirt_ptr<Type> virt_addrof(const be2_array<Type, Size> &ref)\n{\n   return virt_cast<Type *>(cpu::translate(std::addressof(ref)));\n}\n\n\n/**\n * Returns a phys_ptr to a big endian value type.\n *\n * The address of the object should be in physical memory,\n * as in physicalMemoryBase < &ref < physicalMemoryEnd.\n *\n * Will not auto translate a virtual address to a physical address, for that\n * you should use cpu::virtualToPhysicalAddress(virt_addrof(x)).\n */\ntemplate<typename Type>\ninline phys_ptr<Type> phys_addrof(const be2_struct<Type> &ref)\n{\n   return phys_cast<Type *>(cpu::translatePhysical(std::addressof(ref)));\n}\n\ntemplate<typename Type>\ninline phys_ptr<Type> phys_addrof(const be2_val<Type> &ref)\n{\n   return phys_cast<Type *>(cpu::translatePhysical(std::addressof(ref)));\n}\n\ntemplate<typename Type, uint32_t Size>\ninline phys_ptr<Type> phys_addrof(const be2_array<Type, Size> &ref)\n{\n   return phys_cast<Type *>(cpu::translatePhysical(std::addressof(ref)));\n}\n\n\n/**\n * Used to translate the this pointer to a virt_ptr.\n */\ntemplate<typename Type>\ninline virt_ptr<Type> virt_this(Type *self)\n{\n   return virt_cast<Type *>(cpu::translate(self));\n}\n\n\n/**\n * Used to translate the this pointer to a virt_ptr.\n */\ntemplate<typename Type>\ninline phys_ptr<Type> phys_this(Type *self)\n{\n   return phys_cast<Type *>(cpu::translatePhysical(self));\n}\n\n\n// Equivalent to std:true_type if type T is a virt_ptr.\ntemplate<typename>\nstruct is_virt_ptr : std::false_type { };\n\ntemplate<typename T>\nstruct is_virt_ptr<virt_ptr<T>> : std::true_type { };\n\n// Equivalent to std:true_type if type T is a virt_func_ptr.\ntemplate<typename>\nstruct is_virt_func_ptr : std::false_type { };\n\ntemplate<typename T>\nstruct is_virt_func_ptr<virt_func_ptr<T>> : std::true_type { };\n\n// Equivalent to std:true_type if type T is a phys_ptr.\ntemplate<typename>\nstruct is_phys_ptr : std::false_type { };\n\ntemplate<typename T>\nstruct is_phys_ptr<phys_ptr<T>> : std::true_type { };\n\ntemplate<typename ValueType, typename AddressType>\nconstexpr inline cpu::Pointer<ValueType, AddressType>\nalign_up(cpu::Pointer<ValueType, AddressType> value,\n         size_t alignment)\n{\n   auto address = cpu::pointer_cast_impl<AddressType, ValueType *, AddressType>::cast(value);\n   address = static_cast<AddressType>(align_up(address.getAddress(), alignment));\n   return cpu::pointer_cast_impl<AddressType, AddressType, ValueType *>::cast(address);\n}\n\ntemplate<typename ValueType, typename AddressType>\nconstexpr inline cpu::Pointer<ValueType, AddressType>\nalign_down(cpu::Pointer<ValueType, AddressType> value,\n           size_t alignment)\n{\n   auto address = cpu::pointer_cast_impl<AddressType, ValueType *, AddressType>::cast(value);\n   address = static_cast<AddressType>(align_down(address.getAddress(), alignment));\n   return cpu::pointer_cast_impl<AddressType, AddressType, ValueType *>::cast(address);\n}\n\ntemplate<typename Type>\nconstexpr inline Type\nalign_up(be2_val<Type> value,\n         size_t alignment)\n{\n   return align_up(value.value(), alignment);\n}\n\ntemplate<typename Type>\nconstexpr inline Type\nalign_down(be2_val<Type> value,\n           size_t alignment)\n{\n   return align_down(value.value(), alignment);\n}\n"
  },
  {
    "path": "src/libcpu/be2_val.h",
    "content": "#pragma once\n#include <common/byte_swap.h>\n#include <common/type_traits.h>\n\ntemplate<typename Type>\nclass be2_val\n{\npublic:\n   static_assert(!std::is_array<Type>::value,\n                 \"be2_val invalid type: array\");\n\n   static_assert(!std::is_pointer<Type>::value,\n                 \"be2_val invalid type: pointer\");\n\n   static_assert(sizeof(Type) == 1 || sizeof(Type) == 2 || sizeof(Type) == 4 || sizeof(Type) == 8,\n                 \"be2_val invalid type size\");\n\n   using value_type = Type;\n\n   be2_val() = default;\n\n   template<typename OtherType,\n            typename = typename std::enable_if<std::is_constructible<value_type, const OtherType &>::value ||\n                                               std::is_convertible<const OtherType &, value_type>::value>::type>\n   be2_val(const OtherType &other)\n   {\n      if constexpr (std::is_constructible<value_type, const OtherType &>::value) {\n         setValue(value_type { other });\n      } else {\n         setValue(static_cast<value_type>(other));\n      }\n   }\n\n   template<typename OtherType,\n            typename = typename std::enable_if<std::is_constructible<value_type, const OtherType &>::value ||\n                                               std::is_convertible<const OtherType &, value_type>::value>::type>\n   be2_val(OtherType &&other)\n   {\n      if constexpr (std::is_constructible<value_type, const OtherType &>::value) {\n         setValue(value_type { std::forward<OtherType>(other) });\n      } else {\n         setValue(static_cast<value_type>(std::forward<OtherType>(other)));\n      }\n   }\n\n   template<typename OtherType,\n            typename = typename std::enable_if<std::is_convertible<const OtherType &, value_type>::value ||\n                                               std::is_constructible<value_type, const OtherType &>::value>::type>\n   be2_val(const be2_val<OtherType> &other)\n   {\n      if constexpr (std::is_constructible<value_type, const OtherType &>::value) {\n         setValue(value_type { other.value() });\n      } else {\n         setValue(static_cast<value_type>(other.value()));\n      }\n   }\n\n   template<typename OtherType,\n            typename = typename std::enable_if<std::is_convertible<const OtherType &, value_type>::value ||\n                                               std::is_constructible<value_type, const OtherType &>::value>::type>\n   be2_val(be2_val<OtherType> &&other)\n   {\n      if constexpr (std::is_constructible<value_type, const OtherType &>::value) {\n         setValue(value_type { other.value() });\n      } else {\n         setValue(static_cast<value_type>(other.value()));\n      }\n   }\n\n   value_type value() const\n   {\n      return byte_swap(mStorage);\n   }\n\n   void setValue(value_type value)\n   {\n      mStorage = byte_swap(value);\n   }\n\n   operator value_type() const\n   {\n      return value();\n   }\n\n   template<typename T = Type,\n            typename = typename std::enable_if<std::is_convertible<T, bool>::value ||\n                                               std::is_constructible<bool, T>::value\n                                              >::type>\n   explicit operator bool() const\n   {\n      return static_cast<bool>(value());\n   }\n\n   template<typename OtherType,\n            typename = typename std::enable_if<std::is_convertible<Type, OtherType>::value ||\n                                               std::is_constructible<OtherType, Type>::value ||\n                                               std::is_convertible<Type, typename safe_underlying_type<OtherType>::type>::value\n                                              >::type>\n   explicit operator OtherType() const\n   {\n      return static_cast<OtherType>(value());\n   }\n\n   template<typename OtherType,\n            typename = typename std::enable_if<std::is_constructible<value_type, const OtherType &>::value ||\n                                               std::is_convertible<const OtherType &, value_type>::value>::type>\n   be2_val & operator =(const OtherType &other)\n   {\n      if constexpr (std::is_constructible<value_type, const OtherType &>::value) {\n         setValue(value_type { other });\n      } else {\n         setValue(static_cast<value_type>(other));\n      }\n\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = typename std::enable_if<std::is_constructible<value_type, const OtherType &>::value ||\n                                               std::is_convertible<const OtherType &, value_type>::value>::type>\n   be2_val & operator =(OtherType &&other)\n   {\n      if constexpr (std::is_constructible<value_type, const OtherType &>::value) {\n         setValue(value_type { std::forward<OtherType>(other) });\n      } else {\n         setValue(static_cast<value_type>(std::forward<OtherType>(other)));\n      }\n\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = typename std::enable_if<std::is_convertible<const OtherType &, value_type>::value ||\n                                               std::is_constructible<value_type, const OtherType &>::value>::type>\n   be2_val & operator =(const be2_val<OtherType> &other)\n   {\n      if constexpr (std::is_constructible<value_type, const OtherType &>::value) {\n         setValue(value_type { other.value() });\n      } else {\n         setValue(static_cast<value_type>(other.value()));\n      }\n\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = typename std::enable_if<std::is_convertible<const OtherType &, value_type>::value ||\n                                               std::is_constructible<value_type, const OtherType &>::value>::type>\n   be2_val & operator =(be2_val<OtherType> &&other)\n   {\n      if constexpr (std::is_constructible<value_type, const OtherType &>::value) {\n         setValue(value_type { other.value() });\n      } else {\n         setValue(static_cast<value_type>(other.value()));\n      }\n\n      return *this;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator ==(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator ==(std::declval<const OtherType>()))\n   {\n      return value() == other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator !=(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator !=(std::declval<const OtherType>()))\n   {\n      return value() != other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator >=(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator >=(std::declval<const OtherType>()))\n   {\n      return value() >= other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator <=(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator <=(std::declval<const OtherType>()))\n   {\n      return value() <= other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator >(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator >(std::declval<const OtherType>()))\n   {\n      return value() > other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator <(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator <(std::declval<const OtherType>()))\n   {\n      return value() < other;\n   }\n\n   template<typename K = value_type>\n   auto operator +() const\n      ->  decltype(std::declval<const K>(). operator+())\n   {\n      return +value();\n   }\n\n   template<typename K = value_type>\n   auto operator -() const\n      -> decltype(std::declval<const K>(). operator-())\n   {\n      return -value();\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator +(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator +(std::declval<const OtherType>()))\n   {\n      return value() + other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator -(const OtherType &other)const\n      -> decltype(std::declval<const K>().operator -(std::declval<const OtherType>()))\n   {\n      return value() - other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator *(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator *(std::declval<const OtherType>()))\n   {\n      return value() * other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator /(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator /(std::declval<const OtherType>()))\n   {\n      return value() / other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator %(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator %(std::declval<const OtherType>()))\n   {\n      return value() % other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator |(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator |(std::declval<const OtherType>()))\n   {\n      return value() | other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator &(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator &(std::declval<const OtherType>()))\n   {\n      return value() & other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator ^(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator ^(std::declval<const OtherType>()))\n   {\n      return value() ^ other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator <<(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator <<(std::declval<const OtherType>()))\n   {\n      return value() << other;\n   }\n\n   template<typename OtherType, typename K = value_type>\n   auto operator >>(const OtherType &other) const\n      -> decltype(std::declval<const K>().operator >>(std::declval<const OtherType>()))\n   {\n      return value() >> other;\n   }\n\n   template<typename OtherType,\n            typename = decltype(std::declval<const value_type>() + std::declval<const OtherType>())>\n   be2_val &operator +=(const OtherType &other)\n   {\n      *this = static_cast<value_type>(value() + other);\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = decltype(std::declval<const value_type>() - std::declval<const OtherType>())>\n   be2_val &operator -=(const OtherType &other)\n   {\n      *this = static_cast<value_type>(value() - other);\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = decltype(std::declval<const value_type>() * std::declval<const OtherType>())>\n   be2_val &operator *=(const OtherType &other)\n   {\n      *this = static_cast<value_type>(value() * other);\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = decltype(std::declval<const value_type>() / std::declval<const OtherType>())>\n   be2_val &operator /=(const OtherType &other)\n   {\n      *this = static_cast<value_type>(value() / other);\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = decltype(std::declval<const value_type>() % std::declval<const OtherType>())>\n   be2_val &operator %=(const OtherType &other)\n   {\n      *this = static_cast<value_type>(value() % other);\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = decltype(std::declval<const value_type>() | std::declval<const OtherType>())>\n   be2_val &operator |=(const OtherType &other)\n   {\n      *this = static_cast<value_type>(value() | other);\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = decltype(std::declval<const value_type>() & std::declval<const OtherType>())>\n   be2_val &operator &=(const OtherType &other)\n   {\n      *this = static_cast<value_type>(value() & other);\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = decltype(std::declval<const value_type>() ^ std::declval<const OtherType>())>\n   be2_val &operator ^=(const OtherType &other)\n   {\n      *this = static_cast<value_type>(value() ^ other);\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = decltype(std::declval<const value_type>() << std::declval<const OtherType>())>\n   be2_val &operator <<=(const OtherType &other)\n   {\n      *this = value() << other;\n      return *this;\n   }\n\n   template<typename OtherType,\n            typename = decltype(std::declval<const value_type>() >> std::declval<const OtherType>())>\n   be2_val &operator >>=(const OtherType &other)\n   {\n      *this = value() >> other;\n      return *this;\n   }\n\n   template<typename T = Type,\n            typename = decltype(std::declval<const T>() + 1)>\n   be2_val &operator ++()\n   {\n      setValue(value() + 1);\n      return *this;\n   }\n\n   template<typename T = Type,\n            typename = decltype(std::declval<const T>() + 1)>\n   be2_val operator ++(int)\n   {\n      auto before = *this;\n      setValue(value() + 1);\n      return before;\n   }\n\n   template<typename T = Type,\n            typename = decltype(std::declval<const T>() - 1)>\n   be2_val &operator --()\n   {\n      setValue(value() - 1);\n      return *this;\n   }\n\n   template<typename T = Type,\n            typename = decltype(std::declval<const T>() - 1)>\n   be2_val operator --(int)\n   {\n      auto before = *this;\n      setValue(value() - 1);\n      return before;\n   }\n\n   template<typename IndexType,\n            typename K = value_type>\n   auto operator [](const IndexType &index)\n      -> decltype(std::declval<K>().operator [](std::declval<IndexType>()))\n   {\n      return value().operator [](index);\n   }\n\n   template<typename IndexType,\n            typename K = value_type>\n   auto operator [](const IndexType &index) const\n      -> decltype(std::declval<const K>().operator [](std::declval<IndexType>()))\n   {\n      return value().operator [](index);\n   }\n\n   template<typename K = value_type>\n   auto operator ->()\n      -> decltype(std::declval<K>().operator ->())\n   {\n      return value().operator ->();\n   }\n\n   template<typename K = value_type>\n   auto operator ->() const\n      -> decltype(std::declval<const K>().operator ->())\n   {\n      return value().operator ->();\n   }\n\n   template<typename K = value_type>\n   auto operator *()\n      -> decltype(std::declval<K>().operator *())\n   {\n      return value().operator *();\n   }\n\n   template<typename K = value_type>\n   auto operator *() const\n      -> decltype(std::declval<const K>().operator *())\n   {\n      return value().operator ->();\n   }\n\n   // Helper to access FunctionPointer::getAddress\n   template<typename K = value_type>\n   auto getAddress() const\n      -> decltype(std::declval<const K>().getAddress())\n   {\n      return value().getAddress();\n   }\n\n   // Helper to access Pointer::get\n   template<typename K = value_type>\n   auto get() const\n      -> decltype(std::declval<const K>().get())\n   {\n      return value().get();\n   }\n\n   // Helper to access Pointer::getRawPointer\n   template<typename K = value_type>\n   auto getRawPointer() const\n      -> decltype(std::declval<const K>().getRawPointer())\n   {\n      return value().getRawPointer();\n   }\n\n   // Please use virt_addrof or phys_addrof instead\n   auto operator &() = delete;\n\nprivate:\n   value_type mStorage;\n};\n\n// Custom formatters for fmtlib\nnamespace fmt\n{\n\ninline namespace v8\n{\ntemplate<typename Type, typename Char, typename Enabled>\nstruct formatter;\n\n// Disable stream operator detection for be2_val\nnamespace internal\n{\ntemplate<typename T, typename Char>\nclass is_streamable;\n\ntemplate<typename T, typename Char>\nclass is_streamable<be2_val<T>, Char> : public std::false_type\n{\n};\n}\n}\n\n// Custom formatter defined in cpu_formatters.h\ntemplate<typename ValueType, typename Char>\nstruct formatter<be2_val<ValueType>, Char, void>;\n\n} // namespace fmt\n"
  },
  {
    "path": "src/libcpu/cpu.h",
    "content": "#pragma once\n#include \"mem.h\"\n#include \"state.h\"\n#include \"cpu_control.h\"\n#include \"be2_struct.h\"\n\n#include <atomic>\n#include <common/platform_stacktrace.h>\n#include <cstdint>\n#include <functional>\n#include <utility>\n#include <gsl/gsl-lite.hpp>\n\nstruct Tracer;\n\nnamespace cpu\n{\n\nenum class jit_mode {\n   disabled,\n   enabled,\n   verify\n};\n\nstatic const uint32_t CALLBACK_ADDR = 0xFBADCDE0;\n\nusing EntrypointHandler = std::function<void(Core *core)>;\nusing InterruptHandler = void (*)(Core *core, uint32_t interrupt_flags);\nusing SegfaultHandler = void(*)(Core *core, uint32_t address, platform::StackTrace *hostStackTrace);\nusing BranchTraceHandler = void(*)(Core *core, uint32_t target);\nusing SystemCallHandler = Core * (*)(Core *core, uint32_t id);\n\nvoid\ninitialise();\n\nvoid\nsetCoreEntrypointHandler(EntrypointHandler handler);\n\nvoid\nsetInterruptHandler(InterruptHandler handler);\n\nvoid\nsetSegfaultHandler(SegfaultHandler handler);\n\nvoid\nsetBranchTraceHandler(BranchTraceHandler handler);\n\nvoid\nsetUnknownSystemCallHandler(SystemCallHandler handler);\n\nuint32_t\nregisterSystemCallHandler(SystemCallHandler handler);\n\nuint32_t\nregisterIllegalSystemCall();\n\nvoid\nstart();\n\nvoid\njoin();\n\nvoid\nhalt();\n\nusing Tracer = ::Tracer;\n\nTracer *\nallocTracer(size_t size);\n\nvoid\nfreeTracer(Tracer *tracer);\n\nnamespace this_core\n{\n\nvoid\nsetTracer(Tracer *tracer);\n\n\n} // namespace this_core\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/cpu.natvis",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<AutoVisualizer xmlns=\"http://schemas.microsoft.com/vstudio/debugger/natvis/2010\">\n  <Type Name=\"be2_val&lt;*&gt;\">\n    <DisplayString Condition=\"sizeof($T1) == 1\">\n      {mStorage}\n    </DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 2 &amp;&amp; *(unsigned short *)(&amp;mStorage) == 0xCCCC\">(not set) 0xCCCC</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 2 &amp;&amp; *(unsigned short *)(&amp;mStorage) == 0xCDCD\">(not set) 0xCDCD</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 2 &amp;&amp; *(unsigned short *)(&amp;mStorage) == 0xDDDD\">(in freed object) 0xDDDD</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 2 &amp;&amp; *(unsigned short *)(&amp;mStorage) == 0xFEEE\">(in freed object) 0xEEFE</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 2 &amp;&amp; *(unsigned short *)(&amp;mStorage) == 0xFDFD\">(heap buffer overrun) 0xFDFD</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 2 &amp;&amp; *(unsigned short *)(&amp;mStorage) == 0xABAB\">(heap buffer overrun) 0xABAB</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 2\">\n      {($T1)((((*(uint16_t*)&amp;mStorage)&amp;0xFF)&lt;&lt;8)|(((*(uint16_t*)&amp;mStorage)&amp;0xFF00)&gt;&gt;8))} ({($T1)((((*(uint16_t*)&amp;mStorage)&amp;0xFF)&lt;&lt;8)|(((*(uint16_t*)&amp;mStorage)&amp;0xFF00)&gt;&gt;8)),4x})\n    </DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 4 &amp;&amp; *(unsigned *)(&amp;mStorage) == 0xCCCCCCCC\">(not set) {($T1)0xCCCCCCCC}</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 4 &amp;&amp; *(unsigned *)(&amp;mStorage) == 0xCDCDCDCD\">(not set) {($T1)0xCDCDCDCD}</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 4 &amp;&amp; *(unsigned *)(&amp;mStorage) == 0xDDDDDDDD\">(in freed object) {($T1)0xDDDDDDDD}</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 4 &amp;&amp; *(unsigned *)(&amp;mStorage) == 0xFEEEFEEE\">(in freed object) {($T1)0xEEFEEEFE}</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 4 &amp;&amp; *(unsigned *)(&amp;mStorage) == 0xFDFDFDFD\">(heap buffer overrun) {($T1)0xFDFDFDFD}</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 4 &amp;&amp; *(unsigned *)(&amp;mStorage) == 0xABABABAB\">(heap buffer overrun) {($T1)0xABABABAB}</DisplayString>\n    <DisplayString Condition=\"sizeof($T1) == 4\">\n      {($T1)((((*(uint32_t*)&amp;mStorage)&amp;0xFF)&lt;&lt;24)|(((*(uint32_t*)&amp;mStorage)&amp;0xFF00)&lt;&lt;8)|(((*(uint32_t*)&amp;mStorage)&amp;0xFF0000)&gt;&gt;8)|(((*(uint32_t*)&amp;mStorage)&amp;0xFF000000)&gt;&gt;24))} ({((((*(uint32_t*)&amp;mStorage)&amp;0xFF)&lt;&lt;24)|(((*(uint32_t*)&amp;mStorage)&amp;0xFF00)&lt;&lt;8)|(((*(uint32_t*)&amp;mStorage)&amp;0xFF0000)&gt;&gt;8)|(((*(uint32_t*)&amp;mStorage)&amp;0xFF000000)&gt;&gt;24)),8x})\n    </DisplayString>\n    <Expand>\n      <ExpandedItem Condition=\"sizeof($T1) == 1\">\n        mStorage\n      </ExpandedItem>\n      <ExpandedItem Condition=\"sizeof($T1) == 2\">\n        ($T1)((((*(uint16_t*)&amp;mStorage)&amp;0xFF)&lt;&lt;8)|(((*(uint16_t*)&amp;mStorage)&amp;0xFF00)&gt;&gt;8))\n      </ExpandedItem>\n      <ExpandedItem Condition=\"sizeof($T1) == 4\">\n        ($T1)((((*(uint32_t*)&amp;mStorage)&amp;0xFF)&lt;&lt;24)|(((*(uint32_t*)&amp;mStorage)&amp;0xFF00)&lt;&lt;8)|(((*(uint32_t*)&amp;mStorage)&amp;0xFF0000)&gt;&gt;8)|(((*(uint32_t*)&amp;mStorage)&amp;0xFF000000)&gt;&gt;24))\n      </ExpandedItem>\n    </Expand>\n  </Type>\n  <Type Name=\"cpu::Pointer&lt;*, cpu::Address&lt;cpu::Virtual&gt;&gt;\">\n    <DisplayString Condition=\"mAddress.mAddress ==0\">NULL</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xCCCCCCCC\">(not set)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xCDCDCDCD\">(not set)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xDDDDDDDD\">(in freed object)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xFEEEFEEE\">(in freed object)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xFDFDFDFD\">(heap buffer overrun)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xABABABAB\">(heap buffer overrun)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress !=0\">\n      {($T1*)((size_t)cpu::internal::BaseVirtualAddress+(size_t)mAddress.mAddress)}\n    </DisplayString>\n    <Expand>\n      <ExpandedItem Condition=\"mAddress.mAddress == 0\">\n        ($T1*)(nullptr)\n      </ExpandedItem>\n      <ExpandedItem Condition=\"mAddress.mAddress != 0\">\n        ($T1*)((size_t)cpu::internal::BaseVirtualAddress+(size_t)mAddress.mAddress)\n      </ExpandedItem>\n    </Expand>\n  </Type>\n  <Type Name=\"cpu::Pointer&lt;*, cpu::Address&lt;cpu::Physical&gt;&gt;\">\n    <DisplayString Condition=\"mAddress.mAddress ==0\">NULL</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xCCCCCCCC\">(not set)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xCDCDCDCD\">(not set)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xDDDDDDDD\">(in freed object)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xFEEEFEEE\">(in freed object)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xFDFDFDFD\">(heap buffer overrun)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress == 0xABABABAB\">(heap buffer overrun)</DisplayString>\n    <DisplayString Condition=\"mAddress.mAddress !=0\">\n      {($T1*)((size_t)cpu::internal::BasePhysicalAddress+(size_t)mAddress.mAddress)}\n    </DisplayString>\n    <Expand>\n      <ExpandedItem Condition=\"mAddress.mAddress == 0\">\n        ($T1*)(nullptr)\n      </ExpandedItem>\n      <ExpandedItem Condition=\"mAddress.mAddress != 0\">\n        ($T1*)((size_t)cpu::internal::BasePhysicalAddress+(size_t)mAddress.mAddress)\n      </ExpandedItem>\n    </Expand>\n  </Type>\n  <Type Name=\"be2_val&lt;cpu::Pointer&lt;*, cpu::Address&lt;cpu::Virtual&gt;&gt;&gt;\">\n    <DisplayString Condition=\"mStorage.mAddress.mAddress ==0\">NULL</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xCCCCCCCC\">(not set)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xCDCDCDCD\">(not set)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xDDDDDDDD\">(in freed object)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xFEEEFEEE\">(in freed object)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xFDFDFDFD\">(heap buffer overrun)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xABABABAB\">(heap buffer overrun)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress !=0\">\n      {($T1*)((size_t)cpu::internal::BaseVirtualAddress+(size_t)((((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF)&lt;&lt;24)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF00)&lt;&lt;8)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF0000)&gt;&gt;8)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF000000)&gt;&gt;24)))}\n    </DisplayString>\n    <Expand>\n      <ExpandedItem Condition=\"mStorage.mAddress.mAddress == 0\">\n        ($T1*)(nullptr)\n      </ExpandedItem>\n      <ExpandedItem Condition=\"mStorage.mAddress.mAddress != 0\">\n        ($T1*)((size_t)cpu::internal::BaseVirtualAddress+(size_t)((((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF)&lt;&lt;24)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF00)&lt;&lt;8)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF0000)&gt;&gt;8)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF000000)&gt;&gt;24)))\n      </ExpandedItem>\n    </Expand>\n  </Type>\n  <Type Name=\"be2_val&lt;cpu::Pointer&lt;*, cpu::Address&lt;cpu::Physical&gt;&gt;&gt;\">\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0\">NULL</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xCCCCCCCC\">(not set)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xCDCDCDCD\">(not set)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xDDDDDDDD\">(in freed object)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xFEEEFEEE\">(in freed object)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xFDFDFDFD\">(heap buffer overrun)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress == 0xABABABAB\">(heap buffer overrun)</DisplayString>\n    <DisplayString Condition=\"mStorage.mAddress.mAddress != 0\">\n      {($T1*)((size_t)cpu::internal::BasePhysicalAddress+(size_t)((((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF)&lt;&lt;24)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF00)&lt;&lt;8)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF0000)&gt;&gt;8)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF000000)&gt;&gt;24)))}\n    </DisplayString>\n    <Expand>\n      <ExpandedItem Condition=\"mStorage.mAddress.mAddress == 0\">\n        ($T1*)(nullptr)\n      </ExpandedItem>\n      <ExpandedItem Condition=\"mStorage.mAddress.mAddress != 0\">\n        ($T1*)((size_t)cpu::internal::BasePhysicalAddress+(size_t)((((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF)&lt;&lt;24)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF00)&lt;&lt;8)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF0000)&gt;&gt;8)|(((*(uint32_t*)&amp;mStorage.mAddress.mAddress)&amp;0xFF000000)&gt;&gt;24)))\n      </ExpandedItem>\n    </Expand>\n  </Type>\n  <Type Name=\"be2_val&lt;float&gt;\">\n    <DisplayString Condition=\"*(unsigned *)(&amp;mStorage) == 0x0000807F\">infinity</DisplayString>\n    <DisplayString Condition=\"*(unsigned *)(&amp;mStorage) == 0x000080FF\">-infinity</DisplayString>\n    <DisplayString Condition=\"*(unsigned *)(&amp;mStorage) == 0xCCCCCCCC\">(not set)</DisplayString>\n    <DisplayString Condition=\"*(unsigned *)(&amp;mStorage) == 0xCDCDCDCD\">(not set)</DisplayString>\n    <DisplayString Condition=\"*(unsigned *)(&amp;mStorage) == 0xDDDDDDDD\">(in freed object)</DisplayString>\n    <DisplayString Condition=\"*(unsigned *)(&amp;mStorage) == 0xFEEEFEEE\">(in freed object)</DisplayString>\n    <DisplayString Condition=\"*(unsigned *)(&amp;mStorage) == 0xFDFDFDFD\">(heap buffer overrun)</DisplayString>\n    <DisplayString Condition=\"*(unsigned *)(&amp;mStorage) == 0xABABABAB\">(heap buffer overrun)</DisplayString>\n    <DisplayString>\n      { (1-(2*((*(unsigned *)(&amp;mStorage) &amp; 0x80) >> 7))) *   (((long long)65536 &lt;&lt; ((((((*(unsigned *)(&amp;mStorage)  &amp; 0xFF) &lt;&lt; 24) | ((*(unsigned *)(&amp;mStorage) &amp; 0xFF00) &lt;&lt; 8) | ((*(unsigned *)(&amp;mStorage) &amp; 0xFF0000) &gt;&gt; 8) | ((*(unsigned *)(&amp;mStorage) &amp; 0xFF000000) &gt;&gt; 24)) &amp; 0x7F800000) >> 23) - 127)) / 65536.0f) *\n      (((((*(unsigned *)(&amp;mStorage)  &amp; 0xFF) &lt;&lt; 24) | ((*(unsigned *)(&amp;mStorage) &amp; 0xFF00) &lt;&lt; 8) | ((*(unsigned *)(&amp;mStorage) &amp; 0xFF0000) &gt;&gt; 8) | ((*(unsigned *)(&amp;mStorage) &amp; 0xFF000000) &gt;&gt; 24)) &amp; 0x7FFFFF) * 0.00000011920928955078125 + 1) ,g}\n    </DisplayString>\n  </Type>\n</AutoVisualizer>"
  },
  {
    "path": "src/libcpu/cpu_breakpoints.h",
    "content": "#pragma once\n#include <cstdint>\n#include <vector>\n#include <memory>\n\nnamespace cpu\n{\n\nstruct Breakpoint\n{\n   enum Type : uint32_t\n   {\n      SingleFire,\n      MultiFire,\n   };\n\n   //! Breakpoint type.\n   Type type;\n\n   //! Address of breakpoint.\n   uint32_t address;\n\n   //! Code at address before we inserted a TW instruction.\n   uint32_t savedCode;\n};\n\nusing BreakpointList = std::vector<Breakpoint>;\n\nvoid\naddBreakpoint(uint32_t address,\n              Breakpoint::Type type);\n\nvoid\nremoveBreakpoint(uint32_t address);\n\nbool\ntestBreakpoint(uint32_t address);\n\nbool\nhasBreakpoints();\n\nbool\nhasBreakpoint(uint32_t address);\n\nstd::shared_ptr<BreakpointList>\ngetBreakpoints();\n\nuint32_t\ngetBreakpointSavedCode(uint32_t address);\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/cpu_config.h",
    "content": "#pragma once\n#include <cstdint>\n#include <memory>\n#include <vector>\n#include <string>\n\nnamespace cpu\n{\n\nstruct JitSettings\n{\n   //! Enable usage of jit\n   bool enabled = true;\n\n   //! Use JIT in verification mode where it compares execution to interpreter\n   bool verify = false;\n\n   //! Select a single block (starting address) for verification (0 = verify everything)\n   uint32_t verifyAddress = 0u;\n\n   //! JIT code cache size in megabytes\n   unsigned int codeCacheSizeMB = 1024;\n\n   //! JIT data cache size in megabytes\n   unsigned int dataCacheSizeMB = 512;\n\n   //! List of JIT optimizations to enable\n   std::vector<std::string> optimisationFlags =\n   {\n      \"BASIC\",\n      \"DECONDITION\",\n      \"DSE\",\n      \"FOLD_CONSTANTS\",\n      \"PPC_PAIRED_LWARX_STWCX\",\n      \"X86_BRANCH_ALIGNMENT\",\n      \"X86_CONDITION_CODES\",\n      \"X86_FIXED_REGS\",\n      \"X86_FORWARD_CONDITIONS\",\n      \"X86_STORE_IMMEDIATE\",\n   };\n\n   //! Treat .rodata sections as read-only regardless of RPL/RPX flags\n   bool rodataReadOnly = true;\n};\n\nstruct MemorySettings\n{\n   //! Whether page guards for write tracking is enabled or not.\n   bool writeTrackEnabled = false;\n};\n\nstruct Settings\n{\n   JitSettings jit;\n   MemorySettings memory;\n};\n\nstd::shared_ptr<const Settings> config();\nvoid setConfig(const Settings &settings);\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/cpu_control.h",
    "content": "#pragma once\n#include \"state.h\"\n\n#include <chrono>\n\nnamespace cpu\n{\n\nconst uint32_t SRESET_INTERRUPT = 1 << 0;\nconst uint32_t GENERIC_INTERRUPT = 1 << 1;\nconst uint32_t ALARM_INTERRUPT = 1 << 2;\nconst uint32_t DBGBREAK_INTERRUPT = 1 << 3;\nconst uint32_t GPU7_INTERRUPT = 1 << 4;\nconst uint32_t IPC_INTERRUPT = 1 << 5;\nconst uint32_t PROGRAM_INTERRUPT = 1 << 6;\nconst uint32_t INTERRUPT_MASK = 0xFFFFFFFF;\nconst uint32_t NONMASKABLE_INTERRUPTS = SRESET_INTERRUPT;\n\nconst uint32_t InvalidCoreId = 0xFF;\n\nstd::chrono::steady_clock::time_point\ntbToTimePoint(uint64_t ticks);\n\nvoid\nclearInstructionCache();\n\nvoid\ninvalidateInstructionCache(uint32_t address,\n                           uint32_t size);\n\nvoid\naddJitReadOnlyRange(uint32_t address,\n                    uint32_t size);\n\nvoid\ninterrupt(int core_idx,\n          uint32_t flags);\n\nnamespace this_core\n{\n\nvoid\ncheckInterrupts();\n\nvoid\nwaitForInterrupt();\n\nvoid\nwaitNextInterrupt(std::chrono::steady_clock::time_point until = { });\n\nuint32_t\ninterruptMask();\n\nuint32_t\nsetInterruptMask(uint32_t mask);\n\nvoid\nclearInterrupt(uint32_t flags);\n\nvoid\nsetNextAlarm(std::chrono::steady_clock::time_point alarm_time);\n\nvoid\nresume();\n\nvoid\nexecuteSub();\n\ncpu::Core*\nstate();\n\nuint32_t\nid();\n\n} // namespace this_core\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/cpu_formatters.h",
    "content": "#pragma once\n#include \"address.h\"\n#include \"be2_val.h\"\n#include \"pointer.h\"\n\n#include <fmt/format.h>\n\nnamespace fmt\n{\n\n/*\n * fmtlib formatter for cpu::Address\n */\n\ntemplate<typename AddressType, typename Char>\nstruct formatter<cpu::Address<AddressType>, Char, void>\n{\n   template<typename ParseContext>\n   constexpr auto parse(ParseContext &ctx)\n   {\n      return ctx.begin();\n   }\n\n   template<typename FormatContext>\n   auto format(const cpu::Address<AddressType> &addr, FormatContext &ctx)\n   {\n      return format_to(ctx.out(), \"0x{:08X}\", addr.getAddress());\n   }\n};\n\n/*\n * fmtlib formatter for be2_val<T>\n */\n\ntemplate<typename ValueType, typename Char>\nstruct formatter<be2_val<ValueType>, Char, void>\n   : formatter<typename safe_underlying_type<ValueType>::type, Char, void>\n{\n   using value_type = typename safe_underlying_type<ValueType>::type;\n\n   template<typename ParseContext>\n   constexpr auto parse(ParseContext &ctx)\n   {\n      return formatter<value_type, Char, void>::parse(ctx);\n   }\n\n   template<typename FormatContext>\n   auto format(const be2_val<ValueType> &val, FormatContext &ctx)\n   {\n      return formatter<value_type, Char, void>::format(val, ctx);\n   }\n};\n\n/*\n * fmtlib formatter for cpu::FunctionPointer\n */\n\ntemplate<typename AddressType, typename FunctionType, typename Char>\nstruct formatter<cpu::FunctionPointer<AddressType, FunctionType>, Char, void>\n{\n   template<typename ParseContext>\n   constexpr auto parse(ParseContext &ctx)\n   {\n      return ctx.begin();\n   }\n\n   template<typename FormatContext>\n   auto format(const cpu::FunctionPointer<AddressType, FunctionType> &ptr, FormatContext &ctx)\n   {\n      auto addr = cpu::func_pointer_cast_impl<AddressType, FunctionType>::cast(ptr);\n      return format_to(ctx.out(), \"{:08X}\", static_cast<uint32_t>(addr));\n   }\n};\n\n/*\n * fmtlib formatter for cpu::Pointer\n */\n\ntemplate<typename OutputIt>\ninline auto\nformat_escaped_string(OutputIt iter, const char *data)\n{\n   iter = format_to(iter, \"\\\"\");\n\n   auto hasMoreBytes = true;\n   for (auto i = 0; i < 128; ++i) {\n      auto c = data[i];\n      if (c == 0) {\n         hasMoreBytes = false;\n         break;\n      }\n\n      if (c >= ' ' && c <= '~' && c != '\\\\' && c != '\"') {\n         iter = format_to(iter, \"{}\", c);\n      } else {\n         switch (c) {\n         case '\"': iter = format_to(iter, \"\\\\\\\"\"); break;\n         case '\\\\': iter = format_to(iter, \"\\\\\\\\\"); break;\n         case '\\t': iter = format_to(iter, \"\\\\t\"); break;\n         case '\\r': iter = format_to(iter, \"\\\\r\"); break;\n         case '\\n': iter = format_to(iter, \"\\\\n\"); break;\n         default: iter = format_to(iter, \"\\\\x{:02x}\", c); break;\n         }\n      }\n   }\n\n   if (!hasMoreBytes) {\n      iter = format_to(iter, \"\\\"\");\n   } else {\n      iter = format_to(iter, \"\\\"...\");\n   }\n\n   return iter;\n}\n\ntemplate<typename AddressType, typename Char>\nstruct formatter<cpu::Pointer<char, AddressType>, Char, void>\n{\n   template<typename ParseContext>\n   constexpr auto parse(ParseContext &ctx)\n   {\n      return ctx.begin();\n   }\n\n   template<typename FormatContext>\n   auto format(const cpu::Pointer<char, AddressType> &ptr, FormatContext &ctx)\n   {\n      if (!ptr) {\n         return format_to(ctx.out(), \"<NULL>\");\n      } else {\n         auto bytes = ptr.getRawPointer();\n         return format_escaped_string(ctx.out(), bytes);\n      }\n   }\n};\n\ntemplate<typename AddressType, typename Char>\nstruct formatter<cpu::Pointer<const char, AddressType>, Char, void>\n{\n   template<typename ParseContext>\n   constexpr auto parse(ParseContext &ctx)\n   {\n      return ctx.begin();\n   }\n\n   template<typename FormatContext>\n   auto format(const cpu::Pointer<const char, AddressType> &ptr, FormatContext &ctx)\n   {\n      if (!ptr) {\n         return format_to(ctx.out(), \"<NULL>\");\n      } else {\n         const char *bytes = ptr.getRawPointer();\n         return format_escaped_string(ctx.out(), bytes);\n      }\n   }\n};\n\ntemplate<typename ValueType, typename AddressType, typename Char>\nstruct formatter<cpu::Pointer<ValueType, AddressType>, Char, void>\n{\n   template<typename ParseContext>\n   constexpr auto parse(ParseContext &ctx)\n   {\n      return ctx.begin();\n   }\n\n   template<typename FormatContext>\n   auto format(const cpu::Pointer<ValueType, AddressType> &ptr, FormatContext &ctx)\n   {\n      auto addr = cpu::pointer_cast_impl<AddressType, ValueType *, AddressType>::cast(ptr);\n      return format_to(ctx.out(), \"0x{:08X}\", static_cast<uint32_t>(addr));\n   }\n};\n\n} // namespace fmt\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_disassembler.cpp",
    "content": "#include \"espresso_disassembler.h\"\n#include \"espresso_instructionset.h\"\n\n#include <algorithm>\n#include <common/bitutils.h>\n#include <iomanip>\n#include <vector>\n#include <sstream>\n\nnamespace espresso\n{\n\nstatic bool\ndisassembleField(Disassembly::Argument &result, uint32_t cia, Instruction instr, InstructionInfo *data, InstructionField field)\n{\n   switch (field) {\n   case InstructionField::bd:\n      result.type = Disassembly::Argument::Address;\n      result.address = sign_extend<16>(instr.bd << 2) + (instr.aa ? 0 : cia);\n      break;\n   case InstructionField::bi:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.bi;\n      break;\n   case InstructionField::bo:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.bo;\n      break;\n   case InstructionField::crbA:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.crbA;\n      break;\n   case InstructionField::crbB:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.crbB;\n      break;\n   case InstructionField::crbD:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.crbD;\n      break;\n   case InstructionField::crfD:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"crf\" + std::to_string(instr.crfD);\n      break;\n   case InstructionField::crfS:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"crf\" + std::to_string(instr.crfS);\n      break;\n   case InstructionField::crm:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.crm;\n      break;\n   case InstructionField::d:\n      result.type = Disassembly::Argument::ValueSigned;\n      result.valueSigned = sign_extend<16>(instr.d);\n      break;\n   case InstructionField::fm:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.fm;\n      break;\n   case InstructionField::frA:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"f\" + std::to_string(instr.frA);\n      break;\n   case InstructionField::frB:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"f\" + std::to_string(instr.frB);\n      break;\n   case InstructionField::frC:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"f\" + std::to_string(instr.frC);\n      break;\n   case InstructionField::frD:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"f\" + std::to_string(instr.frD);\n      break;\n   case InstructionField::frS:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"f\" + std::to_string(instr.frS);\n      break;\n   case InstructionField::i:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.i;\n      break;\n   case InstructionField::imm:\n      result.type = Disassembly::Argument::ValueUnsigned;\n      result.valueUnsigned = instr.imm;\n      break;\n   case InstructionField::kcn:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.kcn;\n      break;\n   case InstructionField::li:\n      result.type = Disassembly::Argument::Address;\n      result.address = sign_extend<26>(instr.li << 2) + (instr.aa ? 0 : cia);\n      break;\n   case InstructionField::mb:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.mb;\n      break;\n   case InstructionField::me:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.me;\n      break;\n   case InstructionField::nb:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.nb;\n      break;\n   case InstructionField::qd:\n      result.type = Disassembly::Argument::ValueSigned;\n      result.valueSigned = sign_extend<12>(instr.qd);\n      break;\n   case InstructionField::rA:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"r\" + std::to_string(instr.rA);\n      break;\n   case InstructionField::rB:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"r\" + std::to_string(instr.rB);\n      break;\n   case InstructionField::rD:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"r\" + std::to_string(instr.rD);\n      break;\n   case InstructionField::rS:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"r\" + std::to_string(instr.rS);\n      break;\n   case InstructionField::sh:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.sh;\n      break;\n   case InstructionField::simm:\n      result.type = Disassembly::Argument::ValueSigned;\n      result.valueSigned = sign_extend<16>(instr.simm);\n      break;\n   case InstructionField::sr:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.sr;\n      break;\n   case InstructionField::spr: // TODO: Real SPR name\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"spr\" + std::to_string(((instr.spr << 5) & 0x3E0) | ((instr.spr >> 5) & 0x1F));\n      break;\n   case InstructionField::to:\n      result.type = Disassembly::Argument::ConstantUnsigned;\n      result.constantUnsigned = instr.to;\n      break;\n   case InstructionField::tbr:\n      result.type = Disassembly::Argument::Register;\n      result.registerName = \"tbr\" + std::to_string(instr.spr);\n      break;\n   case InstructionField::uimm:\n      result.type = Disassembly::Argument::ValueUnsigned;\n      result.valueUnsigned = instr.uimm;\n      break;\n   // Ignore opcode fields\n   case InstructionField::opcd:\n   case InstructionField::xo1:\n   case InstructionField::xo2:\n   case InstructionField::xo3:\n   case InstructionField::xo4:\n\n   // Ignore name modifiers\n   case InstructionField::aa:\n   case InstructionField::lk:\n   case InstructionField::oe:\n   case InstructionField::rc:\n\n   // Ignore marker fields\n   case InstructionField::PS:\n   case InstructionField::XERO:\n   case InstructionField::XERSO:\n   case InstructionField::XERC:\n   case InstructionField::CR0:\n   case InstructionField::CR1:\n   case InstructionField::FCRISI:\n   case InstructionField::FCRIDI:\n   case InstructionField::FCRSNAN:\n   case InstructionField::FCRZDZ:\n   case InstructionField::FPRF:\n   case InstructionField::AOE:\n   case InstructionField::ARC:\n   case InstructionField::LR:\n   case InstructionField::CTR:\n   case InstructionField::FPSCR:\n   case InstructionField::RSRV:\n      return false;\n   default:\n      result.type = Disassembly::Argument::Invalid;\n      break;\n   }\n\n   return true;\n}\n\nstd::string\ndisassemblyToText(const Disassembly &dis)\n{\n   auto text = dis.name;\n\n   for (auto &arg : dis.args) {\n      if (&arg == &dis.args[0]) {\n         text += \" \";\n      } else {\n         text += \", \";\n      }\n\n      text += disassemblyArgumentToText(arg);\n   }\n\n   return text;\n}\n\nstd::string\ndisassemblyArgumentToText(const Disassembly::Argument &arg)\n{\n   std::stringstream ss;\n\n   switch (arg.type) {\n   case Disassembly::Argument::Address:\n      ss << \"@\" << std::setfill('0') << std::setw(8) << std::hex << std::uppercase << arg.address;\n      return ss.str();\n   case Disassembly::Argument::Register:\n      return arg.registerName;\n   case Disassembly::Argument::ValueUnsigned:\n      if (arg.valueUnsigned > 9) {\n         ss << \"0x\" << std::hex << arg.valueUnsigned;\n      } else {\n         ss << arg.valueUnsigned;\n      }\n      return ss.str();\n   case Disassembly::Argument::ValueSigned:\n      if (arg.valueSigned < -9) {\n         ss << \"-0x\" << std::hex << -arg.valueSigned;\n      } else if (arg.valueSigned > 9) {\n         ss << \"0x\" << std::hex << arg.valueSigned;\n      } else {\n         ss << arg.valueSigned;\n      }\n      return ss.str();\n   case Disassembly::Argument::ConstantUnsigned:\n      return std::to_string(arg.constantUnsigned);\n   case Disassembly::Argument::ConstantSigned:\n      return std::to_string(arg.constantSigned);\n   case Disassembly::Argument::Invalid:\n      return std::string(\"???\");\n   }\n\n   return std::string();\n}\n\nstatic void\ncheckBranchConditionAlias(Instruction instr, Disassembly &dis)\n{\n   auto bi = instr.bi % 4;\n   auto bo = instr.bo;\n   auto name = std::string {};\n\n   // Check for unconditional branch\n   if (bo == 20 && bi == 0) {\n      // Remove bo, bi from args\n      dis.args.erase(dis.args.begin(), dis.args.begin() + 2);\n\n      if (dis.instruction->id == InstructionID::bcctr) {\n         dis.name = \"bctr\";\n      } else if (dis.instruction->id == InstructionID::bclr) {\n         dis.name = \"blr\";\n      } else if (dis.instruction->id == InstructionID::bc) {\n         dis.name = \"b\";\n      }\n\n      return;\n   }\n\n   if (bo == 12 && bi == 0) {\n      name = \"blt\";\n   } else if (bo == 4 && bi == 1) {\n      name = \"ble\";\n   } else if (bo == 12 && bi == 2) {\n      name = \"beq\";\n   } else if (bo == 4 && bi == 0) {\n      name = \"bge\";\n   } else if (bo == 12 && bi == 1) {\n      name = \"bgt\";\n   } else if (bo == 4 && bi == 2) {\n      name = \"bne\";\n   } else if (bo == 12 && bi == 3) {\n      name = \"bso\";\n   } else if (bo == 4 && bi == 3) {\n      name = \"bns\";\n   }\n\n   if (!name.empty()) {\n      // Remove bo, bi from args\n      dis.args.erase(dis.args.begin(), dis.args.begin() + 2);\n\n      // Add crS argument\n      auto cr = Disassembly::Argument { };\n      cr.type = Disassembly::Argument::Register;\n      cr.registerName = \"cr\" + std::to_string(instr.bi / 4);\n      dis.args.push_back(cr);\n\n      // Update name\n      dis.name = name;\n   }\n}\n\nbool\ndisassemble(Instruction instr, Disassembly &dis, uint32_t address)\n{\n   auto data = decodeInstruction(instr);\n   if (!data) {\n      return false;\n   }\n\n   auto alias = findInstructionAlias(data, instr);\n   dis.name = alias ? alias->name : data->name;\n   dis.instruction = data;\n   dis.address = address;\n\n   auto args = std::vector<InstructionField> { };\n   args.reserve(16);\n\n   for (auto &field : data->write) {\n      // Skip arguments that are in read list as well\n      if (std::find(data->read.begin(), data->read.end(), field) != data->read.end()) {\n         continue;\n      }\n\n      // Add only unique arguements\n      if (std::find(args.begin(), args.end(), field) != args.end()) {\n         continue;\n      }\n\n      // Ignore trace only fields for disassembly\n      if (isInstructionFieldMarker(field)) {\n         continue;\n      }\n\n      args.push_back(field);\n   }\n\n   for (auto &field : data->read) {\n      // Add only unique arguements\n      if (std::find(args.begin(), args.end(), field) != args.end()) {\n         continue;\n      }\n\n      args.push_back(field);\n   }\n\n   for (auto &field : args) {\n      auto arg = Disassembly::Argument { };\n\n      // If we have an alias, then skip the LHS field of each alias comparison\n      if (alias) {\n         auto skipField = false;\n\n         for (auto &op : alias->opcode) {\n            if (field == op.field) {\n               skipField = true;\n               break;\n            }\n         }\n\n         if (skipField) {\n            continue;\n         }\n      }\n\n      if (disassembleField(arg, dis.address, instr, data, field)) {\n         dis.args.push_back(arg);\n      }\n   }\n\n   // Check for bc alias\n   if (data->id == InstructionID::bc || data->id == InstructionID::bcctr || data->id == InstructionID::bclr) {\n      checkBranchConditionAlias(instr, dis);\n   }\n\n   for (auto &field : data->flags) {\n      if (field == InstructionField::aa && instr.aa) {\n         dis.name += 'a';\n      } else if (field == InstructionField::lk && instr.lk) {\n         dis.name += 'l';\n      } else if (field == InstructionField::oe && instr.oe) {\n         dis.name += 'o';\n      } else if (field == InstructionField::rc && instr.rc) {\n         dis.name += '.';\n      }\n   }\n\n   return true;\n}\n\n// TODO: Maybe these enums should be moved to a common header rather than\n// duplicated from interpreter\nenum BoBits\n{\n   CtrValue    = 1,\n   NoCheckCtr  = 2,\n   CondValue   = 3,\n   NoCheckCond = 4\n};\n\nenum BcFlags\n{\n   BcCheckCtr  = 1 << 0,\n   BcCheckCond = 1 << 1,\n   BcBranchLR  = 1 << 2,\n   BcBranchCTR = 1 << 3\n};\n\ntemplate<unsigned flags>\nstatic BranchInfo\ndisassembleBranchInfoBX(Instruction instr,\n                        uint32_t address,\n                        uint32_t ctr,\n                        uint32_t cr,\n                        uint32_t lr)\n{\n   auto info = BranchInfo { };\n   info.isVariable = false;\n   info.isCall = instr.lk;\n   info.isConditional = false;\n   info.conditionSatisfied = true;\n   info.target = 0xFFFFFFFF;\n\n   auto bo = instr.bo;\n\n   if constexpr (flags & BcCheckCtr) {\n      if (!get_bit<NoCheckCtr>(bo)) {\n         info.isConditional = true;\n\n         auto ctb = static_cast<uint32_t>(ctr - 1 != 0);\n         auto ctv = get_bit<CtrValue>(bo);\n         if (!(ctb ^ ctv)) {\n            info.conditionSatisfied = false;\n         }\n      }\n   }\n\n   if constexpr (!!(flags & BcCheckCond)) {\n      if (!get_bit<NoCheckCond>(bo)) {\n         info.isConditional = true;\n\n         auto crb = get_bit(cr, 31 - instr.bi);\n         auto crv = get_bit<CondValue>(bo);\n         if (crb != crv) {\n            info.conditionSatisfied = false;\n         }\n      }\n   }\n\n   if constexpr (!!(flags & BcBranchCTR)) {\n      info.isVariable = true;\n      if (ctr) {\n         info.target = ctr & ~0x3;\n      }\n   } else if constexpr (!!(flags & BcBranchLR)) {\n      info.isVariable = true;\n      if (lr) {\n         info.target = lr & ~0x3;\n      }\n   } else {\n      info.target = sign_extend<16>(instr.bd << 2);\n\n      if (!instr.aa) {\n         info.target += address;\n      }\n   }\n\n   return info;\n}\n\nBranchInfo\ndisassembleBranchInfo(InstructionID id,\n                      Instruction ins,\n                      uint32_t address,\n                      uint32_t ctr,\n                      uint32_t cr,\n                      uint32_t lr)\n{\n   auto info = BranchInfo { };\n\n   if (id == InstructionID::b) {\n      info.isVariable = false;\n      info.isCall = ins.lk;\n      info.isConditional = false;\n      info.conditionSatisfied = true;\n      info.target = sign_extend<26>(ins.li << 2);\n      if (!ins.aa) {\n         info.target += address;\n      }\n   } else if (id == InstructionID::bc) {\n      info = disassembleBranchInfoBX<BcCheckCtr | BcCheckCond>(ins, address, ctr, cr, lr);\n   } else if (id == InstructionID::bcctr) {\n      info = disassembleBranchInfoBX<BcBranchCTR | BcCheckCond>(ins, address, ctr, cr, lr);\n   } else if (id == InstructionID::bclr) {\n      info = disassembleBranchInfoBX<BcBranchLR | BcCheckCtr | BcCheckCond>(ins, address, ctr, cr, lr);\n   }\n\n   return info;\n}\n\n} // namespace espresso\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_disassembler.h",
    "content": "#pragma once\n#include <cstdint>\n#include <string>\n#include <vector>\n#include \"espresso_instruction.h\"\n#include \"espresso_instructionid.h\"\n\nnamespace espresso\n{\n\nstruct InstructionInfo;\n\nstruct BranchInfo\n{\n   bool isVariable;\n   uint32_t target;\n   bool isConditional;\n   bool conditionSatisfied;\n   bool isCall;\n};\n\nstruct Disassembly\n{\n   struct Argument\n   {\n      enum Type\n      {\n         Invalid,\n         Address,\n         Register,\n         ValueUnsigned,\n         ValueSigned,\n         ConstantUnsigned,\n         ConstantSigned\n      };\n\n      Type type;\n      std::string registerName;\n\n      union\n      {\n         uint32_t address;\n         uint32_t constantUnsigned;\n         int32_t constantSigned;\n         uint32_t valueUnsigned;\n         int32_t valueSigned;\n      };\n   };\n\n   uint32_t address;\n   InstructionInfo *instruction;\n   std::string name;\n   std::vector<Argument> args;\n};\n\nbool disassemble(Instruction bin, Disassembly &out, uint32_t address);\nstd::string disassemblyToText(const Disassembly &dis);\nstd::string disassemblyArgumentToText(const Disassembly::Argument &arg);\nBranchInfo disassembleBranchInfo(InstructionID id, Instruction ins,\n                                 uint32_t address, uint32_t ctr,\n                                 uint32_t cr, uint32_t lr);\n\n} // namespace espresso\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_instruction.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace espresso\n{\n\nunion Instruction\n{\n   Instruction() : value(0)\n   {\n   }\n\n   constexpr Instruction(uint32_t value_) : value(value_)\n   {\n   }\n\n   uint32_t value;\n\n   operator uint32_t() const\n   {\n      return value;\n   }\n\n#define FLD(x, y, z, ...) \\\n   struct { \\\n      uint32_t : (31 - z); \\\n      uint32_t x : (z - y + 1); \\\n      uint32_t : (y); \\\n   };\n#define MRKR(...)\n#include \"espresso_instruction_fields.inl\"\n#undef FLD\n#undef MRKR\n};\n\n} // namespace espresso\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_instruction_aliases.inl",
    "content": "INSA(li, addi, (rA == 0))\nINSA(lis, addis, (rA == 0))\n\nINSA(mflr, mfspr, (spr == 8))\nINSA(mfctr, mfspr, (spr == 9))\nINSA(mfxer, mfspr, (spr == 1))\n\nINSA(mtlr, mtspr, (spr == 8))\nINSA(mtctr, mtspr, (spr == 9))\nINSA(mtxer, mtspr, (spr == 1))\n\nINSA(mtcr, mtcrf, (crm == 0xFF))\n\nINSA(mr, or_, (rS == rB))\nINSA(nop, ori, (rA == 0, rS == 0, uimm == 0))\nINSA(not, nor, (rS == rB))\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_instruction_definitions.inl",
    "content": "//INS(name, write, read, flags, opcodes, fullname)\n\n// Intger Arithmetic\nINS(add, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 266), \"Add\")\nINS(addc, (rD, XERC), (rA, rB), (oe, rc), (opcd == 31, xo2 == 10), \"Add with Carry\")\nINS(adde, (rD), (rA, rB, XERC), (oe, rc), (opcd == 31, xo2 == 138), \"Add Extended\")\nINS(addi, (rD), (rA, simm), (), (opcd == 14), \"Add Immediate\")\nINS(addic, (rD, XERC), (rA, simm), (), (opcd == 12), \"Add Immediate with Carry\")\nINS(addicx, (rD, XERC), (rA, simm), (AOE, ARC), (opcd == 13), \"Add Immediate with Carry and Record\")\nINS(addis, (rD), (rA, simm), (), (opcd == 15), \"Add Immediate Shifted\")\nINS(addme, (rD), (rA, XERC), (oe, rc), (opcd == 31, xo2 == 234, !_16_20), \"Add to Minus One Extended\")\nINS(addze, (rD), (rA, XERC), (oe, rc), (opcd == 31, xo2 == 202, !_16_20), \"Add to Zero Extended\")\nINS(divw, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 491), \"Divide Word\")\nINS(divwu, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 459), \"Divide Word Unsigned\")\nINS(mulhw, (rD), (rA, rB), (rc), (opcd == 31, xo2 == 75), \"Multiply High Word\")\nINS(mulhwu, (rD), (rA, rB), (rc), (opcd == 31, xo2 == 11), \"Multiply High Word Unsigned\")\nINS(mulli, (rD), (rA, simm), (), (opcd == 7), \"Multiply Low Immediate\")\nINS(mullw, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 235), \"Multiply Low Word\")\nINS(neg, (rD), (rA), (oe, rc), (opcd == 31, xo2 == 104, !_16_20), \"Negate\")\nINS(subf, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 40), \"Subtract From\")\nINS(subfc, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 8), \"Subtract From with Carry\")\nINS(subfe, (rD), (rA, rB, XERC), (oe, rc), (opcd == 31, xo2 == 136), \"Subtract From Extended\")\nINS(subfic, (rD, XERC), (rA, simm), (), (opcd == 8), \"Subtract From Immediate with Carry\")\nINS(subfme, (rD), (rA, XERC), (oe, rc), (opcd == 31, xo2 == 232, !_16_20), \"Subtract From Minus One Extended\")\nINS(subfze, (rD), (rA, XERC), (oe, rc), (opcd == 31, xo2 == 200, !_16_20), \"Subtract From Zero Extended\")\n\n// Integer Compare\nINS(cmp, (crfD), (rA, rB, XERSO), (l), (opcd == 31, xo1 == 0, !_9, !_31), \"Compare\")\nINS(cmpi, (crfD), (rA, simm, XERSO), (l), (opcd == 11, !_9), \"Compare Immediate\")\nINS(cmpl, (crfD), (rA, rB, XERSO), (l), (opcd == 31, xo1 == 32, !_9, !_31), \"Compare Logical\")\nINS(cmpli, (crfD), (rA, uimm, XERSO), (l), (opcd == 10, !_9), \"Compare Logical Immediate\")\n\n// Integer Logical\nINS(and_, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 28), \"AND\")\nINS(andc, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 60), \"AND with Complement\")\nINS(andi, (rA), (rS, uimm), (AOE, ARC), (opcd == 28), \"AND Immediate\")\nINS(andis, (rA), (rS, uimm), (AOE, ARC), (opcd == 29), \"AND Immediate Shifted\")\nINS(cntlzw, (rA), (rS), (rc), (opcd == 31, xo1 == 26, !_16_20), \"Count Leading Zeroes Word\")\nINS(eqv, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 284), \"Equivalent\")\nINS(extsb, (rA), (rS), (rc), (opcd == 31, xo1 == 954, !_16_20), \"Extend Sign Byte\")\nINS(extsh, (rA), (rS), (rc), (opcd == 31, xo1 == 922, !_16_20), \"Extend Sign Half Word\")\nINS(nand, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 476), \"NAND\")\nINS(nor, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 124), \"NOR\")\nINS(or_, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 444), \"OR\")\nINS(orc, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 412), \"OR with Complement\")\nINS(ori, (rA), (rS, uimm), (), (opcd == 24), \"OR Immediate\")\nINS(oris, (rA), (rS, uimm), (), (opcd == 25), \"OR Immediate Shifted\")\nINS(xor_, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 316), \"XOR\")\nINS(xori, (rA), (rS, uimm), (), (opcd == 26), \"XOR Immediate\")\nINS(xoris, (rA), (rS, uimm), (), (opcd == 27), \"XOR Immediate Shifted\")\n\n// Integer Rotate\nINS(rlwimi, (rA), (rA, rS, sh, mb, me), (rc), (opcd == 20), \"Rotate Left Word Immediate then Mask Insert\")\nINS(rlwinm, (rA), (rS, sh, mb, me), (rc), (opcd == 21), \"Rotate Left Word Immediate then AND with Mask\")\nINS(rlwnm, (rA), (rS, rB, mb, me), (rc), (opcd == 23), \"Rotate Left Word then AND with Mask\")\n\n// Integer Shift\nINS(slw, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 24), \"Shift Left Word\")\nINS(sraw, (rA, XERC), (rS, rB), (rc), (opcd == 31, xo1 == 792), \"Shift Right Arithmetic Word\")\nINS(srawi, (rA, XERC), (rS, sh), (rc), (opcd == 31, xo1 == 824), \"Shift Right Arithmetic Word Immediate\")\nINS(srw, (rA), (rS, rB), (rc), (opcd == 31, xo1 == 536), \"Shift Right Word\")\n\n// Floating-Point Arithmetic\nINS(fadd, (frD, FCRISI, FCRSNAN), (frA, frB), (frc), (opcd == 63, xo4 == 21), \"Floating Add\")\nINS(fadds, (frD, FCRISI, FCRSNAN), (frA, frB), (frc), (opcd == 59, xo4 == 21), \"Floating Add Single\")\nINS(fdiv, (frD, FCRZDZ, FCRIDI, FCRSNAN), (frA, frB), (frc), (opcd == 63, xo4 == 18), \"Floating Divide\")\nINS(fdivs, (frD), (frA, frB), (frc), (opcd == 59, xo4 == 18), \"Floating Divide Single\")\nINS(fmul, (frD), (frA, frC), (frc), (opcd == 63, xo4 == 25), \"Floating Multiply\")\nINS(fmuls, (frD), (frA, frC), (frc), (opcd == 59, xo4 == 25), \"Floating Multiply Single\")\nINS(fres, (frD), (frB), (frc), (opcd == 59, xo4 == 24), \"Floating Reciprocal Estimate Single\")\nINS(frsqrte, (frD), (frB), (frc), (opcd == 63, xo4 == 26), \"Floating Reciprocal Square Root Estimate\")\nINS(fsub, (frD), (frA, frB), (frc), (opcd == 63, xo4 == 20), \"Floating Sub\")\nINS(fsubs, (frD), (frA, frB), (frc), (opcd == 59, xo4 == 20), \"Floating Sub Single\")\nINS(fsel, (frD), (frA, frB, frC), (frc), (opcd == 63, xo4 == 23), \"Floating Select\")\n\n// Floating-Point Multiply-Add\nINS(fmadd, (frD), (frA, frC, frB), (rc), (opcd == 63, xo4 == 29), \"Floating Multiply-Add\")\nINS(fmadds, (frD), (frA, frC, frB), (rc), (opcd == 59, xo4 == 29), \"Floating Multiply-Add Single\")\nINS(fmsub, (frD), (frA, frC, frB), (rc), (opcd == 63, xo4 == 28), \"Floating Multiply-Sub\")\nINS(fmsubs, (frD), (frA, frC, frB), (rc), (opcd == 59, xo4 == 28), \"Floating Multiply-Sub Single\")\nINS(fnmadd, (frD), (frA, frC, frB), (rc), (opcd == 63, xo4 == 31), \"Floating Negative Multiply-Add\")\nINS(fnmadds, (frD), (frA, frC, frB), (rc), (opcd == 59, xo4 == 31), \"Floating Negative Multiply-Add Single\")\nINS(fnmsub, (frD), (frA, frC, frB), (rc), (opcd == 63, xo4 == 30), \"Floating Negative Multiply-Sub\")\nINS(fnmsubs, (frD), (frA, frC, frB), (rc), (opcd == 59, xo4 == 30), \"Floating Negative Multiply-Sub Single\")\n\n// Floating-Point Rounding and Conversion\nINS(fctiw, (frD), (frB), (rc), (opcd == 63, xo1 == 14), \"Floating Convert to Integer Word\")\nINS(fctiwz, (frD), (frB), (rc), (opcd == 63, xo1 == 15), \"Floating Convert to Integer Word with Round toward Zero\")\nINS(frsp, (frD), (frB), (rc), (opcd == 63, xo1 == 12), \"Floating Round to Single\")\n\n// Floating-Point Compare\nINS(fcmpo, (crfD), (frA, frB), (), (opcd == 63, xo1 == 32, !_9_10, !_31), \"Floating Compare Ordered\")\nINS(fcmpu, (crfD), (frA, frB), (), (opcd == 63, xo1 == 0, !_9_10, !_31), \"Floating Compare Unordered\")\n\n// Floating-Point Status and Control Register\nINS(mcrfs, (crfD), (crfS), (), (opcd == 63, xo1 == 64, !_9_10, !_14_15, !_16_20, !_31), \"\")\nINS(mffs, (frD), (), (rc), (opcd == 63, xo1 == 583, !_11_15, !_16_20), \"\")\nINS(mtfsb0, (), (crfD), (rc), (opcd == 63, xo1 == 70, !_11_15, !_16_20), \"\")\nINS(mtfsb1, (), (crfD), (rc), (opcd == 63, xo1 == 38, !_11_15, !_16_20), \"\")\nINS(mtfsf, (), (fm, frB), (rc), (opcd == 63, xo1 == 711, !_6, !_15), \"\")\nINS(mtfsfi, (crfD), (), (rc, imm), (opcd == 63, xo1 == 134, !_9_10, !_11_15, !_20), \"\")\n\n// Integer Load\nINS(lbz, (rD), (rA, d), (), (opcd == 34), \"Load Byte and Zero\")\nINS(lbzu, (rD, rA), (rA, d), (), (opcd == 35), \"Load Byte and Zero with Update\")\nINS(lbzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 87, !_31), \"Load Byte and Zero Indexed\")\nINS(lbzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 119, !_31), \"Load Byte and Zero with Update Indexed\")\nINS(lha, (rD), (rA, d), (), (opcd == 42), \"Load Half Word Algebraic\")\nINS(lhau, (rD, rA), (rA, d), (), (opcd == 43), \"Load Half Word Algebraic with Update\")\nINS(lhax, (rD), (rA, rB), (), (opcd == 31, xo1 == 343, !_31), \"Load Half Word Algebraic Indexed\")\nINS(lhaux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 375, !_31), \"Load Half Word Algebraic with Update Indexed\")\nINS(lhz, (rD), (rA, d), (), (opcd == 40), \"Load Half Word and Zero\")\nINS(lhzu, (rD, rA), (rA, d), (), (opcd == 41), \"Load Half Word and Zero with Update\")\nINS(lhzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 279, !_31), \"Load Half Word and Zero Indexed\")\nINS(lhzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 311, !_31), \"Load Half Word and Zero with Update Indexed\")\nINS(lwz, (rD), (rA, d), (), (opcd == 32), \"Load Word and Zero\")\nINS(lwzu, (rD, rA), (rA, d), (), (opcd == 33), \"Load Word and Zero with Update\")\nINS(lwzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 23, !_31), \"Load Word and Zero Indexed\")\nINS(lwzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 55, !_31), \"Load Word and Zero with Update Indexed\")\n\n// Integer Store\nINS(stb, (), (rS, rA, d), (), (opcd == 38), \"Store Byte\")\nINS(stbu, (rA), (rS, rA, d), (), (opcd == 39), \"Store Byte with Update\")\nINS(stbx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 215, !_31), \"Store Byte Indexed\")\nINS(stbux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 247, !_31), \"Store Byte with Update Indexed\")\nINS(sth, (), (rS, rA, d), (), (opcd == 44), \"Store Half Word\")\nINS(sthu, (rA), (rS, rA, d), (), (opcd == 45), \"Store Half Word with Update\")\nINS(sthx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 407, !_31), \"Store Half Word Indexed\")\nINS(sthux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 439, !_31), \"Store Half Word with Update Indexed\")\nINS(stw, (), (rS, rA, d), (), (opcd == 36), \"Store Word\")\nINS(stwu, (rA), (rS, rA, d), (), (opcd == 37), \"Store Word with Update\")\nINS(stwx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 151, !_31), \"Store Word Indexed\")\nINS(stwux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 183, !_31), \"Store Word with Update Indexed\")\n\n// Integer Load and Store with Byte Reverse\nINS(lhbrx, (rD), (rA, rB), (), (opcd == 31, xo1 == 790, !_31), \"Load Half Word Byte-Reverse Indexed\")\nINS(lwbrx, (rD), (rA, rB), (), (opcd == 31, xo1 == 534, !_31), \"Load Word Byte-Reverse Indexed\")\nINS(sthbrx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 918, !_31), \"Store Half Word Byte-Reverse Indexed\")\nINS(stwbrx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 662, !_31), \"Store Word Byte-Reverse Indexed\")\n\n// Integer Load and Store Multiple\nINS(lmw, (rD), (rA, d), (), (opcd == 46), \"Load Multiple Words\")\nINS(stmw, (), (rS, rA, d), (), (opcd == 47), \"Store Multiple Words\")\n\n// Integer Load and Store String\nINS(lswi, (rD), (rA, nb), (), (opcd == 31, xo1 == 597, !_31), \"Load String Word Immediate\")\nINS(lswx, (rD), (rA, rB), (), (opcd == 31, xo1 == 533, !_31), \"Load String Word Indexed\")\nINS(stswi, (), (rS, rA, nb), (), (opcd == 31, xo1 == 725, !_31), \"Store String Word Immediate\")\nINS(stswx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 661, !_31), \"Store String Word Indexed\")\n\n// Memory Synchronisation\nINS(eieio, (), (), (), (opcd == 31, xo1 == 854, !_6_10, !_11_15, !_16_20, !_31), \"Enforce In-Order Execution of I/O\")\nINS(isync, (), (), (), (opcd == 19, xo1 == 150, !_6_10, !_11_15, !_16_20, !_31), \"Instruction Synchronise\")\nINS(lwarx, (rD, RSRV), (rA, rB), (), (opcd == 31, xo1 == 20, !_31), \"Load Word and Reserve Indexed\")\nINS(stwcx, (RSRV), (rS, rA, rB), (), (opcd == 31, xo1 == 150, _31 == 1), \"Store Word Conditional Indexed\")\nINS(sync, (), (), (l), (opcd == 31, xo1 == 598, !_6_9, !_11_15, !_16_20, !_31), \"Synchronise\")\n\n// Floating-Point Load\nINS(lfd, (frD), (rA, d), (), (opcd == 50), \"Load Floating-Point Double\")\nINS(lfdu, (frD, rA), (rA, d), (), (opcd == 51), \"Load Floating-Point Double with Update\")\nINS(lfdx, (frD), (rA, rB), (), (opcd == 31, xo1 == 599, !_31), \"Load Floating-Point Double Indexed\")\nINS(lfdux, (frD, rA), (rA, rB), (), (opcd == 31, xo1 == 631, !_31), \"Load Floating-Point Double with Update Indexed\")\nINS(lfs, (frD), (rA, d), (), (opcd == 48), \"Load Floating-Point Single\")\nINS(lfsu, (frD, rA), (rA, d), (), (opcd == 49), \"Load Floating-Point Single with Update\")\nINS(lfsx, (frD), (rA, rB), (), (opcd == 31, xo1 == 535, !_31), \"Load Floating-Point Single Indexed\")\nINS(lfsux, (frD, rA), (rA, rB), (), (opcd == 31, xo1 == 567, !_31), \"Load Floating-Point Single with Update Indexed\")\n\n// Floating-Point Store\nINS(stfd, (), (frS, rA, d), (), (opcd == 54), \"Store Floating-Point Double\")\nINS(stfdu, (rA), (frS, rA, d), (), (opcd == 55), \"Store Floating-Point Double with Update\")\nINS(stfdx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 727, !_31), \"Store Floating-Point Double Indexed\")\nINS(stfdux, (rA), (frS, rA, rB), (), (opcd == 31, xo1 == 759, !_31), \"Store Floating-Point Double with Update Indexed\")\nINS(stfiwx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 983, !_31), \"Store Floating-Point as Integer Word Indexed\")\nINS(stfs, (), (frS, rA, d), (), (opcd == 52), \"Store Floating-Point Single\")\nINS(stfsu, (rA), (frS, rA, d), (), (opcd == 53), \"Store Floating-Point Single with Update\")\nINS(stfsx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 663, !_31), \"Store Floating-Point Single Indexed\")\nINS(stfsux, (rA), (frS, rA, rB), (), (opcd == 31, xo1 == 695, !_31), \"Store Floating-Point Single with Update Indexed\")\n\n// Floating-Point Move\nINS(fabs, (frD), (frB), (rc), (opcd == 63, xo1 == 264, !_11_15), \"Floating Absolute Value\")\nINS(fmr, (frD), (frB), (rc), (opcd == 63, xo1 == 72, !_11_15), \"Floating Move Register\")\nINS(fnabs, (frD), (frB), (rc), (opcd == 63, xo1 == 136, !_11_15), \"Floating Negative Absolute Value\")\nINS(fneg, (frD), (frB), (rc), (opcd == 63, xo1 == 40, !_11_15), \"Floating Negate\")\n\n// Branch\nINS(b, (), (li), (aa, lk), (opcd == 18), \"Branch\")\nINS(bc, (bo), (bi, bd), (aa, lk), (opcd == 16), \"Branch Conditional\")\nINS(bcctr, (bo), (bi, CTR), (lk), (opcd == 19, xo1 == 528, !_16_20), \"Branch Conditional to CTR\")\nINS(bclr, (bo), (bi, LR), (lk), (opcd == 19, xo1 == 16, !_16_20), \"Branch Conditional to LR\")\n\n// Condition Register Logical\nINS(crand, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 257, !_31), \"Condition Register AND\")\nINS(crandc, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 129, !_31), \"Condition Register AND with Complement\")\nINS(creqv, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 289, !_31), \"Condition Register Equivalent\")\nINS(crnand, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 225, !_31), \"Condition Register NAND\")\nINS(crnor, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 33, !_31), \"Condition Register NOR\")\nINS(cror, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 449, !_31), \"Condition Register OR\")\nINS(crorc, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 417, !_31), \"Condition Register OR with Complement\")\nINS(crxor, (crbD), (crbA, crbB), (), (opcd == 19, xo1 == 193, !_31), \"Condition Register XOR\")\nINS(mcrf, (crfD), (crfS), (), (opcd == 19, xo1 == 0, !_9_10, !_14_15, !_16_20, !_31), \"Move Condition Register Field\")\n\n// System Linkage\nINS(rfi, (), (), (), (opcd == 19, xo1 == 50, !_6_10, !_11_15, !_16_20, !_31), \"\")\nINS(kc, (), (kcn), (), (opcd == 17, _31 == 1), \"krncall\")  // Must come before sc for proper table setup\nINS(sc, (), (), (), (opcd == 17, !_6_10, !_11_15, !_16_29, _30 == 1, !_31), \"Syscall\")\n\n// Trap\nINS(tw, (), (to, rA, rB), (), (opcd == 31, xo1 == 4, !_31), \"\")\nINS(twi, (), (to, rA, simm), (), (opcd == 3), \"\")\n\n// Processor Control\nINS(mcrxr, (crfD), (XERO), (), (opcd == 31, xo1 == 512, !_9_10, !_11_15, !_16_20, !_31), \"Move to Condition Register from XERO\")\n// mfcr requires bit 11 to be 0 (if 1, it's the mfocrf instruction), but the\n// Espresso ignores bit 11 and treats mfocrf as mfcr.\nINS(mfcr, (rD), (), (), (opcd == 31, xo1 == 19, !_20, !_31), \"Move from Condition Register\")\nINS(mfmsr, (rD), (), (), (opcd == 31, xo1 == 83, !_11_15, !_16_20, !_31), \"Move from Machine State Register\")\nINS(mfspr, (rD), (spr), (), (opcd == 31, xo1 == 339, !_31), \"Move from Special Purpose Register\")\nINS(mftb, (rD), (tbr), (), (opcd == 31, xo1 == 371, !_31), \"Move from Time Base Register\")\n// mtcrf requires bit 11 to be 0 (if 1, it's the mtocrf instruction), but the\n// Espresso ignores bit 11 and treats mtocrf as mtcrf.\nINS(mtcrf, (crm), (rS), (), (opcd == 31, xo1 == 144, !_20, !_31), \"Move to Condition Register Fields\")\nINS(mtmsr, (), (rS), (), (opcd == 31, xo1 == 146, !_11_15, !_16_20, !_31), \"Move to Machine State Register\")\nINS(mtspr, (spr), (rS), (), (opcd == 31, xo1 == 467, !_31), \"Move to Special Purpose Register\")\n\n// Cache Management\nINS(dcbf, (), (rA, rB), (), (opcd == 31, xo1 == 86, !_6_10, !_31), \"\")\nINS(dcbi, (), (rA, rB), (), (opcd == 31, xo1 == 470, !_6_10, !_31), \"\")\nINS(dcbst, (), (rA, rB), (), (opcd == 31, xo1 == 54, !_6_10, !_31), \"\")\nINS(dcbt, (), (rA, rB), (), (opcd == 31, xo1 == 278, !_6_10, !_31), \"\")\nINS(dcbtst, (), (rA, rB), (), (opcd == 31, xo1 == 246, !_6_10, !_31), \"\")\nINS(dcbz, (), (rA, rB), (), (opcd == 31, xo1 == 1014, !_6_10, !_31), \"\")\nINS(icbi, (), (rA, rB), (), (opcd == 31, xo1 == 982, !_6_10, !_31), \"\")\nINS(dcbz_l, (), (rA, rB), (), (opcd == 4, xo1 == 1014, !_6_10, !_31), \"\")\n\n// Segment Register Manipulation\nINS(mfsr, (rD), (sr), (), (opcd == 31, xo1 == 595, !_11, !_16_20, !_31), \"Move from Segment Register\")\nINS(mfsrin, (rD), (rB), (), (opcd == 31, xo1 == 659, !_11_15, !_31), \"Move from Segment Register Indirect\")\nINS(mtsr, (), (rD, sr), (), (opcd == 31, xo1 == 210, !_11, !_16_20, !_31), \"Move to Segment Register\")\nINS(mtsrin, (), (rD, rB), (), (opcd == 31, xo1 == 242, !_11_15, !_31), \"Move to Segment Register Indirect\")\n\n// Lookaside Buffer Management\nINS(tlbie, (), (rB), (), (opcd == 31, xo1 == 306, !_6_10, !_11_15, !_31), \"\")\nINS(tlbsync, (), (), (), (opcd == 31, xo1 == 566, !_6_10, !_11_15, !_16_20, !_31), \"\")\n\n// External Control\nINS(eciwx, (rD), (rA, rB), (), (opcd == 31, xo1 == 310, !_31), \"\")\nINS(ecowx, (rD), (rA, rB), (), (opcd == 31, xo1 == 438, !_31), \"\")\n\n// Paired-Single Load and Store\nINS(psq_l, (frD), (rA, qd), (w, i), (opcd == 56), \"Paired Single Load\")\nINS(psq_lu, (frD), (rA, qd), (w, i), (opcd == 57), \"Paired Single Load with Update\")\nINS(psq_lx, (frD), (rA, rB), (qw, qi), (opcd == 4, xo3 == 6, !_31), \"Paired Single Load Indexed\")\nINS(psq_lux, (frD), (rA, rB), (qw, qi), (opcd == 4, xo3 == 38, !_31), \"Paired Single Load with Update Indexed\")\nINS(psq_st, (frD), (rA, qd), (w, i), (opcd == 60), \"Paired Single Store\")\nINS(psq_stu, (frD), (rA, qd), (w, i), (opcd == 61), \"Paired Single Store with Update\")\nINS(psq_stx, (frS), (rA, rB), (qw, qi), (opcd == 4, xo3 == 7, !_31), \"Paired Single Store Indexed\")\nINS(psq_stux, (frS), (rA, rB), (qw, qi), (opcd == 4, xo3 == 39, !_31), \"Paired Single Store with Update Indexed\")\n\n// Paired-Single Floating Point Arithmetic\nINS(ps_add, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 21), \"Paired Single Add\")\nINS(ps_div, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 18), \"Paired Single Divide\")\nINS(ps_mul, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 25), \"Paired Single Multiply\")\nINS(ps_sub, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 20), \"Paired Single Subtract\")\nINS(ps_abs, (frD), (frB), (rc), (opcd == 4, xo1 == 264, !_11_15), \"Paired Single Absolute\")\nINS(ps_nabs, (frD), (frB), (rc), (opcd == 4, xo1 == 136, !_11_15), \"Paired Single Negate Absolute\")\nINS(ps_neg, (frD), (frB), (rc), (opcd == 4, xo1 == 40, !_11_15), \"Paired Single Negate\")\nINS(ps_sel, (frD), (frA, frC, frB), (rc), (opcd == 4, xo4 == 23), \"Paired Single Select\")\nINS(ps_res, (frD, FPSCR), (frB), (rc), (opcd == 4, xo4 == 24), \"Paired Single Reciprocal\")\nINS(ps_rsqrte, (frD, FPSCR), (frB), (rc), (opcd == 4, xo4 == 26), \"Paired Single Reciprocal Square Root Estimate\")\nINS(ps_msub, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 28), \"Paired Single Multiply and Subtract\")\nINS(ps_madd, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 29), \"Paired Single Multiply and Add\")\nINS(ps_nmsub, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 30), \"Paired Single Negate Multiply and Subtract\")\nINS(ps_nmadd, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 31), \"Paired Single Negate Multiply and Add\")\nINS(ps_mr, (frD), (frB), (rc), (opcd == 4, xo1 == 72, !_11_15), \"Paired Single Move Register\")\nINS(ps_sum0, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 10), \"Paired Single Sum High\")\nINS(ps_sum1, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 11), \"Paired Single Sum Low\")\nINS(ps_muls0, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 12), \"Paired Single Multiply Scalar High\")\nINS(ps_muls1, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 13), \"Paired Single Multiply Scalar Low\")\nINS(ps_madds0, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 14), \"Paired Single Multiply and Add Scalar High\")\nINS(ps_madds1, (frD, FPSCR), (frA, frC, frB), (rc), (opcd == 4, xo4 == 15), \"Paired Single Multiply and Add Scalar Low\")\nINS(ps_cmpu0, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 0, !_9_10, !_31), \"Paired Single Compare Unordered High\")\nINS(ps_cmpo0, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 32, !_9_10, !_31), \"Paired Single Compare Ordered High\")\nINS(ps_cmpu1, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 64, !_9_10, !_31), \"Paired Single Compare Unordered Low\")\nINS(ps_cmpo1, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 96, !_9_10, !_31), \"Paired Single Compare Ordered Low\")\nINS(ps_merge00, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 528), \"Paired Single Merge High\")\nINS(ps_merge01, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 560), \"Paired Single Merge Direct\")\nINS(ps_merge10, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 592), \"Paired Single Merge Swapped\")\nINS(ps_merge11, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 624), \"Paired Single Merge Low\")\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_instruction_fields.inl",
    "content": "FLD(aa, 30, 30)\nFLD(bd, 16, 29)\nFLD(bi, 11, 15)\nFLD(bo, 6, 10)\nFLD(crbA, 11, 15)\nFLD(crbB, 16, 20)\nFLD(crbD, 6, 10)\nFLD(crfD, 6, 8)\nFLD(crfS, 11, 13)\nFLD(crm, 12, 19)\nFLD(d, 16, 31)\nFLD(fm, 7, 14)\nFLD(frA, 11, 15)\nFLD(frB, 16, 20)\nFLD(frC, 21, 25)\nFLD(frD, 6, 10)\nFLD(frS, 6, 10)\nFLD(frc, 31, 31) // Special version of rc for float operations\nFLD(i, 17, 19)\nFLD(imm, 16, 19)\nFLD(kcn, 6, 30)\nFLD(l, 10, 10)\nFLD(li, 6, 29)\nFLD(lk, 31, 31)\nFLD(mb, 21, 25)\nFLD(me, 26, 30)\nFLD(nb, 16, 20)\nFLD(oe, 21, 21)\nFLD(opcd, 0, 5)\nFLD(qd, 20, 31)\nFLD(qi, 22, 24)\nFLD(qw, 21, 21)\nFLD(rA, 11, 15)\nFLD(rB, 16, 20)\nFLD(rc, 31, 31)\nFLD(rD, 6, 10)\nFLD(rS, 6, 10)\nFLD(sh, 16, 20)\nFLD(simm, 16, 31)\nFLD(sr, 12, 15)\nFLD(spr, 11, 20)\nFLD(to, 6, 10)\nFLD(tbr, 11, 20)\nFLD(uimm, 16, 31)\nFLD(w, 16, 16)\nFLD(xo1, 21, 30)\nFLD(xo2, 22, 30)\nFLD(xo3, 25, 30)\nFLD(xo4, 26, 30)\n\nFLD(_6, 6, 6)\nFLD(_6_9, 6, 9)\nFLD(_6_10, 6, 10)\nFLD(_9, 9, 9)\nFLD(_9_10, 9, 10)\nFLD(_11, 11, 11)\nFLD(_11_15, 11, 15)\nFLD(_14_15, 14, 15)\nFLD(_15, 15, 15)\nFLD(_16_20, 16, 20)\nFLD(_16_29, 16, 29)\nFLD(_20, 20, 20)\nFLD(_20_26, 20, 26)\nFLD(_21, 21, 21)\nFLD(_21_25, 21, 25)\nFLD(_30, 30, 30)\nFLD(_31, 31, 31)\n\n// Marker Fields\nMRKR(PS)\nMRKR(XERO)\nMRKR(XERSO)\nMRKR(XERC)\nMRKR(CR0)\nMRKR(CR1)\nMRKR(FCRISI)\nMRKR(FCRIDI)\nMRKR(FCRSNAN)\nMRKR(FCRZDZ)\nMRKR(FPRF)\nMRKR(AOE)\nMRKR(ARC)\nMRKR(LR)\nMRKR(CTR)\nMRKR(FPSCR) // TO BE REMOVED\nMRKR(RSRV)\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_instructionid.h",
    "content": "#pragma once\n\nnamespace espresso\n{\n\n#define INS(x, ...) x,\nenum class InstructionID\n{\n#  include \"espresso_instruction_definitions.inl\"\n   Invalid,\n   InstructionCount\n};\n#undef INS\n\n} // namespace espresso\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_instructionset.cpp",
    "content": "#include \"espresso_instructionset.h\"\n#include \"espresso_spr.h\"\n#include <common/bitutils.h>\n#include <common/decaf_assert.h>\n#include <algorithm>\n\nnamespace espresso\n{\n\nstruct TableEntry\n{\n   struct FieldMap\n   {\n      InstructionField field;\n      std::vector<TableEntry> children;\n   };\n\n   void\n   addInstruction(InstructionField field, uint32_t value, InstructionInfo *instrInfo)\n   {\n      auto fieldMap = getFieldMap(field);\n      decaf_check(fieldMap);\n      decaf_check(value < fieldMap->children.size());\n      fieldMap->children[value].instr = instrInfo;\n   }\n\n   void\n   addTable(InstructionField field)\n   {\n      if (!getFieldMap(field)) {\n         auto fieldMap = FieldMap {};\n         auto size = 1 << getInstructionFieldWidth(field);\n         fieldMap.field = field;\n         fieldMap.children.resize(size);\n         fieldMaps.emplace_back(fieldMap);\n      }\n   }\n\n   TableEntry *\n   getEntry(InstructionField field, uint32_t value)\n   {\n      auto fieldMap = getFieldMap(field);\n      decaf_check(fieldMap);\n      decaf_check(value < fieldMap->children.size());\n      return &fieldMap->children[value];\n   }\n\n   FieldMap *\n   getFieldMap(InstructionField field)\n   {\n      for (auto &fieldMap : fieldMaps) {\n         if (fieldMap.field == field) {\n            return &fieldMap;\n         }\n      }\n\n      return nullptr;\n   }\n\n   InstructionInfo *instr = nullptr;\n   std::vector<FieldMap> fieldMaps;\n};\n\nstatic std::vector<InstructionInfo>\nsInstructionInfo;\n\nstatic std::vector<InstructionAlias>\nsAliasData;\n\nstatic TableEntry\nsInstructionTable;\n\n#define FLD(x, y, z, ...) {y, z},\n#define MRKR(x, ...) {-1, -1},\nstatic std::pair<int, int>\nsFieldBits[] = {\n   { -1, -1 },\n#include \"espresso_instruction_fields.inl\"\n};\n#undef FLD\n#undef MRKR\n\n#define FLD(x, ...) #x,\n#define MRKR(x, ...) #x,\nstatic const char *\nsFieldNames[] = {\n#include \"espresso_instruction_fields.inl\"\n};\n#undef FLD\n#undef MRKR\n\n// Returns true if InstructionField is a marker only field\nbool\nisInstructionFieldMarker(InstructionField field)\n{\n   return sFieldBits[static_cast<int>(field)].first == -1;\n}\n\n// Returns true if instruction is a branch instruction\nbool\nisBranchInstruction(InstructionID id)\n{\n   return id == InstructionID::b\n      || id == InstructionID::bc\n      || id == InstructionID::bcctr\n      || id == InstructionID::bclr;\n}\n\n// Get name of InstructionField\nconst char *\ngetInstructionFieldName(InstructionField field)\n{\n   return sFieldNames[static_cast<int>(field)];\n}\n\n// First bit of instruction field\nuint32_t\ngetInstructionFieldStart(InstructionField field)\n{\n   return 31 - sFieldBits[static_cast<int>(field)].second;\n}\n\n// Last bit of instruction field\nuint32_t\ngetInstructionFieldEnd(InstructionField field)\n{\n   return 31 - sFieldBits[static_cast<int>(field)].first;\n}\n\n// Width of instruction field in bits\nuint32_t\ngetInstructionFieldWidth(InstructionField field)\n{\n   auto end = getInstructionFieldEnd(field);\n   auto start = getInstructionFieldStart(field);\n   return end - start + 1;\n}\n\n// Absolute bitmask of instruction field\nuint32_t\ngetInstructionFieldBitmask(InstructionField field)\n{\n   auto end = getInstructionFieldEnd(field);\n   auto start = getInstructionFieldStart(field);\n   return make_bitmask(start, end);\n}\n\nuint32_t\ngetInstructionFieldValue(const InstructionField& field, Instruction instr)\n{\n   if (field == InstructionField::spr) {\n      return static_cast<uint32_t>(decodeSPR(instr));\n   } else {\n      auto mask = getInstructionFieldBitmask(field);\n      auto start = getInstructionFieldStart(field);\n\n      return (instr & mask) >> start;\n   }\n}\n\nSPR\ndecodeSPR(Instruction instr)\n{\n   return static_cast<SPR>(((instr.spr << 5) & 0x3E0) | ((instr.spr >> 5) & 0x1F));\n}\n\nvoid\nencodeSPR(Instruction &instr, SPR spr)\n{\n   auto sprInt = (uint32_t)spr;\n   instr.spr = ((sprInt << 5) & 0x3E0) | ((sprInt >> 5) & 0x1F);\n}\n\n// Decode Instruction to InstructionInfo\nInstructionInfo *\ndecodeInstruction(Instruction instr)\n{\n   auto table = &sInstructionTable;\n\n   while (table) {\n      for (auto &fieldMap : table->fieldMaps) {\n         auto value = getInstructionFieldValue(fieldMap.field, instr);\n         table = &fieldMap.children[value];\n\n         if (table->instr || table->fieldMaps.size()) {\n            break;\n         }\n      }\n\n      if (table->fieldMaps.size() == 0) {\n         return table->instr;\n      }\n   }\n\n   return nullptr;\n}\n\n// Encode specified instruction\nInstruction\nencodeInstruction(InstructionID id)\n{\n   auto &data = sInstructionInfo[static_cast<size_t>(id)];\n   auto instr = 0u;\n\n   for (auto &op : data.opcode) {\n      auto field = op.field;\n      auto value = op.value;\n      auto start = getInstructionFieldStart(field);\n\n      instr |= value << start;\n   }\n\n   return instr;\n}\n\n// Find InstructionInfo for InstructionID\nInstructionInfo *\nfindInstructionInfo(InstructionID instrId)\n{\n   return &sInstructionInfo[static_cast<size_t>(instrId)];\n}\n\n// Find any alias which matches instruction\nInstructionAlias *\nfindInstructionAlias(InstructionInfo *info, Instruction instr)\n{\n   for (auto &alias : sAliasData) {\n      if (alias.id != info->id) {\n         // Not an alias for this field\n         continue;\n      }\n\n      auto opMatch = std::all_of(alias.opcode.begin(), alias.opcode.end(), [instr](const auto &op) {\n         auto x = getInstructionFieldValue(op.field, instr);\n         auto y = op.value;\n\n         if (op.field2 != InstructionField::Invalid) {\n            y = getInstructionFieldValue(op.field2, instr);\n         }\n\n         return x == y;\n      });\n\n      if (opMatch) {\n         return &alias;\n      }\n   }\n\n   return nullptr;\n}\n\n// Check if instruction is a certain instruction\nbool\nisA(InstructionID id, Instruction instr)\n{\n   const auto &data = sInstructionInfo[static_cast<size_t>(id)];\n\n   return std::all_of(data.opcode.begin(), data.opcode.end(), [instr](const auto &op) {\n      auto field = op.field;\n      auto value = op.value;\n      auto start = getInstructionFieldStart(field);\n      auto mask = getInstructionFieldBitmask(field);\n\n      return ((instr.value & mask) >> start) == value;\n   });\n}\n\n// Initialise instructionTable\nstatic void\ninitialiseInstructionTable()\n{\n   for (auto &instr : sInstructionInfo) {\n      TableEntry *table = &sInstructionTable;\n\n      // Resolve opcodes\n      for (auto i = 0u; i < instr.opcode.size() - 1; ++i) {\n         auto field = instr.opcode[i].field;\n         auto value = instr.opcode[i].value;\n         table->addTable(field);\n         table = table->getEntry(field, value);\n      }\n\n      // Add the actual instruction entry\n      auto field = instr.opcode.back().field;\n      auto value = instr.opcode.back().value;\n      table->addTable(field);\n      table->addInstruction(field, value, &instr);\n   }\n}\n\nstatic std::string\ncleanInsName(const std::string& name)\n{\n   if (name[name.size() - 1] == '_') {\n      return name.substr(0, name.size() - 1);\n   }\n\n   return name;\n}\n\n/*\n * FieldIndex is used to generate the decoding rules.\n *\n * For example the add instruction is defined as:\n * INS(add, (rD), (rA, rB), (oe, rc), (opcd == 31, xo2 == 266), \"Add\")\n *\n * Here both opcd and xo2 will be defined as a FieldIndex, and then during\n * initialiseInstructionSet they will use FieldIndex::operator== below to generate\n * an InstructionOpcode which we then use to setup the instruction table decoding.\n */\nstruct FieldIndex\n{\n   FieldIndex(InstructionField _id) : id(_id)\n   {\n   }\n\n   operator InstructionField() const\n   {\n      return id;\n   }\n\n   InstructionOpcode\n   operator==(const int &other) const\n   {\n      return InstructionOpcode(id, other);\n   }\n\n   InstructionOpcode\n   operator==(const FieldIndex &other) const\n   {\n      return InstructionOpcode(id, other.id);\n   }\n\n   InstructionOpcode\n   operator!() const\n   {\n      return InstructionOpcode(id, 0);\n   }\n\n   InstructionField id;\n};\n\n// Create FieldIndex to match the field names we use inside the spec files\n#define FLD(x, ...)  static const FieldIndex x(InstructionField::x);\n#define MRKR(x, ...) static const FieldIndex x(InstructionField::x);\n#include \"espresso_instruction_fields.inl\"\n#undef FLD\n#undef MRKR\n\n#define PRINTOPS(...) __VA_ARGS__\n\n// Used to populate sInstructionInfo\n#define INS(name, write, read, flags, opcodes, fullname) \\\n   static const InstructionInfo \\\n   info_ ## name = InstructionInfo { \\\n                      InstructionID::name, cleanInsName(#name), fullname, \\\n                      { PRINTOPS opcodes }, { PRINTOPS read }, \\\n                      { PRINTOPS write }, { PRINTOPS flags } \\\n                   };\n\n// Used to populate sInstructionAlias\n#define INSA(name, op, opcodes) \\\n   static const InstructionAlias \\\n   alias_ ## name = InstructionAlias { \\\n                       #name, InstructionID::op, \\\n                       { PRINTOPS opcodes } \\\n                    };\n\n#  include \"espresso_instruction_definitions.inl\"\n#  include \"espresso_instruction_aliases.inl\"\n\n#undef INS\n#undef INSA\n\n#define INS(name, ...) sInstructionInfo.emplace_back(info_ ## name);\n#define INSA(name, ...) sAliasData.emplace_back(alias_ ## name);\n\nvoid\ninitialiseInstructionSet()\n{\n   // Populate sInstructionInfo\n#  include \"espresso_instruction_definitions.inl\"\n\n   // Populate sInstructionAlias\n#  include \"espresso_instruction_aliases.inl\"\n\n   // Create instruction table\n   initialiseInstructionTable();\n};\n\n#undef INS\n#undef INSA\n\n} // namespace espresso\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_instructionset.h",
    "content": "#pragma once\n#include <cstdint>\n#include <string>\n#include <vector>\n#include \"espresso_instruction.h\"\n#include \"espresso_instructionid.h\"\n\nnamespace espresso\n{\n\n#define FLD(x, ...) x,\n#define MRKR(x, ...) x,\nenum class InstructionField : uint32_t\n{\n   Invalid,\n#  include \"espresso_instruction_fields.inl\"\n   FieldCount,\n};\n#undef FLD\n#undef MRKR\n\nstruct InstructionOpcode\n{\n   InstructionOpcode()\n   {\n   }\n\n   InstructionOpcode(InstructionField field_, uint32_t value_) :\n      field(field_),\n      value(value_)\n   {\n   }\n\n   InstructionOpcode(InstructionField field_, InstructionField field2_) :\n      field(field_),\n      field2(field2_)\n   {\n   }\n\n   InstructionField field = InstructionField::Invalid;\n   InstructionField field2 = InstructionField::Invalid;\n   uint32_t value = 0;\n};\n\nstruct InstructionInfo\n{\n   InstructionID id;\n   std::string name;\n   std::string fullname;\n   std::vector<InstructionOpcode> opcode;\n   std::vector<InstructionField> read;\n   std::vector<InstructionField> write;\n   std::vector<InstructionField> flags;\n};\n\nstruct InstructionAlias\n{\n   std::string name;\n   InstructionID id;\n   std::vector<InstructionOpcode> opcode;\n};\n\nvoid\ninitialiseInstructionSet();\n\nInstructionInfo *\ndecodeInstruction(Instruction instr);\n\nInstruction\nencodeInstruction(InstructionID id);\n\nInstructionInfo *\nfindInstructionInfo(InstructionID instrId);\n\nInstructionAlias *\nfindInstructionAlias(InstructionInfo *info, Instruction instr);\n\nbool\nisA(InstructionID id, Instruction instr);\n\ntemplate<InstructionID id>\nbool\nisA(Instruction instr)\n{\n   return isA(id, instr);\n}\n\nbool\nisInstructionFieldMarker(InstructionField field);\n\nbool\nisBranchInstruction(InstructionID id);\n\nconst char *\ngetInstructionFieldName(InstructionField field);\n\nuint32_t\ngetInstructionFieldStart(InstructionField field);\n\nuint32_t\ngetInstructionFieldEnd(InstructionField field);\n\nuint32_t\ngetInstructionFieldWidth(InstructionField field);\n\nuint32_t\ngetInstructionFieldBitmask(InstructionField field);\n\n} // namespace espresso\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_registerformats.h",
    "content": "#pragma once\n#include <cstdint>\n#include <common/enum_start.inl>\n\nnamespace espresso\n{\n\n// General Purpose Integer Registers\nusing Register = uint32_t;\n\n\n/**\n * Condition Register\n */\nunion ConditionRegister\n{\n   uint32_t value;\n\n   struct\n   {\n      uint32_t cr7 : 4;\n      uint32_t cr6 : 4;\n      uint32_t cr5 : 4;\n      uint32_t cr4 : 4;\n      uint32_t cr3 : 4;\n      uint32_t cr2 : 4;\n      uint32_t cr1 : 4;\n      uint32_t cr0 : 4;\n   };\n};\n\n/**\n * Condition Register Flags\n *\n * Value of each cr field cr0...cr7\n */\nFLAGS_BEG(ConditionRegisterFlag, uint32_t)\n   FLAGS_VALUE(SummaryOverflow,                    1 << 0)\n   FLAGS_VALUE(Zero,                               1 << 1)\n   FLAGS_VALUE(Positive,                           1 << 2)\n   FLAGS_VALUE(Negative,                           1 << 3)\n\n   FLAGS_VALUE(Unordered,                          SummaryOverflow)\n   FLAGS_VALUE(Equal,                              Zero)\n   FLAGS_VALUE(GreaterThan,                        Positive)\n   FLAGS_VALUE(LessThan,                           Negative)\n\n   FLAGS_VALUE(FloatingPointOverflowException,     SummaryOverflow)\n   FLAGS_VALUE(FloatingPointInvalidException,      Zero)\n   FLAGS_VALUE(FloatingPointExceptionEnabled,      Positive)\n   FLAGS_VALUE(FloatingPointException,             Negative)\nFLAGS_END(ConditionRegisterFlag)\n\n\n/**\n * Floating-Point Registers\n */\nunion alignas(16) FloatingPointRegister\n{\n   struct\n   {\n      double value;\n   };\n\n   struct\n   {\n      double paired0;  // Retains precision of a loaded double value.\n      double paired1;\n   };\n\n   struct\n   {\n      uint64_t idw;\n      uint64_t idw_paired1;\n   };\n\n   struct\n   {\n      uint32_t iw1;\n      uint32_t iw0;\n   };\n};\n\n\n/**\n * Floating-Point Status and Control Register\n */\nunion FloatingPointStatusAndControlRegister\n{\n   uint32_t value;\n\n   struct\n   {\n      uint32_t : 12;\n      uint32_t fpcc : 4;\n      uint32_t : 12;\n      uint32_t cr1 : 4;\n   };\n\n   struct\n   {\n      uint32_t rn : 2;     // FP Rounding Control\n      uint32_t ni : 1;     // FP non-IEEE mode\n      uint32_t xe : 1;     // FP Inexact Exception Enable\n      uint32_t ze : 1;     // IEEE FP Zero Divide Exception Enable\n      uint32_t ue : 1;     // IEEE FP Underflow Exception Enable\n      uint32_t oe : 1;     // IEEE FP Overflow Exception Enable\n      uint32_t ve : 1;     // FP Invalid Operation Exception Enable\n      uint32_t vxcvi : 1;  // FP Invalid Operation Exception for Invalid Integer Convert\n      uint32_t vxsqrt : 1; // FP Invalid Operation Exception for Invalid Square Root\n      uint32_t vxsoft : 1; // FP Invalid Operation Exception for Software Request\n      uint32_t : 1;\n      uint32_t fprf : 5;   // FP Result Flags\n      uint32_t fi : 1;     // FP Fraction Inexact\n      uint32_t fr : 1;     // FP Fraction Rounded\n      uint32_t vxvc : 1;   // FP Invalid Operation Exception for Invalid Compare\n      uint32_t vximz : 1;  // FP Invalid Operation Exception for Inf*0\n      uint32_t vxzdz : 1;  // FP Invalid Operation Exception for 0/0\n      uint32_t vxidi : 1;  // FP Invalid Operation Exception for Inf/Inf\n      uint32_t vxisi : 1;  // FP Invalid Operation Exception for Inf-Inf\n      uint32_t vxsnan : 1; // FP Invalid Operation Exception for SNaN\n      uint32_t xx : 1;     // FP Inexact Exception\n      uint32_t zx : 1;     // FP Zero Divide Exception\n      uint32_t ux : 1;     // FP Underflow Exception\n      uint32_t ox : 1;     // FP Overflow Exception\n      uint32_t vx : 1;     // FP Invalid Operation Exception Summary\n      uint32_t fex : 1;    // FP Enabled Exception Summary\n      uint32_t fx : 1;     // FP Exception Summary\n   };\n};\n\n/**\n * Floating-Point Status and Control Register Flags\n */\nFLAGS_BEG(FpscrFlags, uint32_t)\n   FLAGS_VALUE(VXCVI,                              1u << 8)\n   FLAGS_VALUE(VXSQRT,                             1u << 9)\n   FLAGS_VALUE(VXSOFT,                             1u << 10)\n\n   FLAGS_VALUE(VXVC,                               1u << 19)\n   FLAGS_VALUE(VXIMZ,                              1u << 20)\n   FLAGS_VALUE(VXZDZ,                              1u << 21)\n   FLAGS_VALUE(VXIDI,                              1u << 22)\n   FLAGS_VALUE(VXISI,                              1u << 23)\n   FLAGS_VALUE(VXSNAN,                             1u << 24)\n   FLAGS_VALUE(XX,                                 1u << 25)\n   FLAGS_VALUE(ZX,                                 1u << 26)\n   FLAGS_VALUE(UX,                                 1u << 27)\n   FLAGS_VALUE(OX,                                 1u << 28)\n   FLAGS_VALUE(VX,                                 1u << 29)\n   FLAGS_VALUE(FEX,                                1u << 30)\n   FLAGS_VALUE(FX,                                 1u << 31)\n\n   FLAGS_VALUE(AllVX,            VXSNAN | VXISI | VXIDI | VXZDZ | VXIMZ |\n                                 VXVC | VXSOFT | VXSQRT | VXCVI)\n   FLAGS_VALUE(AllExceptions,    AllVX | OX | UX | ZX | XX)\nENUM_END(FpscrFlags)\n\n/**\n * Floating-Point Result Flags\n *\n * Value of fpscr.fprf\n */\nFLAGS_BEG(FloatingPointResultFlags, uint32_t)\n   FLAGS_VALUE(NaN,                                1 << 0)\n   FLAGS_VALUE(Zero,                               1 << 1)\n   FLAGS_VALUE(Positive,                           1 << 2)\n   FLAGS_VALUE(Negative,                           1 << 3)\n   FLAGS_VALUE(ClassDescriptor,                    1 << 4)\n\n   FLAGS_VALUE(Unordered,                          NaN)\n   FLAGS_VALUE(Equal,                              Zero)\n   FLAGS_VALUE(GreaterThan,                        Positive)\n   FLAGS_VALUE(LessThan,                           Negative)\nFLAGS_END(FloatingPointResultFlags)\n\n/**\n * Floating-Point Rounding Mode\n *\n * Value of fpscr.rn\n */\nENUM_BEG(FloatingPointRoundMode, uint32_t)\n   ENUM_VALUE(Nearest,                             0)\n   ENUM_VALUE(Zero,                                1)\n   ENUM_VALUE(Positive,                            2)\n   ENUM_VALUE(Negative,                            3)\nENUM_END(FloatingPointRoundMode)\n\n\n/**\n * Graphics Quantization Registers\n */\nunion GraphicsQuantisationRegister\n{\n   uint32_t value;\n\n   struct\n   {\n      uint32_t st_type : 3;\n      uint32_t : 5;\n      uint32_t st_scale : 6;\n      uint32_t : 2;\n      uint32_t ld_type : 3;\n      uint32_t : 5;\n      uint32_t ld_scale : 6;\n      uint32_t : 2;\n   };\n};\n\n\n/**\n * Graphics Quantization Registers Quantized Data Type\n *\n * Value of gqr.st_type / gqr.ld_type\n */\nENUM_BEG(QuantizedDataType, uint32_t)\n   ENUM_VALUE(Floating,                            0)\n   ENUM_VALUE(Unsigned8,                           4)\n   ENUM_VALUE(Unsigned16,                          5)\n   ENUM_VALUE(Signed8,                             6)\n   ENUM_VALUE(Signed16,                            7)\nENUM_END(QuantizedDataType)\n\n\n/**\n * Machine State Register\n */\nunion MachineStateRegister\n{\n   uint32_t value;\n\n   struct\n   {\n      uint32_t le : 1;  // Little-endian mode enabled\n      uint32_t ri : 1;  // Exception is recoverable\n      uint32_t : 2;\n      uint32_t dr : 1;  // Data address translation enabled\n      uint32_t ir : 1;  // Instruction address translation enabled\n      uint32_t ip : 1;  // Exception prefix\n      uint32_t : 1;\n      uint32_t fe1 : 1; // Floating-point exception mode 1\n      uint32_t be : 1;  // Branch trace enabled\n      uint32_t se : 1;  // Single-step trace enabled\n      uint32_t fe0 : 1; // Floating-point exception mode 0\n      uint32_t me : 1;  // Machine check enabled\n      uint32_t fp : 1;  // Floating-point available\n      uint32_t pr : 1;  // Privelege level (0 = supervisor, 1 = user)\n      uint32_t ee : 1;  // External interrupt enabled\n      uint32_t ile : 1; // Exception little-endian mode\n      uint32_t : 1;\n      uint32_t pow : 1; // Power management enabled\n      uint32_t : 13;\n   };\n};\n\n\n/**\n * Processor Version Register\n */\nunion ProcessorVersionRegister\n{\n   uint32_t value;\n\n   struct\n   {\n      uint32_t revision : 16;\n      uint32_t version : 16;\n   };\n};\n\n\n/**\n * Fixed Point Exception Register\n */\nunion FixedPointExceptionRegister\n{\n   uint32_t value;\n\n   struct\n   {\n      // Byte count for lmwx, stmwx\n      uint32_t byteCount : 7;\n\n      uint32_t : 22;\n\n      //! Carry\n      uint32_t ca : 1;\n\n      //! Overflow\n      uint32_t ov : 1;\n\n      //! Sticky overflow\n      uint32_t so : 1;\n   };\n\n   struct\n   {\n      uint32_t : 28;\n      uint32_t crxr : 4;\n   };\n};\n\n} // namespace espresso\n\n#include <common/enum_end.inl>\n"
  },
  {
    "path": "src/libcpu/espresso/espresso_spr.h",
    "content": "#pragma once\n#include \"espresso_instruction.h\"\n\nnamespace espresso\n{\n\nenum class SPR\n{\n   XER = 0x1,\n   LR = 0x8,\n   CTR = 0x9,\n   DSISR = 0x12,\n   DAR = 0x13,\n   DEC = 0x16,\n   SDR1 = 0x19,\n   SRR0 = 0x1A,\n   SRR1 = 0x1B,\n   UTBL = 0x10C,\n   UTBU = 0x10D,\n   SPRG0 = 0x110,\n   SPRG1 = 0x111,\n   SPRG2 = 0x112,\n   SPRG3 = 0x113,\n   EAR = 0x11A,\n   TBL = 0x11C,\n   TBU = 0x11D,\n   PVR = 0x11F,\n   IBAT0U = 0x210,\n   IBAT0L = 0x211,\n   IBAT1U = 0x212,\n   IBAT1L = 0x213,\n   IBAT2U = 0x214,\n   IBAT2L = 0x215,\n   IBAT3U = 0x216,\n   IBAT3L = 0x217,\n   DBAT0U = 0x218,\n   DBAT0L = 0x219,\n   DBAT1U = 0x21A,\n   DBAT1L = 0x21B,\n   DBAT2U = 0x21C,\n   DBAT2L = 0x21D,\n   DBAT3U = 0x21E,\n   DBAT3L = 0x21F,\n   IBAT4U = 0x230,\n   IBAT4L = 0x231,\n   IBAT5U = 0x232,\n   IBAT5L = 0x233,\n   IBAT6U = 0x234,\n   IBAT6L = 0x235,\n   IBAT7U = 0x236,\n   IBAT7L = 0x237,\n   DBAT4U = 0x238,\n   DBAT4L = 0x239,\n   DBAT5U = 0x23A,\n   DBAT5L = 0x23B,\n   DBAT6U = 0x23C,\n   DBAT6L = 0x23D,\n   DBAT7U = 0x23E,\n   DBAT7L = 0x23F,\n   UGQR0 = 0x380,\n   UGQR1 = 0x381,\n   UGQR2 = 0x382,\n   UGQR3 = 0x383,\n   UGQR4 = 0x384,\n   UGQR5 = 0x385,\n   UGQR6 = 0x386,\n   UGQR7 = 0x387,\n   UHID2 = 0x388,\n   UWPAR = 0x389,\n   UDMAU = 0x38A,\n   UDMAL = 0x38B,\n   GQR0 = 0x390,\n   GQR1 = 0x391,\n   GQR2 = 0x392,\n   GQR3 = 0x393,\n   GQR4 = 0x394,\n   GQR5 = 0x395,\n   GQR6 = 0x396,\n   GQR7 = 0x397,\n   HID2 = 0x398,\n   WPAR = 0x399,\n   DMA_U = 0x39A,\n   DMA_L = 0x39B,\n   UMMCR0 = 0x3A8,\n   UPMC1 = 0x3A9,\n   UPMC2 = 0x3AA,\n   USIA = 0x3AB,\n   UMMCR1 = 0x3AC,\n   UPMC3 = 0x3AD,\n   UPMC4 = 0x3AE,\n   HID5 = 0x3B0,\n   PCSR = 0x3B2,\n   SCR = 0x3B3,\n   CAR = 0x3B4,\n   BCR = 0x3B5,\n   WPSAR = 0x3B6,\n   MMCR0 = 0x3B8,\n   PMC1 = 0x3B9,\n   PMC2 = 0x3BA,\n   SIA = 0x3BB,\n   MMCR1 = 0x3BC,\n   PMC3 = 0x3BD,\n   PMC4 = 0x3BE,\n   DCATE = 0x3D0,\n   DCATR = 0x3D1,\n   DMATL0 = 0x3D8,\n   DMATU0 = 0x3D9,\n   DMATR0 = 0x3DA,\n   DMATL1 = 0x3DB,\n   DMATU1 = 0x3DC,\n   DMATR1 = 0x3DD,\n   UPIR = 0x3EF,\n   HID0 = 0x3F0,\n   HID1 = 0x3F1,\n   IABR = 0x3F2,\n   HID4 = 0x3F3,\n   TDCL = 0x3F4,\n   DABR = 0x3F5,\n   L2CR = 0x3F9,\n   TDCH = 0x3FA,\n   ICTC = 0x3FB,\n   THRM1 = 0x3FC,\n   THRM2 = 0x3FD,\n   THRM3 = 0x3FE,\n   PIR = 0x3FF,\n};\n\nSPR\ndecodeSPR(Instruction instr);\n\nvoid\nencodeSPR(Instruction &instr, SPR spr);\n\n} // namespace espresso\n"
  },
  {
    "path": "src/libcpu/functionpointer.h",
    "content": "#pragma once\n#include \"address.h\"\n\nnamespace cpu\n{\n\ntemplate<typename AddressType, typename FunctionType>\nstruct func_pointer_cast_impl;\n\ntemplate<typename AddressType, typename FunctionType>\nclass FunctionPointer;\n\ntemplate<typename AddressType, typename FunctionType>\nclass FunctionPointer\n{\npublic:\n   using function_type = FunctionType;\n\n   FunctionPointer() = default;\n   FunctionPointer(const FunctionPointer &other) = default;\n   FunctionPointer(FunctionPointer &&other) = default;\n   FunctionPointer &operator=(const FunctionPointer &) = default;\n   FunctionPointer &operator=(FunctionPointer &&) = default;\n\n   /**\n   * Constructs a FunctionPointer from a nullptr\n   */\n   FunctionPointer(std::nullptr_t) :\n      mAddress(0)\n   {\n   }\n\n   AddressType getAddress() const\n   {\n      return mAddress;\n   }\n\n   explicit operator bool() const\n   {\n      return static_cast<bool>(mAddress);\n   }\n\n   FunctionPointer &\n   operator =(std::nullptr_t)\n   {\n      mAddress = AddressType { 0 };\n      return *this;\n   }\n\n   constexpr bool operator ==(std::nullptr_t) const\n   {\n      return !static_cast<bool>(mAddress);\n   }\n\n   constexpr bool operator == (const FunctionPointer &other) const\n   {\n      return mAddress == other.mAddress;\n   }\n\n   constexpr bool operator != (const FunctionPointer &other) const\n   {\n      return mAddress != other.mAddress;\n   }\n\nprotected:\n   template<typename, typename>\n   friend struct func_pointer_cast_impl;\n\n   AddressType mAddress;\n};\n\ntemplate<typename FunctionType>\nusing VirtualFunctionPointer = FunctionPointer<VirtualAddress, FunctionType>;\n\ntemplate<typename FunctionType>\nusing PhysicalFunctionPointer = FunctionPointer<PhysicalAddress, FunctionType>;\n\ntemplate<typename AddressType, typename FunctionType>\nstruct func_pointer_cast_impl\n{\n   using FunctionPointerType = FunctionPointer<AddressType, FunctionType>;\n\n   static constexpr AddressType cast(FunctionPointerType src)\n   {\n      return src.mAddress;\n   }\n\n   static constexpr FunctionPointerType cast(AddressType src)\n   {\n      FunctionPointerType dst;\n      dst.mAddress = src;\n      return dst;\n   }\n};\n\ntemplate<typename AddressType, typename FunctionType>\nstruct func_pointer_cast_impl<AddressType, FunctionPointer<AddressType, FunctionType>>\n{\n   using FunctionPointerType = FunctionPointer<AddressType, FunctionType>;\n\n   static constexpr AddressType cast(FunctionPointerType src)\n   {\n      return src.mAddress;\n   }\n\n   static constexpr FunctionPointerType cast(AddressType src)\n   {\n      FunctionPointerType dst;\n      dst.mAddress = src;\n      return dst;\n   }\n};\n\n} // namespace cpu\n\n// Custom formatters for fmtlib\nnamespace fmt\n{\n\ninline namespace v8\n{\ntemplate<typename Type, typename Char, typename Enabled>\nstruct formatter;\n}\n\n// Custom formatter defined in cpu_formatters.h\ntemplate<typename AddressType, typename FunctionType, typename Char>\nstruct formatter<cpu::FunctionPointer<AddressType, FunctionType>, Char, void>;\n\n} // namespace fmt\n"
  },
  {
    "path": "src/libcpu/jit_stats.h",
    "content": "#pragma once\n#include <array>\n#include <atomic>\n#include <common/platform.h>\n#include <cstdint>\n#include <gsl/gsl-lite.hpp>\n\n#ifdef PLATFORM_WINDOWS\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\n#endif\n\nnamespace cpu\n{\n\nnamespace jit\n{\n\n#ifdef PLATFORM_WINDOWS\nstruct CodeBlockUnwindInfo\n{\n   static constexpr size_t MaxUnwindInfoSize = 4 + 2 * 30 + 8;\n   std::array<uint8_t, MaxUnwindInfoSize> data;\n   uint32_t size;\n   RUNTIME_FUNCTION rtlFuncTable;\n};\n#else\nstruct CodeBlockUnwindInfo\n{\n};\n#endif\n\nstruct CodeBlockProfileData\n{\n   std::atomic<uint64_t> count;\n   std::atomic<uint64_t> time;\n};\n\nstruct CodeBlock\n{\n   //! Guest address of PPC code.\n   uint32_t address;\n\n   //! Host address of compiled code.\n   void *code;\n\n   //! Size of compiled code.\n   uint32_t codeSize;\n\n   //! Profiling data.\n   CodeBlockProfileData profileData;\n\n   //! Code block unwind info, only used on Windows.\n   CodeBlockUnwindInfo unwindInfo;\n};\n\nusing CodeBlockIndex = int32_t;\n\nstatic constexpr CodeBlockIndex CodeBlockIndexUncompiled = -1;\nstatic constexpr CodeBlockIndex CodeBlockIndexCompiling = -2;\nstatic constexpr CodeBlockIndex CodeBlockIndexError = -3;\n\nstruct JitStats\n{\n   uint64_t totalTimeInCodeBlocks = 0;\n   uint64_t usedCodeCacheSize = 0;\n   uint64_t usedDataCacheSize = 0;\n   gsl::span<CodeBlock> compiledBlocks;\n};\n\nbool\nsampleStats(JitStats &stats);\n\nvoid\nresetProfileStats();\n\nvoid\nsetProfilingMask(unsigned mask);\n\nunsigned\ngetProfilingMask();\n\n} // namespace jit\n\n} // namespace cpu"
  },
  {
    "path": "src/libcpu/mem.h",
    "content": "#pragma once\n#include <common/byte_swap.h>\n#include <common/decaf_assert.h>\n#include \"mmu.h\"\n\nusing ppcaddr_t = uint32_t;\n\nnamespace mem\n{\n\n// Translate WiiU virtual address to host address\ntemplate<typename Type = uint8_t>\ninline Type *\ntranslate(ppcaddr_t address)\n{\n   if (!address) {\n      return nullptr;\n   } else {\n      return reinterpret_cast<Type*>(cpu::getBaseVirtualAddress() + address);\n   }\n}\n\n// Translate host address to WiiU virtual address\ninline ppcaddr_t\nuntranslate(const void *ptr)\n{\n   if (!ptr) {\n      return 0;\n   }\n\n   auto sptr = reinterpret_cast<size_t>(ptr);\n   auto sbase = cpu::getBaseVirtualAddress();\n   decaf_check(sptr >= sbase);\n   decaf_check(sptr <= sbase + 0xFFFFFFFF);\n   return static_cast<ppcaddr_t>(sptr - sbase);\n}\n\n// Read Type from virtual address with no endian byte_swap\ntemplate<typename Type>\ninline Type\nreadNoSwap(ppcaddr_t address)\n{\n   return *reinterpret_cast<Type*>(translate(address));\n}\n\n// Read Type from virtual address\ntemplate<typename Type>\ninline Type\nread(ppcaddr_t address)\n{\n   return byte_swap(readNoSwap<Type>(address));\n}\n\n// Write Type to virtual address with no endian byte_swap\ntemplate<typename Type>\ninline void\nwriteNoSwap(ppcaddr_t address, Type value)\n{\n   *reinterpret_cast<Type*>(translate(address)) = value;\n}\n\n// Write Type to virtual address\ntemplate<typename Type>\ninline void\nwrite(ppcaddr_t address, Type value)\n{\n   writeNoSwap(address, byte_swap(value));\n}\n\n} // namespace mem\n"
  },
  {
    "path": "src/libcpu/memtrack.h",
    "content": "#pragma once\n#include \"address.h\"\n#include <common/decaf_assert.h>\n#include \"mmu.h\"\n\nnamespace cpu\n{\n\nnamespace internal\n{\n\nvoid\ninitialiseMemtrack();\n\nvoid\nregisterTrackedRange(VirtualAddress virtualAddress,\n                     PhysicalAddress physicalAddress,\n                     uint32_t size);\n\nvoid\nunregisterTrackedRange(VirtualAddress virtualAddress,\n                       uint32_t size);\n\nvoid\nclearTrackedRanges();\n\n} // namespace internal\n\nstruct MemtrackState\n{\n   bool operator==(const MemtrackState& other) const\n   {\n      return state == other.state;\n   }\n\n   bool operator!=(const MemtrackState& other) const\n   {\n      return state != other.state;\n   }\n\n   uint64_t state;\n};\n\nMemtrackState\ngetMemoryState(PhysicalAddress physicalAddress,\n               uint32_t size);\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/mmu.h",
    "content": "#pragma once\n#include \"address.h\"\n#include <common/decaf_assert.h>\n\nnamespace cpu\n{\n\nnamespace internal\n{\n\nextern uintptr_t BaseVirtualAddress;\nextern uintptr_t BasePhysicalAddress;\n\ntemplate<typename Value>\ninline Value *translate(VirtualAddress address)\n{\n   return reinterpret_cast<Value *>(BaseVirtualAddress + address.getAddress());\n}\n\ntemplate<typename Value>\ninline Value *translate(PhysicalAddress address)\n{\n   return reinterpret_cast<Value *>(BasePhysicalAddress + address.getAddress());\n}\n\n} // namespace internal\n\nenum class MapPermission\n{\n   ReadOnly,\n   ReadWrite,\n};\n\nenum class VirtualMemoryType\n{\n   Invalid,\n   MappedReadOnly,\n   MappedReadWrite,\n   Free,\n   Allocated,\n};\n\nenum class PhysicalMemoryType\n{\n   Invalid,\n   MEM0,\n   MEM1,\n   MEM2,\n   SRAM0,\n   SRAM1,\n   UNKRAM,\n   LockedCache,\n   TilingAperture,\n};\n\nconstexpr auto PageSize = uint32_t { 128 * 1024 };\n\nbool\ninitialiseMemory();\n\nbool\nallocateVirtualAddress(VirtualAddress virtualAddress,\n                       uint32_t size);\n\nbool\nfreeVirtualAddress(VirtualAddress virtualAddress,\n                   uint32_t size);\n\nVirtualAddressRange\nfindFreeVirtualAddress(uint32_t size,\n                       uint32_t align);\n\nVirtualAddressRange\nfindFreeVirtualAddressInRange(VirtualAddressRange range,\n                              uint32_t size,\n                              uint32_t align);\n\nbool\nmapMemory(VirtualAddress virtualAddress,\n          PhysicalAddress physicalAddress,\n          uint32_t size,\n          MapPermission permission);\n\nbool\nunmapMemory(VirtualAddress virtualAddress,\n            uint32_t size);\n\nbool\nresetVirtualMemory();\n\nVirtualMemoryType\nqueryVirtualAddress(VirtualAddress virtualAddress);\n\nbool\nisValidAddress(VirtualAddress address);\n\nbool\nvirtualToPhysicalAddress(VirtualAddress virtualAddress,\n                         PhysicalAddress &out);\n\ntemplate<typename Type>\ninline VirtualAddress\ntranslate(Type *pointer)\n{\n   if (!pointer) {\n      return VirtualAddress { 0u };\n   } else {\n      auto addr = reinterpret_cast<uintptr_t>(pointer);\n      decaf_check(addr >= internal::BaseVirtualAddress);\n      decaf_check(addr <= internal::BaseVirtualAddress + 0x100000000ull);\n      return VirtualAddress { static_cast<uint32_t>(addr - internal::BaseVirtualAddress) };\n   }\n}\n\ntemplate<typename Type>\ninline PhysicalAddress\ntranslatePhysical(Type *pointer)\n{\n   if (!pointer) {\n      return PhysicalAddress { 0u };\n   } else {\n      auto addr = reinterpret_cast<uintptr_t>(pointer);\n      decaf_check(addr >= internal::BasePhysicalAddress);\n      decaf_check(addr <= internal::BasePhysicalAddress + 0x100000000ull);\n      return PhysicalAddress { static_cast<uint32_t>(addr - internal::BasePhysicalAddress) };\n   }\n}\n\ninline uintptr_t\ngetBaseVirtualAddress()\n{\n   return internal::BaseVirtualAddress;\n}\n\ninline uintptr_t\ngetBasePhysicalAddress()\n{\n   return internal::BasePhysicalAddress;\n}\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/pointer.h",
    "content": "#pragma once\n#include \"address.h\"\n#include \"be2_val.h\"\n#include \"mmu.h\"\n\n#include <type_traits>\n\ntemplate<typename Type>\nstruct be2_struct;\n\ntemplate<typename Type, uint32_t Size>\nclass be2_array;\n\nnamespace cpu\n{\n\ntemplate<typename ValueType, typename AddressType>\nclass Pointer;\n\ntemplate<typename AddressType, typename FunctonType>\nclass FunctionPointer;\n\ntemplate<typename AddressType, typename SrcType, typename DstType, typename = void>\nstruct pointer_cast_impl;\n\ntemplate<typename>\nstruct is_big_endian_value : std::false_type { };\n\ntemplate<typename ValueType>\nstruct is_big_endian_value<be2_val<ValueType>> : std::true_type { };\n\ntemplate<typename>\nstruct is_cpu_pointer : std::false_type { };\n\ntemplate<typename ValueType, typename AddressType>\nstruct is_cpu_pointer<Pointer<ValueType, AddressType>> : std::true_type { };\n\ntemplate<typename>\nstruct is_cpu_address : std::false_type { };\n\ntemplate<typename AddressType>\nstruct is_cpu_address<Address<AddressType>> : std::true_type { };\n\ntemplate<typename>\nstruct is_cpu_func_pointer : std::false_type { };\n\ntemplate<typename AddressType, typename FunctionType>\nstruct is_cpu_func_pointer<FunctionPointer<AddressType, FunctionType>> : std::true_type { };\n\ntemplate <typename T, typename = void>\nstruct pointer_dereference_type;\n\ntemplate <typename T>\nstruct pointer_dereference_type<T,\n   typename std::enable_if<std::is_void<T>::value>::type>\n{\n   using type = T;\n};\n\n/*\n * 1 byte values do not need to have be2_val<> wrapped around them.\n */\ntemplate <typename T>\nstruct pointer_dereference_type<T,\n   typename std::enable_if<(std::is_arithmetic<T>::value\n                         || std::is_enum<T>::value)\n                         && sizeof(T) == 1>::type>\n{\n   using type = T;\n};\n\n/*\n * If Type is an arithmetic type, enum type, or cpu::Pointer<> type, then we\n * must dereference to a be2_val.\n */\ntemplate <typename T>\nstruct pointer_dereference_type<T,\n   typename std::enable_if<(std::is_arithmetic<T>::value\n                         || std::is_enum<T>::value\n                         || is_cpu_address<T>::value\n                         || is_cpu_pointer<T>::value\n                         || is_cpu_func_pointer<T>::value)\n                         && !std::is_const<T>::value\n                         && sizeof(T) != 1>::type>\n{\n   using type = be2_val<T>;\n};\n\n/*\n * const version of above\n */\ntemplate <typename T>\nstruct pointer_dereference_type<T,\n   typename std::enable_if<(std::is_arithmetic<T>::value\n                         || std::is_enum<T>::value\n                         || is_cpu_address<typename std::remove_const<T>::type>::value\n                         || is_cpu_pointer<typename std::remove_const<T>::type>::value\n                         || is_cpu_func_pointer<typename std::remove_const<T>::type>::value)\n                         && std::is_const<T>::value\n                         && sizeof(T) != 1>::type>\n{\n   using type = const be2_val<T>;\n};\n\n/*\n * If Type is an array then we must dereference to be2_array.\n */\ntemplate <typename T>\nstruct pointer_dereference_type<T,\n   typename std::enable_if<std::is_array<T>::value>::type>\n{\n   using type = be2_array<typename std::remove_all_extents<T>::type, std::extent<T>::value>;\n};\n\n/*\n * If Type is an class type, or union type, and NOT a cpu::Pointer<> type, then\n * we must dereference to be2_struct<Type>.\n */\ntemplate <typename T>\nstruct pointer_dereference_type<T,\n   typename std::enable_if<(std::is_class<T>::value || std::is_union<T>::value)\n                         && !is_cpu_pointer<T>::value\n                         && !is_cpu_address<T>::value\n                         && !is_cpu_func_pointer<T>::value\n                         && !std::is_array<T>::value>::type>\n{\n   using type = be2_struct<T>;\n};\n\n\n/*\n * Basically the same as pointer_dereference_type but does not wrap structs in\n * be2_struct.\n *\n * Used for pointer_get_type::type * Pointer::get()\n */\ntemplate <typename T, typename = void>\nstruct pointer_get_type;\n\ntemplate <typename T>\nstruct pointer_get_type<T,\n   typename std::enable_if<!(std::is_class<T>::value || std::is_union<T>::value)\n                         || is_cpu_pointer<T>::value\n                         || is_cpu_address<T>::value\n                         || is_cpu_func_pointer<T>::value\n                         || std::is_array<T>::value>::type>\n{\n   using type = typename pointer_dereference_type<T>::type;\n};\n\ntemplate <typename T>\nstruct pointer_get_type<T,\n   typename std::enable_if<(std::is_class<T>::value || std::is_union<T>::value)\n                         && !is_cpu_pointer<T>::value\n                         && !is_cpu_address<T>::value\n                         && !is_cpu_func_pointer<T>::value\n                         && !std::is_array<T>::value>::type>\n{\n   using type = T;\n};\n\ntemplate<typename ValueType, typename AddressType>\nclass Pointer\n{\npublic:\n   using value_type = ValueType;\n   using address_type = AddressType;\n   using dereference_type = typename pointer_dereference_type<value_type>::type;\n   using get_type = typename pointer_get_type<value_type>::type;\n\n   static_assert(!std::is_pointer<value_type>::value,\n                 \"cpu::Pointer should point to another cpu::Pointer, not a raw T* pointer.\");\n\n   static_assert(!is_big_endian_value<value_type>::value,\n                 \"be2_val should not be used as ValueType for pointer, it is implied implicitly.\");\n\n   static_assert(std::is_class<value_type>::value\n              || std::is_union<value_type>::value\n              || std::is_arithmetic<value_type>::value\n              || std::is_enum<value_type>::value\n              || std::is_void<value_type>::value\n              || std::is_array<value_type>::value,\n                 \"Invalid ValueType for Pointer\");\n\n   Pointer() = default;\n   Pointer(const Pointer &) = default;\n   Pointer(Pointer &&) = default;\n   Pointer &operator =(const Pointer &) = default;\n   Pointer &operator =(Pointer &&) = default;\n\n   // Pointer<Type>(nullptr)\n   Pointer(std::nullptr_t) :\n      mAddress(0)\n   {\n   }\n\n    // Pointer<const Type>(Pointer<Type>)\n   template<typename V = value_type>\n   Pointer(const Pointer<typename std::remove_const<V>::type, address_type> &other,\n           typename std::enable_if<std::is_const<V>::value>::type * = nullptr) :\n      mAddress(other.mAddress)\n   {\n   }\n\n   // Pointer<void>(Pointer<Type>)\n   template<typename O, typename V = value_type>\n   Pointer(const Pointer<O, address_type> &other,\n           typename std::enable_if<std::is_void<V>::value>::type * = nullptr) :\n      mAddress(other.mAddress)\n   {\n   }\n\n   // Pointer<void>(be2_val<Pointer<Type>>)\n   template<typename O, typename V = value_type>\n   Pointer(const be2_val<Pointer<O, address_type>> &other,\n           typename std::enable_if<std::is_void<V>::value>::type * = nullptr) :\n      mAddress(other.value().mAddress)\n   {\n   }\n\n   // Pointer<const Type> = const Pointer<Type> &\n   template<typename V = value_type>\n   typename std::enable_if<std::is_const<V>::value, Pointer>::type &\n   operator =(const Pointer<typename std::remove_const<V>::type, address_type> &other)\n   {\n      mAddress = other.mAddress;\n      return *this;\n   }\n\n   get_type *get() const\n   {\n      return internal::translate<dereference_type>(mAddress);\n   }\n\n   value_type *getRawPointer() const\n   {\n      return internal::translate<value_type>(mAddress);\n   }\n\n   explicit operator bool() const\n   {\n      return static_cast<bool>(mAddress);\n   }\n\n   explicit operator Pointer<void, address_type>() const\n   {\n      return { *this };\n   }\n\n   Pointer &\n   operator =(std::nullptr_t)\n   {\n      mAddress = AddressType { 0u };\n      return *this;\n   }\n\n   template<typename K = value_type>\n   typename std::enable_if<!std::is_void<K>::value, dereference_type>::type &\n   operator *() const\n   {\n      return *internal::translate<dereference_type>(mAddress);\n   }\n\n   template<typename K = value_type>\n   typename std::enable_if<!std::is_void<K>::value, dereference_type>::type *\n   operator ->() const\n   {\n      return internal::translate<dereference_type>(mAddress);\n   }\n\n   template<typename K = value_type>\n   typename std::enable_if<!std::is_void<K>::value, dereference_type>::type &\n   operator [](size_t index) const\n   {\n      return internal::translate<dereference_type>(mAddress)[index];\n   }\n\n   constexpr bool operator == (std::nullptr_t) const\n   {\n      return !static_cast<bool>(mAddress);\n   }\n\n   template<typename O>\n   constexpr bool operator == (const Pointer<O, address_type> &other) const\n   {\n      return mAddress == other.mAddress;\n   }\n\n   template<typename O>\n   constexpr bool operator == (const be2_val<Pointer<O, address_type>> &other) const\n   {\n      return mAddress == other.value().mAddress;\n   }\n\n   constexpr bool operator != (std::nullptr_t) const\n   {\n      return static_cast<bool>(mAddress);\n   }\n\n   template<typename O>\n   constexpr bool operator != (const Pointer<O, address_type> &other) const\n   {\n      return mAddress != other.mAddress;\n   }\n\n   template<typename O>\n   constexpr bool operator != (const be2_val<Pointer<O, address_type>> &other) const\n   {\n      return mAddress != other.value().mAddress;\n   }\n\n   template<typename O>\n   constexpr bool operator >= (const Pointer<O, address_type> &other) const\n   {\n      return mAddress >= other.mAddress;\n   }\n\n   template<typename O>\n   constexpr bool operator >= (const be2_val<Pointer<O, address_type>> &other) const\n   {\n      return mAddress >= other.value().mAddress;\n   }\n\n   template<typename O>\n   constexpr bool operator <= (const Pointer<O, address_type> &other) const\n   {\n      return mAddress <= other.mAddress;\n   }\n\n   template<typename O>\n   constexpr bool operator <= (const be2_val<Pointer<O, address_type>> &other) const\n   {\n      return mAddress <= other.value().mAddress;\n   }\n\n   template<typename O>\n   constexpr bool operator > (const Pointer<O, address_type> &other) const\n   {\n      return mAddress > other.mAddress;\n   }\n\n   template<typename O>\n   constexpr bool operator > (const be2_val<Pointer<O, address_type>> &other) const\n   {\n      return mAddress > other.value().mAddress;\n   }\n\n   template<typename O>\n   constexpr bool operator < (const Pointer<O, address_type> &other) const\n   {\n      return mAddress < other.mAddress;\n   }\n\n   template<typename O>\n   constexpr bool operator < (const be2_val<Pointer<O, address_type>> &other) const\n   {\n      return mAddress < other.value().mAddress;\n   }\n\n   template<typename T = value_type>\n   constexpr typename std::enable_if<!std::is_void<T>::value, Pointer &>::type\n   operator ++ ()\n   {\n      mAddress += sizeof(value_type);\n      return *this;\n   }\n\n   template<typename T = value_type>\n   constexpr typename std::enable_if<!std::is_void<T>::value, Pointer>::type\n   operator ++ (int)\n   {\n      auto prev = *this;\n      mAddress += sizeof(value_type);\n      return prev;\n   }\n\n   template<typename T = value_type>\n   constexpr typename std::enable_if<!std::is_void<T>::value, Pointer &>::type\n   operator += (ptrdiff_t value)\n   {\n      mAddress += value * sizeof(value_type);\n      return *this;\n   }\n\n   template<typename T = value_type>\n   constexpr typename std::enable_if<!std::is_void<T>::value, Pointer &>::type\n   operator -= (ptrdiff_t value)\n   {\n      mAddress -= value * sizeof(value_type);\n      return *this;\n   }\n\n   template<typename T = value_type>\n   constexpr typename std::enable_if<!std::is_void<T>::value, Pointer>::type\n   operator + (ptrdiff_t value) const\n   {\n      Pointer dst;\n      dst.mAddress = mAddress + (value * sizeof(value_type));\n      return dst;\n   }\n\n   template<typename T = value_type>\n   constexpr typename std::enable_if<!std::is_void<T>::value, ptrdiff_t>::type\n   operator -(const Pointer &other) const\n   {\n      return (mAddress - other.mAddress) / sizeof(value_type);\n   }\n\n   template<typename T = value_type>\n   constexpr typename std::enable_if<!std::is_void<T>::value, Pointer>::type\n   operator -(ptrdiff_t value) const\n   {\n      Pointer dst;\n      dst.mAddress = mAddress - (value * sizeof(value_type));\n      return dst;\n   }\n\nprotected:\n   template<typename, typename, typename, typename>\n   friend struct pointer_cast_impl;\n\n   template<typename AddressType2, typename ValueType2>\n   friend class Pointer;\n\n   address_type mAddress;\n};\n\ntemplate<typename Value>\nusing VirtualPointer = Pointer<Value, VirtualAddress>;\n\ntemplate<typename Value>\nusing PhysicalPointer = Pointer<Value, PhysicalAddress>;\n\ntemplate<typename AddressType, typename SrcTypePtr, typename DstTypePtr>\nstruct pointer_cast_impl<AddressType, SrcTypePtr, DstTypePtr,\n   typename std::enable_if<\n      std::conjunction_v<\n         std::is_pointer<SrcTypePtr>,\n         std::is_pointer<DstTypePtr>,\n         std::negation<std::is_const<std::remove_pointer_t<SrcTypePtr>>>\n      >>::type>\n{\n   using DstType = std::remove_pointer_t<DstTypePtr>;\n   using SrcType = std::remove_pointer_t<SrcTypePtr>;\n\n   // Pointer<X, AddressType> to Pointer<Y, AddressType>\n   static constexpr Pointer<DstType, AddressType> cast(Pointer<SrcType, AddressType> src)\n   {\n      Pointer<DstType, AddressType> dst;\n      dst.mAddress = src.mAddress;\n      return dst;\n   }\n};\n\ntemplate<typename AddressType, typename SrcTypePtr, typename DstTypePtr>\nstruct pointer_cast_impl<AddressType, SrcTypePtr, DstTypePtr,\n   typename std::enable_if<\n      std::conjunction_v<\n         std::is_pointer<SrcTypePtr>,\n         std::is_pointer<DstTypePtr>,\n         std::is_const<std::remove_pointer_t<SrcTypePtr>>,\n         std::is_const<std::remove_pointer_t<DstTypePtr>>\n      >>::type>\n{\n   using DstType = std::remove_pointer_t<DstTypePtr>;\n   using SrcType = std::remove_pointer_t<SrcTypePtr>;\n\n   // Pointer<const X, AddressType> to Pointer<const Y, AddressType>\n   static constexpr Pointer<const DstType, AddressType> cast(Pointer<SrcType, AddressType> src)\n   {\n      Pointer<DstType, AddressType> dst;\n      dst.mAddress = src.mAddress;\n      return dst;\n   }\n};\n\ntemplate<typename AddressType, typename DstTypePtr>\nstruct pointer_cast_impl<AddressType, AddressType, DstTypePtr,\n   typename std::enable_if<std::is_pointer<DstTypePtr>::value>::type>\n{\n   static_assert(std::is_pointer<DstTypePtr>::value);\n   using DstType = typename std::remove_pointer<DstTypePtr>::type;\n\n   // AddressType to Pointer<X, AddressType>\n   static constexpr Pointer<DstType, AddressType> cast(AddressType src)\n   {\n      Pointer<DstType, AddressType> dst;\n      dst.mAddress = src;\n      return dst;\n   }\n};\n\ntemplate<typename AddressType, typename SrcTypePtr>\nstruct pointer_cast_impl<AddressType, SrcTypePtr, AddressType,\n   typename std::enable_if<std::is_pointer<SrcTypePtr>::value>::type>\n{\n   using SrcType = typename std::remove_pointer<SrcTypePtr>::type;\n\n   // Pointer<X, AddressType> to AddressType\n   static constexpr AddressType cast(Pointer<SrcType, AddressType> src)\n   {\n      return src.mAddress;\n   }\n};\n\n} // namespace cpu\n\n// Custom formatters for fmtlib\nnamespace fmt\n{\n\ninline namespace v8\n{\ntemplate<typename Type, typename Char, typename Enabled>\nstruct formatter;\n}\n\n// Custom formatter in cpu_formatters.h\ntemplate<typename AddressType, typename Char>\nstruct formatter<cpu::Pointer<char, AddressType>, Char, void>;\n\ntemplate<typename AddressType, typename Char>\nstruct formatter<cpu::Pointer<const char, AddressType>, Char, void>;\n\ntemplate<typename ValueType, typename AddressType, typename Char>\nstruct formatter<cpu::Pointer<ValueType, AddressType>, Char, void>;\n\n} // namespace fmt\n"
  },
  {
    "path": "src/libcpu/src/cpu.cpp",
    "content": "#include \"cpu.h\"\n#include \"cpu_alarm.h\"\n#include \"cpu_config.h\"\n#include \"cpu_host_exception.h\"\n#include \"cpu_internal.h\"\n#include \"espresso/espresso_instructionset.h\"\n#include \"interpreter/interpreter.h\"\n#include \"jit/jit.h\"\n#include \"jit/binrec/jit_binrec.h\"\n#include \"mem.h\"\n#include \"mmu.h\"\n\n#include <cfenv>\n#include <chrono>\n#include <common/decaf_assert.h>\n#include <common/platform_thread.h>\n#include <memory>\n\nnamespace cpu\n{\n\nstd::chrono::time_point<std::chrono::steady_clock>\nsStartupTime;\n\nstatic EntrypointHandler\nsCoreEntryPointHandler;\n\nBranchTraceHandler\ngBranchTraceHandler;\n\nstatic bool\nsJitEnabled = false;\n\nstatic std::array<std::unique_ptr<Core>, 3>\nsCores { };\n\nstatic thread_local uint32_t\ntCurrentCoreId = InvalidCoreId;\n\nstatic thread_local cpu::Core *\ntCurrentCore = nullptr;\n\nvoid\ninitialise()\n{\n   auto settings = config();\n   sJitEnabled = settings->jit.enabled;\n\n   // Initalise cpu!\n   initialiseMemory();\n   espresso::initialiseInstructionSet();\n   interpreter::initialise();\n\n   if (sJitEnabled) {\n      auto backend = new jit::BinrecBackend {\n         settings->jit.codeCacheSizeMB * 1024 * 1024,\n         settings->jit.dataCacheSizeMB * 1024 * 1024\n      };\n      backend->setOptFlags(settings->jit.optimisationFlags);\n      backend->setVerifyEnabled(settings->jit.verify, settings->jit.verifyAddress);\n      jit::setBackend(backend);\n   }\n\n   sStartupTime = std::chrono::steady_clock::now();\n}\n\nvoid\nclearInstructionCache()\n{\n   cpu::jit::clearCache(0, 0xFFFFFFFF);\n}\n\nvoid\ninvalidateInstructionCache(uint32_t address,\n                           uint32_t size)\n{\n   cpu::jit::clearCache(address, size);\n}\n\nvoid\naddJitReadOnlyRange(uint32_t address,\n                    uint32_t size)\n{\n   jit::addReadOnlyRange(address, size);\n}\n\nvoid\ncoreEntryPoint(Core *core)\n{\n   tCurrentCoreId = core->id;\n   tCurrentCore = core;\n   sCoreEntryPointHandler(core);\n}\n\nvoid\nstart()\n{\n   internal::installHostExceptionHandler();\n\n   for (auto i = 0u; i < sCores.size(); ++i) {\n      auto core = jit::initialiseCore(i);\n      if (!core) {\n         core = new Core {};\n         core->id = i;\n      }\n\n      sCores[i] = std::unique_ptr<Core> { core };\n      core->thread = std::thread { coreEntryPoint, core };\n      core->next_alarm = std::chrono::steady_clock::time_point::max();\n\n      static const std::string coreNames[] = { \"Core #0\", \"Core #1\", \"Core #2\" };\n      platform::setThreadName(&core->thread, coreNames[i]);\n   }\n\n   internal::startAlarmThread();\n}\n\nvoid\njoin()\n{\n   for (auto &core : sCores) {\n      if (core && core->thread.joinable()) {\n         core->thread.join();\n         core.reset();\n      }\n   }\n\n   internal::joinAlarmThread();\n}\n\nvoid\nhalt()\n{\n   for (auto i = 0; i < 3; ++i) {\n      interrupt(i, SRESET_INTERRUPT);\n   }\n\n   internal::stopAlarmThread();\n}\n\nCore *\ngetCore(int index)\n{\n   return sCores[index].get();\n}\n\nvoid\nsetCoreEntrypointHandler(EntrypointHandler handler)\n{\n   sCoreEntryPointHandler = handler;\n}\n\nvoid\nsetSegfaultHandler(SegfaultHandler handler)\n{\n   internal::setUserSegfaultHandler(handler);\n}\n\nvoid\nsetBranchTraceHandler(BranchTraceHandler handler)\n{\n   gBranchTraceHandler = handler;\n}\n\nstd::chrono::steady_clock::time_point\ntbToTimePoint(uint64_t ticks)\n{\n   auto cpuTicks = TimerDuration { ticks };\n   auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(cpuTicks);\n   return sStartupTime + nanos;\n}\n\nuint64_t\nCore::tb()\n{\n   auto now = std::chrono::steady_clock::now();\n   auto ticks = std::chrono::duration_cast<TimerDuration>(now - sStartupTime);\n   return ticks.count();\n}\n\nnamespace this_core\n{\n\ncpu::Core *\nstate()\n{\n   return tCurrentCore;\n}\n\nuint32_t\nid()\n{\n   return tCurrentCoreId;\n}\n\nvoid\nresume()\n{\n   if (sJitEnabled) {\n      jit::resume();\n   } else {\n      interpreter::resume();\n   }\n}\n\nvoid\nexecuteSub()\n{\n   auto lr = tCurrentCore->lr;\n   tCurrentCore->lr = CALLBACK_ADDR;\n   resume();\n   tCurrentCore->lr = lr;\n}\n\nvoid\nupdateRoundingMode()\n{\n   Core *core = tCurrentCore;\n\n   static const int modes[4] = {\n      FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD\n   };\n   fesetround(modes[core->fpscr.rn]);\n}\n\n} // namespace this_core\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/cpu_alarm.cpp",
    "content": "#include \"cpu.h\"\n#include \"cpu_alarm.h\"\n#include \"cpu_breakpoints.h\"\n#include \"cpu_internal.h\"\n\n#include <common/decaf_assert.h>\n#include <common/platform_thread.h>\n#include <atomic>\n#include <condition_variable>\n#include <mutex>\n#include <thread>\n\nstruct\n{\n   std::atomic<bool> running { false };\n   std::mutex mutex;\n   std::condition_variable cv;\n   std::thread thread;\n} sAlarmData;\n\nnamespace cpu::internal\n{\n\nstatic void\nalarmEntryPoint()\n{\n   while (sAlarmData.running) {\n      std::unique_lock<std::mutex> lock{ sAlarmData.mutex };\n      auto now = std::chrono::steady_clock::now();\n      auto next = std::chrono::steady_clock::time_point::max();\n      bool timedWait = false;\n\n      for (auto i = 0; i < 3; ++i) {\n         auto core = getCore(i);\n\n         if (core->next_alarm <= now) {\n            core->next_alarm = std::chrono::steady_clock::time_point::max();\n            cpu::interrupt(i, ALARM_INTERRUPT);\n         } else if (core->next_alarm < next) {\n            next = core->next_alarm;\n            timedWait = true;\n         }\n      }\n\n      if (timedWait) {\n         sAlarmData.cv.wait_until(lock, next);\n      } else {\n         sAlarmData.cv.wait(lock);\n      }\n   }\n}\n\nvoid\nstartAlarmThread()\n{\n   decaf_check(!sAlarmData.running.load());\n   sAlarmData.running = true;\n   sAlarmData.thread = std::thread { alarmEntryPoint };\n   platform::setThreadName(&sAlarmData.thread, \"CPU Alarm Thread\");\n}\n\nvoid\njoinAlarmThread()\n{\n   if (sAlarmData.thread.joinable()) {\n      sAlarmData.thread.join();\n   }\n}\n\nvoid\nstopAlarmThread()\n{\n   sAlarmData.running = false;\n   sAlarmData.cv.notify_all();\n}\n\n} // namespace cpu::internal\n\nnamespace cpu::this_core\n{\n\nvoid\nsetNextAlarm(std::chrono::steady_clock::time_point time)\n{\n   auto core = this_core::state();\n   std::unique_lock<std::mutex> lock { sAlarmData.mutex };\n   core->next_alarm = time;\n   sAlarmData.cv.notify_all();\n}\n\n} // namespace cpu::this_core\n"
  },
  {
    "path": "src/libcpu/src/cpu_alarm.h",
    "content": "#pragma once\n\nnamespace cpu::internal\n{\n\nvoid startAlarmThread();\nvoid joinAlarmThread();\nvoid stopAlarmThread();\n\n} // namespace cpu::internal\n"
  },
  {
    "path": "src/libcpu/src/cpu_breakpoints.cpp",
    "content": "#include \"cpu.h\"\n#include \"cpu_breakpoints.h\"\n#include \"espresso/espresso_instructionset.h\"\n#include \"mem.h\"\n\n#include <algorithm>\n#include <atomic>\n#include <functional>\n#include <memory>\n#include <vector>\n\nnamespace cpu\n{\n\nstatic std::shared_ptr<BreakpointList>\nsActiveBreakpoints;\n\nusing ModifyListFn = std::function<bool (BreakpointList &list)>;\n\nstatic inline void\nupdateBreakpointList(ModifyListFn fn)\n{\n   auto newList = std::make_shared<BreakpointList>();\n   auto currentList = sActiveBreakpoints;\n\n   do {\n      if (currentList) {\n         *newList = *currentList;\n      } else {\n         newList->clear();\n      }\n\n      if (!fn(*newList)) {\n         // If function returns false, do not update breakpoint list.\n         break;\n      }\n   } while (!std::atomic_compare_exchange_strong(&sActiveBreakpoints, &currentList, newList));\n}\n\n\n/**\n * Add a breakpoint at an address.\n */\nvoid\naddBreakpoint(uint32_t address,\n              Breakpoint::Type type)\n{\n   auto savedCode = mem::read<uint32_t>(address);\n\n   updateBreakpointList([address, type, savedCode](BreakpointList &list) {\n      auto itr = std::find_if(list.begin(), list.end(),\n                              [address](auto &bp) {\n                                 return bp.address == address;\n                              });\n\n      if (itr != list.end()) {\n         if (itr->type == type) {\n            // Same type of breakpoint already at address, do not modify list.\n            return false;\n         }\n\n         if (type == Breakpoint::SingleFire && itr->type == Breakpoint::MultiFire) {\n            // Already have a multi fire breakpoint, no need to set single fire.\n            return false;\n         }\n\n         // Update existing breakpoint type.\n         itr->type = type;\n         return true;\n      }\n\n      list.push_back({ type, address, savedCode });\n      return true;\n   });\n\n   // Set unconditional trap instruction at address.\n   auto trapInstr = espresso::encodeInstruction(espresso::InstructionID::tw);\n   trapInstr.to = 31;\n   trapInstr.rA = 0;\n   trapInstr.rB = 0;\n   mem::write<uint32_t>(address, trapInstr);\n\n   cpu::invalidateInstructionCache(address, 4);\n}\n\n\n/**\n * Remove a breakpoint at an address.\n */\nvoid\nremoveBreakpoint(uint32_t address)\n{\n   updateBreakpointList([address](BreakpointList &list) {\n      auto itr = std::find_if(list.begin(), list.end(),\n                              [address](auto &bp) {\n                                 return bp.address == address;\n                              });\n\n      if (itr == list.end()) {\n         return false;\n      }\n\n      // Restore saved code.\n      mem::write<uint32_t>(address, itr->savedCode);\n\n      list.erase(itr);\n      return true;\n   });\n\n   cpu::invalidateInstructionCache(address, 4);\n}\n\n\n/**\n * Returns true if we hit a breakpoint at the address.\n *\n * Will remove the breakpoint if it is a SingleFire breakpoint.\n */\nbool\ntestBreakpoint(uint32_t address)\n{\n   auto list = sActiveBreakpoints;\n\n   if (!list) {\n      return false;\n   }\n\n   auto itr = std::find_if(list->begin(), list->end(),\n                           [address](auto &bp) {\n                              return bp.address == address;\n                           });\n\n   if (itr == list->end()) {\n      return false;\n   }\n\n   if (itr->type == Breakpoint::SingleFire) {\n      removeBreakpoint(address);\n   }\n\n   return true;\n}\n\n\n/**\n * Returns true if there are any breakpoints set.\n */\nbool\nhasBreakpoints()\n{\n   return sActiveBreakpoints != nullptr;\n}\n\n\n/**\n * Returns true if there is a breakpoint at the specified address.\n */\nbool\nhasBreakpoint(uint32_t address)\n{\n   auto list = sActiveBreakpoints;\n\n   if (!list) {\n      return false;\n   }\n\n   auto itr = std::find_if(list->begin(), list->end(),\n                           [address](auto &bp) {\n                              return bp.address == address;\n                           });\n\n   return itr != list->end();\n}\n\n\n/**\n * Get a list of all active breakpoints.\n */\nstd::shared_ptr<BreakpointList>\ngetBreakpoints()\n{\n   return sActiveBreakpoints;\n}\n\n\n/**\n * Get the instruction at an address before we edited it with a tw.\n *\n * If there is no breakpoint, reads the memory and returns the current instruction.\n */\nuint32_t\ngetBreakpointSavedCode(uint32_t address)\n{\n   auto list = sActiveBreakpoints;\n   if (list) {\n      auto itr = std::find_if(list->begin(), list->end(),\n                              [address](auto &bp) {\n                                 return bp.address == address;\n                              });\n\n      if (itr != list->end()) {\n         return itr->savedCode;\n      }\n   }\n\n   return mem::read<uint32_t>(address);\n}\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/cpu_configstorage.cpp",
    "content": "#include \"cpu_config.h\"\n#include \"cpu_configstorage.h\"\n\n#include <common/configstorage.h>\n\nnamespace cpu\n{\n\nstatic ConfigStorage<Settings> sSettings;\n\nstd::shared_ptr<const Settings>\nconfig()\n{\n   return sSettings.get();\n}\n\nvoid\nsetConfig(const Settings &settings)\n{\n   sSettings.set(std::make_shared<Settings>(settings));\n}\n\nvoid\nregisterConfigChangeListener(ConfigStorage<Settings>::ChangeListener listener)\n{\n   sSettings.addListener(listener);\n}\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/cpu_configstorage.h",
    "content": "#pragma once\n#include \"cpu_config.h\"\n\n#include <common/configstorage.h>\n\nnamespace cpu\n{\n\nvoid\nregisterConfigChangeListener(ConfigStorage<Settings>::ChangeListener listener);\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/cpu_host_exception.cpp",
    "content": "#include \"cpu.h\"\n\n#include <common/decaf_assert.h>\n#include <common/platform_exception.h>\n#include <common/platform_stacktrace.h>\n#include <common/platform_thread.h>\n#include <fmt/core.h>\n\nnamespace cpu::internal\n{\n\nstatic thread_local uint32_t sSegfaultAddr = 0;\nstatic thread_local platform::StackTrace *sSegfaultStackTrace = nullptr;\nstatic SegfaultHandler sUserSegfaultHandler = nullptr;\n\nstatic void\ncoreSegfaultEntry()\n{\n   auto core = cpu::this_core::state();\n   if (sSegfaultAddr == core->nia) {\n      core->srr0 = sSegfaultAddr;\n   } else {\n      core->srr0 = core->nia;\n      core->dar = sSegfaultAddr;\n      core->dsisr = 0u;\n   }\n\n   if (sUserSegfaultHandler) {\n      sUserSegfaultHandler(core, sSegfaultAddr, sSegfaultStackTrace);\n      decaf_abort(\"The user segfault handler unexpectedly returned.\");\n   } else {\n      decaf_host_fault(fmt::format(\"Segfault exception, srr0: 0x{:08X}, dar: 0x{:08X}\\n\",\n                                   core->srr0, core->dar),\n                       sSegfaultStackTrace);\n   }\n}\n\nstatic void\nillegalInstructionHandler()\n{\n   auto core = cpu::this_core::state();\n   core->srr0 = sSegfaultAddr;\n   decaf_host_fault(fmt::format(\"Illegal instruction exception, srr0: 0x{:08X}\\n\",\n                                core->srr0),\n                    sSegfaultStackTrace);\n}\n\nstatic platform::ExceptionResumeFunc\nhostExceptionHandler(platform::Exception *exception)\n{\n   // Handle illegal instructions!\n   if (exception->type == platform::Exception::InvalidInstruction) {\n      sSegfaultStackTrace = platform::captureStackTrace();\n      return illegalInstructionHandler;\n   }\n\n   // Only handle AccessViolation exceptions\n   if (exception->type != platform::Exception::AccessViolation) {\n      return platform::UnhandledException;\n   }\n\n   // Only handle exceptions from the CPU cores\n   if (this_core::id() >= 0xFF) {\n      return platform::UnhandledException;\n   }\n\n   // Retreive the exception information\n   auto info = reinterpret_cast<platform::AccessViolationException *>(exception);\n   auto address = info->address;\n\n   // Only handle exceptions within the virtual memory bounds\n   auto memBase = getBaseVirtualAddress();\n   if (address != 0 && (address < memBase || address >= memBase + 0x100000000)) {\n      return platform::UnhandledException;\n   }\n\n   sSegfaultAddr = static_cast<uint32_t>(address - memBase);\n   sSegfaultStackTrace = platform::captureStackTrace();\n   return coreSegfaultEntry;\n}\n\nvoid\ninstallHostExceptionHandler()\n{\n   static bool installed = false;\n   if (!installed) {\n      installed = platform::installExceptionHandler(hostExceptionHandler);\n   }\n}\n\nvoid\nsetUserSegfaultHandler(SegfaultHandler userHandler)\n{\n   sUserSegfaultHandler = userHandler;\n}\n\n} // namespace cpu::internal\n"
  },
  {
    "path": "src/libcpu/src/cpu_host_exception.h",
    "content": "#pragma once\n#include \"cpu.h\"\n\nnamespace cpu::internal\n{\n\nvoid\ninstallHostExceptionHandler();\n\nvoid\nsetUserSegfaultHandler(SegfaultHandler userHandler);\n\n} // namespace cpu::internal\n"
  },
  {
    "path": "src/libcpu/src/cpu_internal.h",
    "content": "#pragma once\n#include \"cpu.h\"\n#include \"cpu_config.h\"\n#include \"mem.h\"\n\n#include <array>\n#include <condition_variable>\n#include <memory>\n\nnamespace cpu\n{\n\nextern BranchTraceHandler gBranchTraceHandler;\n\nCore *\ngetCore(int index);\n\nSystemCallHandler\ngetSystemCallHandler(uint32_t id);\n\nbool\ninitialiseMemory();\n\nnamespace this_core\n{\n\nvoid\nupdateRoundingMode();\n\n} // namespace this_core\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/cpu_interrupts.cpp",
    "content": "#include \"cpu.h\"\n#include \"cpu_breakpoints.h\"\n#include \"cpu_internal.h\"\n\n#include <common/decaf_assert.h>\n#include <condition_variable>\n#include <atomic>\n\nnamespace cpu\n{\n\nstatic void defaultInterruptHandler(Core *core, uint32_t interrupt_flags) { }\n\nstatic InterruptHandler sUserInterruptHandler = &defaultInterruptHandler;\nstatic std::mutex sInterruptMutex;\nstatic std::condition_variable sInterruptCondition;\n\nvoid\ninterrupt(int coreIndex, uint32_t flags)\n{\n   std::unique_lock<std::mutex> lock { sInterruptMutex };\n   auto core = getCore(coreIndex);\n   if (core) {\n      core->interrupt.fetch_or(flags);\n   }\n   sInterruptCondition.notify_all();\n}\n\nvoid\nsetInterruptHandler(InterruptHandler handler)\n{\n   sUserInterruptHandler = handler;\n}\n\nnamespace this_core\n{\n\nvoid\nclearInterrupt(uint32_t flags)\n{\n   state()->interrupt.fetch_and(~flags);\n}\n\nuint32_t\ninterruptMask()\n{\n   return state()->interrupt_mask;\n}\n\nuint32_t\nsetInterruptMask(uint32_t mask)\n{\n   auto core = state();\n   auto old_mask = core->interrupt_mask;\n   core->interrupt_mask = mask;\n   return old_mask;\n}\n\nvoid\ncheckInterrupts()\n{\n   auto core = state();\n   auto mask = core->interrupt_mask | NONMASKABLE_INTERRUPTS;\n   auto flags = core->interrupt.fetch_and(~mask);\n\n   if (flags & mask) {\n      sUserInterruptHandler(core, flags);\n   }\n}\n\nvoid\nwaitForInterrupt()\n{\n   auto core = this_core::state();\n   std::unique_lock<std::mutex> lock { sInterruptMutex };\n\n   while (true) {\n      if (!(core->interrupt_mask & ~NONMASKABLE_INTERRUPTS)) {\n         decaf_abort(\"WFI thread found all maskable interrupts were disabled\");\n      }\n\n      auto mask = core->interrupt_mask | NONMASKABLE_INTERRUPTS;\n      auto flags = core->interrupt.fetch_and(~mask);\n\n      if (flags & mask) {\n         lock.unlock();\n         sUserInterruptHandler(core, flags);\n         lock.lock();\n      } else {\n         sInterruptCondition.wait(lock);\n      }\n   }\n}\n\nvoid\nwaitNextInterrupt(std::chrono::steady_clock::time_point until)\n{\n   auto core = this_core::state();\n   std::unique_lock<std::mutex> lock { sInterruptMutex };\n\n   if (!(core->interrupt_mask & ~NONMASKABLE_INTERRUPTS)) {\n      decaf_abort(\"WFI thread found all maskable interrupts were disabled\");\n   }\n\n   auto mask = core->interrupt_mask | NONMASKABLE_INTERRUPTS;\n   auto flags = core->interrupt.fetch_and(~mask);\n\n   if (!(flags & mask)) {\n      if (until == std::chrono::steady_clock::time_point { }) {\n         sInterruptCondition.wait(lock);\n      } else {\n         sInterruptCondition.wait_until(lock, until);\n      }\n\n      mask = core->interrupt_mask | NONMASKABLE_INTERRUPTS;\n      flags = core->interrupt.fetch_and(~mask);\n   }\n\n   lock.unlock();\n\n   if (flags & mask) {\n      sUserInterruptHandler(core, flags);\n   }\n}\n\n} // namespace this_core\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/cpu_memtrack_posix.cpp",
    "content": "#include \"memtrack.h\"\n#include \"mmu.h\"\n\n#include <common/datahash.h>\n#include <common/platform.h>\n#ifdef PLATFORM_POSIX\n\nnamespace cpu\n{\n\nnamespace internal\n{\n\nvoid\ninitialiseMemtrack()\n{\n}\n\nvoid\nregisterTrackedRange(VirtualAddress virtualAddress,\n                     PhysicalAddress physicalAddress,\n                     uint32_t size)\n{\n}\n\nvoid\nunregisterTrackedRange(VirtualAddress virtualAddress,\n                       uint32_t size)\n{\n}\n\nvoid\nclearTrackedRanges()\n{\n}\n\n} // namespace internal\n\nMemtrackState\ngetMemoryState(PhysicalAddress physicalAddress,\n               uint32_t size)\n{\n   // POSIX always hashes for the moment.  This is due to my inability to actually\n   // test any sort of implementation on a linux system.\n   auto physPtr = reinterpret_cast<void *>(cpu::getBasePhysicalAddress() + physicalAddress.getAddress());\n   auto hashVal = DataHash {}.write(physPtr, size);\n   return MemtrackState { hashVal.value() };\n}\n\n} // namespace cpu\n\n#endif // PLATFORM_POSIX\n"
  },
  {
    "path": "src/libcpu/src/cpu_memtrack_win.cpp",
    "content": "#include \"memtrack.h\"\n\n#ifdef PLATFORM_WINDOWS\n\n#include \"cpu_config.h\"\n#include \"cpu_internal.h\"\n#include \"mmu.h\"\n\n#include <common/datahash.h>\n#include <common/platform.h>\n#include <common/rangecombiner.h>\n#include <unordered_map>\n\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\n\nnamespace cpu\n{\n\nstatic constexpr uint64_t PhysTrackSetBit = 0x8000000000000000;\nstatic constexpr uint64_t PhysIsMappedBit = 0x4000000000000000;\nstatic constexpr uint32_t VirtTrackSetBit = 0x80000000;\nstatic constexpr uint32_t VirtIsMappedBit = 0x40000000;\n\nstruct MappedArea\n{\n   cpu::VirtualAddress virtAddr;\n   cpu::PhysicalAddress physAddr;\n   uint32_t size;\n};\n\nstatic uintptr_t sPhysBaseAddress = 0;\nstatic uintptr_t sVirtBaseAddress = 0;\nstatic uint64_t sPageSizeBits = 0;\nstatic std::atomic<uint32_t> *sVirtLookup = nullptr;\nstatic std::atomic<uint64_t> *sTrackCount = nullptr;\nstatic std::vector<MappedArea> sVirtMap;\nnamespace internal\n{\n\nLONG writeExceptionHandler(_EXCEPTION_POINTERS *ExceptionInfo)\n{\n   auto &ExceptionRecord = ExceptionInfo->ExceptionRecord;\n   if (UNLIKELY(ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)) {\n      return EXCEPTION_CONTINUE_SEARCH;\n   }\n\n   // We do not check to see if sTrackCount is initialized here because we can assume\n   // that it must be initialized if the vectored exeception handler was registered.\n\n   // We do not verify that the SET bit is set for physical or virtual since another\n   // thread may be racing us.  Instead we only check that the region was mapped at\n   // least once (via the IsMapped bits), and if its been mapped once we can assume\n   // that it was meant to be set up.  We also allow VirtualProtect to return an old\n   // protection of READ _OR_ READWRITE for the same reasons.\n\n   auto MemoryAddress = ExceptionRecord->ExceptionInformation[1];\n   if (MemoryAddress >= sVirtBaseAddress && MemoryAddress < sVirtBaseAddress + 0x100000000) {\n      // This is a virtual address, we need to do a lookup first.\n      auto lookupIdx = (MemoryAddress - sVirtBaseAddress) >> sPageSizeBits;\n\n      auto oldLookupValue = sVirtLookup[lookupIdx].fetch_and(~VirtTrackSetBit);\n      if (!(oldLookupValue & VirtIsMappedBit)) {\n         // This was an unmapped range to begin with.\n         return EXCEPTION_CONTINUE_SEARCH;\n      }\n\n      // Figure out which page we belong to.\n      auto trackIdx = oldLookupValue & ~(VirtTrackSetBit | VirtIsMappedBit);\n\n      // Increment the counter to mark it as having changed\n      sTrackCount[trackIdx].fetch_add(1);\n   } else if (MemoryAddress >= sPhysBaseAddress && MemoryAddress < sPhysBaseAddress + 0x100000000) {\n      // This is a physical address, we can directly convert\n      auto trackIdx = static_cast<uint32_t>((MemoryAddress - sPhysBaseAddress) >> sPageSizeBits);\n\n      // Then we remove the tracking bit\n      auto oldTrackValue = sTrackCount[trackIdx].fetch_and(~PhysTrackSetBit);\n      if (!(oldTrackValue & PhysIsMappedBit)) {\n         // There was no protection on this area.  Something else has gone wrong.\n         return EXCEPTION_CONTINUE_SEARCH;\n      }\n\n      // Increment the counter to mark it as having changed\n      sTrackCount[trackIdx].fetch_add(1);\n   } else {\n      return EXCEPTION_CONTINUE_SEARCH;\n   }\n\n   // Finally we reprotect the memory to its normal state\n   DWORD oldProtection;\n   VirtualProtect(reinterpret_cast<void*>(MemoryAddress), 1, PAGE_READWRITE, &oldProtection);\n   if (oldProtection != PAGE_READONLY && oldProtection != PAGE_READWRITE) {\n      VirtualProtect(reinterpret_cast<void*>(MemoryAddress), 1, oldProtection, nullptr);\n      return EXCEPTION_CONTINUE_SEARCH;\n   }\n\n   return EXCEPTION_CONTINUE_EXECUTION;\n}\n\nvoid\ninitialiseMemtrack()\n{\n   if (!config()->memory.writeTrackEnabled) {\n      return;\n   }\n\n   SYSTEM_INFO sysInfo;\n   GetSystemInfo(&sysInfo);\n\n   auto pageSize = sysInfo.dwPageSize;\n   auto pageSizeBits = 0;\n   auto i = pageSize;\n   while (i >>= 1) pageSizeBits++;\n\n   sPhysBaseAddress = cpu::getBasePhysicalAddress();\n   sVirtBaseAddress = cpu::getBaseVirtualAddress();\n   sPageSizeBits = pageSizeBits;\n\n   auto numtrackTableEntries = 0x100000000 >> pageSizeBits;\n\n   // Initialise the lookup table to point everything to 0xFFFFFFFF (invalid)\n   sVirtLookup = new std::atomic<uint32_t>[numtrackTableEntries];\n   memset(sVirtLookup, 0x00, numtrackTableEntries * sizeof(uint32_t));\n\n   // Initialise the tracking table to all 0's\n   sTrackCount = new std::atomic<uint64_t>[numtrackTableEntries];\n   memset(sTrackCount, 0x00, numtrackTableEntries * sizeof(uint64_t));\n\n   // Install the exception handler\n   AddVectoredExceptionHandler(1, writeExceptionHandler);\n}\n\nvoid\nregisterTrackedRange(VirtualAddress virtualAddress,\n                     PhysicalAddress physicalAddress,\n                     uint32_t size)\n{\n   if (!sTrackCount) {\n      return;\n   }\n\n   if (size == 0) {\n      return;\n   }\n\n   // We have to remove any conflicting virtual mappings before we can proceed.  Note\n   // that this is technically an incorrect operation, as they may not be overlaying on\n   // eachother perfectly, but its challenging to handle this correctly, and this should\n   // work for the immediate future.\n   // TODO: Correctly unmap regions from the memory tracker.\n   for (auto iter = sVirtMap.begin(); iter != sVirtMap.end(); ++iter) {\n      if (virtualAddress >= iter->virtAddr && virtualAddress < iter->virtAddr + iter->size) {\n         iter = sVirtMap.erase(iter);\n      }\n   }\n\n   // Add an entry to the mappings list\n   sVirtMap.push_back({ virtualAddress, physicalAddress, size });\n\n   // Apply the neccessary changes to the tracking tables\n   auto firstPhysPage = physicalAddress.getAddress() >> sPageSizeBits;\n   auto firstPage = virtualAddress.getAddress() >> sPageSizeBits;\n   auto lastPage = (virtualAddress.getAddress() + (size - 1)) >> sPageSizeBits;\n\n   for (auto pageIdx = firstPage, physPageIdx = firstPhysPage;\n        pageIdx <= lastPage;\n        ++pageIdx, ++physPageIdx)\n   {\n      auto oldPhysPage = sVirtLookup[pageIdx].exchange(VirtIsMappedBit | physPageIdx);\n      if (oldPhysPage & VirtIsMappedBit) {\n         decaf_abort(\"write tracker attempted to register an already registered page\");\n      }\n\n      // In order to avoid needing to go searching for all the pages that we\n      // need to protect, we simply increment the tracking table to indicate\n      // that any of the memory may have changed.  The next time the pages are\n      // checked for changes, the correct protections will be applied.\n      sTrackCount[physPageIdx].fetch_add(1);\n   }\n}\n\nvoid\nunregisterTrackedRange(VirtualAddress virtualAddress,\n                       uint32_t size)\n{\n   if (!sTrackCount) {\n      return;\n   }\n\n   if (size == 0) {\n      return;\n   }\n\n   // Remove the entry from the mappings list\n   for (auto iter = sVirtMap.begin(); iter != sVirtMap.end(); ++iter) {\n      if (iter->virtAddr == virtualAddress && iter->size == size) {\n         sVirtMap.erase(iter);\n         break;\n      }\n   }\n\n   // Apply the neccessary changes to the tracking tables\n   auto firstPage = virtualAddress.getAddress() >> sPageSizeBits;\n   auto lastPage = firstPage + ((size - 1) >> sPageSizeBits);\n\n   for (auto pageIdx = firstPage; pageIdx <= lastPage; ++pageIdx) {\n      auto oldPhysPage = sVirtLookup[pageIdx].exchange(0x00000000);\n      if (!(oldPhysPage & VirtIsMappedBit)) {\n         decaf_abort(\"write tracker attempted to unregister an already unregister page\");\n      }\n   }\n}\n\nvoid\nclearTrackedRanges()\n{\n   if (!sTrackCount) {\n      return;\n   }\n\n   // Resetting the tracked ranges is as simple as clearing the lookup table\n   // we are using to translate virtual addresses to physical pages.\n   auto numtrackTableEntries = 0x100000000 >> sPageSizeBits;\n   memset(sVirtLookup, 0x00, numtrackTableEntries * sizeof(uint32_t));\n}\n\n} // namespace internal\n\nMemtrackState\ngetMemoryState(PhysicalAddress physicalAddress,\n               uint32_t size)\n{\n   if (!sTrackCount) {\n      // If the write tracking system is not enabled, we simply hash.\n      auto physPtr = reinterpret_cast<void *>(cpu::getBasePhysicalAddress() + physicalAddress.getAddress());\n      auto hashVal = DataHash {}.write(physPtr, size);\n      return MemtrackState { hashVal.value() };\n   }\n\n   if (size == 0) {\n      return MemtrackState { 0 };\n   }\n\n   // Calculate the actual return value\n   uint64_t pageIndexTotal = 0;\n\n   {\n      uintptr_t startAddr = physicalAddress.getAddress();\n      uintptr_t endAddr = startAddr + (size - 1);\n\n      auto startPage = startAddr >> sPageSizeBits;\n      auto lastPage = endAddr >> sPageSizeBits;\n\n      for (auto i = startPage; i <= lastPage; ++i) {\n         auto pageData = sTrackCount[i].load();\n         auto pageCount = pageData & ~(PhysTrackSetBit);\n         pageIndexTotal += pageCount;\n      }\n   }\n\n   // Write-protect all these regions for the future\n   for (auto &area : sVirtMap) {\n      if (physicalAddress < area.physAddr || physicalAddress >= area.physAddr + area.size) {\n         continue;\n      }\n\n      auto virtualAddress = area.virtAddr + (physicalAddress - area.physAddr);\n\n      uintptr_t startAddr = virtualAddress.getAddress();\n      uintptr_t endAddr = startAddr + (size - 1);\n\n      auto startPage = startAddr >> sPageSizeBits;\n      auto lastPage = endAddr >> sPageSizeBits;\n\n      auto pagePtr = reinterpret_cast<uint8_t*>(sVirtBaseAddress + (startPage << sPageSizeBits));\n      auto pageSize = 1 << sPageSizeBits;\n\n      auto protectCombiner = makeRangeCombiner<void*, uint8_t*, uint64_t>(\n         [=](void*, uint8_t* pagePtr, uint64_t pageSize)\n         {\n            DWORD oldProtection;\n            VirtualProtect(pagePtr, pageSize, PAGE_READONLY, &oldProtection);\n            if (oldProtection != PAGE_READONLY && oldProtection != PAGE_READWRITE) {\n               decaf_abort(\"Attempted to write-track a weird page\");\n            }\n         });\n\n      for (auto i = startPage; i <= lastPage; ++i) {\n         auto oldTrackValue = sVirtLookup[i].fetch_or(VirtTrackSetBit | VirtIsMappedBit);\n         if (!(oldTrackValue & VirtIsMappedBit)) {\n            decaf_abort(\"Attempted to write-track unmapped memory\");\n         }\n\n         if (!(oldTrackValue & VirtTrackSetBit)) {\n            // If we weren't previous tracking this memory, we need to add it.\n            protectCombiner.push(nullptr, pagePtr, pageSize);\n         }\n\n         pagePtr += pageSize;\n      }\n\n      protectCombiner.flush();\n   }\n\n   return MemtrackState { pageIndexTotal };\n}\n\n} // namespace cpu\n\n#endif // PLATFORM_WINDOWS\n"
  },
  {
    "path": "src/libcpu/src/cpu_mmu.cpp",
    "content": "#include \"mmu.h\"\n#include \"memorymap.h\"\n#include \"memtrack.h\"\n\nnamespace cpu\n{\n\nstatic MemoryMap\nsMemoryMap;\n\nbool\ninitialiseMemory()\n{\n   if (sMemoryMap.reserve()) {\n      internal::initialiseMemtrack();\n      return true;\n   }\n   return false;\n}\n\nbool\nallocateVirtualAddress(VirtualAddress virtualAddress,\n                       uint32_t size)\n{\n   return sMemoryMap.allocateVirtualAddress(virtualAddress, size);\n}\n\nbool\nfreeVirtualAddress(VirtualAddress virtualAddress,\n                   uint32_t size)\n{\n   return sMemoryMap.freeVirtualAddress(virtualAddress, size);\n}\n\nVirtualAddressRange\nfindFreeVirtualAddress(uint32_t size,\n                       uint32_t align)\n{\n   return sMemoryMap.findFreeVirtualAddress(size, align);\n}\n\nVirtualAddressRange\nfindFreeVirtualAddressInRange(VirtualAddressRange range,\n                              uint32_t size,\n                              uint32_t align)\n{\n   return sMemoryMap.findFreeVirtualAddressInRange(range, size, align);\n}\n\nbool\nmapMemory(VirtualAddress virtualAddress,\n          PhysicalAddress physicalAddress,\n          uint32_t size,\n          MapPermission permission)\n{\n   if (sMemoryMap.mapMemory(virtualAddress, physicalAddress, size, permission)) {\n      if (permission == MapPermission::ReadWrite) {\n         internal::registerTrackedRange(virtualAddress, physicalAddress, size);\n      }\n      return true;\n   }\n   return false;\n}\n\nbool\nunmapMemory(VirtualAddress virtualAddress,\n            uint32_t size)\n{\n   if (sMemoryMap.unmapMemory(virtualAddress, size)) {\n      internal::unregisterTrackedRange(virtualAddress, size);\n      return true;\n   }\n   return false;\n}\n\nbool\nresetVirtualMemory()\n{\n   internal::clearTrackedRanges();\n   return sMemoryMap.resetVirtualMemory();\n}\n\nVirtualMemoryType\nqueryVirtualAddress(VirtualAddress virtualAddress)\n{\n   return sMemoryMap.queryVirtualAddress(virtualAddress);\n}\n\nbool\nisValidAddress(VirtualAddress address)\n{\n   PhysicalAddress physical;\n   return sMemoryMap.virtualToPhysicalAddress(address, physical);\n}\n\nbool\nvirtualToPhysicalAddress(VirtualAddress virtualAddress,\n                         PhysicalAddress &out)\n{\n   return sMemoryMap.virtualToPhysicalAddress(virtualAddress, out);\n}\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/cpu_systemcall.cpp",
    "content": "#include \"cpu.h\"\n\n#include <atomic>\n\nnamespace cpu\n{\n\nconstexpr auto MaxRegisteredSystemCalls = 0xffff; // This must be `(1<<Bits)-1` due to AND below.\n\nstruct StaticSystemCallData\n{\n   SystemCallHandler unknownHandler =\n      [](Core *core, uint32_t id) {\n         return core;\n      };\n\n   std::atomic<SystemCallHandler> handlers[MaxRegisteredSystemCalls] = { nullptr };\n   std::atomic_uint32_t validHandlerID = 0;\n   std::atomic_uint32_t illegalHandlerID = 0;\n} sSystemCall;\n\nvoid\nsetUnknownSystemCallHandler(SystemCallHandler handler)\n{\n   sSystemCall.unknownHandler = handler;\n}\n\nuint32_t\nregisterSystemCallHandler(SystemCallHandler handler)\n{\n   auto id = sSystemCall.validHandlerID++;\n   sSystemCall.handlers[id] = handler;\n   return 0x100000 | id;\n}\n\nuint32_t\nregisterIllegalSystemCall()\n{\n   return 0x800000 | (++sSystemCall.illegalHandlerID);\n}\n\nSystemCallHandler\ngetSystemCallHandler(uint32_t id)\n{\n   if (LIKELY(id & 0x100000)) {\n      return sSystemCall.handlers[id & MaxRegisteredSystemCalls];\n   }\n\n   return sSystemCall.unknownHandler;\n}\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter.cpp",
    "content": "#include \"cpu_breakpoints.h\"\n#include \"cpu_internal.h\"\n#include \"espresso/espresso_instructionset.h\"\n#include \"interpreter.h\"\n#include \"interpreter_insreg.h\"\n#include \"mem.h\"\n#include \"trace.h\"\n\n#include <cfenv>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n\nnamespace cpu\n{\n\nnamespace interpreter\n{\n\nstatic std::vector<instrfptr_t>\nsInstructionMap;\n\nvoid\ninitialise()\n{\n   sInstructionMap.resize(static_cast<size_t>(espresso::InstructionID::InstructionCount), nullptr);\n\n   // Register instruction handlers\n   registerBranchInstructions();\n   registerConditionInstructions();\n   registerFloatInstructions();\n   registerIntegerInstructions();\n   registerLoadStoreInstructions();\n   registerPairedInstructions();\n   registerSystemInstructions();\n}\n\ninstrfptr_t\ngetInstructionHandler(espresso::InstructionID id)\n{\n   auto instrId = static_cast<size_t>(id);\n\n   if (instrId >= sInstructionMap.size()) {\n      return nullptr;\n   }\n\n   return sInstructionMap[instrId];\n}\n\nvoid\nregisterInstruction(espresso::InstructionID id, instrfptr_t fptr)\n{\n   sInstructionMap[static_cast<size_t>(id)] = fptr;\n}\n\nbool\nhasInstruction(espresso::InstructionID id)\n{\n   return getInstructionHandler(id) != nullptr;\n}\n\nCore *\nstep_one(Core *core)\n{\n   // Check if we hit any breakpoints\n   if (testBreakpoint(core->nia)) {\n      core->interrupt.fetch_or(DBGBREAK_INTERRUPT);\n      this_core::checkInterrupts();\n   }\n\n   auto cia = core->nia;\n   core->cia = cia;\n   core->nia = cia + 4;\n\n   auto instr = mem::read<espresso::Instruction>(cia);\n   auto data = espresso::decodeInstruction(instr);\n\n   if (!data) {\n      gLog->error(\"Could not decode instruction at {:08x} = {:08x}\", cia, instr.value);\n   }\n   decaf_check(data);\n\n   auto trace = traceInstructionStart(instr, data, core);\n   auto fptr = sInstructionMap[static_cast<size_t>(data->id)];\n\n   if (!fptr) {\n      gLog->error(\"Unimplemented interpreter instruction {}\", data->name);\n   }\n   decaf_check(fptr);\n\n   fptr(core, instr);\n\n   if (data->id == InstructionID::kc) {\n      // If this is a KC, there is the potential that we are running on a\n      //  different core now.  Lets make sure that we are using the right one.\n      core = this_core::state();\n   }\n\n   decaf_check(core->cia == cia);\n   traceInstructionEnd(trace, instr, data, core);\n   return core;\n}\n\nvoid\nresume()\n{\n   // Before we resume, we need to update our states!\n   this_core::updateRoundingMode();\n   std::feclearexcept(FE_ALL_EXCEPT);\n\n   auto core = cpu::this_core::state();\n   while (core->nia != cpu::CALLBACK_ADDR) {\n      this_core::checkInterrupts();\n      core = step_one(this_core::state());\n   }\n}\n\n} // namespace interpreter\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter.h",
    "content": "#pragma once\n#include \"cpu.h\"\n\nnamespace cpu\n{\n\nnamespace interpreter\n{\n\nvoid\ninitialise();\n\nCore *\nstep_one(Core *core);\n\nvoid\nresume();\n\n} // namespace interpreter\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter_branch.cpp",
    "content": "#include <common/bitutils.h>\n#include \"cpu_internal.h\"\n#include \"interpreter_insreg.h\"\n\nstatic void\nb(cpu::Core *state, Instruction instr)\n{\n   uint32_t nia;\n   nia = sign_extend<26>(instr.li << 2);\n\n   if (!instr.aa) {\n      nia += state->cia;\n   }\n\n   state->nia = nia;\n\n   if (instr.lk) {\n      state->lr = state->cia + 4;\n   }\n\n   if (cpu::gBranchTraceHandler) {\n      cpu::gBranchTraceHandler(state, state->nia);\n   }\n}\n\n// Branch Conditional\nenum BoBits\n{\n   CtrValue    = 1,\n   NoCheckCtr  = 2,\n   CondValue   = 3,\n   NoCheckCond = 4\n};\n\nenum BcFlags\n{\n   BcCheckCtr  = 1 << 0,\n   BcCheckCond = 1 << 1,\n   BcBranchLR  = 1 << 2,\n   BcBranchCTR = 1 << 3\n};\n\ntemplate<unsigned flags>\nstatic void\nbcGeneric(cpu::Core *state, Instruction instr)\n{\n   auto bo = instr.bo;\n   auto ctr_ok = true;\n   auto cond_ok = true;\n\n   if constexpr (!!(flags & BcCheckCtr)) {\n      if (!get_bit<NoCheckCtr>(bo)) {\n         state->ctr--;\n\n         auto ctb = static_cast<uint32_t>(state->ctr != 0);\n         auto ctv = get_bit<CtrValue>(bo);\n         ctr_ok = !!(ctb ^ ctv);\n      }\n   }\n\n   if constexpr (!!(flags & BcCheckCond)) {\n      if (!get_bit<NoCheckCond>(bo)) {\n         auto crb = get_bit(state->cr.value, 31 - instr.bi);\n         auto crv = get_bit<CondValue>(bo);\n         cond_ok = (crb == crv);\n      }\n   }\n\n   if (ctr_ok && cond_ok) {\n      uint32_t nia;\n\n      if constexpr (!!(flags & BcBranchCTR)) {\n         nia = state->ctr & ~0x3;\n      } else if constexpr (!!(flags & BcBranchLR)) {\n         nia = state->lr & ~0x3;\n      } else {\n         nia = sign_extend<16>(instr.bd << 2);\n\n         if (!instr.aa) {\n            nia += state->cia;\n         }\n      }\n\n      state->nia = nia;\n\n      if (instr.lk) {\n         state->lr = state->cia + 4;\n      }\n   }\n\n   if (cpu::gBranchTraceHandler) {\n      cpu::gBranchTraceHandler(state, state->nia);\n   }\n}\n\n// Branch Conditional\nstatic void\nbc(cpu::Core *state, Instruction instr)\n{\n   return bcGeneric<BcCheckCtr | BcCheckCond>(state, instr);\n}\n\n// Branch Conditional to CTR\nstatic void\nbcctr(cpu::Core *state, Instruction instr)\n{\n   return bcGeneric<BcBranchCTR | BcCheckCond>(state, instr);\n}\n\n// Branch Conditional to LR\nstatic void\nbclr(cpu::Core *state, Instruction instr)\n{\n   return bcGeneric<BcBranchLR | BcCheckCtr | BcCheckCond>(state, instr);\n}\n\nvoid\ncpu::interpreter::registerBranchInstructions()\n{\n   RegisterInstruction(b);\n   RegisterInstruction(bc);\n   RegisterInstruction(bcctr);\n   RegisterInstruction(bclr);\n}\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter_condition.cpp",
    "content": "#include <utility>\n#include <cfenv>\n#include \"interpreter_float.h\"\n#include \"interpreter_insreg.h\"\n#include <common/bitutils.h>\n#include <common/floatutils.h>\n\nusing espresso::ConditionRegisterFlag;\nusing espresso::FpscrFlags;\n\n// TODO: Move these getCRX functions\n\nstatic std::pair<uint32_t, uint32_t>\ngetCRFRange(uint32_t field)\n{\n   auto me = ((8 - field) * 4) - 1;\n   auto mb = me - 3;\n   return { mb, me };\n}\n\nuint32_t\ngetCRF(cpu::Core *state, uint32_t field)\n{\n   auto bits = getCRFRange(field);\n   return (state->cr.value >> bits.first) & 0xf;\n}\n\nvoid\nsetCRF(cpu::Core *state, uint32_t field, uint32_t value)\n{\n   auto cr = state->cr.value;\n   auto bits = getCRFRange(field);\n   auto mask = make_bitmask<uint32_t>(bits.first, bits.second);\n\n   cr = (cr & ~mask) | (value << bits.first);\n   state->cr.value = cr;\n}\n\nuint32_t\ngetCRB(cpu::Core *state, uint32_t bit)\n{\n   return get_bit(state->cr.value, 31 - bit);\n}\n\nvoid\nsetCRB(cpu::Core *state, uint32_t bit, uint32_t value)\n{\n   state->cr.value = set_bit_value(state->cr.value, 31 - bit, value);\n}\n\n// Compare\nenum CmpFlags\n{\n   CmpImmediate = 1 << 0, // b = imm\n};\n\ntemplate<typename Type, unsigned flags = 0>\nstatic void\ncmpGeneric(cpu::Core *state, Instruction instr)\n{\n   Type a, b;\n   uint32_t c;\n\n   a = state->gpr[instr.rA];\n\n   if constexpr (flags & CmpImmediate) {\n      if (std::is_signed<Type>::value) {\n         b = sign_extend<16>(instr.simm);\n      } else {\n         b = instr.uimm;\n      }\n   } else {\n      b = state->gpr[instr.rB];\n   }\n\n   if (a < b) {\n      c = ConditionRegisterFlag::LessThan;\n   } else if (a > b) {\n      c = ConditionRegisterFlag::GreaterThan;\n   } else {\n      c = ConditionRegisterFlag::Equal;\n   }\n\n   if (state->xer.so) {\n      c |= ConditionRegisterFlag::SummaryOverflow;\n   }\n\n   setCRF(state, instr.crfD, c);\n}\n\nstatic void\ncmp(cpu::Core *state, Instruction instr)\n{\n   return cmpGeneric<int32_t>(state, instr);\n}\n\nstatic void\ncmpi(cpu::Core *state, Instruction instr)\n{\n   return cmpGeneric<int32_t, CmpImmediate>(state, instr);\n}\n\nstatic void\ncmpl(cpu::Core *state, Instruction instr)\n{\n   return cmpGeneric<uint32_t>(state, instr);\n}\n\nstatic void\ncmpli(cpu::Core *state, Instruction instr)\n{\n   return cmpGeneric<uint32_t, CmpImmediate>(state, instr);\n}\n\n// Floating Compare\nenum FCmpFlags\n{\n   FCmpOrdered    = 1 << 0,\n   FCmpUnordered  = 1 << 1,\n   FCmpPS1        = 1 << 2,\n};\n\ntemplate<unsigned flags>\nstatic void\nfcmpGeneric(cpu::Core *state, Instruction instr)\n{\n   double a, b;\n   uint32_t c;\n\n   if constexpr (!!(flags & FCmpPS1)) {\n      a = state->fpr[instr.frA].paired1;\n      b = state->fpr[instr.frB].paired1;\n   } else {\n      a = state->fpr[instr.frA].value;\n      b = state->fpr[instr.frB].value;\n   }\n\n   const uint32_t oldFPSCR = state->fpscr.value;\n\n   if (is_nan(a) || is_nan(b)) {\n      c = ConditionRegisterFlag::Unordered;\n      const bool vxsnan = is_signalling_nan(a) || is_signalling_nan(b);\n      state->fpscr.vxsnan |= vxsnan;\n      if constexpr (flags & FCmpOrdered) {\n         if (!(vxsnan && state->fpscr.ve)) {\n            state->fpscr.vxvc = 1;\n         }\n      }\n   } else if (a < b) {\n      c = ConditionRegisterFlag::LessThan;\n   } else if (a > b) {\n      c = ConditionRegisterFlag::GreaterThan;\n   } else {  // a == b\n      c = ConditionRegisterFlag::Equal;\n   }\n\n   setCRF(state, instr.crfD, c);\n   state->fpscr.fpcc = c;\n   updateFX_FEX_VX(state, oldFPSCR);\n}\n\nstatic void\nfcmpo(cpu::Core *state, Instruction instr)\n{\n   return fcmpGeneric<FCmpOrdered>(state, instr);\n}\n\nstatic void\nfcmpu(cpu::Core *state, Instruction instr)\n{\n   return fcmpGeneric<FCmpUnordered>(state, instr);\n}\n\nstatic void\nps_cmpo0(cpu::Core *state, Instruction instr)\n{\n   return fcmpGeneric<FCmpOrdered>(state, instr);\n}\n\nstatic void\nps_cmpo1(cpu::Core *state, Instruction instr)\n{\n   return fcmpGeneric<FCmpOrdered | FCmpPS1>(state, instr);\n}\n\nstatic void\nps_cmpu0(cpu::Core *state, Instruction instr)\n{\n   return fcmpGeneric<FCmpUnordered>(state, instr);\n}\n\nstatic void\nps_cmpu1(cpu::Core *state, Instruction instr)\n{\n   return fcmpGeneric<FCmpUnordered | FCmpPS1>(state, instr);\n}\n\n// Condition Register AND\nstatic void\ncrand(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, b, d;\n   a = getCRB(state, instr.crbA);\n   b = getCRB(state, instr.crbB);\n\n   d = a & b;\n   setCRB(state, instr.crbD, d);\n}\n\n// Condition Register AND with Complement\nstatic void\ncrandc(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, b, d;\n   a = getCRB(state, instr.crbA);\n   b = getCRB(state, instr.crbB);\n\n   d = a & ~b;\n   setCRB(state, instr.crbD, d);\n}\n\n// Condition Register Equivalent\nstatic void\ncreqv(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, b, d;\n   a = getCRB(state, instr.crbA);\n   b = getCRB(state, instr.crbB);\n\n   d = ~(a ^ b);\n   setCRB(state, instr.crbD, d);\n}\n\n// Condition Register NAND\nstatic void\ncrnand(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, b, d;\n   a = getCRB(state, instr.crbA);\n   b = getCRB(state, instr.crbB);\n\n   d = ~(a & b);\n   setCRB(state, instr.crbD, d);\n}\n\n// Condition Register NOR\nstatic void\ncrnor(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, b, d;\n   a = getCRB(state, instr.crbA);\n   b = getCRB(state, instr.crbB);\n\n   d = ~(a | b);\n   setCRB(state, instr.crbD, d);\n}\n\n// Condition Register OR\nstatic void\ncror(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, b, d;\n   a = getCRB(state, instr.crbA);\n   b = getCRB(state, instr.crbB);\n\n   d = a | b;\n   setCRB(state, instr.crbD, d);\n}\n\n// Condition Register OR with Complement\nstatic void\ncrorc(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, b, d;\n   a = getCRB(state, instr.crbA);\n   b = getCRB(state, instr.crbB);\n\n   d = a | ~b;\n   setCRB(state, instr.crbD, d);\n}\n\n// Condition Register XOR\nstatic void\ncrxor(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, b, d;\n   a = getCRB(state, instr.crbA);\n   b = getCRB(state, instr.crbB);\n\n   d = a ^ b;\n   setCRB(state, instr.crbD, d);\n}\n\n// Move Condition Register Field\nstatic void\nmcrf(cpu::Core *state, Instruction instr)\n{\n   setCRF(state, instr.crfD, getCRF(state, instr.crfS));\n}\n\n// Move to Condition Register from FPSCR\nstatic void\nmcrfs(cpu::Core *state, Instruction instr)\n{\n   const int shiftS = 4 * (7 - instr.crfS);\n\n   const uint32_t fpscrBits = (state->fpscr.value >> shiftS) & 0xF;\n   setCRF(state, instr.crfD, fpscrBits);\n\n   // All exception bits copied are cleared; other bits are left alone.\n   // FEX and VX are updated following the normal rules.\n   const uint32_t exceptionBits = FpscrFlags::FX | FpscrFlags::AllExceptions;\n   const uint32_t bitsToClear = exceptionBits & (0xF << shiftS);\n   state->fpscr.value &= ~bitsToClear;\n   updateFEX_VX(state);\n}\n\n// Move to Condition Register from XER\nstatic void\nmcrxr(cpu::Core *state, Instruction instr)\n{\n   setCRF(state, instr.crfD, state->xer.crxr);\n   state->xer.crxr = 0;\n}\n\n// Move from Condition Register\nstatic void\nmfcr(cpu::Core *state, Instruction instr)\n{\n   state->gpr[instr.rD] = state->cr.value;\n}\n\n// Move to Condition Register Fields\nstatic void\nmtcrf(cpu::Core *state, Instruction instr)\n{\n   uint32_t cr, crm, s, mask;\n\n   s = state->gpr[instr.rS];\n   cr = state->cr.value;\n   crm = instr.crm;\n   mask = 0;\n\n   for (auto i = 0u; i < 8; ++i) {\n      if (crm & (1 << i)) {\n         mask |= 0xf << (i * 4);\n      }\n   }\n\n   cr = (s & mask) | (cr & ~mask);\n   state->cr.value = cr;\n}\n\nvoid\ncpu::interpreter::registerConditionInstructions()\n{\n   RegisterInstruction(cmp);\n   RegisterInstruction(cmpi);\n   RegisterInstruction(cmpl);\n   RegisterInstruction(cmpli);\n   RegisterInstruction(fcmpo);\n   RegisterInstruction(fcmpu);\n   RegisterInstruction(crand);\n   RegisterInstruction(crandc);\n   RegisterInstruction(creqv);\n   RegisterInstruction(crnand);\n   RegisterInstruction(crnor);\n   RegisterInstruction(cror);\n   RegisterInstruction(crorc);\n   RegisterInstruction(crxor);\n   RegisterInstruction(mcrf);\n   RegisterInstruction(mcrfs);\n   RegisterInstruction(mcrxr);\n   RegisterInstruction(mfcr);\n   RegisterInstruction(mtcrf);\n   RegisterInstruction(ps_cmpu0);\n   RegisterInstruction(ps_cmpo0);\n   RegisterInstruction(ps_cmpu1);\n   RegisterInstruction(ps_cmpo1);\n}\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter_float.cpp",
    "content": "#include <cfenv>\n#include <cmath>\n#include <numeric>\n#include \"../cpu_internal.h\"\n#include \"interpreter.h\"\n#include \"interpreter_float.h\"\n#include \"interpreter_insreg.h\"\n#include <common/bitutils.h>\n#include <common/floatutils.h>\n#include <common/platform_compiler.h>\n\nusing espresso::FpscrFlags;\nusing espresso::FloatingPointResultFlags;\nusing espresso::FloatingPointRoundMode;\n\nconst uint16_t fres_base[] =\n{\n   0x3FFC, 0x3C1C, 0x3875, 0x3504, 0x31C4, 0x2EB1, 0x2BC8, 0x2904,\n   0x2664, 0x23E5, 0x2184, 0x1F40, 0x1D16, 0x1B04, 0x190A, 0x1725,\n   0x1554, 0x1396, 0x11EB, 0x104F, 0x0EC4, 0x0D48, 0x0BD7, 0x0A7C,\n   0x0922, 0x07DF, 0x069C, 0x056F, 0x0442, 0x0328, 0x020E, 0x0106,\n};\n\nconst uint16_t fres_delta[] =\n{\n   0x3E1, 0x3A7, 0x371, 0x340, 0x313, 0x2EA, 0x2C4, 0x2A0,\n   0x27F, 0x261, 0x245, 0x22A, 0x212, 0x1FB, 0x1E5, 0x1D1,\n   0x1BE, 0x1AC, 0x19B, 0x18B, 0x17C, 0x16E, 0x15B, 0x15B,\n   0x143, 0x143, 0x12D, 0x12D, 0x11A, 0x11A, 0x108, 0x106,\n};\n\nconst uint16_t frsqrte_base[] =\n{\n   0x7FF4, 0x7852, 0x7154, 0x6AE4, 0x64F2, 0x5F6E, 0x5A4C, 0x5580,\n   0x5102, 0x4CCA, 0x48D0, 0x450E, 0x4182, 0x3E24, 0x3AF2, 0x37E8,\n   0x34FD, 0x2F97, 0x2AA5, 0x2618, 0x21E4, 0x1DFE, 0x1A5C, 0x16F8,\n   0x13CA, 0x10CE, 0x0DFE, 0x0B57, 0x08D4, 0x0673, 0x0431, 0x020B,\n};\n\nconst uint16_t frsqrte_delta[] =\n{\n   0x7A4, 0x700, 0x670, 0x5F2, 0x584, 0x524, 0x4CC, 0x47E,\n   0x43A, 0x3FA, 0x3C2, 0x38E, 0x35E, 0x332, 0x30A, 0x2E6,\n   0x568, 0x4F3, 0x48D, 0x435, 0x3E7, 0x3A2, 0x365, 0x32E,\n   0x2FC, 0x2D0, 0x2A8, 0x283, 0x261, 0x243, 0x226, 0x20B,\n};\n\nfloat\nppc_estimate_reciprocal(float v)\n{\n   auto bits = get_float_bits(v);\n\n   // Check for an infinite or NaN input.\n   if (bits.exponent == bits.exponent_max) {\n      if (bits.mantissa == 0) {\n         return std::copysign(0.0f, v);\n      } else {\n         std::feraiseexcept(FE_INVALID);\n         return make_quiet(v);\n      }\n   }\n\n   // Check for a zero or denormal input.\n   int exponent = bits.exponent;\n   uint32_t mantissa = bits.mantissa;\n   if (exponent == 0) {\n      if (mantissa == 0) {\n         std::feraiseexcept(FE_DIVBYZERO);\n         return std::copysign(std::numeric_limits<float>::infinity(), v);\n      } else if (mantissa < 0x200000) {\n         std::feraiseexcept(FE_OVERFLOW | FE_INEXACT);\n         return std::copysign(std::numeric_limits<float>::max(), v);\n      } else if (mantissa < 0x400000) {\n         exponent = -1;\n         mantissa = (mantissa << 2) & 0x7FFFFF;\n      } else {\n         mantissa = (mantissa << 1) & 0x7FFFFF;\n      }\n   }\n\n   // Calculate the result.  The lookup result has 24 bits; the high 23 bits\n   // are copied to the mantissa of the output (except for denormals), and\n   // the lowest bit is copied to FPSCR[FI].\n   int new_exponent = 253 - exponent;\n   int table_index = mantissa >> 18;\n   int delta_mult = (mantissa >> 8) & 0x3FF;\n   uint32_t lookup_result = ((fres_base[table_index] << 10)\n                             - (fres_delta[table_index] * delta_mult));\n   uint32_t new_mantissa = lookup_result >> 1;\n   bool fpscr_FI = lookup_result & 1;\n\n   // Denormalize the result if necessary.\n   if (new_exponent <= 0) {\n      fpscr_FI |= new_mantissa & 1;\n      new_mantissa = (new_mantissa >> 1) | 0x400000;\n      if (new_exponent < 0) {\n         fpscr_FI |= new_mantissa & 1;\n         new_mantissa >>= 1;\n         new_exponent = 0;\n      }\n      if (fpscr_FI) {\n         std::feraiseexcept(FE_UNDERFLOW);\n      }\n   }\n\n   if (fpscr_FI) {\n      std::feraiseexcept(FE_INEXACT);\n   }\n   bits.exponent = new_exponent;\n   bits.mantissa = new_mantissa;\n   return bits.v;\n}\n\ndouble\nppc_estimate_reciprocal_root(double v)\n{\n   auto bits = get_float_bits(v);\n\n   // Check for an infinite or NaN input.\n   if (bits.exponent == bits.exponent_max) {\n      if (bits.mantissa == 0) {\n         if (bits.sign) {\n            std::feraiseexcept(FE_INVALID);\n            return make_nan<double>();\n         } else {\n            return 0.0;\n         }\n      } else {\n         std::feraiseexcept(FE_INVALID);\n         return make_quiet(v);\n      }\n   }\n\n   // Check for a zero or denormal input.\n   int exponent = bits.exponent;\n   uint64_t mantissa = bits.mantissa;\n   if (exponent == 0) {\n      if (mantissa == 0) {\n         std::feraiseexcept(FE_DIVBYZERO);\n         return std::copysign(std::numeric_limits<float>::infinity(), v);\n      } else {\n         int shift = clz64(mantissa) - 11;\n         mantissa = (mantissa << shift) & UINT64_C(0xFFFFFFFFFFFFF);\n         exponent -= shift - 1;\n      }\n   }\n\n   // Negative nonzero values are always invalid.  If we get this far, we\n   // know the value is not zero or NaN.\n   if (bits.sign) {\n      std::feraiseexcept(FE_INVALID);\n      return make_nan<double>();\n   }\n\n   // Calculate the result.\n   int new_exponent = (3068 - exponent) / 2;\n   int table_index = ((mantissa >> 48) & 15) | (exponent & 1 ? 0 : 16);\n   int delta_mult = (mantissa >> 37) & 0x7FF;\n   uint64_t lookup_result = ((frsqrte_base[table_index] << 11)\n                             - (frsqrte_delta[table_index] * delta_mult));\n   uint64_t new_mantissa = lookup_result << 26;\n\n   bits.exponent = new_exponent;\n   bits.mantissa = new_mantissa;\n   return bits.v;\n}\n\nvoid\nupdateFEX_VX(cpu::Core *state)\n{\n   auto &fpscr = state->fpscr;\n\n   // Invalid Operation Summary\n   fpscr.vx =\n        fpscr.vxsnan\n      | fpscr.vxisi\n      | fpscr.vxidi\n      | fpscr.vxzdz\n      | fpscr.vximz\n      | fpscr.vxvc\n      | fpscr.vxsqrt\n      | fpscr.vxsoft\n      | fpscr.vxcvi;\n\n   // FP Enabled Exception Summary\n   fpscr.fex =\n        (fpscr.vx & fpscr.ve)\n      | (fpscr.ox & fpscr.oe)\n      | (fpscr.ux & fpscr.ue)\n      | (fpscr.zx & fpscr.ze)\n      | (fpscr.xx & fpscr.xe);\n}\n\n\nvoid\nupdateFX_FEX_VX(cpu::Core *state, uint32_t oldValue)\n{\n   auto &fpscr = state->fpscr;\n\n   updateFEX_VX(state);\n\n   // FP Exception Summary\n   const uint32_t newBits = (oldValue ^ fpscr.value) & fpscr.value;\n   if (newBits & FpscrFlags::AllExceptions) {\n      fpscr.fx = 1;\n   }\n}\n\nvoid\nupdateFPSCR(cpu::Core *state, uint32_t oldValue)\n{\n   auto except = std::fetestexcept(FE_ALL_EXCEPT);\n   auto round = std::fegetround();\n   auto &fpscr = state->fpscr;\n\n   // Underflow\n   fpscr.ux |= !!(except & FE_UNDERFLOW);\n\n   // Overflow\n   fpscr.ox |= !!(except & FE_OVERFLOW);\n\n   // Zerodivide\n   fpscr.zx |= !!(except & FE_DIVBYZERO);\n\n   // Inexact\n   fpscr.fi = !!(except & FE_INEXACT);\n   fpscr.xx |= fpscr.fi;\n\n   // Fraction Rounded\n   fpscr.fr = !!(round & FE_UPWARD);\n\n   updateFX_FEX_VX(state, oldValue);\n\n   std::feclearexcept(FE_ALL_EXCEPT);\n}\n\ntemplate<typename Type>\nvoid\nupdateFPRF(cpu::Core *state, Type value)\n{\n   auto cls = std::fpclassify(value);\n   auto flags = 0u;\n\n   if (cls == FP_NAN) {\n      flags |= FloatingPointResultFlags::ClassDescriptor;\n      flags |= FloatingPointResultFlags::Unordered;\n   } else if (value != 0) {\n      if (value > 0) {\n         flags |= FloatingPointResultFlags::Positive;\n      } else {\n         flags |= FloatingPointResultFlags::Negative;\n      }\n      if (cls == FP_INFINITE) {\n         flags |= FloatingPointResultFlags::Unordered;\n      } else if (cls == FP_SUBNORMAL) {\n         flags |= FloatingPointResultFlags::ClassDescriptor;\n      }\n   } else {\n      flags |= FloatingPointResultFlags::Equal;\n      if (std::signbit(value)) {\n         flags |= FloatingPointResultFlags::ClassDescriptor;\n      }\n   }\n\n   state->fpscr.fprf = flags;\n}\n\n// Make sure both float and double versions are available to other sources:\ntemplate void updateFPRF(cpu::Core *state, float value);\ntemplate void updateFPRF(cpu::Core *state, double value);\n\nvoid\nupdateFloatConditionRegister(cpu::Core *state)\n{\n   state->cr.cr1 = state->fpscr.cr1;\n}\n\n// Helper for fmuls/fmadds to round the second (frC) operand appropriately.\n// May also need to modify the first operand, so both operands are passed\n// by reference.\nvoid\nroundForMultiply(double *a, double *c)\n{\n   // The mantissa is truncated from 52 to 24 bits, so bit 27 (counting from\n   // the LSB) is rounded.\n   const uint64_t roundBit = UINT64_C(1) << 27;\n\n   FloatBitsDouble aBits = get_float_bits(*a);\n   FloatBitsDouble cBits = get_float_bits(*c);\n\n   // If the second operand has no bits that would be rounded, this whole\n   // function is a no-op, so skip out early.\n   if (!(cBits.uv & ((roundBit << 1) - 1))) {\n      return;\n   }\n\n   // If the first operand is zero, the result is always zero (even if the\n   // second operand would round to infinity), so avoid generating any\n   // exceptions.\n   if (is_zero(*a)) {\n      return;\n   }\n\n   // If the first operand is infinity and the second is not zero, the result\n   // is always infinity; get out now so we don't have to worry about it in\n   // normalization.\n   if (is_infinity(*a)) {\n      return;\n   }\n\n   // If the second operand is a denormal, we normalize it before rounding,\n   // adjusting the exponent of the other operand accordingly.  If the\n   // other operand becomes denormal, the product will round to zero in any\n   // case, so we just abort and let the operation proceed normally.\n   if (is_denormal(*c)) {\n      auto cSign = cBits.sign;\n      while (cBits.exponent == 0) {\n         cBits.uv <<= 1;\n         if (aBits.exponent == 0) {\n            return;\n         }\n         aBits.exponent--;\n      }\n      cBits.sign = cSign;\n   }\n\n   // Perform the rounding.  If this causes the value to go to infinity,\n   // we move a power of two to the other operand (if possible) for the\n   // case of an FMA operation in which we need to keep precision for the\n   // intermediate result.  Note that this particular rounding operation\n   // ignores FPSCR[RN].\n   cBits.uv &= -static_cast<int64_t>(roundBit);\n   cBits.uv += cBits.uv & roundBit;\n   if (is_infinity(cBits.v)) {\n      cBits.exponent--;\n      if (aBits.exponent == 0) {\n         auto aSign = aBits.sign;\n         aBits.uv <<= 1;\n         aBits.sign = aSign;\n      } else if (aBits.exponent < aBits.exponent_max - 1) {\n         aBits.exponent++;\n      } else {\n         // The product will overflow anyway, so just leave the first\n         // operand alone and let the host FPU raise exceptions as\n         // appropriate.\n      }\n   }\n\n   *a = aBits.v;\n   *c = cBits.v;\n}\n\n// Helper for fmadds to properly round to single precision.  Assumes\n// round-to-nearest mode.\nCLANG_FPU_BUG_WORKAROUND\nfloat\nroundFMAResultToSingle(double result, double a, double b, double c)\n{\n   if (is_zero(a) || is_zero(b) || is_zero(c)) {\n      return static_cast<float>(result);  // Can't lose precision if one addend is zero.\n   }\n\n   auto resultBits = get_float_bits(result);\n\n   if (resultBits.exponent < 874 || resultBits.exponent > 1150) {\n      return static_cast<float>(result);  // Out of range or inf/NaN.\n   }\n\n   uint64_t centerValue = 1<<28;\n   uint64_t centerMask = (centerValue << 1) - 1;\n   if (resultBits.exponent < 897) {\n      centerValue <<= 897 - resultBits.exponent;\n      centerMask <<= 897 - resultBits.exponent;\n   }\n   if ((resultBits.mantissa & centerMask) != centerValue) {\n      return static_cast<float>(result);  // Not exactly between two single-precision values.\n   }\n\n   // Repeat the operation in round-toward-zero mode to determine which way\n   // to round the result.\n   const int oldRound = fegetround();\n   fesetround(FE_TOWARDZERO);\n   feclearexcept(FE_INEXACT);\n\n   double test = fma(a, c, b);\n\n   fesetround(oldRound);\n\n   // We only need to adjust the result if there were dropped bits.\n   // If the truncated result is equal to the original (rounded) result,\n   // it means the infinitely precise result is greater than the rounded\n   // value, so we add 1 ulp (unit in the last place) to force the value\n   // to round up when we convert it to float; likewise, if the truncated\n   // result is different, we subtract 1 ulp to force rounding down.\n   // In both cases, we know the result mantissa is nonzero so it's safe\n   // to just increment or decrement the entire value as an integer.\n   if (fetestexcept(FE_INEXACT)) {\n      if (get_float_bits(test).uv == resultBits.uv) {\n         resultBits.uv++;\n      } else {\n         resultBits.uv--;\n      }\n   }\n\n   return static_cast<float>(resultBits.v);\n}\n\n\n// Floating Arithmetic\nenum FPArithOperator {\n    FPAdd,\n    FPSub,\n    FPMul,\n    FPDiv,\n};\ntemplate<FPArithOperator op, typename Type>\nstatic void\nfpArithGeneric(cpu::Core *state, Instruction instr)\n{\n   double a, b;\n   Type d;\n\n   a = state->fpr[instr.frA].value;\n   b = state->fpr[op == FPMul ? instr.frC : instr.frB].value;\n\n   const bool vxsnan = is_signalling_nan(a) || is_signalling_nan(b);\n   bool vxisi, vximz, vxidi, vxzdz, zx;\n\n   if constexpr (op == FPAdd) {\n      vxisi = is_infinity(a) && is_infinity(b) && std::signbit(a) != std::signbit(b);\n      vximz = false;\n      vxidi = false;\n      vxzdz = false;\n      zx = false;\n   } else if constexpr (op == FPSub) {\n      vxisi = is_infinity(a) && is_infinity(b) && std::signbit(a) == std::signbit(b);\n      vximz = false;\n      vxidi = false;\n      vxzdz = false;\n      zx = false;\n   } else if constexpr (op == FPMul) {\n      vxisi = false;\n      vximz = (is_infinity(a) && is_zero(b)) || (is_zero(a) && is_infinity(b));\n      vxidi = false;\n      vxzdz = false;\n      zx = false;\n   } else if constexpr (op == FPDiv) {\n      vxisi = false;\n      vximz = false;\n      vxidi = is_infinity(a) && is_infinity(b);\n      vxzdz = is_zero(a) && is_zero(b);\n      zx = !(vxzdz || vxsnan) && is_zero(b);\n   } else {\n      static_assert(\"Unexpected flags for fpArithGeneric\");\n   }\n\n   const uint32_t oldFPSCR = state->fpscr.value;\n   state->fpscr.vxsnan |= vxsnan;\n   state->fpscr.vxisi |= vxisi;\n   state->fpscr.vximz |= vximz;\n   state->fpscr.vxidi |= vxidi;\n   state->fpscr.vxzdz |= vxzdz;\n\n   if ((vxsnan || vxisi || vximz || vxidi || vxzdz) && state->fpscr.ve) {\n      updateFX_FEX_VX(state, oldFPSCR);\n   } else if (zx && state->fpscr.ze) {\n      state->fpscr.zx = 1;\n      updateFX_FEX_VX(state, oldFPSCR);\n   } else {\n      if (is_nan(a)) {\n         d = static_cast<Type>(make_quiet(a));\n      } else if (is_nan(b)) {\n         d = static_cast<Type>(make_quiet(b));\n      } else if (vxisi || vximz || vxidi || vxzdz) {\n         d = make_nan<Type>();\n      } else {\n         // The Espresso appears to use double precision arithmetic even for\n         // single-precision instructions (for example, 2^128 * 0.5 does not\n         // cause overflow), so we do the same here.\n         if constexpr (op == FPAdd) {\n            d = static_cast<Type>(a + b);\n         } else if constexpr (op == FPSub) {\n            d = static_cast<Type>(a - b);\n         } else if constexpr (op == FPMul) {\n            // But!  The second operand to a single-precision multiply\n            // operation is rounded to 24 bits.\n            if constexpr (std::is_same<Type, float>::value) {\n               roundForMultiply(&a, &b);\n            }\n            d = static_cast<Type>(a * b);\n         } else if constexpr (op == FPDiv) {\n            d = static_cast<Type>(a / b);\n         } else {\n            static_assert(\"Unexpected flags for fpArithGeneric\");\n         }\n      }\n\n      // The PowerPC signals underflow if the result is tiny before rounding;\n      // this differs from x86, which signals underflow only if the result\n      // is tiny after rounding.  Sadly, IEEE allows both of these behaviors,\n      // so we can't just say that one is broken, and we have to handle this\n      // case manually.  If the rounded result is equal in magnitude to the\n      // minimum normal value for the type, then the unrounded result may\n      // have had a smaller magnitude, so we temporarily set the rounding\n      // mode to round-toward-zero and repeat the operation, discarding the\n      // result but allowing it to set the underflow flag if appropriate.\n      // (In round-toward-zero mode, any value which is tiny before rounding\n      // will also be tiny after rounding.)\n      if (possibleUnderflow<Type>(d)) {\n         const int oldRound = fegetround();\n         fesetround(FE_TOWARDZERO);\n\n         // Use volatile variables to force the operation to be performed\n         // and prevent the previous result from being reused.\n         volatile double bTemp = b;\n         volatile Type dummy;\n         if constexpr (op == FPAdd) {\n            dummy = static_cast<Type>(a + bTemp);\n         } else if constexpr (op == FPSub) {\n            dummy = static_cast<Type>(a - bTemp);\n         } else if constexpr (op == FPMul) {\n            // a and b have already been rounded if necessary.\n            dummy = static_cast<Type>(a * bTemp);\n         } else if constexpr (op == FPDiv) {\n            dummy = static_cast<Type>(a / bTemp);\n         } else {\n            static_assert(\"Unexpected flags for fpArithGeneric\");\n         }\n\n         fesetround(oldRound);\n      }\n\n      if constexpr (std::is_same<Type, float>::value) {\n         double dd = extend_float(d);\n         state->fpr[instr.frD].paired0 = dd;\n         state->fpr[instr.frD].paired1 = dd;\n         updateFPRF(state, d);\n      } else {\n         state->fpr[instr.frD].value = d;\n         updateFPRF(state, d);\n      }\n\n      updateFPSCR(state, oldFPSCR);\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Floating Add Double\nstatic void\nfadd(cpu::Core *state, Instruction instr)\n{\n   fpArithGeneric<FPAdd, double>(state, instr);\n}\n\n// Floating Add Single\nstatic void\nfadds(cpu::Core *state, Instruction instr)\n{\n   fpArithGeneric<FPAdd, float>(state, instr);\n}\n\n// Floating Divide Double\nstatic void\nfdiv(cpu::Core *state, Instruction instr)\n{\n   fpArithGeneric<FPDiv, double>(state, instr);\n}\n\n// Floating Divide Single\nstatic void\nfdivs(cpu::Core *state, Instruction instr)\n{\n   fpArithGeneric<FPDiv, float>(state, instr);\n}\n\n// Floating Multiply Double\nstatic void\nfmul(cpu::Core *state, Instruction instr)\n{\n   fpArithGeneric<FPMul, double>(state, instr);\n}\n\n// Floating Multiply Single\nstatic void\nfmuls(cpu::Core *state, Instruction instr)\n{\n   fpArithGeneric<FPMul, float>(state, instr);\n}\n\n// Floating Subtract Double\nstatic void\nfsub(cpu::Core *state, Instruction instr)\n{\n   fpArithGeneric<FPSub, double>(state, instr);\n}\n\n// Floating Subtract Single\nstatic void\nfsubs(cpu::Core *state, Instruction instr)\n{\n   fpArithGeneric<FPSub, float>(state, instr);\n}\n\n// Floating Reciprocal Estimate Single\nstatic void\nfres(cpu::Core *state, Instruction instr)\n{\n   double b;\n   float d;\n   b = state->fpr[instr.frB].value;\n\n   const bool vxsnan = is_signalling_nan(b);\n   const bool zx = is_zero(b);\n\n   const uint32_t oldFPSCR = state->fpscr.value;\n   state->fpscr.vxsnan |= vxsnan;\n\n   if (vxsnan && state->fpscr.ve) {\n      updateFX_FEX_VX(state, oldFPSCR);\n   } else if (zx && state->fpscr.ze) {\n      state->fpscr.zx = 1;\n      updateFX_FEX_VX(state, oldFPSCR);\n   } else {\n      d = ppc_estimate_reciprocal(static_cast<float>(b));\n      state->fpr[instr.frD].paired0 = d;\n      state->fpr[instr.frD].paired1 = d;\n      updateFPRF(state, d);\n      state->fpscr.zx |= zx;\n      if (std::fetestexcept(FE_INEXACT)) {\n         // On inexact result, fres sets FPSCR[FI] without also setting\n         // FPSCR[XX].\n         std::feclearexcept(FE_INEXACT);\n         updateFPSCR(state, oldFPSCR);\n         state->fpscr.fi = 1;\n      } else {\n         updateFPSCR(state, oldFPSCR);\n      }\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Floating Reciprocal Square Root Estimate\nstatic void\nfrsqrte(cpu::Core *state, Instruction instr)\n{\n   double b, d;\n   b = state->fpr[instr.frB].value;\n\n   const bool vxsnan = is_signalling_nan(b);\n   const bool vxsqrt = !vxsnan && b < 0.0;\n   const bool zx = is_zero(b);\n\n   const uint32_t oldFPSCR = state->fpscr.value;\n   state->fpscr.vxsnan |= vxsnan;\n   state->fpscr.vxsqrt |= vxsqrt;\n\n   if ((vxsnan || vxsqrt) && state->fpscr.ve) {\n      updateFX_FEX_VX(state, oldFPSCR);\n   } else if (zx && state->fpscr.ze) {\n      state->fpscr.zx = 1;\n      updateFX_FEX_VX(state, oldFPSCR);\n   } else {\n      d = ppc_estimate_reciprocal_root(b);\n      state->fpr[instr.frD].value = d;\n      updateFPRF(state, d);\n      state->fpscr.zx |= zx;\n      updateFPSCR(state, oldFPSCR);\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\nstatic void\nfsel(cpu::Core *state, Instruction instr)\n{\n   double a, b, c, d;\n   a = state->fpr[instr.frA].value;\n   b = state->fpr[instr.frB].value;\n   c = state->fpr[instr.frC].value;\n\n   if (a >= 0.0) {\n      d = c;\n   } else {\n      d = b;\n   }\n\n   state->fpr[instr.frD].value = d;\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Fused multiply-add instructions\nenum FMAFlags\n{\n   FMASubtract   = 1 << 0, // Subtract instead of add\n   FMANegate     = 1 << 1, // Negate result\n   FMASinglePrec = 1 << 2, // Round result to single precision\n};\n\ntemplate<unsigned flags>\nstatic void\nfmaGeneric(cpu::Core *state, Instruction instr)\n{\n   double a, b, c, d;\n   a = state->fpr[instr.frA].value;\n   b = state->fpr[instr.frB].value;\n   c = state->fpr[instr.frC].value;\n\n   const double addend = (flags & FMASubtract) ? -b : b;\n\n   const bool vxsnan = is_signalling_nan(a) || is_signalling_nan(b) || is_signalling_nan(c);\n   const bool vximz = (is_infinity(a) && is_zero(c)) || (is_zero(a) && is_infinity(c));\n   const bool vxisi = (!vximz && !is_nan(a) && !is_nan(c)\n                       && (is_infinity(a) || is_infinity(c)) && is_infinity(b)\n                       && (std::signbit(a) ^ std::signbit(c)) != std::signbit(addend));\n\n   const uint32_t oldFPSCR = state->fpscr.value;\n   state->fpscr.vxsnan |= vxsnan;\n   state->fpscr.vxisi |= vxisi;\n   state->fpscr.vximz |= vximz;\n\n   if ((vxsnan || vxisi || vximz) && state->fpscr.ve) {\n      updateFX_FEX_VX(state, oldFPSCR);\n   } else {\n      if (is_nan(a)) {\n         d = make_quiet(a);\n      } else if (is_nan(b)) {\n         d = make_quiet(b);\n      } else if (is_nan(c)) {\n         d = make_quiet(c);\n      } else if (vxisi || vximz) {\n         d = make_nan<double>();\n      } else {\n         if constexpr (!!(flags & FMASinglePrec)) {\n            roundForMultiply(&a, &c);\n         }\n\n         d = std::fma(a, c, addend);\n\n         bool checkUnderflow;\n         if constexpr (!!(flags & FMASinglePrec)) {\n            checkUnderflow = possibleUnderflow<float>(static_cast<float>(d));\n         } else {\n            checkUnderflow = possibleUnderflow<double>(d);\n         }\n\n         if (checkUnderflow) {\n            const int oldRound = fegetround();\n            fesetround(FE_TOWARDZERO);\n\n            volatile double addendTemp = addend;\n            if constexpr (!!(flags & FMASinglePrec)) {\n               volatile float dummy;\n               dummy = static_cast<float>(std::fma(a, c, addendTemp));\n            } else {\n               volatile double dummy;\n               dummy = std::fma(a, c, addendTemp);\n            }\n\n            fesetround(oldRound);\n         }\n\n         if constexpr (!!(flags & FMANegate)) {\n            d = -d;\n         }\n      }\n\n      if constexpr (!!(flags & FMASinglePrec)) {\n         float dFloat;\n         if (state->fpscr.rn == FloatingPointRoundMode::Nearest) {\n            dFloat = roundFMAResultToSingle(d, a, addend, c);\n         } else {\n            dFloat = static_cast<float>(d);\n         }\n         d = extend_float(dFloat);\n         state->fpr[instr.frD].paired0 = d;\n         state->fpr[instr.frD].paired1 = d;\n         updateFPRF(state, dFloat);\n      } else {\n         state->fpr[instr.frD].value = d;\n         updateFPRF(state, d);\n      }\n\n      updateFPSCR(state, oldFPSCR);\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Floating Multiply-Add\nstatic void\nfmadd(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<0>(state, instr);\n}\n\n// Floating Multiply-Add Single\nstatic void\nfmadds(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<FMASinglePrec>(state, instr);\n}\n\n// Floating Multiply-Sub\nstatic void\nfmsub(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<FMASubtract>(state, instr);\n}\n\n// Floating Multiply-Sub Single\nstatic void\nfmsubs(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<FMASubtract | FMASinglePrec>(state, instr);\n}\n\n// Floating Negative Multiply-Add\nstatic void\nfnmadd(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<FMANegate>(state, instr);\n}\n\n// Floating Negative Multiply-Add Single\nstatic void\nfnmadds(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<FMANegate | FMASinglePrec>(state, instr);\n}\n\n// Floating Negative Multiply-Sub\nstatic void\nfnmsub(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<FMANegate | FMASubtract>(state, instr);\n}\n\n// Floating Negative Multiply-Sub Single\nstatic void\nfnmsubs(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<FMANegate | FMASubtract | FMASinglePrec>(state, instr);\n}\n\n// fctiw/fctiwz common implementation\nstatic void\nfctiwGeneric(cpu::Core *state, Instruction instr, FloatingPointRoundMode roundMode)\n{\n   double b;\n   int32_t bi;\n   b = state->fpr[instr.frB].value;\n\n   const bool vxsnan = is_signalling_nan(b);\n   bool vxcvi, fi;\n\n   if (is_nan(b)) {\n      vxcvi = true;\n      fi = false;\n      bi = INT_MIN;\n   } else if (b > static_cast<double>(INT_MAX)) {\n      vxcvi = true;\n      fi = false;\n      bi = INT_MAX;\n   } else if (b < static_cast<double>(INT_MIN)) {\n      vxcvi = true;\n      fi = false;\n      bi = INT_MIN;\n   } else {\n      vxcvi = false;\n      switch (roundMode) {\n      case FloatingPointRoundMode::Nearest:\n         // We have to use nearbyint() instead of round() here, because\n         // round() rounds 0.5 away from zero instead of to the nearest\n         // even integer.  nearbyint() is dependent on the host's FPU\n         // rounding mode, but since that will reflect FPSCR here, it's\n         // safe to use.\n         bi = static_cast<int32_t>(std::nearbyint(b));\n         break;\n      case FloatingPointRoundMode::Zero:\n         bi = static_cast<int32_t>(std::trunc(b));\n         break;\n      case FloatingPointRoundMode::Positive:\n         bi = static_cast<int32_t>(std::ceil(b));\n         break;\n      case FloatingPointRoundMode::Negative:\n         bi = static_cast<int32_t>(std::floor(b));\n         break;\n      default:\n         decaf_abort(\"Unexpected floating point round mode\");\n      }\n      fi = get_float_bits(b).exponent < 1075 && bi != b;\n   }\n\n   const uint32_t oldFPSCR = state->fpscr.value;\n   state->fpscr.vxsnan |= vxsnan;\n   state->fpscr.vxcvi |= vxcvi;\n\n   if ((vxsnan || vxcvi) && state->fpscr.ve) {\n      state->fpscr.fr = 0;\n      state->fpscr.fi = 0;\n      updateFX_FEX_VX(state, oldFPSCR);\n   } else {\n      state->fpr[instr.frD].iw1 = bi;\n      state->fpr[instr.frD].iw0 = 0xFFF80000 | (is_negative_zero(b) ? 1 : 0);\n      updateFPSCR(state, oldFPSCR);\n      // We need to set FPSCR[FI] manually since the rounding functions\n      // don't always raise inexact exceptions.\n      if (fi) {\n         state->fpscr.fi = 1;\n         state->fpscr.xx = 1;\n         updateFX_FEX_VX(state, oldFPSCR);\n      }\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\nstatic void\nfctiw(cpu::Core *state, Instruction instr)\n{\n   return fctiwGeneric(state, instr, static_cast<FloatingPointRoundMode>(state->fpscr.rn));\n}\n\n// Floating Convert to Integer Word with Round toward Zero\nstatic void\nfctiwz(cpu::Core *state, Instruction instr)\n{\n   return fctiwGeneric(state, instr, FloatingPointRoundMode::Zero);\n}\n\n// Floating Round to Single\nstatic void\nfrsp(cpu::Core *state, Instruction instr)\n{\n   auto b = state->fpr[instr.frB].value;\n   auto vxsnan = is_signalling_nan(b);\n\n   const uint32_t oldFPSCR = state->fpscr.value;\n   state->fpscr.vxsnan |= vxsnan;\n\n   if (vxsnan && state->fpscr.ve) {\n      updateFX_FEX_VX(state, oldFPSCR);\n   } else {\n      auto d = static_cast<float>(b);\n      state->fpr[instr.frD].paired0 = d;\n      // frD(ps1) is left undefined in the 750CL manual, but the processor\n      // actually copies the result to ps1 like other single-precision\n      // instructions.\n      state->fpr[instr.frD].paired1 = d;\n      updateFPRF(state, d);\n      updateFPSCR(state, oldFPSCR);\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// TODO: do fabs/fnabs/fneg behave like fmr w.r.t. paired singles?\n// Floating Absolute Value\nstatic void\nfabs(cpu::Core *state, Instruction instr)\n{\n   uint64_t b, d;\n\n   b = state->fpr[instr.frB].idw;\n   d = clear_bit(b, 63);\n   state->fpr[instr.frD].idw = d;\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Floating Negative Absolute Value\nstatic void\nfnabs(cpu::Core *state, Instruction instr)\n{\n   uint64_t b, d;\n\n   b = state->fpr[instr.frB].idw;\n   d = set_bit(b, 63);\n   state->fpr[instr.frD].idw = d;\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Floating Move Register\nstatic void\nfmr(cpu::Core *state, Instruction instr)\n{\n   state->fpr[instr.frD].idw = state->fpr[instr.frB].idw;\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Floating Negate\nstatic void\nfneg(cpu::Core *state, Instruction instr)\n{\n   uint64_t b, d;\n\n   b = state->fpr[instr.frB].idw;\n   d = flip_bit(b, 63);\n   state->fpr[instr.frD].idw = d;\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Move from FPSCR\nstatic void\nmffs(cpu::Core *state, Instruction instr)\n{\n   state->fpr[instr.frD].iw1 = state->fpscr.value;\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Move to FPSCR Bit 0\nstatic void\nmtfsb0(cpu::Core *state, Instruction instr)\n{\n   state->fpscr.value = clear_bit(state->fpscr.value, 31 - instr.crbD);\n   updateFEX_VX(state);\n   if (instr.crbD >= 30) {\n      cpu::this_core::updateRoundingMode();\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Move to FPSCR Bit 1\nstatic void\nmtfsb1(cpu::Core *state, Instruction instr)\n{\n   const uint32_t oldValue = state->fpscr.value;\n   state->fpscr.value = set_bit(state->fpscr.value, 31 - instr.crbD);\n   updateFX_FEX_VX(state, oldValue);\n   if (instr.crbD >= 30) {\n      cpu::this_core::updateRoundingMode();\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Move to FPSCR Fields\nstatic void\nmtfsf(cpu::Core *state, Instruction instr)\n{\n   const uint32_t value = state->fpr[instr.frB].iw1;\n   for (int field = 0; field < 8; field++) {\n      // Technically field 0 is at the high end, but as long as the bit\n      // position in the mask and the field we operate on match up, it\n      // doesn't matter which direction we go in.  So we use host bit\n      // order for simplicity.\n      if (get_bit(instr.fm, field)) {\n         const uint32_t mask = make_bitmask(4 * field, 4 * field + 3);\n         state->fpscr.value &= ~mask;\n         state->fpscr.value |= value & mask;\n      }\n   }\n   updateFEX_VX(state);\n   if (get_bit(instr.fm, 0)) {\n      cpu::this_core::updateRoundingMode();\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Move to FPSCR Field Immediate\nstatic void\nmtfsfi(cpu::Core *state, Instruction instr)\n{\n   const int shift = 4 * (7 - instr.crfD);\n   state->fpscr.value &= ~(0xF << shift);\n   state->fpscr.value |= instr.imm << shift;\n   updateFEX_VX(state);\n   if (instr.crfD == 7) {\n      cpu::this_core::updateRoundingMode();\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\nvoid\ncpu::interpreter::registerFloatInstructions()\n{\n   RegisterInstruction(fadd);\n   RegisterInstruction(fadds);\n   RegisterInstruction(fdiv);\n   RegisterInstruction(fdivs);\n   RegisterInstruction(fmul);\n   RegisterInstruction(fmuls);\n   RegisterInstruction(fsub);\n   RegisterInstruction(fsubs);\n   RegisterInstruction(fres);\n   RegisterInstruction(frsqrte);\n   RegisterInstruction(fsel);\n   RegisterInstruction(fmadd);\n   RegisterInstruction(fmadds);\n   RegisterInstruction(fmsub);\n   RegisterInstruction(fmsubs);\n   RegisterInstruction(fnmadd);\n   RegisterInstruction(fnmadds);\n   RegisterInstruction(fnmsub);\n   RegisterInstruction(fnmsubs);\n   RegisterInstruction(fctiw);\n   RegisterInstruction(fctiwz);\n   RegisterInstruction(frsp);\n   RegisterInstruction(fabs);\n   RegisterInstruction(fnabs);\n   RegisterInstruction(fmr);\n   RegisterInstruction(fneg);\n   RegisterInstruction(mffs);\n   RegisterInstruction(mtfsb0);\n   RegisterInstruction(mtfsb1);\n   RegisterInstruction(mtfsf);\n   RegisterInstruction(mtfsfi);\n}\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter_float.h",
    "content": "#pragma once\n#include <common/floatutils.h>\n#include \"../state.h\"\n\ntemplate<typename Type>\nstatic inline bool\npossibleUnderflow(Type v)\n{\n   auto bits = get_float_bits(v);\n   return bits.exponent == 1 && bits.mantissa == 0;\n}\n\nfloat\nppc_estimate_reciprocal(float v);\n\ndouble\nppc_estimate_reciprocal_root(double v);\n\nvoid\nupdateFEX_VX(cpu::Core *state);\n\nvoid\nupdateFX_FEX_VX(cpu::Core *state, uint32_t oldValue);\n\nvoid\nupdateFPSCR(cpu::Core *state, uint32_t oldValue);\n\ntemplate<typename Type> void\nupdateFPRF(cpu::Core *state, Type value);\n\nvoid\nupdateFloatConditionRegister(cpu::Core *state);\n\nvoid\nroundForMultiply(double *a, double *c);\n\nfloat\nroundFMAResultToSingle(double result, double a, double b, double c);\n\ntemplate<typename Type> Type\ngetFpr(cpu::Core *state, unsigned fr);\n\ntemplate<> float\ngetFpr<float>(cpu::Core *state, unsigned fr);\n\ntemplate<> double\ngetFpr<double>(cpu::Core *state, unsigned fr);\n\nvoid\nsetFpr(cpu::Core *state, unsigned fr, float value);\n\nvoid\nsetFpr(cpu::Core *state, unsigned fr, double value);\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter_insreg.h",
    "content": "#pragma once\n#include \"../state.h\"\n#include \"../espresso/espresso_instruction.h\"\n#include \"../espresso/espresso_instructionid.h\"\n\n// TODO: Remove me\nusing espresso::Instruction;\nusing espresso::InstructionID;\n\nnamespace cpu\n{\n\nnamespace interpreter\n{\n\nusing instrfptr_t = void(*)(Core*, Instruction);\n\nbool\nhasInstruction(espresso::InstructionID instrId);\n\ninstrfptr_t\ngetInstructionHandler(espresso::InstructionID id);\n\nvoid\nregisterInstruction(espresso::InstructionID id, instrfptr_t fptr);\n\nvoid\nregisterBranchInstructions();\n\nvoid\nregisterConditionInstructions();\n\nvoid\nregisterFloatInstructions();\n\nvoid\nregisterIntegerInstructions();\n\nvoid\nregisterLoadStoreInstructions();\n\nvoid\nregisterPairedInstructions();\n\nvoid\nregisterSystemInstructions();\n\n} // namespace interpreter\n\n} // namespace cpu\n\n#undef RegisterInstruction\n#undef RegisterInstructionFn\n\n#define RegisterInstruction(x) \\\n   cpu::interpreter::registerInstruction(espresso::InstructionID::x, &x)\n#define RegisterInstructionFn(x, fn) \\\n   cpu::interpreter::registerInstruction(espresso::InstructionID::x, &fn)\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter_integer.cpp",
    "content": "#include <type_traits>\n#include \"interpreter_insreg.h\"\n#include <common/bitutils.h>\n\nusing espresso::ConditionRegisterFlag;\n\n// Update cr0 with value\nstatic void\nupdateConditionRegister(cpu::Core *state, uint32_t value)\n{\n   auto flags = 0;\n\n   if (value == 0) {\n      flags |= ConditionRegisterFlag::Zero;\n   } else if (get_bit<31>(value)) {\n      flags |= ConditionRegisterFlag::Negative;\n   } else {\n      flags |= ConditionRegisterFlag::Positive;\n   }\n\n   if (state->xer.so) {\n      flags |= ConditionRegisterFlag::SummaryOverflow;\n   }\n\n   state->cr.cr0 = flags;\n}\n\n// Update carry flags\nstatic void\nupdateCarry(cpu::Core *state, bool carry)\n{\n   state->xer.ca = carry;\n}\n\n// Update overflow flags\nstatic void\nupdateOverflow(cpu::Core *state, bool overflow)\n{\n   state->xer.ov = overflow;\n   state->xer.so |= overflow;\n}\n\n// Add\nenum AddFlags\n{\n   AddCarry          = 1 << 0, // xer[ca] = carry\n   AddExtended       = 1 << 1, // d = a + b + xer[ca]\n   AddImmediate      = 1 << 2, // d = a + simm\n   AddCheckRecord    = 1 << 3, // Check rc and oe then update cr0 and xer\n   AddAlwaysRecord   = 1 << 4, // Always update cr0 and xer\n   AddShifted        = 1 << 5, // d = a + (b << 16)\n   AddToZero         = 1 << 6, // d = a + 0\n   AddToMinusOne     = 1 << 7, // d = a + -1\n   AddZeroRA         = 1 << 8, // a = (rA == 0) ? 0 : gpr[rA]\n   AddSubtract       = 1 << 9, // a = ~a and +1 when not carry\n};\n\ntemplate<unsigned flags = 0>\nstatic void\naddGeneric(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, b, d;\n\n   // Get a value\n   if constexpr (!!(flags & AddZeroRA)) {\n      if (instr.rA == 0) {\n         a = 0;\n      } else {\n         a = state->gpr[instr.rA];\n      }\n   } else {\n      a = state->gpr[instr.rA];\n   }\n\n   if constexpr (!!(flags & AddSubtract)) {\n      a = ~a;\n   }\n\n   // Get b value\n   if constexpr (!!(flags & AddImmediate)) {\n      b = sign_extend<16>(instr.simm);\n   } else if constexpr (!!(flags & AddToZero)) {\n      b = 0;\n   } else if constexpr (!!(flags & AddToMinusOne)) {\n      b = static_cast<uint32_t>(-1);\n   } else {\n      b = state->gpr[instr.rB];\n   }\n\n   if constexpr (!!(flags & AddShifted)) {\n      b <<= 16;\n   }\n\n   // Calculate d\n   d = a + b;\n\n   // Add xer[ca] if needed\n   if constexpr (!!(flags & AddExtended)) {\n      d += state->xer.ca;\n   } else if constexpr (!!(flags & AddSubtract)) {\n      d += 1;\n   }\n\n   // Update rD\n   state->gpr[instr.rD] = d;\n\n   if constexpr (!!(flags & AddCarry)) {\n      auto carry = d < a || (d == a && b > 0);\n      updateCarry(state, carry);\n   }\n\n   if constexpr (!!(flags & AddAlwaysRecord)) {\n      // Always record only means update CR0, NOT overflow\n      updateConditionRegister(state, d);\n   } else if constexpr (!!(flags & AddCheckRecord)) {\n      if (instr.oe) {\n         auto overflow = !!get_bit<31>((a ^ d) & (b ^ d));\n         updateOverflow(state, overflow);\n      }\n\n      if (instr.rc) {\n         updateConditionRegister(state, d);\n      }\n   }\n}\n\nstatic void\nadd(cpu::Core *state, Instruction instr)\n{\n   return addGeneric<AddCheckRecord>(state, instr);\n}\n\nstatic void\naddc(cpu::Core *state, Instruction instr)\n{\n   return addGeneric<AddCarry | AddCheckRecord>(state, instr);\n}\n\nstatic void\nadde(cpu::Core *state, Instruction instr)\n{\n   return addGeneric<AddExtended | AddCarry | AddCheckRecord>(state, instr);\n}\n\nstatic void\naddi(cpu::Core *state, Instruction instr)\n{\n   return addGeneric<AddImmediate | AddZeroRA>(state, instr);\n}\n\nstatic void\naddic(cpu::Core *state, Instruction instr)\n{\n   return addGeneric<AddImmediate | AddCarry>(state, instr);\n}\n\nstatic void\naddicx(cpu::Core *state, Instruction instr)\n{\n   return addGeneric<AddImmediate | AddCarry | AddAlwaysRecord>(state, instr);\n}\n\nstatic void\naddis(cpu::Core *state, Instruction instr)\n{\n   return addGeneric<AddImmediate | AddShifted | AddZeroRA>(state, instr);\n}\n\nstatic void\naddme(cpu::Core *state, Instruction instr)\n{\n   return addGeneric<AddCheckRecord | AddCarry | AddExtended | AddToMinusOne>(state, instr);\n}\n\nstatic void\naddze(cpu::Core *state, Instruction instr)\n{\n   return addGeneric<AddCheckRecord | AddCarry | AddExtended | AddToZero>(state, instr);\n}\n\n// AND\nenum AndFlags\n{\n   AndComplement = 1 << 0, // b = ~b\n   AndCheckRecord = 1 << 1, // Check rc then update cr\n   AndImmediate = 1 << 2, // b = uimm\n   AndShifted = 1 << 3, // b = (b << 16)\n   AndAlwaysRecord = 1 << 4, // Always update cr\n};\n\ntemplate<unsigned flags>\nstatic void\nandGeneric(cpu::Core *state, Instruction instr)\n{\n   uint32_t s, a, b;\n\n   s = state->gpr[instr.rS];\n\n   if constexpr (!!(flags & AndImmediate)) {\n      b = instr.uimm;\n   } else {\n      b = state->gpr[instr.rB];\n   }\n\n   if constexpr (!!(flags & AndShifted)) {\n      b <<= 16;\n   }\n\n   if constexpr (!!(flags & AndComplement)) {\n      b = ~b;\n   }\n\n   a = s & b;\n   state->gpr[instr.rA] = a;\n\n   if constexpr (!!(flags & AndAlwaysRecord)) {\n      updateConditionRegister(state, a);\n   } else if constexpr (!!(flags & AndCheckRecord)) {\n      if (instr.rc) {\n         updateConditionRegister(state, a);\n      }\n   }\n}\n\nstatic void\nand_(cpu::Core *state, Instruction instr)\n{\n   return andGeneric<AndCheckRecord>(state, instr);\n}\n\nstatic void\nandc(cpu::Core *state, Instruction instr)\n{\n   return andGeneric<AndCheckRecord | AndComplement>(state, instr);\n}\n\nstatic void\nandi(cpu::Core *state, Instruction instr)\n{\n   return andGeneric<AndAlwaysRecord | AndImmediate>(state, instr);\n}\n\nstatic void\nandis(cpu::Core *state, Instruction instr)\n{\n   return andGeneric<AndAlwaysRecord | AndImmediate | AndShifted>(state, instr);\n}\n\n// Count Leading Zeroes Word\nstatic void\ncntlzw(cpu::Core *state, Instruction instr)\n{\n   unsigned long a;\n   uint32_t s;\n\n   s = state->gpr[instr.rS];\n\n   if (!bit_scan_reverse(&a, s)) {\n      a = 32;\n   } else {\n      a = 31 - a;\n   }\n\n   state->gpr[instr.rA] = a;\n\n   if (instr.rc) {\n      updateConditionRegister(state, a);\n   }\n}\n\n// Divide\ntemplate<typename Type>\nstatic void\ndivGeneric(cpu::Core *state, Instruction instr)\n{\n   Type a, b, d;\n   a = state->gpr[instr.rA];\n   b = state->gpr[instr.rB];\n\n   auto overflow = (b == 0);\n\n   if constexpr (std::is_signed<Type>::value) {\n      if (a == 0x80000000 && b == -1) {\n         overflow = true;\n      }\n   }\n\n   if (!overflow) {\n      d = a / b;\n   } else {\n      // rD = -1 when rA is negative, 0 when rA is positive\n      d = a < 0 ? -1 : 0;\n   }\n\n   state->gpr[instr.rD] = d;\n\n   if (instr.oe) {\n      updateOverflow(state, overflow);\n   }\n\n   if (instr.rc) {\n      updateConditionRegister(state, d);\n   }\n}\n\nstatic void\ndivw(cpu::Core *state, Instruction instr)\n{\n   return divGeneric<int32_t>(state, instr);\n}\n\nstatic void\ndivwu(cpu::Core *state, Instruction instr)\n{\n   return divGeneric<uint32_t>(state, instr);\n}\n\n// Equivalent\nstatic void\neqv(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, s, b;\n\n   s = state->gpr[instr.rS];\n   b = state->gpr[instr.rB];\n\n   a = ~(s ^ b);\n   state->gpr[instr.rA] = a;\n\n   if (instr.rc) {\n      updateConditionRegister(state, a);\n   }\n}\n\n// Extend Sign Byte\nstatic void\nextsb(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, s;\n\n   s = state->gpr[instr.rS];\n\n   a = sign_extend<8>(s);\n   state->gpr[instr.rA] = a;\n\n   if (instr.rc) {\n      updateConditionRegister(state, a);\n   }\n}\n\n// Extend Sign Half Word\nstatic void\nextsh(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, s;\n\n   s = state->gpr[instr.rS];\n\n   a = sign_extend<16>(s);\n   state->gpr[instr.rA] = a;\n\n   if (instr.rc) {\n      updateConditionRegister(state, a);\n   }\n}\n\n// Multiply\nenum MulFlags\n{\n   MulLow           = 1 << 0, // (uint32_t)d\n   MulHigh          = 1 << 1, // d >> 32\n   MulImmediate     = 1 << 2, // b = simm\n   MulCheckOverflow = 1 << 3, // Check oe then update xer\n   MulCheckRecord   = 1 << 4, // Check rc then update cr\n};\n\n// Signed multiply\ntemplate<unsigned flags>\nstatic void\nmulSignedGeneric(cpu::Core *state, Instruction instr)\n{\n   int64_t a, b;\n   int32_t d;\n   a = static_cast<int32_t>(state->gpr[instr.rA]);\n\n   if constexpr (!!(flags & MulImmediate)) {\n      b = sign_extend<16>(instr.simm);\n   } else {\n      b = static_cast<int32_t>(state->gpr[instr.rB]);\n   }\n\n   const int64_t product = a * b;\n   if constexpr (!!(flags & MulLow)) {\n      d = static_cast<int32_t>(product);\n   } else if constexpr (!!(flags & MulHigh)) {\n      d = product >> 32;\n      // oe is ignored for mulhw* instructions.\n   } else {\n      static_assert(\"Unexpected flags for mulSignedGeneric\");\n   }\n\n   state->gpr[instr.rD] = d;\n\n   if constexpr (!!(flags & MulCheckOverflow)) {\n      bool overflow;\n\n      if constexpr (!!(flags & MulLow)) {\n         overflow = (product < INT64_C(-0x80000000) || product > 0x7FFFFFFF);\n      } else {\n         static_assert(\"Unexpected flags for mulSignedGeneric\");\n      }\n\n      if (instr.oe) {\n         updateOverflow(state, overflow);\n      }\n   }\n   if constexpr (!!(flags & MulCheckRecord)) {\n      if (instr.rc) {\n         updateConditionRegister(state, d);\n      }\n   }\n}\n\n// Unsigned multiply\ntemplate<unsigned flags>\nstatic void\nmulUnsignedGeneric(cpu::Core *state, Instruction instr)\n{\n   uint64_t a, b;\n   uint32_t d;\n   a = state->gpr[instr.rA];\n   b = state->gpr[instr.rB];\n\n   if constexpr (!!(flags & MulLow)) {\n      d = static_cast<uint32_t>(a * b);\n   } else if constexpr (!!(flags & MulHigh)) {\n      d = (a * b) >> 32;\n   } else {\n      static_assert(\"Unexpected flags for mulUnsignedGeneric\");\n   }\n\n   state->gpr[instr.rD] = d;\n\n   if constexpr (!!(flags & MulCheckRecord)) {\n      if (instr.rc) {\n         updateConditionRegister(state, d);\n      }\n   }\n}\n\nstatic void\nmulhw(cpu::Core *state, Instruction instr)\n{\n   return mulSignedGeneric<MulHigh | MulCheckRecord>(state, instr);\n}\n\nstatic void\nmulhwu(cpu::Core *state, Instruction instr)\n{\n   return mulUnsignedGeneric<MulHigh | MulCheckRecord>(state, instr);\n}\n\nstatic void\nmulli(cpu::Core *state, Instruction instr)\n{\n   return mulSignedGeneric<MulImmediate | MulLow>(state, instr);\n}\n\nstatic void\nmullw(cpu::Core *state, Instruction instr)\n{\n   return mulSignedGeneric<MulLow | MulCheckRecord | MulCheckOverflow>(state, instr);\n}\n\n// NAND\nstatic void\nnand(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, s, b;\n\n   s = state->gpr[instr.rS];\n   b = state->gpr[instr.rB];\n\n   a = ~(s & b);\n   state->gpr[instr.rA] = a;\n\n   if (instr.rc) {\n      updateConditionRegister(state, a);\n   }\n}\n\n// Negate\nstatic void\nneg(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, d;\n\n   a = state->gpr[instr.rA];\n\n   d = (~a) + 1;\n   state->gpr[instr.rD] = d;\n\n   bool overflow = (a == 0x80000000);\n\n   if (instr.oe) {\n      updateOverflow(state, overflow);\n   }\n\n   if (instr.rc) {\n      updateConditionRegister(state, d);\n   }\n}\n\n// NOR\nstatic void\nnor(cpu::Core *state, Instruction instr)\n{\n   uint32_t a, s, b;\n\n   s = state->gpr[instr.rS];\n   b = state->gpr[instr.rB];\n\n   a = ~(s | b);\n   state->gpr[instr.rA] = a;\n\n   if (instr.rc) {\n      updateConditionRegister(state, a);\n   }\n}\n\n// OR\nenum OrFlags\n{\n   OrComplement = 1 << 0, // b = ~b\n   OrCheckRecord = 1 << 1, // Check rc then update cr\n   OrImmediate = 1 << 2, // b = uimm\n   OrShifted = 1 << 3, // b = (b << 16)\n   OrAlwaysRecord = 1 << 4, // Always update cr\n};\n\ntemplate<unsigned flags>\nstatic void\norGeneric(cpu::Core *state, Instruction instr)\n{\n   uint32_t s, a, b;\n\n   s = state->gpr[instr.rS];\n\n   if constexpr (!!(flags & OrImmediate)) {\n      b = instr.uimm;\n   } else {\n      b = state->gpr[instr.rB];\n   }\n\n   if constexpr (!!(flags & OrShifted)) {\n      b <<= 16;\n   }\n\n   if constexpr (!!(flags & OrComplement)) {\n      b = ~b;\n   }\n\n   a = s | b;\n   state->gpr[instr.rA] = a;\n\n   if constexpr (!!(flags & OrAlwaysRecord)) {\n      updateConditionRegister(state, a);\n   } else if constexpr (!!(flags & OrCheckRecord)) {\n      if (instr.rc) {\n         updateConditionRegister(state, a);\n      }\n   }\n}\n\nstatic void\nor_(cpu::Core *state, Instruction instr)\n{\n   return orGeneric<OrCheckRecord>(state, instr);\n}\n\nstatic void\norc(cpu::Core *state, Instruction instr)\n{\n   return orGeneric<OrCheckRecord | OrComplement>(state, instr);\n}\n\nstatic void\nori(cpu::Core *state, Instruction instr)\n{\n   return orGeneric<OrImmediate>(state, instr);\n}\n\nstatic void\noris(cpu::Core *state, Instruction instr)\n{\n   return orGeneric<OrImmediate | OrShifted>(state, instr);\n}\n\n// Rotate left word\nenum RlwFlags\n{\n   RlwImmediate   = 1 << 0, // n = sh\n   RlwAnd         = 1 << 1, // a = r & m\n   RlwInsert      = 1 << 2  // a = (r & m) | (r & ~m)\n};\n\ntemplate<unsigned flags>\nstatic void\nrlwGeneric(cpu::Core *state, Instruction instr)\n{\n   uint32_t s, n, r, m, a;\n\n   s = state->gpr[instr.rS];\n   a = state->gpr[instr.rA];\n\n   if constexpr (!!(flags & RlwImmediate)) {\n      n = instr.sh;\n   } else {\n      n = state->gpr[instr.rB] & 0x1f;\n   }\n\n   r = bit_rotate_left(s, n);\n   m = make_ppc_bitmask(instr.mb, instr.me);\n\n   if constexpr (!!(flags & RlwAnd)) {\n      a = (r & m);\n   } else if constexpr (!!(flags & RlwInsert)) {\n      a = (r & m) | (a & ~m);\n   }\n\n   state->gpr[instr.rA] = a;\n\n   if (instr.rc) {\n      updateConditionRegister(state, a);\n   }\n}\n\n// Rotate Left Word Immediate then Mask Insert\nstatic void\nrlwimi(cpu::Core *state, Instruction instr)\n{\n   return rlwGeneric<RlwImmediate | RlwInsert>(state, instr);\n}\n\n// Rotate Left Word Immediate then AND with Mask\nstatic void\nrlwinm(cpu::Core *state, Instruction instr)\n{\n   return rlwGeneric<RlwImmediate | RlwAnd>(state, instr);\n}\n\n// Rotate Left Word then AND with Mask\nstatic void\nrlwnm(cpu::Core *state, Instruction instr)\n{\n   return rlwGeneric<RlwAnd>(state, instr);\n}\n\n// Shift Logical\nenum ShiftFlags\n{\n   ShiftLeft = 1 << 0,\n   ShiftRight = 1 << 1,\n   ShiftImmediate = 1 << 2,\n};\n\ntemplate<unsigned flags>\nstatic void\nshiftLogical(cpu::Core *state, Instruction instr)\n{\n   uint32_t n, s, b, a;\n\n   s = state->gpr[instr.rS];\n\n   if constexpr (!!(flags & ShiftImmediate)) {\n      b = instr.sh;\n   } else {\n      b = state->gpr[instr.rB];\n   }\n\n   n = b & 0x1f;\n\n   if (b & 0x20) {\n      a = 0;\n   } else {\n      if constexpr (!!(flags & ShiftLeft)) {\n         a = s << n;\n      } else if constexpr (!!(flags & ShiftRight)) {\n         a = s >> n;\n      } else {\n         static_assert(\"Unexpected flags for shiftLogical\");\n      }\n   }\n\n   state->gpr[instr.rA] = a;\n\n   if (instr.rc) {\n      updateConditionRegister(state, a);\n   }\n}\n\n// Shift Left Word\nstatic void\nslw(cpu::Core *state, Instruction instr)\n{\n   shiftLogical<ShiftLeft>(state, instr);\n}\n\n// Shift Right Word\nstatic void\nsrw(cpu::Core *state, Instruction instr)\n{\n   shiftLogical<ShiftRight>(state, instr);\n}\n\n// Shift Arithmetic\ntemplate<unsigned flags>\nstatic void\nshiftArithmetic(cpu::Core *state, Instruction instr)\n{\n   static_assert(flags & ShiftRight, \"Shift Arithmetic only supports ShiftRight\");\n   int32_t s, a, n, b;\n\n   s = state->gpr[instr.rS];\n\n   if constexpr (!!(flags & ShiftImmediate)) {\n      b = instr.sh;\n   } else {\n      b = state->gpr[instr.rB];\n   }\n\n   bool carry = false;\n   if (b & 0x20) {\n      if (s >= 0) {\n         a = 0;\n      } else {\n         a = -1;\n         carry = true;\n      }\n   } else {\n      n = b & 0x1f;\n      if (n == 0) {\n         a = s;\n      } else {\n         a = s >> n;\n\n         if ((s < 0) && (s << (32 - n))) {\n            carry = true;\n         }\n      }\n   }\n\n   state->gpr[instr.rA] = a;\n\n   updateCarry(state, carry);\n\n   if (instr.rc) {\n      updateConditionRegister(state, a);\n   }\n}\n\nstatic void\nsraw(cpu::Core *state, Instruction instr)\n{\n   shiftArithmetic<ShiftRight>(state, instr);\n}\n\nstatic void\nsrawi(cpu::Core *state, Instruction instr)\n{\n   shiftArithmetic<ShiftRight | ShiftImmediate>(state, instr);\n}\n\n// Because sub is rD = ~rA + rB + 1 we can reuse our generic add\nstatic void\nsubf(cpu::Core *state, Instruction instr)\n{\n   addGeneric<AddSubtract | AddCheckRecord>(state, instr);\n}\n\nstatic void\nsubfc(cpu::Core *state, Instruction instr)\n{\n   addGeneric<AddCarry | AddSubtract | AddCheckRecord>(state, instr);\n}\n\nstatic void\nsubfe(cpu::Core *state, Instruction instr)\n{\n   addGeneric<AddExtended | AddCarry | AddSubtract | AddCheckRecord>(state, instr);\n}\n\nstatic void\nsubfic(cpu::Core *state, Instruction instr)\n{\n   addGeneric<AddImmediate | AddCarry | AddSubtract>(state, instr);\n}\n\nstatic void\nsubfme(cpu::Core *state, Instruction instr)\n{\n   addGeneric<AddToMinusOne | AddExtended | AddCarry | AddCheckRecord | AddSubtract>(state, instr);\n}\n\nstatic void\nsubfze(cpu::Core *state, Instruction instr)\n{\n   addGeneric<AddToZero | AddExtended | AddCarry | AddCheckRecord | AddSubtract>(state, instr);\n}\n\n// XOR\nenum XorFlags\n{\n   XorCheckRecord = 1 << 1, // Check rc then update cr\n   XorImmediate   = 1 << 2, // b = uimm\n   XorShifted     = 1 << 3, // b = (b << 16)\n};\n\ntemplate<unsigned flags>\nstatic void\nxorGeneric(cpu::Core *state, Instruction instr)\n{\n   uint32_t s, a, b;\n\n   s = state->gpr[instr.rS];\n\n   if constexpr (!!(flags & XorImmediate)) {\n      b = instr.uimm;\n   } else {\n      b = state->gpr[instr.rB];\n   }\n\n   if constexpr (!!(flags & XorShifted)) {\n      b <<= 16;\n   }\n\n   a = s ^ b;\n   state->gpr[instr.rA] = a;\n\n   if constexpr (!!(flags & XorCheckRecord)) {\n      if (instr.rc) {\n         updateConditionRegister(state, a);\n      }\n   }\n}\n\nstatic void\nxor_(cpu::Core *state, Instruction instr)\n{\n   return xorGeneric<XorCheckRecord>(state, instr);\n}\n\nstatic void\nxori(cpu::Core *state, Instruction instr)\n{\n   return xorGeneric<XorImmediate>(state, instr);\n}\n\nstatic void\nxoris(cpu::Core *state, Instruction instr)\n{\n   return xorGeneric<XorImmediate | XorShifted>(state, instr);\n}\n\nvoid\ncpu::interpreter::registerIntegerInstructions()\n{\n   RegisterInstruction(add);\n   RegisterInstruction(addc);\n   RegisterInstruction(adde);\n   RegisterInstruction(addi);\n   RegisterInstruction(addic);\n   RegisterInstruction(addicx);\n   RegisterInstruction(addis);\n   RegisterInstruction(addme);\n   RegisterInstruction(addze);\n   RegisterInstruction(and_);\n   RegisterInstruction(andc);\n   RegisterInstruction(andi);\n   RegisterInstruction(andis);\n   RegisterInstruction(cntlzw);\n   RegisterInstruction(divw);\n   RegisterInstruction(divwu);\n   RegisterInstruction(extsb);\n   RegisterInstruction(extsh);\n   RegisterInstruction(eqv);\n   RegisterInstruction(mulhw);\n   RegisterInstruction(mulhwu);\n   RegisterInstruction(mulli);\n   RegisterInstruction(mullw);\n   RegisterInstruction(nand);\n   RegisterInstruction(neg);\n   RegisterInstruction(nor);\n   RegisterInstruction(or_);\n   RegisterInstruction(orc);\n   RegisterInstruction(ori);\n   RegisterInstruction(oris);\n   RegisterInstruction(rlwimi);\n   RegisterInstruction(rlwinm);\n   RegisterInstruction(rlwnm);\n   RegisterInstruction(srw);\n   RegisterInstruction(slw);\n   RegisterInstruction(sraw);\n   RegisterInstruction(srawi);\n   RegisterInstruction(subf);\n   RegisterInstruction(subf);\n   RegisterInstruction(subfc);\n   RegisterInstruction(subfe);\n   RegisterInstruction(subfic);\n   RegisterInstruction(subfme);\n   RegisterInstruction(subfze);\n   RegisterInstruction(xor_);\n   RegisterInstruction(xori);\n   RegisterInstruction(xoris);\n}\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter_internal.h",
    "content": ""
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter_loadstore.cpp",
    "content": "#include \"interpreter_float.h\"\n#include \"interpreter_insreg.h\"\n#include \"mem.h\"\n\n#include <algorithm>\n#include <atomic>\n#include <cmath>\n#include <common/bitutils.h>\n#include <common/decaf_assert.h>\n#include <common/floatutils.h>\n#include <fmt/core.h>\n#include <mutex>\n\nusing espresso::QuantizedDataType;\nusing espresso::ConditionRegisterFlag;\n\n// Load\nenum LoadFlags\n{\n   LoadUpdate        = 1 << 0, // Save EA in rA\n   LoadIndexed       = 1 << 1, // Use rB instead of d\n   LoadSignExtend    = 1 << 2, // Sign extend\n   LoadByteReverse   = 1 << 3, // Swap bytes\n   LoadReserve       = 1 << 4, // lwarx hardware reserve\n   LoadZeroRA        = 1 << 5, // Use 0 instead of r0\n};\n\nstatic double\nconvertFloatToDouble(float f)\n{\n   if (!is_signalling_nan(f)) {\n      return static_cast<double>(f);\n   } else {\n      return extend_float(f);\n   }\n}\n\nstatic double\nloadFloatAsDouble(uint32_t ea)\n{\n   return convertFloatToDouble(mem::read<float>(ea));\n}\n\ntemplate<unsigned flags = 0>\nstatic void\nloadFloat(cpu::Core *state, Instruction instr)\n{\n   uint32_t ea;\n\n   if constexpr (!!(flags & LoadZeroRA)) {\n      if (instr.rA == 0) {\n         ea = 0;\n      } else {\n         ea = state->gpr[instr.rA];\n      }\n   } else {\n      ea = state->gpr[instr.rA];\n   }\n\n   if constexpr (!!(flags & LoadIndexed)) {\n      ea += state->gpr[instr.rB];\n   } else {\n      ea += sign_extend<16, int32_t>(instr.d);\n   }\n\n   const float f = mem::read<float>(ea);\n   state->fpr[instr.rD].paired0 = convertFloatToDouble(f);\n   state->fpr[instr.rD].paired1 = convertFloatToDouble(f);\n\n   if constexpr (!!(flags & LoadUpdate)) {\n      state->gpr[instr.rA] = ea;\n   }\n}\n\ntemplate<typename Type, unsigned flags = 0>\nstatic void\nloadGeneric(cpu::Core *state, Instruction instr)\n{\n   uint32_t ea;\n   Type d;\n\n   if constexpr (!!(flags & LoadZeroRA)) {\n      if (instr.rA == 0) {\n         ea = 0;\n      } else {\n         ea = state->gpr[instr.rA];\n      }\n   } else {\n      ea = state->gpr[instr.rA];\n   }\n\n   if constexpr (!!(flags & LoadIndexed)) {\n      ea += state->gpr[instr.rB];\n   } else {\n      ea += sign_extend<16, int32_t>(instr.d);\n   }\n\n   Type memd = mem::readNoSwap<Type>(ea);\n\n   if constexpr (!!(flags & LoadByteReverse)) {\n      d = memd;\n   } else {\n      d = byte_swap(memd);\n   }\n\n   if constexpr (!!(flags & LoadReserve)) {\n      static_assert(!(flags & LoadReserve) || sizeof(Type) == 4, \"Reserved reads are only valid on 32-bit values\");\n\n      state->reserveFlag = true;\n      state->reserveData = *reinterpret_cast<uint32_t*>(&memd);\n   }\n\n   if constexpr (std::is_floating_point<Type>::value) {\n      state->fpr[instr.rD].value = static_cast<double>(d);\n      // Normally lfd instructions do not modify the second paired-single\n      // slot (ps1) of an FPR, but if the lfd is immediately preceded or\n      // followed by paired-single instructions, frD(ps1) may receive the\n      // high 32 bits of the loaded word or some other value that happens\n      // to be stored in the FP unit.  This data hazard is strongly\n      // dependent on pipeline state, and we don't attempt to emulate it.\n      // (Using a double-precision value with paired-single instructions\n      // is documented to result in undefined behavior anyway, so it seems\n      // unlikely this sort of instruction sequence would be found in real\n      // software.)\n   } else {\n      if constexpr (!!(flags & LoadSignExtend)) {\n         state->gpr[instr.rD] = static_cast<uint32_t>(sign_extend<bit_width<Type>::value, uint64_t>(static_cast<uint64_t>(d)));\n      } else {\n         state->gpr[instr.rD] = static_cast<uint32_t>(d);\n      }\n   }\n\n   if constexpr (!!(flags & LoadUpdate)) {\n      state->gpr[instr.rA] = ea;\n   }\n}\n\nstatic void\nlbz(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint8_t, LoadZeroRA>(state, instr);\n}\n\nstatic void\nlbzu(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint8_t, LoadUpdate>(state, instr);\n}\n\nstatic void\nlbzux(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint8_t, LoadUpdate | LoadIndexed>(state, instr);\n}\n\nstatic void\nlbzx(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint8_t, LoadIndexed | LoadZeroRA>(state, instr);\n}\n\nstatic void\nlha(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint16_t, LoadSignExtend | LoadZeroRA>(state, instr);\n}\n\nstatic void\nlhau(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint16_t, LoadSignExtend | LoadUpdate>(state, instr);\n}\n\nstatic void\nlhaux(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint16_t, LoadSignExtend | LoadUpdate | LoadIndexed>(state, instr);\n}\n\nstatic void\nlhax(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint16_t, LoadSignExtend | LoadIndexed | LoadZeroRA>(state, instr);\n}\n\nstatic void\nlhbrx(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint16_t, LoadByteReverse | LoadIndexed | LoadZeroRA>(state, instr);\n}\n\nstatic void\nlhz(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint16_t, LoadZeroRA>(state, instr);\n}\n\nstatic void\nlhzu(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint16_t, LoadUpdate>(state, instr);\n}\n\nstatic void\nlhzux(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint16_t, LoadUpdate | LoadIndexed>(state, instr);\n}\n\nstatic void\nlhzx(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint16_t, LoadIndexed | LoadZeroRA>(state, instr);\n}\n\nstatic void\nlwbrx(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint32_t, LoadByteReverse | LoadIndexed | LoadZeroRA>(state, instr);\n}\n\nstatic void\nlwarx(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint32_t, LoadReserve | LoadIndexed | LoadZeroRA>(state, instr);\n}\n\nstatic void\nlwz(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint32_t, LoadZeroRA>(state, instr);\n}\n\nstatic void\nlwzu(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint32_t, LoadUpdate>(state, instr);\n}\n\nstatic void\nlwzux(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint32_t, LoadUpdate | LoadIndexed>(state, instr);\n}\n\nstatic void\nlwzx(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<uint32_t, LoadIndexed | LoadZeroRA>(state, instr);\n}\n\nstatic void\nlfs(cpu::Core *state, Instruction instr)\n{\n   return loadFloat<LoadZeroRA>(state, instr);\n}\n\nstatic void\nlfsu(cpu::Core *state, Instruction instr)\n{\n   return loadFloat<LoadUpdate>(state, instr);\n}\n\nstatic void\nlfsux(cpu::Core *state, Instruction instr)\n{\n   return loadFloat<LoadUpdate | LoadIndexed>(state, instr);\n}\n\nstatic void\nlfsx(cpu::Core *state, Instruction instr)\n{\n   return loadFloat<LoadZeroRA | LoadIndexed>(state, instr);\n}\n\nstatic void\nlfd(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<double, LoadZeroRA>(state, instr);\n}\n\nstatic void\nlfdu(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<double, LoadUpdate>(state, instr);\n}\n\nstatic void\nlfdux(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<double, LoadUpdate | LoadIndexed>(state, instr);\n}\n\nstatic void\nlfdx(cpu::Core *state, Instruction instr)\n{\n   return loadGeneric<double, LoadZeroRA | LoadIndexed>(state, instr);\n}\n\n// Load Multiple Words\n// Fills registers from rD to r31 with consecutive words from memory\nstatic void\nlmw(cpu::Core *state, Instruction instr)\n{\n   uint32_t b, ea, r;\n\n   if (instr.rA) {\n      b = state->gpr[instr.rA];\n   } else {\n      b = 0;\n   }\n\n   ea = b + sign_extend<16, int32_t>(instr.d);\n\n   for (r = instr.rD; r <= 31; ++r, ea += 4) {\n      state->gpr[r] = mem::read<uint32_t>(ea);\n   }\n}\n\n// Load String Word (byte-by-byte version of lmw)\nenum LswFlags\n{\n   LswIndexed = 1 >> 0,\n};\n\ntemplate<unsigned flags = 0>\nstatic void\nlswGeneric(cpu::Core *state, Instruction instr)\n{\n   uint32_t ea, i, n, r;\n\n   ea = instr.rA ? state->gpr[instr.rA] : 0;\n\n   if constexpr (!!(flags & LswIndexed)) {\n      ea += state->gpr[instr.rB];\n      n = state->xer.byteCount;\n   } else {\n      n = instr.nb ? instr.nb : 32;\n   }\n\n   r = instr.rD - 1;\n   i = 0;\n\n   while (n > 0) {\n      if (i == 0) {\n         r = (r + 1) % 32;\n         state->gpr[r] = 0;\n      }\n\n      state->gpr[r] |= mem::read<uint8_t>(ea) << (24 - i);\n\n      i = (i + 8) % 32;\n      ea = ea + 1;\n      n = n - 1;\n   }\n}\n\nstatic void\nlswi(cpu::Core *state, Instruction instr)\n{\n   lswGeneric(state, instr);\n}\n\nstatic void\nlswx(cpu::Core *state, Instruction instr)\n{\n   lswGeneric<LswIndexed>(state, instr);\n}\n\n// Store\nenum StoreFlags\n{\n   StoreUpdate          = 1 << 0, // Save EA in rA\n   StoreIndexed         = 1 << 1, // Use rB instead of d\n   StoreByteReverse     = 1 << 2, // Swap Bytes\n   StoreConditional     = 1 << 3, // lwarx/stwcx Conditional\n   StoreZeroRA          = 1 << 4, // Use 0 instead of r0\n   StoreFloatAsInteger  = 1 << 5, // stfiwx\n};\n\nstatic void\nstoreDoubleAsFloat(uint32_t ea, double d)\n{\n   auto dBits = get_float_bits(d);\n   uint32_t fBits;\n   if (dBits.exponent > 896 || dBits.uv << 1 == 0) {\n      // Not truncate_double()!  See the PowerPC documentation.\n      fBits = truncate_double_bits(dBits.uv);\n   } else {\n      fBits = static_cast<uint32_t>(((1u << 23) | (dBits.mantissa >> 29)) >> (897 - dBits.exponent));\n      fBits |= dBits.sign << 31;\n   }\n   mem::write<uint32_t>(ea, fBits);\n}\n\ntemplate<unsigned flags = 0>\nstatic void\nstoreFloat(cpu::Core *state, Instruction instr)\n{\n   uint32_t ea;\n\n   if constexpr (!!(flags & StoreZeroRA)) {\n      if (instr.rA == 0) {\n         ea = 0;\n      } else {\n         ea = state->gpr[instr.rA];\n      }\n   } else {\n      ea = state->gpr[instr.rA];\n   }\n\n   if constexpr (!!(flags & StoreIndexed)) {\n      ea += state->gpr[instr.rB];\n   } else {\n      ea += sign_extend<16, int32_t>(instr.d);\n   }\n\n   const double d = state->fpr[instr.rS].value;\n   storeDoubleAsFloat(ea, d);\n\n   if constexpr (!!(flags & StoreUpdate)) {\n      state->gpr[instr.rA] = ea;\n   }\n}\n\ntemplate<bool IsReserved>\nstruct ReservedWrite { };\n\ntemplate<>\nstruct ReservedWrite<true> {\n   template<typename Type>\n   static inline bool write(cpu::Core *state, uint32_t ea, Type s)\n   {\n      static_assert(sizeof(Type) == 4, \"Reserved writes are only valid on 32-bit values\");\n      static_assert(sizeof(std::atomic<Type>) == sizeof(Type), \"Non-locking reserve relies on zero-overhead atomics\");\n      auto atomicPtr = reinterpret_cast<std::atomic<Type>*>(mem::translate(ea));\n\n      state->cr.cr0 = state->xer.so ? ConditionRegisterFlag::SummaryOverflow : 0;\n\n      bool reserveFlag = state->reserveFlag;\n      state->reserveFlag = false;\n      if (!reserveFlag) {\n         // The processor did not have a reservation\n         return false;\n      }\n\n      auto reserveData = state->reserveData;\n\n      if (!atomicPtr->compare_exchange_strong(reserveData, s)) {\n         // The data has been modified since it was reserved.\n         return false;\n      }\n\n      // Store was succesful, set CR0[EQ]\n      state->cr.cr0 |= ConditionRegisterFlag::Equal;\n      return true;\n   }\n};\n\ntemplate<>\nstruct ReservedWrite<false> {\n   template<typename Type>\n   static inline bool write(cpu::Core *state, uint32_t ea, Type s)\n   {\n      mem::writeNoSwap(ea, s);\n      return true;\n   }\n};\n\ntemplate<typename Type, unsigned flags = 0>\nstatic void\nstoreGeneric(cpu::Core *state, Instruction instr)\n{\n   uint32_t ea;\n   Type s;\n\n   if constexpr (!!(flags & StoreZeroRA)) {\n      if (instr.rA == 0) {\n         ea = 0;\n      } else {\n         ea = state->gpr[instr.rA];\n      }\n   } else {\n      ea = state->gpr[instr.rA];\n   }\n\n   if constexpr (!!(flags & StoreIndexed)) {\n      ea += state->gpr[instr.rB];\n   } else {\n      ea += sign_extend<16, int32_t>(instr.d);\n   }\n\n   if constexpr (!!(flags & StoreFloatAsInteger)) {\n      s = static_cast<Type>(state->fpr[instr.rS].iw1);\n   } else if constexpr (std::is_floating_point<Type>::value) {\n      s = static_cast<Type>(state->fpr[instr.rS].value);\n   } else {\n      s = static_cast<Type>(state->gpr[instr.rS]);\n   }\n\n   if constexpr (!(flags & StoreByteReverse)) {\n      s = byte_swap(s);\n   }\n\n   if (!ReservedWrite<(flags & StoreConditional) != 0>::write(state, ea, s)) {\n      return;\n   }\n\n   if constexpr (!!(flags & StoreUpdate)) {\n      state->gpr[instr.rA] = ea;\n   }\n}\n\nstatic void\nstb(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint8_t, StoreZeroRA>(state, instr);\n}\n\nstatic void\nstbu(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint8_t, StoreUpdate>(state, instr);\n}\n\nstatic void\nstbux(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint8_t, StoreUpdate | StoreIndexed>(state, instr);\n}\n\nstatic void\nstbx(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint8_t, StoreZeroRA | StoreIndexed>(state, instr);\n}\n\nstatic void\nsth(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint16_t, StoreZeroRA>(state, instr);\n}\n\nstatic void\nsthu(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint16_t, StoreUpdate>(state, instr);\n}\n\nstatic void\nsthux(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint16_t, StoreUpdate | StoreIndexed>(state, instr);\n}\n\nstatic void\nsthx(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint16_t, StoreZeroRA | StoreIndexed>(state, instr);\n}\n\nstatic void\nstw(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint32_t, StoreZeroRA>(state, instr);\n}\n\nstatic void\nstwu(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint32_t, StoreUpdate>(state, instr);\n}\n\nstatic void\nstwux(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint32_t, StoreUpdate | StoreIndexed>(state, instr);\n}\n\nstatic void\nstwx(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint32_t, StoreZeroRA | StoreIndexed>(state, instr);\n}\n\nstatic void\nsthbrx(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint16_t, StoreZeroRA | StoreByteReverse | StoreIndexed>(state, instr);\n}\n\nstatic void\nstwbrx(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint32_t, StoreZeroRA | StoreByteReverse | StoreIndexed>(state, instr);\n}\n\nstatic void\nstwcx(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint32_t, StoreZeroRA | StoreConditional | StoreIndexed>(state, instr);\n}\n\nstatic void\nstfs(cpu::Core *state, Instruction instr)\n{\n   storeFloat<StoreZeroRA>(state, instr);\n}\n\nstatic void\nstfsu(cpu::Core *state, Instruction instr)\n{\n   storeFloat<StoreUpdate>(state, instr);\n}\n\nstatic void\nstfsux(cpu::Core *state, Instruction instr)\n{\n   storeFloat<StoreUpdate | StoreIndexed>(state, instr);\n}\n\nstatic void\nstfsx(cpu::Core *state, Instruction instr)\n{\n   storeFloat<StoreZeroRA | StoreIndexed>(state, instr);\n}\n\nstatic void\nstfd(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<double, StoreZeroRA>(state, instr);\n}\n\nstatic void\nstfdu(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<double, StoreUpdate>(state, instr);\n}\n\nstatic void\nstfdux(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<double, StoreUpdate | StoreIndexed>(state, instr);\n}\n\nstatic void\nstfdx(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<double, StoreZeroRA | StoreIndexed>(state, instr);\n}\n\nstatic void\nstfiwx(cpu::Core *state, Instruction instr)\n{\n   storeGeneric<uint32_t, StoreFloatAsInteger | StoreZeroRA | StoreIndexed>(state, instr);\n}\n\n// Store Multiple Words\n// Writes consecutive words to memory from rS to r31\nstatic void\nstmw(cpu::Core *state, Instruction instr)\n{\n   uint32_t b, ea, r;\n\n   if (instr.rA) {\n      b = state->gpr[instr.rA];\n   } else {\n      b = 0;\n   }\n\n   ea = b + sign_extend<16, int32_t>(instr.d);\n\n   for (r = instr.rS; r <= 31; ++r, ea += 4) {\n      mem::write<uint32_t>(ea, state->gpr[r]);\n   }\n}\n\n// Store String Word (byte-by-byte version of lmw)\nenum StswFlags\n{\n   StswIndexed = 1 >> 0,\n};\n\ntemplate<unsigned flags = 0>\nstatic void\nstswGeneric(cpu::Core *state, Instruction instr)\n{\n   uint32_t ea, i, n, r;\n\n   ea = instr.rA ? state->gpr[instr.rA] : 0;\n\n   if constexpr (!!(flags & StswIndexed)) {\n      ea += state->gpr[instr.rB];\n      n = state->xer.byteCount;\n   } else {\n      n = instr.nb ? instr.nb : 32;\n   }\n\n   r = instr.rS - 1;\n   i = 0;\n\n   while (n > 0) {\n      if (i == 0) {\n         r = (r + 1) % 32;\n      }\n\n      mem::write<uint8_t>(ea, (state->gpr[r] >> (24 - i)) & 0xff);\n\n      i = (i + 8) % 32;\n      ea = ea + 1;\n      n = n - 1;\n   }\n}\n\nstatic void\nstswi(cpu::Core *state, Instruction instr)\n{\n   stswGeneric(state, instr);\n}\n\nstatic void\nstswx(cpu::Core *state, Instruction instr)\n{\n   stswGeneric<StswIndexed>(state, instr);\n}\n\nstatic double\ndequantize(uint32_t ea, QuantizedDataType type, uint32_t scale)\n{\n   int exp = static_cast<int>(scale);\n   exp -= (exp & 32) << 1;  // Sign extend.\n   double result;\n\n   switch (type) {\n   case QuantizedDataType::Floating:\n      result = loadFloatAsDouble(ea);\n      break;\n   case QuantizedDataType::Unsigned8:\n      result = std::ldexp(static_cast<double>(mem::read<uint8_t>(ea)), -exp);\n      break;\n   case QuantizedDataType::Unsigned16:\n      result = std::ldexp(static_cast<double>(mem::read<uint16_t>(ea)), -exp);\n      break;\n   case QuantizedDataType::Signed8:\n      result = std::ldexp(static_cast<double>(mem::read<int8_t>(ea)), -exp);\n      break;\n   case QuantizedDataType::Signed16:\n      result = std::ldexp(static_cast<double>(mem::read<int16_t>(ea)), -exp);\n      break;\n   default:\n      decaf_abort(fmt::format(\"Unknown QuantizedDataType {}\", static_cast<int>(type)));\n   }\n\n   return result;\n}\n\ntemplate<typename Type>\ninline Type\nclamp(double value)\n{\n   double min = static_cast<double>(std::numeric_limits<Type>::min());\n   double max = static_cast<double>(std::numeric_limits<Type>::max());\n   return static_cast<Type>(std::max(min, std::min(value, max)));\n}\n\nstatic void\nquantize(uint32_t ea, double value, QuantizedDataType type, uint32_t scale)\n{\n   int exp = static_cast<int>(scale);\n   exp -= (exp & 32) << 1;  // Sign extend.\n\n   switch (type) {\n   case QuantizedDataType::Floating:\n      if (get_float_bits(value).exponent <= 896) {\n         // Make sure to write a zero with the correct sign!\n         mem::write(ea, bit_cast<float>(static_cast<uint32_t>(std::signbit(value)) << 31));\n      } else {\n         storeDoubleAsFloat(ea, value);\n      }\n      break;\n   case QuantizedDataType::Unsigned8:\n      if (is_nan(value)) {\n         mem::write(ea, (uint8_t)(std::signbit(value) ? 0 : 0xFF));\n      } else {\n         mem::write(ea, clamp<uint8_t>(std::ldexp(value, exp)));\n      }\n      break;\n   case QuantizedDataType::Unsigned16:\n      if (is_nan(value)) {\n         mem::write(ea, (uint16_t)(std::signbit(value) ? 0 : 0xFFFF));\n      } else {\n         mem::write(ea, clamp<uint16_t>(std::ldexp(value, exp)));\n      }\n      break;\n   case QuantizedDataType::Signed8:\n      if (is_nan(value)) {\n         mem::write(ea, (int8_t)(std::signbit(value) ? -0x80 : 0x7F));\n      } else {\n         mem::write(ea, clamp<int8_t>(std::ldexp(value, exp)));\n      }\n      break;\n   case QuantizedDataType::Signed16:\n      if (is_nan(value)) {\n         mem::write(ea, (int16_t)(std::signbit(value) ? -0x8000 : 0x7FFF));\n      } else {\n         mem::write(ea, clamp<int16_t>(std::ldexp(value, exp)));\n      }\n      break;\n   default:\n      decaf_abort(fmt::format(\"Unknown QuantizedDataType {}\", static_cast<int>(type)));\n   }\n}\n\n// Paired Single Load\nenum PsqLoadFlags\n{\n   PsqLoadZeroRA  = 1 << 0,\n   PsqLoadUpdate  = 1 << 1,\n   PsqLoadIndexed = 1 << 2,\n};\n\ntemplate<unsigned flags = 0>\nstatic void\npsqLoad(cpu::Core *state, Instruction instr)\n{\n   uint32_t ea, ls, c, i, w;\n   QuantizedDataType lt;\n\n   if constexpr (!!(flags & PsqLoadZeroRA)) {\n      if (instr.rA == 0) {\n         ea = 0;\n      } else {\n         ea = state->gpr[instr.rA];\n      }\n   } else {\n      ea = state->gpr[instr.rA];\n   }\n\n   if constexpr (!!(flags & PsqLoadIndexed)) {\n      ea += state->gpr[instr.rB];\n   } else {\n      ea += sign_extend<12, int32_t>(instr.qd);\n   }\n\n   if constexpr (!!(flags & PsqLoadIndexed)) {\n      i = instr.qi;\n      w = instr.qw;\n   } else {\n      i = instr.i;\n      w = instr.w;\n   }\n\n   c = 4;\n   lt = static_cast<QuantizedDataType>(state->gqr[i].ld_type);\n   ls = state->gqr[i].ld_scale;\n\n   if (lt == QuantizedDataType::Unsigned8 || lt == QuantizedDataType::Signed8) {\n      c = 1;\n   } else if (lt == QuantizedDataType::Unsigned16 || lt == QuantizedDataType::Signed16) {\n      c = 2;\n   }\n\n   if (w == 0) {\n      state->fpr[instr.frD].paired0 = dequantize(ea, lt, ls);\n      state->fpr[instr.frD].paired1 = dequantize(ea + c, lt, ls);\n   } else {\n      state->fpr[instr.frD].paired0 = dequantize(ea, lt, ls);\n      state->fpr[instr.frD].paired1 = 1.0;\n   }\n\n   if constexpr (!!(flags & PsqLoadUpdate)) {\n      state->gpr[instr.rA] = ea;\n   }\n}\n\nstatic void\npsq_l(cpu::Core *state, Instruction instr)\n{\n   psqLoad<PsqLoadZeroRA>(state, instr);\n}\n\nstatic void\npsq_lu(cpu::Core *state, Instruction instr)\n{\n   psqLoad<PsqLoadUpdate>(state, instr);\n}\n\nstatic void\npsq_lx(cpu::Core *state, Instruction instr)\n{\n   psqLoad<PsqLoadZeroRA | PsqLoadIndexed>(state, instr);\n}\n\nstatic void\npsq_lux(cpu::Core *state, Instruction instr)\n{\n   psqLoad<PsqLoadUpdate | PsqLoadIndexed>(state, instr);\n}\n\n// Paired Single Store\nenum PsqStoreFlags\n{\n   PsqStoreZeroRA    = 1 << 0,\n   PsqStoreUpdate    = 1 << 1,\n   PsqStoreIndexed   = 1 << 2,\n};\n\ntemplate<unsigned flags = 0>\nstatic void\npsqStore(cpu::Core *state, Instruction instr)\n{\n   uint32_t ea, sts, c, i, w;\n   QuantizedDataType stt;\n\n   if constexpr (flags & PsqStoreZeroRA) {\n      if (instr.rA == 0) {\n         ea = 0;\n      } else {\n         ea = state->gpr[instr.rA];\n      }\n   } else {\n      ea = state->gpr[instr.rA];\n   }\n\n   if constexpr (!!(flags & PsqStoreIndexed)) {\n      ea += state->gpr[instr.rB];\n   } else {\n      ea += sign_extend<12, int32_t>(instr.qd);\n   }\n\n   if constexpr (!!(flags & PsqStoreIndexed)) {\n      i = instr.qi;\n      w = instr.qw;\n   } else {\n      i = instr.i;\n      w = instr.w;\n   }\n\n   c = 4;\n   stt = static_cast<QuantizedDataType>(state->gqr[i].st_type);\n   sts = state->gqr[i].st_scale;\n\n   if (stt == QuantizedDataType::Unsigned8 || stt == QuantizedDataType::Signed8) {\n      c = 1;\n   } else if (stt == QuantizedDataType::Unsigned16 || stt == QuantizedDataType::Signed16) {\n      c = 2;\n   }\n\n   if (w == 0) {\n      quantize(ea, state->fpr[instr.frS].paired0, stt, sts);\n      quantize(ea + c, state->fpr[instr.frS].paired1, stt, sts);\n   } else {\n      quantize(ea, state->fpr[instr.frS].paired0, stt, sts);\n   }\n\n   if constexpr (!!(flags & PsqStoreUpdate)) {\n      state->gpr[instr.rA] = ea;\n   }\n}\n\nstatic void\npsq_st(cpu::Core *state, Instruction instr)\n{\n   psqStore<PsqStoreZeroRA>(state, instr);\n}\n\nstatic void\npsq_stu(cpu::Core *state, Instruction instr)\n{\n   psqStore<PsqLoadUpdate>(state, instr);\n}\n\nstatic void\npsq_stx(cpu::Core *state, Instruction instr)\n{\n   psqStore<PsqStoreZeroRA | PsqStoreIndexed>(state, instr);\n}\n\nstatic void\npsq_stux(cpu::Core *state, Instruction instr)\n{\n   psqStore<PsqStoreUpdate | PsqStoreIndexed>(state, instr);\n}\n\nvoid cpu::interpreter::registerLoadStoreInstructions()\n{\n   RegisterInstruction(lbz);\n   RegisterInstruction(lbzu);\n   RegisterInstruction(lbzx);\n   RegisterInstruction(lbzux);\n   RegisterInstruction(lha);\n   RegisterInstruction(lhau);\n   RegisterInstruction(lhax);\n   RegisterInstruction(lhaux);\n   RegisterInstruction(lhz);\n   RegisterInstruction(lhzu);\n   RegisterInstruction(lhzx);\n   RegisterInstruction(lhzux);\n   RegisterInstruction(lwz);\n   RegisterInstruction(lwzu);\n   RegisterInstruction(lwzx);\n   RegisterInstruction(lwzux);\n   RegisterInstruction(lhbrx);\n   RegisterInstruction(lwbrx);\n   RegisterInstruction(lwarx);\n   RegisterInstruction(lmw);\n   RegisterInstruction(lswi);\n   RegisterInstruction(lswx);\n   RegisterInstruction(stb);\n   RegisterInstruction(stbu);\n   RegisterInstruction(stbx);\n   RegisterInstruction(stbux);\n   RegisterInstruction(sth);\n   RegisterInstruction(sthu);\n   RegisterInstruction(sthx);\n   RegisterInstruction(sthux);\n   RegisterInstruction(stw);\n   RegisterInstruction(stwu);\n   RegisterInstruction(stwx);\n   RegisterInstruction(stwux);\n   RegisterInstruction(sthbrx);\n   RegisterInstruction(stwbrx);\n   RegisterInstruction(stmw);\n   RegisterInstruction(stswi);\n   RegisterInstruction(stswx);\n   RegisterInstruction(stwcx);\n   RegisterInstruction(lfs);\n   RegisterInstruction(lfsu);\n   RegisterInstruction(lfsx);\n   RegisterInstruction(lfsux);\n   RegisterInstruction(lfd);\n   RegisterInstruction(lfdu);\n   RegisterInstruction(lfdx);\n   RegisterInstruction(lfdux);\n   RegisterInstruction(stfs);\n   RegisterInstruction(stfsu);\n   RegisterInstruction(stfsx);\n   RegisterInstruction(stfsux);\n   RegisterInstruction(stfd);\n   RegisterInstruction(stfdu);\n   RegisterInstruction(stfdx);\n   RegisterInstruction(stfdux);\n   RegisterInstruction(stfiwx);\n   RegisterInstruction(psq_l);\n   RegisterInstruction(psq_lu);\n   RegisterInstruction(psq_lx);\n   RegisterInstruction(psq_lux);\n   RegisterInstruction(psq_st);\n   RegisterInstruction(psq_stu);\n   RegisterInstruction(psq_stx);\n   RegisterInstruction(psq_stux);\n}\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter_pairedsingle.cpp",
    "content": "#include <cfenv>\n#include <cmath>\n#include \"interpreter_insreg.h\"\n#include \"interpreter_float.h\"\n#include <common/floatutils.h>\n#include <common/platform_compiler.h>\n\n// Register move / sign bit manipulation\nenum MoveMode\n{\n   MoveDirect,\n   MoveNegate,\n   MoveAbsolute,\n   MoveNegAbsolute,\n};\n\ntemplate<MoveMode mode>\nstatic void\nmoveGeneric(cpu::Core *state, Instruction instr)\n{\n   uint32_t b0, b1, d0, d1;\n   const bool ps0_nan = is_signalling_nan(state->fpr[instr.frB].paired0);\n   const bool ps1_nan = is_signalling_nan(state->fpr[instr.frB].paired1);\n   if (ps0_nan) {\n      b0 = truncate_double_bits(state->fpr[instr.frB].idw);\n   } else {\n      // We have to round ps0 if it has excess precision, so we can't just\n      // chop off the trailing bits.\n      b0 = bit_cast<uint32_t>(static_cast<float>(state->fpr[instr.frB].paired0));\n   }\n   // ps1 is always truncated, whether NaN or not.\n   b1 = bit_cast<uint32_t>(truncate_double(state->fpr[instr.frB].paired1));\n\n   if constexpr (mode == MoveDirect) {\n      d0 = b0;\n      d1 = b1;\n   } else if constexpr (mode == MoveNegate) {\n      d0 = b0 ^ 0x80000000;\n      d1 = b1 ^ 0x80000000;\n   } else if constexpr (mode == MoveAbsolute) {\n      d0 = b0 & ~0x80000000;\n      d1 = b1 & ~0x80000000;\n   } else if constexpr (mode == MoveNegAbsolute) {\n      d0 = b0 | 0x80000000;\n      d1 = b1 | 0x80000000;\n   } else {\n      static_assert(\"Unexpected mode for moveGeneric\");\n   }\n\n   if (!ps0_nan) {\n      state->fpr[instr.frD].paired0 = static_cast<double>(bit_cast<float>(d0));\n   } else {\n      state->fpr[instr.frD].idw = extend_float_nan_bits(d0);\n   }\n   if (!ps1_nan) {\n      state->fpr[instr.frD].paired1 = static_cast<double>(bit_cast<float>(d1));\n   } else {\n      state->fpr[instr.frD].idw_paired1 = extend_float_nan_bits(d1);\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Move Register\nstatic void\nps_mr(cpu::Core *state, Instruction instr)\n{\n   return moveGeneric<MoveDirect>(state, instr);\n}\n\n// Negate\nstatic void\nps_neg(cpu::Core *state, Instruction instr)\n{\n   return moveGeneric<MoveNegate>(state, instr);\n}\n\n// Absolute\nstatic void\nps_abs(cpu::Core *state, Instruction instr)\n{\n   return moveGeneric<MoveAbsolute>(state, instr);\n}\n\n// Negative Absolute\nstatic void\nps_nabs(cpu::Core *state, Instruction instr)\n{\n   return moveGeneric<MoveNegAbsolute>(state, instr);\n}\n\n// Paired-single arithmetic\nenum PSArithOperator {\n    PSAdd,\n    PSSub,\n    PSMul,\n    PSDiv,\n};\n\n// Returns whether a result value was written (i.e., not aborted by an\n// exception).\ntemplate<PSArithOperator op, int slotA, int slotB>\nstatic bool\npsArithSingle(cpu::Core *state, Instruction instr, float *result)\n{\n   double a, b;\n   if constexpr (slotA == 0) {\n      a = state->fpr[instr.frA].paired0;\n   } else {\n      a = state->fpr[instr.frA].paired1;\n   }\n   if constexpr (slotB == 0) {\n      b = state->fpr[op == PSMul ? instr.frC : instr.frB].paired0;\n   } else {\n      b = state->fpr[op == PSMul ? instr.frC : instr.frB].paired1;\n   }\n\n   const bool vxsnan = is_signalling_nan(a) || is_signalling_nan(b);\n   bool vxisi, vximz, vxidi, vxzdz, zx;\n   if constexpr (op == PSAdd) {\n      vxisi = is_infinity(a) && is_infinity(b) && std::signbit(a) != std::signbit(b);\n      vximz = false;\n      vxidi = false;\n      vxzdz = false;\n      zx = false;\n   } else if constexpr (op == PSSub) {\n      vxisi = is_infinity(a) && is_infinity(b) && std::signbit(a) == std::signbit(b);\n      vximz = false;\n      vxidi = false;\n      vxzdz = false;\n      zx = false;\n   } else if constexpr (op == PSMul) {\n      vxisi = false;\n      vximz = (is_infinity(a) && is_zero(b)) || (is_zero(a) && is_infinity(b));\n      vxidi = false;\n      vxzdz = false;\n      zx = false;\n   } else if constexpr (op == PSDiv) {\n      vxisi = false;\n      vximz = false;\n      vxidi = is_infinity(a) && is_infinity(b);\n      vxzdz = is_zero(a) && is_zero(b);\n      zx = !(vxzdz || vxsnan) && is_zero(b);\n   } else {\n      static_assert(\"Unexpected flags for psArithSingle\");\n   }\n\n   state->fpscr.vxsnan |= vxsnan;\n   state->fpscr.vxisi |= vxisi;\n   state->fpscr.vximz |= vximz;\n   state->fpscr.vxidi |= vxidi;\n   state->fpscr.vxzdz |= vxzdz;\n   state->fpscr.zx |= zx;\n\n   const bool vxEnabled = (vxsnan || vxisi || vximz || vxidi || vxzdz) && state->fpscr.ve;\n   const bool zxEnabled = zx && state->fpscr.ze;\n   if (vxEnabled || zxEnabled) {\n      return false;\n   }\n\n   float d;\n   if (is_nan(a)) {\n      d = make_quiet(truncate_double(a));\n   } else if (is_nan(b)) {\n      d = make_quiet(truncate_double(b));\n   } else if (vxisi || vximz || vxidi || vxzdz) {\n      d = make_nan<float>();\n   } else {\n      if constexpr (op == PSAdd) {\n         d = static_cast<float>(a + b);\n      } else if constexpr (op == PSSub) {\n         d = static_cast<float>(a - b);\n      } else if constexpr (op == PSMul) {\n         if constexpr (slotB == 0) {\n            roundForMultiply(&a, &b);  // Not necessary for slot 1.\n         }\n         d = static_cast<float>(a * b);\n      } else if constexpr (op == PSDiv) {\n         d = static_cast<float>(a / b);\n      } else {\n         static_assert(\"Unexpected flags for psArithSingle\");\n      }\n\n      if (possibleUnderflow<float>(d)) {\n         const int oldRound = fegetround();\n         fesetround(FE_TOWARDZERO);\n\n         volatile double bTemp = b;\n         volatile float dummy;\n         if constexpr (op == PSAdd) {\n            dummy = static_cast<float>(a + bTemp);\n         } else if constexpr (op == PSSub) {\n            dummy = static_cast<float>(a - bTemp);\n         } else if constexpr (op == PSMul) {\n            dummy = static_cast<float>(a * bTemp);\n         } else if constexpr (op == PSDiv) {\n            dummy = static_cast<float>(a / bTemp);\n         } else {\n            static_assert(\"Unexpected flags for psArithSingle\");\n         }\n         fesetround(oldRound);\n      }\n   }\n\n   *result = d;\n   return true;\n}\n\ntemplate<PSArithOperator op, int slotB0, int slotB1>\nstatic void\npsArithGeneric(cpu::Core *state, Instruction instr)\n{\n   const uint32_t oldFPSCR = state->fpscr.value;\n\n   float d0, d1;\n   const bool wrote0 = psArithSingle<op, 0, slotB0>(state, instr, &d0);\n   const bool wrote1 = psArithSingle<op, 1, slotB1>(state, instr, &d1);\n   if (wrote0 && wrote1) {\n      state->fpr[instr.frD].paired0 = extend_float(d0);\n      state->fpr[instr.frD].paired1 = extend_float(d1);\n   }\n\n   if (wrote0) {\n      updateFPRF(state, d0);\n   }\n   updateFPSCR(state, oldFPSCR);\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Add\nstatic void\nps_add(cpu::Core *state, Instruction instr)\n{\n   return psArithGeneric<PSAdd, 0, 1>(state, instr);\n}\n\n// Subtract\nstatic void\nps_sub(cpu::Core *state, Instruction instr)\n{\n   return psArithGeneric<PSSub, 0, 1>(state, instr);\n}\n\n// Multiply\nstatic void\nps_mul(cpu::Core *state, Instruction instr)\n{\n   return psArithGeneric<PSMul, 0, 1>(state, instr);\n}\n\nstatic void\nps_muls0(cpu::Core *state, Instruction instr)\n{\n   return psArithGeneric<PSMul, 0, 0>(state, instr);\n}\n\nstatic void\nps_muls1(cpu::Core *state, Instruction instr)\n{\n   return psArithGeneric<PSMul, 1, 1>(state, instr);\n}\n\n// Divide\nstatic void\nps_div(cpu::Core *state, Instruction instr)\n{\n   return psArithGeneric<PSDiv, 0, 1>(state, instr);\n}\n\ntemplate<int slot>\nstatic void\npsSumGeneric(cpu::Core *state, Instruction instr) CLANG_FPU_BUG_WORKAROUND\n{\n   const uint32_t oldFPSCR = state->fpscr.value;\n   float d;\n\n   if (psArithSingle<PSAdd, 0, 1>(state, instr, &d)) {\n      updateFPRF(state, d);\n\n      if constexpr (slot == 0) {\n          state->fpr[instr.frD].paired0 = extend_float(d);\n          state->fpr[instr.frD].idw_paired1 = state->fpr[instr.frC].idw_paired1;\n      } else {\n          float ps0;\n\n          if (is_nan(state->fpr[instr.frC].paired0)) {\n             ps0 = truncate_double(state->fpr[instr.frC].paired0);\n          } else {\n             const auto inexact = !!std::fetestexcept(FE_INEXACT);\n             const auto overflow = !!std::fetestexcept(FE_OVERFLOW);\n\n             ps0 = static_cast<float>(state->fpr[instr.frC].paired0);\n\n             if (!inexact) {\n                 std::feclearexcept(FE_INEXACT);\n             }\n\n             if (!overflow) {\n                 std::feclearexcept(FE_OVERFLOW);\n             }\n          }\n\n          state->fpr[instr.frD].paired0 = extend_float(ps0);\n          state->fpr[instr.frD].paired1 = extend_float(d);\n      }\n   }\n\n   updateFPSCR(state, oldFPSCR);\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Sum High\nstatic void\nps_sum0(cpu::Core *state, Instruction instr)\n{\n   return psSumGeneric<0>(state, instr);\n}\n\n// Sum Low\nstatic void\nps_sum1(cpu::Core *state, Instruction instr)\n{\n   return psSumGeneric<1>(state, instr);\n}\n\n// Fused multiply-add instructions\nenum FMAFlags\n{\n   FMASubtract   = 1 << 0, // Subtract instead of add\n   FMANegate     = 1 << 1, // Negate result\n};\n\n// Returns whether a result value was written (i.e., not aborted by an\n// exception).\ntemplate<unsigned flags, int slotAB, int slotC>\nstatic bool\nfmaSingle(cpu::Core *state, Instruction instr, float *result)\n{\n   double a, b, c;\n   if constexpr (slotAB == 0) {\n      a = state->fpr[instr.frA].paired0;\n      b = state->fpr[instr.frB].paired0;\n   } else {\n      a = state->fpr[instr.frA].paired1;\n      b = state->fpr[instr.frB].paired1;\n   }\n   if constexpr (slotC == 0) {\n      c = state->fpr[instr.frC].paired0;\n   } else {\n      c = state->fpr[instr.frC].paired1;\n   }\n   const double addend = (flags & FMASubtract) ? -b : b;\n\n   const bool vxsnan = is_signalling_nan(a) || is_signalling_nan(b) || is_signalling_nan(c);\n   const bool vximz = (is_infinity(a) && is_zero(c)) || (is_zero(a) && is_infinity(c));\n   const bool vxisi = (!vximz && !is_nan(a) && !is_nan(c)\n                       && (is_infinity(a) || is_infinity(c)) && is_infinity(b)\n                       && (std::signbit(a) ^ std::signbit(c)) != std::signbit(addend));\n\n   state->fpscr.vxsnan |= vxsnan;\n   state->fpscr.vxisi |= vxisi;\n   state->fpscr.vximz |= vximz;\n\n   if ((vxsnan || vxisi || vximz) && state->fpscr.ve) {\n      return false;\n   }\n\n   float d;\n   if (is_nan(a)) {\n      d = make_quiet(truncate_double(a));\n   } else if (is_nan(b)) {\n      d = make_quiet(truncate_double(b));\n   } else if (is_nan(c)) {\n      d = make_quiet(truncate_double(c));\n   } else if (vxisi || vximz) {\n      d = make_nan<float>();\n   } else {\n      if constexpr (slotC == 0) {\n         roundForMultiply(&a, &c);  // Not necessary for slot 1.\n      }\n\n      double d64 = std::fma(a, c, addend);\n      if (state->fpscr.rn == espresso::FloatingPointRoundMode::Nearest) {\n         d = roundFMAResultToSingle(d64, a, addend, c);\n      } else {\n         d = static_cast<float>(d64);\n      }\n\n      if (possibleUnderflow<float>(d)) {\n         const int oldRound = fegetround();\n         fesetround(FE_TOWARDZERO);\n\n         volatile double addendTemp = addend;\n         volatile float dummy;\n         dummy = (float)std::fma(a, c, addendTemp);\n\n         fesetround(oldRound);\n      }\n\n      if constexpr (!!(flags & FMANegate)) {\n         d = -d;\n      }\n   }\n\n   *result = d;\n   return true;\n}\n\ntemplate<unsigned flags, int slotC0, int slotC1>\nstatic void\nfmaGeneric(cpu::Core *state, Instruction instr)\n{\n   const uint32_t oldFPSCR = state->fpscr.value;\n\n   float d0, d1;\n   const bool wrote0 = fmaSingle<flags, 0, slotC0>(state, instr, &d0);\n   const bool wrote1 = fmaSingle<flags, 1, slotC1>(state, instr, &d1);\n   if (wrote0 && wrote1) {\n      state->fpr[instr.frD].paired0 = extend_float(d0);\n      state->fpr[instr.frD].paired1 = extend_float(d1);\n   }\n\n   if (wrote0) {\n      updateFPRF(state, d0);\n   }\n   updateFPSCR(state, oldFPSCR);\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\nstatic void\nps_madd(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<0, 0, 1>(state, instr);\n}\n\nstatic void\nps_madds0(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<0, 0, 0>(state, instr);\n}\n\nstatic void\nps_madds1(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<0, 1, 1>(state, instr);\n}\n\nstatic void\nps_msub(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<FMASubtract, 0, 1>(state, instr);\n}\n\nstatic void\nps_nmadd(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<FMANegate, 0, 1>(state, instr);\n}\n\nstatic void\nps_nmsub(cpu::Core *state, Instruction instr)\n{\n   return fmaGeneric<FMANegate | FMASubtract, 0, 1>(state, instr);\n}\n\n// Merge registers\nenum MergeFlags\n{\n   MergeValue0 = 1 << 0,\n   MergeValue1 = 1 << 1\n};\n\ntemplate<unsigned flags = 0>\nstatic void\nmergeGeneric(cpu::Core *state, Instruction instr)\n{\n   float d0, d1;\n\n   if constexpr (!!(flags & MergeValue0)) {\n      if (!is_signalling_nan(state->fpr[instr.frA].paired1)) {\n         d0 = static_cast<float>(state->fpr[instr.frA].paired1);\n      } else {\n         d0 = truncate_double(state->fpr[instr.frA].paired1);\n      }\n   } else {\n      if (!is_signalling_nan(state->fpr[instr.frA].paired0)) {\n         d0 = static_cast<float>(state->fpr[instr.frA].paired0);\n      } else {\n         d0 = truncate_double(state->fpr[instr.frA].paired0);\n      }\n   }\n\n   // When inserting a double-precision value into slot 1, the value is\n   // truncated rather than rounded.\n   double d1_double;\n   if constexpr (!!(flags & MergeValue1)) {\n      d1_double = state->fpr[instr.frB].paired1;\n   } else {\n      d1_double = state->fpr[instr.frB].paired0;\n   }\n   auto d1_bits = get_float_bits(d1_double);\n   if (d1_bits.exponent >= 1151 && d1_bits.exponent < 2047) {\n      d1 = std::numeric_limits<float>::max();\n   } else {\n      d1 = truncate_double(d1_double);\n   }\n\n   state->fpr[instr.frD].paired0 = extend_float(d0);\n   state->fpr[instr.frD].paired1 = extend_float(d1);\n\n   // Don't leak any exceptions (inexact, overflow etc.) to later instructions.\n   std::feclearexcept(FE_ALL_EXCEPT);\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\nstatic void\nps_merge00(cpu::Core *state, Instruction instr)\n{\n   return mergeGeneric(state, instr);\n}\n\nstatic void\nps_merge01(cpu::Core *state, Instruction instr)\n{\n   return mergeGeneric<MergeValue1>(state, instr);\n}\n\nstatic void\nps_merge11(cpu::Core *state, Instruction instr)\n{\n   return mergeGeneric<MergeValue0 | MergeValue1>(state, instr);\n}\n\nstatic void\nps_merge10(cpu::Core *state, Instruction instr)\n{\n   return mergeGeneric<MergeValue0>(state, instr);\n}\n\n// Reciprocal\nstatic void\nps_res(cpu::Core *state, Instruction instr)\n{\n   const double b0 = state->fpr[instr.frB].paired0;\n   const double b1 = state->fpr[instr.frB].paired1;\n\n   const bool vxsnan0 = is_signalling_nan(b0);\n   const bool vxsnan1 = is_signalling_nan(b1);\n   const bool zx0 = is_zero(b0);\n   const bool zx1 = is_zero(b1);\n\n   const uint32_t oldFPSCR = state->fpscr.value;\n   state->fpscr.vxsnan |= vxsnan0 || vxsnan1;\n   state->fpscr.zx |= zx0 || zx1;\n\n   float d0 = 0.0, d1 = 0.0;\n   auto write = true;\n\n   if ((vxsnan0 && state->fpscr.ve) || (zx0 && state->fpscr.ze)) {\n      write = false;\n   } else {\n      d0 = ppc_estimate_reciprocal(truncate_double(b0));\n      updateFPRF(state, d0);\n   }\n\n   if ((vxsnan1 && state->fpscr.ve) || (zx1 && state->fpscr.ze)) {\n      write = false;\n   } else {\n      d1 = ppc_estimate_reciprocal(truncate_double(b1));\n   }\n\n   if (write) {\n      state->fpr[instr.frD].paired0 = extend_float(d0);\n      state->fpr[instr.frD].paired1 = extend_float(d1);\n   }\n\n   if (std::fetestexcept(FE_INEXACT)) {\n      // On inexact result, ps_res sets FPSCR[FI] without also setting\n      // FPSCR[XX] (like fres).\n      std::feclearexcept(FE_INEXACT);\n      updateFPSCR(state, oldFPSCR);\n      state->fpscr.fi = 1;\n   } else {\n      updateFPSCR(state, oldFPSCR);\n   }\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Reciprocal Square Root\nstatic void\nps_rsqrte(cpu::Core *state, Instruction instr)\n{\n   const double b0 = state->fpr[instr.frB].paired0;\n   const double b1 = state->fpr[instr.frB].paired1;\n\n   const bool vxsnan0 = is_signalling_nan(b0);\n   const bool vxsnan1 = is_signalling_nan(b1);\n   const bool vxsqrt0 = !vxsnan0 && std::signbit(b0) && !is_zero(b0);\n   const bool vxsqrt1 = !vxsnan1 && std::signbit(b1) && !is_zero(b1);\n   const bool zx0 = is_zero(b0);\n   const bool zx1 = is_zero(b1);\n\n   const uint32_t oldFPSCR = state->fpscr.value;\n   state->fpscr.vxsnan |= vxsnan0 || vxsnan1;\n   state->fpscr.vxsqrt |= vxsqrt0 || vxsqrt1;\n   state->fpscr.zx |= zx0 || zx1;\n\n   double d0 = 0.0, d1 = 0.0;\n   bool write = true;\n   if (((vxsnan0 || vxsqrt0) && state->fpscr.ve) || (zx0 && state->fpscr.ze)) {\n      write = false;\n   } else {\n      d0 = ppc_estimate_reciprocal_root(b0);\n      updateFPRF(state, d0);\n   }\n   if (((vxsnan1 || vxsqrt1) && state->fpscr.ve) || (zx1 && state->fpscr.ze)) {\n      write = false;\n   } else {\n      d1 = ppc_estimate_reciprocal_root(b1);\n   }\n\n   if (write) {\n      // ps_rsqrte behaves strangely when the result's magnitude is out of\n      // range: ps0 keeps its double-precision exponent, while ps1 appears\n      // to get an arbitrary value from the floating-point circuitry.  The\n      // details of how ps1's exponent is affected are unknown, but the\n      // logic below works for double-precision inputs 0x7FE...FFF (maximum\n      // normal) and 0x000...001 (minimum denormal).\n\n      auto bits0 = get_float_bits(d0);\n      bits0.mantissa &= UINT64_C(0xFFFFFE0000000);\n      state->fpr[instr.frD].paired0 = bits0.v;\n\n      auto bits1 = get_float_bits(d1);\n      if (bits1.exponent == 0) {\n         // Leave as zero (reciprocal square root can never be a denormal).\n      } else if (bits1.exponent < 1151) {\n         int8_t exponent8 = (bits1.exponent - 1023) & 0xFF;\n         bits1.exponent = 1023 + exponent8;\n      } else if (bits1.exponent < 2047) {\n         bits1.exponent = 1022;\n      }\n      bits1.mantissa &= UINT64_C(0xFFFFFE0000000);\n      state->fpr[instr.frD].paired1 = bits1.v;\n   }\n\n   updateFPSCR(state, oldFPSCR);\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\n// Select\nstatic void\nps_sel(cpu::Core *state, Instruction instr)\n{\n   auto a0 = state->fpr[instr.frA].paired0;\n   auto a1 = state->fpr[instr.frA].paired1;\n\n   auto b0 = state->fpr[instr.frB].paired0;\n   auto b1 = state->fpr[instr.frB].paired1;\n\n   auto c0 = state->fpr[instr.frC].paired0;\n   auto c1 = state->fpr[instr.frC].paired1;\n\n   auto d1 = (a1 >= 0) ? c1 : b1;\n   auto d0 = (a0 >= 0) ? c0 : b0;\n\n   state->fpr[instr.frD].paired0 = d0;\n   state->fpr[instr.frD].paired1 = d1;\n\n   if (instr.rc) {\n      updateFloatConditionRegister(state);\n   }\n}\n\nvoid\ncpu::interpreter::registerPairedInstructions()\n{\n   RegisterInstruction(ps_add);\n   RegisterInstruction(ps_div);\n   RegisterInstruction(ps_mul);\n   RegisterInstruction(ps_sub);\n   RegisterInstruction(ps_abs);\n   RegisterInstruction(ps_nabs);\n   RegisterInstruction(ps_neg);\n   RegisterInstruction(ps_sel);\n   RegisterInstruction(ps_res);\n   RegisterInstruction(ps_rsqrte);\n   RegisterInstruction(ps_msub);\n   RegisterInstruction(ps_madd);\n   RegisterInstruction(ps_nmsub);\n   RegisterInstruction(ps_nmadd);\n   RegisterInstruction(ps_mr);\n   RegisterInstruction(ps_sum0);\n   RegisterInstruction(ps_sum1);\n   RegisterInstruction(ps_muls0);\n   RegisterInstruction(ps_muls1);\n   RegisterInstruction(ps_madds0);\n   RegisterInstruction(ps_madds1);\n   RegisterInstruction(ps_merge00);\n   RegisterInstruction(ps_merge01);\n   RegisterInstruction(ps_merge10);\n   RegisterInstruction(ps_merge11);\n}\n"
  },
  {
    "path": "src/libcpu/src/interpreter/interpreter_system.cpp",
    "content": "#include \"cpu.h\"\n#include \"cpu_breakpoints.h\"\n#include \"cpu_internal.h\"\n#include \"espresso/espresso_spr.h\"\n#include \"espresso/espresso_instructionset.h\"\n#include \"interpreter_insreg.h\"\n#include \"mem.h\"\n\n#include <common/align.h>\n#include <common/bitutils.h>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <fmt/core.h>\n\nusing espresso::SPR;\n\n/*\n// System Linkage\nINS(rfi, (), (), (), (opcd == 19, xo1 == 50), \"\")\n\n// Trap\nINS(tw, (), (to, ra, rb), (), (opcd == 31, xo1 == 4), \"\")\nINS(twi, (), (to, ra, simm), (), (opcd == 3), \"\")\n\n// Lookaside Buffer Management\nINS(tlbie, (), (rb), (), (opcd == 31, xo1 == 306), \"\")\nINS(tlbsync, (), (), (), (opcd == 31, xo1 == 566), \"\")\n\n// External Control\nINS(eciwx, (rd), (ra, rb), (), (opcd == 31, xo1 == 310), \"\")\nINS(ecowx, (rd), (ra, rb), (), (opcd == 31, xo1 == 438), \"\")\n*/\n\n// Cache Management\nstatic void\nicbi(cpu::Core *state, Instruction instr)\n{\n}\n\n// Data Cache Block Flush\nstatic void\ndcbf(cpu::Core *state, Instruction instr)\n{\n}\n\n// Data Cache Block Invalidate\nstatic void\ndcbi(cpu::Core *state, Instruction instr)\n{\n}\n\n// Data Cache Block Store\nstatic void\ndcbst(cpu::Core *state, Instruction instr)\n{\n}\n\n// Data Cache Block Touch\nstatic void\ndcbt(cpu::Core *state, Instruction instr)\n{\n}\n\n// Data Cache Block Touch for Store\nstatic void\ndcbtst(cpu::Core *state, Instruction instr)\n{\n}\n\n// Data Cache Block Zero\nstatic void\ndcbz(cpu::Core *state, Instruction instr)\n{\n   uint32_t addr;\n\n   if (instr.rA == 0) {\n      addr = 0;\n   } else {\n      addr = state->gpr[instr.rA];\n   }\n\n   addr += state->gpr[instr.rB];\n   addr = align_down(addr, 32);\n   memset(mem::translate(addr), 0, 32);\n}\n\n// Data Cache Block Zero Locked\nstatic void\ndcbz_l(cpu::Core *state, Instruction instr)\n{\n   dcbz(state, instr);\n}\n\n// Enforce In-Order Execution of I/O\nstatic void\neieio(cpu::Core *state, Instruction instr)\n{\n}\n\n// Synchronise\nstatic void\nsync(cpu::Core *state, Instruction instr)\n{\n}\n\n// Instruction Synchronise\nstatic void\nisync(cpu::Core *state, Instruction instr)\n{\n}\n\n// Move from Special Purpose Register\nstatic void\nmfspr(cpu::Core *state, Instruction instr)\n{\n   auto spr = decodeSPR(instr);\n   auto value = 0u;\n\n   switch (spr) {\n   case SPR::XER:\n      value = state->xer.value;\n      break;\n   case SPR::LR:\n      value = state->lr;\n      break;\n   case SPR::CTR:\n      value = state->ctr;\n      break;\n   case SPR::UGQR0:\n      value = state->gqr[0].value;\n      break;\n   case SPR::UGQR1:\n      value = state->gqr[1].value;\n      break;\n   case SPR::UGQR2:\n      value = state->gqr[2].value;\n      break;\n   case SPR::UGQR3:\n      value = state->gqr[3].value;\n      break;\n   case SPR::UGQR4:\n      value = state->gqr[4].value;\n      break;\n   case SPR::UGQR5:\n      value = state->gqr[5].value;\n      break;\n   case SPR::UGQR6:\n      value = state->gqr[6].value;\n      break;\n   case SPR::UGQR7:\n      value = state->gqr[7].value;\n      break;\n   case SPR::UPIR:\n      value = cpu::this_core::id();\n      break;\n   default:\n      gLog->error(\"Invalid mfspr SPR {}\", static_cast<uint32_t>(spr));\n      state->interrupt.fetch_or(cpu::PROGRAM_INTERRUPT);\n      cpu::this_core::checkInterrupts();\n   }\n\n   state->gpr[instr.rD] = value;\n}\n\n// Move to Special Purpose Register\nstatic void\nmtspr(cpu::Core *state, Instruction instr)\n{\n   auto spr = decodeSPR(instr);\n   auto value = state->gpr[instr.rS];\n\n   switch (spr) {\n   case SPR::XER:\n      state->xer.value = value;\n      break;\n   case SPR::LR:\n      state->lr = value;\n      break;\n   case SPR::CTR:\n      state->ctr = value;\n      break;\n   case SPR::UGQR0:\n      state->gqr[0].value = value;\n      break;\n   case SPR::UGQR1:\n      state->gqr[1].value = value;\n      break;\n   case SPR::UGQR2:\n      state->gqr[2].value = value;\n      break;\n   case SPR::UGQR3:\n      state->gqr[3].value = value;\n      break;\n   case SPR::UGQR4:\n      state->gqr[4].value = value;\n      break;\n   case SPR::UGQR5:\n      state->gqr[5].value = value;\n      break;\n   case SPR::UGQR6:\n      state->gqr[6].value = value;\n      break;\n   case SPR::UGQR7:\n      state->gqr[7].value = value;\n      break;\n   default:\n      gLog->error(\"Invalid mtspr SPR {}\", static_cast<uint32_t>(spr));\n      state->interrupt.fetch_or(cpu::PROGRAM_INTERRUPT);\n      cpu::this_core::checkInterrupts();\n   }\n}\n\n// Move from Time Base Register\nstatic void\nmftb(cpu::Core *state, Instruction instr)\n{\n   auto tbr = decodeSPR(instr);\n   auto value = 0u;\n\n   switch (tbr) {\n   case SPR::UTBL:\n      value = static_cast<uint32_t>(state->tb() & 0xFFFFFFFF);\n      break;\n   case SPR::UTBU:\n      value = static_cast<uint32_t>(state->tb()  >> 32);\n      break;\n   default:\n      decaf_abort(fmt::format(\"Invalid mftb TBR {}\", static_cast<uint32_t>(tbr)));\n   }\n\n   state->gpr[instr.rD] = value;\n}\n\n// Move from Machine State Register\nstatic void\nmfmsr(cpu::Core *state, Instruction instr)\n{\n   state->gpr[instr.rD] = state->msr.value;\n}\n\n// Move to Machine State Register\nstatic void\nmtmsr(cpu::Core *state, Instruction instr)\n{\n   state->msr.value = state->gpr[instr.rS];\n}\n\n// Move from Segment Register\nstatic void\nmfsr(cpu::Core *state, Instruction instr)\n{\n   state->gpr[instr.rD] = state->sr[instr.sr];\n}\n\n// Move from Segment Register Indirect\nstatic void\nmfsrin(cpu::Core *state, Instruction instr)\n{\n   auto sr = state->gpr[instr.rB] & 0xf;\n   state->gpr[instr.rD] = state->sr[sr];\n}\n\n// Move to Segment Register\nstatic void\nmtsr(cpu::Core *state, Instruction instr)\n{\n   state->sr[instr.sr] = state->gpr[instr.rS];\n}\n\n// Move to Segment Register Indirect\nstatic void\nmtsrin(cpu::Core *state, Instruction instr)\n{\n   auto sr = state->gpr[instr.rB] & 0xf;\n   state->sr[sr] = state->gpr[instr.rS];\n}\n\n// Kernel call\nstatic void\nkc(cpu::Core *state, Instruction instr)\n{\n   auto kcId = instr.kcn;\n\n   auto handler = cpu::getSystemCallHandler(kcId);\n   state->systemCallStackHead = state->gpr[1];\n   state = handler(state, kcId);\n}\n\n// Trap Word\nstatic void\ntw(cpu::Core *state, Instruction instr)\n{\n   if (!cpu::hasBreakpoint(state->cia)) {\n      decaf_abort(fmt::format(\"Game raised a trap exception at 0x{:08X}.\", state->nia));\n   }\n\n   // By this point we have already handled the breakpoint thanks to checkInterrupts,\n   // so here we just need to execute the breakpoint's saved instruction!\n   auto savedInstr = cpu::getBreakpointSavedCode(state->cia);\n   auto data = espresso::decodeInstruction(savedInstr);\n   decaf_check(data);\n\n   auto handler = cpu::interpreter::getInstructionHandler(data->id);\n   decaf_check(handler);\n\n   handler(state, savedInstr);\n}\n\nvoid\ncpu::interpreter::registerSystemInstructions()\n{\n   RegisterInstruction(dcbf);\n   RegisterInstruction(dcbi);\n   RegisterInstruction(dcbst);\n   RegisterInstruction(dcbt);\n   RegisterInstruction(dcbtst);\n   RegisterInstruction(dcbz);\n   RegisterInstruction(dcbz_l);\n   RegisterInstruction(eieio);\n   RegisterInstruction(icbi);\n   RegisterInstruction(isync);\n   RegisterInstruction(sync);\n   RegisterInstruction(mfspr);\n   RegisterInstruction(mtspr);\n   RegisterInstruction(mftb);\n   RegisterInstruction(mfmsr);\n   RegisterInstruction(mtmsr);\n   RegisterInstruction(mfsr);\n   RegisterInstruction(mfsrin);\n   RegisterInstruction(mtsr);\n   RegisterInstruction(mtsrin);\n   RegisterInstruction(kc);\n   RegisterInstruction(tw);\n}\n"
  },
  {
    "path": "src/libcpu/src/jit/binrec/jit_binrec.cpp",
    "content": "#include \"cpu.h\"\n#include \"cpu_breakpoints.h\"\n#include \"cpu_internal.h\"\n#include \"espresso/espresso_instructionset.h\"\n#include \"jit_binrec.h\"\n#include \"interpreter/interpreter.h\"\n#include \"mem.h\"\n#include \"mmu.h\"\n\n#include <cfenv>\n#include <common/bitutils.h>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <cstdlib>\n#include <fmt/core.h>\n\n#define offsetof2(s, m) ((size_t)&reinterpret_cast<char const volatile&>((((s*)0)->m)))\n\nnamespace cpu\n{\n\nnamespace jit\n{\n\nstatic void *brChainLookup(BinrecCore *core, ppcaddr_t address);\nstatic uint64_t brTimeBaseHandler(BinrecCore *core);\nstatic BinrecCore *brSyscallHandler(BinrecCore *core, espresso::Instruction instr);\nstatic BinrecCore *brTrapHandler(BinrecCore *core);\n\nstatic void\nbrLog(void *, binrec::LogLevel level, const char *message);\n\nBinrecBackend::BinrecBackend(size_t codeCacheSize,\n                             size_t dataCacheSize)\n{\n   mCodeCache.initialise(codeCacheSize, dataCacheSize);\n   mHandles.fill(nullptr);\n}\n\nBinrecBackend::~BinrecBackend()\n{\n   mCodeCache.free();\n}\n\nCore *\nBinrecBackend::initialiseCore(uint32_t id)\n{\n   // See binrec.h.\n   static const uint16_t fresTable[64] = {\n      0x3FFC,0x3E1, 0x3C1C,0x3A7, 0x3875,0x371, 0x3504,0x340,\n      0x31C4,0x313, 0x2EB1,0x2EA, 0x2BC8,0x2C4, 0x2904,0x2A0,\n      0x2664,0x27F, 0x23E5,0x261, 0x2184,0x245, 0x1F40,0x22A,\n      0x1D16,0x212, 0x1B04,0x1FB, 0x190A,0x1E5, 0x1725,0x1D1,\n      0x1554,0x1BE, 0x1396,0x1AC, 0x11EB,0x19B, 0x104F,0x18B,\n      0x0EC4,0x17C, 0x0D48,0x16E, 0x0BD7,0x15B, 0x0A7C,0x15B,\n      0x0922,0x143, 0x07DF,0x143, 0x069C,0x12D, 0x056F,0x12D,\n      0x0442,0x11A, 0x0328,0x11A, 0x020E,0x108, 0x0106,0x106\n   };\n   static const uint16_t frsqrteTable[64] = {\n      0x7FF4,0x7A4, 0x7852,0x700, 0x7154,0x670, 0x6AE4,0x5F2,\n      0x64F2,0x584, 0x5F6E,0x524, 0x5A4C,0x4CC, 0x5580,0x47E,\n      0x5102,0x43A, 0x4CCA,0x3FA, 0x48D0,0x3C2, 0x450E,0x38E,\n      0x4182,0x35E, 0x3E24,0x332, 0x3AF2,0x30A, 0x37E8,0x2E6,\n      0x34FD,0x568, 0x2F97,0x4F3, 0x2AA5,0x48D, 0x2618,0x435,\n      0x21E4,0x3E7, 0x1DFE,0x3A2, 0x1A5C,0x365, 0x16F8,0x32E,\n      0x13CA,0x2FC, 0x10CE,0x2D0, 0x0DFE,0x2A8, 0x0B57,0x283,\n      0x08D4,0x261, 0x0673,0x243, 0x0431,0x226, 0x020B,0x20B\n   };\n\n   auto core = new BinrecCore {};\n   core->id = id;\n   core->backend = this;\n   core->chainLookup = brChainLookup;\n   core->mftbHandler = brTimeBaseHandler;\n   core->scHandler = brSyscallHandler;\n   core->trapHandler = brTrapHandler;\n   core->fresTable = fresTable;\n   core->frsqrteTable = frsqrteTable;\n\n#ifdef DECAF_JIT_ALLOW_PROFILING\n   core->calledHLE = false;\n#endif\n\n   return core;\n}\n\nvoid\nBinrecBackend::addReadOnlyRange(uint32_t address, uint32_t size)\n{\n   mReadOnlyRanges.emplace_back(address, size);\n}\n\nvoid\nBinrecBackend::clearCache(uint32_t address, uint32_t size)\n{\n   if (address == 0 && size == 0xFFFFFFFF) {\n      mCodeCache.clear();\n      mTotalProfileTime = 0;\n   } else {\n      mCodeCache.invalidate(address, size);\n   }\n}\n\nBinrecHandle *\nBinrecBackend::createBinrecHandle()\n{\n   binrec::Setup setup;\n   std::memset(&setup, 0, sizeof(setup));\n   setup.guest = binrec::Arch::BINREC_ARCH_PPC_7XX;\n\n#ifdef PLATFORM_WINDOWS\n   setup.host = binrec::Arch::BINREC_ARCH_X86_64_WINDOWS_SEH;\n#else\n   setup.host = binrec::native_arch();\n#endif\n\n   setup.host_features = binrec::native_features();\n   setup.guest_memory_base = reinterpret_cast<void *>(getBaseVirtualAddress());\n   setup.state_offsets_ppc.gpr = offsetof2(BinrecCore, gpr);\n   setup.state_offsets_ppc.fpr = offsetof2(BinrecCore, fpr);\n   setup.state_offsets_ppc.gqr = offsetof2(BinrecCore, gqr);\n   setup.state_offsets_ppc.lr = offsetof2(BinrecCore, lr);\n   setup.state_offsets_ppc.ctr = offsetof2(BinrecCore, ctr);\n   setup.state_offsets_ppc.cr = offsetof2(BinrecCore, cr);\n   setup.state_offsets_ppc.xer = offsetof2(BinrecCore, xer);\n   setup.state_offsets_ppc.fpscr = offsetof2(BinrecCore, fpscr);\n   setup.state_offsets_ppc.pvr = offsetof2(BinrecCore, pvr);\n   setup.state_offsets_ppc.pir = offsetof2(BinrecCore, id);\n   setup.state_offsets_ppc.reserve_flag = offsetof2(BinrecCore, reserveFlag);\n   setup.state_offsets_ppc.reserve_state = offsetof2(BinrecCore, reserveData);\n   setup.state_offsets_ppc.nia = offsetof2(BinrecCore, nia);\n   setup.state_offsets_ppc.timebase_handler = offsetof2(BinrecCore, mftbHandler);\n   setup.state_offsets_ppc.sc_handler = offsetof2(BinrecCore, scHandler);\n   setup.state_offsets_ppc.trap_handler = offsetof2(BinrecCore, trapHandler);\n   setup.state_offsets_ppc.fres_lut = offsetof2(BinrecCore, fresTable);\n   setup.state_offsets_ppc.frsqrte_lut = offsetof2(BinrecCore, frsqrteTable);\n   setup.state_offset_chain_lookup = offsetof2(BinrecCore, chainLookup);\n   setup.state_offset_branch_exit_flag = offsetof2(BinrecCore, interrupt);\n   setup.log = brLog;\n\n   auto handle = new BinrecHandle {};\n   if (!handle->initialize(setup)) {\n      delete handle;\n      return nullptr;\n   }\n\n   handle->set_optimization_flags(mOptFlags.common, mOptFlags.guest, mOptFlags.host);\n   handle->enable_branch_exit_test(true);\n   handle->enable_chaining(mOptFlags.useChaining);\n\n   if (mVerifyEnabled && mVerifyAddress == 0) {\n      handle->set_pre_insn_callback(brVerifyPreHandler);\n      handle->set_post_insn_callback(brVerifyPostHandler);\n   }\n\n   for (const auto &range : mReadOnlyRanges) {\n      handle->add_readonly_region(range.first, range.second);\n   }\n\n   return handle;\n}\n\nCodeBlock *\nBinrecBackend::checkForCodeBlockTrampoline(uint32_t address)\n{\n   // If the first instruction is an unconditional branch (such as for a\n   // function wrapping another one), just call the target's code directly.\n   // core->nia will eventually be set to a proper value, so we don't need\n   // to worry that it will be out of sync here.\n   auto instr = mem::read<espresso::Instruction>(address);\n   auto data = espresso::decodeInstruction(instr);\n\n   if (data && data->id == espresso::InstructionID::b && !instr.lk) {\n      // Watch out for cycles when looking up the target!\n      auto target = address;\n      for (int tries = 10;\n           tries > 0 && data && data->id == espresso::InstructionID::b && !instr.lk;\n           --tries) {\n         auto branchAddress = target;\n         target = sign_extend<26>(instr.li << 2);\n\n         if (!instr.aa) {\n            target += branchAddress;\n         }\n\n         instr = mem::read<uint32_t>(target);\n         data = espresso::decodeInstruction(instr);\n      }\n\n      if (target != address) {\n         auto block = mCodeCache.getBlockByAddress(target);\n\n         if (block) {\n            // Mark this address to point to target block\n            mCodeCache.setBlockIndex(address, mCodeCache.getIndex(block));\n            return block;\n         }\n      }\n   }\n\n   return nullptr;\n}\n\nCodeBlock *\nBinrecBackend::getCodeBlock(BinrecCore *core, uint32_t address)\n{\n   auto indexPtr = mCodeCache.getIndexPointer(address);\n   auto blockIndex = indexPtr->load();\n\n   // If block is uncompiled, let's try mark it as compiling!\n   if (UNLIKELY(blockIndex == CodeBlockIndexUncompiled)) {\n      if (!indexPtr->compare_exchange_strong(blockIndex, CodeBlockIndexCompiling)) {\n         // Another thread has started compiling, wait for it to finish.\n         while (blockIndex == CodeBlockIndexCompiling) {\n            using namespace std::chrono_literals;\n            std::this_thread::sleep_for(10us);\n            blockIndex = indexPtr->load();\n         }\n      }\n   }\n\n   // Check if the block has been compiled\n   if (LIKELY(blockIndex >= 0)) {\n      auto block = mCodeCache.getBlockByIndex(blockIndex);\n      return block;\n   }\n\n   // Do not try to recompile again if it failed before\n   if (UNLIKELY(blockIndex == CodeBlockIndexError)) {\n      return nullptr;\n   }\n\n   // Do not compile if there is a breakpoint at address.\n   if (UNLIKELY(hasBreakpoint(address))) {\n      return nullptr;\n   }\n\n   // Check for possible branch trampoline\n   if (auto block = checkForCodeBlockTrampoline(address)) {\n      return block;\n   }\n\n   auto handle = mHandles[core->id];\n   if (!handle) {\n      handle = createBinrecHandle();\n      mHandles[core->id] = handle;\n   }\n\n   if (mVerifyEnabled && mVerifyAddress != 0) {\n      if (address == mVerifyAddress) {\n         handle->set_pre_insn_callback(brVerifyPreHandler);\n         handle->set_post_insn_callback(brVerifyPostHandler);\n      } else {\n         handle->set_pre_insn_callback(nullptr);\n         handle->set_post_insn_callback(nullptr);\n      }\n   }\n\n   // In extreme cases (such as dense floating-point code with no\n   // optimizations enabled), translation could fail due to internal\n   // libbinrec limits, so try repeatedly with smaller code ranges if\n   // the first translation attempt fails.\n   auto limit = 4096u;\n   auto size = long { 0 };\n   void *buffer = nullptr;\n\n   while (!handle->translate(core, address, address + limit - 1, &buffer, &size)) {\n      limit /= 2;\n\n      if (limit < 256) {\n         gLog->warn(\"Failed to translate code at 0x{:X}\", address);\n         indexPtr->store(CodeBlockIndexError);\n         return nullptr;\n      }\n   }\n\n#ifdef PLATFORM_WINDOWS\n   // First 8 bytes of buffer is offset to start of code\n   auto codeOffset = *reinterpret_cast<uint64_t *>(buffer);\n   auto unwindInfo = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(buffer) + 8);\n   auto unwindSize = codeOffset - 8;\n   auto code = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(buffer) + codeOffset);\n   auto codeSize = size - codeOffset;\n#else\n   auto code = buffer;\n   auto codeSize = size;\n   void *unwindInfo = nullptr;\n   auto unwindSize = size_t { 0 };\n#endif\n\n   auto block = mCodeCache.registerCodeBlock(address, code, codeSize, unwindInfo, unwindSize);\n   decaf_check(block);\n   free(buffer);\n\n   // Clear any floating-point exceptions raised by the translation so\n   // the translated code doesn't pick them up.\n   std::feclearexcept(FE_ALL_EXCEPT);\n   return block;\n}\n\ninline CodeBlock *\nBinrecBackend::getCodeBlockFast(BinrecCore *core, uint32_t address)\n{\n   auto indexPtr = mCodeCache.getConstIndexPointer(address);\n   if (LIKELY(indexPtr)) {\n      auto blockIndex = indexPtr->load();\n      if (LIKELY(blockIndex >= 0)) {\n         auto block = mCodeCache.getBlockByIndex(blockIndex);\n         return block;\n      }\n   }\n\n   // Block is not yet compiled, so take the slow path.\n   return getCodeBlock(core, address);\n}\n\nstatic inline uint64_t\nrdtsc()\n{\n#ifdef _MSC_VER\n   return __rdtsc();\n#else\n   uint64_t tsc;\n   __asm__ volatile(\"rdtsc; shl $32,%%rdx; or %%rdx,%%rax\"\n                    : \"=a\" (tsc) : : \"rdx\");\n   return tsc;\n#endif\n}\n\nvoid\nBinrecBackend::resumeExecution()\n{\n   auto memBase = cpu::getBaseVirtualAddress();\n\n   // Prepare FPU state for guest code execution.\n   this_core::updateRoundingMode();\n   std::feclearexcept(FE_ALL_EXCEPT);\n\n   auto core = reinterpret_cast<BinrecCore *>(this_core::state());\n   decaf_check(core->nia != CALLBACK_ADDR);\n\n   if (mVerifyEnabled) {\n      // Use a separate routine for verify mode so we don't have to check\n      //  the current mode on every iteration through the loop.  Note that\n      //  we don't attempt to profile while verifying.\n      return resumeVerifyExecution();\n   }\n\n   do {\n      if (UNLIKELY(core->interrupt.load())) {\n         this_core::checkInterrupts();\n         // We might have been rescheduled onto a different core.\n         core = reinterpret_cast<BinrecCore *>(this_core::state());\n      }\n\n      const ppcaddr_t address = core->nia;\n      auto block = getCodeBlockFast(core, address);\n\n      // To keep overhead in the non-profiling case as low as possible, we\n      //  only check for zeroness of the profiling mask here, which is just\n      //  a memory-immediate compare and a non-taken branch on x86.  If the\n      //  mask is nonzero, we'll check again for the specific core bit on\n      //  the profiling side of the test.\n#ifdef DECAF_JIT_ALLOW_PROFILING\n      if (LIKELY(!mProfilingMask)) {\n#else\n      if (1) {\n#endif\n\n         if (LIKELY(block)) {\n            auto entry = reinterpret_cast<BinrecEntry>(block->code);\n            core = entry(core, memBase);\n         } else {\n            // Step over the current instruction, in case it's confusing\n            // the translator.  TODO: Consider blacklisting the address to\n            // avoid trying to translate it every time we encounter it.\n            interpreter::step_one(core);\n\n            // If we just returned from a system call, we might have been\n            //  rescheduled onto a different core.\n            core = reinterpret_cast<BinrecCore *>(this_core::state());\n         }\n      } else { // mProfilingMask != 0\n         const uint64_t start = rdtsc();\n\n         if (block) {\n            auto entry = reinterpret_cast<BinrecEntry>(block->code);\n            core = entry(core, memBase);\n         } else {\n            interpreter::step_one(core);\n            core = reinterpret_cast<BinrecCore *>(this_core::state());\n         }\n\n         // Don't count profiling data for HLE calls since those have\n         //  nothing to do with JIT performance (and might also have\n         //  caused us to switch cores, so the RDTSC difference wouldn't\n         //  make any sense).\n         if (UNLIKELY(core->calledHLE)) {\n            core->calledHLE = false;\n         } else if (block && mProfilingMask & (1 << core->id)) {\n            const uint64_t time = rdtsc() - start;\n            mTotalProfileTime += time;\n            block->profileData.time += time;\n            block->profileData.count++;\n         }\n      }\n   } while (core->nia != CALLBACK_ADDR);\n}\n\n\n/**\n * Get a sample of JIT stats.\n */\nbool\nBinrecBackend::sampleStats(JitStats &stats)\n{\n   stats.totalTimeInCodeBlocks = mTotalProfileTime;\n   stats.compiledBlocks = mCodeCache.getCompiledCodeBlocks();\n   stats.usedCodeCacheSize = mCodeCache.getCodeCacheSize();\n   stats.usedDataCacheSize = mCodeCache.getDataCacheSize();\n   return true;\n}\n\n\n/**\n * Reset JIT profiling stats.\n */\nvoid\nBinrecBackend::resetProfileStats()\n{\n   // Clear block stats\n   auto blocks = mCodeCache.getCompiledCodeBlocks();\n   for (auto &block : blocks) {\n      block.profileData.count = 0;\n      block.profileData.time = 0;\n   }\n\n   // Clear generic stats\n   mTotalProfileTime = 0;\n}\n\n\n/**\n * Set which cores to profile.\n */\nvoid\nBinrecBackend::setProfilingMask(unsigned mask)\n{\n   mProfilingMask = mask;\n}\n\n\n/**\n * Get which cores are being profiled.\n */\nunsigned\nBinrecBackend::getProfilingMask()\n{\n   return mProfilingMask;\n}\n\n\n/**\n * Callback from libbinrec to look up translated blocks for function chaining.\n */\nvoid *\nbrChainLookup(BinrecCore *core, ppcaddr_t address)\n{\n   auto block = core->backend->getCodeBlock(core, address);\n   if (!block) {\n      return nullptr;\n   }\n\n   return block->code;\n}\n\n\n/**\n * Callback from libbinrec to handle time base reads.\n */\nuint64_t\nbrTimeBaseHandler(BinrecCore *core)\n{\n   return core->tb();\n}\n\n\n/**\n * Callback from libbinrec to handle system calls.\n */\nBinrecCore *\nbrSyscallHandler(BinrecCore *core,\n                 espresso::Instruction instr)\n{\n   core->systemCallStackHead = core->gpr[1];\n   auto handler = cpu::getSystemCallHandler(instr.kcn);\n   auto newCore = handler(core, instr.kcn);\n\n   // We might have been rescheduled on a new core.\n   core = reinterpret_cast<BinrecCore *>(newCore);\n\n   // If the next instruction is a blr, execute it ourselves rather than\n   // spending the overhead of calling into JIT for just that instruction.\n   auto next_instr = mem::read<uint32_t>(core->nia);\n   if (next_instr == 0x4E800020) {\n      core->nia = core->lr;\n   }\n\n#ifdef DECAF_JIT_ALLOW_PROFILING\n   core->calledHLE = true;  // Suppress profiling for this call.\n#endif\n   return core;\n}\n\n\n/**\n * Callback from libbinrec to handle PPC trap exceptions.\n */\nBinrecCore *\nbrTrapHandler(BinrecCore *core)\n{\n   if (!cpu::hasBreakpoint(core->nia)) {\n      decaf_abort(fmt::format(\"Game raised a trap exception at 0x{:08X}.\",\n                              core->nia));\n   }\n\n   // If we have a breakpoint, we will fall back to interpreter to handle it.\n   return core;\n}\n\n\n/**\n * Callback from libbinrec to handle log output.\n */\nvoid\nbrLog(void *, binrec::LogLevel level, const char *message)\n{\n   switch (level) {\n   case BINREC_LOGLEVEL_ERROR:\n      gLog->error(\"[libbinrec] {}\", message);\n      break;\n   case BINREC_LOGLEVEL_WARNING:\n      gLog->warn(\"[libbinrec] {}\", message);\n      break;\n   case BINREC_LOGLEVEL_INFO:\n      // Nothing really important here, so output as debug instead\n      gLog->debug(\"[libbinrec] {}\", message);\n      break;\n   }\n}\n\n} // namespace jit\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/jit/binrec/jit_binrec.h",
    "content": "#pragma once\n#include \"state.h\"\n#include \"mem.h\"\n#include \"espresso/espresso_instruction.h\"\n#include \"jit/jit_codecache.h\"\n#include \"jit/jit_backend.h\"\n\n#include <binrec++.h>\n#include <vector>\n#include <string>\n\nnamespace cpu\n{\n\nnamespace jit\n{\n\nstruct BinrecOptimisationFlags\n{\n   bool useChaining = false;\n   unsigned int common = 0;\n   unsigned int guest = 0;\n   unsigned int host = 0;\n};\n\nclass BinrecBackend;\nstruct VerifyBuffer;\n\nstruct BinrecCore : public Core\n{\n   BinrecBackend *backend;\n\n   // JIT callback functions\n   void *(*chainLookup)(BinrecCore *, ppcaddr_t);\n   bool (*branchCallback)(BinrecCore *, ppcaddr_t);\n   uint64_t (*mftbHandler)(BinrecCore *);\n   BinrecCore *(*scHandler)(BinrecCore *, espresso::Instruction);\n   BinrecCore *(*trapHandler)(BinrecCore *);\n\n   // Lookup tables for fres/frsqrte instructions\n   const uint16_t *fresTable;\n   const uint16_t *frsqrteTable;\n\n   // JIT verification buffer (local to jit::resume())\n   VerifyBuffer *verifyBuffer;\n\n   // HLE call flag (for JIT profiler)\n   bool calledHLE;\n\n   //! Trap Handler hit a breakpoint.\n   bool hitBreakpoint;\n};\n\nusing BinrecHandle = binrec::Handle<BinrecCore *>;\nusing BinrecEntry = BinrecCore * (*)(BinrecCore *core, uintptr_t membase);\n\nclass BinrecBackend : public JitBackend\n{\npublic:\n   BinrecBackend(size_t codeCacheSize,\n                 size_t dataCacheSize);\n\n   ~BinrecBackend() override;\n\n   Core *\n   initialiseCore(uint32_t id) override;\n\n   void\n   clearCache(uint32_t address,\n              uint32_t size) override;\n\n   void\n   resumeExecution() override;\n\n   void\n   addReadOnlyRange(uint32_t address,\n                    uint32_t size) override;\n\n   bool\n   sampleStats(JitStats &stats) override;\n\n   void\n   resetProfileStats() override;\n\n   void\n   setProfilingMask(unsigned mask) override;\n\n   unsigned\n   getProfilingMask() override;\n\npublic:\n   void\n   setOptFlags(const std::vector<std::string> &optList);\n\n   void\n   setVerifyEnabled(bool enabled, uint32_t address = 0);\n\n   CodeBlock *\n   getCodeBlock(BinrecCore *core, uint32_t address);\n\nprotected:\n   BinrecHandle *createBinrecHandle();\n\n   inline CodeBlock *\n   getCodeBlockFast(BinrecCore *core, uint32_t address);\n\n   CodeBlock *\n   checkForCodeBlockTrampoline(uint32_t address);\n\n   void resumeVerifyExecution();\n\n   void\n   verifyInit(Core *core, VerifyBuffer *verifyBuf);\n\n   void\n   verifyPre(Core *core,\n             VerifyBuffer *verifyBuf,\n             uint32_t cia,\n             uint32_t instr);\n\n   void\n   verifyPost(Core *core,\n              VerifyBuffer *verifyBuf,\n              uint32_t cia,\n              uint32_t instr);\n\n   static void\n   brVerifyPreHandler(BinrecCore *core, uint32_t address);\n\n   static void\n   brVerifyPostHandler(BinrecCore *core, uint32_t address);\n\nprivate:\n   CodeCache mCodeCache;\n   std::array<BinrecHandle *, 3> mHandles;\n   BinrecOptimisationFlags mOptFlags;\n   std::vector<std::pair<ppcaddr_t, uint32_t>> mReadOnlyRanges;\n   std::atomic<uint64_t> mTotalProfileTime { 0 };\n   uint32_t mProfilingMask = 0;\n   bool mVerifyEnabled = false;\n   uint32_t mVerifyAddress = 0;\n};\n\n} // namespace jit\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/jit/binrec/jit_binrec_opt.cpp",
    "content": "#include \"jit_binrec.h\"\n\n#include <common/log.h>\n#include <map>\n#include <string>\n#include <vector>\n\nnamespace cpu\n{\n\nnamespace jit\n{\n\nstruct OptFlagInfo\n{\n   enum {\n      OPTFLAG_COMMON,\n      OPTFLAG_GUEST,\n      OPTFLAG_HOST,\n      OPTFLAG_CHAIN\n   } type;\n\n   unsigned int value;\n};\n\nstatic const std::map<std::string, OptFlagInfo>\nsOptFlags = {\n   {\"BASIC\",                    {OptFlagInfo::OPTFLAG_COMMON,\n                                 binrec::Optimize::BASIC}},\n   {\"DECONDITION\",              {OptFlagInfo::OPTFLAG_COMMON,\n                                 binrec::Optimize::DECONDITION}},\n   {\"DEEP_DATA_FLOW\",           {OptFlagInfo::OPTFLAG_COMMON,\n                                 binrec::Optimize::DEEP_DATA_FLOW}},\n   {\"DSE\",                      {OptFlagInfo::OPTFLAG_COMMON,\n                                 binrec::Optimize::DSE}},\n   {\"DSE_FP\",                   {OptFlagInfo::OPTFLAG_COMMON,\n                                 binrec::Optimize::DSE_FP}},\n   {\"FOLD_CONSTANTS\",           {OptFlagInfo::OPTFLAG_COMMON,\n                                 binrec::Optimize::FOLD_CONSTANTS}},\n   {\"FOLD_FP_CONSTANTS\",        {OptFlagInfo::OPTFLAG_COMMON,\n                                 binrec::Optimize::FOLD_FP_CONSTANTS}},\n   {\"NATIVE_IEEE_NAN\",          {OptFlagInfo::OPTFLAG_COMMON,\n                                 binrec::Optimize::NATIVE_IEEE_NAN}},\n   {\"NATIVE_IEEE_UNDERFLOW\",    {OptFlagInfo::OPTFLAG_COMMON,\n                                 binrec::Optimize::NATIVE_IEEE_UNDERFLOW}},\n\n   {\"PPC_ASSUME_NO_SNAN\",       {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::ASSUME_NO_SNAN}},\n   {\"PPC_CONSTANT_GQRS\",        {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::CONSTANT_GQRS}},\n   {\"PPC_DETECT_FCFI_EMUL\",     {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::DETECT_FCFI_EMUL}},\n   {\"PPC_FAST_FCTIW\",           {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::FAST_FCTIW}},\n   {\"PPC_FAST_FMADDS\",          {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::FAST_FMADDS}},\n   {\"PPC_FAST_FMULS\",           {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::FAST_FMULS}},\n   {\"PPC_FAST_STFS\",            {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::FAST_STFS}},\n   {\"PPC_FNMADD_ZERO_SIGN\",     {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::FNMADD_ZERO_SIGN}},\n   {\"PPC_FORWARD_LOADS\",        {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::FORWARD_LOADS}},\n   {\"PPC_IGNORE_FPSCR_VXFOO\",   {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::IGNORE_FPSCR_VXFOO}},\n   {\"PPC_NATIVE_RECIPROCAL\",    {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::NATIVE_RECIPROCAL}},\n   {\"PPC_NO_FPSCR_STATE\",       {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::NO_FPSCR_STATE}},\n   {\"PPC_PAIRED_LWARX_STWCX\",   {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::PAIRED_LWARX_STWCX}},\n   {\"PPC_PS_STORE_DENORMALS\",   {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::PS_STORE_DENORMALS}},\n   {\"PPC_SC_BLR\",               {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::SC_BLR}},\n   {\"PPC_SINGLE_PREC_INPUTS\",   {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::SINGLE_PREC_INPUTS}},\n   {\"PPC_TRIM_CR_STORES\",       {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::TRIM_CR_STORES}},\n   {\"PPC_USE_SPLIT_FIELDS\",     {OptFlagInfo::OPTFLAG_GUEST,\n                                 binrec::Optimize::GuestPPC::USE_SPLIT_FIELDS}},\n\n   {\"X86_ADDRESS_OPERANDS\",     {OptFlagInfo::OPTFLAG_HOST,\n                                 binrec::Optimize::HostX86::ADDRESS_OPERANDS}},\n   {\"X86_BRANCH_ALIGNMENT\",     {OptFlagInfo::OPTFLAG_HOST,\n                                 binrec::Optimize::HostX86::BRANCH_ALIGNMENT}},\n   {\"X86_CONDITION_CODES\",      {OptFlagInfo::OPTFLAG_HOST,\n                                 binrec::Optimize::HostX86::CONDITION_CODES}},\n   {\"X86_FIXED_REGS\",           {OptFlagInfo::OPTFLAG_HOST,\n                                 binrec::Optimize::HostX86::FIXED_REGS}},\n   {\"X86_FORWARD_CONDITIONS\",   {OptFlagInfo::OPTFLAG_HOST,\n                                 binrec::Optimize::HostX86::FORWARD_CONDITIONS}},\n   {\"X86_MERGE_REGS\",           {OptFlagInfo::OPTFLAG_HOST,\n                                 binrec::Optimize::HostX86::MERGE_REGS}},\n   {\"X86_STORE_IMMEDIATE\",      {OptFlagInfo::OPTFLAG_HOST,\n                                 binrec::Optimize::HostX86::STORE_IMMEDIATE}},\n\n   // Maps to sUseChaining instead of a flag value\n   {\"CHAIN\",                    {OptFlagInfo::OPTFLAG_CHAIN}},\n};\n\nvoid\nBinrecBackend::setOptFlags(const std::vector<std::string> &optList)\n{\n   mOptFlags.common = 0;\n   mOptFlags.guest = 0;\n   mOptFlags.host = 0;\n   mOptFlags.useChaining = false;\n\n   for (const auto &i : optList) {\n      auto flag = sOptFlags.find(i);\n\n      if (flag == sOptFlags.end()) {\n         gLog->warn(\"Unknown optimization flag: {}\", i);\n         continue;\n      }\n\n      switch (flag->second.type) {\n      case OptFlagInfo::OPTFLAG_CHAIN:\n         mOptFlags.useChaining = true;\n         break;\n      case OptFlagInfo::OPTFLAG_COMMON:\n         mOptFlags.common |= flag->second.value;\n         break;\n      case OptFlagInfo::OPTFLAG_GUEST:\n         mOptFlags.guest |= flag->second.value;\n         break;\n      case OptFlagInfo::OPTFLAG_HOST:\n         mOptFlags.host |= flag->second.value;\n         break;\n      }\n   }\n}\n\nvoid\nBinrecBackend::setVerifyEnabled(bool enabled,\n                                uint32_t address)\n{\n   mVerifyEnabled = enabled;\n   mVerifyAddress = address;\n}\n\n} // namespace jit\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/jit/binrec/jit_binrec_verify.cpp",
    "content": "#include \"cpu.h\"\n#include \"cpu_internal.h\"\n#include \"state.h\"\n#include \"espresso/espresso_disassembler.h\"\n#include \"espresso/espresso_instructionset.h\"\n#include \"interpreter/interpreter.h\"\n#include \"interpreter/interpreter_float.h\"\n#include \"interpreter/interpreter_insreg.h\"\n#include \"jit_binrec.h\"\n#include \"mem.h\"\n#include \"mmu.h\"\n\n#include <binrec++.h>\n#include <common/align.h>\n#include <common/bitutils.h>\n#include <common/byte_swap.h>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <fmt/core.h>\n\n// Define this to ignore differences in generated QNaN sign bits (PowerPC\n//  0x7FF8...0 vs Intel 0xFFF8...0) when any of the NATIVE_IEEE_NAN,\n//  PPC_IGNORE_FPSCR_VXFOO, or PPC_NO_FPSCR_STATE optimizations are\n//  enabled.  If this is defined and a difference in generated QNaN sign\n//  bit is found, the JIT-generated value will be copied to the verify\n//  block, which may mask JIT bugs!\n#define FIXUP_OPTIMIZED_QNAN\n\nnamespace cpu::jit\n{\n\nstruct VerifyBuffer\n{\n   //! Copy of core state before JIT execution\n   Core coreCopy;\n\n   //! True if current instruction touches memory\n   bool isMemoryInstr;\n\n   //! Address accessed by instruction (if any)\n   uint32_t memoryAddress;\n\n   //! Number of bytes accessed by instruction\n   uint32_t memorySize;\n\n   //! Copy of memory before JIT execution\n   uint8_t preJitBuffer[128];\n\n   //! Copy of memory as written by JIT code\n   uint8_t postJitBuffer[128];\n};\n\nvoid\nBinrecBackend::resumeVerifyExecution()\n{\n   auto core = reinterpret_cast<BinrecCore *>(this_core::state());\n   if (!core->verifyBuffer) {\n      core->verifyBuffer = new VerifyBuffer();\n   }\n\n   do {\n      if (core->interrupt.load()) {\n         this_core::checkInterrupts();\n         core = reinterpret_cast<BinrecCore *>(this_core::state());\n      }\n\n      const ppcaddr_t address = core->nia;\n      auto codeBlock = core->backend->getCodeBlock(core, core->nia);\n\n      if (codeBlock) {\n         if (!mVerifyAddress || address == mVerifyAddress) {\n            verifyInit(core, core->verifyBuffer);\n         }\n\n         auto entry = reinterpret_cast<BinrecEntry>(codeBlock->code);\n         entry(core, getBaseVirtualAddress());\n      } else {\n         interpreter::step_one(core);\n      }\n\n      core = reinterpret_cast<BinrecCore *>(this_core::state());\n   } while (core->nia != CALLBACK_ADDR);\n}\n\n\n/**\n * Callback from libbinrec to for block pre execution verify callback.\n */\nvoid\nBinrecBackend::brVerifyPreHandler(BinrecCore *core, uint32_t address)\n{\n   auto instr = mem::read<uint32_t>(address);\n   core->backend->verifyPre(core, core->verifyBuffer, address, instr);\n}\n\n\n/**\n * Callback from libbinrec to for block post execution verify callback.\n */\nvoid\nBinrecBackend::brVerifyPostHandler(BinrecCore *core, uint32_t address)\n{\n   auto instr = mem::read<uint32_t>(address);\n   core->backend->verifyPost(core, core->verifyBuffer, address, instr);\n}\n\n\n// Ensure load/store verification is not broken by other threads\nstatic std::mutex\nmemoryLock;\n\n// Return whether the given instruction accesses memory.  We include HLE\n//  calls (the kc pseudoinstruction) in this set since HLE code could (and\n//  generally will) touch guest memory.\nstatic bool\nisMemoryInstruction(uint32_t instr)\n{\n   auto data = espresso::decodeInstruction(instr);\n   return data->id == espresso::InstructionID::lbz\n       || data->id == espresso::InstructionID::lbzu\n       || data->id == espresso::InstructionID::lbzx\n       || data->id == espresso::InstructionID::lbzux\n       || data->id == espresso::InstructionID::lhz\n       || data->id == espresso::InstructionID::lhzu\n       || data->id == espresso::InstructionID::lhzx\n       || data->id == espresso::InstructionID::lhzux\n       || data->id == espresso::InstructionID::lhbrx\n       || data->id == espresso::InstructionID::lha\n       || data->id == espresso::InstructionID::lhau\n       || data->id == espresso::InstructionID::lhax\n       || data->id == espresso::InstructionID::lhaux\n       || data->id == espresso::InstructionID::lwz\n       || data->id == espresso::InstructionID::lwzu\n       || data->id == espresso::InstructionID::lwzx\n       || data->id == espresso::InstructionID::lwzux\n       || data->id == espresso::InstructionID::lwbrx\n       || data->id == espresso::InstructionID::lwarx\n       || data->id == espresso::InstructionID::lfs\n       || data->id == espresso::InstructionID::lfsu\n       || data->id == espresso::InstructionID::lfsx\n       || data->id == espresso::InstructionID::lfsux\n       || data->id == espresso::InstructionID::lfd\n       || data->id == espresso::InstructionID::lfdu\n       || data->id == espresso::InstructionID::lfdx\n       || data->id == espresso::InstructionID::lfdux\n       || data->id == espresso::InstructionID::lmw\n       || data->id == espresso::InstructionID::lswi\n       || data->id == espresso::InstructionID::lswx\n       || data->id == espresso::InstructionID::psq_l\n       || data->id == espresso::InstructionID::psq_lu\n       || data->id == espresso::InstructionID::psq_lx\n       || data->id == espresso::InstructionID::psq_lux\n       || data->id == espresso::InstructionID::stb\n       || data->id == espresso::InstructionID::stbu\n       || data->id == espresso::InstructionID::stbx\n       || data->id == espresso::InstructionID::stbux\n       || data->id == espresso::InstructionID::sth\n       || data->id == espresso::InstructionID::sthu\n       || data->id == espresso::InstructionID::sthx\n       || data->id == espresso::InstructionID::sthux\n       || data->id == espresso::InstructionID::sthbrx\n       || data->id == espresso::InstructionID::stw\n       || data->id == espresso::InstructionID::stwu\n       || data->id == espresso::InstructionID::stwx\n       || data->id == espresso::InstructionID::stwux\n       || data->id == espresso::InstructionID::stwbrx\n       || data->id == espresso::InstructionID::stwcx\n       || data->id == espresso::InstructionID::stfs\n       || data->id == espresso::InstructionID::stfsu\n       || data->id == espresso::InstructionID::stfsx\n       || data->id == espresso::InstructionID::stfsux\n       || data->id == espresso::InstructionID::stfiwx\n       || data->id == espresso::InstructionID::stfd\n       || data->id == espresso::InstructionID::stfdu\n       || data->id == espresso::InstructionID::stfdx\n       || data->id == espresso::InstructionID::stfdux\n       || data->id == espresso::InstructionID::stmw\n       || data->id == espresso::InstructionID::stswi\n       || data->id == espresso::InstructionID::stswx\n       || data->id == espresso::InstructionID::dcbz\n       || data->id == espresso::InstructionID::dcbz_l\n       || data->id == espresso::InstructionID::psq_st\n       || data->id == espresso::InstructionID::psq_stu\n       || data->id == espresso::InstructionID::psq_stx\n       || data->id == espresso::InstructionID::psq_stux\n       || data->id == espresso::InstructionID::kc\n;\n}\n\nstatic void\nlookupMemoryTarget(Core *core,\n                   VerifyBuffer *verifyBuf,\n                   espresso::Instruction instr)\n{\n   auto coreCopy = &verifyBuf->coreCopy;\n   auto data = espresso::decodeInstruction(instr);\n\n   if (!data) {  // Instruction word was invalid\n      verifyBuf->memorySize = 0;\n      verifyBuf->memoryAddress = 0;\n      return;\n   }\n\n   // Calculate size and address separately to reduce code duplication\n\n   switch (data->id) {\n   case espresso::InstructionID::stb:\n   case espresso::InstructionID::stbu:\n   case espresso::InstructionID::stbx:\n   case espresso::InstructionID::stbux:\n      verifyBuf->memorySize = 1;\n      break;\n   case espresso::InstructionID::sth:\n   case espresso::InstructionID::sthu:\n   case espresso::InstructionID::sthx:\n   case espresso::InstructionID::sthux:\n   case espresso::InstructionID::sthbrx:\n      verifyBuf->memorySize = 2;\n      break;\n\n   case espresso::InstructionID::stw:\n   case espresso::InstructionID::stwu:\n   case espresso::InstructionID::stwx:\n   case espresso::InstructionID::stwux:\n   case espresso::InstructionID::stwbrx:\n   case espresso::InstructionID::stwcx:\n   case espresso::InstructionID::stfs:\n   case espresso::InstructionID::stfsu:\n   case espresso::InstructionID::stfsx:\n   case espresso::InstructionID::stfsux:\n   case espresso::InstructionID::stfiwx:\n      verifyBuf->memorySize = 4;\n      break;\n\n   case espresso::InstructionID::stfd:\n   case espresso::InstructionID::stfdu:\n   case espresso::InstructionID::stfdx:\n   case espresso::InstructionID::stfdux:\n      verifyBuf->memorySize = 8;\n      break;\n\n   case espresso::InstructionID::stmw:\n      verifyBuf->memorySize = 4 * (32 - instr.rS);\n      break;\n\n   case espresso::InstructionID::stswi:\n      verifyBuf->memorySize = instr.nb;\n      break;\n\n   case espresso::InstructionID::stswx:\n      verifyBuf->memorySize = coreCopy->xer.byteCount;\n      break;\n\n   case espresso::InstructionID::dcbz:\n   case espresso::InstructionID::dcbz_l:\n      verifyBuf->memorySize = 32;\n      break;\n\n   case espresso::InstructionID::psq_stx:\n   case espresso::InstructionID::psq_stux:\n   {\n      auto i = instr.qi;\n      auto w = instr.qw;\n      auto numStores = (w == 1) ? 1 : 2;\n      auto stt = static_cast<espresso::QuantizedDataType>(core->gqr[i].st_type);\n      if (stt == espresso::QuantizedDataType::Unsigned8 || stt == espresso::QuantizedDataType::Signed8) {\n         verifyBuf->memorySize = 1 * numStores;\n      } else if (stt == espresso::QuantizedDataType::Unsigned16 || stt == espresso::QuantizedDataType::Signed16) {\n         verifyBuf->memorySize = 2 * numStores;\n      } else {\n         verifyBuf->memorySize = 4 * numStores;\n      }\n      break;\n   }\n\n   case espresso::InstructionID::psq_st:\n   case espresso::InstructionID::psq_stu:\n   {\n      auto i = instr.i;\n      auto w = instr.w;\n      auto numStores = (w == 1) ? 1 : 2;\n      auto stt = static_cast<espresso::QuantizedDataType>(core->gqr[i].st_type);\n      if (stt == espresso::QuantizedDataType::Unsigned8 || stt == espresso::QuantizedDataType::Signed8) {\n         verifyBuf->memorySize = 1 * numStores;\n      } else if (stt == espresso::QuantizedDataType::Unsigned16 || stt == espresso::QuantizedDataType::Signed16) {\n         verifyBuf->memorySize = 2 * numStores;\n      } else {\n         verifyBuf->memorySize = 4 * numStores;\n      }\n      break;\n   }\n\n   default:\n      verifyBuf->memorySize = 0;\n      verifyBuf->memoryAddress = 0;\n      return;\n   }\n\n   switch (data->id) {\n   case espresso::InstructionID::stb:\n   case espresso::InstructionID::stbu:\n   case espresso::InstructionID::sth:\n   case espresso::InstructionID::sthu:\n   case espresso::InstructionID::stw:\n   case espresso::InstructionID::stwu:\n   case espresso::InstructionID::stmw:\n   case espresso::InstructionID::stfs:\n   case espresso::InstructionID::stfsu:\n   case espresso::InstructionID::stfd:\n   case espresso::InstructionID::stfdu:\n      if (instr.rA == 0) {\n         verifyBuf->memoryAddress = 0;\n      } else {\n         verifyBuf->memoryAddress = coreCopy->gpr[instr.rA];\n      }\n      verifyBuf->memoryAddress += sign_extend<16, int32_t>(instr.d);\n      break;\n\n   case espresso::InstructionID::psq_st:\n   case espresso::InstructionID::psq_stu:\n      if (instr.rA == 0) {\n         verifyBuf->memoryAddress = 0;\n      } else {\n         verifyBuf->memoryAddress = coreCopy->gpr[instr.rA];\n      }\n      verifyBuf->memoryAddress += sign_extend<12, int32_t>(instr.qd);\n      break;\n\n   case espresso::InstructionID::stbx:\n   case espresso::InstructionID::stbux:\n   case espresso::InstructionID::sthx:\n   case espresso::InstructionID::sthux:\n   case espresso::InstructionID::sthbrx:\n   case espresso::InstructionID::stwx:\n   case espresso::InstructionID::stwux:\n   case espresso::InstructionID::stwbrx:\n   case espresso::InstructionID::stwcx:\n   case espresso::InstructionID::stswx:\n   case espresso::InstructionID::stfsx:\n   case espresso::InstructionID::stfsux:\n   case espresso::InstructionID::stfiwx:\n   case espresso::InstructionID::stfdx:\n   case espresso::InstructionID::stfdux:\n   case espresso::InstructionID::psq_stx:\n   case espresso::InstructionID::psq_stux:\n      if (instr.rA == 0) {\n         verifyBuf->memoryAddress = 0;\n      } else {\n         verifyBuf->memoryAddress = coreCopy->gpr[instr.rA];\n      }\n      verifyBuf->memoryAddress += coreCopy->gpr[instr.rB];\n      break;\n\n   case espresso::InstructionID::stswi:\n      if (instr.rA == 0) {\n         verifyBuf->memoryAddress = 0;\n      } else {\n         verifyBuf->memoryAddress = coreCopy->gpr[instr.rA];\n      }\n      break;\n\n   case espresso::InstructionID::dcbz:\n   case espresso::InstructionID::dcbz_l:\n      if (instr.rA == 0) {\n         verifyBuf->memoryAddress = 0;\n      } else {\n         verifyBuf->memoryAddress = coreCopy->gpr[instr.rA];\n      }\n      verifyBuf->memoryAddress += coreCopy->gpr[instr.rB];\n      verifyBuf->memoryAddress = align_down(verifyBuf->memoryAddress, 32);\n      break;\n\n   default:\n      decaf_abort(\"Missing memoryAddress calculation\");\n   }\n}\n\n// Helper for verifyPost() so we don't have to pay the disassembly cost\n//  if the instruction worked as expected.\nstatic std::string\ndisassemble(uint32_t instr,\n            uint32_t address)\n{\n   espresso::Disassembly disassembly;\n   espresso::disassemble(static_cast<espresso::Instruction>(instr), disassembly, address);\n   return espresso::disassemblyToText(disassembly);\n}\n\nstatic bool\nshouldVerify(const espresso::InstructionInfo *data)\n{\n   return data != nullptr\n       && data->id != espresso::InstructionID::kc\n       && data->id != espresso::InstructionID::lwarx\n       && data->id != espresso::InstructionID::mftb\n       && data->id != espresso::InstructionID::stwcx;\n}\n\n\nvoid\nBinrecBackend::verifyInit(Core *core,\n                          VerifyBuffer *verifyBuf)\n{\n   // We copy the core state once when entering the JIT block, then call\n   //  the interpreter repeatedly on this copy.  This lets the interpreter\n   //  behave correctly even if the JIT callbacks don't fully update the\n   //  state due to optimizations (and also avoids the cost of a copy on\n   //  every instruction).\n   memcpy(static_cast<CoreRegs *>(&verifyBuf->coreCopy),\n          static_cast<CoreRegs *>(core),\n          sizeof(CoreRegs));\n   // Regenerate FEX and VX because libbinrec doesn't store them in the\n   //  state block.\n   updateFEX_VX(&verifyBuf->coreCopy);\n}\n\nvoid\nBinrecBackend::verifyPre(Core *core,\n                         VerifyBuffer *verifyBuf,\n                         uint32_t cia,\n                         uint32_t instr)\n{\n   if (!shouldVerify(espresso::decodeInstruction(instr))) {\n      return;\n   }\n\n   // If entering a load/store instruction, lock out other cores so we can\n   //  safely verify the instruction's behavior.\n   verifyBuf->isMemoryInstr = isMemoryInstruction(instr);\n   if (verifyBuf->isMemoryInstr) {\n      memoryLock.lock();\n   }\n\n   // Save the initial contents of any memory touched by the instruction.\n   lookupMemoryTarget(core, verifyBuf, static_cast<espresso::Instruction>(instr));\n   if (verifyBuf->memorySize > 0) {\n      decaf_check(verifyBuf->memorySize <= sizeof(verifyBuf->preJitBuffer));\n      memcpy(verifyBuf->preJitBuffer, mem::translate(verifyBuf->memoryAddress), verifyBuf->memorySize);\n   }\n}\n\nvoid\nBinrecBackend::verifyPost(Core *core,\n                          VerifyBuffer *verifyBuf,\n                          uint32_t cia,\n                          uint32_t instr)\n{\n   auto data = espresso::decodeInstruction(instr);\n   auto instrId = data ? data->id : static_cast<espresso::InstructionID>(-1);\n\n   if (!shouldVerify(data)) {\n      // We can't repeat the instruction without causing side effects, so\n      //  assume it worked and reinitialize the verify buffer from the\n      //  current core state, taking into account any optimizations that\n      //  may leave the active state block not up to date.\n\n      auto savedCR = verifyBuf->coreCopy.cr;\n      auto savedFPSCR = verifyBuf->coreCopy.fpscr;\n\n      verifyInit(core, verifyBuf);\n\n      if (mOptFlags.guest & binrec::Optimize::GuestPPC::USE_SPLIT_FIELDS) {\n         verifyBuf->coreCopy.cr.value = savedCR.value;\n         // stwcx. will properly update cr0.eq even if USE_SPLIT_FIELDS,\n         //  so copy that bit across.\n         if (instrId == espresso::InstructionID::stwcx) {\n            verifyBuf->coreCopy.cr.value &= ~(1<<29);\n            verifyBuf->coreCopy.cr.value |= core->cr.value & (1<<29);\n         }\n      }\n      if (mOptFlags.guest & binrec::Optimize::GuestPPC::NO_FPSCR_STATE) {\n         verifyBuf->coreCopy.fpscr.value &= 0xFF;\n         verifyBuf->coreCopy.fpscr.value |= savedFPSCR.value & 0xFFFFFF00;\n      } else if (mOptFlags.guest & binrec::Optimize::GuestPPC::USE_SPLIT_FIELDS) {\n         verifyBuf->coreCopy.fpscr.fr = savedFPSCR.fr;\n         verifyBuf->coreCopy.fpscr.fi = savedFPSCR.fi;\n         verifyBuf->coreCopy.fpscr.fprf = savedFPSCR.fprf;\n      } else if (mOptFlags.common & binrec::Optimize::FOLD_FP_CONSTANTS) {\n         verifyBuf->coreCopy.fpscr.fr = savedFPSCR.fr;\n         verifyBuf->coreCopy.fpscr.fi = savedFPSCR.fi;\n      }\n\n      return;\n   }\n\n   auto coreCopy = &verifyBuf->coreCopy;\n\n   if (verifyBuf->memorySize > 0) {\n      // Save the data written by JIT code...\n      memcpy(verifyBuf->postJitBuffer, mem::translate(verifyBuf->memoryAddress), verifyBuf->memorySize);\n      // ... and restore the original data for the interpreter.\n      memcpy(mem::translate(verifyBuf->memoryAddress), verifyBuf->preJitBuffer, verifyBuf->memorySize);\n   }\n\n   // Execute the instruction using the interpreter implementation on the\n   //  saved copy of the core state.\n   if (data) {\n      auto fptr = interpreter::getInstructionHandler(instrId);\n      decaf_assert(fptr, fmt::format(\"Unimplemented interpreter instruction {}\", data->name));\n      coreCopy->cia = cia;\n      coreCopy->nia = cia + 4;\n      fptr(coreCopy, instr);\n   }\n\n   uint8_t expectedMemory[128];\n   if (verifyBuf->memorySize > 0) {\n      // Save the expected data (as written by the interpreter).\n      memcpy(expectedMemory, mem::translate(verifyBuf->memoryAddress), verifyBuf->memorySize);\n   }\n\n   // If this was a load/store instruction, let other cores proceed again.\n   if (verifyBuf->isMemoryInstr) {\n      memoryLock.unlock();\n   }\n\n   // Check all registers and any touched memory for discrepancies.\n\n   decaf_assert(core->nia == coreCopy->nia,\n                fmt::format(\"Wrong NIA at 0x{:X}: {}\\n      Found: 0x{:08X}\\n   Expected: 0x{:08X}\",\n                            cia, disassemble(instr, cia),\n                            core->nia, coreCopy->nia));\n\n   for (auto i = 0; i < 32; ++i) {\n      decaf_assert(core->gpr[i] == coreCopy->gpr[i],\n                   fmt::format(\"Wrong value in GPR {} at 0x{:X}: {}\\n      Found: 0x{:08X}\\n   Expected: 0x{:08X}\",\n                               i, cia, disassemble(instr, cia),\n                               core->gpr[i], coreCopy->gpr[i]));\n   }\n\n   for (auto i = 0; i < 32; ++i) {\n#ifdef FIXUP_OPTIMIZED_QNAN\n      if ((mOptFlags.common & binrec::Optimize::NATIVE_IEEE_NAN)\n       || (mOptFlags.guest & (binrec::Optimize::GuestPPC::IGNORE_FPSCR_VXFOO\n                        | binrec::Optimize::GuestPPC::NO_FPSCR_STATE))) {\n         if (core->fpr[i].idw == UINT64_C(0xFFF8000000000000)\n          && coreCopy->fpr[i].idw == UINT64_C(0x7FF8000000000000)) {\n            coreCopy->fpr[i].idw = core->fpr[i].idw;\n         }\n      }\n#endif\n      decaf_assert(core->fpr[i].idw == coreCopy->fpr[i].idw,\n                   fmt::format(\"Wrong value in FPR {} at 0x{:X}: {}\\n      Found: 0x{:08X}_{:08X} ({:g})\\n   Expected: 0x{:08X}_{:08X} ({:g})\",\n                               i, cia, disassemble(instr, cia),\n                               static_cast<uint32_t>(core->fpr[i].idw >> 32),\n                               static_cast<uint32_t>(core->fpr[i].idw),\n                               core->fpr[i].value,\n                               static_cast<uint32_t>(coreCopy->fpr[i].idw >> 32),\n                               static_cast<uint32_t>(coreCopy->fpr[i].idw),\n                               coreCopy->fpr[i].value));\n   }\n\n   for (auto i = 0; i < 32; ++i) {\n#ifdef FIXUP_OPTIMIZED_QNAN\n      if ((mOptFlags.common & binrec::Optimize::NATIVE_IEEE_NAN)\n       || (mOptFlags.guest & (binrec::Optimize::GuestPPC::IGNORE_FPSCR_VXFOO\n                        | binrec::Optimize::GuestPPC::NO_FPSCR_STATE))) {\n         if (core->fpr[i].idw_paired1 == UINT64_C(0xFFF8000000000000)\n          && coreCopy->fpr[i].idw_paired1 == UINT64_C(0x7FF8000000000000)) {\n            coreCopy->fpr[i].idw_paired1 = core->fpr[i].idw_paired1;\n         }\n      }\n#endif\n      decaf_assert(core->fpr[i].idw_paired1 == coreCopy->fpr[i].idw_paired1,\n                   fmt::format(\"Wrong value in PS1 {} at 0x{:X}: {}\\n      Found: 0x{:08X}_{:08X} ({:g})\\n   Expected: 0x{:08X}_{:08X} ({:g})\",\n                               i, cia, disassemble(instr, cia),\n                               static_cast<uint32_t>(core->fpr[i].idw_paired1 >> 32),\n                               static_cast<uint32_t>(core->fpr[i].idw_paired1),\n                               core->fpr[i].paired1,\n                               static_cast<uint32_t>(coreCopy->fpr[i].idw_paired1 >> 32),\n                               static_cast<uint32_t>(coreCopy->fpr[i].idw_paired1),\n                               coreCopy->fpr[i].paired1));\n   }\n\n   for (auto i = 0; i < 8; ++i) {\n      decaf_assert(core->gqr[i].value == coreCopy->gqr[i].value,\n                   fmt::format(\"Wrong value in GQR {} at 0x{:X}: {}\\n      Found: 0x{:08X}\\n   Expected: 0x{:08X}\",\n                               i, cia, disassemble(instr, cia),\n                               core->gqr[i].value, coreCopy->gqr[i].value));\n   }\n\n   decaf_assert(core->lr == coreCopy->lr,\n                fmt::format(\"Wrong value in LR at 0x{:X}: {}\\n      Found: 0x{:08X}\\n   Expected: 0x{:08X}\",\n                            cia, disassemble(instr, cia),\n                            core->lr, coreCopy->lr));\n\n   decaf_assert(core->ctr == coreCopy->ctr,\n                fmt::format(\"Wrong value in CTR at 0x{:X}: {}\\n      Found: 0x{:08X}\\n   Expected: 0x{:08X}\",\n                            cia, disassemble(instr, cia),\n                            core->ctr, coreCopy->ctr));\n\n   // Skip CR check if split fields are enabled, because the value in CR\n   //  may not be up to date.\n   if (!(mOptFlags.guest & binrec::Optimize::GuestPPC::USE_SPLIT_FIELDS)) {\n      decaf_assert(core->cr.value == coreCopy->cr.value,\n                   fmt::format(\"Wrong value in CR at 0x{:X}: {}\\n      Found: 0x{:08X}\\n   Expected: 0x{:08X}\",\n                               cia, disassemble(instr, cia),\n                               core->cr.value, coreCopy->cr.value));\n   }\n\n   decaf_assert(core->xer.value == coreCopy->xer.value,\n                fmt::format(\"Wrong value in XER at 0x{:X}: {}\\n      Found: 0x{:08X}\\n   Expected: 0x{:08X}\",\n                            cia, disassemble(instr, cia),\n                            core->xer.value, coreCopy->xer.value));\n\n   // Skip FPRF check for fctiw[z] and mffs, which leave it undefined (and\n   //  we don't attempt to mimic whatever the hardware actually does for\n   //  them).  For these instructions, we copy the JIT state into the\n   //  local copy so as not to trigger spurious failures on subsequent\n   //  instructions.\n   if (instrId == espresso::InstructionID::fctiw\n    || instrId == espresso::InstructionID::fctiwz\n    || instrId == espresso::InstructionID::mffs) {\n      coreCopy->fpscr.value &= ~0x0001F000;\n      coreCopy->fpscr.value |= core->fpscr.value & 0x0001F000;\n   }\n   // Regenerate FEX and VX because libbinrec doesn't store them in the\n   //  state block.\n   updateFEX_VX(core);\n   // Ignore parts of FPSCR which may not be up to date based on enabled\n   //  optimizations.\n   uint32_t fpscrMask;\n   if (mOptFlags.guest & binrec::Optimize::GuestPPC::NO_FPSCR_STATE) {\n      fpscrMask = 0xFF;\n   } else if (mOptFlags.guest & binrec::Optimize::GuestPPC::USE_SPLIT_FIELDS) {\n      fpscrMask = ~0x0007F000u;\n   } else if (mOptFlags.common & binrec::Optimize::FOLD_FP_CONSTANTS) {\n      fpscrMask = ~0x00060000u;\n   } else {\n      fpscrMask = ~0u;\n   }\n   decaf_assert((core->fpscr.value & fpscrMask) == (coreCopy->fpscr.value & fpscrMask),\n                fmt::format(\"Wrong value in FPSCR at 0x{:X}: {}\\n      Found: 0x{:08X}\\n   Expected: 0x{:08X}\",\n                            cia, disassemble(instr, cia),\n                            core->fpscr.value, coreCopy->fpscr.value));\n\n   for (auto i = 0u; i < verifyBuf->memorySize; ++i) {\n      auto found = verifyBuf->postJitBuffer[i];\n      auto expected = expectedMemory[i];\n      if (found != expected) {\n         // Try and make the output reasonably useful\n         std::string addressStr = fmt::format(\"0x{:X}\", verifyBuf->memoryAddress);\n         std::string foundStr, expectedStr;\n         if (instrId == espresso::InstructionID::stswi || instrId == espresso::InstructionID::stswx) {\n            addressStr += fmt::format(\"+0x{:X}\", i);\n            foundStr = fmt::format(\"0x{:02X}\", found);\n            expectedStr = fmt::format(\"0x{:02X}\", expected);\n         } else if (instrId == espresso::InstructionID::stmw) {\n            auto offset = align_down(i, 4);\n            addressStr += fmt::format(\"+0x{:X}\", offset);\n            foundStr = fmt::format(\"0x{:08X}\", byte_swap(*reinterpret_cast<uint32_t *>(&verifyBuf->postJitBuffer[offset])));\n            expectedStr = fmt::format(\"0x{:08X}\", byte_swap(*reinterpret_cast<uint32_t *>(&expectedMemory[offset])));\n         } else if (verifyBuf->memorySize == 8) {\n            foundStr = fmt::format(\"0x{:08X}_{:08X}\",\n                                   byte_swap(*reinterpret_cast<uint32_t *>(verifyBuf->postJitBuffer)),\n                                   byte_swap(*reinterpret_cast<uint32_t *>(&verifyBuf->postJitBuffer[4])));\n            expectedStr = fmt::format(\"0x{:08X}_{:08X}\",\n                                      byte_swap(*reinterpret_cast<uint32_t *>(expectedMemory)),\n                                      byte_swap(*reinterpret_cast<uint32_t *>(&expectedMemory[4])));\n         } else if (verifyBuf->memorySize == 4) {\n            foundStr = fmt::format(\"0x{:08X}\", byte_swap(*reinterpret_cast<uint32_t *>(verifyBuf->postJitBuffer)));\n            expectedStr = fmt::format(\"0x{:08X}\", byte_swap(*reinterpret_cast<uint32_t *>(expectedMemory)));\n         } else if (verifyBuf->memorySize == 2) {\n            foundStr = fmt::format(\"0x{:04X}\", byte_swap(*reinterpret_cast<uint16_t *>(verifyBuf->postJitBuffer)));\n            expectedStr = fmt::format(\"0x{:04X}\", byte_swap(*reinterpret_cast<uint16_t *>(expectedMemory)));\n         } else {\n            foundStr = fmt::format(\"0x{:02X}\", found);\n            expectedStr = fmt::format(\"0x{:02X}\", expected);\n         }\n         decaf_abort(fmt::format(\"Wrong data written to {} at 0x{:X}: {}\\n      Found: {}\\n   Expected: {}\",\n                                 addressStr, cia, disassemble(instr, cia),\n                                 foundStr, expectedStr));\n      }\n   }\n}\n\n} // namespace cpu::jit\n"
  },
  {
    "path": "src/libcpu/src/jit/jit.cpp",
    "content": "#include \"jit.h\"\n#include \"jit_backend.h\"\n\nnamespace cpu\n{\n\nnamespace jit\n{\n\nstatic JitBackend *\nsBackend = nullptr;\n\n/**\n * Set the JIT backend to use.\n */\nvoid\nsetBackend(JitBackend *backend)\n{\n   sBackend = backend;\n}\n\n\n/**\n * Return the current active JIT backend.\n */\nJitBackend *\ngetBackend()\n{\n   return sBackend;\n}\n\n\n/**\n * Initialize JIT-related fields in a Core instance.\n */\nCore *\ninitialiseCore(uint32_t id)\n{\n   if (sBackend) {\n      return sBackend->initialiseCore(id);\n   } else {\n      return nullptr;\n   }\n}\n\n\n/**\n * Clear the JIT cache for the given address range.\n *\n * This function must not be called while any JIT code is being executed.\n * There is no guarentee that only the selected address range will be cleared.\n */\nvoid\nclearCache(uint32_t address, uint32_t size)\n{\n   if (sBackend) {\n      sBackend->clearCache(address, size);\n   }\n}\n\n\n/**\n * Mark the given range of addresses as read-only for JIT optimization.\n */\nvoid\naddReadOnlyRange(uint32_t address, uint32_t size)\n{\n   if (sBackend) {\n      sBackend->addReadOnlyRange(address, size);\n   }\n}\n\n\n/**\n * Begin executing guest code on the current core.\n */\nvoid\nresume()\n{\n   sBackend->resumeExecution();\n}\n\n} // namespace jit\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/jit/jit.h",
    "content": "#pragma once\n#include \"jit_backend.h\"\n\nnamespace cpu\n{\n\nnamespace jit\n{\n\nJitBackend *\ngetBackend();\n\nvoid\nsetBackend(JitBackend *backend);\n\nCore *\ninitialiseCore(uint32_t id);\n\nvoid\nclearCache(uint32_t address, uint32_t size);\n\nvoid\naddReadOnlyRange(uint32_t address, uint32_t size);\n\nvoid\nresume();\n\n} // namespace jit\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/jit/jit_backend.h",
    "content": "#pragma once\n#include \"jit_stats.h\"\n#include \"state.h\"\n#include <cstdint>\n\nnamespace cpu\n{\n\nnamespace jit\n{\n\nclass JitBackend\n{\npublic:\n   virtual ~JitBackend() = default;\n\n   //! Initialise core specific state.\n   virtual Core *\n   initialiseCore(uint32_t id) = 0;\n\n   //! Clear any cached code for specified memory range.\n   virtual void\n   clearCache(uint32_t address, uint32_t size) = 0;\n\n   //! Resume execution on current core.\n   virtual void\n   resumeExecution() = 0;\n\n   //! Mark a region of memory as read only.\n   virtual void\n   addReadOnlyRange(uint32_t address, uint32_t size) = 0;\n\n   //! Sample JIT stats.\n   virtual bool\n   sampleStats(JitStats &stats) = 0;\n\n   //! Reset JIT profiling stats.\n   virtual void\n   resetProfileStats() = 0;\n\n   //! Set which cores to profile\n   virtual void\n   setProfilingMask(unsigned mask) = 0;\n\n   //! Get which cores are being profiled\n   virtual unsigned\n   getProfilingMask() = 0;\n\nprivate:\n};\n\n} // namespace jit\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/jit/jit_codecache.cpp",
    "content": "#include \"jit_codecache.h\"\n#include \"jit_stats.h\"\n\n#include <atomic>\n#include <cstdint>\n#include <cstring>\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <common/platform.h>\n#include <common/platform_memory.h>\n#include <gsl/gsl-lite.hpp>\n#include <mutex>\n#include <thread>\n\nnamespace cpu\n{\n\nnamespace jit\n{\n\nCodeCache::~CodeCache()\n{\n   free();\n}\n\n\n/**\n * Initialise the code cache.\n */\nbool\nCodeCache::initialise(size_t codeSize,\n                      size_t dataSize)\n{\n   mReserveAddress = 0;\n   mReserveSize = codeSize + dataSize;\n\n   for (auto n = 2; n < 32; ++n) {\n      auto base = 0x100000000 * n;\n\n      if (platform::reserveMemory(base, mReserveSize)) {\n         mReserveAddress = base;\n         break;\n      }\n   }\n\n   decaf_assert(mReserveAddress, \"Failed to map memory for JIT\");\n\n   mCodeAllocator.flags = platform::ProtectFlags::ReadWriteExecute;\n   mCodeAllocator.baseAddress = mReserveAddress;\n   mCodeAllocator.reserved = codeSize;\n   mCodeAllocator.growthSize = 4 * 1024 * 1024;\n   mCodeAllocator.committed = 0;\n   mCodeAllocator.allocated = 0;\n\n   mDataAllocator.flags = platform::ProtectFlags::ReadWrite;\n   mDataAllocator.baseAddress = mReserveAddress + codeSize;\n   mDataAllocator.reserved = dataSize;\n   mDataAllocator.growthSize = 1 * 1024 * 1024;\n   mDataAllocator.committed = 0;\n   mDataAllocator.allocated = 0;\n\n   mFastIndex = new std::atomic<std::atomic<CodeBlockIndex> *>[Level1Size];\n   std::memset(mFastIndex, 0, sizeof(mFastIndex[0]) * Level1Size);\n   return true;\n}\n\n\n/**\n * Clear the code cache.\n *\n * This will also unregister any unwind info with Windows.\n */\nvoid\nCodeCache::clear()\n{\n#ifdef PLATFORM_WINDOWS\n   // Delete any registered function tables\n   for (auto offset = 0u; offset < mDataAllocator.allocated; offset += sizeof(CodeBlock)) {\n      auto blockAddress = mDataAllocator.baseAddress + offset;\n      auto block = reinterpret_cast<CodeBlock *>(blockAddress);\n      RtlDeleteFunctionTable(&block->unwindInfo.rtlFuncTable);\n   }\n#endif\n\n   // Reset the allocators, don't bother uncommitting their memory.\n   mDataAllocator.allocated = 0;\n   mCodeAllocator.allocated = 0;\n\n   // Clear fast index, don't bother unallocating memory.\n   if (mFastIndex) {\n      for (auto i = 0u; i < Level1Size; ++i) {\n         auto level2 = mFastIndex[i].load();\n\n         for (auto j = 0u; level2 && j < Level2Size; ++j) {\n            level2[j].store(CodeBlockIndexUncompiled);\n         }\n\n         mFastIndex[i].store(nullptr);\n      }\n\n      std::memset(mFastIndex, 0, sizeof(mFastIndex[0]) * Level1Size);\n   }\n}\n\n\n/**\n * Invalidate a region of code.\n *\n * Because it's super complicated to do properly let's just be a leaky fuck,\n * for now our \"invalidation\" is really just forgetting that we compiled a block.\n */\nvoid\nCodeCache::invalidate(uint32_t base,\n                      uint32_t size)\n{\n   // Find any block containing this address and invalidate them!\n   auto blocks = getCompiledCodeBlocks();\n\n   for (auto &block : blocks) {\n      auto start = block.address;\n      auto end = start + 4096; // FIXME: Just assume 4096 limit for now..\n\n      if (base + size < start) {\n         continue;\n      }\n\n      if (base >= end) {\n         continue;\n      }\n\n      getIndexPointer(block.address)->store(CodeBlockIndexUncompiled);\n   }\n}\n\n\n/**\n * Free the memory we are using for our JIT.\n */\nvoid\nCodeCache::free()\n{\n   clear();\n\n   if (mFastIndex) {\n      for (auto i = 0u; i < Level1Size; ++i) {\n         auto level2 = mFastIndex[i].load();\n\n         if (level2) {\n            delete[] level2;\n         }\n      }\n\n      delete[] mFastIndex;\n      mFastIndex = nullptr;\n   }\n\n   if (mReserveAddress) {\n      platform::freeMemory(mReserveAddress, mReserveSize);\n      mReserveAddress = 0;\n      mReserveSize = 0;\n   }\n}\n\n\n/**\n * Returns the amount of data allocated in the code cache.\n */\nsize_t\nCodeCache::getCodeCacheSize()\n{\n   return mCodeAllocator.allocated;\n}\n\n\n/**\n * Returns the amount of data allocated in the data cache.\n */\nsize_t\nCodeCache::getDataCacheSize()\n{\n   return mDataAllocator.allocated;\n}\n\n\n/**\n * Returns a list of all compiled code blocks.\n */\ngsl::span<CodeBlock>\nCodeCache::getCompiledCodeBlocks()\n{\n   auto count = mDataAllocator.allocated.load() / sizeof(CodeBlock);\n   auto first = reinterpret_cast<CodeBlock *>(mDataAllocator.baseAddress);\n   return gsl::make_span(first, count);\n}\n\n\n/**\n * Find a compiled code block from it's address.\n */\nCodeBlock *\nCodeCache::getBlockByAddress(uint32_t address)\n{\n   auto index = getIndexPointer(address)->load();\n\n   if (index < 0) {\n      return nullptr;\n   } else {\n      return getBlockByIndex(index);\n   }\n}\n\n\n/**\n * Find a compiled code block's CodeBlockIndex.\n */\nCodeBlockIndex\nCodeCache::getIndex(uint32_t address)\n{\n   return getIndexPointer(address)->load();\n}\n\n\n/**\n * Get a compiled code block's CodeBlockIndex.\n */\nCodeBlockIndex\nCodeCache::getIndex(CodeBlock *block)\n{\n   auto blockAddress = reinterpret_cast<uintptr_t>(block);\n   auto index = (blockAddress - mDataAllocator.baseAddress) / sizeof(CodeBlock);\n   return static_cast<CodeBlockIndex>(index);\n}\n\n\n/**\n * Get a pointer to the CodeBlockIndex for the address.\n *\n * This is used for registering the CodeBlock whilst compiling.\n */\nstd::atomic<CodeBlockIndex> *\nCodeCache::getIndexPointer(uint32_t address)\n{\n   decaf_check((address & 0x3) == 0);\n\n   auto index1 = (address & 0xFFFF0000) >> 16;\n   auto index2 = (address & 0x0000FFFC) >> 2;\n   auto level2 = mFastIndex[index1].load();\n\n   if (UNLIKELY(!level2)) {\n      auto newLevel2 = new std::atomic<CodeBlockIndex>[Level2Size];\n      std::memset(newLevel2, CodeBlockIndexUncompiled, sizeof(newLevel2[0]) * Level2Size);\n\n      if (mFastIndex[index1].compare_exchange_strong(level2, newLevel2)) {\n         level2 = newLevel2;\n      } else {\n         // compare_exchange updates level1 if we were preempted\n         delete[] newLevel2;\n      }\n   }\n\n   return &level2[index2];\n}\n\n\n/**\n * Set a CodeBlockIndex for an address, useful for mirroring duplicate functions.\n */\nvoid\nCodeCache::setBlockIndex(uint32_t address,\n                         CodeBlockIndex index)\n{\n   decaf_check(index >= 0);\n   getIndexPointer(address)->store(index);\n}\n\n\n/**\n * Register a block of code in the CodeCache.\n *\n * This will allocate memory for the code and data, and update the code block index.\n */\nCodeBlock *\nCodeCache::registerCodeBlock(uint32_t address,\n                             void *code,\n                             size_t size,\n                             void *unwindInfo,\n                             size_t unwindSize)\n{\n   auto dataAddress = allocate(mDataAllocator, sizeof(CodeBlock), 1);\n   auto codeAddress = allocate(mCodeAllocator, size, 16);\n\n   // Setup me block\n   auto block = reinterpret_cast<CodeBlock *>(dataAddress);\n   block->address = address;\n   block->code = reinterpret_cast<void *>(codeAddress);\n   block->codeSize = static_cast<uint32_t>(size);\n   std::memcpy(block->code, code, size);\n\n   // Initialise profiling data\n   block->profileData.count = 0;\n   block->profileData.time = 0;\n\n#ifdef PLATFORM_WINDOWS\n   // Register unwind info\n   decaf_check(unwindSize <= CodeBlockUnwindInfo::MaxUnwindInfoSize);\n   block->unwindInfo.size = static_cast<uint32_t>(unwindSize);\n   std::memcpy(block->unwindInfo.data.data(), unwindInfo, unwindSize);\n\n   auto unwindAddress = reinterpret_cast<uintptr_t>(block->unwindInfo.data.data());\n   block->unwindInfo.rtlFuncTable.BeginAddress = static_cast<DWORD>(codeAddress - mReserveAddress);\n   block->unwindInfo.rtlFuncTable.EndAddress = static_cast<DWORD>(block->unwindInfo.rtlFuncTable.BeginAddress + size);\n   block->unwindInfo.rtlFuncTable.UnwindData = static_cast<DWORD>(unwindAddress - mReserveAddress);\n   RtlAddFunctionTable(&block->unwindInfo.rtlFuncTable, 1, mReserveAddress);\n#endif\n\n   auto index = getIndex(block);\n   auto indexPtr = getIndexPointer(address);\n   indexPtr->store(index);\n   return block;\n}\n\n\n/**\n * Allocate memory from the specified CodeCache::FrameAllocator.\n */\nuintptr_t\nCodeCache::allocate(FrameAllocator &allocator,\n                    size_t size,\n                    size_t alignment)\n{\n   auto alignedSize = align_up(size + (alignment - 1), alignment);\n   auto offset = allocator.allocated.fetch_add(alignedSize);\n   auto alignedOffset = align_up(offset, alignment);\n\n   // Check if we have gone past end of committed memory.\n   if (alignedOffset + alignedSize > allocator.committed.load()) {\n      std::lock_guard<std::mutex> lock { allocator.mutex };\n      auto committed = allocator.committed.load();\n\n      while (alignedOffset + alignedSize > committed) {\n         if (!platform::commitMemory(allocator.baseAddress + committed, allocator.growthSize, allocator.flags)) {\n            decaf_abort(\"Failed to commit memory for JIT\");\n         }\n\n         committed += allocator.growthSize;\n      }\n\n      allocator.committed.store(committed);\n   }\n\n   return allocator.baseAddress + alignedOffset;\n}\n\n} // namespace jit\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/jit/jit_codecache.h",
    "content": "#pragma once\n#include \"jit_stats.h\"\n\n#include <atomic>\n#include <cstdint>\n#include <common/platform_compiler.h>\n#include <common/platform_memory.h>\n#include <gsl/gsl-lite.hpp>\n#include <mutex>\n\nnamespace cpu\n{\n\nnamespace jit\n{\n\n/**\n * Code Cache Responsibilities:\n *\n * 1. Map guest address to host address.\n * 2. Allocate executable host memory.\n * 3. Allocate and populate unwind information.\n */\nclass CodeCache\n{\n   struct FrameAllocator\n   {\n      // Memory flags\n      platform::ProtectFlags flags;\n\n      //! Base address\n      uintptr_t baseAddress;\n\n      //! Amount of data allocated so far.\n      std::atomic<size_t> allocated;\n\n      //! Amount of memory committed\n      std::atomic<size_t> committed;\n\n      //! Amount of memory reserved\n      size_t reserved;\n\n      size_t growthSize;\n      std::mutex mutex;\n   };\n\n   // Fast Index level sizes\n   static constexpr size_t Level1Size = 0x10000;\n   static constexpr size_t Level2Size = 0x4000;\n\npublic:\n   ~CodeCache();\n\n   bool\n   initialise(size_t codeSize,\n              size_t dataSize);\n\n   void\n   clear();\n\n   void\n   invalidate(uint32_t address,\n              uint32_t size);\n\n   void\n   free();\n\n   size_t\n   getCodeCacheSize();\n\n   size_t\n   getDataCacheSize();\n\n   gsl::span<CodeBlock>\n   getCompiledCodeBlocks();\n\n   CodeBlock *\n   getBlockByAddress(uint32_t address);\n\n   /**\n    * Find a compiled code block from its CodeBlockIndex.\n    */\n   CodeBlock *\n   getBlockByIndex(CodeBlockIndex index)\n   {\n      auto blockAddress = mDataAllocator.baseAddress + index * sizeof(CodeBlock);\n      return reinterpret_cast<CodeBlock *>(blockAddress);\n   }\n\n   CodeBlockIndex\n   getIndex(uint32_t address);\n\n   CodeBlockIndex\n   getIndex(CodeBlock *block);\n\n   std::atomic<CodeBlockIndex> *\n   getIndexPointer(uint32_t address);\n\n   /**\n    * Get a const pointer to the CodeBlockIndex for the address, or null if\n    * no block is registered for the address.\n    *\n    * This is used for quickly looking up a code block for execution.\n    */\n   const std::atomic<CodeBlockIndex> *\n   getConstIndexPointer(uint32_t address)\n   {\n      auto index1 = (address & 0xFFFF0000) >> 16;\n      auto index2 = (address & 0x0000FFFC) >> 2;\n\n      auto level2 = mFastIndex[index1].load();\n      if (UNLIKELY(!level2)) {\n         return nullptr;\n      }\n\n      return &level2[index2];\n   }\n\n   void\n   setBlockIndex(uint32_t address,\n                 CodeBlockIndex index);\n\n   CodeBlock *\n   registerCodeBlock(uint32_t address,\n                     void *code,\n                     size_t size,\n                     void *unwindInfo,\n                     size_t unwindSize);\n\n\nprivate:\n   uintptr_t\n   allocate(FrameAllocator &allocator,\n            size_t size,\n            size_t alignment);\n\nprivate:\n   size_t mReserveAddress = 0;\n   size_t mReserveSize = 0;\n   FrameAllocator mCodeAllocator;\n   FrameAllocator mDataAllocator;\n   std::atomic<std::atomic<CodeBlockIndex> *> *mFastIndex = nullptr;\n};\n\n} // namespace jit\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/jit/jit_stats.cpp",
    "content": "#include \"jit.h\"\n#include \"jit_stats.h\"\n\nnamespace cpu\n{\n\nnamespace jit\n{\n\nbool\nsampleStats(JitStats &stats)\n{\n   auto backend = getBackend();\n\n   if (backend) {\n      return backend->sampleStats(stats);\n   } else {\n      return false;\n   }\n}\n\nvoid\nresetProfileStats()\n{\n   auto backend = getBackend();\n\n   if (backend) {\n      backend->resetProfileStats();\n   }\n}\n\nvoid\nsetProfilingMask(unsigned mask)\n{\n   auto backend = getBackend();\n\n   if (backend) {\n      backend->setProfilingMask(mask);\n   }\n}\n\nunsigned\ngetProfilingMask()\n{\n   auto backend = getBackend();\n\n   if (backend) {\n      return backend->getProfilingMask();\n   } else {\n      return 0;\n   }\n}\n\n} // namespace jit\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/memorymap.cpp",
    "content": "#include \"memorymap.h\"\n\n#include <algorithm>\n#include <common/align.h>\n#include <common/log.h>\n#include <common/platform.h>\n#include <common/platform_memory.h>\n\nnamespace cpu\n{\n\nnamespace internal\n{\n\nuintptr_t BaseVirtualAddress = 0;\nuintptr_t BasePhysicalAddress = 0;\n\n} // namespace internal\n\nstatic constexpr PhysicalAddress MEM1BaseAddress = PhysicalAddress { 0 };\nstatic constexpr PhysicalAddress MEM1EndAddress  = PhysicalAddress { 0x01FFFFFF };\nstatic constexpr size_t MEM1Size = (MEM1EndAddress - MEM1BaseAddress) + 1;\n\nstatic constexpr PhysicalAddress MEM0BaseAddress = PhysicalAddress { 0x08000000 };\nstatic constexpr PhysicalAddress MEM0EndAddress = PhysicalAddress { 0x082DFFFF };\nstatic constexpr size_t MEM0Size = (MEM0EndAddress - MEM0BaseAddress) + 1;\n\nstatic constexpr PhysicalAddress MEM2BaseAddress = PhysicalAddress { 0x10000000 };\nstatic constexpr PhysicalAddress MEM2EndAddress  = PhysicalAddress { 0x8FFFFFFF };\nstatic constexpr size_t MEM2Size = (MEM2EndAddress - MEM2BaseAddress) + 1;\n\nstatic constexpr PhysicalAddress UNKRAMBaseAddress = PhysicalAddress { 0xFFC00000 };\nstatic constexpr PhysicalAddress UNKRAMEndAddress = PhysicalAddress { 0xFFE7FFFF };\nstatic constexpr size_t UNKRAMSize = (UNKRAMEndAddress - UNKRAMBaseAddress) + 1;\n\nstatic constexpr PhysicalAddress SRAM1BaseAddress = PhysicalAddress { 0xFFF00000 };\nstatic constexpr PhysicalAddress SRAM1EndAddress = PhysicalAddress { 0xFFF07FFF };\nstatic constexpr size_t SRAM1Size = (SRAM1EndAddress - SRAM1BaseAddress) + 1;\n\nstatic constexpr PhysicalAddress SRAM0BaseAddress = PhysicalAddress { 0xFFFF0000 };\nstatic constexpr PhysicalAddress SRAM0EndAddress = PhysicalAddress { 0xFFFFFFFF };\nstatic constexpr size_t SRAM0Size = (SRAM0EndAddress - SRAM0BaseAddress) + 1;\n\n// HACK: Doesn't exist at this physical address on hardware. _DECAF ONLY_\n// HACK: Set to page size (128kb) even though it's actually 16kb per core,\n// due to memory map restrictions.\nstatic constexpr PhysicalAddress LCBaseAddress = PhysicalAddress { 0x02000000 };\nstatic constexpr PhysicalAddress LCEndAddress = PhysicalAddress { 0x0201FFFF };\nstatic constexpr size_t LCSize = (LCEndAddress - LCBaseAddress) + 1;\n\n// Tiling Aperture dedicated memory.\n// HACK: Doesn't exist at this physical address on hardware. _DECAF ONLY_\n// On hardware the memory controller maps apertures using fancy logic,\n// we can't do that so we need a dedicated memory region for it.\nstatic constexpr size_t TASize = 256 * 1024 * 1024;\nstatic constexpr PhysicalAddress TABaseAddress = PhysicalAddress { 0xD0000000 };\nstatic constexpr PhysicalAddress TAEndAddress = TABaseAddress + TASize - 1;\n\n\nMemoryMap::~MemoryMap()\n{\n   free();\n}\n\n\nbool\nMemoryMap::reserve()\n{\n   decaf_check(platform::getSystemPageSize() <= cpu::PageSize);\n\n   // Reserve physical address space\n   mPhysicalBase = reserveBaseAddress();\n\n   if (!mPhysicalBase) {\n      gLog->error(\"Unable to reserve base address for physical memory\");\n      free();\n      return false;\n   }\n\n   // Reserve virtual address space\n   mVirtualBase = reserveBaseAddress();\n\n   if (!mVirtualBase) {\n      gLog->error(\"Unable to reserve base address for virtual memory\");\n      free();\n      return false;\n   }\n\n   internal::BaseVirtualAddress = mVirtualBase;\n   internal::BasePhysicalAddress = mPhysicalBase;\n   mReservedMemory.push_back({ VirtualAddress { 0 }, VirtualAddress { 0xFFFFFFFF } });\n\n   // Commit MEM0\n   mMem0 = platform::createMemoryMappedFile(MEM0Size);\n   if (mMem0 == platform::InvalidMapFileHandle) {\n      gLog->error(\"Unable to create MEM1 mapping\");\n      free();\n      return false;\n   }\n\n   // Commit MEM1\n   mMem1 = platform::createMemoryMappedFile(MEM1Size);\n   if (mMem1 == platform::InvalidMapFileHandle) {\n      gLog->error(\"Unable to create MEM1 mapping\");\n      free();\n      return false;\n   }\n\n   // Commit MEM2\n   mMem2 = platform::createMemoryMappedFile(MEM2Size);\n   if (mMem2 == platform::InvalidMapFileHandle) {\n      gLog->error(\"Unable to create MEM2 mapping\");\n      free();\n      return false;\n   }\n\n   // Commit unknown kernel RAM\n   mUnkRam = platform::createMemoryMappedFile(UNKRAMSize);\n   if (mUnkRam == platform::InvalidMapFileHandle) {\n      gLog->error(\"Unable to create UNKRAM mapping\");\n      free();\n      return false;\n   }\n\n   // Commit SRAM0\n   mSram0 = platform::createMemoryMappedFile(SRAM0Size);\n   if (mSram0 == platform::InvalidMapFileHandle) {\n      gLog->error(\"Unable to create SRAM0 mapping\");\n      free();\n      return false;\n   }\n\n   // Commit SRAM1\n   mSram1 = platform::createMemoryMappedFile(SRAM1Size);\n   if (mSram1 == platform::InvalidMapFileHandle) {\n      gLog->error(\"Unable to create SRAM1 mapping\");\n      free();\n      return false;\n   }\n\n   // Commit LC\n   mLockedCache = platform::createMemoryMappedFile(LCSize);\n   if (mLockedCache == platform::InvalidMapFileHandle) {\n      gLog->error(\"Unable to create Locked Cache mapping\");\n      free();\n      return false;\n   }\n\n   // Commit Tiling Apertures\n   mTilingAperture = platform::createMemoryMappedFile(TASize);\n   if (mTilingAperture == platform::InvalidMapFileHandle) {\n      gLog->error(\"Unable to create Tiling Aperture mapping\");\n      free();\n      return false;\n   }\n\n   // Release our reserved memory so we can map it\n   if (!platform::freeMemory(mPhysicalBase, 0x100000000ull)) {\n      gLog->error(\"Unable to release physical address space\");\n      free();\n      return false;\n   }\n\n   // Map physical address space\n   auto ptrMem0 = getPhysicalPointer(MEM0BaseAddress);\n   auto viewMem0 = platform::mapViewOfFile(mMem0, platform::ProtectFlags::ReadWrite, 0, MEM0Size, ptrMem0);\n   if (viewMem0 != ptrMem0) {\n      gLog->error(\"Unable to map MEM0 to physical address space\");\n      free();\n      return false;\n   }\n\n   auto ptrMem1 = getPhysicalPointer(MEM1BaseAddress);\n   auto viewMem1 = platform::mapViewOfFile(mMem1, platform::ProtectFlags::ReadWrite, 0, MEM1Size, ptrMem1);\n   if (viewMem1 != ptrMem1) {\n      gLog->error(\"Unable to map MEM1 to physical address space\");\n      free();\n      return false;\n   }\n\n   auto ptrMem2 = getPhysicalPointer(MEM2BaseAddress);\n   auto viewMem2 = platform::mapViewOfFile(mMem2, platform::ProtectFlags::ReadWrite, 0, MEM2Size, ptrMem2);\n   if (viewMem2 != ptrMem2) {\n      gLog->error(\"Unable to map MEM2 to physical address space\");\n      free();\n      return false;\n   }\n\n   auto ptrUnkRam = getPhysicalPointer(UNKRAMBaseAddress);\n   auto viewUnkRam = platform::mapViewOfFile(mUnkRam, platform::ProtectFlags::ReadWrite, 0, UNKRAMSize, ptrUnkRam);\n   if (viewUnkRam != ptrUnkRam) {\n      gLog->error(\"Unable to map UNKRAM to physical address space\");\n      free();\n      return false;\n   }\n\n   auto ptrSram0 = getPhysicalPointer(SRAM0BaseAddress);\n   auto viewSram0 = platform::mapViewOfFile(mSram0, platform::ProtectFlags::ReadWrite, 0, SRAM0Size, ptrSram0);\n   if (viewSram0 != ptrSram0) {\n      gLog->error(\"Unable to map SRAM0 to physical address space\");\n      free();\n      return false;\n   }\n\n   auto ptrSram1 = getPhysicalPointer(SRAM1BaseAddress);\n   auto viewSram1 = platform::mapViewOfFile(mSram1, platform::ProtectFlags::ReadWrite, 0, SRAM1Size, ptrSram1);\n   if (viewSram1 != ptrSram1) {\n      gLog->error(\"Unable to map SRAM1 to physical address space\");\n      free();\n      return false;\n   }\n\n   auto ptrLC = getPhysicalPointer(LCBaseAddress);\n   auto viewLC = platform::mapViewOfFile(mLockedCache, platform::ProtectFlags::ReadWrite, 0, LCSize, ptrLC);\n   if (viewLC != ptrLC) {\n      gLog->error(\"Unable to map Locked Cache to physical address space\");\n      free();\n      return false;\n   }\n\n   auto ptrTA = getPhysicalPointer(TABaseAddress);\n   auto viewTA = platform::mapViewOfFile(mTilingAperture, platform::ProtectFlags::ReadWrite, 0, TASize, ptrTA);\n   if (viewTA != ptrTA) {\n      gLog->error(\"Unable to map Tiling Aperture to physical address space\");\n      free();\n      return false;\n   }\n\n   return true;\n}\n\n\nvoid\nMemoryMap::free()\n{\n   // Unmap all views\n   while (mMappedMemory.size()) {\n      auto &mapping = mMappedMemory[0];\n      unmapMemory(mapping.virtualAddress, mapping.size);\n   }\n\n   mReservedMemory.clear();\n\n   // Close file mappings\n   if (mMem0 != platform::InvalidMapFileHandle) {\n      platform::unmapViewOfFile(getPhysicalPointer(MEM0BaseAddress), MEM0Size);\n      platform::closeMemoryMappedFile(mMem0);\n      mMem0 = platform::InvalidMapFileHandle;\n   }\n\n   if (mMem1 != platform::InvalidMapFileHandle) {\n      platform::unmapViewOfFile(getPhysicalPointer(MEM1BaseAddress), MEM1Size);\n      platform::closeMemoryMappedFile(mMem1);\n      mMem1 = platform::InvalidMapFileHandle;\n   }\n\n   if (mMem2 != platform::InvalidMapFileHandle) {\n      platform::unmapViewOfFile(getPhysicalPointer(MEM2BaseAddress), MEM2Size);\n      platform::closeMemoryMappedFile(mMem2);\n      mMem2 = platform::InvalidMapFileHandle;\n   }\n\n   if (mUnkRam != platform::InvalidMapFileHandle) {\n      platform::unmapViewOfFile(getPhysicalPointer(UNKRAMBaseAddress), UNKRAMSize);\n      platform::closeMemoryMappedFile(mUnkRam);\n      mUnkRam = platform::InvalidMapFileHandle;\n   }\n\n   if (mSram0 != platform::InvalidMapFileHandle) {\n      platform::unmapViewOfFile(getPhysicalPointer(SRAM0BaseAddress), SRAM0Size);\n      platform::closeMemoryMappedFile(mSram0);\n      mSram0 = platform::InvalidMapFileHandle;\n   }\n\n   if (mSram1 != platform::InvalidMapFileHandle) {\n      platform::unmapViewOfFile(getPhysicalPointer(SRAM1BaseAddress), SRAM1Size);\n      platform::closeMemoryMappedFile(mSram1);\n      mSram1 = platform::InvalidMapFileHandle;\n   }\n\n   if (mLockedCache != platform::InvalidMapFileHandle) {\n      platform::unmapViewOfFile(getPhysicalPointer(LCBaseAddress), LCSize);\n      platform::closeMemoryMappedFile(mLockedCache);\n      mLockedCache = platform::InvalidMapFileHandle;\n   }\n\n   if (mTilingAperture != platform::InvalidMapFileHandle) {\n      platform::unmapViewOfFile(getPhysicalPointer(TABaseAddress), TASize);\n      platform::closeMemoryMappedFile(mTilingAperture);\n      mTilingAperture = platform::InvalidMapFileHandle;\n   }\n\n   // Release virtual memory\n   if (mVirtualBase) {\n      platform::freeMemory(mVirtualBase, 0x100000000ull);\n      mVirtualBase = 0;\n   }\n\n   // Release physical memory\n   if (mPhysicalBase) {\n      platform::freeMemory(mPhysicalBase, 0x100000000ull);\n      mPhysicalBase = 0;\n   }\n}\n\n\nbool\nMemoryMap::isVirtualAddressFree(VirtualAddress start,\n                                uint32_t size)\n{\n   auto end = start + (size - 1);\n\n   for (auto &reservation : mReservedMemory) {\n      if (reservation.start <= start && reservation.end >= end) {\n         return true;\n      }\n   }\n\n   return false;\n}\n\n\nVirtualMemoryType\nMemoryMap::queryVirtualAddress(VirtualAddress virtualAddress)\n{\n   for (auto &mapped : mMappedMemory) {\n      auto mapStart = mapped.virtualAddress;\n      auto mapEnd = mapped.virtualAddress + (mapped.size - 1);\n\n      if (virtualAddress >= mapStart && virtualAddress <= mapEnd) {\n         if (mapped.permission == MapPermission::ReadOnly) {\n            return VirtualMemoryType::MappedReadOnly;\n         } else {\n            return VirtualMemoryType::MappedReadWrite;\n         }\n      }\n   }\n\n   if (isVirtualAddressFree(virtualAddress, 1)) {\n      return VirtualMemoryType::Free;\n   }\n\n   return VirtualMemoryType::Allocated;\n}\n\n\nPhysicalMemoryType\nMemoryMap::queryPhysicalAddress(PhysicalAddress physicalAddress)\n{\n   if (physicalAddress >= MEM0BaseAddress && physicalAddress <= MEM0EndAddress) {\n      return PhysicalMemoryType::MEM0;\n   } else if (physicalAddress >= MEM1BaseAddress && physicalAddress <= MEM1EndAddress) {\n      return PhysicalMemoryType::MEM1;\n   } else if (physicalAddress >= MEM2BaseAddress && physicalAddress <= MEM2EndAddress) {\n      return PhysicalMemoryType::MEM2;\n   } else if (physicalAddress >= UNKRAMBaseAddress && physicalAddress <= UNKRAMEndAddress) {\n      return PhysicalMemoryType::UNKRAM;\n   } else if (physicalAddress >= SRAM0BaseAddress && physicalAddress <= SRAM0EndAddress) {\n      return PhysicalMemoryType::SRAM0;\n   } else if (physicalAddress >= SRAM1BaseAddress && physicalAddress <= SRAM1EndAddress) {\n      return PhysicalMemoryType::SRAM1;\n   } else if (physicalAddress >= LCBaseAddress && physicalAddress <= LCEndAddress) {\n      return PhysicalMemoryType::LockedCache;\n   } else if (physicalAddress >= TABaseAddress && physicalAddress <= TAEndAddress) {\n      return PhysicalMemoryType::TilingAperture;\n   }\n\n   return PhysicalMemoryType::Invalid;\n}\n\n\nbool\nMemoryMap::virtualToPhysicalAddress(VirtualAddress virtualAddress,\n                                    PhysicalAddress &out)\n{\n   for (auto &mapping : mMappedMemory) {\n      if (mapping.virtualAddress <= virtualAddress\n         && mapping.virtualAddress + mapping.size > virtualAddress) {\n         out = mapping.physicalAddress + (virtualAddress - mapping.virtualAddress);\n         return true;\n      }\n   }\n\n   return false;\n}\n\nbool\nMemoryMap::allocateVirtualAddress(VirtualAddress start,\n                                  uint32_t size)\n{\n   if (size == 0) {\n      return false;\n   }\n\n   start = align_up(start, cpu::PageSize);\n   size = align_up(size, cpu::PageSize);\n   auto end = start + (size - 1);\n\n   if (!isVirtualAddressFree(start, size)) {\n      // Must be free to be able to allocate.\n      return false;\n   }\n\n   for (auto itr = mReservedMemory.begin(); itr != mReservedMemory.end(); ++itr) {\n      auto &reservation = *itr;\n\n      if (start >= reservation.start && end <= reservation.end) {\n         releaseReservation(reservation);\n\n         if (start == reservation.start && end == reservation.end) {\n            // Consumed whole reservation\n            mReservedMemory.erase(itr);\n         } else if (start == reservation.start) {\n            // Consumed from start of reservation\n            reservation.start += size;\n            acquireReservation(reservation);\n         } else if (end == reservation.end) {\n            // Consumed from end of reservation\n            reservation.end -= size;\n            acquireReservation(reservation);\n         } else {\n            // Consumed in middle of reservation\n            auto newReservation = VirtualReservation {};\n            newReservation.start = start + size;\n            newReservation.end = reservation.end;\n            acquireReservation(newReservation);\n\n            reservation.end = start;\n            reservation.end -= 1;\n            acquireReservation(reservation);\n            mReservedMemory.insert(itr + 1, newReservation);\n         }\n\n         return true;\n      }\n   }\n\n   return false;\n}\n\nbool\nMemoryMap::freeVirtualAddress(VirtualAddress start,\n                              uint32_t size)\n{\n   start = align_up(start, cpu::PageSize);\n   size = align_up(size, cpu::PageSize);\n\n   if (isVirtualAddressFree(start, size)) {\n      // Check if it's already free\n      return true;\n   }\n\n   // Check if we can combine with a previous reservation\n   for (auto itr = mReservedMemory.begin(); itr != mReservedMemory.end(); ++itr) {\n      auto &reservation = *itr;\n      auto mergeItr = mReservedMemory.end();\n\n      if (reservation.start == start + size) {\n         // Expand reservation backward\n         releaseReservation(reservation);\n         reservation.start -= size;\n\n         if (itr != mReservedMemory.begin()) {\n            // Check if we can merge with the previous reservation\n            auto prev = itr - 1;\n\n            if (prev->end + 1 == reservation.start) {\n               releaseReservation(*prev);\n               reservation.start = prev->start;\n               mergeItr = prev;\n            }\n         }\n\n         acquireReservation(reservation);\n\n         if (mergeItr != mReservedMemory.end()) {\n            mReservedMemory.erase(mergeItr);\n         }\n\n         return true;\n      } else if (reservation.end + 1 == start) {\n         // Expand reservation forward\n         releaseReservation(reservation);\n         reservation.end += size;\n\n         auto next = itr + 1;\n\n         if (next != mReservedMemory.end()) {\n            // Check if we can merge with the next reservation\n            if (next->start == reservation.end + 1) {\n               releaseReservation(*next);\n               reservation.end = next->end;\n               mergeItr = next;\n            }\n         }\n\n         acquireReservation(reservation);\n\n         if (mergeItr != mReservedMemory.end()) {\n            mReservedMemory.erase(mergeItr);\n         }\n\n         return true;\n      }\n   }\n\n   // We cannot combine with a previous reservation so we must make a new one!\n   auto reservation = VirtualReservation {};\n   reservation.start = start;\n   reservation.end = start + (size - 1);\n   acquireReservation(reservation);\n\n   mReservedMemory.insert(std::upper_bound(mReservedMemory.begin(),\n                                           mReservedMemory.end(),\n                                           reservation,\n                                           [](const auto &m1, const auto &m2) {\n      return m1.start < m2.start;\n   }),\n                          reservation);\n   return true;\n}\n\nVirtualAddressRange\nMemoryMap::findFreeVirtualAddress(uint32_t size,\n                                  uint32_t align)\n{\n   size = align_up(size, cpu::PageSize);\n\n   for (auto &reservation : mReservedMemory) {\n      auto alignedStart = align_up(align_up(reservation.start, align), cpu::PageSize);\n      auto alignedEnd = alignedStart + (size - 1);\n\n      if (reservation.start <= alignedStart && reservation.end >= alignedEnd) {\n         return { alignedStart, size };\n      }\n   }\n   return { VirtualAddress { 0u }, 0u };\n}\n\nVirtualAddressRange\nMemoryMap::findFreeVirtualAddressInRange(VirtualAddressRange range,\n                                         uint32_t size,\n                                         uint32_t align)\n{\n   auto rangeStart = range.start;\n   auto rangeEnd = range.start + (range.size - 1);\n   size = align_up(size, cpu::PageSize);\n\n   for (auto &reservation : mReservedMemory) {\n      if (rangeEnd < reservation.start || rangeStart > reservation.end) {\n         // Not in range\n         continue;\n      }\n\n      auto start = std::max(reservation.start, rangeStart);\n      auto end = std::min(reservation.end, rangeEnd);\n\n      auto alignedStart = align_up(align_up(start, align), cpu::PageSize);\n      auto alignedEnd = alignedStart + (size - 1);\n\n      // Ensure address is in range\n      if (start <= alignedStart && end >= alignedEnd) {\n         return { alignedStart, size };\n      }\n   }\n\n   return { VirtualAddress { 0u }, 0u };\n}\n\nbool\nMemoryMap::mapMemory(VirtualAddress virtualAddress,\n                     PhysicalAddress physicalAddress,\n                     uint32_t size,\n                     MapPermission permission)\n{\n   auto physicalMemoryType = queryPhysicalAddress(physicalAddress);\n\n   if (physicalMemoryType == PhysicalMemoryType::Invalid) {\n      gLog->error(\"Attempted to map invalid physical address 0x{:08X}\",\n                  physicalAddress.getAddress());\n      return false;\n   }\n\n   if (queryVirtualAddress(virtualAddress) != VirtualMemoryType::Allocated) {\n      gLog->error(\"Attempted to map physical address 0x{:08X} to an invalid virtual address 0x{:08X}\",\n                  virtualAddress.getAddress(), physicalAddress.getAddress());\n      return false;\n   }\n\n   // Map virtual address to physical memory\n   void *view = nullptr;\n   auto virtualPtr = getVirtualPointer(virtualAddress);\n   auto protectFlags = platform::ProtectFlags { };\n\n   if (permission == MapPermission::ReadOnly) {\n      protectFlags = platform::ProtectFlags::ReadOnly;\n   } else if (permission == MapPermission::ReadWrite) {\n      protectFlags = platform::ProtectFlags::ReadWrite;\n   } else {\n      gLog->error(\"Invalid permission {} passed to mapMemory\", static_cast<int>(permission));\n      return false;\n   }\n\n   if (physicalMemoryType == PhysicalMemoryType::MEM0) {\n      view = platform::mapViewOfFile(mMem0, protectFlags, physicalAddress - MEM0BaseAddress, size, virtualPtr);\n   } else if (physicalMemoryType == PhysicalMemoryType::MEM1) {\n      view = platform::mapViewOfFile(mMem1, protectFlags, physicalAddress - MEM1BaseAddress, size, virtualPtr);\n   } else if (physicalMemoryType == PhysicalMemoryType::MEM2) {\n      view = platform::mapViewOfFile(mMem2, protectFlags, physicalAddress - MEM2BaseAddress, size, virtualPtr);\n   } else if (physicalMemoryType == PhysicalMemoryType::UNKRAM) {\n      view = platform::mapViewOfFile(mUnkRam, protectFlags, physicalAddress - UNKRAMBaseAddress, size, virtualPtr);\n   } else if (physicalMemoryType == PhysicalMemoryType::SRAM0) {\n      view = platform::mapViewOfFile(mSram0, protectFlags, physicalAddress - SRAM0BaseAddress, size, virtualPtr);\n   } else if (physicalMemoryType == PhysicalMemoryType::SRAM1) {\n      view = platform::mapViewOfFile(mSram1, protectFlags, physicalAddress - SRAM1BaseAddress, size, virtualPtr);\n   } else if (physicalMemoryType == PhysicalMemoryType::LockedCache) {\n      view = platform::mapViewOfFile(mLockedCache, protectFlags, physicalAddress - LCBaseAddress, size, virtualPtr);\n   } else if (physicalMemoryType == PhysicalMemoryType::TilingAperture) {\n      view = platform::mapViewOfFile(mTilingAperture, protectFlags, physicalAddress - TABaseAddress, size, virtualPtr);\n   } else {\n      gLog->error(\"Invalid physicalMemoryType {} for mapMemory\", static_cast<int>(physicalMemoryType));\n      return false;\n   }\n\n   // Add to the memory map\n   auto virtualMemoryMap = VirtualMemoryMap {};\n   virtualMemoryMap.virtualAddress = virtualAddress;\n   virtualMemoryMap.physicalAddress = physicalAddress;\n   virtualMemoryMap.size = size;\n   virtualMemoryMap.permission = permission;\n\n   mMappedMemory.insert(std::upper_bound(mMappedMemory.begin(),\n                                         mMappedMemory.end(),\n                                         virtualMemoryMap,\n                                         [](const auto &m1, const auto &m2) {\n                                            return m1.virtualAddress < m2.virtualAddress;\n                                         }),\n                        virtualMemoryMap);\n\n   if (view != virtualPtr) {\n      gLog->error(\"Unable to map virtual address 0x{:08X} to physical address 0x{:08X}\",\n                  virtualAddress.getAddress(), physicalAddress.getAddress());\n      unmapMemory(virtualAddress, size);\n      return false;\n   }\n\n   return true;\n}\n\n\nbool\nMemoryMap::unmapMemory(VirtualAddress virtualAddress,\n                       uint32_t size)\n{\n   std::vector<VirtualMemoryMap> remaps;\n   auto start = align_up(virtualAddress, cpu::PageSize);\n   auto end = start + (align_up(size, cpu::PageSize) - 1);\n\n   for (auto itr = mMappedMemory.begin(); itr != mMappedMemory.end(); ) {\n      auto mapStart = itr->virtualAddress;\n      auto mapSize = itr->size;\n      auto mapEnd = itr->virtualAddress + (itr->size - 1);\n      auto remap = VirtualMemoryMap { };\n\n      if (mapStart < start && mapEnd > start) {\n         // End of map goes into unmap range\n         remap.physicalAddress = itr->physicalAddress;\n         remap.virtualAddress = itr->virtualAddress;\n         remap.size = static_cast<uint32_t>(start - mapStart);\n         remap.permission = itr->permission;\n         remaps.push_back(remap);\n      } else if (mapStart < end && mapEnd > end) {\n         // Start of map is in unmap range\n         auto offset = end - mapStart;\n         remap.physicalAddress = itr->physicalAddress + offset;\n         remap.virtualAddress = itr->virtualAddress + offset;\n         remap.size = static_cast<uint32_t>(remap.size - offset);\n         remap.permission = itr->permission;\n         remaps.push_back(remap);\n      } else if (mapStart > end || mapEnd < start) {\n         // Not in unmap range\n         ++itr;\n         continue;\n      }\n\n      itr = mMappedMemory.erase(itr);\n\n      if (!platform::unmapViewOfFile(getVirtualPointer(mapStart), mapSize)) {\n         gLog->error(\"Unexpected error whilst unmapping virtual address 0x{:08X}\",\n                     virtualAddress.getAddress());\n      }\n   }\n\n   for (auto &remap : remaps) {\n      if (!mapMemory(remap.virtualAddress,\n                     remap.physicalAddress,\n                     remap.size,\n                     remap.permission)) {\n         gLog->error(\"Unexpected error whilst remapping virtual address 0x{:08X}\",\n                     remap.virtualAddress.getAddress());\n      }\n   }\n\n   return true;\n}\n\n\nbool\nMemoryMap::resetVirtualMemory()\n{\n   // First unmap all memory\n   for (auto &mapping : mMappedMemory) {\n      if (!platform::unmapViewOfFile(getVirtualPointer(mapping.virtualAddress),\n                                     mapping.size)) {\n         gLog->error(\"Unexpected error whilst unmapping virtual address 0x{:08X}\",\n                     mapping.virtualAddress.getAddress());\n      }\n   }\n\n   mMappedMemory.clear();\n\n   if (mReservedMemory.size() == 1) {\n      // If there is only 1 reservation then we should be good to go.\n      decaf_check(mReservedMemory[0].start == VirtualAddress { 0 });\n      decaf_check(mReservedMemory[0].end == VirtualAddress { 0xFFFFFFFF });\n      return true;\n   }\n\n   // Cleanup reservations\n   for (auto &reservation : mReservedMemory) {\n      if (!releaseReservation(reservation)) {\n         gLog->error(\"Unexpected error whilst releasing virtual address 0x{:08X}\",\n                     reservation.start.getAddress());\n      }\n   }\n\n   mReservedMemory.clear();\n\n   // Reserve whole range\n   acquireReservation({ VirtualAddress { 0 }, VirtualAddress { 0xFFFFFFFF } });\n   mReservedMemory.push_back({ VirtualAddress { 0 }, VirtualAddress { 0xFFFFFFFF } });\n   return true;\n}\n\n\nbool\nMemoryMap::acquireReservation(VirtualReservation reservation)\n{\n   return platform::reserveMemory(mVirtualBase + reservation.start.getAddress(),\n                                  (reservation.end - reservation.start) + 1);\n}\n\n\nbool\nMemoryMap::releaseReservation(VirtualReservation reservation)\n{\n   return platform::freeMemory(mVirtualBase + reservation.start.getAddress(),\n                               (reservation.end - reservation.start) + 1);\n}\n\n\nuintptr_t\nMemoryMap::reserveBaseAddress()\n{\n   for (auto n = 32; n < 64; n++) {\n      auto baseAddress = 1ull << n;\n\n      if (platform::reserveMemory(baseAddress, 0x100000000ull)) {\n         return static_cast<uintptr_t>(baseAddress);\n      }\n   }\n\n   return 0;\n}\n\n\nvoid *\nMemoryMap::getPhysicalPointer(PhysicalAddress physicalAddress)\n{\n   return reinterpret_cast<void *>(mPhysicalBase + physicalAddress.getAddress());\n}\n\n\nvoid *\nMemoryMap::getVirtualPointer(VirtualAddress virtualAddress)\n{\n   return reinterpret_cast<void *>(mVirtualBase + virtualAddress.getAddress());\n}\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/memorymap.h",
    "content": "#pragma once\n#include \"address.h\"\n#include \"mmu.h\"\n#include \"pointer.h\"\n\n#include <common/platform_memory.h>\n#include <cstdint>\n#include <vector>\n\nnamespace cpu\n{\n\nclass MemoryMap\n{\n   struct VirtualReservation\n   {\n      //! Inclusive start address.\n      VirtualAddress start;\n\n      //! Inclusive end address.\n      VirtualAddress end;\n   };\n\n   struct VirtualMemoryMap\n   {\n      VirtualAddress virtualAddress;\n      PhysicalAddress physicalAddress;\n      uint32_t size;\n      MapPermission permission;\n   };\n\npublic:\n   MemoryMap() = default;\n   ~MemoryMap();\n\n   bool\n   reserve();\n\n   void\n   free();\n\n   bool\n   allocateVirtualAddress(VirtualAddress start,\n                          uint32_t size);\n\n   bool\n   freeVirtualAddress(VirtualAddress start,\n                      uint32_t size);\n\n   VirtualAddressRange\n   findFreeVirtualAddress(uint32_t size,\n                          uint32_t align);\n\n   VirtualAddressRange\n   findFreeVirtualAddressInRange(VirtualAddressRange range,\n                                 uint32_t size,\n                                 uint32_t align);\n\n   bool\n   virtualToPhysicalAddress(VirtualAddress virtualAddress,\n                            PhysicalAddress &out);\n\n   bool\n   mapMemory(VirtualAddress virtualAddress,\n             PhysicalAddress physicalAddress,\n             uint32_t size,\n             MapPermission permission);\n\n   bool\n   unmapMemory(VirtualAddress virtualAddress,\n               uint32_t size);\n\n   bool\n   resetVirtualMemory();\n\n   PhysicalMemoryType\n   queryPhysicalAddress(PhysicalAddress physicalAddress);\n\n   VirtualMemoryType\n   queryVirtualAddress(VirtualAddress virtualAddress);\n\nprivate:\n   uintptr_t reserveBaseAddress();\n\n   bool\n   isVirtualAddressFree(VirtualAddress start,\n                        uint32_t size);\n\n   bool\n   acquireReservation(VirtualReservation reservation);\n\n   bool\n   releaseReservation(VirtualReservation reservation);\n\n   void *getPhysicalPointer(PhysicalAddress physicalAddress);\n   void *getVirtualPointer(VirtualAddress virtualAddress);\n\nprivate:\n   platform::MapFileHandle mMem0 = platform::InvalidMapFileHandle;\n   platform::MapFileHandle mMem1 = platform::InvalidMapFileHandle;\n   platform::MapFileHandle mMem2 = platform::InvalidMapFileHandle;\n   platform::MapFileHandle mUnkRam = platform::InvalidMapFileHandle;\n   platform::MapFileHandle mSram0 = platform::InvalidMapFileHandle;\n   platform::MapFileHandle mSram1 = platform::InvalidMapFileHandle;\n   platform::MapFileHandle mLockedCache = platform::InvalidMapFileHandle;\n   platform::MapFileHandle mTilingAperture = platform::InvalidMapFileHandle;\n\n   uintptr_t mVirtualBase = 0;\n   uintptr_t mPhysicalBase = 0;\n   std::vector<VirtualMemoryMap> mMappedMemory;\n   std::vector<VirtualReservation> mReservedMemory;\n};\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/src/statedbg.h",
    "content": "#pragma once\n#include <iostream>\n#include <cstring>\n#include <vector>\n#include <string>\n#include <sstream>\n#include \"state.h\"\n\ntemplate<typename T>\nstatic std::string toHexString(T i)\n{\n   std::ostringstream oss;\n   oss << \"0x\" << std::hex << i;\n   return oss.str();\n}\n\ninline bool dbgStateCmp(cpu::Core* state, cpu::Core* estate, std::vector<std::string>& errors)\n{\n   if (memcmp(state, estate, sizeof(cpu::Core)) == 0) {\n      return true;\n   }\n\n#define CHECKONE(n, m) if (state->n != estate->n) errors.push_back(std::string(m) + \" (got:\" + toHexString(state->n)  + \" expected:\" + toHexString(estate->n) + \")\")\n#define CHECKONEI(n, m, i) CHECKONE(n, std::string(m) + \"[\" + std::to_string(i) + \"]\")\n   CHECKONE(cia, \"CIA\");\n   CHECKONE(nia, \"NIA\");\n   for (auto i = 0; i < 32; ++i) {\n      CHECKONEI(gpr[i], \"GPR\", i);\n   }\n   for (auto i = 0; i < 32; ++i) {\n      CHECKONEI(fpr[i].idw, \"FPR\", i);\n   }\n   CHECKONE(cr.value, \"CR\");\n   CHECKONE(xer.value, \"XER\");\n   CHECKONE(lr, \"CTR\");\n   CHECKONE(ctr, \"CTR\");\n   CHECKONE(fpscr.value, \"FPSCR\");\n   CHECKONE(pvr.value, \"PVR\");\n   CHECKONE(msr.value, \"MSR\");\n   for (auto i = 0; i < 16; ++i) {\n      CHECKONEI(sr[i], \"SR\", i);\n   }\n   for (auto i = 0; i < 8; ++i) {\n      CHECKONEI(gqr[i].value, \"GQR\", i);\n   }\n#undef CHECKONEI\n#undef CHECKONE\n\n   return false;\n}\n"
  },
  {
    "path": "src/libcpu/src/trace.cpp",
    "content": "#include \"trace.h\"\n#include \"state.h\"\n#include \"statedbg.h\"\n#include \"cpu.h\"\n#include \"espresso/espresso_disassembler.h\"\n#include \"espresso/espresso_instructionset.h\"\n#include \"espresso/espresso_spr.h\"\n\n#include <common/log.h>\n#include <common/platform_debug.h>\n#include <common/decaf_assert.h>\n#include <fmt/format.h>\n#include <iterator>\n#include <mutex>\n\nusing espresso::Instruction;\nusing espresso::InstructionID;\nusing espresso::InstructionInfo;\nusing espresso::InstructionField;\nusing espresso::SPR;\n\n//#define TRACE_ENABLED\n//#define TRACE_SC_ENABLED\n//#define TRACE_VERIFICATION\n\nstruct Tracer\n{\n   size_t index;\n   size_t numTraces;\n   std::vector<Trace> traces;\n   cpu::CoreRegs prevState;\n};\n\nstatic inline void\ndebugPrint(std::string out)\n{\n   gLog->debug(out);\n\n   out.push_back('\\n');\n   platform::debugLog(out);\n}\n\nnamespace cpu\n{\n\ncpu::Tracer *\nallocTracer(size_t size)\n{\n   // TODO: TRACE_ENABLED should actually be handled by kernel\n#ifdef TRACE_ENABLED\n   auto tracer = new Tracer();\n   tracer->index = 0;\n   tracer->numTraces = 0;\n   tracer->traces.resize(size);\n   return tracer;\n#else\n   return nullptr;\n#endif\n}\n\nvoid\nfreeTracer(cpu::Tracer *tracer)\n{\n   if (tracer) {\n      delete tracer;\n   }\n}\n\nnamespace this_core\n{\n\nvoid setTracer(cpu::Tracer *tracer)\n{\n   state()->tracer = tracer;\n}\n\n} // namespace this_core\n\n} // namespace cpu\n\nstd::string\ngetStateFieldName(TraceFieldType type)\n{\n   if (type == StateField::Invalid) {\n      return \"Invalid\";\n   }\n\n   if (type >= StateField::GPR0 && type <= StateField::GPR31) {\n      return fmt::format(\"r{:02}\", type - StateField::GPR);\n   } else if (type >= StateField::FPR0 && type <= StateField::FPR31) {\n      return fmt::format(\"f{:02}\", type - StateField::FPR);\n   } else if (type >= StateField::GQR0 && type <= StateField::GQR7) {\n      return fmt::format(\"q{:02}\", type - StateField::FPR);\n   } else if (type == StateField::CR) {\n      return \"CR\";\n   } else if (type == StateField::XER) {\n      return \"XER\";\n   } else if (type == StateField::LR) {\n      return \"LR\";\n   } else if (type == StateField::FPSCR) {\n      return \"FPSCR\";\n   } else if (type == StateField::CTR) {\n      return \"CTR\";\n   } else {\n      decaf_abort(fmt::format(\"Invalid TraceFieldType {}\", static_cast<int>(type)));\n   }\n}\n\nstatic void\nprintFieldValue(fmt::memory_buffer &out, Instruction instr, TraceFieldType type, const TraceFieldValue& value)\n{\n   if (type == StateField::Invalid) {\n      return;\n   }\n\n   if (type >= StateField::GPR0 && type <= StateField::GPR31) {\n      fmt::format_to(std::back_inserter(out),\n         \"    r{:02} = {:08x}\\n\", type - StateField::GPR, value.u32v0);\n   } else if (type == StateField::CR) {\n      auto valX = [&](int i) { return (value.u32v0 >> ((i) * 4)) & 0xF; };\n      fmt::format_to(std::back_inserter(out),\n         \"    CR = {:04b} {:04b} {:04b} {:04b} {:04b} {:04b} {:04b} {:04b}\\n\",\n         valX(7), valX(6), valX(5), valX(4), valX(3), valX(2), valX(1), valX(0));\n   } else if (type == StateField::XER) {\n      fmt::format_to(std::back_inserter(out), \"    XER = {:08x}\\n\", value.u32v0);\n   } else if (type == StateField::LR) {\n      fmt::format_to(std::back_inserter(out), \"    LR = {:08x}\\n\", value.u32v0);\n   } else if (type == StateField::CTR) {\n      fmt::format_to(std::back_inserter(out), \"    CTR = {:08x}\\n\", value.u32v0);\n   } else {\n      decaf_abort(fmt::format(\"Invalid TraceFieldType {}\", static_cast<int>(type)));\n   }\n}\n\nstatic void\nprintInstruction(fmt::memory_buffer &out, const Trace& trace, int index)\n{\n   auto dis = espresso::Disassembly { };\n   espresso::disassemble(trace.instr, dis, trace.cia);\n\n   auto disassemblyText = espresso::disassemblyToText(dis);\n   auto addend = std::string { };\n\n   /*\n   if (dis.instruction->id == InstructionID::kc) {\n      auto scall = gSystem.getSyscallData(trace.instr.li);\n      addend = \" [\" + std::string(scall->name) + \"]\";\n   }\n   */\n\n   for (auto &write : trace.writes) {\n      printFieldValue(out, trace.instr, write.type, write.value);\n   }\n\n   fmt::format_to(std::back_inserter(out),\n      \"  [{}] {:08x} {}{}\\n\", index, trace.cia, disassemblyText.c_str(), addend.c_str());\n\n   for (auto &read : trace.reads) {\n      printFieldValue(out, trace.instr, read.type, read.value);\n   }\n}\n\nconst Trace&\ngetTrace(Tracer *tracer, int index)\n{\n   auto tracerSize = tracer->numTraces;\n   decaf_check(index >= 0);\n   decaf_check(index < tracerSize);\n   auto realIndex = (int)tracer->index - 1 - index;\n   while (realIndex < 0) {\n      realIndex += (int)tracerSize;\n   }\n   while (realIndex >= tracerSize) {\n      realIndex -= (int)tracerSize;\n   }\n   return tracer->traces[realIndex];\n}\n\nsize_t\ngetTracerNumTraces(Tracer *tracer)\n{\n   return tracer->numTraces;\n}\n\nvoid\ntraceInit(cpu::Core *state, size_t size)\n{\n#ifdef TRACE_ENABLED\n   state->tracer = new Tracer();\n   state->tracer->index = 0;\n   state->tracer->numTraces = 0;\n   state->tracer->traces.resize(size);\n#else\n   state->tracer = nullptr;\n#endif\n}\n\nstatic uint32_t\ngetFieldStateField(Instruction instr, InstructionField field)\n{\n   switch (field) {\n   case InstructionField::rA:\n      return StateField::GPR + instr.rA;\n   case InstructionField::rB:\n      return StateField::GPR + instr.rB;\n   case InstructionField::rS:\n      return StateField::GPR + instr.rS;\n   case InstructionField::rD:\n      return StateField::GPR + instr.rD;\n   case InstructionField::frA:\n      return StateField::FPR + instr.frA;\n   case InstructionField::frB:\n      return StateField::FPR + instr.frB;\n   case InstructionField::frC:\n      return StateField::FPR + instr.frC;\n   case InstructionField::frD:\n      return StateField::FPR + instr.frD;\n   case InstructionField::frS:\n      return StateField::FPR + instr.frS;\n   case InstructionField::spr:\n      switch (decodeSPR(instr)) {\n      case SPR::CTR:\n         return StateField::CTR;\n      case SPR::LR:\n         return StateField::LR;\n      case SPR::XER:\n         return StateField::XER;\n      case SPR::GQR0:\n         return StateField::GQR + 0;\n      case SPR::GQR1:\n         return StateField::GQR + 1;\n      case SPR::GQR2:\n         return StateField::GQR + 2;\n      case SPR::GQR3:\n         return StateField::GQR + 3;\n      case SPR::GQR4:\n         return StateField::GQR + 4;\n      case SPR::GQR5:\n         return StateField::GQR + 5;\n      case SPR::GQR6:\n         return StateField::GQR + 6;\n      case SPR::GQR7:\n         return StateField::GQR + 7;\n      default:\n         break;\n      }\n      break;\n   case InstructionField::bo:\n      if ((instr.bo & 4) == 0) {\n         return StateField::CTR;\n      }\n      break;\n   case InstructionField::bi:\n      return StateField::CR;\n   case InstructionField::crbA:\n   case InstructionField::crbB:\n   case InstructionField::crbD:\n   case InstructionField::crfD:\n   case InstructionField::crfS:\n   case InstructionField::crm:\n      return StateField::CR;\n   case InstructionField::oe:\n      if (instr.oe) {\n         return StateField::XER;\n      }\n      break;\n   case InstructionField::rc:\n      if (instr.rc) {\n         return StateField::CR;\n      }\n      break;\n   case InstructionField::lk:\n      return StateField::LR;\n   case InstructionField::XERO:\n      return StateField::XER;\n   case InstructionField::CTR:\n      return StateField::CTR;\n   case InstructionField::LR:\n      return StateField::LR;\n   case InstructionField::FPSCR:\n      return StateField::FPSCR;\n   case InstructionField::RSRV:\n      // TODO: Handle this?\n   default:\n      break;\n   }\n   return StateField::Invalid;\n}\n\nvoid\nsaveStateField(const cpu::CoreRegs *state, TraceFieldType type, TraceFieldValue &field)\n{\n   field.u64v0 = 0;\n   field.u64v1 = 0;\n\n   if (type == StateField::Invalid) {\n      return;\n   }\n\n   if (type >= StateField::GPR0 && type <= StateField::GPR31) {\n      field.u32v0 = state->gpr[type - StateField::GPR];\n   } else if (type >= StateField::FPR0 && type <= StateField::FPR31) {\n      field.u64v0 = state->fpr[type - StateField::FPR].idw;\n   } else if (type >= StateField::GQR0 && type <= StateField::GQR7) {\n      field.u32v0 = state->gqr[type - StateField::GQR].value;\n   } else if (type == StateField::CR) {\n      field.u32v0 = state->cr.value;\n   } else if (type == StateField::XER) {\n      field.u32v0 = state->xer.value;\n   } else if (type == StateField::LR) {\n      field.u32v0 = state->lr;\n   } else if (type == StateField::CTR) {\n      field.u32v0 = state->ctr;\n   } else if (type == StateField::FPSCR) {\n      field.u32v0 = state->fpscr.value;\n   } else {\n      decaf_abort(fmt::format(\"Invalid TraceFieldType {}\", static_cast<int>(type)));\n   }\n}\n\nvoid\nrestoreStateField(cpu::CoreRegs *state, TraceFieldType type, const TraceFieldValue &field)\n{\n   if (type == StateField::Invalid) {\n      return;\n   }\n\n   if (type >= StateField::GPR0 && type <= StateField::GPR31) {\n      state->gpr[type - StateField::GPR] = field.u32v0;\n   } else if (type >= StateField::FPR0 && type <= StateField::FPR31) {\n      state->fpr[type - StateField::FPR].idw = field.u64v0;\n   } else if (type >= StateField::GQR0 && type <= StateField::GQR7) {\n      state->gqr[type - StateField::GQR].value = field.u32v0;\n   } else if (type == StateField::CR) {\n      state->cr.value = field.u32v0;\n   } else if (type == StateField::XER) {\n      state->xer.value = field.u32v0;\n   } else if (type == StateField::LR) {\n      state->lr = field.u32v0;\n   } else if (type == StateField::CTR) {\n      state->ctr = field.u32v0;\n   } else if (type == StateField::FPSCR) {\n      state->fpscr.value = field.u32v0;\n   } else {\n      decaf_abort(fmt::format(\"Invalid TraceFieldType {}\", static_cast<int>(type)));\n   }\n}\n\ntemplate<typename T>\nstatic void\npushUniqueField(std::vector<T> &fields, uint32_t fieldId)\n{\n   if (fieldId == StateField::Invalid) {\n      return;\n   }\n\n   for (auto &i : fields) {\n      if (i.type == fieldId) {\n         return;\n      }\n   }\n\n   fields.push_back({ fieldId });\n}\n\nTrace *\ntraceInstructionStart(Instruction instr, InstructionInfo *data, cpu::Core *state)\n{\n   if (!state->tracer) {\n      return nullptr;\n   }\n\n   auto tracer = state->tracer;\n   auto &trace = tracer->traces[tracer->index];\n   auto tracerSize = tracer->traces.size();\n\n   if (tracer->numTraces < tracerSize) {\n      tracer->numTraces++;\n   }\n\n   tracer->index = (tracer->index + 1) % tracerSize;\n\n   // Setup Trace\n   trace.instr = instr;\n   trace.cia = state->cia;\n   trace.reads.clear();\n   trace.writes.clear();\n\n   // Automatically determine register changes\n   for (auto i = 0u; i < data->read.size(); ++i) {\n      auto stateField = getFieldStateField(instr, data->read[i]);\n      pushUniqueField(trace.reads, stateField);\n   }\n\n   for (auto i = 0u; i < data->write.size(); ++i) {\n      auto stateField = getFieldStateField(instr, data->write[i]);\n      pushUniqueField(trace.writes, stateField);\n   }\n\n   for (auto i = 0u; i < data->flags.size(); ++i) {\n      auto stateField = getFieldStateField(instr, data->flags[i]);\n      pushUniqueField(trace.writes, stateField);\n   }\n\n   // Some special cases.\n   if (data->id == InstructionID::lmw) {\n      for (uint32_t i = StateField::GPR + instr.rD; i <= StateField::GPR31; ++i) {\n         pushUniqueField(trace.writes, i);\n      }\n   } else if (data->id == InstructionID::stmw) {\n      for (uint32_t i = StateField::GPR + instr.rS; i <= StateField::GPR31; ++i) {\n         pushUniqueField(trace.reads, i);\n      }\n   } else if (data->id == InstructionID::stswi) {\n      // TODO: Implement Me\n   } else if (data->id == InstructionID::stswx) {\n      // TODO: Implement Me\n   }\n\n#ifdef TRACE_VERIFICATION\n   if (data->id == InstructionID::stswi) {\n      //assert(0);\n   } else if (data->id == InstructionID::stswx) {\n      //assert(0);\n   }\n#endif\n\n   // Save all state\n   for (auto &i : trace.reads) {\n      saveStateField(state, i.type, i.value);\n   }\n\n   for (auto &i : trace.writes) {\n      saveStateField(state, i.type, i.prevalue);\n   }\n\n   // TODO: This is a bit of a hack... We should probably not do this...\n   tracer->prevState = *state;\n   return &trace;\n}\n\nvoid\ntraceInstructionEnd(Trace *trace, Instruction instr, InstructionInfo *data, cpu::Core *state)\n{\n   if (!trace) {\n      return;\n   }\n\n   auto tracer = state->tracer;\n\n   // Special hack for KC for now\n   if (data->id == InstructionID::kc) {\n      trace->writes.clear();\n\n      for (int i = 0; i < StateField::Max; ++i) {\n         TraceFieldValue curVal, prevVal;\n         saveStateField(&tracer->prevState, i, prevVal);\n         saveStateField(state, i, curVal);\n         if (curVal.value != prevVal.value) {\n            pushUniqueField(trace->writes, i);\n         }\n      }\n\n      for (auto &i : trace->writes) {\n         saveStateField(&tracer->prevState, i.type, i.prevalue);\n      }\n   }\n\n   for (auto &i : trace->writes) {\n      saveStateField(state, i.type, i.value);\n   }\n\n#ifdef TRACE_VERIFICATION\n   if (tracer->numTraces > 0) {\n      auto errors = std::vector<std::string> {};\n      auto checkState = *state;\n      checkState.nia = tracer->prevState.nia;\n\n      for (auto &i : trace->writes) {\n         restoreStateField(&checkState, i.type, i.prevalue);\n      }\n\n      if (!dbgStateCmp(&checkState, &tracer->prevState, errors)) {\n         gLog->error(\"Trace Compliance errors w/ {}\", data->name);\n\n         for (auto &err : errors) {\n            gLog->error(err);\n         }\n\n         DebugBreak();\n      }\n   }\n#endif\n}\n\nvoid\ntracePrint(cpu::Core *state, int start, int count)\n{\n   auto tracer = state->tracer;\n   if (!tracer) {\n      debugPrint(\"Tracing is disabled\");\n      return;\n   }\n\n   auto tracerSize = static_cast<int>(getTracerNumTraces(tracer));\n\n   if (count == 0) {\n      count = tracerSize - start;\n   }\n\n   auto end = start + count;\n   decaf_check(start >= 0);\n   decaf_check(end <= tracerSize);\n\n   fmt::memory_buffer out;\n   fmt::format_to(std::back_inserter(out), \"Trace - Print {} to {}\\n\", start, end);\n\n   for (auto i = start; i < end; ++i) {\n      auto &trace = getTrace(tracer, i);\n      printInstruction(out, trace, i);\n   }\n\n   debugPrint(to_string(out));\n}\n\nint\ntraceReg(cpu::Core *state, int start, int regIdx)\n{\n   auto tracer = state->tracer;\n   auto tracerSize = static_cast<int>(getTracerNumTraces(tracer));\n   bool found = false;\n\n   fmt::memory_buffer out;\n   fmt::format_to(std::back_inserter(out),\n      \"Trace - Search {} to {} for write r{}\\n\", start, tracerSize, regIdx);\n\n   decaf_check(start >= 0);\n   decaf_check(start < tracerSize);\n\n   for (auto i = start; i < tracerSize; ++i) {\n      auto &trace = getTrace(tracer, i);\n\n      bool wasMatchedWrite = false;\n      for (auto &j : trace.writes) {\n         if (j.type == StateField::GPR + static_cast<uint32_t>(regIdx)) {\n            wasMatchedWrite = true;\n            break;\n         }\n      }\n\n      if (wasMatchedWrite) {\n         printInstruction(out, trace, i);\n         found = true;\n         break;\n      }\n   }\n\n   if (!found) {\n      fmt::format_to(std::back_inserter(out), \"  Nothing Found\");\n   }\n\n   debugPrint(to_string(out));\n   return -1;\n}\n\nstatic cpu::Core *gRegTraceState = nullptr;\nstatic int gRegTraceIndex = 0;\nstatic int gRegTraceNextReg = -1;\n\nvoid\ntraceRegStart(cpu::Core *state, int start, int regIdx)\n{\n   gRegTraceState = state;\n   gRegTraceIndex = start;\n   gRegTraceNextReg = -1;\n   traceRegNext(regIdx);\n}\n\nvoid\ntraceRegNext(int regIdx)\n{\n   if (!gRegTraceState || gRegTraceIndex < 0) {\n      debugPrint(\"Need to use traceRegStart first.\");\n      return;\n   }\n\n   auto foundIndex = traceReg(gRegTraceState, gRegTraceIndex, regIdx);\n   if (foundIndex == -1) {\n      gRegTraceIndex = -1;\n      return;\n   }\n\n   auto tracer = gRegTraceState->tracer;\n   auto &trace = getTrace(tracer, foundIndex);\n\n   if (trace.reads.size() == 1) {\n      if (trace.reads.front().type >= StateField::GPR0 && trace.reads.front().type <= StateField::GPR31) {\n         gRegTraceNextReg = trace.reads.front().type - StateField::GPR;\n         debugPrint(fmt::format(\"Suggested next register is r{}\", gRegTraceNextReg));\n      } else {\n         // Not a GPR read\n         gRegTraceNextReg = -1;\n      }\n   } else {\n      // More than one read field\n      gRegTraceNextReg = -1;\n   }\n\n   gRegTraceIndex = foundIndex + 1;\n}\n\nvoid\ntraceRegAround()\n{\n   if (!gRegTraceState || gRegTraceIndex < 0) {\n      debugPrint(\"Need to use traceRegStart first.\");\n      return;\n   }\n\n   int start = gRegTraceIndex - 5;\n   int end = gRegTraceIndex + 4;\n\n   auto tracer = gRegTraceState->tracer;\n   auto tracerSize = static_cast<int>(getTracerNumTraces(tracer));\n\n   if (start < 0) {\n      start = 0;\n   }\n   if (end > tracerSize) {\n      end = tracerSize;\n   }\n\n   tracePrint(gRegTraceState, start, end - start);\n}\n\nvoid\ntraceRegContinue()\n{\n   if (gRegTraceNextReg == -1) {\n      debugPrint(\"No stored suggested next register.\");\n      return;\n   }\n\n   traceRegNext(gRegTraceNextReg);\n}\n\nstd::list<std::string> gSyscallTrace;\nstd::mutex gSyscallTraceLock;\n\nvoid\ntracePrintSyscall(int count)\n{\n   std::unique_lock<std::mutex> lock(gSyscallTraceLock);\n\n   if (count <= 0 || count >= gSyscallTrace.size()) {\n      count = (int)gSyscallTrace.size();\n   }\n\n   fmt::memory_buffer out;\n   fmt::format_to(std::back_inserter(out), \"Trace - Last {} syscalls\\n\", count);\n\n   int j = 0;\n   for (auto i = gSyscallTrace.begin(); i != gSyscallTrace.end() && j < count; ++i, ++j) {\n      fmt::format_to(std::back_inserter(out), \"  {}\\n\", *i);\n   }\n\n   debugPrint(to_string(out));\n}\n\nvoid\ntraceLogSyscall(const std::string &info)\n{\n#ifdef TRACE_SC_ENABLED\n   std::unique_lock<std::mutex> lock(gSyscallTraceLock);\n\n   gSyscallTrace.push_front(info);\n   while (gSyscallTrace.size() > 2048) {\n      gSyscallTrace.pop_back();\n   }\n#endif\n}\n"
  },
  {
    "path": "src/libcpu/src/utils.h",
    "content": "#pragma once\n#include <cstdint>\n#include \"state.h\"\n\n// TODO: Move these getCRX functions\n\nuint32_t\ngetCRF(cpu::Core *state,\n       uint32_t field);\n\nvoid\nsetCRF(cpu::Core *state,\n       uint32_t field,\n       uint32_t value);\n\nuint32_t\ngetCRB(cpu::Core *state,\n       uint32_t bit);\n\nvoid\nsetCRB(cpu::Core *state,\n       uint32_t bit,\n       uint32_t value);\n"
  },
  {
    "path": "src/libcpu/state.h",
    "content": "#pragma once\n#include \"espresso/espresso_registerformats.h\"\n\n#include <atomic>\n#include <chrono>\n#include <thread>\n\nstruct Tracer;\n\nnamespace cpu\n{\n\nstatic const uint32_t coreClockSpeed = 1243125000;\nstatic const uint32_t busClockSpeed = 248625000;\nstatic const uint32_t timerClockSpeed = busClockSpeed / 4;\n\nusing TimerDuration = std::chrono::duration<uint64_t, std::ratio<1, timerClockSpeed>>;\n\nstruct CoreRegs\n{\n   //! Current execution address\n   uint32_t cia;\n\n   //! Next execution address\n   uint32_t nia;\n\n   //! Integer Registers\n   espresso::Register gpr[32];\n\n   //! Floating-point Registers\n   espresso::FloatingPointRegister fpr[32];\n\n   //! Condition Register\n   espresso::ConditionRegister cr;\n\n   //! XER Carry/Overflow register\n   espresso::FixedPointExceptionRegister xer;\n\n   //! Link Register\n   uint32_t lr;\n\n   //! Count Register\n   uint32_t ctr;\n\n   //! Floating-Point Status and Control Register\n   espresso::FloatingPointStatusAndControlRegister fpscr;\n\n   //! Processor Version Register\n   espresso::ProcessorVersionRegister pvr;\n\n   //! Machine State Register\n   espresso::MachineStateRegister msr;\n\n   //! Segment Registers\n   uint32_t sr[16];\n\n   //! Graphics Quantization Registers\n   espresso::GraphicsQuantisationRegister gqr[8];\n\n   //! Data Address Register\n   uint32_t dar;\n\n   //! DSI Status Register\n   uint32_t dsisr;\n\n   //! Machine Status Save and Restore Register 0\n   uint32_t srr0;\n};\n\nstruct Core : CoreRegs\n{\n   // Core ID\n   uint32_t id;\n\n   // Value of gpr[1] at time of system call\n   uint32_t systemCallStackHead;\n\n   uint32_t interrupt_mask { 0xFFFFFFFF };\n   std::atomic<uint32_t> interrupt { 0 };\n\n   bool reserveFlag { false };\n   uint32_t reserveData;\n\n   std::thread thread;\n   std::chrono::steady_clock::time_point next_alarm;\n\n   // Tracer used to record executed instructions\n   Tracer *tracer;\n\n   // Get current core time\n   uint64_t tb();\n};\n\n} // namespace cpu\n"
  },
  {
    "path": "src/libcpu/trace.h",
    "content": "#pragma once\n#include <vector>\n#include <list>\n#include <string>\n#include \"espresso/espresso_instruction.h\"\n#include \"espresso/espresso_instructionset.h\"\n\nnamespace cpu\n{\nstruct Core;\nstruct CoreRegs;\n} // namespace cpu\n\nstruct Tracer;\n\n// TODO: Probably should rename this to something reasonable\nnamespace StateField\n{\nenum Field : uint8_t\n{\n   Invalid,\n   GPR,\n   GPR0 = GPR,\n   GPR31 = GPR + 31,\n   FPR,\n   FPR0 = FPR,\n   FPR31 = FPR + 31,\n   GQR,\n   GQR0 = GQR,\n   GQR7 = GQR + 7,\n   CR,\n   XER,\n   LR,\n   CTR,\n   FPSCR,\n   Max,\n};\n}\n\ntypedef uint32_t TraceFieldType;\n\nstruct TraceFieldValue\n{\n   struct ValueType\n   {\n      bool operator==(const ValueType& rhs)\n      {\n         return data[0] == rhs.data[0] && data[1] == rhs.data[1];\n      }\n\n      bool operator!=(const ValueType& rhs)\n      {\n         return data[0] != rhs.data[0] || data[1] != rhs.data[1];\n      }\n\n      uint64_t data[2];\n   };\n\n   union\n   {\n      struct\n      {\n         uint32_t u32v0;\n         uint32_t u32v1;\n         uint32_t u32v2;\n         uint32_t u32v3;\n      };\n\n      struct\n      {\n         uint64_t u64v0;\n         uint64_t u64v1;\n      };\n\n      struct\n      {\n         float f32v0;\n         float f32v1;\n         float f32v2;\n         float f32v3;\n      };\n\n      struct\n      {\n         double f64v0;\n         double f64v1;\n      };\n\n      struct\n      {\n         uint32_t mem_size;\n         uint32_t mem_offset;\n      };\n\n      struct\n      {\n         uint64_t value0;\n         uint64_t value1;\n      };\n\n      ValueType value;\n   };\n};\nstatic_assert(sizeof(TraceFieldValue) == sizeof(TraceFieldValue::value), \"TraceFieldValue::value size must match total structure size\");\n\nstd::string\ngetStateFieldName(TraceFieldType type);\n\nvoid\nsaveStateField(const cpu::CoreRegs *state,\n               TraceFieldType type,\n               TraceFieldValue &field);\n\nvoid\nrestoreStateField(cpu::CoreRegs *state,\n                  TraceFieldType type,\n                  const TraceFieldValue &field);\n\nstruct Trace\n{\n   struct _R\n   {\n      TraceFieldType type;\n      TraceFieldValue value;\n   };\n\n   struct _W\n   {\n      TraceFieldType type;\n      TraceFieldValue value;\n      TraceFieldValue prevalue;\n   };\n\n   espresso::Instruction instr;\n   uint32_t cia;\n   std::vector<_R> reads;\n   std::vector<_W> writes;\n};\n\nconst Trace &\ngetTrace(Tracer *tracer,\n         int index);\n\nsize_t\ngetTracerNumTraces(Tracer *tracer);\n\nvoid\ntraceInit(cpu::Core *state,\n          size_t size);\n\nTrace *\ntraceInstructionStart(espresso::Instruction instr,\n                      espresso::InstructionInfo *data,\n                      cpu::Core *state);\n\nvoid\ntraceInstructionEnd(Trace *trace,\n                    espresso::Instruction instr,\n                    espresso::InstructionInfo *data,\n                    cpu::Core *state);\n\nvoid\ntracePrint(cpu::Core *state,\n           int start,\n           int count);\n\nint\ntraceReg(cpu::Core *state,\n         int start,\n         int regIdx);\n\nvoid\ntraceRegStart(cpu::Core *state,\n              int start,\n              int regIdx);\n\nvoid\ntraceRegNext(int regIdx);\n\nvoid\ntraceRegContinue();\n\nvoid\ntracePrintSyscall(int count);\n\nvoid\ntraceLogSyscall(const std::string& info);\n"
  },
  {
    "path": "src/libdecaf/CMakeLists.txt",
    "content": "project(libdecaf)\n\ninclude_directories(\".\")\ninclude_directories(\"src\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nadd_library(libdecaf STATIC ${SOURCE_FILES} ${HEADER_FILES})\nGroupSources(\"Source Files\" src)\n\ntarget_compile_definitions(libdecaf PRIVATE\n    DECAF_INSTALL_RESOURCESDIR=\"${DECAF_INSTALL_RESOURCESDIR}\")\n\ntarget_link_libraries(libdecaf\n    addrlib\n    common\n    libcpu\n    libgfd\n    libgpu)\n\ntarget_link_libraries(libdecaf\n    cnl\n    imgui\n    pugixml\n    ${CARES_LIBRARY}\n    ${CURL_LIBRARY}\n    ${OPENSSL_LIBRARY}\n    ${LIBUV_LIBRARY}\n    ${ZLIB_LIBRARY}\n    ${FFMPEG_LIBRARY})\n\nif(MSVC)\n    target_link_libraries(libdecaf Crypt32 ws2_32 Psapi IPHLPAPI userenv)\n    target_compile_options(libdecaf PUBLIC /wd4251)\nendif()\n\nif(${CMAKE_SYSTEM_NAME} MATCHES \"Linux\")\n    target_link_libraries(libdecaf dl)\nendif()\n\nif(DECAF_PCH)\n    target_precompile_headers(libdecaf\n      PRIVATE\n        <common/pch.h>\n    )\n\n    AutoGroupPCHFiles()\nendif()\n"
  },
  {
    "path": "src/libdecaf/decaf.h",
    "content": "#pragma once\n#include \"decaf_eventlistener.h\"\n#include \"decaf_graphics.h\"\n#include \"decaf_input.h\"\n\n#include <string>\n#include <vector>\n#include <memory>\n\nnamespace decaf\n{\n\nstd::string\nmakeConfigPath(const std::string &filename);\n\nbool\ncreateConfigDirectory();\n\nstd::string\ngetResourcePath(const std::string &filename);\n\nbool\ninitialise(const std::string &gamePath);\n\nvoid\nstart();\n\nbool\nstopping();\n\nint\nwaitForExit();\n\nvoid\nshutdown();\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/decaf_config.h",
    "content": "#pragma once\n#include <memory>\n#include <string>\n#include <vector>\n#include <utility>\n\nnamespace decaf\n{\n\nstruct DebuggerSettings\n{\n   bool enabled = true;\n   bool break_on_entry = false;\n   bool break_on_exit = true;\n   bool gdb_stub = false;\n   unsigned gdb_stub_port = 2159;\n};\n\nstruct Gx2Settings\n{\n   bool dump_textures = false;\n   bool dump_shaders = false;\n   bool dump_shader_binaries_only = false;\n};\n\nstruct LogSettings\n{\n   bool async = false;\n   bool to_file = false;\n   bool to_stdout = false;\n   std::string level = \"debug\";\n   std::vector<std::pair<std::string, std::string>> levels = {\n      { \"ios_fs\", \"warn\" },\n   };\n   std::string directory = \".\";\n   bool branch_trace = false;\n   bool hle_trace = false;\n   bool hle_trace_res = false;\n   std::vector<std::string> hle_trace_filters =\n   {\n      \"+.*\",\n      \"-coreinit.rpl::__ghsLock\",\n      \"-coreinit.rpl::__ghsUnlock\",\n      \"-coreinit.rpl::__gh_errno_ptr\",\n      \"-coreinit.rpl::__gh_set_errno\",\n      \"-coreinit.rpl::__gh_get_errno\",\n      \"-coreinit.rpl::__get_eh_globals\",\n      \"-coreinit.rpl::OSGetTime\",\n      \"-coreinit.rpl::OSGetSystemTime\",\n   };\n};\n\nstruct SoundSettings\n{\n   bool dump_sounds = false;\n};\n\nenum class SystemRegion\n{\n   Japan = 0x01,\n   USA = 0x02,\n   Europe = 0x04,\n   Unknown8 = 0x08,\n   China = 0x10,\n   Korea = 0x20,\n   Taiwan = 0x40,\n};\n\nstruct SystemSettings\n{\n   SystemRegion region = SystemRegion::Europe;\n   std::string content_path = {};\n   std::string hfio_path = \"\";\n   std::string mlc_path = \"mlc\";\n   std::string otp_path = \"otp.bin\";\n   std::string sdcard_path = \"sdcard\";\n   std::string resources_path = \"resources\";\n   std::string slc_path = \"slc\";\n   std::vector<std::string> title_directories = {};\n   bool time_scale_enabled = false;\n   double time_scale = 1.0;\n   std::vector<std::string> lle_modules;\n   bool dump_hle_rpl = false; // TODO: Move this to a debug api command?\n};\n\nstruct Settings\n{\n   DebuggerSettings debugger;\n   Gx2Settings gx2;\n   LogSettings log;\n   SoundSettings sound;\n   SystemSettings system;\n};\n\nstd::shared_ptr<const Settings> config();\nvoid setConfig(const Settings &settings);\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/decaf_content.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace decaf\n{\n\nenum class TitleType\n{\n   Application    = 0,\n   Demo           = 2,\n   Data           = 0xB,\n   DLC            = 0xC,\n   Update         = 0xE,\n};\n\nusing TitleID = uint64_t;\n\nconstexpr inline bool isSystemTitle(TitleID id)\n{\n   return !!((id >> 32) & 0x10);\n}\n\nconstexpr inline TitleType getTitleTypeFromID(TitleID id)\n{\n   return static_cast<TitleType>((id >> 32) & 0x0f);\n}\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/decaf_debug_api.h",
    "content": "#pragma once\n#include <array>\n#include <chrono>\n#include <cstdint>\n#include <functional>\n#include <map>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\nnamespace decaf::debug\n{\n\nusing VirtualAddress = uint32_t;\nusing PhysicalAddress = uint32_t;\n\nusing CafeThreadHandle = VirtualAddress;\n\nusing PauseCallback = std::function<void()>;\n\nstruct AnalyseDatabase\n{\n   struct Function\n   {\n      VirtualAddress start;\n      VirtualAddress end;\n      std::string name;\n   };\n\n   struct Instruction\n   {\n      // Addresses of instructions which jump to this one\n      std::vector<uint32_t> sourceBranches;\n\n      // User-left comments\n      std::string comments;\n   };\n\n   struct Lookup\n   {\n      //! Information about the function at this address.\n      const Function *function = nullptr;\n\n      //! Information about the instruction at this address.\n      const Instruction *instruction = nullptr;\n   };\n\n   std::vector<Function> functions;\n   std::unordered_map<uint32_t, Instruction> instructions;\n};\n\nstruct CafeMemorySegment\n{\n   //! Name of the memory segment.\n   std::string name;\n\n   //! Virtual Address of the start of the memory segment.\n   VirtualAddress address;\n\n   //! Size of the memory segment.\n   uint32_t size;\n\n   //! Alignment of the memory segment.\n   uint32_t align;\n\n   //! Segment has read permissions.\n   bool read;\n\n   //! Segment has write permissions.\n   bool write;\n\n   //! Segment has execute permissions.\n   bool execute;\n};\n\nstruct CafeModuleInfo\n{\n   //! Start address of text section.\n   VirtualAddress textAddr = 0;\n\n   //! Size of text.\n   uint32_t textSize = 0;\n\n   //! Start address of data section.\n   VirtualAddress dataAddr = 0;\n\n   //! Size of data.\n   uint32_t dataSize = 0;\n};\n\nstruct CafeThread\n{\n   enum ThreadAffinity\n   {\n      None = 0,\n      Core0 = 1 << 0,\n      Core1 = 1 << 1,\n      Core2 = 1 << 2,\n      Any = Core0 | Core1 | Core2,\n   };\n\n   enum ThreadState\n   {\n      //! Thread is inactive.\n      Inactive = 0,\n\n      //! Thread is ready to execute.\n      Ready = 1 << 0,\n\n      //! Thread is currently executing.\n      Running = 1 << 1,\n\n      //! Thread is blocked waiting for something e.g. a mutex.\n      Waiting = 1 << 2,\n\n      //! Thread is about to be terminated.\n      Moribund = 1 << 3,\n   };\n\n   //! Virtual Address of the OSThread structure.\n   CafeThreadHandle handle = 0;\n\n   //! Assigned thread id.\n   int id = -1;\n\n   //! Name of the thread.\n   std::string name;\n\n   //! Current execution state.\n   ThreadState state = ThreadState::Inactive;\n\n   //! Thread core affinity.\n   ThreadAffinity affinity = ThreadAffinity::None;\n\n   //! Base thread priority.\n   int basePriority = 0;\n\n   //! Active thread priority.\n   int priority = 0;\n\n   //! The core this thread is currently running on, -1 if not running on a core.\n   int coreId = -1;\n\n   //! The amount of time this thread has been running.\n   std::chrono::nanoseconds executionTime;\n\n   //! The starting address of the stack.\n   VirtualAddress stackStart = 0;\n\n   //! The end address of the stack.\n   VirtualAddress stackEnd = 0;\n\n   //! Current execution address\n   VirtualAddress cia = 0;\n\n   //! Next execution address\n   VirtualAddress nia = 0;\n\n   //! Integer Registers\n   std::array<uint32_t, 32> gpr;\n\n   //! Floating-point Registers\n   std::array<double, 32> fpr;\n\n   //! Floating-point Registers - Paired Single 1\n   std::array<double, 32> ps1;\n\n   //! Condition Register\n   uint32_t cr;\n\n   //! XER Carry/Overflow register\n   uint32_t xer;\n\n   //! Link Register\n   uint32_t lr;\n\n   //! Count Register\n   uint32_t ctr;\n\n   //! Machine State Register\n   uint32_t msr;\n};\n\nstruct CpuBreakpoint\n{\n   enum Type\n   {\n      SingleFire,\n      MultiFire,\n   };\n\n   //! Breakpoint type.\n   Type type;\n\n   //! Address of breakpoint.\n   uint32_t address;\n\n   //! Code at address before we inserted a TW instruction.\n   uint32_t savedCode;\n};\n\nstruct CpuContext\n{\n   //! Current execution address\n   uint32_t cia;\n\n   //! Next execution address\n   uint32_t nia;\n\n   //! Integer Registers\n   std::array<uint32_t, 32> gpr;\n\n   //! Floating-point Registers\n   std::array<double, 32> fpr;\n\n   //! Floating-point Registers - Paired Single 1\n   std::array<double, 32> ps1;\n\n   //! Condition Register\n   uint32_t cr;\n\n   //! XER Carry/Overflow register\n   uint32_t xer;\n\n   //! Link Register\n   uint32_t lr;\n\n   //! Count Register\n   uint32_t ctr;\n\n   //! Floating-Point Status and Control Register\n   uint32_t fpscr;\n\n   //! Processor Version Register\n   uint32_t pvr;\n\n   //! Machine State Register\n   uint32_t msr;\n\n   //! Segment Registers\n   std::array<uint32_t, 16> sr;\n\n   //! Graphics Quantization Registers\n   std::array<uint32_t, 8> gqr;\n\n   //! Data Address Register\n   uint32_t dar;\n\n   //! DSI Status Register\n   uint32_t dsisr;\n\n   //! Machine Status Save and Restore Register 0\n   uint32_t srr0;\n};\n\nstruct CafeVoice\n{\n   enum State\n   {\n      Stopped = 0,\n      Playing = 1,\n   };\n\n   enum Format\n   {\n      ADPCM = 0,\n      LPCM16 = 0x0A,\n      LPCM8 = 0x19,\n   };\n\n   enum VoiceType\n   {\n      Default = 0,\n      Streaming = 1,\n   };\n\n   //! The index of this voice.\n   int index = -1;\n\n   //! Current play state of the voice.\n   State state = State::Stopped;\n\n   //! Encoding format of the voice data.\n   Format format;\n\n   //! Voice type.\n   VoiceType type;\n\n   //! Address of voice data.\n   VirtualAddress data;\n\n   //! Current offset into voice data.\n   int currentOffset;\n\n   //! Loop offset of voice data.\n   int loopOffset;\n\n   //! End offset of voice data.\n   int endOffset;\n\n   //! Looping enabled.\n   bool loopingEnabled;\n};\n\nenum class Pm4CaptureState\n{\n   Disabled,\n   WaitStartNextFrame,\n   WaitEndNextFrame,\n   Enabled,\n};\n\n//! Check if the debug API is ready to be used, this returns true once a game\n//! .rpx has been loaded\nbool ready();\n\n// Code analysis\nvoid analyseLoadedModules(AnalyseDatabase &db);\nvoid analyseCode(AnalyseDatabase &db, VirtualAddress start, VirtualAddress end);\n\nconst AnalyseDatabase::Function *analyseLookupFunction(const AnalyseDatabase &db,\n                                                       VirtualAddress address);\nAnalyseDatabase::Lookup analyseLookupAddress(const AnalyseDatabase &db,\n                                             VirtualAddress address);\nuint32_t analyseScanFunctionEnd(VirtualAddress start);\nvoid analyseToggleAsFunction(AnalyseDatabase &db, VirtualAddress address);\n\n// CafeOS\nbool findClosestSymbol(VirtualAddress addr, uint32_t *outSymbolDistance,\n                       char *symbolNameBuffer, uint32_t symbolNameBufferLength,\n                       char *moduleNameBuffer, uint32_t moduleNameBufferLength);\nbool getLoadedModuleInfo(CafeModuleInfo &info);\nbool sampleCafeMemorySegments(std::vector<CafeMemorySegment> &segments);\nbool sampleCafeRunningThread(int coreId, CafeThread &info);\nbool sampleCafeThreads(std::vector<CafeThread> &threads);\nbool sampleCafeVoices(std::vector<CafeVoice> &voiceInfos);\n\n// pm4 capture\nPm4CaptureState pm4CaptureState();\nbool pm4CaptureNextFrame();\nbool pm4CaptureBegin();\nbool pm4CaptureEnd();\n\n// Controller\nvoid setPauseCallback(PauseCallback callback);\nbool pause();\nbool resume();\nbool isPaused();\nint getPauseInitiatorCoreId();\nconst CpuContext *getPausedContext(int core);\nbool stepInto(int core);\nbool stepOver(int core);\nbool hasBreakpoint(VirtualAddress address);\nbool addBreakpoint(VirtualAddress address);\nbool removeBreakpoint(VirtualAddress address);\n\n// CPU\nvoid sampleCpuBreakpoints(std::vector<CpuBreakpoint> &breakpoints);\n\n// Memory\nbool isValidVirtualAddress(VirtualAddress address);\nsize_t getMemoryPageSize();\nsize_t readMemory(VirtualAddress address, void *dst, size_t size);\nsize_t writeMemory(VirtualAddress address, const void *src, size_t size);\n\n} // namespace decaf::debug\n"
  },
  {
    "path": "src/libdecaf/decaf_erreula.h",
    "content": "#pragma once\n#include <string>\n\nnamespace decaf\n{\n\nclass ErrEulaDriver\n{\npublic:\n   void buttonClicked();\n   void button1Clicked();\n   void button2Clicked();\n\n   virtual void onOpenErrorCode(int32_t errorCode) { };\n   virtual void onOpenErrorMessage(std::u16string message,\n                                   std::u16string button1 = {},\n                                   std::u16string button2 = {}) { };\n   virtual void onClose() { };\n};\n\nvoid setErrEulaDriver(ErrEulaDriver *driver);\nErrEulaDriver *errEulaDriver();\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/decaf_eventlistener.h",
    "content": "#pragma once\n#include \"decaf_game.h\"\n\nnamespace decaf\n{\n\nenum class EventType\n{\n   GameLoaded,\n};\n\nclass EventListener;\n\nvoid\naddEventListener(EventListener *listener);\n\nvoid\nremoveEventListener(EventListener *listener);\n\nclass EventListener\n{\npublic:\n   virtual ~EventListener()\n   {\n      removeEventListener(this);\n   }\n\n   virtual void onGameLoaded(const GameInfo &info) { };\n};\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/decaf_graphics.h",
    "content": "#pragma once\n#include <cstdint>\n#include <functional>\n#include <libgpu/gpu_graphicsdriver.h>\n\nnamespace decaf\n{\n\nvoid\nsetGraphicsDriver(gpu::GraphicsDriver *driver);\n\ngpu::GraphicsDriver *\ngetGraphicsDriver();\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/decaf_input.h",
    "content": "#pragma once\n#include <cstddef>\n#include <cstdint>\n#include <string_view>\n\nnamespace decaf\n{\n\nnamespace input\n{\n\nenum class Category\n{\n   Invalid,\n   VPAD,    // DRC\n   WPAD,    // Wii Remote + extensions, Pro Controller, etc\n   WBC,     // Balance board\n};\n\nnamespace vpad\n{\n\nstruct ButtonStatus\n{\n   uint32_t sync : 1;\n   uint32_t home : 1;\n   uint32_t minus : 1;\n   uint32_t plus : 1;\n   uint32_t r : 1;\n   uint32_t l : 1;\n   uint32_t zr : 1;\n   uint32_t zl : 1;\n   uint32_t down : 1;\n   uint32_t up : 1;\n   uint32_t right : 1;\n   uint32_t left : 1;\n   uint32_t y : 1;\n   uint32_t x : 1;\n   uint32_t b : 1;\n   uint32_t a : 1;\n   uint32_t stickR : 1;\n   uint32_t stickL : 1;\n};\n\nstruct AccelerometerStatus\n{\n   float accelX;\n   float accelY;\n   float accelZ;\n   float magnitute;\n   float variation;\n   float verticalX;\n   float verticalY;\n};\n\nstruct GyroStatus\n{\n   // TODO: Gyro status\n};\n\nstruct MagnetometerStatus\n{\n   float x;\n   float y;\n   float z;\n};\n\nstruct TouchStatus\n{\n   //! True if screen is currently being touched\n   bool down;\n\n   //! The normalised x-coordinate of a touched point, between 0.0 and 1.0.\n   float x;\n\n   //! The normalised y-coordinate of a touched point, between 0.0 and 1.0.\n   float y;\n};\n\nstruct Status\n{\n   //! Whether the controller is currently connected or not.\n   bool connected;\n\n   //! Indicates what buttons are held down.\n   ButtonStatus buttons;\n\n   //! Position of left analog stick\n   float leftStickX;\n   float leftStickY;\n\n   //! Position of right analog stick\n   float rightStickX;\n   float rightStickY;\n\n   //! Status of accelorometer\n   AccelerometerStatus accelorometer;\n\n   //! Status of gyro\n   GyroStatus gyro;\n\n   //! Current touch position on DRC\n   TouchStatus touch;\n\n   //! Status of DRC magnetometer\n   MagnetometerStatus magnetometer;\n\n   //! Current volume set by the slide control\n   uint8_t slideVolume;\n\n   //! Battery level of controller\n   uint8_t battery;\n\n   //! Status of DRC microphone\n   uint8_t micStatus;\n};\n\n} // namespace vpad\n\nnamespace wpad\n{\n\nstatic const size_t\nMaxChannels = 4;\n\nenum class BaseControllerType\n{\n   Disconnected,\n   Wiimote,\n   Classic,\n   Pro,\n};\n\nstruct WiimoteButtonStatus\n{\n   uint32_t up : 1;\n   uint32_t down : 1;\n   uint32_t right : 1;\n   uint32_t left : 1;\n   uint32_t a : 1;\n   uint32_t b : 1;\n   uint32_t button1 : 1;\n   uint32_t button2 : 1;\n   uint32_t minus : 1;\n   uint32_t home : 1;\n   uint32_t plus : 1;\n};\n\n//! Wiimote\nstruct WiimoteStatus\n{\n   WiimoteButtonStatus buttons;\n};\n\nstruct ClassicButtonStatus\n{\n   uint32_t up : 1;\n   uint32_t down : 1;\n   uint32_t right : 1;\n   uint32_t left : 1;\n   uint32_t a : 1;\n   uint32_t b : 1;\n   uint32_t x : 1;\n   uint32_t y : 1;\n   uint32_t r : 1;\n   uint32_t l : 1;\n   uint32_t zr : 1;\n   uint32_t zl : 1;\n   uint32_t plus : 1;\n   uint32_t home : 1;\n   uint32_t minus : 1;\n};\n\n//! Wii Classic Controller\nstruct ClassicStatus\n{\n   ClassicButtonStatus buttons;\n   float leftStickX;\n   float leftStickY;\n   float rightStickX;\n   float rightStickY;\n   float triggerL;\n   float triggerR;\n};\n\nstruct ProButtonStatus\n{\n   uint32_t up : 1;\n   uint32_t down : 1;\n   uint32_t right : 1;\n   uint32_t left : 1;\n   uint32_t a : 1;\n   uint32_t b : 1;\n   uint32_t x : 1;\n   uint32_t y : 1;\n   uint32_t r : 1;\n   uint32_t l : 1;\n   uint32_t zr : 1;\n   uint32_t zl : 1;\n   uint32_t plus : 1;\n   uint32_t home : 1;\n   uint32_t minus : 1;\n   uint32_t leftStick : 1;\n   uint32_t rightStick : 1;\n};\n\n//! Wii U Pro Controller\nstruct ProStatus\n{\n   ProButtonStatus buttons;\n   float leftStickX;\n   float leftStickY;\n   float rightStickX;\n   float rightStickY;\n};\n\nstruct NunchukButtonStatus\n{\n   uint32_t z : 1;\n   uint32_t c : 1;\n};\n\n//! Wii Nunchuk extension\nstruct NunchukStatus\n{\n   bool connected;\n   NunchukButtonStatus buttons;\n   float accelX;\n   float accelY;\n   float accelZ;\n   float stickX;\n   float stickY;\n};\n\n//! Wii Motion Plus extension\nstruct MotionPlusStatus\n{\n   bool connected;\n   float pitch;\n   float yaw;\n   float roll;\n};\n\nstruct Status\n{\n   //! Base type of this controller, optionally a nunchuk or motion plus device\n   //! can be connected on top of this base controller type.\n   BaseControllerType type;\n\n   union\n   {\n      WiimoteStatus wiimote;\n      ProStatus pro;\n      ClassicStatus classic;\n   };\n\n   MotionPlusStatus motionPlus;\n   NunchukStatus nunchuk;\n};\n\n} // namespace wpad\n\n} // namespace input\n\nclass InputDriver\n{\npublic:\n   virtual void sampleVpadController(int channel, input::vpad::Status &status) = 0;\n   virtual void sampleWpadController(int channel, input::wpad::Status &status) = 0;\n};\n\nvoid setInputDriver(InputDriver *driver);\nInputDriver *getInputDriver();\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/decaf_log.h",
    "content": "#pragma once\n#include <spdlog/spdlog.h>\n#include <string>\n#include <string_view>\n#include <vector>\n\nnamespace decaf\n{\n\nvoid\ninitialiseLogging(std::string_view filename);\n\nstd::shared_ptr<spdlog::logger>\nmakeLogger(std::string name,\n           std::vector<spdlog::sink_ptr> userSinks = {});\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/decaf_nullinputdriver.h",
    "content": "#pragma once\n#include \"decaf_input.h\"\n\nnamespace decaf\n{\n\nclass NullInputDriver : public InputDriver\n{\npublic:\n   virtual void sampleVpadController(int channel, input::vpad::Status &status) override;\n   virtual void sampleWpadController(int channel, input::wpad::Status &status) override;\n};\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/decaf_pm4replay.h",
    "content": "#pragma once\n#include <array>\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n\nnamespace decaf::pm4\n{\n\n#pragma pack(push, 1)\n\nstatic const std::array<char, 4> CaptureMagic =\n{\n   'D', 'P', 'M', '4'\n};\n\nstruct CapturePacket\n{\n   enum Type : uint32_t\n   {\n      Invalid,\n      CommandBuffer,\n      MemoryLoad,\n      RegisterSnapshot,\n      SetBuffer,\n   };\n\n   Type type;\n   uint32_t size;\n   uint64_t timestamp;\n};\n\nstruct CaptureMemoryLoad\n{\n   enum MemoryType : uint32_t\n   {\n      Unknown,\n      CpuFlush,\n      SurfaceSync,\n      ShadowState,\n      CommandBuffer,\n      AttributeBuffer,\n      UniformBuffer,\n      IndexBuffer,\n      Surface,\n      FetchShader,\n      VertexShader,\n      PixelShader,\n      GeometryShader,\n   };\n\n   MemoryType type;\n   phys_addr address;\n};\n\nstruct CaptureSetBuffer\n{\n   enum Type : uint32_t\n   {\n      Invalid,\n      TvBuffer,\n      DrcBuffer,\n   };\n\n   Type type;\n   phys_addr address;\n   uint32_t size;\n   uint32_t renderMode;\n   uint32_t surfaceFormat;\n   uint32_t bufferingMode;\n   uint32_t width;\n   uint32_t height;\n};\n\n#pragma pack(pop)\n\n} // namespace decaf::pm4\n"
  },
  {
    "path": "src/libdecaf/decaf_softwarekeyboard.h",
    "content": "#pragma once\n#include <string>\n#include <string_view>\n\nnamespace decaf\n{\n\nclass SoftwareKeyboardDriver\n{\npublic:\n   void accept();\n   void reject();\n   void setInputString(std::u16string_view text);\n\n   virtual void onOpen(std::u16string defaultText) { };\n   virtual void onClose() { };\n   virtual void onInputStringChanged(std::u16string text) { };\n};\n\nvoid setSoftwareKeyboardDriver(SoftwareKeyboardDriver *driver);\nSoftwareKeyboardDriver *softwareKeyboardDriver();\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/decaf_sound.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace decaf\n{\n\nclass SoundDriver\n{\npublic:\n   virtual ~SoundDriver()\n   {\n   }\n\n   virtual bool\n   start(unsigned outputRate,\n         unsigned numChannels) = 0;\n\n   // Sample data has channels interleaved.  The implementation may reuse\n   //  the sample buffer as a temporary buffer (of length\n   //  samples * numChannels * numSamples).\n   // TODO: DRC/controller output not yet supported.\n   virtual void\n   output(int16_t *samples,\n          unsigned numSamples) = 0;\n\n   virtual void\n   stop() = 0;\n};\n\nvoid setSoundDriver(SoundDriver *driver);\nSoundDriver *getSoundDriver();\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/src/cafe/cafe_ppc_interface.h",
    "content": "#pragma once\n#include <array>\n#include <cstdint>\n#include <common/type_traits.h>\n#include <libcpu/be2_struct.h>\n#include <tuple>\n#include <type_traits>\n\nnamespace cafe\n{\n\n/**\n * Type used to indicate function takes variable arguments.\n */\nstruct var_args\n{\n   // Set to the index of the first gpr register to use.\n   uint32_t gpr;\n\n   // Set to the index of the first fpr register to use.\n   uint32_t fpr;\n};\n\nnamespace detail\n{\n\n/**\n * The register a type is stored in.\n *\n * We must distinguish between gpr for 32 and 64 bit values because a 64 bit\n * value has register index alignment in the PPC calling convention.\n */\nenum class RegisterType\n{\n   Gpr32,\n   Gpr64,\n   Fpr,\n   Void,\n   VarArgs,\n};\n\n/**\n * A type T is stored in an FPR register if it is:\n * - A floating point type\n */\ntemplate<typename T>\nusing is_fpr_type = is_true<std::is_floating_point<T>::value>;\n\n/**\n * Check if a type looks like a common/bitfield.h defined bitfield.\n */\ntemplate<class T>\nstruct void_t { typedef void type; };\n\ntemplate<class T, class U = void>\nstruct is_bitfield_type : std::false_type { };\n\ntemplate<class T>\nstruct is_bitfield_type<T, typename void_t<typename T::BitfieldType>::type> : std::true_type { };\n\n/**\n * A type T is stored in a single GPR register if it is:\n * - sizeof(T) <= 4 or T is a bool (sizeof bool is not defined)\n * - Not a floating point\n * - Not a var_args type\n * - If it is a cpu function pointer\n * - If it is a cpu pointer\n * - If a uint32_t can be constructed from T.\n */\ntemplate<typename T>\nusing is_gpr32_type = is_true<\n   (sizeof(T) <= 4 || std::is_same<bool, T>::value) &&\n   !std::is_floating_point<T>::value &&\n   !std::is_pointer<T>::value &&\n   !std::is_same<var_args, T>::value &&\n   (std::is_constructible<typename safe_underlying_type<T>::type, uint32_t>::value ||\n    cpu::is_cpu_pointer<T>::value ||\n    cpu::is_cpu_func_pointer<T>::value ||\n    is_bitfield_type<T>::value)>;\n\n/**\n * A type T is stored in two aligned GPR registers if it is:\n * - sizeof(T) == 8\n * - Not a floating point\n * - If a uint64_t can be constructed from T\n */\ntemplate<typename T>\nusing is_gpr64_type = is_true<\n   sizeof(T) == 8 &&\n   !std::is_floating_point<T>::value &&\n   !std::is_pointer<T>::value &&\n   std::is_constructible<typename safe_underlying_type<T>::type, uint64_t>::value &&\n   !std::is_same<var_args, T>::value>;\n\ntemplate<typename T>\nusing is_var_args_type = is_true<\n   std::is_same<var_args, T>::value>;\n\n// Gets the register type for a type T.\ntemplate<typename, typename T2 = void>\nstruct register_type;\n\ntemplate<typename T>\nstruct register_type<T, typename std::enable_if<std::is_void<T>::value>::type>\n{\n   static constexpr auto value = RegisterType::Void;\n   static constexpr auto return_index = 0;\n};\n\ntemplate<typename T>\nstruct register_type<T, typename std::enable_if<is_fpr_type<T>::value>::type>\n{\n   static constexpr auto value = RegisterType::Fpr;\n   static constexpr auto return_index = 1;\n};\n\ntemplate<typename T>\nstruct register_type<T, typename std::enable_if<is_gpr32_type<T>::value>::type>\n{\n   static constexpr auto value = RegisterType::Gpr32;\n   static constexpr auto return_index = 3;\n};\n\ntemplate<typename T>\nstruct register_type<T, typename std::enable_if<is_gpr64_type<T>::value>::type>\n{\n   static constexpr auto value = RegisterType::Gpr64;\n   static constexpr auto return_index = 3;\n};\n\ntemplate<typename T>\nstruct register_type<T, typename std::enable_if<is_var_args_type<T>::value>::type>\n{\n   static constexpr auto value = RegisterType::VarArgs;\n   static constexpr auto return_index = 0;\n};\n\n// Prepends a type T to a tuple\ntemplate<typename T, typename Ts>\nstruct tuple_prepend;\n\ntemplate<typename T, typename... Ts>\nstruct tuple_prepend<T, std::tuple<Ts...>>\n{\n   using type = std::tuple<T, Ts...>;\n};\n\ntemplate<typename T>\nstruct tuple_prepend<T, void>\n{\n   using type = std::tuple<T>;\n};\n\n// Calculate the index for a given RegisterType\ntemplate<RegisterType type, int GprIndex, int FprIndex>\nstruct register_index;\n\ntemplate<int GprIndex, int FprIndex>\nstruct register_index<RegisterType::Gpr32, GprIndex, FprIndex>\n{\n   static constexpr auto value = GprIndex;\n   static constexpr auto gpr_next = value + 1;\n   static constexpr auto fpr_next = FprIndex;\n};\n\ntemplate<int GprIndex, int FprIndex>\nstruct register_index<RegisterType::Gpr64, GprIndex, FprIndex>\n{\n   static constexpr auto value = ((GprIndex % 2) == 0) ? (GprIndex + 1) : GprIndex;\n   static constexpr auto gpr_next = value + 2;\n   static constexpr auto fpr_next = FprIndex;\n};\n\ntemplate<int GprIndex, int FprIndex>\nstruct register_index<RegisterType::Fpr, GprIndex, FprIndex>\n{\n   static constexpr auto value = FprIndex;\n   static constexpr auto gpr_next = GprIndex;\n   static constexpr auto fpr_next = value + 1;\n};\n\ntemplate<int GprIndex, int FprIndex>\nstruct register_index<RegisterType::VarArgs, GprIndex, FprIndex>\n{\n   static constexpr auto value = GprIndex | (FprIndex << 8);\n   static constexpr auto gpr_next = -1;\n   static constexpr auto fpr_next = -1;\n};\n\n// An empty type to store function parameter info\ntemplate<typename ValueType, RegisterType Type, auto Index>\nstruct param_info_t\n{\n   using type = ValueType;\n   static constexpr auto reg_index = Index;\n   static constexpr auto reg_type = Type;\n};\n\n// Calculates a std::tuple<param_info_t...> type for a list of types\ntemplate<int GprIndex, int FprIndex, typename... Ts>\nstruct get_param_infos_impl;\n\ntemplate<int GprIndex, int FprIndex, typename Head, typename... Tail>\nstruct get_param_infos_impl<GprIndex, FprIndex, Head, Tail...>\n{\n   using head_register_type = register_type<std::remove_cv_t<Head>>;\n   using head_arg_index = register_index<head_register_type::value, GprIndex, FprIndex>;\n   using type = typename tuple_prepend<\n      param_info_t<Head, head_register_type::value, head_arg_index::value>,\n      typename get_param_infos_impl<head_arg_index::gpr_next, head_arg_index::fpr_next, Tail...>::type\n   >::type;\n};\n\ntemplate<int GprIndex, int FprIndex>\nstruct get_param_infos_impl<GprIndex, FprIndex>\n{\n   using type = std::tuple<>;\n};\n\n\n// Creates a runtime data structure of parameter info\nstruct RuntimeParamInfo\n{\n   // Required default constructor for empty make_runtime_param_info\n   constexpr RuntimeParamInfo() :\n      reg_type(RegisterType::Gpr32),\n      reg_index(0),\n      is_signed(false),\n      is_pointer(false),\n      is_string(false)\n   {\n   }\n\n   constexpr RuntimeParamInfo(RegisterType type, int index,\n                              bool is_signed, bool is_pointer, bool is_string) :\n      reg_type(type),\n      reg_index(index),\n      is_signed(is_signed),\n      is_pointer(is_pointer),\n      is_string(is_string)\n   {\n   }\n\n   RegisterType reg_type;\n   int reg_index;\n   bool is_signed;\n   bool is_pointer;\n   bool is_string;\n};\n\ntemplate<size_t NumArgs, typename... Ts>\nstatic constexpr std::array<RuntimeParamInfo, NumArgs>\nmake_runtime_param_info(std::tuple<Ts...>)\n{\n   return {\n      RuntimeParamInfo {\n         Ts::reg_type, Ts::reg_index,\n         std::is_signed<typename std::decay<typename Ts::type>::type>::value,\n         cpu::is_cpu_pointer<typename Ts::type>::value || cpu::is_cpu_func_pointer<typename Ts::type>::value,\n         std::is_same<typename Ts::type, virt_ptr<const char>>::value\n      }...\n   };\n}\ntemplate<size_t NumArgs>\nstatic constexpr std::array<RuntimeParamInfo, NumArgs>\nmake_runtime_param_info(std::tuple<>)\n{\n   return {};\n}\n\n// Stores information about a function\ntemplate<typename T>\nstruct function_traits;\n\ntemplate<typename ReturnType, typename... ArgTypes>\nstruct function_traits<ReturnType(ArgTypes...)>\n{\n   using return_type = register_type<std::remove_cv_t<ReturnType>>;\n   using return_info = param_info_t<ReturnType, return_type::value, return_type::return_index>;\n   using param_info = typename get_param_infos_impl<3, 1, ArgTypes...>::type;\n\n   static constexpr auto is_member_function = false;\n   static constexpr auto num_args = sizeof...(ArgTypes);\n   static constexpr auto has_return_value = !std::is_void<ReturnType>::value;\n   static constexpr std::array<RuntimeParamInfo, num_args> runtime_param_info = make_runtime_param_info<num_args>(param_info {});\n};\n\ntemplate<typename ObjectType, typename ReturnType, typename... ArgTypes>\nstruct function_traits<ReturnType(ObjectType::*)(ArgTypes...)>\n{\n   using return_type = register_type<std::remove_cv_t<ReturnType>>;\n   using return_info = param_info_t<ReturnType, return_type::value, return_type::return_index>;\n   using param_info = typename get_param_infos_impl<4, 1, ArgTypes...>::type;\n   using object_info = param_info_t<virt_ptr<ObjectType>, RegisterType::Gpr32, 3>;\n\n   static constexpr auto is_member_function = true;\n   static constexpr auto num_args = sizeof...(ArgTypes);\n   static constexpr auto has_return_value = !std::is_void<ReturnType>::value;\n   static constexpr std::array<RuntimeParamInfo, num_args> runtime_param_info = make_runtime_param_info<num_args>(param_info{});\n};\n\ntemplate<typename ObjectType, typename ReturnType, typename... ArgTypes>\nstruct function_traits<ReturnType(ObjectType::*)(ArgTypes...) const> : function_traits<ReturnType(ObjectType::*)(ArgTypes...)>\n{\n   static constexpr auto is_const_member_function = true;\n};\n\ntemplate<typename ReturnType, typename... ArgTypes>\nstruct function_traits<ReturnType(*)(ArgTypes...)> : function_traits<ReturnType(ArgTypes...)>\n{\n};\n\ntemplate <typename T>\nstruct function_traits<T&> : function_traits<T> { };\n\ntemplate <typename T>\nstruct function_traits<const T&> : function_traits<T> { };\n\ntemplate <typename T>\nstruct function_traits<volatile T&> : function_traits<T> { };\n\ntemplate <typename T>\nstruct function_traits<const volatile T&> : function_traits<T> { };\n\ntemplate <typename T>\nstruct function_traits<T&&> : function_traits<T> { };\n\ntemplate <typename T>\nstruct function_traits<const T&&> : function_traits<T> { };\n\ntemplate <typename T>\nstruct function_traits<volatile T&&> : function_traits<T> { };\n\ntemplate <typename T>\nstruct function_traits<const volatile T&&> : function_traits<T> { };\n\n} // namespace detail\n\n} // namespace cafe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/cafe_ppc_interface_invoke_guest.h",
    "content": "#pragma once\n#include \"cafe_ppc_interface_params.h\"\n\n#include <libcpu/cpu_control.h>\n\nnamespace cafe\n{\n\nnamespace detail\n{\n\ntemplate<typename FunctionTraitsType, std::size_t... I, typename... ArgTypes>\ninline decltype(auto)\ninvoke_guest_impl(cpu::Core *core,\n                  cpu::VirtualAddress address,\n                  FunctionTraitsType &&,\n                  std::index_sequence<I...>,\n                  const ArgTypes &... args)\n{\n   if constexpr (FunctionTraitsType::num_args > 0) {\n      // Write arguments to registers\n      auto param_info = typename FunctionTraitsType::param_info { };\n      (writeParam(core, std::get<I>(param_info), args), ...);\n   }\n\n   // Write stack frame back chain\n   auto frame = virt_cast<uint32_t *>(virt_addr{ core->gpr[1] - 8 });\n   frame[0] = core->systemCallStackHead;\n   frame[1] = core->lr;\n   core->gpr[1] -= 8;\n\n   // Save state\n   auto cia = core->cia;\n   auto nia = core->nia;\n\n   // Set state to our function to call\n   core->cia = 0;\n   core->nia = address.getAddress();\n\n   // Start executing!\n   cpu::this_core::executeSub();\n\n   // Grab the most recent core state as it may have changed.\n   core = cpu::this_core::state();\n\n   // Restore state\n   core->cia = cia;\n   core->nia = nia;\n\n   // Restore stack\n   core->gpr[1] += 8;\n\n   // Return the result\n   if constexpr (FunctionTraitsType::has_return_value) {\n      return readParam(core, typename FunctionTraitsType::return_info { });\n   }\n}\n\n} // namespace detail\n\n// Invoke a guest function from a host context\ntemplate<typename FunctionType, typename... ArgTypes>\ninline decltype(auto)\ninvoke(cpu::Core *core,\n       cpu::FunctionPointer<cpu::VirtualAddress, FunctionType> fn,\n       const ArgTypes &... args)\n{\n   using func_traits = detail::function_traits<FunctionType>;\n   return detail::invoke_guest_impl(core,\n                                    fn.getAddress(),\n                                    func_traits { },\n                                    std::make_index_sequence<func_traits::num_args> {},\n                                    args...);\n}\n\n// Invoke a be2_val guest function from a host context\ntemplate<typename FunctionType, typename... ArgTypes>\ninline decltype(auto)\ninvoke(cpu::Core *core,\n       be2_val<cpu::FunctionPointer<cpu::VirtualAddress, FunctionType>> fn,\n       const ArgTypes &... args)\n{\n   using func_traits = detail::function_traits<FunctionType>;\n   return detail::invoke_guest_impl(core,\n                                    fn.getAddress(),\n                                    func_traits { },\n                                    std::make_index_sequence<func_traits::num_args> {},\n                                    args...);\n}\n\n} // namespace cafe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/cafe_ppc_interface_invoke_host.h",
    "content": "#pragma once\n#include \"cafe_ppc_interface_params.h\"\n\n#include <libcpu/cpu_control.h>\n\nnamespace cafe\n{\n\nnamespace detail\n{\n\n// Because of the way the host invocations work, some of our functions\n// are no-return but they run through here anyways, causing the compiler\n// to become confused.  We disable those warnings for this section.\n#ifdef _MSC_VER\n#   pragma warning(push)\n#   pragma warning(disable: 4702)\n#endif\n\ntemplate<typename HostFunctionType, HostFunctionType HostFunc, typename FunctionTraitsType, std::size_t... I>\ninline cpu::Core *\ninvoke_host_impl(cpu::Core *core,\n                 FunctionTraitsType &&,\n                 std::index_sequence<I...>)\n{\n   auto param_info = typename FunctionTraitsType::param_info { };\n\n   if constexpr (FunctionTraitsType::has_return_value) {\n      auto return_info = typename FunctionTraitsType::return_info { };\n\n      if constexpr (FunctionTraitsType::is_member_function) {\n         auto obj = readParam(core, typename FunctionTraitsType::object_info { });\n         auto result = (obj.getRawPointer()->*HostFunc)(readParam(core, std::get<I>(param_info))...);\n         core = cpu::this_core::state();\n         writeParam(core, return_info, result);\n      } else {\n         auto result = HostFunc(readParam(core, std::get<I>(param_info))...);\n         core = cpu::this_core::state();\n         writeParam(core, return_info, result);\n      }\n\n      return core;\n   } else {\n      if constexpr (FunctionTraitsType::is_member_function) {\n         auto obj = readParam(core, typename FunctionTraitsType::object_info { });\n         (obj.getRawPointer()->*HostFunc)(readParam(core, std::get<I>(param_info))...);\n      } else {\n         HostFunc(readParam(core, std::get<I>(param_info))...);\n      }\n\n      // We must refresh our Core* as it may have changed during the kernel call\n      return cpu::this_core::state();\n   }\n}\n\n#ifdef _MSC_VER\n#   pragma warning(pop)\n#endif\n\n} // namespace detail\n\n// Invoke a host function from a guest context\ntemplate<typename FunctionType, FunctionType Func>\n[[nodiscard]]\ninline cpu::Core *\ninvoke(cpu::Core *core)\n{\n   using func_traits = detail::function_traits<FunctionType>;\n   return detail::invoke_host_impl<FunctionType, Func>(core,\n                                                       func_traits { },\n                                                       std::make_index_sequence<func_traits::num_args> {});\n}\n\n} // namespace cafe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/cafe_ppc_interface_params.h",
    "content": "#pragma once\n#include \"cafe_ppc_interface.h\"\n\n#include <libcpu/state.h>\n#include <libcpu/functionpointer.h>\n#include <libcpu/be2_val.h>\n\nnamespace cafe::detail\n{\n\ntemplate<auto regIndex>\ninline uint32_t\nreadGpr(cpu::Core* core)\n{\n   if constexpr (regIndex <= 10) {\n      return core->gpr[regIndex];\n   } else {\n      // Args come after the backchain from the caller (8 bytes).\n      auto addr = core->gpr[1] + 8 + 4 * static_cast<uint32_t>(regIndex - 11);\n      return *virt_cast<uint32_t*>(virt_addr { addr });\n   }\n}\n\ntemplate<typename ArgType, RegisterType regType, auto regIndex>\ninline ArgType\nreadParam(cpu::Core* core,\n          param_info_t<ArgType, regType, regIndex>)\n{\n   using ValueType = std::remove_cv_t<ArgType>;\n   if constexpr (regType == RegisterType::Gpr32) {\n      auto value = readGpr<regIndex>(core);\n\n      if constexpr (is_virt_ptr<ValueType>::value) {\n         return virt_cast<typename ArgType::value_type*>(static_cast<virt_addr>(value));\n      } else if constexpr (is_phys_ptr<ValueType>::value) {\n         return phys_cast<typename ArgType::value_type*>(static_cast<phys_addr>(value));\n      } else if constexpr (is_virt_func_ptr<ValueType>::value) {\n         return virt_func_cast<typename ArgType::function_type>(static_cast<virt_addr>(value));\n      } else if constexpr (is_bitfield_type<ValueType>::value) {\n         return ArgType::get(value);\n      } else if constexpr (std::is_same<bool, ValueType>::value) {\n         return !!value;\n      } else {\n         return static_cast<ArgType>(value);\n      }\n   } else if constexpr (regType == RegisterType::Gpr64) {\n      auto hi = static_cast<uint64_t>(readGpr<regIndex>(core)) << 32;\n      auto lo = static_cast<uint64_t>(readGpr<regIndex + 1>(core));\n      return static_cast<ArgType>(hi | lo);\n   } else if constexpr (regType == RegisterType::Fpr) {\n      return static_cast<ArgType>(core->fpr[regIndex].paired0);\n   } else if constexpr (regType == RegisterType::VarArgs) {\n      return var_args { ((regIndex - 3) & 0xFF), (regIndex >> 8) };\n   }\n}\n\ntemplate<typename ArgType, RegisterType regType, auto regIndex>\ninline void\nwriteParam(cpu::Core *core,\n           param_info_t<ArgType, regType, regIndex>,\n           const std::remove_cv_t<ArgType> &value)\n{\n   using ValueType = std::remove_cv_t<ArgType>;\n   static_assert(regType != RegisterType::VarArgs,\n                 \"writeParam not supported with VarArgs\");\n\n   if constexpr (regType == RegisterType::Gpr32) {\n      if constexpr (is_virt_ptr<ValueType>::value) {\n         core->gpr[regIndex] = static_cast<uint32_t>(virt_cast<virt_addr>(value));\n      } else if constexpr (is_phys_ptr<ValueType>::value) {\n         core->gpr[regIndex] = static_cast<uint32_t>(phys_cast<phys_addr>(value));\n      } else if constexpr (is_virt_func_ptr<ValueType>::value) {\n         core->gpr[regIndex] = static_cast<uint32_t>(virt_func_cast<virt_addr>(value));\n      } else if constexpr (is_bitfield_type<ValueType>::value) {\n         core->gpr[regIndex] = static_cast<uint32_t>(value.value);\n      } else if constexpr (std::is_same<bool, ValueType>::value) {\n         core->gpr[regIndex] = static_cast<uint32_t>(value ? 1 : 0);\n      } else {\n         core->gpr[regIndex] = static_cast<uint32_t>(value);\n      }\n   } else if constexpr (regType == RegisterType::Gpr64) {\n      core->gpr[regIndex] = static_cast<uint32_t>((value >> 32) & 0xFFFFFFFF);\n      core->gpr[regIndex + 1] = static_cast<uint32_t>(value & 0xFFFFFFFF);\n   } else if constexpr (regType == RegisterType::Fpr) {\n      core->fpr[regIndex].paired0 = static_cast<double>(value);\n   }\n}\n\ntemplate<typename ArgType, RegisterType regType, auto regIndex>\ninline void\nwriteParam(cpu::Core *core,\n           param_info_t<ArgType, regType, regIndex> p,\n           const be2_val<ArgType> &value)\n{\n   writeParam<ArgType, regType, regIndex>(core, p, value.value());\n}\n\n} // namespace cafe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/cafe_ppc_interface_trace_host.cpp",
    "content": "#include \"cafe_ppc_interface_trace_host.h\"\n\n#include <common/log.h>\n#include <fmt/format.h>\n#include <iterator>\n#include <libcpu/cpu_formatters.h>\n#include <string_view>\n\nnamespace cafe::detail\n{\n\ninline uint32_t\nreadGpr(cpu::Core *core, int regIndex)\n{\n   if (regIndex <= 10) {\n      return core->gpr[regIndex];\n   } else {\n      // Args come after the backchain from the caller (8 bytes).\n      auto addr = core->gpr[1] + 8 + 4 * static_cast<uint32_t>(regIndex - 11);\n      return *virt_cast<uint32_t *>(virt_addr { addr });\n   }\n}\n\nvoid\ninvoke_trace_host_impl(cpu::Core *core, const char *name, bool is_member_function, const RuntimeParamInfo *params, size_t numParams)\n{\n   fmt::memory_buffer message;\n   fmt::format_to(std::back_inserter(message), \"{}(\", name);\n\n   if (is_member_function) {\n      fmt::format_to(std::back_inserter(message),\n         \"this = {}, \", static_cast<virt_addr>(readGpr(core, 3)));\n   }\n\n   for (auto i = 0u; i < numParams; ++i) {\n      auto &p = params[i];\n\n      if (i > 0) {\n         fmt::format_to(std::back_inserter(message), \", \");\n      }\n\n      switch (p.reg_type) {\n      case RegisterType::Gpr32:\n         if (p.is_string) {\n            auto value = readGpr(core, p.reg_index);\n            if (value) {\n               fmt::format_to(std::back_inserter(message),\n                              \"\\\"{}\\\"\", virt_cast<const char *>(static_cast<virt_addr>(value)).get());\n            } else {\n               fmt::format_to(std::back_inserter(message),\n                              \"{}\", static_cast<virt_addr>(value));\n            }\n         } else if (p.is_pointer) {\n            fmt::format_to(std::back_inserter(message),\n                           \"{}\", static_cast<virt_addr>(readGpr(core, p.reg_index)));\n         } else  if (p.is_signed) {\n            fmt::format_to(std::back_inserter(message),\n                           \"{}\", static_cast<int32_t>(readGpr(core, p.reg_index)));\n         } else {\n            fmt::format_to(std::back_inserter(message),\n                           \"{}\", readGpr(core, p.reg_index));\n         }\n         break;\n      case RegisterType::Gpr64:\n      {\n         auto hi = static_cast<uint64_t>(readGpr(core, p.reg_index)) << 32;\n         auto lo = static_cast<uint64_t>(readGpr(core, p.reg_index + 1));\n         if (p.is_signed) {\n            fmt::format_to(std::back_inserter(message),\n                           \"{}\", static_cast<int64_t>(hi | lo));\n         } else {\n            fmt::format_to(std::back_inserter(message),\n                           \"{}\", static_cast<uint64_t>(hi | lo));\n         }\n         break;\n      }\n      case RegisterType::Fpr:\n         fmt::format_to(std::back_inserter(message),\n                        \"{}\", core->fpr[p.reg_index].paired0);\n         break;\n      case RegisterType::VarArgs:\n         fmt::format_to(std::back_inserter(message), \"...\");\n         break;\n      case RegisterType::Void:\n         break;\n      }\n   }\n\n   fmt::format_to(std::back_inserter(message), \") from 0x{:08X}\", core->lr);\n   gLog->debug(std::string_view { message.data(), message.size() });\n}\n\n} // namespace cafe::detail\n"
  },
  {
    "path": "src/libdecaf/src/cafe/cafe_ppc_interface_trace_host.h",
    "content": "#pragma once\n#include \"cafe_ppc_interface.h\"\n\n#include <libcpu/state.h>\n\nnamespace cafe\n{\n\nnamespace detail\n{\n\nvoid\ninvoke_trace_host_impl(cpu::Core *core, const char *name, bool is_member_function, const RuntimeParamInfo *params, size_t numParams);\n\n} // namespace detail\n\n//! Trace log a host function call from a guest context\ntemplate<typename FunctionType>\nvoid\ninvoke_trace(cpu::Core *core,\n             const char *name)\n{\n   using func_traits = detail::function_traits<FunctionType>;\n   invoke_trace_host_impl(core, name, func_traits::is_member_function, func_traits::runtime_param_info.data(), func_traits::runtime_param_info.size());\n}\n\n} // namespace cafe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/cafe_ppc_interface_varargs.h",
    "content": "#pragma once\n#include \"cafe_ppc_interface.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libcpu/cpu_control.h>\n\nnamespace cafe\n{\n\nstruct va_list\n{\n   static constexpr auto NumSavedRegs = 8u;\n\n   class iterator\n   {\n   public:\n      iterator(va_list *list,\n               unsigned gpr,\n               unsigned fpr) :\n         mList(list),\n         mGpr(gpr),\n         mFpr(fpr)\n      {\n      }\n\n      template<typename Type>\n      typename std::enable_if<\n         sizeof(Type) == 4 &&\n         !std::is_floating_point<Type>::value &&\n         !std::is_pointer<Type>::value &&\n         !cpu::is_cpu_pointer<Type>::value\n      , Type>::type\n      next()\n      {\n         return bit_cast<Type>(nextGpr32());\n      }\n\n      template<typename Type>\n      typename std::enable_if<\n         sizeof(Type) == 8 &&\n         !std::is_floating_point<Type>::value &&\n         !std::is_pointer<Type>::value &&\n         !cpu::is_cpu_pointer<Type>::value\n      , Type>::type\n      next()\n      {\n         return bit_cast<Type>(nextGpr64());\n      }\n\n      template<typename Type>\n      typename std::enable_if<\n         cpu::is_cpu_pointer<Type>::value\n      , Type>::type\n      next()\n      {\n         return virt_cast<typename Type::value_type *>(virt_addr { nextGpr32() });\n      }\n\n      template<typename Type>\n      typename std::enable_if<\n         std::is_floating_point<Type>::value\n      , Type>::type\n      next()\n      {\n         return static_cast<Type>(nextFpr());\n      }\n\n   protected:\n      uint32_t nextGpr32()\n      {\n         auto value = uint32_t { 0 };\n         auto index = mGpr;\n\n         if (index < NumSavedRegs) {\n            value = mList->reg_save_area[index];\n         } else {\n            value = mList->overflow_arg_area[index - NumSavedRegs];\n         }\n\n         mGpr++;\n         return value;\n      }\n\n      uint64_t nextGpr64()\n      {\n         // Align gpr to 64 bit\n         if (mGpr % 2) {\n            mGpr++;\n         }\n\n         auto value = static_cast<uint64_t>(nextGpr32()) << 32;\n         value |= nextGpr32();\n         return value;\n      }\n\n      double nextFpr()\n      {\n         auto value = double { 0.0 };\n         auto fpr_save_area = virt_cast<double *>(virt_addrof(mList->reg_save_area[NumSavedRegs]));\n\n         if (mFpr < NumSavedRegs) {\n            value = fpr_save_area[mFpr];\n         } else {\n            decaf_abort(\"How the fuck do we handle va_list with FPR overflow\");\n         }\n\n         mFpr++;\n         return value;\n      }\n\n   private:\n      va_list *mList;\n      unsigned mGpr;\n      unsigned mFpr;\n   };\n\n   iterator begin()\n   {\n      return iterator(this, firstGpr, firstFpr);\n   }\n\n   //! Index of first GPR\n   be2_val<uint8_t> firstGpr;\n\n   //! Index of first FPR\n   be2_val<uint8_t> firstFpr;\n\n   PADDING(2);\n\n   //! Pointer to register values r10+\n   be2_virt_ptr<uint32_t> overflow_arg_area;\n\n   //! Pointer to register values r3...r10 followed by f1...f8 (if saved)\n   be2_virt_ptr<uint32_t> reg_save_area;\n};\nCHECK_OFFSET(va_list, 0x00, firstGpr);\nCHECK_OFFSET(va_list, 0x01, firstFpr);\nCHECK_OFFSET(va_list, 0x04, overflow_arg_area);\nCHECK_OFFSET(va_list, 0x08, reg_save_area);\nCHECK_SIZE(va_list, 0x0C);\n\n/**\n * Structure to help us allocate a va_list on the stack\n */\nstruct stack_va_list\n{\n   //! Actual va_list structure\n   be2_struct<va_list> list;\n\n   //! Padding to 8 byte align\n   PADDING(0x4);\n\n   //! Save area for r3...r10\n   be2_array<uint32_t, 8> gpr_save_area;\n\n   //! Save area for f1...f8\n   be2_array<double, 8> fpr_save_area;\n};\nCHECK_OFFSET(stack_va_list, 0x00, list);\nCHECK_OFFSET(stack_va_list, 0x10, gpr_save_area);\nCHECK_OFFSET(stack_va_list, 0x30, fpr_save_area);\nCHECK_SIZE(stack_va_list, 0x70);\n\nstatic inline virt_ptr<va_list>\nmake_va_list(const var_args &va)\n{\n   auto core = cpu::this_core::state();\n   auto overflow = virt_addr { core->gpr[1] + 8 + 8 };  // +8 for kcstub() adjustment\n\n   // Allocate space on stack, 8 bytes for weird PPC ABI storage\n   core->gpr[1] -= 8 + sizeof(stack_va_list);\n\n   // Setup the va_list structure\n   auto stack_list = virt_cast<stack_va_list *>(virt_addr { core->gpr[1] + 8 });\n   stack_list->list.firstGpr = static_cast<uint8_t>(va.gpr);\n   stack_list->list.firstFpr = static_cast<uint8_t>(va.fpr);\n   stack_list->list.reg_save_area = virt_addrof(stack_list->gpr_save_area[0]);\n   stack_list->list.overflow_arg_area = virt_cast<uint32_t *>(overflow);\n\n   // Save r3...r10\n   for (auto i = 3; i <= 10; ++i) {\n      stack_list->gpr_save_area[i - 3] = core->gpr[i];\n   }\n\n   // Optionally save f1...f8\n   auto saveFloat = !!(core->cr.value & (1 << (31 - 6)));\n\n   if (saveFloat) {\n      for (auto i = 1; i <= 8; ++i) {\n         stack_list->fpr_save_area[i - 1] = core->fpr[i].value;\n      }\n   }\n\n   return virt_addrof(stack_list->list);\n}\n\nstatic inline void\nfree_va_list(virt_ptr<va_list> list)\n{\n   auto core = cpu::this_core::state();\n   core->gpr[1] += 8 + sizeof(stack_va_list);\n}\n\n} // namespace cafe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/cafe_stackobject.h",
    "content": "#pragma once\n#include <algorithm>\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <libcpu/cpu_control.h>\n#include <libcpu/be2_struct.h>\n#include <memory>\n#include <string_view>\n\nnamespace cafe\n{\n\n// #define DECAF_CHECK_STACK_CAFE_OBJECTS\n\ntemplate<typename Type, size_t NumElements = 1, size_t Alignment = alignof(Type)>\nclass StackObject : public virt_ptr<Type>\n{\n   static constexpr auto\n   StackAlignment = align_up(Alignment, 4u);\n\n   static constexpr auto\n   StackSize = align_up(static_cast<uint32_t>(sizeof(Type) * NumElements),\n                        StackAlignment);\n\n   virt_addr mRestoreStackAddress;\n\npublic:\n   StackObject()\n   {\n      auto core = cpu::this_core::state();\n\n      // Adjust stack\n      mRestoreStackAddress = virt_addr { core->gpr[1] };\n      auto address = align_down(mRestoreStackAddress - StackSize, StackAlignment);\n      core->gpr[1] = static_cast<uint32_t>(address);\n\n      // Initialise object\n      virt_ptr<Type>::mAddress = address;\n      std::uninitialized_default_construct_n(virt_ptr<Type>::get(),\n                                             NumElements);\n   }\n\n   StackObject(const Type &value) :\n      StackObject()\n   {\n      std::memcpy(virt_ptr<Type>::get(), &value, sizeof(Type));\n   }\n\n   ~StackObject()\n   {\n      auto core = cpu::this_core::state();\n\n      // Destroy object\n      std::destroy_n(virt_ptr<Type>::get(), NumElements);\n\n      // Restore stack\n      core->gpr[1] = static_cast<uint32_t>(mRestoreStackAddress);\n\n#ifdef DECAF_CHECK_STACK_CAFE_OBJECTS\n      decaf_check(virt_ptr<Type>::mAddress == oldStackTop);\n#endif\n   }\n\n   // Disable copy\n   StackObject(const StackObject &) = delete;\n   StackObject &operator=(const StackObject&) = delete;\n\n   // Disable move\n   StackObject(StackObject &&) noexcept = delete;\n   StackObject &operator=(StackObject&&) noexcept = delete;\n};\n\ntemplate<typename Type, size_t NumElements, size_t BaseAlignment = alignof(Type)>\nclass StackArray : public StackObject<Type, NumElements, BaseAlignment>\n{\npublic:\n   StackArray()\n   {\n   }\n\n   StackArray(const Type (&values)[NumElements])\n   {\n      std::memcpy(virt_ptr<Type>::get(), &values, sizeof(Type) * NumElements);\n   }\n\n   // Disable copy\n   StackArray(const StackArray &) = delete;\n   StackArray &operator=(const StackArray &) = delete;\n\n   // Disable move\n   StackArray(StackArray &&) noexcept = delete;\n   StackArray &operator=(StackArray &&) noexcept = delete;\n\n   constexpr uint32_t size() const\n   {\n      return NumElements;\n   }\n\n   constexpr auto &operator[](std::size_t index)\n   {\n      return virt_ptr<Type>::get()[index];\n   }\n\n   constexpr const auto &operator[](std::size_t index) const\n   {\n      return virt_ptr<Type>::get()[index];\n   }\n};\n\nclass StackString : public virt_ptr<char>\n{\npublic:\n   StackString(std::string_view hostString) :\n      mSize(align_up(static_cast<uint32_t>(hostString.size()) + 1, 4))\n   {\n      auto core = cpu::this_core::state();\n\n      // Adjust stack\n      auto oldStackTop = virt_addr { core->gpr[1] };\n      auto newStackTop = oldStackTop - mSize;\n      core->gpr[1] = newStackTop.getAddress();\n\n      // Initialise string\n      virt_ptr<char>::mAddress = virt_addr { newStackTop };\n      std::memcpy(virt_ptr<char>::get(), hostString.data(), hostString.size());\n      virt_ptr<char>::get()[hostString.size()] = char { 0 };\n   }\n\n   ~StackString()\n   {\n      if (mSize) {\n         auto core = cpu::this_core::state();\n\n         // Adjust stack\n         auto oldStackTop = virt_addr { core->gpr[1] };\n         auto newStackTop = oldStackTop + mSize;\n         core->gpr[1] = newStackTop.getAddress();\n\n#ifdef DECAF_CHECK_STACK_CAFE_OBJECTS\n         decaf_check(virt_ptr<char>::mAddress == oldStackTop);\n#endif\n      }\n   }\n\n   // Disable copy\n   StackString(const StackString &) = delete;\n   StackString &operator=(const StackString &) = delete;\n\n   StackString(StackString &&from) :\n      mSize(from.mSize)\n   {\n      from.mSize = 0u;\n      virt_ptr<char>::mAddress = from.mAddress;\n   }\n\n   StackString &operator =(StackString &&from)\n   {\n      mSize = from.mSize;\n      virt_ptr<char>::mAddress = from.mAddress;\n      from.mSize = 0u;\n      return *this;\n   }\n\nprivate:\n   uint32_t mSize;\n};\n\ninline StackString\nmake_stack_string(std::string_view str)\n{\n   return { str };\n}\n\n} // namespace cafe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/cafe_tinyheap.cpp",
    "content": "#include \"cafe_tinyheap.h\"\n\n#include <common/align.h>\n#include <common/log.h>\n#include <cstring>\n#include <libcpu/be2_struct.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe\n{\n\ntemplate<typename AddressType>\nstruct TrackingBlockBase\n{\n   template<typename ValueType>\n   using be2_pointer_type = be2_val<cpu::Pointer<ValueType, AddressType>>;\n\n   //! Pointer to the data heap for this block\n   be2_pointer_type<void> data;\n\n   //! Size is negative when unallocated, positive when allocated.\n   be2_val<int32_t> size;\n\n   //! Index of next block\n   be2_val<int32_t> prevBlockIdx;\n\n   //! Index of previous block\n   be2_val<int32_t> nextBlockIdx;\n};\nCHECK_OFFSET(TrackingBlockBase<virt_addr>, 0x00, data);\nCHECK_OFFSET(TrackingBlockBase<virt_addr>, 0x04, size);\nCHECK_OFFSET(TrackingBlockBase<virt_addr>, 0x08, prevBlockIdx);\nCHECK_OFFSET(TrackingBlockBase<virt_addr>, 0x0C, nextBlockIdx);\nCHECK_SIZE(TrackingBlockBase<virt_addr>, 0x10);\n\nusing TrackingBlockVirtual = TrackingBlockBase<virt_addr>;\nusing TrackingBlockPhysical = TrackingBlockBase<phys_addr>;\nusing TrackingBlock = TrackingBlockVirtual;\n\nstatic virt_ptr<TrackingBlockVirtual>\ngetTrackingBlocks(virt_ptr<TinyHeapVirtual> heap)\n{\n   return virt_cast<TrackingBlockVirtual *>(virt_cast<virt_addr>(heap) + sizeof(TinyHeapVirtual));\n}\n\nstatic phys_ptr<TrackingBlockPhysical>\ngetTrackingBlocks(phys_ptr<TinyHeapPhysical> heap)\n{\n   return phys_cast<TrackingBlockPhysical *>(phys_cast<phys_addr>(heap) + sizeof(TinyHeapPhysical));\n}\n\n// TODO: Make this pointer_cast in cpu headers?\ntemplate<typename Type, typename SrcType>\nstatic auto\npointer_cast(virt_ptr<SrcType> src)\n{\n   return virt_cast<Type>(src);\n}\n\ntemplate<typename Type, typename SrcType>\nstatic auto\npointer_cast(be2_virt_ptr<SrcType> src)\n{\n   return virt_cast<Type>(src);\n}\n\ntemplate<typename Type, typename SrcType>\nstatic auto\npointer_cast(phys_ptr<SrcType> src)\n{\n   return phys_cast<Type>(src);\n}\n\ntemplate<typename Type, typename SrcType>\nstatic auto\npointer_cast(be2_phys_ptr<SrcType> src)\n{\n   return phys_cast<Type>(src);\n}\n\ntemplate<typename SrcType>\nstatic virt_addr\npointer_to_address(virt_ptr<SrcType> src)\n{\n   return virt_cast<virt_addr>(src);\n}\n\ntemplate<typename SrcType>\nstatic virt_addr\npointer_to_address(be2_virt_ptr<SrcType> src)\n{\n   return virt_cast<virt_addr>(src);\n}\n\ntemplate<typename SrcType>\nstatic phys_addr\npointer_to_address(phys_ptr<SrcType> src)\n{\n   return phys_cast<phys_addr>(src);\n}\n\ntemplate<typename SrcType>\nstatic phys_addr\npointer_to_address(be2_phys_ptr<SrcType> src)\n{\n   return phys_cast<phys_addr>(src);\n}\n\ntemplate<typename AddressType, typename SrcType>\nstatic auto\npointer_addrof(SrcType &src)\n{\n   if constexpr (std::is_same<AddressType, virt_addr>::value) {\n      return virt_addrof(src);\n   } else {\n      return phys_addrof(src);\n   }\n}\n\ntemplate<typename HeapPointer>\nstatic void\ndumpHeap(HeapPointer heap)\n{\n   auto idx = heap->firstBlockIdx;\n   auto blocks = getTrackingBlocks(heap);\n\n   gLog->debug(\"Heap at {} - {}\", heap->dataHeapStart, heap->dataHeapEnd);\n   while (idx != -1) {\n      auto addr = pointer_to_address(blocks[idx].data);\n      auto size = blocks[idx].size;\n      if (size < 0) {\n         size = -size;\n      }\n\n      gLog->debug(\"block {} - {} {}\",\n                  addr,\n                  addr + size,\n                  blocks[idx].size < 0 ? \"free\" : \"alloc\");\n      idx = blocks[idx].nextBlockIdx;\n   }\n}\n\ntemplate<typename AddressType>\nstatic int32_t\nfindBlockIdxContaining(cpu::Pointer<TinyHeapBase<AddressType>, AddressType> heap,\n                       cpu::Pointer<void, AddressType> ptr)\n{\n   if (!heap || !ptr || ptr < heap->dataHeapStart || ptr >= heap->dataHeapEnd) {\n      return -1;\n   }\n\n   auto trackingBlocks = getTrackingBlocks(heap);\n   auto distFromStart = pointer_to_address(ptr) - pointer_to_address(heap->dataHeapStart);\n   auto distFromEnd = pointer_to_address(heap->dataHeapEnd) - pointer_to_address(ptr);\n\n   if (distFromStart < distFromEnd) {\n      // Search forwards from start\n      auto idx = heap->firstBlockIdx;\n      while (idx >= 0) {\n         auto &block = trackingBlocks[idx];\n         auto blockStart = pointer_cast<uint8_t *>(block.data);\n         auto blockEnd = blockStart + (block.size >= 0 ? +block.size : -block.size);\n\n         if (ptr >= blockStart && ptr < blockEnd) {\n            return idx;\n         }\n\n         idx = block.nextBlockIdx;\n      }\n   } else {\n      // Search backwards from end\n      auto idx = heap->lastBlockIdx;\n      while (idx >= 0) {\n         auto &block = trackingBlocks[idx];\n         auto blockStart = pointer_cast<uint8_t *>(block.data);\n         auto blockEnd = blockStart + (block.size >= 0 ? +block.size : -block.size);\n\n         if (ptr >= blockStart && ptr < blockEnd) {\n            return idx;\n         }\n\n         idx = block.prevBlockIdx;\n      }\n   }\n\n   return -1;\n}\n\ntemplate<typename AddressType>\nTinyHeapError\nTinyHeap_Setup(cpu::Pointer<TinyHeapBase<AddressType>, AddressType> heap,\n               int32_t trackingHeapSize,\n               cpu::Pointer<void, AddressType> dataHeap,\n               int32_t dataHeapSize)\n{\n   if (trackingHeapSize < 64 || dataHeapSize <= 0) {\n      return TinyHeapError::SetupFailed;\n   }\n\n   auto numTrackingBlocks = (trackingHeapSize - 0x30) / 16;\n   if (numTrackingBlocks <= 0) {\n      return TinyHeapError::SetupFailed;\n   }\n\n   auto trackingBlocks = getTrackingBlocks(heap);\n   std::memset(trackingBlocks.get(), 0, numTrackingBlocks * sizeof(TrackingBlock));\n\n   for (auto i = 1; i < numTrackingBlocks; ++i) {\n      trackingBlocks[i].prevBlockIdx = i - 1;\n      trackingBlocks[i].nextBlockIdx = i + 1;\n   }\n\n   trackingBlocks[1].prevBlockIdx = -1;\n   trackingBlocks[numTrackingBlocks - 1].nextBlockIdx = -1;\n\n   trackingBlocks[0].data = dataHeap;\n   trackingBlocks[0].size = -dataHeapSize;\n   trackingBlocks[0].prevBlockIdx = -1;\n   trackingBlocks[0].nextBlockIdx = -1;\n\n   heap->dataHeapStart = dataHeap;\n   heap->dataHeapEnd = pointer_cast<uint8_t *>(dataHeap) + dataHeapSize;\n   heap->firstBlockIdx = 0;\n   heap->lastBlockIdx = 0;\n   heap->nextFreeBlockIdx = 1;\n   return TinyHeapError::OK;\n}\n\ntemplate<typename AddressType>\nstatic TinyHeapError\nallocInBlock(cpu::Pointer<TinyHeapBase<AddressType>, AddressType> heap,\n             int32_t blockIdx,\n             int32_t beforeOffset,\n             int32_t holeBeforeIdx,\n             int32_t holeAfterIdx,\n             int32_t size)\n{\n   auto trackingBlocks = getTrackingBlocks(heap);\n   auto &block = trackingBlocks[blockIdx];\n\n   // Check if we need to create a hole before the allocation\n   if (beforeOffset != 0) {\n      auto &beforeBlock = trackingBlocks[holeBeforeIdx];\n      beforeBlock.data = block.data;\n      beforeBlock.size = -beforeOffset;\n      beforeBlock.prevBlockIdx = block.prevBlockIdx;\n      beforeBlock.nextBlockIdx = blockIdx;\n\n      if (beforeBlock.prevBlockIdx >= 0) {\n         trackingBlocks[beforeBlock.prevBlockIdx].nextBlockIdx = holeBeforeIdx;\n      } else {\n         heap->firstBlockIdx = holeBeforeIdx;\n      }\n\n      block.data = pointer_cast<uint8_t *>(block.data) + beforeOffset;\n      block.size += beforeOffset; // += because block.size is negative at this point\n      block.prevBlockIdx = holeBeforeIdx;\n   }\n\n   // Mark the block as allocated by flipping sign of size\n   block.size = -block.size;\n\n   // Check if we need to create a hole after the allocation\n   if (block.size != size) {\n      auto &afterBlock = trackingBlocks[holeAfterIdx];\n      afterBlock.data = pointer_cast<uint8_t *>(block.data) + size;\n      afterBlock.size = -(block.size - size);\n      afterBlock.prevBlockIdx = blockIdx;\n      afterBlock.nextBlockIdx = block.nextBlockIdx;\n\n      if (afterBlock.nextBlockIdx >= 0) {\n         trackingBlocks[afterBlock.nextBlockIdx].prevBlockIdx = holeAfterIdx;\n      } else {\n         heap->lastBlockIdx = holeAfterIdx;\n      }\n\n      block.size = size;\n      block.nextBlockIdx = holeAfterIdx;\n   }\n\n   return TinyHeapError::OK;\n}\n\ntemplate<typename AddressType>\nstatic void\nfreeBlock(cpu::Pointer<TinyHeapBase<AddressType>, AddressType> heap,\n          int32_t blockIdx)\n{\n   auto trackingBlocks = getTrackingBlocks(heap);\n   auto block = pointer_addrof<AddressType>(trackingBlocks[blockIdx]);\n\n   // Mark the block as unallocated\n   block->size = -block->size;\n\n   if (block->prevBlockIdx >= 0) {\n      auto prevBlockIdx = block->prevBlockIdx;\n      auto prevBlock = pointer_addrof<AddressType>(trackingBlocks[prevBlockIdx]);\n      if (prevBlock->size < 0) {\n         // Merge block into prevBlock!\n         prevBlock->size += block->size;\n         prevBlock->nextBlockIdx = block->nextBlockIdx;\n\n         if (prevBlock->nextBlockIdx >= 0) {\n            trackingBlocks[prevBlock->nextBlockIdx].prevBlockIdx = prevBlockIdx;\n         } else {\n            heap->lastBlockIdx = prevBlockIdx;\n         }\n\n         // Insert block at start of free list\n         block->data = nullptr;\n         block->size = 0;\n         block->prevBlockIdx = -1;\n         block->nextBlockIdx = heap->nextFreeBlockIdx;\n\n         if (heap->nextFreeBlockIdx >= 0) {\n            trackingBlocks[heap->nextFreeBlockIdx].prevBlockIdx = blockIdx;\n         }\n\n         heap->nextFreeBlockIdx = blockIdx;\n\n         // Set block to prevBlock so we can maybe merge with nextBlock\n         block = prevBlock;\n         blockIdx = prevBlockIdx;\n      }\n   }\n\n   if (block->nextBlockIdx >= 0) {\n      auto nextBlockIdx = block->nextBlockIdx;\n      auto nextBlock = pointer_addrof<AddressType>(trackingBlocks[nextBlockIdx]);\n      if (nextBlock->size < 0) {\n         // Merge nextBlock into block!\n         block->size += nextBlock->size;\n         block->nextBlockIdx = nextBlock->nextBlockIdx;\n\n         if (block->nextBlockIdx >= 0) {\n            trackingBlocks[block->nextBlockIdx].prevBlockIdx = blockIdx;\n         } else {\n            heap->lastBlockIdx = blockIdx;\n         }\n\n         // Insert nextBlock at start of free list\n         nextBlock->data = nullptr;\n         nextBlock->size = 0;\n         nextBlock->prevBlockIdx = -1;\n         nextBlock->nextBlockIdx = heap->nextFreeBlockIdx;\n\n         if (heap->nextFreeBlockIdx >= 0) {\n            trackingBlocks[heap->nextFreeBlockIdx].prevBlockIdx = nextBlockIdx;\n         }\n\n         heap->nextFreeBlockIdx = nextBlockIdx;\n      }\n   }\n}\n\nTinyHeapError\nTinyHeap_Setup(virt_ptr<TinyHeapVirtual> heap,\n               int32_t trackingHeapSize,\n               virt_ptr<void> dataHeap,\n               int32_t dataHeapSize)\n{\n   return TinyHeap_Setup<virt_addr>(heap, trackingHeapSize, dataHeap, dataHeapSize);\n}\n\nTinyHeapError\nTinyHeap_Setup(phys_ptr<TinyHeapPhysical> heap,\n               int32_t trackingHeapSize,\n               phys_ptr<void> dataHeap,\n               int32_t dataHeapSize)\n{\n   return TinyHeap_Setup<phys_addr>(heap, trackingHeapSize, dataHeap, dataHeapSize);\n}\n\n\ntemplate<typename AddressType>\nTinyHeapError\nTinyHeap_Alloc(cpu::Pointer<TinyHeapBase<AddressType>, AddressType> heap,\n               int32_t size,\n               int32_t align,\n               cpu::Pointer<void, AddressType>  *outPtr)\n{\n   auto fromFront = true;\n   if (!heap) {\n      return TinyHeapError::InvalidHeap;\n   }\n\n   *outPtr = nullptr;\n   if (size <= 0) {\n      return TinyHeapError::OK;\n   }\n\n   auto trackingBlocks = getTrackingBlocks(heap);\n   auto blockIdx = -1;\n\n   if (align < 0) {\n      align = -align;\n      fromFront = false;\n   }\n\n   if (fromFront) {\n      // Search forwards from first\n      auto idx = heap->firstBlockIdx;\n\n      while (idx >= 0) {\n         auto &block = trackingBlocks[idx];\n         if (block.size < 0) {\n            auto blockStart = pointer_to_address(block.data);\n            auto blockEnd = blockStart - block.size;\n            auto alignedStart = align_up(blockStart, align);\n\n            if (alignedStart + size <= blockEnd) {\n               blockIdx = idx;\n               break;\n            }\n         }\n\n         idx = block.nextBlockIdx;\n      }\n   } else {\n      // Search backwards from last\n      auto idx = heap->lastBlockIdx;\n\n      while (idx >= 0) {\n         auto &block = trackingBlocks[idx];\n         if (block.size < 0) {\n            auto blockStart = pointer_to_address(block.data);\n            auto blockEnd = blockStart - block.size;\n            auto alignedStart = align_up(blockStart, align);\n\n            if (alignedStart + size <= blockEnd) {\n               blockIdx = idx;\n               break;\n            }\n         }\n\n         idx = block.prevBlockIdx;\n      }\n   }\n\n   if (blockIdx < 0) {\n      // No blocks to fit this allocation in\n      return TinyHeapError::AllocFailed;\n   }\n\n   auto &block = trackingBlocks[blockIdx];\n   auto blockStart = pointer_to_address(block.data);\n   auto blockEnd = blockStart - block.size;\n\n   auto alignedStart = fromFront ?\n      align_up(blockStart, align) :\n      align_down(blockEnd - size, align);\n   auto beforeOffset = static_cast<int32_t>(alignedStart - blockStart);\n   auto afterOffset = blockEnd - (alignedStart + size);\n\n   // Check if we need to insert a block before allocated block\n   auto holeBeforeIdx = -1;\n   if (beforeOffset != 0) {\n      holeBeforeIdx = heap->nextFreeBlockIdx;\n      if (holeBeforeIdx < 0) {\n         // No free block to use to punch a hole\n         return TinyHeapError::AllocFailed;\n      }\n\n      heap->nextFreeBlockIdx = trackingBlocks[holeBeforeIdx].nextBlockIdx;\n   }\n\n   // Check if we need to insert a block after allocated block\n   auto holeAfterIdx = -1;\n   if (afterOffset != 0) {\n      holeAfterIdx = heap->nextFreeBlockIdx;\n      if (holeAfterIdx < 0) {\n         // No free block to use to punch a hole\n         if (holeBeforeIdx >= 0) {\n            // Restore holeBeforeIdx\n            heap->nextFreeBlockIdx = holeBeforeIdx;\n         }\n\n         return TinyHeapError::AllocFailed;\n      }\n\n      heap->nextFreeBlockIdx = trackingBlocks[holeAfterIdx].nextBlockIdx;\n   }\n\n   auto error = allocInBlock(heap, blockIdx, beforeOffset, holeBeforeIdx, holeAfterIdx, size);\n   *outPtr = block.data;\n   return error;\n}\n\nTinyHeapError\nTinyHeap_Alloc(virt_ptr<TinyHeapVirtual> heap,\n               int32_t size,\n               int32_t align,\n               virt_ptr<void> *outPtr)\n{\n   return TinyHeap_Alloc<virt_addr>(heap, size, align, outPtr);\n}\n\nTinyHeapError\nTinyHeap_Alloc(phys_ptr<TinyHeapPhysical> heap,\n               int32_t size,\n               int32_t align,\n               phys_ptr<void> *outPtr)\n{\n   return TinyHeap_Alloc<phys_addr>(heap, size, align, outPtr);\n}\n\n\ntemplate<typename AddressType>\nTinyHeapError\nTinyHeap_AllocAt(cpu::Pointer<TinyHeapBase<AddressType>, AddressType> heap,\n                 cpu::Pointer<void, AddressType> ptr,\n                 int32_t size)\n{\n   auto blockIdx = findBlockIdxContaining(heap, ptr);\n   if (blockIdx < 0) {\n      // Address not in heap\n      return TinyHeapError::InvalidHeap;\n   }\n\n   auto trackingBlocks = getTrackingBlocks(heap);\n   auto &block = trackingBlocks[blockIdx];\n   if (block.size > 0) {\n      // Already allocated\n      return TinyHeapError::AllocAtFailed;\n   }\n\n   auto beforeOffset =\n      static_cast<int32_t>(pointer_cast<uint8_t *>(ptr) -\n                           pointer_cast<uint8_t *>(block.data));\n\n   auto afterOffset =\n      (pointer_cast<uint8_t *>(ptr) + -block.size) -\n      (pointer_cast<uint8_t *>(block.data) + size);\n\n   if (afterOffset < 0) {\n      // Not enough space\n      return TinyHeapError::AllocAtFailed;\n   }\n\n   // Check if we need to insert a block before allocated block\n   auto holeBeforeIdx = -1;\n   if (beforeOffset != 0) {\n      holeBeforeIdx = heap->nextFreeBlockIdx;\n      if (holeBeforeIdx < 0) {\n         // No free block to use to punch a hole\n         return TinyHeapError::AllocAtFailed;\n      }\n\n      heap->nextFreeBlockIdx = trackingBlocks[holeBeforeIdx].nextBlockIdx;\n   }\n\n   // Check if we need to insert a block after allocated block\n   auto holeAfterIdx = -1;\n   if (afterOffset != 0) {\n      holeAfterIdx = heap->nextFreeBlockIdx;\n      if (holeAfterIdx < 0) {\n         // No free block to use to punch a hole\n         if (holeBeforeIdx >= 0) {\n            // Restore holeBeforeIdx\n            heap->nextFreeBlockIdx = holeBeforeIdx;\n         }\n\n         return TinyHeapError::AllocAtFailed;\n      }\n\n      heap->nextFreeBlockIdx = trackingBlocks[holeAfterIdx].nextBlockIdx;\n   }\n\n   return allocInBlock(heap, blockIdx, beforeOffset, holeBeforeIdx,\n                       holeAfterIdx, size);\n}\n\nTinyHeapError\nTinyHeap_AllocAt(virt_ptr<TinyHeapVirtual> heap,\n                 virt_ptr<void> ptr,\n                 int32_t size)\n{\n   return TinyHeap_AllocAt<virt_addr>(heap, ptr, size);\n}\n\n\nTinyHeapError\nTinyHeap_AllocAt(phys_ptr<TinyHeapPhysical> heap,\n                 phys_ptr<void> ptr,\n                 int32_t size)\n{\n   return TinyHeap_AllocAt<phys_addr>(heap, ptr, size);\n}\n\n\ntemplate<typename AddressType>\nstatic void\nTinyHeap_Free(cpu::Pointer<TinyHeapBase<AddressType>, AddressType> heap,\n              cpu::Pointer<void, AddressType> ptr)\n{\n   auto blockIdx = findBlockIdxContaining(heap, ptr);\n   if (blockIdx < 0) {\n      // Address not in heap\n      return;\n   }\n\n   auto trackingBlocks = getTrackingBlocks(heap);\n   auto &block = trackingBlocks[blockIdx];\n   if (block.data != ptr) {\n      // Can only free whole blocks\n      return;\n   }\n\n   if (block.size < 0) {\n      // Already free\n      return;\n   }\n\n   freeBlock(heap, blockIdx);\n}\n\nvoid\nTinyHeap_Free(virt_ptr<TinyHeapVirtual> heap,\n              virt_ptr<void> ptr)\n{\n   TinyHeap_Free<virt_addr>(heap, ptr);\n}\n\nvoid\nTinyHeap_Free(phys_ptr<TinyHeapPhysical> heap,\n              phys_ptr<void> ptr)\n{\n   TinyHeap_Free<phys_addr>(heap, ptr);\n}\n\n\ntemplate<typename AddressType>\nstatic int32_t\nTinyHeap_GetLargestFree(cpu::Pointer<TinyHeapBase<AddressType>, AddressType> heap)\n{\n   if (!heap) {\n      return 0;\n   }\n\n   if (heap->firstBlockIdx == -1) {\n      return 0;\n   }\n\n   auto trackingBlocks = getTrackingBlocks(heap);\n   auto idx = heap->firstBlockIdx;\n   auto largestFree = 0;\n\n   while (idx >= 0) {\n      auto &block = trackingBlocks[idx];\n      if (block.size < 0) {\n         largestFree = std::max(largestFree, -block.size);\n      }\n\n      idx = block.nextBlockIdx;\n   }\n\n   return largestFree;\n}\n\nint32_t\nTinyHeap_GetLargestFree(virt_ptr<TinyHeapVirtual> heap)\n{\n   return TinyHeap_GetLargestFree<virt_addr>(heap);\n}\n\nint32_t\nTinyHeap_GetLargestFree(phys_ptr<TinyHeapPhysical> heap)\n{\n   return TinyHeap_GetLargestFree<phys_addr>(heap);\n}\n\n\ntemplate<typename AddressType>\nstatic cpu::Pointer<void, AddressType>\nTinyHeap_Enum(cpu::Pointer<TinyHeapBase<AddressType>, AddressType> heap,\n              cpu::Pointer<void, AddressType> prevBlockPtr,\n              cpu::Pointer<void, AddressType> *outPtr,\n              uint32_t *outSize)\n{\n   if (!heap) {\n      return nullptr;\n   }\n\n   auto blocks = getTrackingBlocks(heap);\n   auto block = blocks + heap->firstBlockIdx;\n\n   if (prevBlockPtr) {\n      // Iterate through list to make sure prevBlockPtr is actually in it.\n      auto prevBlock = pointer_cast<TrackingBlock *>(prevBlockPtr);\n      while (block != prevBlock) {\n         if (block->nextBlockIdx == -1) {\n            goto error;\n         }\n\n         block = blocks + block->nextBlockIdx;\n      }\n\n      // Now we're at prevBlock, let's go to the next block\n      if (block->nextBlockIdx == -1) {\n         goto error;\n      }\n\n      block = blocks + block->nextBlockIdx;\n   }\n\n   if (block) {\n      // Find the next allocated block\n      while (block->size <= 0) {\n         if (block->nextBlockIdx == -1) {\n            goto error;\n         }\n\n         block = blocks + block->nextBlockIdx;\n      }\n\n      if (outPtr) {\n         *outPtr = block->data;\n      }\n\n      if (outSize) {\n         *outSize = static_cast<uint32_t>(block->size);\n      }\n\n      return block;\n   }\n\nerror:\n   if (outPtr) {\n      *outPtr = nullptr;\n   }\n\n   if (outSize) {\n      *outSize = 0;\n   }\n\n   return nullptr;\n}\n\nvirt_ptr<void>\nTinyHeap_Enum(virt_ptr<TinyHeap> heap,\n              virt_ptr<void> prevBlockPtr,\n              virt_ptr<void> *outPtr,\n              uint32_t *outSize)\n{\n   return TinyHeap_Enum<virt_addr>(heap, prevBlockPtr, outPtr, outSize);\n}\n\nphys_ptr<void>\nTinyHeap_Enum(phys_ptr<TinyHeapBase<phys_addr>> heap,\n              phys_ptr<void> prevBlockPtr,\n              phys_ptr<void> *outPtr,\n              uint32_t *outSize)\n{\n   return TinyHeap_Enum<phys_addr>(heap, prevBlockPtr, outPtr, outSize);\n}\n\n\ntemplate<typename AddressType>\nstatic cpu::Pointer<void, AddressType>\nTinyHeap_EnumFree(cpu::Pointer<TinyHeapBase<AddressType>, AddressType> heap,\n                  cpu::Pointer<void, AddressType> prevBlockPtr,\n                  cpu::Pointer<void, AddressType> *outPtr,\n                  uint32_t *outSize)\n{\n   if (!heap) {\n      return nullptr;\n   }\n\n   auto blocks = getTrackingBlocks(heap);\n   auto block = blocks + heap->firstBlockIdx;\n\n   if (prevBlockPtr) {\n      // Iterate through list to make sure prevBlockPtr is actually in it.\n      auto prevBlock = pointer_cast<TrackingBlockBase<AddressType> *>(prevBlockPtr);\n      while (block != prevBlock) {\n         if (block->nextBlockIdx == -1) {\n            goto error;\n         }\n\n         block = blocks + block->nextBlockIdx;\n      }\n\n      // Now we're at prevBlock, let's go to the next block\n      if (block->nextBlockIdx == -1) {\n         goto error;\n      }\n\n      block = blocks + block->nextBlockIdx;\n   }\n\n   if (block) {\n      // Find the next free block\n      while (block->size >= 0) {\n         if (block->nextBlockIdx == -1) {\n            goto error;\n         }\n\n         block = blocks + block->nextBlockIdx;\n      }\n\n      if (outPtr) {\n         *outPtr = block->data;\n      }\n\n      if (outSize) {\n         *outSize = static_cast<uint32_t>(-block->size);\n      }\n\n      return block;\n   }\n\nerror:\n   if (outPtr) {\n      *outPtr = nullptr;\n   }\n\n   if (outSize) {\n      *outSize = 0;\n   }\n\n   return nullptr;\n}\n\nvirt_ptr<void>\nTinyHeap_EnumFree(virt_ptr<TinyHeap> heap,\n                  virt_ptr<void> prevBlockPtr,\n                  virt_ptr<void> *outPtr,\n                  uint32_t *outSize)\n{\n   return TinyHeap_EnumFree<virt_addr>(heap, prevBlockPtr, outPtr, outSize);\n}\n\nphys_ptr<void>\nTinyHeap_EnumFree(phys_ptr<TinyHeapBase<phys_addr>> heap,\n                  phys_ptr<void> prevBlockPtr,\n                  phys_ptr<void> *outPtr,\n                  uint32_t *outSize)\n{\n   return TinyHeap_EnumFree<phys_addr>(heap, prevBlockPtr, outPtr, outSize);\n}\n\n} // namespace cafe::tinyheap\n"
  },
  {
    "path": "src/libdecaf/src/cafe/cafe_tinyheap.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe\n{\n\nconstexpr auto TinyHeapHeaderSize = 0x30;\nconstexpr auto TinyHeapBlockSize = 16;\n\nenum class TinyHeapError\n{\n   OK = 0,\n   SetupFailed = -520001,\n   InvalidHeap = -520002,\n   AllocAtFailed = -520003,\n   AllocFailed = -520004,\n};\n\ntemplate<typename AddressType>\nstruct TinyHeapBase\n{\n   template<typename ValueType>\n   using be2_pointer_type = be2_val<cpu::Pointer<ValueType, AddressType>>;\n\n   //! Pointer to the start of the data heap\n   be2_pointer_type<void> dataHeapStart;\n\n   //! Pointer to the end of the data heap\n   be2_pointer_type<void> dataHeapEnd;\n\n   //! Index of first tracking block\n   be2_val<int32_t> firstBlockIdx;\n\n   //! Index of last tracking block\n   be2_val<int32_t> lastBlockIdx;\n\n   //! Index of first unused tracking block\n   be2_val<int32_t> nextFreeBlockIdx;\n};\nCHECK_OFFSET(TinyHeapBase<virt_addr>, 0x00, dataHeapStart);\nCHECK_OFFSET(TinyHeapBase<virt_addr>, 0x04, dataHeapEnd);\nCHECK_OFFSET(TinyHeapBase<virt_addr>, 0x08, firstBlockIdx);\nCHECK_OFFSET(TinyHeapBase<virt_addr>, 0x0C, lastBlockIdx);\nCHECK_OFFSET(TinyHeapBase<virt_addr>, 0x10, nextFreeBlockIdx);\nCHECK_SIZE(TinyHeapBase<virt_addr>, 0x14);\n\nusing TinyHeapVirtual = TinyHeapBase<virt_addr>;\nusing TinyHeapPhysical = TinyHeapBase<phys_addr>;\nusing TinyHeap = TinyHeapVirtual;\n\n// TinyHeap virtual\nTinyHeapError\nTinyHeap_Setup(virt_ptr<TinyHeapVirtual> heap,\n               int32_t trackingHeapSize,\n               virt_ptr<void> dataHeap,\n               int32_t dataHeapSize);\n\nTinyHeapError\nTinyHeap_Alloc(virt_ptr<TinyHeapVirtual> heap,\n               int32_t size,\n               int32_t align,\n               virt_ptr<void> *outPtr);\n\nTinyHeapError\nTinyHeap_AllocAt(virt_ptr<TinyHeapVirtual> heap,\n                 virt_ptr<void> ptr,\n                 int32_t size);\n\nvoid\nTinyHeap_Free(virt_ptr<TinyHeapVirtual> heap,\n              virt_ptr<void> ptr);\n\nint32_t\nTinyHeap_GetLargestFree(virt_ptr<TinyHeapVirtual> heap);\n\nvirt_ptr<void>\nTinyHeap_Enum(virt_ptr<TinyHeapVirtual> heap,\n              virt_ptr<void> prevBlockPtr,\n              virt_ptr<void> *outPtr,\n              uint32_t *outSize);\n\nvirt_ptr<void>\nTinyHeap_EnumFree(virt_ptr<TinyHeapVirtual> heap,\n                  virt_ptr<void> prevBlockPtr,\n                  virt_ptr<void> *outPtr,\n                  uint32_t *outSize);\n\n// TinyHeap physical\nTinyHeapError\nTinyHeap_Setup(phys_ptr<TinyHeapPhysical> heap,\n               int32_t trackingHeapSize,\n               phys_ptr<void> dataHeap,\n               int32_t dataHeapSize);\n\nTinyHeapError\nTinyHeap_Alloc(phys_ptr<TinyHeapPhysical> heap,\n               int32_t size,\n               int32_t align,\n               phys_ptr<void> *outPtr);\n\nTinyHeapError\nTinyHeap_AllocAt(phys_ptr<TinyHeapPhysical> heap,\n                 phys_ptr<void> ptr,\n                 int32_t size);\n\nvoid\nTinyHeap_Free(phys_ptr<TinyHeapPhysical> heap,\n              phys_ptr<void> ptr);\n\nint32_t\nTinyHeap_GetLargestFree(phys_ptr<TinyHeapPhysical> heap);\n\nphys_ptr<void>\nTinyHeap_Enum(phys_ptr<TinyHeapPhysical> heap,\n              phys_ptr<void> prevBlockPtr,\n              phys_ptr<void> *outPtr,\n              uint32_t *outSize);\n\nphys_ptr<void>\nTinyHeap_EnumFree(phys_ptr<TinyHeapPhysical> heap,\n                  phys_ptr<void> prevBlockPtr,\n                  phys_ptr<void> *outPtr,\n                  uint32_t *outSize);\n\n} // namespace cafe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel.cpp",
    "content": "#include \"cafe_kernel.h\"\n#include \"cafe_kernel_context.h\"\n#include \"cafe_kernel_exception.h\"\n#include \"cafe_kernel_heap.h\"\n#include \"cafe_kernel_ipckdriver.h\"\n#include \"cafe_kernel_ipc.h\"\n#include \"cafe_kernel_lock.h\"\n#include \"cafe_kernel_loader.h\"\n#include \"cafe_kernel_mcp.h\"\n#include \"cafe_kernel_mmu.h\"\n#include \"cafe_kernel_process.h\"\n#include \"cafe_kernel_shareddata.h\"\n#include \"cafe_kernel_userdrivers.h\"\n\n#include \"cafe/libraries/cafe_hle.h\"\n#include \"debug_api/debug_api_controller.h\"\n#include \"decaf_config.h\"\n#include \"decaf_configstorage.h\"\n#include \"decaf_events.h\"\n#include \"decaf_game.h\"\n#include \"ios/mcp/ios_mcp_mcp_types.h\"\n\n#include <atomic>\n#include <common/log.h>\n#include <common/platform_dir.h>\n#include <common/strutils.h>\n#include <libcpu/cpu.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::kernel\n{\n\nstruct StaticKernelData\n{\n   struct CoreData\n   {\n      // Used for cpu branch trace handler\n      be2_val<uint32_t> symbolDistance;\n      be2_array<char, 256> symbolNameBuffer;\n      be2_array<char, 256> moduleNameBuffer;\n   };\n\n   be2_array<CoreData, 3> coreData;\n   be2_struct<ios::mcp::MCPPPrepareTitleInfo> prepareTitleInfo;\n};\n\nstatic virt_ptr<StaticKernelData> sKernelData = nullptr;\nstatic internal::AddressSpace sKernelAddressSpace;\nstatic std::array<virt_ptr<Context>, 3> sSubCoreEntryContexts = { };\nstatic std::string sExecutableName;\nstatic std::atomic<bool> sStopping { false };\nstatic std::atomic<bool> sBranchTraceEnabled { false };\nstatic std::atomic<bool> sBranchTraceHandlerSet { false };\n\nstatic void\nmainCoreEntryPoint(cpu::Core *core)\n{\n   internal::setActiveAddressSpace(&sKernelAddressSpace);\n   internal::initialiseWorkAreaHeap();\n   internal::initialiseCoreContext(core);\n   internal::initialiseExceptionContext(core);\n   internal::initialiseExceptionHandlers();\n\n   // Set all cores to kernel process\n   for (auto i = 0; i < 3; ++i) {\n      internal::initialiseCoreProcess(i,\n                                      RamPartitionId::Kernel,\n                                      UniqueProcessId::Kernel,\n                                      KernelProcessId::Invalid);\n   }\n\n   internal::initialiseProcessData();\n   internal::ipckDriverInit();\n   internal::ipckDriverOpen();\n   internal::initialiseIpc();\n\n   // TODO: This is normally called by root.rpx\n   loadShared();\n   internal::registerRootUserDrivers();\n\n   // Prepare title\n   auto titleInfo = virt_addrof(sKernelData->prepareTitleInfo);\n   if (auto error = internal::mcpPrepareTitle(ios::mcp::DefaultTitleId,\n                                              titleInfo);\n       error || titleInfo->argstr[0] == '\\0') {\n      // Not a full title - fill out some default values!\n      titleInfo->version = 1u;\n      titleInfo->cmdFlags = 0u;\n      titleInfo->avail_size = 0u;\n      titleInfo->codegen_size = 0u;\n      titleInfo->codegen_core = 1u;\n      titleInfo->max_size = 0x40000000u;\n      titleInfo->max_codesize = 0x0E000000u;\n      titleInfo->default_stack0_size = 0u;\n      titleInfo->default_stack1_size = 0u;\n      titleInfo->default_stack2_size = 0u;\n      titleInfo->exception_stack0_size = 0x1000u;\n      titleInfo->exception_stack1_size = 0x1000u;\n      titleInfo->exception_stack2_size = 0x1000u;\n\n      string_copy(virt_addrof(titleInfo->argstr).get(),\n                  titleInfo->argstr.size(),\n                  sExecutableName.data(),\n                  sExecutableName.size());\n   } else {\n      gLog->info(\"Loaded title {:016X}, argstr \\\"{}\\\"\",\n                 titleInfo->titleId,\n                 virt_addrof(titleInfo->argstr).get());\n   }\n\n   auto rpx = std::string_view { virt_addrof(titleInfo->argstr).get() };\n   if (rpx.empty()) {\n      gLog->error(\"Could not find game executable to load.\");\n      return;\n   }\n\n   // Perform the initial load\n   internal::loadGameProcess(rpx, titleInfo);\n\n   // Notify front end that game is loaded\n   auto gameInfo = decaf::GameInfo { };\n   gameInfo.titleId = titleInfo->titleId;\n   if (auto pos = rpx.find_first_of(' '); pos != std::string_view::npos) {\n      gameInfo.executable = rpx.substr(0, pos);\n   } else {\n      gameInfo.executable = rpx;\n   }\n   decaf::event::onGameLoaded(gameInfo);\n\n   // Start the game\n   internal::finishInitAndPreload();\n}\n\nstatic void\nsubCoreEntryPoint(cpu::Core *core)\n{\n   internal::setActiveAddressSpace(&sKernelAddressSpace);\n   internal::initialiseCoreContext(core);\n   internal::initialiseExceptionContext(core);\n   internal::ipckDriverInit();\n   internal::ipckDriverOpen();\n\n   while (!sStopping.load()) {\n      internal::kernelLockAcquire();\n      auto entryContext = sSubCoreEntryContexts[core->id];\n      internal::kernelLockRelease();\n\n      if (entryContext) {\n         // Set the core's current process to the main application\n         internal::setCoreToProcessId(RamPartitionId::MainApplication,\n                                      KernelProcessId::Kernel);\n         internal::initialiseCoreProcess(core->id,\n                                         RamPartitionId::MainApplication,\n                                         UniqueProcessId::Game,\n                                         KernelProcessId::Kernel);\n\n         switchContext(entryContext);\n         break;\n      }\n\n      cpu::this_core::waitNextInterrupt();\n   }\n}\n\nvoid\nsetSubCoreEntryContext(int coreId,\n                       virt_ptr<Context> context)\n{\n   internal::kernelLockAcquire();\n   sSubCoreEntryContexts[coreId] = context;\n   internal::kernelLockRelease();\n\n   cpu::interrupt(coreId, cpu::GENERIC_INTERRUPT);\n}\n\nstatic void\ncpuEntrypoint(cpu::Core *core)\n{\n   if (core->id == 1) {\n      mainCoreEntryPoint(core);\n   } else {\n      subCoreEntryPoint(core);\n   }\n\n   internal::idleCoreLoop(core);\n}\n\nstatic void\ncpuBranchTraceHandler(cpu::Core *core,\n                      uint32_t target)\n{\n   if (sBranchTraceEnabled) {\n      auto &data = sKernelData->coreData[core->id];\n      auto symbolFound =\n         internal::findClosestSymbol(virt_addr { target },\n                                     virt_addrof(data.symbolDistance),\n                                     virt_addrof(data.symbolNameBuffer),\n                                     data.symbolNameBuffer.size(),\n                                     virt_addrof(data.moduleNameBuffer),\n                                     data.moduleNameBuffer.size());\n\n      if (symbolFound && data.moduleNameBuffer[0] && data.symbolNameBuffer[0]) {\n         gLog->trace(\"CPU branched to: 0x{:08X} {}|{}+0x{:X}\",\n                     target,\n                     virt_addrof(data.moduleNameBuffer).get(),\n                     virt_addrof(data.symbolNameBuffer).get(),\n                     data.symbolDistance);\n      } else {\n         gLog->trace(\"CPU branched to: 0x{:08X}\", target);\n      }\n   }\n}\n\nstatic cpu::Core *\ncpuUnknownSystemCallHandler(cpu::Core *core,\n                            uint32_t id)\n{\n   return cafe::hle::Library::handleUnknownSystemCall(core, id);\n}\n\nvoid\nstart()\n{\n   // Register config change handler\n   static std::once_flag sRegisteredConfigChangeListener;\n   std::call_once(sRegisteredConfigChangeListener,\n      []() {\n         decaf::registerConfigChangeListener(\n            [](const decaf::Settings &settings) {\n               if (settings.log.branch_trace && !sBranchTraceHandlerSet) {\n                  cpu::setBranchTraceHandler(&cpuBranchTraceHandler);\n                  sBranchTraceHandlerSet = true;\n               }\n\n               sBranchTraceEnabled = settings.log.branch_trace;\n            });\n      });\n\n   // Initialise CafeOS HLE\n   hle::initialiseLibraries();\n\n   // Initialise memory\n   internal::initialiseAddressSpace(&sKernelAddressSpace,\n                                    RamPartitionId::Kernel,\n                                    phys_addr { 0x72000000 }, 0x0E000000,\n                                    phys_addr { 0x20000000 }, 0x52000000,\n                                    0, 0,\n                                    phys_addr { 0 }, 0,\n                                    phys_addr { 0 }, 0,\n                                    0, false);\n   internal::loadAddressSpace(&sKernelAddressSpace);\n   internal::initialiseStaticDataHeap();\n\n   // Initialise static data\n   sKernelData = internal::allocStaticData<StaticKernelData>();\n   internal::initialiseStaticContextData();\n   internal::initialiseStaticExceptionData();\n   internal::initialiseStaticIpckDriverData();\n   internal::initialiseStaticIpcData();\n   internal::initialiseStaticUserDriversData();\n\n   // Setup cpu\n   cpu::setCoreEntrypointHandler(&cpuEntrypoint);\n\n   sBranchTraceEnabled = decaf::config()->log.branch_trace;\n   if (sBranchTraceEnabled) {\n      cpu::setBranchTraceHandler(&cpuBranchTraceHandler);\n      sBranchTraceHandlerSet = true;\n   }\n\n   cpu::setUnknownSystemCallHandler(&cpuUnknownSystemCallHandler);\n\n   // Start the cpu\n   cpu::start();\n}\n\nbool\nstopping()\n{\n   return sStopping;\n}\n\nvoid\njoin()\n{\n   cpu::join();\n}\n\nvoid\nstop()\n{\n   if (!sStopping) {\n      sStopping = true;\n      cpu::halt();\n   }\n}\n\nvoid\nsetExecutableFilename(const std::string& name)\n{\n   sExecutableName = name;\n}\n\nnamespace internal\n{\n\nvoid\nidleCoreLoop(cpu::Core *core)\n{\n   // Set up the default expected state for the nia/cia of idle threads.\n   //  This must be kept in sync with reschedule which sets them to this\n   //  for debugging purposes.\n   core->nia = 0xFFFFFFFF;\n   core->cia = 0xFFFFFFFF;\n\n   while (!sStopping.load()) {\n      cpu::this_core::waitForInterrupt();\n   }\n\n   gLog->info(\"Core {} exit\", core->id);\n}\n\nvoid\nexit()\n{\n   // Cafe kernel is about to exit - IOS threads should also stop.\n   auto error = IOS_Ioctl(RamPartitionId::Kernel,\n                          RamPartitionId::Invalid,\n                          getPpcAppHandle(),\n                          ios::mcp::PPCAppCommand::PowerOff,\n                          nullptr, 0,\n                          nullptr, 0);\n   if (error != ios::Error::OK) {\n      gLog->warn(\"/dev/ppc_app power off ioctl failed with error: {}\", error);\n   }\n\n   // Set the running flag to false so idle loops exit.\n   sStopping = true;\n\n   // Tell the CPU to stop.\n   if (decaf::config()->debugger.break_on_exit) {\n      decaf::debug::handleDebugBreakInterrupt();\n   }\n\n   cpu::halt();\n\n   // Switch to idle context to prevent further execution.\n   switchContext(nullptr);\n}\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n#include <string>\n\nnamespace cpu\n{\nstruct Core;\n}\n\nnamespace cafe::kernel\n{\n\nstruct Context;\n\nvoid\nstart();\n\nvoid\njoin();\n\nvoid\nstop();\n\nbool\nstopping();\n\nvoid\nsetExecutableFilename(const std::string &name);\n\nvoid\nsetSubCoreEntryContext(int coreId,\n                       virt_ptr<Context> context);\n\nnamespace internal\n{\n\nvoid\nidleCoreLoop(cpu::Core *core);\n\nvoid\nexit();\n\n} // internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_context.cpp",
    "content": "#include \"cafe_kernel_context.h\"\n#include \"cafe_kernel_heap.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/kernel/cafe_kernel.h\"\n#include \"cafe/libraries/cafe_hle.h\"\n\n#include <array>\n#include <common/platform_fiber.h>\n#include <common/log.h>\n#include <libcpu/cpu.h>\n\nnamespace cafe::kernel\n{\n\nstruct HostContext\n{\n   platform::Fiber *fiber = nullptr;\n   virt_ptr<Context> context = nullptr;\n   cpu::Tracer *tracer = nullptr;\n};\n\nstatic std::array<virt_ptr<Context>, 3>\nsCurrentContext;\n\nstatic std::array<virt_ptr<Context>, 3>\nsDeadContext;\n\nstatic std::array<virt_ptr<Context>, 3>\nsIdleContext;\n\nconstexpr auto CoreThreadStackSize = 0x100u;\n\nstruct StaticContextData\n{\n   be2_array<Context, 3> coreThreadContext;\n   be2_array<std::byte, CoreThreadStackSize * 3> coreThreadStackBuffer;\n};\n\nstatic virt_ptr<StaticContextData>\nsContextData;\n\nstatic void\ncheckDeadContext();\n\nusing ContextEntryPoint = virt_func_ptr<void()>;\n\nvoid\ncopyContextFromCpu(virt_ptr<Context> context)\n{\n   auto state = cpu::this_core::state();\n\n   for (auto i = 0; i < 32; ++i) {\n      context->gpr[i] = state->gpr[i];\n   }\n\n   for (auto i = 0; i < 32; ++i) {\n      context->fpr[i] = state->fpr[i].value;\n      context->psf[i] = state->fpr[i].paired1;\n   }\n\n   for (auto i = 0; i < 8; ++i) {\n      context->gqr[i] = state->gqr[i].value;\n   }\n\n   context->cr = state->cr.value;\n   context->lr = state->lr;\n   context->ctr = state->ctr;\n   context->xer = state->xer.value;\n   context->fpscr = state->fpscr.value;\n\n   context->srr0 = state->srr0;\n   context->dar = state->dar;\n   context->dsisr = state->dsisr;\n}\n\nvoid\ncopyContextToCpu(virt_ptr<Context> context)\n{\n   auto state = cpu::this_core::state();\n\n   for (auto i = 0; i < 32; ++i) {\n      state->gpr[i] = context->gpr[i];\n   }\n\n   for (auto i = 0; i < 32; ++i) {\n      state->fpr[i].value = context->fpr[i];\n      state->fpr[i].paired1 = context->psf[i];\n   }\n\n   for (auto i = 0; i < 8; ++i) {\n      state->gqr[i].value = context->gqr[i];\n   }\n\n   state->cr.value = context->cr;\n   state->lr = context->lr;\n   state->ctr = context->ctr;\n   state->xer.value = context->xer;\n   state->fpscr.value = context->fpscr;\n\n   state->srr0 = context->srr0;\n   state->dar = context->dar;\n   state->dsisr = context->dsisr;\n}\n\nvoid\nsleepCurrentContext()\n{\n   // Grab the current core and context information\n   auto core = cpu::this_core::state();\n   auto context = sCurrentContext[core->id];\n   decaf_check(context);\n\n   // Save all our registers to the context\n   copyContextFromCpu(context);\n   context->nia = core->nia;\n   context->cia = core->cia;\n\n   // Some things to help us when debugging...\n   core->nia = 0xFFFFFFFF;\n   core->cia = 0xFFFFFFFF;\n   cpu::this_core::setTracer(nullptr);\n}\n\nvoid\nwakeCurrentContext()\n{\n   // Clean up any dead fibers\n   checkDeadContext();\n\n   // Grab the current core and context information\n   auto core = cpu::this_core::state();\n   auto context = sCurrentContext[core->id];\n   decaf_check(context);\n\n   // Restore our context from the OSContext\n   copyContextToCpu(context);\n   core->nia = context->nia;\n   core->cia = context->cia;\n\n   // Some things to help us when debugging...\n   cpu::this_core::setTracer(context->hostContext->tracer);\n}\n\nstatic void\nfiberEntryPoint(void *)\n{\n   // Load up the context\n   wakeCurrentContext();\n\n   // Invoke the PPC thread entry point, note we do not pass any arguments\n   // because whoever created the thread would have already put the arguments\n   // into the guest registers.\n   auto core = cpu::this_core::state();\n   auto exitPoint = virt_func_cast<ContextEntryPoint>(static_cast<virt_addr>(core->lr));\n   auto entryPoint = virt_func_cast<ContextEntryPoint>(static_cast<virt_addr>(core->nia));\n\n   invoke(core, entryPoint);\n   invoke(core, exitPoint);\n   decaf_check(\"Control flow returned to fiber entry point\");\n}\n\nstatic void\nfreeHostContext(HostContext *hostContext)\n{\n   cpu::freeTracer(hostContext->tracer);\n   platform::destroyFiber(hostContext->fiber);\n   delete hostContext;\n}\n\n// This must be called under the same scheduler lock\n// that added the thread to tDeadThread, we simply use\n// the thread_local to pass it between fibers.\nstatic void\ncheckDeadContext()\n{\n   auto coreId = cpu::this_core::id();\n   auto deadContext = sDeadContext[coreId];\n\n   if (deadContext) {\n      sDeadContext[coreId] = nullptr;\n\n      // Something broken if we are accidentally cleaning\n      //  up currently active context...\n      decaf_check(deadContext != sCurrentContext[coreId]);\n\n      // Something is broken if we have no fiber\n      decaf_check(deadContext->hostContext);\n\n      // Destroy the fiber\n      freeHostContext(deadContext->hostContext);\n      deadContext->hostContext = nullptr;\n   }\n}\n\nvoid\nexitThreadNoLock()\n{\n   auto coreId = cpu::this_core::id();\n\n   // Make sure exitThread is not called multiple times\n   decaf_check(!sDeadContext[coreId]);\n\n   // Mark this fiber to be cleaned up\n   sDeadContext[coreId] = sCurrentContext[coreId];\n}\n\nstatic platform::Fiber *\ngetContextFiber(virt_ptr<Context> context)\n{\n   if (!context->hostContext) {\n      context->hostContext = new HostContext();\n      context->hostContext->tracer = cpu::allocTracer(1024 * 10 * 10);\n      context->hostContext->fiber = platform::createFiber(fiberEntryPoint, nullptr);\n      context->hostContext->context = context;\n   }\n\n   return context->hostContext->fiber;\n}\n\nvoid\nresetFaultedContextFiber(virt_ptr<Context> context,\n                         platform::FiberEntryPoint entry,\n                         void *param)\n{\n   auto oldFiber = context->hostContext->fiber;\n   setContextFiberEntry(context, entry, param);\n   platform::swapToFiber(oldFiber, context->hostContext->fiber);\n}\n\nvoid\nsetContextFiberEntry(virt_ptr<Context> context,\n                     platform::FiberEntryPoint entry,\n                     void *param)\n{\n   if (!context->hostContext) {\n      context->hostContext = new HostContext();\n      context->hostContext->tracer = cpu::allocTracer(1024 * 10 * 10);\n      context->hostContext->context = context;\n   }\n\n   context->hostContext->fiber = platform::createFiber(entry, param);\n}\n\nvirt_ptr<Context>\ngetCurrentContext()\n{\n   return sCurrentContext[cpu::this_core::id()];\n}\n\nvoid\nswitchContext(virt_ptr<Context> next)\n{\n   // Don't do anything if we are switching to the same context.\n   auto coreId = cpu::this_core::id();\n   auto current = sCurrentContext[coreId];\n   if (current == next) {\n      return;\n   }\n\n   if (!next) {\n      next = sIdleContext[coreId];\n   }\n\n   // Perform savage operations before the switch\n   sleepCurrentContext();\n\n   // Switch to the new fiber, note that coreId is no longer valid\n   // after this point, as this context may have been switched to\n   // a new core.\n   sCurrentContext[coreId] = next;\n   platform::swapToFiber(getContextFiber(current),\n                         getContextFiber(next));\n\n   // Perform restoral operations after the switch\n   wakeCurrentContext();\n}\n\n\n/**\n * This should only be run from coreinit entry point, it will hijack the idle\n * context for core 1 and adopt it into coreinit's default thread 1.\n */\nvoid\nhijackCurrentHostContext(virt_ptr<Context> context)\n{\n   auto coreId = cpu::this_core::id();\n   auto current = sCurrentContext[coreId];\n   decaf_check(current == sIdleContext[1]);\n\n   context->hostContext = current->hostContext;\n   sCurrentContext[coreId] = context;\n\n   // Reset the current core to an idle context\n   current->hostContext = nullptr;\n   setContextFiberEntry(\n      current,\n      [](void *core)\n      {\n         wakeCurrentContext();\n         internal::idleCoreLoop(reinterpret_cast<cpu::Core *>(core));\n      },\n      cpu::this_core::state());\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseCoreContext(cpu::Core *core)\n{\n   // Allocate the root context\n   auto context = virt_addrof(sContextData->coreThreadContext[core->id]);\n   std::memset(context.get(), 0, sizeof(Context));\n\n   auto stack = virt_addrof(sContextData->coreThreadStackBuffer[core->id * CoreThreadStackSize]);\n   context->gpr[1] = virt_cast<virt_addr>(stack).getAddress() + CoreThreadStackSize - 8;\n   context->attr |= 1 << core->id;\n\n   // Setup host context for the root fiber\n   context->hostContext = new HostContext();\n   context->hostContext->tracer = cpu::allocTracer(1024 * 10 * 10);\n   context->hostContext->fiber = platform::getThreadFiber();\n   context->hostContext->context = context;\n\n   // Save some needed information about the fiber run states.\n   sIdleContext[core->id] = context;\n   sCurrentContext[core->id] = context;\n   sDeadContext[core->id] = nullptr;\n\n   // Copy our core context to cpu core\n   copyContextToCpu(context);\n\n   // Set the core nia/cia to something debuggable\n   core->nia = 0xFFFFFFF0u | core->id;\n   core->cia = 0xFFFFFFF0u | core->id;\n}\n\nvoid\ninitialiseStaticContextData()\n{\n   sContextData = allocStaticData<StaticContextData>();\n}\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_context.h",
    "content": "#pragma once\n#include <common/platform_fiber.h>\n#include <libcpu/state.h>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::kernel\n{\n\n#ifndef DECAF_KERNEL_LLE\nstruct HostContext;\n#endif\n\nstruct Context\n{\n   static constexpr uint64_t Tag = 0x4F53436F6E747874ull;\n\n   //! Should always be set to the value OSContext::Tag.\n   be2_val<uint64_t> tag;\n   be2_array<uint32_t, 32> gpr;\n   be2_val<uint32_t> cr;\n   be2_val<uint32_t> lr;\n   be2_val<uint32_t> ctr;\n   be2_val<uint32_t> xer;\n\n   be2_val<uint32_t> srr0;\n   be2_val<uint32_t> srr1;\n\n   //These are only set during an exception\n   be2_val<uint32_t> dsisr;\n   be2_val<uint32_t> dar;\n   be2_val<uint32_t> exceptionType;\n\n   UNKNOWN(0x8);\n\n   be2_val<uint32_t> fpscr;\n   be2_array<double, 32> fpr;\n   be2_val<uint16_t> spinLockCount;\n   be2_val<uint16_t> state;\n   be2_array<uint32_t, 8> gqr;\n   be2_val<uint32_t> pir;\n   be2_array<double, 32> psf;\n   be2_array<int64_t, 3> coretime;\n   be2_val<int64_t> starttime;\n   be2_val<int32_t> error;\n   be2_val<uint32_t> attr;\n\n#ifdef DECAF_KERNEL_LLE\n   be2_val<uint32_t> pmc1;\n   be2_val<uint32_t> pmc2;\n   be2_val<uint32_t> pmc3;\n   be2_val<uint32_t> pmc4;\n#else\n   HostContext *hostContext;\n   be2_val<uint32_t> nia;\n   be2_val<uint32_t> cia;\n#endif\n\n   be2_val<uint32_t> mmcr0;\n   be2_val<uint32_t> mmcr1;\n};\nCHECK_OFFSET(Context, 0x00, tag);\nCHECK_OFFSET(Context, 0x08, gpr);\nCHECK_OFFSET(Context, 0x88, cr);\nCHECK_OFFSET(Context, 0x8c, lr);\nCHECK_OFFSET(Context, 0x90, ctr);\nCHECK_OFFSET(Context, 0x94, xer);\nCHECK_OFFSET(Context, 0x98, srr0);\nCHECK_OFFSET(Context, 0x9c, srr1);\nCHECK_OFFSET(Context, 0xA0, dsisr);\nCHECK_OFFSET(Context, 0xA4, dar);\nCHECK_OFFSET(Context, 0xA8, exceptionType);\nCHECK_OFFSET(Context, 0xb4, fpscr);\nCHECK_OFFSET(Context, 0xb8, fpr);\nCHECK_OFFSET(Context, 0x1b8, spinLockCount);\nCHECK_OFFSET(Context, 0x1ba, state);\nCHECK_OFFSET(Context, 0x1bc, gqr);\nCHECK_OFFSET(Context, 0x1DC, pir);\nCHECK_OFFSET(Context, 0x1e0, psf);\nCHECK_OFFSET(Context, 0x2e0, coretime);\nCHECK_OFFSET(Context, 0x2f8, starttime);\nCHECK_OFFSET(Context, 0x300, error);\n#ifdef DECAF_KERNEL_LLE\nCHECK_OFFSET(Context, 0x308, pmc1);\nCHECK_OFFSET(Context, 0x30c, pmc2);\nCHECK_OFFSET(Context, 0x310, pmc3);\nCHECK_OFFSET(Context, 0x314, pmc4);\n#endif\nCHECK_OFFSET(Context, 0x318, mmcr0);\nCHECK_OFFSET(Context, 0x31c, mmcr1);\nCHECK_SIZE(Context, 0x320);\n\nvoid\ncopyContextToCpu(virt_ptr<Context> context);\n\nvoid\ncopyContextFromCpu(virt_ptr<Context> context);\n\nvoid\nexitThreadNoLock();\n\nvoid\nresetFaultedContextFiber(virt_ptr<Context> context,\n                         platform::FiberEntryPoint entry,\n                         void *param);\n\nvoid\nsetContextFiberEntry(virt_ptr<Context> context,\n                     platform::FiberEntryPoint entry,\n                     void *param);\n\nvirt_ptr<Context>\ngetCurrentContext();\n\nvoid\nswitchContext(virt_ptr<Context> next);\n\nvoid\nhijackCurrentHostContext(virt_ptr<Context> context);\n\nvoid\nsleepCurrentContext();\n\nvoid\nwakeCurrentContext();\n\nnamespace internal\n{\n\nvoid\ninitialiseCoreContext(cpu::Core *core);\n\nvoid\ninitialiseStaticContextData();\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_exception.cpp",
    "content": "#include \"cafe_kernel_context.h\"\n#include \"cafe_kernel_exception.h\"\n#include \"cafe_kernel_interrupts.h\"\n#include \"cafe_kernel_heap.h\"\n#include \"cafe_kernel_ipckdriver.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\n#include \"decaf_config.h\"\n#include \"debug_api/debug_api_controller.h\"\n#include \"cafe/libraries/coreinit/coreinit_alarm.h\"\n#include \"cafe/libraries/coreinit/coreinit_interrupts.h\"\n#include \"cafe/libraries/coreinit/coreinit_scheduler.h\"\n#include \"cafe/libraries/gx2/gx2_event.h\"\n\n#include <array>\n#include <common/log.h>\n#include <common/platform_stacktrace.h>\n#include <common/platform_thread.h>\n#include <fmt/format.h>\n#include <iterator>\n#include <libcpu/cpu.h>\n#include <libcpu/cpu_formatters.h>\n#include <vector>\n#include <queue>\n\nnamespace cafe::kernel\n{\n\nconstexpr auto ExceptionThreadStackSize = 0x100u;\n\nstruct StaticExceptionData\n{\n   be2_array<Context, 3> exceptionThreadContext;\n   be2_array<std::byte, ExceptionThreadStackSize * 3> exceptionStackBuffer;\n};\n\nstatic virt_ptr<StaticExceptionData>\nsExceptionData;\n\nstatic std::array<ExceptionHandlerFn, ExceptionType::Max>\nsUserExceptionHandlers;\n\nstatic std::array<ExceptionHandlerFn, ExceptionType::Max>\nsKernelExceptionHandlers;\n\nstatic std::array<platform::StackTrace *, 3>\nsExceptionStackTraces;\n\nbool\nsetUserModeExceptionHandler(ExceptionType type,\n                            ExceptionHandlerFn handler)\n{\n   if (sUserExceptionHandlers[type]) {\n      return false;\n   }\n\n   sUserExceptionHandlers[type] = handler;\n   return true;\n}\n\nnamespace internal\n{\n\nstatic void\ndefaultExceptionHandler(ExceptionType type,\n                        virt_ptr<Context> interruptedContext);\n\ninline void\ndispatchException(ExceptionType type,\n                  virt_ptr<Context> interruptedContext)\n{\n   if (sKernelExceptionHandlers[type]) {\n      sKernelExceptionHandlers[type](type, interruptedContext);\n   } else if (sUserExceptionHandlers[type]) {\n      sUserExceptionHandlers[type](type, interruptedContext);\n   } else {\n      defaultExceptionHandler(type, interruptedContext);\n   }\n}\n\nstatic void\nexceptionContextFiberEntry(void *)\n{\n   auto coreId = cpu::this_core::id();\n   wakeCurrentContext();\n\n   while (true) {\n      auto context = getCurrentContext();\n      auto interruptedContext = virt_cast<Context *>(virt_addr { context->gpr[3].value() });\n      auto flags = context->gpr[4];\n\n      if (flags & cpu::ALARM_INTERRUPT) {\n         dispatchException(ExceptionType::Decrementer, interruptedContext);\n      }\n\n      if (flags & cpu::GPU7_INTERRUPT) {\n         dispatchExternalInterrupt(InterruptType::Gpu7, interruptedContext);\n      }\n\n      if (flags & cpu::IPC_INTERRUPT) {\n         dispatchExternalInterrupt(static_cast<InterruptType>(InterruptType::IpcPpc0 + coreId),\n                                   interruptedContext);\n      }\n\n      // Return to interrupted context\n      switchContext(interruptedContext);\n   }\n}\n\nstatic void\nhandleCpuInterrupt(cpu::Core *core,\n                   uint32_t flags)\n{\n   auto interruptedContext = getCurrentContext();\n\n   if (flags & cpu::SRESET_INTERRUPT) {\n      dispatchException(ExceptionType::SystemReset, interruptedContext);\n   }\n\n   if (flags & cpu::DBGBREAK_INTERRUPT) {\n      dispatchException(ExceptionType::Breakpoint, interruptedContext);\n   }\n\n   if (flags & cpu::PROGRAM_INTERRUPT) {\n      dispatchException(ExceptionType::Program, interruptedContext);\n   }\n\n   // Disable interrupts\n   auto originalInterruptMask =\n      cpu::this_core::setInterruptMask(cpu::SRESET_INTERRUPT |\n                                       cpu::DBGBREAK_INTERRUPT);\n\n   // Switch to the exception context fiber\n   auto exceptionContext = virt_addrof(sExceptionData->exceptionThreadContext[core->id]);\n   exceptionContext->gpr[3] = static_cast<uint32_t>(virt_cast<virt_addr>(interruptedContext));\n   exceptionContext->gpr[4] = flags;\n   switchContext(exceptionContext);\n\n   // Restore interrupts\n   cpu::this_core::setInterruptMask(originalInterruptMask);\n\n   // Always dispatch an ICI so userland coreinit can reschedule\n   dispatchException(ExceptionType::ICI, interruptedContext);\n}\n\nstruct UnhandledExceptionData\n{\n   ExceptionType type;\n   virt_ptr<Context> context;\n   int coreId;\n   platform::StackTrace *stackTrace = nullptr;\n};\n\nstatic void\nunhandledExceptionFiberEntryPoint(void *param)\n{\n   auto exceptionData = reinterpret_cast<UnhandledExceptionData *>(param);\n   auto context = exceptionData->context;\n\n   // We may have been in the middle of a kernel function...\n   if (coreinit::internal::isSchedulerLocked()) {\n      coreinit::internal::unlockScheduler();\n   }\n\n   // Log the core state\n   fmt::memory_buffer out;\n   fmt::format_to(std::back_inserter(out),\n                  \"Unhandled exception {}\\n\", exceptionData->type);\n   fmt::format_to(std::back_inserter(out),\n                  \"Warning: Register values may not be reliable when using JIT.\\n\");\n\n   switch (exceptionData->type) {\n   case ExceptionType::DSI:\n      fmt::format_to(std::back_inserter(out),\n                     \"Core{} Instruction at 0x{:08X} (value from SRR0) attempted to access invalid address 0x{:08X} (value from DAR)\\n\",\n                     exceptionData->coreId, context->srr0, context->dar);\n      break;\n   case ExceptionType::ISI:\n      fmt::format_to(std::back_inserter(out),\n                     \"Core{} Attempted to fetch instruction from invalid address 0x{:08X} (value from SRR0)\\n\",\n                     exceptionData->coreId, context->srr0);\n      break;\n   case ExceptionType::Alignment:\n      fmt::format_to(std::back_inserter(out),\n                     \"Core{} Instruction at 0x{:08X} (value from SRR0) attempted to access unaligned address 0x{:08X} (value from DAR)\\n\",\n                     exceptionData->coreId, context->srr0, context->dar);\n      break;\n   case ExceptionType::Program:\n      fmt::format_to(std::back_inserter(out),\n                     \"Core{} Program exception: Possible illegal instruction/operation at or around 0x{:08X} (value from SRR0)\\n\",\n                     exceptionData->coreId, context->srr0);\n      break;\n   default:\n      break;\n   }\n\n   fmt::format_to(std::back_inserter(out), \"nia: 0x{:08x}\\n\", context->nia);\n   fmt::format_to(std::back_inserter(out), \"lr: 0x{:08x}\\n\", context->lr);\n   fmt::format_to(std::back_inserter(out), \"cr: 0x{:08x}\\n\", context->cr);\n   fmt::format_to(std::back_inserter(out), \"ctr: 0x{:08x}\\n\", context->ctr);\n   fmt::format_to(std::back_inserter(out), \"xer: 0x{:08x}\\n\", context->xer);\n\n   for (auto i = 0u; i < 32; ++i) {\n      fmt::format_to(std::back_inserter(out), \"gpr[{}]: 0x{:08x}\\n\", i, context->gpr[i]);\n   }\n\n   gLog->critical(std::string_view { out.data(), out.size() });\n\n   // If the decaf debugger is enabled, we will catch this exception there\n   if (decaf::config()->debugger.enabled) {\n      // Move back an instruction so we can re-execute the failed instruction\n      //  and so that the debugger shows the right stop point.\n      cpu::this_core::state()->nia -= 4;\n\n      coreinit::internal::pauseCoreTime(true);\n      decaf::debug::handleDebugBreakInterrupt();\n      coreinit::internal::pauseCoreTime(false);\n\n      // This will shut down the thread and reschedule.  This is required\n      //  since returning from the segfault handler is an error.\n      coreinit::OSExitThread(0);\n   } else {\n      // If there is no debugger then let's crash decaf lul!\n      decaf_host_fault(fmt::format(\"Unhandled exception {}, srr0: 0x{:08X} nia: 0x{:08X}\\n\",\n                                   exceptionData->type, context->srr0, context->nia),\n                       exceptionData->stackTrace);\n   }\n}\n\nstatic void\ndefaultExceptionHandler(ExceptionType type,\n                        virt_ptr<Context> interruptedContext)\n{\n   auto exceptionData = new UnhandledExceptionData { };\n   exceptionData->type = type;\n   exceptionData->coreId = cpu::this_core::id();\n   exceptionData->context = interruptedContext;\n   exceptionData->stackTrace = sExceptionStackTraces[exceptionData->coreId];\n   resetFaultedContextFiber(getCurrentContext(), unhandledExceptionFiberEntryPoint, exceptionData);\n}\n\nstatic void\nhandleCpuSegfault(cpu::Core *core,\n                  uint32_t address,\n                  platform::StackTrace *stackTrace)\n{\n   auto interruptedContext = getCurrentContext();\n   copyContextFromCpu(interruptedContext);\n   sExceptionStackTraces[core->id] = stackTrace;\n\n   if (address == core->nia) {\n      dispatchException(ExceptionType::ISI, interruptedContext);\n   } else {\n      dispatchException(ExceptionType::DSI, interruptedContext);\n   }\n}\n\nstatic void\nhandleDebugBreakException(ExceptionType type,\n                          virt_ptr<Context> interruptedContext)\n{\n   if (decaf::config()->debugger.enabled) {\n      coreinit::internal::pauseCoreTime(true);\n      decaf::debug::handleDebugBreakInterrupt();\n      coreinit::internal::pauseCoreTime(false);\n   }\n}\n\nstatic void\nhandleIciException(ExceptionType type,\n                   virt_ptr<Context> interruptedContext)\n{\n   // Call user ICI handler if set, else just ignore\n   if (sUserExceptionHandlers[type]) {\n      sUserExceptionHandlers[type](type, interruptedContext);\n   }\n}\n\nstatic void\nhandleSystemResetException(ExceptionType type,\n                           virt_ptr<Context> interruptedContext)\n{\n   platform::exitThread(0);\n}\n\nvoid\ninitialiseExceptionContext(cpu::Core *core)\n{\n   auto context = virt_addrof(sExceptionData->exceptionThreadContext[core->id]);\n   std::memset(context.get(), 0, sizeof(Context));\n\n   auto stack = virt_addrof(sExceptionData->exceptionStackBuffer[core->id * ExceptionThreadStackSize]);\n   context->gpr[1] = virt_cast<virt_addr>(stack).getAddress() + ExceptionThreadStackSize - 8;\n   context->attr |= 1 << core->id;\n\n   setContextFiberEntry(context, exceptionContextFiberEntry, nullptr);\n}\n\nvoid\ninitialiseExceptionHandlers()\n{\n   setKernelExceptionHandler(ExceptionType::SystemReset,\n                             handleSystemResetException);\n\n   setKernelExceptionHandler(ExceptionType::Breakpoint,\n                             handleDebugBreakException);\n\n   setKernelExceptionHandler(ExceptionType::ICI,\n                             handleIciException);\n\n   // TODO: Move this to kernel timers\n   setKernelExceptionHandler(ExceptionType::Decrementer,\n      [](ExceptionType type,\n         virt_ptr<Context> interruptedContext)\n      {\n         coreinit::internal::disableScheduler();\n         coreinit::internal::handleAlarmInterrupt(interruptedContext);\n         coreinit::internal::enableScheduler();\n      });\n\n   cpu::setInterruptHandler(&handleCpuInterrupt);\n   cpu::setSegfaultHandler(&handleCpuSegfault);\n}\n\nvoid\ninitialiseStaticExceptionData()\n{\n   sExceptionData = allocStaticData<StaticExceptionData>();\n}\n\nvoid\nsetKernelExceptionHandler(ExceptionType type,\n                          ExceptionHandlerFn handler)\n{\n   sKernelExceptionHandlers[type] = handler;\n}\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_exception.h",
    "content": "#pragma once\n#include \"cafe_kernel_context.h\"\n#include <libcpu/be2_struct.h>\n#include <libcpu/state.h>\n\nnamespace cafe::kernel\n{\n\n#include <common/enum_start.inl>\n\nENUM_BEG(ExceptionType, uint32_t)\n   ENUM_VALUE(SystemReset, 0)\n   ENUM_VALUE(MachineCheck, 1)\n   ENUM_VALUE(DSI, 2)\n   ENUM_VALUE(ISI, 3)\n   ENUM_VALUE(ExternalInterrupt, 4)\n   ENUM_VALUE(Alignment, 5)\n   ENUM_VALUE(Program, 6)\n   ENUM_VALUE(FloatingPoint, 7)\n   ENUM_VALUE(Decrementer, 8)\n   ENUM_VALUE(SystemCall, 9)\n   ENUM_VALUE(Trace, 10)\n   ENUM_VALUE(PerformanceMonitor, 11)\n   ENUM_VALUE(Breakpoint, 12)\n   ENUM_VALUE(SystemInterrupt, 13)\n   ENUM_VALUE(ICI, 14)\n   ENUM_VALUE(Max, 15)\nENUM_END(ExceptionType)\n\n#include <common/enum_end.inl>\n\nusing ExceptionHandlerFn =\n   void(*)(ExceptionType type,\n           virt_ptr<Context> interruptedContext);\n\nbool\nsetUserModeExceptionHandler(ExceptionType type,\n                            ExceptionHandlerFn handler);\n\nnamespace internal\n{\n\nvoid\ninitialiseExceptionContext(cpu::Core *core);\n\nvoid\ninitialiseExceptionHandlers();\n\nvoid\ninitialiseStaticExceptionData();\n\nvoid\nsetKernelExceptionHandler(ExceptionType type,\n                          ExceptionHandlerFn handler);\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_heap.cpp",
    "content": "#include \"cafe_kernel_heap.h\"\n#include \"cafe_kernel_mmu.h\"\n#include \"cafe/cafe_tinyheap.h\"\n\n#include <common/frameallocator.h>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::kernel::internal\n{\n\nstatic FrameAllocator\nsStaticDataHeap;\n\nstatic virt_ptr<TinyHeap>\nsWorkAreaHeap;\n\nstatic constexpr auto\nWorkAreaHeapTrackingBlockCount = 0x80;\n\nvoid\ninitialiseStaticDataHeap()\n{\n   auto staticDataMapping = getVirtualMemoryMap(VirtualMemoryRegion::Kernel_0xFFE00000);\n   sStaticDataHeap = FrameAllocator {\n      virt_cast<void *>(staticDataMapping.vaddr).get(),\n      staticDataMapping.size,\n   };\n}\n\nbool\ninitialiseWorkAreaHeap()\n{\n   auto trackingSize = WorkAreaHeapTrackingBlockCount * TinyHeapBlockSize + TinyHeapHeaderSize;\n   auto workAreaMapping = getVirtualMemoryMap(VirtualMemoryRegion::KernelWorkAreaHeap);\n   sWorkAreaHeap = virt_cast<TinyHeap *>(workAreaMapping.vaddr);\n   auto error = TinyHeap_Setup(sWorkAreaHeap,\n                               trackingSize,\n                               virt_cast<void *>(workAreaMapping.vaddr + trackingSize),\n                               workAreaMapping.size - trackingSize);\n   return error == TinyHeapError::OK;\n}\n\nvirt_ptr<void>\nallocStaticData(size_t size,\n                size_t alignment)\n{\n   return virt_cast<void *>(cpu::translate(sStaticDataHeap.allocate(size, alignment)));\n}\n\nvirt_ptr<void>\nallocFromWorkArea(int32_t size,\n                  int32_t alignment)\n{\n   virt_ptr<void> ptr;\n   if (TinyHeap_Alloc(sWorkAreaHeap, size, alignment, &ptr) != TinyHeapError::OK) {\n      return nullptr;\n   }\n\n   return ptr;\n}\n\nvoid\nfreeToWorkArea(virt_ptr<void> ptr)\n{\n   TinyHeap_Free(sWorkAreaHeap, ptr);\n}\n\n} // namespace cafe::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_heap.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::kernel::internal\n{\n\nvoid\ninitialiseStaticDataHeap();\n\nbool\ninitialiseWorkAreaHeap();\n\nvirt_ptr<void>\nallocStaticData(size_t size,\n                size_t alignment = 4u);\n\nvirt_ptr<void>\nallocFromWorkArea(int32_t size,\n                  int32_t alignment = 4);\n\nvoid\nfreeToWorkArea(virt_ptr<void> ptr);\n\ntemplate<typename Type>\ninline virt_ptr<Type>\nallocStaticData()\n{\n   return virt_cast<Type *>(allocStaticData(sizeof(Type), alignof(Type)));\n}\n\n} // namespace cafe::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_info.cpp",
    "content": "#include \"cafe_kernel_info.h\"\n#include \"cafe_kernel_process.h\"\n#include \"cafe/loader/cafe_loader_init.h\"\n#include \"cafe/loader/cafe_loader_loaded_rpl.h\"\n\nnamespace cafe::kernel\n{\n\nvoid\ngetType0Info(virt_ptr<Info0> info,\n             uint32_t size)\n{\n   info->upid = internal::getCurrentUniqueProcessId();\n   info->rampid = internal::getCurrentRamPartitionId();\n   info->appFlags = ProcessFlags::get(0)\n      .debugLevel(DebugLevel::Verbose)\n      .disableSharedLibraries(false)\n      .isFirstProcess(true);\n\n   auto startInfo = internal::getCurrentRamPartitionStartInfo();\n   info->dataAreaStart = startInfo->dataAreaStart;\n   info->dataAreaEnd = startInfo->dataAreaEnd;\n   info->sdaBase = startInfo->sdaBase;\n   info->sda2Base = startInfo->sda2Base;\n   info->systemHeapSize = startInfo->systemHeapSize;\n\n   auto partitionData = internal::getCurrentRamPartitionData();\n   auto &core0 = partitionData->perCoreStartInfo[0];\n   auto &core1 = partitionData->perCoreStartInfo[1];\n   auto &core2 = partitionData->perCoreStartInfo[2];\n\n   info->stackBase0 = core0.stackBase;\n   info->stackBase1 = core1.stackBase;\n   info->stackBase2 = core2.stackBase;\n\n   info->stackEnd0 = core0.stackEnd;\n   info->stackEnd1 = core1.stackEnd;\n   info->stackEnd2 = core2.stackEnd;\n\n   info->exceptionStackBase0 = core0.exceptionStackBase;\n   info->exceptionStackBase1 = core1.exceptionStackBase;\n   info->exceptionStackBase2 = core2.exceptionStackBase;\n\n   info->exceptionStackEnd0 = core0.exceptionStackEnd;\n   info->exceptionStackEnd1 = core1.exceptionStackEnd;\n   info->exceptionStackEnd2 = core2.exceptionStackEnd;\n\n   info->lockedCacheBase0 = virt_addr { 0xFFC00000 };\n   info->lockedCacheBase1 = virt_addr { 0xFFC40000 };\n   info->lockedCacheBase2 = virt_addr { 0xFFC80000 };\n\n   info->physDataAreaStart = partitionData->ramPartitionAllocation.dataStart;\n   info->physDataAreaEnd = partitionData->ramPartitionAllocation.availStart;\n\n   info->physAvailStart = partitionData->ramPartitionAllocation.availStart;\n   info->physAvailEnd = partitionData->ramPartitionAllocation.codeGenStart;\n\n   info->physCodeGenStart = partitionData->ramPartitionAllocation.codeGenStart;\n   info->physCodeGenEnd = partitionData->ramPartitionAllocation.codeStart;\n\n   info->titleId = partitionData->titleInfo.titleId;\n\n   if (startInfo->coreinit) {\n      auto coreinit = startInfo->coreinit;\n      info->coreinit.loaderHandle = coreinit->moduleNameBuffer;\n      info->coreinit.textAddr = coreinit->textAddr;\n      info->coreinit.textOffset = coreinit->textOffset;\n      info->coreinit.textSize = coreinit->textSize;\n      info->coreinit.dataAddr = coreinit->dataAddr;\n      info->coreinit.dataOffset = coreinit->dataOffset;\n      info->coreinit.dataSize = coreinit->dataSize;\n      info->coreinit.loadAddr = coreinit->loadAddr;\n      info->coreinit.loadOffset = coreinit->loadOffset;\n      info->coreinit.loadSize = coreinit->loadSize;\n   }\n}\n\nvoid\ngetType6Info(virt_ptr<Info6> info,\n             uint32_t size)\n{\n   std::memset(info.get(), 0, sizeof(Info6));\n\n   // TODO: This comes from ios/mcp GetLaunchParameters\n   info->osTitleId = 0x000500101000400Aull;\n   info->unk0x08 = 0u;\n}\n\nvoid\ngetArgStr(virt_ptr<char> buffer,\n          uint32_t size)\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   auto length = partitionData->argstr.size();\n   if (length >= size) {\n      length = size - 1;\n   }\n\n   std::memcpy(buffer.get(),\n               partitionData->argstr.data(),\n               length);\n   buffer[length] = char { 0 };\n}\n\nvoid\ngetInfo(InfoType type,\n        virt_ptr<void> buffer,\n        uint32_t size)\n{\n   switch (type) {\n   case InfoType::Type0:\n      getType0Info(virt_cast<Info0 *>(buffer), size);\n      break;\n   case InfoType::Type6:\n      getType6Info(virt_cast<Info6 *>(buffer), size);\n      break;\n   case InfoType::ArgStr:\n      getArgStr(virt_cast<char *>(buffer), size);\n      break;\n   default:\n      decaf_abort(fmt::format(\"Unexpected kernel info type {}\",\n                              static_cast<unsigned>(type)));\n   }\n}\n\nSystemMode\ngetSystemMode()\n{\n   return SystemMode::Production;\n}\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_info.h",
    "content": "#pragma once\n#include \"cafe_kernel_process.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::kernel\n{\n\nenum class InfoType\n{\n   Type0 = 0,\n   Type6 = 6,\n   ArgStr = 3,\n};\n\nenum class SystemMode : int32_t\n{\n   Production = 1,\n   Debug = 2,\n};\n\nstruct Info0\n{\n   struct CoreinitInfo\n   {\n      be2_virt_ptr<void> loaderHandle;\n      be2_val<virt_addr> textAddr;\n      be2_val<uint32_t> textOffset;\n      be2_val<uint32_t> textSize;\n      be2_val<virt_addr> dataAddr;\n      be2_val<uint32_t> dataOffset;\n      be2_val<uint32_t> dataSize;\n      be2_val<virt_addr> loadAddr;\n      be2_val<uint32_t> loadOffset;\n      be2_val<uint32_t> loadSize;\n   };\n\n   be2_val<UniqueProcessId> upid;\n   be2_val<RamPartitionId> rampid;\n   be2_val<ProcessFlags> appFlags;\n   be2_val<virt_addr> dataAreaStart;\n   be2_val<virt_addr> dataAreaEnd;\n   be2_val<phys_addr> physDataAreaStart;\n   be2_val<phys_addr> physDataAreaEnd;\n   be2_val<phys_addr> physAvailStart;\n   be2_val<phys_addr> physAvailEnd;\n   be2_val<phys_addr> physCodeGenStart;\n   be2_val<phys_addr> physCodeGenEnd;\n   be2_val<virt_addr> sdaBase;\n   be2_val<virt_addr> sda2Base;\n   be2_val<uint32_t> systemHeapSize;\n   be2_val<virt_addr> stackEnd0;\n   be2_val<virt_addr> stackEnd1;\n   be2_val<virt_addr> stackEnd2;\n   be2_val<virt_addr> stackBase0;\n   be2_val<virt_addr> stackBase1;\n   be2_val<virt_addr> stackBase2;\n   be2_val<virt_addr> exceptionStackEnd0;\n   be2_val<virt_addr> exceptionStackEnd1;\n   be2_val<virt_addr> exceptionStackEnd2;\n   be2_val<virt_addr> exceptionStackBase0;\n   be2_val<virt_addr> exceptionStackBase1;\n   be2_val<virt_addr> exceptionStackBase2;\n   be2_val<virt_addr> lockedCacheBase0;\n   be2_val<virt_addr> lockedCacheBase1;\n   be2_val<virt_addr> lockedCacheBase2;\n   be2_struct<CoreinitInfo> coreinit;\n   be2_val<uint32_t> unk0x9C;\n   be2_val<TitleId> titleId;\n};\nCHECK_OFFSET(Info0, 0x00, upid);\nCHECK_OFFSET(Info0, 0x04, rampid);\nCHECK_OFFSET(Info0, 0x08, appFlags);\nCHECK_OFFSET(Info0, 0x0C, dataAreaStart);\nCHECK_OFFSET(Info0, 0x10, dataAreaEnd);\nCHECK_OFFSET(Info0, 0x14, physDataAreaStart);\nCHECK_OFFSET(Info0, 0x18, physDataAreaEnd);\nCHECK_OFFSET(Info0, 0x1C, physAvailStart);\nCHECK_OFFSET(Info0, 0x20, physAvailEnd);\nCHECK_OFFSET(Info0, 0x24, physCodeGenStart);\nCHECK_OFFSET(Info0, 0x28, physCodeGenEnd);\nCHECK_OFFSET(Info0, 0x2C, sdaBase);\nCHECK_OFFSET(Info0, 0x30, sda2Base);\nCHECK_OFFSET(Info0, 0x34, systemHeapSize);\nCHECK_OFFSET(Info0, 0x38, stackEnd0);\nCHECK_OFFSET(Info0, 0x3C, stackEnd1);\nCHECK_OFFSET(Info0, 0x40, stackEnd2);\nCHECK_OFFSET(Info0, 0x44, stackBase0);\nCHECK_OFFSET(Info0, 0x48, stackBase1);\nCHECK_OFFSET(Info0, 0x4C, stackBase2);\nCHECK_OFFSET(Info0, 0x50, exceptionStackEnd0);\nCHECK_OFFSET(Info0, 0x54, exceptionStackEnd1);\nCHECK_OFFSET(Info0, 0x58, exceptionStackEnd2);\nCHECK_OFFSET(Info0, 0x5C, exceptionStackBase0);\nCHECK_OFFSET(Info0, 0x60, exceptionStackBase1);\nCHECK_OFFSET(Info0, 0x64, exceptionStackBase2);\nCHECK_OFFSET(Info0, 0x68, lockedCacheBase0);\nCHECK_OFFSET(Info0, 0x6C, lockedCacheBase1);\nCHECK_OFFSET(Info0, 0x70, lockedCacheBase2);\nCHECK_OFFSET(Info0, 0x74, coreinit);\nCHECK_OFFSET(Info0, 0x9C, unk0x9C);\nCHECK_OFFSET(Info0, 0xA0, titleId);\nCHECK_SIZE(Info0, 0xA8);\n\nstruct Info6\n{\n   be2_val<uint64_t> osTitleId;\n   be2_val<uint32_t> unk0x08;\n   PADDING(0x108 - 0xC);\n};\nCHECK_OFFSET(Info6, 0x00, osTitleId);\nCHECK_OFFSET(Info6, 0x08, unk0x08);\nCHECK_SIZE(Info6, 0x108);\n\nvoid\ngetType0Info(virt_ptr<Info0> info,\n             uint32_t size);\n\nvoid\ngetType6Info(virt_ptr<Info6> info,\n             uint32_t size);\n\nvoid\ngetArgStr(virt_ptr<char> buffer,\n          uint32_t size);\n\nvoid\ngetInfo(InfoType type,\n        virt_ptr<void> buffer,\n        uint32_t size);\n\nSystemMode\ngetSystemMode();\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_interrupts.cpp",
    "content": "#include \"cafe_kernel_context.h\"\n#include \"cafe_kernel_interrupts.h\"\n#include \"cafe_kernel_process.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\nnamespace cafe::kernel\n{\n\nstruct InterruptData\n{\n   std::array<internal::KernelInterruptHandlerFn, InterruptType::Max> kernelHandlers;\n   std::array<UserInterruptHandlerFn, InterruptType::Max> userHandlers;\n   std::array<virt_ptr<void>, InterruptType::Max> userHandlerData;\n};\n\nstatic std::array<InterruptData, 3>\nsPerCoreInterruptData;\n\nUserInterruptHandlerFn\nsetUserModeInterruptHandler(InterruptType type,\n                            UserInterruptHandlerFn handler,\n                            virt_ptr<void> userData)\n{\n   auto &data = sPerCoreInterruptData[cpu::this_core::id()];\n   if (type >= InterruptType::Max) {\n      // Invalid interrupt type\n      return nullptr;\n   }\n\n   auto previous = data.userHandlers[type];\n   data.userHandlers[type] = handler;\n   data.userHandlerData[type] = userData;\n   return previous;\n}\n\nvoid\nclearAndEnableInterrupt(InterruptType type)\n{\n}\n\nvoid\ndisableInterrupt(InterruptType type)\n{\n}\n\nnamespace internal\n{\n\nvoid\ndispatchExternalInterrupt(InterruptType type,\n                          virt_ptr<Context> interruptedContext)\n{\n   auto &data = sPerCoreInterruptData[cpu::this_core::id()];\n\n   if (auto kernelHandler = data.kernelHandlers[type]) {\n      kernelHandler(type, interruptedContext);\n   } else if (auto userHandler = data.userHandlers[type]) {\n      userHandler(type, interruptedContext, data.userHandlerData[type]);\n   }\n}\n\nvoid\nsetKernelInterruptHandler(InterruptType type,\n                          KernelInterruptHandlerFn handler)\n{\n   sPerCoreInterruptData[cpu::this_core::id()].kernelHandlers[type] = handler;\n}\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_interrupts.h",
    "content": "#pragma once\n#include \"cafe_kernel_context.h\"\n#include \"cafe_kernel_processid.h\"\n\nnamespace cafe::kernel\n{\n\n#include <common/enum_start.inl>\n\nENUM_BEG(InterruptType, uint32_t)\n   ENUM_VALUE(Error,          0)\n   ENUM_VALUE(Dsp,            1)\n   ENUM_VALUE(Gpu7,           2)\n   ENUM_VALUE(GpiPpc,         3)\n   ENUM_VALUE(PrimaryI2C,     4)\n   ENUM_VALUE(DspAi,          5)\n   ENUM_VALUE(DspAi2,         6)\n   ENUM_VALUE(DspAcc,         7)\n   ENUM_VALUE(DspDsp,         8)\n   ENUM_VALUE(IpcPpc0,        9)\n   ENUM_VALUE(IpcPpc1,        10)\n   ENUM_VALUE(IpcPpc2,        11)\n   ENUM_VALUE(Ahb,            12)\n   ENUM_VALUE(Max,            13)\nENUM_END(InterruptType)\n\n#include <common/enum_end.inl>\n\nusing UserInterruptHandlerFn =\n   void(*)(InterruptType type,\n           virt_ptr<Context> interruptedContext,\n           virt_ptr<void> userData);\n\nUserInterruptHandlerFn\nsetUserModeInterruptHandler(InterruptType type,\n                            UserInterruptHandlerFn callback,\n                            virt_ptr<void> userData);\n\nvoid\nclearAndEnableInterrupt(InterruptType type);\n\nvoid\ndisableInterrupt(InterruptType type);\n\nnamespace internal\n{\n\nusing KernelInterruptHandlerFn =\n   void(*)(InterruptType type,\n           virt_ptr<Context> interruptedContext);\n\nvoid\ndispatchExternalInterrupt(InterruptType type,\n                          virt_ptr<Context> interruptContext);\n\nvoid\nsetKernelInterruptHandler(InterruptType type,\n                          KernelInterruptHandlerFn handler);\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_ipc.cpp",
    "content": "#include \"cafe_kernel.h\"\n#include \"cafe_kernel_ipc.h\"\n#include \"cafe_kernel_ipckdriver.h\"\n#include \"cafe_kernel_heap.h\"\n#include \"cafe_kernel_lock.h\"\n#include \"cafe_kernel_mmu.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/cafe_tinyheap.h\"\n#include \"ios/ios_ipc.h\"\n\n#include <common/strutils.h>\n#include <libcpu/cpu_control.h>\n#include <mutex>\n\nnamespace cafe::kernel::internal\n{\n\nconstexpr auto IpcBufferSize = 0x4000u;\nconstexpr auto IpcBufferAlign = 0x40u;\n\nstruct StaticIpcData\n{\n   be2_virt_ptr<TinyHeap> ipcHeap;\n   be2_array<std::byte, IpcBufferSize> ipcHeapBuffer;\n   be2_val<int32_t> mcpHandle;\n   be2_val<int32_t> ppcAppHandle;\n   be2_val<int32_t> cblHandle;\n};\n\nstruct SynchronousCallback\n{\n   std::atomic<bool> replyReceived = false;\n   ios::Error error = ios::Error::InvalidArg;\n   virt_ptr<void> buffer = nullptr;\n};\n\nstatic virt_ptr<StaticIpcData>\nsIpcData;\n\nstatic std::mutex\nsIpcHeapMutex;\n\nstatic void\nipcInitialiseHeap()\n{\n   sIpcData->ipcHeap = virt_cast<TinyHeap *>(virt_addrof(sIpcData->ipcHeapBuffer));\n   TinyHeap_Setup(sIpcData->ipcHeap,\n                  0x430,\n                  virt_addrof(sIpcData->ipcHeapBuffer) + 0x430,\n                  sIpcData->ipcHeapBuffer.size() - 0x430);\n}\n\nvirt_ptr<void>\nipcAllocBuffer(uint32_t size,\n               int32_t *outError)\n{\n   auto lock = std::unique_lock { sIpcHeapMutex };\n   auto allocPtr = virt_ptr<void> { nullptr };\n   auto error = TinyHeap_Alloc(sIpcData->ipcHeap,\n                               align_up(size, IpcBufferAlign),\n                               IpcBufferAlign,\n                               &allocPtr);\n   if (outError) {\n      *outError = static_cast<int32_t>(error);\n   }\n\n   return allocPtr;\n}\n\nvoid\nipcFreeBuffer(virt_ptr<void> buffer)\n{\n   auto lock = std::unique_lock { sIpcHeapMutex };\n   TinyHeap_Free(sIpcData->ipcHeap, buffer);\n}\n\nstatic void\nsynchronousCallback(ios::Error error,\n                    virt_ptr<void> context)\n{\n   auto synchronousReply = virt_cast<SynchronousCallback *>(context);\n   synchronousReply->error = error;\n   synchronousReply->replyReceived = true;\n\n   if (synchronousReply->buffer) {\n      ipcFreeBuffer(synchronousReply->buffer);\n   }\n}\n\nstatic ios::Error\nwaitSynchronousReply(virt_ptr<SynchronousCallback> synchronousReply,\n                     std::chrono::microseconds timeout,\n                     uint32_t unk)\n{\n   auto waitUntil = std::chrono::steady_clock::now() + timeout;\n   auto error = ios::Error::Timeout;\n\n   while (!synchronousReply->replyReceived) {\n      cpu::this_core::waitNextInterrupt(waitUntil);\n\n      if (std::chrono::steady_clock::now() >= waitUntil) {\n         break;\n      }\n   }\n\n   if (synchronousReply->replyReceived) {\n      error = synchronousReply->error;\n      synchronousReply->replyReceived = false;\n      synchronousReply->buffer = nullptr;\n   }\n\n   return error;\n}\n\nios::Error\nIOS_OpenAsync(RamPartitionId clientProcessId,\n              virt_ptr<const char> device,\n              ios::OpenMode mode,\n              IPCKDriverHostAsyncCallbackFn asyncCallback,\n              virt_ptr<void> asyncCallbackData)\n{\n   virt_ptr<IPCKDriverRequestBlock> requestBlock;\n   auto driver = ipckDriverGetInstance();\n   auto error = ipckDriverAllocateRequestBlock(clientProcessId,\n                                               RamPartitionId::Invalid,\n                                               driver,\n                                               &requestBlock,\n                                               0,\n                                               ios::Command::Open,\n                                               asyncCallback,\n                                               asyncCallbackData);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   requestBlock->request->request.args.open.name = effectiveToPhysical(device);\n   requestBlock->request->request.args.open.nameLen =\n      static_cast<uint32_t>(strlen(device.get()));\n\n   requestBlock->request->request.args.open.mode = mode;\n   requestBlock->request->request.args.open.caps = 0ull;\n\n   error = ipckDriverSubmitRequest(driver, requestBlock);\n   if (error < ios::Error::OK) {\n      ipckDriverFreeRequestBlock(driver, requestBlock);\n   }\n\n   return error;\n}\n\nios::Error\nIOS_Open(RamPartitionId clientProcessId,\n         virt_ptr<const char> device,\n         ios::OpenMode mode)\n{\n   auto synchronousReply = StackObject<SynchronousCallback> { };\n   auto error = IOS_OpenAsync(clientProcessId,\n                              device,\n                              mode,\n                              &synchronousCallback,\n                              synchronousReply);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   return waitSynchronousReply(synchronousReply,\n                               std::chrono::milliseconds { 35 },\n                               6);\n}\n\nios::Error\nIOS_CloseAsync(RamPartitionId clientProcessId,\n               ios::Handle handle,\n               IPCKDriverHostAsyncCallbackFn asyncCallback,\n               virt_ptr<void> asyncCallbackData,\n               uint32_t unkArg0)\n{\n   auto requestBlock = virt_ptr<IPCKDriverRequestBlock> { };\n   auto driver = ipckDriverGetInstance();\n   auto error = ipckDriverAllocateRequestBlock(clientProcessId,\n                                               RamPartitionId::Invalid,\n                                               driver,\n                                               &requestBlock,\n                                               handle,\n                                               ios::Command::Open,\n                                               asyncCallback,\n                                               asyncCallbackData);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   requestBlock->request->request.args.close.unkArg0 = unkArg0;\n\n   error = ipckDriverSubmitRequest(driver, requestBlock);\n   if (error < ios::Error::OK) {\n      ipckDriverFreeRequestBlock(driver, requestBlock);\n   }\n\n   return error;\n}\n\nios::Error\nIOS_Close(RamPartitionId clientProcessId,\n          ios::Handle handle,\n          uint32_t unkArg0)\n{\n   auto synchronousReply = StackObject<SynchronousCallback> { };\n   auto error = IOS_CloseAsync(clientProcessId,\n                               handle,\n                               &synchronousCallback,\n                               synchronousReply,\n                               unkArg0);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   return waitSynchronousReply(synchronousReply, std::chrono::milliseconds { 35 }, 6);\n}\n\nios::Error\nIOS_IoctlAsync(RamPartitionId clientProcessId,\n               RamPartitionId loaderProcessId,\n               ios::Handle handle,\n               uint32_t request,\n               virt_ptr<void> inBuf,\n               uint32_t inLen,\n               virt_ptr<void> outBuf,\n               uint32_t outLen,\n               IPCKDriverHostAsyncCallbackFn asyncCallback,\n               virt_ptr<void> asyncCallbackData)\n{\n   auto requestBlock = virt_ptr<IPCKDriverRequestBlock> { };\n   auto driver = ipckDriverGetInstance();\n   auto error = ipckDriverAllocateRequestBlock(clientProcessId,\n                                               loaderProcessId,\n                                               driver,\n                                               &requestBlock,\n                                               handle,\n                                               ios::Command::Ioctl,\n                                               asyncCallback,\n                                               asyncCallbackData);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n\n   auto &ioctl = requestBlock->request->request.args.ioctl;\n   ioctl.request = request;\n   ioctl.inputLength = inLen;\n   ioctl.outputLength = outLen;\n\n   if (inBuf) {\n      ioctl.inputBuffer = effectiveToPhysical(inBuf);\n   } else {\n      ioctl.inputBuffer = nullptr;\n   }\n\n   if (outBuf) {\n      ioctl.outputBuffer = effectiveToPhysical(outBuf);\n   } else {\n      ioctl.outputBuffer = nullptr;\n   }\n\n   error = ipckDriverSubmitRequest(driver, requestBlock);\n   if (error < ios::Error::OK) {\n      ipckDriverFreeRequestBlock(driver, requestBlock);\n   }\n\n   return error;\n}\n\nios::Error\nIOS_Ioctl(RamPartitionId clientProcessId,\n          RamPartitionId loaderProcessId,\n          ios::Handle handle,\n          uint32_t request,\n          virt_ptr<void> inBuf,\n          uint32_t inLen,\n          virt_ptr<void> outBuf,\n          uint32_t outLen)\n{\n   auto synchronousReply = StackObject<SynchronousCallback> { };\n   auto error = IOS_IoctlAsync(clientProcessId,\n                               loaderProcessId,\n                               handle,\n                               request,\n                               inBuf, inLen,\n                               outBuf, outLen,\n                               &synchronousCallback,\n                               synchronousReply);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   return waitSynchronousReply(synchronousReply, std::chrono::seconds { 35 }, 6);\n}\n\nvoid\ninitialiseIpc()\n{\n   ipcInitialiseHeap();\n   auto nameBuffer = virt_cast<char *>(ipcAllocBuffer(0x20));\n\n   string_copy(nameBuffer.get(), \"/dev/mcp\", 0x20);\n   sIpcData->mcpHandle = IOS_Open(RamPartitionId::Kernel,\n                                  nameBuffer,\n                                  ios::OpenMode::None);\n\n   string_copy(nameBuffer.get(), \"/dev/ppc_app\", 0x20);\n   sIpcData->ppcAppHandle = IOS_Open(RamPartitionId::Kernel,\n                                     nameBuffer,\n                                     ios::OpenMode::None);\n\n   string_copy(nameBuffer.get(), \"/dev/mcp\", 0x20);\n   sIpcData->cblHandle = IOS_Open(RamPartitionId::Kernel,\n                                  nameBuffer,\n                                  ios::OpenMode::None);\n\n   ipcFreeBuffer(nameBuffer);\n}\n\nvoid\ninitialiseStaticIpcData()\n{\n   sIpcData = allocStaticData<StaticIpcData>();\n}\n\n} // namespace cafe::kernel::internal\n\nnamespace cafe::kernel\n{\n\nios::Handle\ngetMcpHandle()\n{\n   return internal::sIpcData->mcpHandle;\n}\n\nios::Handle\ngetPpcAppHandle()\n{\n   return internal::sIpcData->ppcAppHandle;\n}\n\nios::Handle\ngetCblHandle()\n{\n   return internal::sIpcData->cblHandle;\n}\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_ipc.h",
    "content": "#pragma once\n#include \"cafe_kernel_ipckdriver.h\"\n#include \"cafe_kernel_processid.h\"\n\n#include \"ios/ios_ipc.h\"\n\nnamespace cafe::kernel\n{\n\nios::Handle\ngetMcpHandle();\n\nios::Handle\ngetPpcAppHandle();\n\nios::Handle\ngetCblHandle();\n\nnamespace internal\n{\n\nios::Error\nIOS_OpenAsync(RamPartitionId clientProcessId,\n              virt_ptr<const char> device,\n              ios::OpenMode mode,\n              IPCKDriverHostAsyncCallbackFn asyncCallback,\n              virt_ptr<void> asyncCallbackData);\n\nios::Error\nIOS_Open(RamPartitionId clientProcessId,\n         virt_ptr<const char> device,\n         ios::OpenMode mode);\n\nios::Error\nIOS_CloseAsync(RamPartitionId clientProcessId,\n               ios::Handle handle,\n               IPCKDriverHostAsyncCallbackFn asyncCallback,\n               virt_ptr<void> asyncCallbackData,\n               uint32_t unkArg0);\n\nios::Error\nIOS_Close(RamPartitionId clientProcessId,\n          ios::Handle handle,\n          uint32_t unkArg0);\n\nios::Error\nIOS_IoctlAsync(RamPartitionId clientProcessId,\n               RamPartitionId loaderProcessId,\n               ios::Handle handle,\n               uint32_t request,\n               virt_ptr<void> inBuf,\n               uint32_t inLen,\n               virt_ptr<void> outBuf,\n               uint32_t outLen,\n               IPCKDriverHostAsyncCallbackFn asyncCallback,\n               virt_ptr<void> asyncCallbackData);\n\nios::Error\nIOS_Ioctl(RamPartitionId clientProcessId,\n          RamPartitionId loaderProcessId,\n          ios::Handle handle,\n          uint32_t request,\n          virt_ptr<void> inBuf,\n          uint32_t inLen,\n          virt_ptr<void> outBuf,\n          uint32_t outLen);\n\nvirt_ptr<void>\nipcAllocBuffer(uint32_t size,\n               int32_t *outError = nullptr);\n\nvoid\nipcFreeBuffer(virt_ptr<void> buffer);\n\nvoid\ninitialiseIpc();\n\nvoid\ninitialiseStaticIpcData();\n\n} // namespace internal\n\n} // namespace cafe::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_ipckdriver.cpp",
    "content": "#include \"cafe_kernel_ipckdriver.h\"\n#include \"cafe_kernel_interrupts.h\"\n#include \"cafe_kernel_heap.h\"\n#include \"cafe_kernel_process.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_ipcdriver.h\"\n#include \"cafe/libraries/coreinit/coreinit_scheduler.h\"\n#include \"ios/kernel/ios_kernel_ipc_thread.h\"\n\n#include <libcpu/cpu_control.h>\n#include <condition_variable>\n#include <mutex>\n#include <queue>\n\nnamespace cafe::kernel\n{\n\nstruct StaticIpckDriverData\n{\n   be2_array<internal::IPCKDriver, 3> drivers;\n   be2_array<IPCKDriverRequest, internal::IPCKRequestsPerCore * 3> requestBuffer;\n};\n\nstatic virt_ptr<StaticIpckDriverData>\nsIpckDriverData = nullptr;\n\nstatic std::mutex\nsIpcMutex;\n\nstatic std::vector<phys_ptr<ios::IpcRequest>>\nsPendingResponses[3];\n\n\nnamespace internal\n{\n\nios::Error\nsubmitUserRequest(virt_ptr<IPCKDriver> driver,\n                  virt_ptr<IPCKDriverRequest> request);\n\nios::Error\nsubmitLoaderRequest(virt_ptr<IPCKDriver> driver,\n                    virt_ptr<IPCKDriverRequest> request);\n\n} // namespace internal\n\n\n/**\n * Open the current core's IPCKDriver for the current userland process.\n */\nios::Error\nipckDriverUserOpen(uint32_t numReplies,\n                   virt_ptr<IPCKDriverReplyQueue> replyQueue,\n                   IPCKUserInterruptHandlerFn handler)\n{\n   auto driver = internal::ipckDriverGetInstance();\n   if (!driver) {\n      return ios::Error::FailInternal;\n   }\n\n   auto pidx = static_cast<uint32_t>(internal::getCurrentRamPartitionId());\n   if (driver->perProcessReplyQueue[pidx]) {\n      // Already open!\n      return ios::Error::Busy;\n   }\n\n   if (!replyQueue || numReplies != IPCKDriverReplyQueue::Size) {\n      return ios::Error::Invalid;\n   }\n\n   driver->perProcessNumUserRequests[pidx] = 0u;\n   driver->perProcessReplyQueue[pidx] = replyQueue;\n   driver->perProcessCallbacks[pidx] = handler;\n   std::memset(replyQueue.get(), 0, sizeof(IPCKDriverReplyQueue));\n   return ios::Error::OK;\n}\n\n\n/**\n * Close the current core's IPCKDriver for the current userland process.\n */\nios::Error\nipckDriverUserClose()\n{\n   auto driver = internal::ipckDriverGetInstance();\n   if (!driver) {\n      return ios::Error::OK;\n   }\n\n   // TODO: Cleanup any pending requests for the current process\n   return ios::Error::OK;\n}\n\n\n/**\n * Submit a user IPC request.\n */\nios::Error\nipckDriverUserSubmitRequest(virt_ptr<IPCKDriverRequest> request)\n{\n   auto driver = internal::ipckDriverGetInstance();\n   if (!driver) {\n      return ios::Error::OK;\n   }\n\n   return internal::submitUserRequest(driver, request);\n}\n\n\n/**\n * Open the current core's IPCKDriver for the loader process.\n */\nios::Error\nipckDriverLoaderOpen()\n{\n   auto driver = internal::ipckDriverGetInstance();\n   if (!driver) {\n      return ios::Error::FailInternal;\n   }\n\n   /* TODO: Enable this check when we have proper multi process\n   auto kernelProcessId = getKernelProcessId();\n   if (kernelProcessId != KernelProcessId::Loader) {\n      return ios::Error::Invalid;\n   }\n   */\n\n   auto pidx = static_cast<uint32_t>(internal::getCurrentRamPartitionId());\n   driver->perProcessNumLoaderRequests[pidx] = 0u;\n   IPCKDriver_FIFOInit(virt_addrof(driver->perProcessLoaderReply[pidx]));\n   return ios::Error::OK;\n}\n\n\n/**\n * Close the current core's IPCKDriver for the loader process.\n */\nios::Error\nipckDriverLoaderClose()\n{\n   auto driver = internal::ipckDriverGetInstance();\n   if (!driver) {\n      return ios::Error::OK;\n   }\n\n   // TODO: Cleanup any pending loader requests\n   return ios::Error::OK;\n}\n\n\n/**\n * Submit a loader IPC request.\n */\nios::Error\nipckDriverLoaderSubmitRequest(virt_ptr<IPCKDriverRequest> request)\n{\n   auto driver = internal::ipckDriverGetInstance();\n   if (!driver) {\n      return ios::Error::FailInternal;\n   }\n\n   return internal::submitLoaderRequest(driver, request);\n}\n\n\n/**\n * Poll for completion of any loader IPC request.\n */\nvirt_ptr<IPCKDriverRequest>\nipckDriverLoaderPollCompletion()\n{\n   auto driver = internal::ipckDriverGetInstance();\n   auto block = virt_ptr<internal::IPCKDriverRequestBlock> { nullptr };\n   auto pidx = static_cast<uint32_t>(internal::getCurrentRamPartitionId());\n   auto error = IPCKDriver_FIFOPop(virt_addrof(driver->perProcessLoaderReply[pidx]),\n                                   &block);\n   if (error < ios::Error::OK) {\n      return nullptr;\n   }\n\n   // Copy reply to our user request structure\n   std::memcpy(block->userRequest.get(),\n               block->request.get(),\n               0x48u);\n\n   driver->perProcessNumLoaderRequests[pidx]--;\n\n   auto request = block->userRequest;\n   ipckDriverFreeRequestBlock(driver, block);\n   return request;\n}\n\n\n/**\n * Submit an IPC reply from IOS.\n */\nvoid\nipckDriverIosSubmitReply(phys_ptr<ios::IpcRequest> reply)\n{\n   auto coreId = reply->cpuId - ios::CpuId::PPC0;\n\n   sIpcMutex.lock();\n   sPendingResponses[coreId].push_back(reply);\n   sIpcMutex.unlock();\n\n   cpu::interrupt(coreId, cpu::IPC_INTERRUPT);\n}\n\n\nnamespace internal\n{\n\nvirt_ptr<IPCKDriver>\nipckDriverGetInstance()\n{\n   return virt_addrof(sIpckDriverData->drivers[cpu::this_core::id()]);\n}\n\n\nios::Error\nipckDriverAllocateRequestBlock(RamPartitionId clientProcessId,\n                               RamPartitionId loaderProcessId,\n                               virt_ptr<IPCKDriver> driver,\n                               virt_ptr<IPCKDriverRequestBlock> *outRequestBlock,\n                               ios::Handle handle,\n                               ios::Command command,\n                               IPCKDriverHostAsyncCallbackFn asyncCallback,\n                               virt_ptr<void> asyncCallbackData)\n{\n   auto error = IPCKDriver_FIFOPop(virt_addrof(driver->freeFifo), outRequestBlock);\n   if (error < ios::Error::OK) {\n      if (error == ios::Error::QEmpty) {\n         return ios::Error::QFull;\n      }\n\n      return error;\n   }\n\n   auto requestBlock = *outRequestBlock;\n   requestBlock->flags = requestBlock->flags.value()\n      .unk_0x0C00(0)\n      .replyState(IPCKDriverRequestBlock::WaitReply)\n      .requestState(IPCKDriverRequestBlock::Allocated)\n      .clientProcessId(clientProcessId)\n      .loaderProcessId(loaderProcessId);\n\n   requestBlock->asyncCallback->func = asyncCallback;\n   requestBlock->asyncCallbackData = asyncCallbackData;\n   requestBlock->userRequest = nullptr;\n\n   auto request = requestBlock->request;\n   std::memset(virt_addrof(request->request.args).get(),\n               0,\n               sizeof(request->request.args));\n\n   request->request.clientPid = static_cast<int32_t>(clientProcessId);\n   request->request.handle = handle;\n   request->request.command = command;\n   request->request.flags = 0u;\n   request->request.reply = ios::Error::OK;\n   request->request.cpuId = static_cast<ios::CpuId>(cpu::this_core::id() + 1);\n\n   if (clientProcessId != RamPartitionId::Kernel) {\n      request->request.titleId = getCurrentTitleId();\n   }\n\n   request->prevHandle = handle;\n   request->prevCommand = command;\n   return ios::Error::OK;\n}\n\n\nvoid\nipckDriverFreeRequestBlock(virt_ptr<IPCKDriver> driver,\n                           virt_ptr<IPCKDriverRequestBlock> requestBlock)\n{\n   requestBlock->flags = requestBlock->flags.value()\n      .requestState(IPCKDriverRequestBlock::Unallocated)\n      .replyState(IPCKDriverRequestBlock::WaitReply)\n      .unk_0x0C00(0);\n   requestBlock->asyncCallback->func = nullptr;\n   requestBlock->asyncCallbackData = nullptr;\n   requestBlock->userRequest = nullptr;\n   IPCKDriver_FIFOPush(virt_addrof(driver->freeFifo), requestBlock);\n}\n\n\nios::Error\nipckDriverSubmitRequest(virt_ptr<IPCKDriver> driver,\n                        virt_ptr<IPCKDriverRequestBlock> requestBlock)\n{\n   auto error = IPCKDriver_FIFOPush(virt_addrof(driver->outboundFIFO),\n                                    requestBlock);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   if (driver->state != IPCKDriverState::Open) {\n      return ios::Error::NotReady;\n   }\n\n   error = IPCKDriver_FIFOPop(virt_addrof(driver->outboundFIFO),\n                              &requestBlock);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   ios::kernel::submitIpcRequest(\n      effectiveToPhysical(virt_addrof(requestBlock->request->request)));\n   return ios::Error::OK;\n}\n\n\nstatic ios::Error\nallocateUserRequestBlock(RamPartitionId clientProcessId,\n                         RamPartitionId loaderProcessId,\n                         virt_ptr<IPCKDriver> driver,\n                         virt_ptr<IPCKDriverRequestBlock> *outRequestBlock,\n                         virt_ptr<IPCKDriverRequest> userRequest)\n{\n   auto error = ipckDriverAllocateRequestBlock(clientProcessId,\n                                               loaderProcessId,\n                                               driver,\n                                               outRequestBlock,\n                                               0,\n                                               ios::Command::Invalid,\n                                               nullptr, nullptr);\n\n   if (error >= ios::Error::OK) {\n      auto requestBlock = *outRequestBlock;\n      requestBlock->userRequest = userRequest;\n      requestBlock->flags = requestBlock->flags.value()\n         .clientProcessId(clientProcessId);\n   }\n\n   return error;\n}\n\n\nstatic ios::Error\nprocessLoaderOrUserRequest(virt_ptr<IPCKDriver> driver,\n                           virt_ptr<IPCKDriverRequestBlock> requestBlock,\n                           bool isLoader)\n{\n   auto request = requestBlock->request;\n   request->prevHandle = request->request.handle;\n   request->prevCommand = request->request.command;\n   request->request.flags = 0u;\n   request->request.reply = ios::Error::OK;\n   request->request.cpuId = static_cast<ios::CpuId>(cpu::this_core::id() + 1);\n\n   if (!isLoader) {\n      request->request.clientPid = static_cast<int32_t>(getCurrentRamPartitionId());\n      request->request.titleId = getCurrentTitleId();\n   }\n\n   switch (request->request.command) {\n   case ios::Command::Open:\n      request->request.args.open.name =\n         phys_cast<const char *>(effectiveToPhysical(request->buffer1));\n      break;\n   case ios::Command::Read:\n      request->request.args.read.data = effectiveToPhysical(request->buffer1);\n      break;\n   case ios::Command::Write:\n      request->request.args.write.data = effectiveToPhysical(request->buffer1);\n      break;\n   case ios::Command::Ioctl:\n      if (request->buffer1) {\n         request->request.args.ioctl.inputBuffer =\n            phys_cast<const char *>(effectiveToPhysical(request->buffer1));\n      } else {\n         request->request.args.ioctl.inputBuffer = nullptr;\n      }\n\n      if (request->buffer2) {\n         request->request.args.ioctl.outputBuffer =\n            phys_cast<const char *>(effectiveToPhysical(request->buffer2));\n      } else {\n         request->request.args.ioctl.outputBuffer = nullptr;\n      }\n      break;\n   case ios::Command::Ioctlv:\n   {\n      auto &ioctlv = request->request.args.ioctlv;\n\n      if (request->buffer1) {\n         ioctlv.vecs =\n            phys_cast<ios::IoctlVec *>(effectiveToPhysical(request->buffer1));\n      } else {\n         ioctlv.vecs = nullptr;\n      }\n\n      for (auto i = 0u; i < ioctlv.numVecIn + ioctlv.numVecOut; ++i) {\n         if (!ioctlv.vecs[i].vaddr) {\n            continue;\n         }\n\n         ioctlv.vecs[i].paddr = effectiveToPhysical(ioctlv.vecs[i].vaddr);\n      }\n      break;\n   }\n   default:\n      break;\n   }\n\n   return ios::Error::OK;\n}\n\n\nstatic ios::Error\nsubmitUserOrLoaderRequest(virt_ptr<IPCKDriver> driver,\n                          virt_ptr<IPCKDriverRequest> userRequest,\n                          bool isLoader)\n{\n   auto error = ios::Error::OK;\n   auto rampid = getCurrentRamPartitionId();\n   auto pidx = static_cast<uint32_t>(rampid);\n\n   if (!driver) {\n      error = ios::Error::Invalid;\n   } else {\n      if (driver->perProcessNumUserRequests[pidx] +\n          driver->perProcessNumLoaderRequests[pidx] >= IPCKRequestsPerProcess) {\n         error = ios::Error::QFull;\n      } else {\n         virt_ptr<IPCKDriverRequestBlock> requestBlock;\n         error = allocateUserRequestBlock(isLoader ? RamPartitionId::Kernel : rampid,\n                                          isLoader ? rampid : RamPartitionId::Invalid,\n                                          driver,\n                                          &requestBlock,\n                                          userRequest);\n         if (error >= ios::Error::OK) {\n            std::memcpy(requestBlock->request.get(),\n                        userRequest.get(),\n                        0x48u);\n\n            error = processLoaderOrUserRequest(driver, requestBlock, isLoader);\n            if (error >= ios::Error::OK) {\n               error = ipckDriverSubmitRequest(driver, requestBlock);\n            }\n\n            if (isLoader) {\n               driver->perProcessNumLoaderRequests[pidx]++;\n            } else {\n               driver->perProcessNumUserRequests[pidx]++;\n            }\n\n            if (error < ios::Error::OK) {\n               ipckDriverFreeRequestBlock(driver, requestBlock);\n            }\n         }\n      }\n   }\n\n   driver->perProcessLastError[pidx] = error;\n   return ios::Error::OK;\n}\n\n\nios::Error\nsubmitUserRequest(virt_ptr<IPCKDriver> driver,\n                  virt_ptr<IPCKDriverRequest> userRequest)\n{\n   return submitUserOrLoaderRequest(driver, userRequest, false);\n}\n\n\nios::Error\nsubmitLoaderRequest(virt_ptr<IPCKDriver> driver,\n                    virt_ptr<IPCKDriverRequest> userRequest)\n{\n   return submitUserOrLoaderRequest(driver, userRequest, true);\n}\n\n\nstatic ios::Error\ndefensiveProcessIncomingMessagePointer(virt_ptr<IPCKDriver> driver,\n                                       virt_ptr<IPCKDriverRequest> request,\n                                       virt_ptr<IPCKDriverRequestBlock> *outRequestBlock)\n{\n   auto index = request - driver->requestsBuffer;\n   if (index >= IPCKRequestsPerCore || index < 0) {\n      return ios::Error::Invalid;\n   }\n\n   if (driver->requestBlocks[index].request != request) {\n      return ios::Error::Invalid;\n   }\n\n   auto requestBlock = virt_addrof(driver->requestBlocks[index]);\n   auto flags = requestBlock->flags.value();\n   if (flags.requestState() == IPCKDriverRequestBlock::Unallocated) {\n      return ios::Error::Invalid;\n   }\n\n   requestBlock->flags =\n      flags.replyState(IPCKDriverRequestBlock::ReceivedReply);\n   *outRequestBlock = requestBlock;\n   return ios::Error::OK;\n}\n\n\nstatic void\nprocessReply(virt_ptr<IPCKDriver> driver,\n             phys_ptr<ios::IpcRequest> reply)\n{\n   if (driver->state < IPCKDriverState::Open) {\n      return;\n   }\n\n   auto requestBlock = virt_ptr<IPCKDriverRequestBlock> { nullptr };\n   auto request = virt_cast<IPCKDriverRequest *>(physicalToEffectiveCached(phys_cast<phys_addr>(reply)));\n   auto error = defensiveProcessIncomingMessagePointer(driver, request, &requestBlock);\n   if (error < ios::Error::OK) {\n      return;\n   }\n\n   if (requestBlock->asyncCallback->func) {\n      requestBlock->asyncCallback->func(reply->reply, requestBlock->asyncCallbackData);\n      ipckDriverFreeRequestBlock(driver, requestBlock);\n   } else {\n      auto isLoader = false;\n      auto flags = requestBlock->flags.value();\n      auto processId = flags.clientProcessId();\n\n      if (flags.loaderProcessId() != RamPartitionId::Invalid &&\n          flags.loaderProcessId() != RamPartitionId::Kernel) {\n         processId = flags.loaderProcessId();\n         isLoader = true;\n      }\n\n      auto pidx = static_cast<unsigned>(processId);\n      if (isLoader) {\n         IPCKDriver_FIFOPush(virt_addrof(driver->perProcessLoaderReply[pidx]),\n                             requestBlock);\n      } else {\n         IPCKDriver_FIFOPush(virt_addrof(driver->perProcessUserReply[pidx]),\n                             requestBlock);\n      }\n   }\n}\n\n\nstatic void\ndispatchUserRepliesCallback(virt_ptr<IPCKDriver> driver,\n                            virt_ptr<Context> interruptedContext,\n                            uint32_t pidx)\n{\n   // Fill the process reply queue\n   auto requestBlock = virt_ptr<IPCKDriverRequestBlock> { nullptr };\n   auto replyQueue = driver->perProcessReplyQueue[pidx];\n\n   while (replyQueue->numReplies < replyQueue->replies.size()) {\n      auto error = IPCKDriver_FIFOPop(virt_addrof(driver->perProcessUserReply[pidx]),\n                                      &requestBlock);\n      if (error < ios::Error::OK) {\n         break;\n      }\n\n      std::memcpy(requestBlock->userRequest.get(),\n                  requestBlock->request.get(),\n                  0x48u);\n\n      replyQueue->replies[replyQueue->numReplies] = requestBlock->userRequest;\n      replyQueue->numReplies++;\n\n      driver->perProcessNumUserRequests[pidx]--;\n      ipckDriverFreeRequestBlock(driver, requestBlock);\n   }\n\n   for (auto i = replyQueue->numReplies; i < replyQueue->replies.size(); ++i) {\n      replyQueue->replies[i] = nullptr;\n   }\n\n   // Call the user callback\n   if (driver->perProcessCallbacks[pidx]) {\n      driver->perProcessCallbacks[pidx](driver->interruptType, interruptedContext);\n   }\n}\n\n\nstatic void\nipckDriverHandleInterrupt(InterruptType type,\n                          virt_ptr<Context> interruptedContext)\n{\n   auto driver = ipckDriverGetInstance();\n   auto responses = std::vector<phys_ptr<ios::IpcRequest>> { };\n\n   // Get the pending replies\n   sIpcMutex.lock();\n   sPendingResponses[driver->coreId].swap(responses);\n   sIpcMutex.unlock();\n\n   // Process replies into process queue\n   for (auto response : responses) {\n      processReply(driver, response);\n   }\n\n   // Dispatch any pending user replies for the current process\n   auto pidx = static_cast<uint32_t>(getCurrentRamPartitionId());\n   if (driver->perProcessUserReply[pidx].count) {\n      dispatchUserRepliesCallback(driver, interruptedContext, pidx);\n   }\n}\n\n\nstatic ios::Error\ninitialiseRegisters(virt_ptr<IPCKDriver> driver)\n{\n   switch (driver->coreId) {\n   case 0:\n      driver->registers.ppcMsg = virt_cast<void *>(virt_addr { 0x0D800400 });\n      driver->registers.ppcCtrl = virt_cast<void *>(virt_addr { 0x0D800404 });\n      driver->registers.armMsg = virt_cast<void *>(virt_addr { 0x0D800408 });\n      driver->registers.ahbLt = virt_cast<void *>(virt_addr { 0x0D800444 });\n      driver->registers.unkMaybeFlags = 0x40000000u;\n      break;\n   case 1:\n      driver->registers.ppcMsg = virt_cast<void *>(virt_addr { 0x0D800410 });\n      driver->registers.ppcCtrl = virt_cast<void *>(virt_addr { 0x0D800414 });\n      driver->registers.armMsg = virt_cast<void *>(virt_addr { 0x0D800418 });\n      driver->registers.ahbLt = virt_cast<void *>(virt_addr { 0x0D800454 });\n      driver->registers.unkMaybeFlags = 0x10000000u;\n      break;\n   case 2:\n      driver->registers.ppcMsg = virt_cast<void *>(virt_addr { 0x0D800420 });\n      driver->registers.ppcCtrl = virt_cast<void *>(virt_addr { 0x0D800424 });\n      driver->registers.armMsg = virt_cast<void *>(virt_addr { 0x0D800428 });\n      driver->registers.ahbLt = virt_cast<void *>(virt_addr { 0x0D800464 });\n      driver->registers.unkMaybeFlags = 0x04000000u;\n      break;\n   }\n\n   return ios::Error::OK;\n}\n\n\nios::Error\nipckDriverInit()\n{\n   auto driver = ipckDriverGetInstance();\n   std::memset(driver.get(), 0, sizeof(IPCKDriver));\n\n   driver->coreId = cpu::this_core::id();\n\n   switch (driver->coreId) {\n   case 0:\n      driver->interruptType = InterruptType::IpcPpc0;\n      driver->requestsBuffer = virt_addrof(sIpckDriverData->requestBuffer);\n      break;\n   case 1:\n      driver->interruptType = InterruptType::IpcPpc1;\n      driver->requestsBuffer =\n         virt_addrof(sIpckDriverData->requestBuffer) + IPCKRequestsPerCore;\n      break;\n   case 2:\n      driver->interruptType = InterruptType::IpcPpc2;\n      driver->requestsBuffer =\n         virt_addrof(sIpckDriverData->requestBuffer) + IPCKRequestsPerCore * 2;\n      break;\n   }\n\n   auto error = initialiseRegisters(driver);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   std::memset(driver->requestsBuffer.get(),\n               0,\n               sizeof(IPCKDriverRequest) * IPCKRequestsPerCore);\n   driver->state = IPCKDriverState::Initialised;\n\n   // TODO: Register proc action callback to cleanup IPCKDriver for process\n   return ios::Error::OK;\n}\n\n\nstatic ios::Error\ninitialiseResourceBuffers(virt_ptr<IPCKDriver> driver)\n{\n   for (auto i = 0u; i < IPCKRequestsPerCore; ++i) {\n      // Allocate memory to hold our host function pointer, this is because the\n      // host function pointer will be 8 bytes on 64bit systems but we only\n      // have space for 4 bytes in the structure.\n      auto hostCallbackPtr = allocStaticData<IPCKDriverHostAsyncCallback>();\n      hostCallbackPtr->func = nullptr;\n\n      driver->requestBlocks[i].asyncCallback = hostCallbackPtr;\n      driver->requestBlocks[i].asyncCallbackData = nullptr;\n      driver->requestBlocks[i].userRequest = nullptr;\n      driver->requestBlocks[i].request = virt_addrof(driver->requestsBuffer[i]);\n   }\n\n   return ios::Error::OK;\n}\n\n\nios::Error\nipckDriverOpen()\n{\n   auto driver = ipckDriverGetInstance();\n   if (driver->state != IPCKDriverState::Initialised &&\n       driver->state != IPCKDriverState::Unknown1) {\n      return ios::Error::NotReady;\n   }\n\n   auto error = initialiseResourceBuffers(driver);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   IPCKDriver_FIFOInit(virt_addrof(driver->freeFifo));\n   IPCKDriver_FIFOInit(virt_addrof(driver->outboundFIFO));\n\n   for (auto i = 0u; i < NumRamPartitions; ++i) {\n      IPCKDriver_FIFOInit(virt_addrof(driver->perProcessUserReply[i]));\n      IPCKDriver_FIFOInit(virt_addrof(driver->perProcessLoaderReply[i]));\n   }\n\n   for (auto i = 0u; i < IPCKRequestsPerCore; ++i) {\n      IPCKDriver_FIFOPush(virt_addrof(driver->freeFifo),\n                          virt_addrof(driver->requestBlocks[i]));\n   }\n\n   driver->unk0x04++;\n   setKernelInterruptHandler(driver->interruptType,\n                             ipckDriverHandleInterrupt);\n\n   driver->state = IPCKDriverState::Open;\n   return ios::Error::OK;\n}\n\n\nvoid\ninitialiseStaticIpckDriverData()\n{\n   sIpckDriverData = allocStaticData<StaticIpckDriverData>();\n}\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_ipckdriver.h",
    "content": "#pragma once\n#include \"cafe_kernel_interrupts.h\"\n#include \"cafe_kernel_ipckdriverfifo.h\"\n#include \"cafe_kernel_processid.h\"\n\n#include \"ios/ios_error.h\"\n#include \"ios/ios_ipc.h\"\n\n#include <array>\n#include <common/bitfield.h>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::kernel\n{\n\n#pragma pack(push, 1)\n\nusing IpcRequest = ios::IpcRequest;\nusing IpcCommand = ios::Command;\n\nstruct IPCKDriverRequest\n{\n   //! Actual IPC request\n   be2_struct<IpcRequest> request;\n\n   //! Allegedly the previous IPC command\n   be2_val<IpcCommand> prevCommand;\n\n   //! Allegedly the previous IPC handle\n   be2_val<int32_t> prevHandle;\n\n   //! Buffer argument 1\n   be2_virt_ptr<void> buffer1;\n\n   //! Buffer argument 2\n   be2_virt_ptr<void> buffer2;\n\n   //! Buffer to copy device name to for IOS_Open\n   be2_array<char, 0x20> nameBuffer;\n\n   UNKNOWN(0x80 - 0x68);\n};\nCHECK_OFFSET(IPCKDriverRequest, 0x00, request);\nCHECK_OFFSET(IPCKDriverRequest, 0x38, prevCommand);\nCHECK_OFFSET(IPCKDriverRequest, 0x3C, prevHandle);\nCHECK_OFFSET(IPCKDriverRequest, 0x40, buffer1);\nCHECK_OFFSET(IPCKDriverRequest, 0x44, buffer2);\nCHECK_OFFSET(IPCKDriverRequest, 0x48, nameBuffer);\nCHECK_SIZE(IPCKDriverRequest, 0x80);\n\nstruct IPCKDriverReplyQueue\n{\n   static constexpr auto Size = 0x30u;\n   be2_val<uint32_t> numReplies;\n   be2_array<virt_ptr<IPCKDriverRequest>, Size> replies;\n};\nCHECK_OFFSET(IPCKDriverReplyQueue, 0x00, numReplies);\nCHECK_OFFSET(IPCKDriverReplyQueue, 0x04, replies);\nCHECK_SIZE(IPCKDriverReplyQueue, 0xC4);\n\nusing IPCKUserInterruptHandlerFn =\n   void(*)(InterruptType type,\n           virt_ptr<Context> interruptedContext);\n\n#pragma pack(pop)\n\nios::Error\nipckDriverUserOpen(uint32_t numReplies,\n                   virt_ptr<IPCKDriverReplyQueue> replyQueue,\n                   IPCKUserInterruptHandlerFn handler);\n\nios::Error\nipckDriverUserClose();\n\nios::Error\nipckDriverUserSubmitRequest(virt_ptr<IPCKDriverRequest> request);\n\nios::Error\nipckDriverLoaderOpen();\n\nios::Error\nipckDriverLoaderClose();\n\nios::Error\nipckDriverLoaderSubmitRequest(virt_ptr<IPCKDriverRequest> request);\n\nvirt_ptr<IPCKDriverRequest>\nipckDriverLoaderPollCompletion();\n\nvoid\nipckDriverIosSubmitReply(phys_ptr<ios::IpcRequest> reply);\n\nnamespace internal\n{\n\n\n#pragma pack(push, 1)\n\nconstexpr auto IPCKRequestsPerCore = 0xB0u;\nconstexpr auto IPCKRequestsPerProcess = 0x30u;\n\nenum class IPCKDriverState : uint32_t\n{\n   Invalid = 0,\n   Unknown1 = 1,\n   Initialised = 2,\n   Open = 3,\n   Submitting = 4,\n};\n\nstruct IPCKDriverRegisters\n{\n   be2_virt_ptr<void> ppcMsg;\n   be2_virt_ptr<void> ppcCtrl;\n   be2_virt_ptr<void> armMsg;\n   be2_virt_ptr<void> ahbLt;\n   be2_val<uint32_t> unkMaybeFlags;\n};\n\n// This is a pointer to a host function pointer - we must allocate guest\n// memory to store the host function pointer in.\nusing IPCKDriverHostAsyncCallbackFn =\n   void(*)(ios::Error, virt_ptr<void>);\n\nstruct IPCKDriverHostAsyncCallback\n{\n   IPCKDriverHostAsyncCallbackFn func;\n};\n\nstruct IPCKDriverRequestBlock\n{\n   enum RequestState\n   {\n      Unallocated = 0,\n      Allocated = 1,\n   };\n\n   enum ReplyState\n   {\n      WaitReply = 0,\n      ReceivedReply = 1,\n   };\n\n   BITFIELD_BEG(Flags, uint32_t)\n      BITFIELD_ENTRY(10, 2, uint8_t, unk_0x0C00)\n      BITFIELD_ENTRY(12, 2, ReplyState, replyState)\n      BITFIELD_ENTRY(14, 2, RequestState, requestState)\n      BITFIELD_ENTRY(16, 8, RamPartitionId, loaderProcessId)\n      BITFIELD_ENTRY(24, 8, RamPartitionId, clientProcessId)\n   BITFIELD_END\n\n   be2_val<Flags> flags;\n\n   //! Kernel request callback\n   be2_virt_ptr<IPCKDriverHostAsyncCallback> asyncCallback;\n\n   //! Data passed to kernel callback\n   be2_virt_ptr<void> asyncCallbackData;\n\n   //! User memory for a request\n   be2_virt_ptr<IPCKDriverRequest> userRequest;\n\n   //! Kernel memory for request, assigned from driver's requestsBuffer\n   be2_virt_ptr<IPCKDriverRequest> request;\n};\nCHECK_OFFSET(IPCKDriverRequestBlock, 0x00, flags);\nCHECK_OFFSET(IPCKDriverRequestBlock, 0x04, asyncCallback);\nCHECK_OFFSET(IPCKDriverRequestBlock, 0x08, asyncCallbackData);\nCHECK_OFFSET(IPCKDriverRequestBlock, 0x0C, userRequest);\nCHECK_OFFSET(IPCKDriverRequestBlock, 0x10, request);\nCHECK_SIZE(IPCKDriverRequestBlock, 0x14);\n\nstruct IPCKDriver\n{\n   be2_val<IPCKDriverState> state;\n   be2_val<uint32_t> unk0x04;\n   be2_val<uint32_t> coreId;\n   be2_val<InterruptType> interruptType;\n   be2_virt_ptr<IPCKDriverRequest> requestsBuffer;\n   UNKNOWN(0x4);\n   be2_array<virt_ptr<IPCKDriverReplyQueue>, NumRamPartitions> perProcessReplyQueue;\n#ifdef DECAF_KERNEL_LLE\n   be2_array<virt_ptr<void>, NumRamPartitions> perProcessCallbacks;\n   be2_array<virt_ptr<void>, NumRamPartitions> perProcessCallbackStacks;\n   be2_array<virt_ptr<Context>, NumRamPartitions> perProcessCallbackContexts;\n#else\n   std::array<IPCKUserInterruptHandlerFn, NumRamPartitions> perProcessCallbacks;\n   PADDING(0x98 - (0x38 + sizeof(IPCKUserInterruptHandlerFn) * NumRamPartitions));\n#endif\n   be2_array<ios::Error, NumRamPartitions> perProcessLastError;\n   UNKNOWN(0x4);\n   be2_struct<IPCKDriverRegisters> registers;\n   UNKNOWN(0x8);\n   be2_array<uint32_t, NumRamPartitions> perProcessNumUserRequests;\n   be2_array<uint32_t, NumRamPartitions> perProcessNumLoaderRequests;\n   be2_struct<IPCKDriverFIFO<IPCKRequestsPerCore>> freeFifo;\n   be2_struct<IPCKDriverFIFO<IPCKRequestsPerCore>> outboundFIFO;\n   be2_array<IPCKDriverFIFO<IPCKRequestsPerProcess>, NumRamPartitions> perProcessUserReply;\n   be2_array<IPCKDriverFIFO<IPCKRequestsPerProcess>, NumRamPartitions> perProcessLoaderReply;\n   be2_array<IPCKDriverRequestBlock, IPCKRequestsPerCore> requestBlocks;\n};\nCHECK_OFFSET(IPCKDriver, 0x0, state);\nCHECK_OFFSET(IPCKDriver, 0x4, unk0x04);\nCHECK_OFFSET(IPCKDriver, 0x8, coreId);\nCHECK_OFFSET(IPCKDriver, 0xC, interruptType);\nCHECK_OFFSET(IPCKDriver, 0x10, requestsBuffer);\nCHECK_OFFSET(IPCKDriver, 0x18, perProcessReplyQueue);\n#ifdef DECAF_KERNEL_LLE\nCHECK_OFFSET(IPCKDriver, 0x38, perProcessCallbacks);\nCHECK_OFFSET(IPCKDriver, 0x58, perProcessCallbackStacks);\nCHECK_OFFSET(IPCKDriver, 0x78, perProcessCallbackContexts);\n#endif\nCHECK_OFFSET(IPCKDriver, 0x98, perProcessLastError);\nCHECK_OFFSET(IPCKDriver, 0xBC, registers);\nCHECK_OFFSET(IPCKDriver, 0xD8, perProcessNumUserRequests);\nCHECK_OFFSET(IPCKDriver, 0xF8, perProcessNumLoaderRequests);\nCHECK_OFFSET(IPCKDriver, 0x118, freeFifo);\nCHECK_OFFSET(IPCKDriver, 0x3E8, outboundFIFO);\nCHECK_OFFSET(IPCKDriver, 0x6B8, perProcessUserReply);\nCHECK_OFFSET(IPCKDriver, 0xD38, perProcessLoaderReply);\nCHECK_OFFSET(IPCKDriver, 0x13B8, requestBlocks);\nCHECK_SIZE(IPCKDriver, 0x2178);\n\n#pragma pack(pop)\n\nvirt_ptr<IPCKDriver>\nipckDriverGetInstance();\n\nios::Error\nipckDriverAllocateRequestBlock(RamPartitionId clientProcessId,\n                               RamPartitionId loaderProcessId,\n                               virt_ptr<IPCKDriver> driver,\n                               virt_ptr<IPCKDriverRequestBlock> *outRequestBlock,\n                               ios::Handle handle,\n                               ios::Command command,\n                               IPCKDriverHostAsyncCallbackFn asyncCallback,\n                               virt_ptr<void> asyncContext);\n\nvoid\nipckDriverFreeRequestBlock(virt_ptr<IPCKDriver> driver,\n                           virt_ptr<IPCKDriverRequestBlock> requestBlock);\n\nios::Error\nipckDriverSubmitRequest(virt_ptr<IPCKDriver> driver,\n                        virt_ptr<IPCKDriverRequestBlock> requestBlock);\n\nios::Error\nipckDriverInit();\n\nios::Error\nipckDriverOpen();\n\nvoid\ninitialiseStaticIpckDriverData();\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_ipckdriverfifo.h",
    "content": "#pragma once\n#include \"ios/ios_error.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::kernel::internal\n{\n\nstruct IPCKDriverRequestBlock;\n\n/**\n * FIFO queue for IPCKDriverRequestBlocks.\n *\n * Functions similar to a ring buffer.\n */\ntemplate<std::size_t Size>\nstruct IPCKDriverFIFO\n{\n   //! The current item index to push to\n   be2_val<int32_t> pushIndex;\n\n   //! The current item index to pop from\n   be2_val<int32_t> popIndex;\n\n   //! The number of items in the queue\n   be2_val<int32_t> count;\n\n   //! Tracks the highest amount of items there has been in the queue\n   be2_val<int32_t> maxCount;\n\n   //! Items in the queue\n   be2_array<virt_ptr<IPCKDriverRequestBlock>, Size> requestBlocks;\n};\nCHECK_OFFSET(IPCKDriverFIFO<1>, 0x00, pushIndex);\nCHECK_OFFSET(IPCKDriverFIFO<1>, 0x04, popIndex);\nCHECK_OFFSET(IPCKDriverFIFO<1>, 0x08, count);\nCHECK_OFFSET(IPCKDriverFIFO<1>, 0x0C, maxCount);\nCHECK_OFFSET(IPCKDriverFIFO<1>, 0x10, requestBlocks);\nCHECK_SIZE(IPCKDriverFIFO<1>, 0x14);\n\n/**\n * Initialises an IPCKDriverFIFO structure.\n */\ntemplate<std::size_t Size>\nstatic void\nIPCKDriver_FIFOInit(virt_ptr<IPCKDriverFIFO<Size>> fifo)\n{\n   fifo->pushIndex = 0;\n   fifo->popIndex = -1;\n   fifo->count = 0;\n   fifo->requestBlocks.fill(nullptr);\n}\n\n\n/**\n * Push a request into an IPCKDriverFIFO structure.\n *\n * \\retval ios::Error::OK\n * Success.\n *\n * \\retval ios::Error::QFull\n * There was no free space in the queue to push the request.\n */\ntemplate<std::size_t Size>\nstatic ios::Error\nIPCKDriver_FIFOPush(virt_ptr<IPCKDriverFIFO<Size>> fifo,\n                    virt_ptr<IPCKDriverRequestBlock> requestBlock)\n{\n   if (fifo->pushIndex == fifo->popIndex) {\n      return ios::Error::QFull;\n   }\n\n   fifo->requestBlocks[fifo->pushIndex] = requestBlock;\n\n   if (fifo->popIndex == -1) {\n      fifo->popIndex = fifo->pushIndex;\n   }\n\n   fifo->count += 1;\n   fifo->pushIndex = static_cast<int32_t>((fifo->pushIndex + 1) % Size);\n\n   if (fifo->count > fifo->maxCount) {\n      fifo->maxCount = fifo->count;\n   }\n\n   return ios::Error::OK;\n}\n\n\n/**\n * Pop a request from an IPCKDriverFIFO structure.\n *\n * \\retval ios::Error::OK\n * Success.\n *\n * \\retval ios::Error::QEmpty\n * There was no requests to pop from the queue.\n */\ntemplate<std::size_t Size>\nstatic ios::Error\nIPCKDriver_FIFOPop(virt_ptr<IPCKDriverFIFO<Size>> fifo,\n                   virt_ptr<IPCKDriverRequestBlock> *outRequestBlock)\n{\n   if (fifo->popIndex == -1) {\n      return ios::Error::QEmpty;\n   }\n\n   auto requestBlock = fifo->requestBlocks[fifo->popIndex];\n   fifo->count -= 1;\n\n   if (fifo->count == 0) {\n      fifo->popIndex = -1;\n   } else {\n      fifo->popIndex = static_cast<int32_t>((fifo->popIndex + 1) % Size);\n   }\n\n   *outRequestBlock = requestBlock;\n   return ios::Error::OK;\n}\n\n} // namespace cafe::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_loader.cpp",
    "content": "#include \"cafe_kernel_loader.h\"\n#include \"cafe_kernel_mmu.h\"\n#include \"cafe_kernel_process.h\"\n\n#include \"cafe/loader/cafe_loader_entry.h\"\n#include \"cafe/loader/cafe_loader_globals.h\"\n#include \"cafe/loader/cafe_loader_loaded_rpl.h\"\n\n#include <common/strutils.h>\n#include <cstdint>\n#include <libcpu/cpu_control.h>\n\nnamespace cafe::kernel\n{\n\nnamespace internal\n{\nstatic int32_t loaderEntry();\n} // namespace internal\n\n\nint32_t\nloaderLink(loader::LOADER_Handle handle,\n           virt_ptr<loader::LOADER_MinFileInfo> minFileInfo,\n           virt_ptr<loader::LOADER_LinkInfo> linkInfo,\n           uint32_t linkInfoSize)\n{\n   auto loaderIpc = loader::getKernelIpcStorage();\n   loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::Link;\n   loaderIpc->entryParams.dispatch.handle = handle;\n   loaderIpc->entryParams.dispatch.minFileInfo = minFileInfo;\n   loaderIpc->entryParams.dispatch.linkInfo = linkInfo;\n   loaderIpc->entryParams.dispatch.linkInfoSize = linkInfoSize;\n   return internal::loaderEntry();\n}\n\nint32_t\nloaderPrep(virt_ptr<loader::LOADER_MinFileInfo> minFileInfo)\n{\n   auto loaderIpc = loader::getKernelIpcStorage();\n   loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::Prep;\n   loaderIpc->entryParams.dispatch.minFileInfo = minFileInfo;\n   return internal::loaderEntry();\n}\n\nint32_t\nloaderPurge(loader::LOADER_Handle handle)\n{\n   auto loaderIpc = loader::getKernelIpcStorage();\n   loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::Purge;\n   loaderIpc->entryParams.dispatch.handle = handle;\n   return internal::loaderEntry();\n}\n\nint32_t\nloaderSetup(loader::LOADER_Handle handle,\n            virt_ptr<loader::LOADER_MinFileInfo> minFileInfo)\n{\n   auto loaderIpc = loader::getKernelIpcStorage();\n   loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::Setup;\n   loaderIpc->entryParams.dispatch.handle = handle;\n   loaderIpc->entryParams.dispatch.minFileInfo = minFileInfo;\n   return internal::loaderEntry();\n}\n\nint32_t\nloaderQuery(loader::LOADER_Handle handle,\n            virt_ptr<loader::LOADER_MinFileInfo> outMinFileInfo)\n{\n   auto loaderIpc = loader::getKernelIpcStorage();\n   loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::Query;\n   loaderIpc->entryParams.dispatch.handle = handle;\n   loaderIpc->entryParams.dispatch.minFileInfo = outMinFileInfo;\n   return internal::loaderEntry();\n}\n\nint32_t\nloaderUserGainControl()\n{\n   auto loaderIpc = loader::getKernelIpcStorage();\n   loaderIpc->entryParams.dispatch.code = loader::LOADER_Code::UserGainControl;\n   return internal::loaderEntry();\n}\n\nint32_t\nfindClosestSymbol(virt_addr addr,\n                  virt_ptr<uint32_t> outSymbolDistance,\n                  virt_ptr<char> symbolNameBuffer,\n                  uint32_t symbolNameBufferLength,\n                  virt_ptr<char> moduleNameBuffer,\n                  uint32_t moduleNameBufferLength)\n{\n   // Verify parameters\n   if (outSymbolDistance &&\n      (virt_cast<virt_addr>(outSymbolDistance) < virt_addr { 0x10000000 } ||\n       virt_cast<virt_addr>(outSymbolDistance) >= virt_addr { 0xC0000000 } ||\n       virt_cast<virt_addr>(outSymbolDistance) & 3 ||\n       !validateAddressRange(virt_cast<virt_addr>(outSymbolDistance), 4))) {\n      return 0xBAD20008;\n   }\n\n   if (symbolNameBufferLength) {\n      if (virt_cast<virt_addr>(symbolNameBuffer) < virt_addr { 0x10000000 } ||\n          virt_cast<virt_addr>(symbolNameBuffer) >= virt_addr { 0xC0000000 } ||\n          !validateAddressRange(virt_cast<virt_addr>(symbolNameBuffer), symbolNameBufferLength)) {\n          return 0xBAD2000A;\n      }\n   } else {\n      symbolNameBuffer = nullptr;\n   }\n\n   if (moduleNameBufferLength) {\n      if (virt_cast<virt_addr>(moduleNameBuffer) < virt_addr { 0x10000000 } ||\n          virt_cast<virt_addr>(moduleNameBuffer) >= virt_addr { 0xC0000000 } ||\n          !validateAddressRange(virt_cast<virt_addr>(moduleNameBuffer), moduleNameBufferLength)) {\n       return 0xBAD2000A;\n      }\n   } else {\n      moduleNameBuffer = nullptr;\n   }\n\n   return internal::findClosestSymbol(addr,\n                                      outSymbolDistance,\n                                      symbolNameBuffer,\n                                      symbolNameBufferLength,\n                                      moduleNameBuffer,\n                                      moduleNameBufferLength);\n}\n\nnamespace internal\n{\n\nstatic std::pair<virt_ptr<Context>, virt_addr>\ngetLoaderContext()\n{\n   auto contextStorage = loader::getContextStorage();\n   auto context = virt_ptr<Context> { nullptr };\n   auto stackTop = virt_addr { 0 };\n\n   switch (cpu::this_core::id()) {\n   case 0:\n      context = virt_addrof(contextStorage->context0);\n      stackTop = virt_cast<virt_addr>(virt_addrof(contextStorage->stack0) + contextStorage->stack0.size() - 16);\n      break;\n   case 1:\n      context = virt_addrof(contextStorage->context1);\n      stackTop = virt_cast<virt_addr>(virt_addrof(contextStorage->stack1) + contextStorage->stack1.size() - 16);\n      break;\n   case 2:\n      context = virt_addrof(contextStorage->context2);\n      stackTop = virt_cast<virt_addr>(virt_addrof(contextStorage->stack2) + contextStorage->stack2.size() - 16);\n      break;\n   default:\n      decaf_abort(fmt::format(\"Unexpected core id {}\", cpu::this_core::id()));\n   }\n\n   return { context, stackTop };\n}\n\nstatic int32_t\nloaderEntry()\n{\n   auto loaderIpc = loader::getKernelIpcStorage();\n   auto [context, stackTop] = getLoaderContext();\n\n   loaderIpc->entryParams.context = context;\n   loaderIpc->entryParams.procConfig = 0;\n   loaderIpc->entryParams.procContext = getCurrentContext();\n   loaderIpc->entryParams.interruptsAllowed = TRUE;\n   loaderIpc->entryParams.procId = getCurrentUniqueProcessId();\n\n   // In a real kernel we'd switch to the PPC context\n   // But our loader is HLE only atm so we just call the loader directly\n   return loader::LoaderStart(TRUE, virt_addrof(loaderIpc->entryParams));\n}\n\nstatic void\nKiRPLLoaderSetup(ProcessFlags processFlags,\n                 UniqueProcessId callerProcessId,\n                 UniqueProcessId targetProcessId)\n{\n   auto loaderIpc = loader::getKernelIpcStorage();\n   auto [context, stackTop] = getLoaderContext();\n\n   if (targetProcessId == UniqueProcessId::Root) {\n      processFlags = ProcessFlags::get(0).\n         isFirstProcess(true);\n   } else {\n      // Clear all flags except debugLevel\n      processFlags = ProcessFlags::get(0).\n         debugLevel(processFlags.debugLevel());\n\n      if (processFlags.unkBit12() || processFlags.isFirstProcess()) {\n         processFlags = processFlags\n            .disableSharedLibraries(true);\n      }\n\n      // TODO: Fix this when we implement multi-process\n      // Normally this would only be set by Root process, but in decaf until we\n      // implement processes then we will always be first process\n      processFlags = processFlags.\n         isFirstProcess(true);\n   }\n\n   loaderIpc->unk0x00 = 0u;\n   loaderIpc->processFlags = processFlags;\n\n   // In a real kernel we'd switch to the PPC context\n   // context->srr0 = loader entry point\n   // context->srr1 = 0x4000\n   context->gpr[1] = static_cast<uint32_t>(stackTop);\n   context->gpr[3] = 0u;\n   context->gpr[4] = static_cast<uint32_t>(virt_cast<virt_addr>(virt_addrof(loaderIpc->entryParams)));\n\n   // But our loader is HLE only atm so we just call the loader directly\n   loader::LoaderStart(FALSE, virt_addrof(loaderIpc->entryParams));\n}\n\nvoid\nKiRPLStartup(UniqueProcessId callerProcessId,\n             UniqueProcessId targetProcessId,\n             ProcessFlags processFlags,\n             uint32_t numCodeAreaHeapBlocks,\n             uint32_t maxCodeSize,\n             uint32_t maxDataSize,\n             uint32_t titleLoc)\n{\n   auto loaderIpc = loader::getKernelIpcStorage();\n   loaderIpc->maxDataSize = maxDataSize;\n   loaderIpc->callerProcessId = callerProcessId;\n   loaderIpc->maxCodeSize = maxCodeSize;\n   loaderIpc->procTitleLoc = titleLoc;\n   loaderIpc->targetProcessId = targetProcessId;\n   loaderIpc->numCodeAreaHeapBlocks = numCodeAreaHeapBlocks;\n   loaderIpc->rpxModule = nullptr;\n   loaderIpc->loadedModuleList = nullptr;\n   loaderIpc->unk0x28 = 0u;\n\n   loaderIpc->entryParams.procContext = nullptr;\n   loaderIpc->entryParams.procId = UniqueProcessId::Invalid;\n   loaderIpc->entryParams.procConfig = -1;\n   loaderIpc->entryParams.context = nullptr;\n   loaderIpc->entryParams.interruptsAllowed = FALSE;\n   std::memset(virt_addrof(loaderIpc->entryParams.dispatch).get(),\n               0,\n               sizeof(loader::LOADER_EntryDispatch));\n\n   KiRPLLoaderSetup(processFlags, callerProcessId, targetProcessId);\n}\n\nint32_t\nfindClosestSymbol(virt_addr addr,\n                  uint32_t *outSymbolDistance,\n                  char *symbolNameBuffer,\n                  uint32_t symbolNameBufferLength,\n                  char *moduleNameBuffer,\n                  uint32_t moduleNameBufferLength)\n{\n   auto partitionData = getCurrentRamPartitionData();\n   if (!partitionData) {\n      return 0xBAD20002;\n   }\n\n   if (outSymbolDistance) {\n      *outSymbolDistance = static_cast<uint32_t>(addr);\n   }\n\n   if (symbolNameBuffer) {\n      symbolNameBuffer[0] = char { 0 };\n   }\n\n   if (moduleNameBuffer) {\n      moduleNameBuffer[0] = char { 0 };\n   }\n\n   // Find the module and section containing the given address\n   auto containingModule = virt_ptr<loader::LOADED_RPL> { nullptr };\n   auto containingSectionIndex = 0u;\n\n   for (auto rpl = partitionData->loadedModuleList; rpl; rpl = rpl->nextLoadedRpl) {\n      for (auto i = 0u; i < rpl->elfHeader.shnum; ++i) {\n         auto sectionAddress = rpl->sectionAddressBuffer[i];\n         if (!sectionAddress) {\n            continue;\n         }\n\n         auto sectionHeader =\n            virt_cast<loader::rpl::SectionHeader *>(\n               virt_cast<virt_addr>(rpl->sectionHeaderBuffer) +\n               rpl->elfHeader.shentsize * i);\n\n         if (addr >= sectionAddress &&\n             addr < sectionAddress + sectionHeader->size) {\n            containingModule = rpl;\n            containingSectionIndex = i;\n            break;\n         }\n      }\n   }\n\n   // Search the module's symbol table for the nearest symbol\n   auto nearestSymbolName = virt_ptr<const char> { nullptr };\n   auto nearestSymbolValue = virt_addr { 0 };\n\n   if (containingModule) {\n      auto nearestSymbolDistance = uint32_t { 0xFFFFFFFFu };\n      for (auto i = 0u; i < containingModule->elfHeader.shnum; ++i) {\n         auto sectionAddress = containingModule->sectionAddressBuffer[i];\n         auto sectionHeader =\n            virt_cast<loader::rpl::SectionHeader *>(\n               virt_cast<virt_addr>(containingModule->sectionHeaderBuffer) +\n               containingModule->elfHeader.shentsize * i);\n\n         if (sectionHeader->type == loader::rpl::SHT_SYMTAB) {\n            auto strTab = containingModule->sectionAddressBuffer[sectionHeader->link];\n            auto symTabEntSize =\n               sectionHeader->entsize ?\n               static_cast<uint32_t>(sectionHeader->entsize) :\n               sizeof(loader::rpl::Symbol);\n            auto numSymbols = sectionHeader->size / symTabEntSize;\n\n            for (auto j = 0u; j < numSymbols; ++j) {\n               auto symbol =\n                  virt_cast<loader::rpl::Symbol *>(sectionAddress + j * symTabEntSize);\n               auto symbolAddr = virt_addr { static_cast<uint32_t>(symbol->value) };\n               if (symbol->shndx != containingSectionIndex ||\n                   symbolAddr > addr) {\n                  continue;\n               }\n\n               // Ignore symbols beginning with $ or .\n               auto symbolName = virt_cast<const char *>(strTab + symbol->name);\n               if (symbolName[0] == '$' || symbolName[0] == '.') {\n                  continue;\n               }\n\n               if (addr - symbolAddr < nearestSymbolDistance) {\n                  nearestSymbolName = symbolName;\n                  nearestSymbolDistance = static_cast<uint32_t>(addr - symbolAddr);\n                  nearestSymbolValue = symbolAddr;\n\n                  if (nearestSymbolDistance == 0) {\n                     break;\n                  }\n               }\n            }\n\n            if (nearestSymbolDistance == 0) {\n               break;\n            }\n         }\n      }\n   }\n\n   // Set the output\n   if (moduleNameBuffer) {\n      if (containingModule) {\n         string_copy(moduleNameBuffer,\n                     containingModule->moduleNameBuffer.get(),\n                     moduleNameBufferLength);\n      } else {\n         moduleNameBuffer[0] = char { 0 };\n      }\n   }\n\n   if (symbolNameBuffer) {\n      if (nearestSymbolName && nearestSymbolName[0]) {\n         string_copy(symbolNameBuffer,\n                     nearestSymbolName.get(),\n                     symbolNameBufferLength);\n      } else {\n         string_copy(symbolNameBuffer, \"<unknown>\", symbolNameBufferLength);\n      }\n   }\n\n   if (outSymbolDistance) {\n      *outSymbolDistance = static_cast<uint32_t>(addr - nearestSymbolValue);\n   }\n\n   return 0;\n}\n\nint32_t\nfindClosestSymbol(virt_addr addr,\n                  virt_ptr<uint32_t> outSymbolDistance,\n                  virt_ptr<char> symbolNameBuffer,\n                  uint32_t symbolNameBufferLength,\n                  virt_ptr<char> moduleNameBuffer,\n                  uint32_t moduleNameBufferLength)\n{\n   auto symbolDistance = uint32_t { 0 };\n   auto result = findClosestSymbol(addr,\n                                   &symbolDistance,\n                                   symbolNameBuffer.get(),\n                                   symbolNameBufferLength,\n                                   moduleNameBuffer.get(),\n                                   moduleNameBufferLength);\n   *outSymbolDistance = symbolDistance;\n   return result;\n}\n\n} // namespace internal\n\n} // namespace cafe::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_loader.h",
    "content": "#include \"cafe_kernel_process.h\"\n#include \"cafe/loader/cafe_loader_minfileinfo.h\"\n\n#include <cstdint>\n\nnamespace cafe::kernel\n{\n\nint32_t\nloaderLink(loader::LOADER_Handle handle,\n           virt_ptr<loader::LOADER_MinFileInfo> minFileInfo,\n           virt_ptr<loader::LOADER_LinkInfo> linkInfo,\n           uint32_t linkInfoSize);\n\nint32_t\nloaderPrep(virt_ptr<loader::LOADER_MinFileInfo> minFileInfo);\n\nint32_t\nloaderPurge(loader::LOADER_Handle handle);\n\nint32_t\nloaderSetup(loader::LOADER_Handle handle,\n            virt_ptr<loader::LOADER_MinFileInfo> minFileInfo);\n\nint32_t\nloaderQuery(loader::LOADER_Handle handle,\n            virt_ptr<loader::LOADER_MinFileInfo> outMinFileInfo);\n\nint32_t\nloaderUserGainControl();\n\nint32_t\nfindClosestSymbol(virt_addr addr,\n                  virt_ptr<uint32_t> outSymbolDistance,\n                  virt_ptr<char> symbolNameBuffer,\n                  uint32_t symbolNameBufferLength,\n                  virt_ptr<char> moduleNameBuffer,\n                  uint32_t moduleNameBufferLength);\n\nnamespace internal\n{\n\nvoid\nKiRPLStartup(UniqueProcessId callerProcessId,\n             UniqueProcessId targetProcessId,\n             ProcessFlags processFlags,\n             uint32_t numCodeAreaHeapBlocks,\n             uint32_t maxCodeSize,\n             uint32_t maxDataSize,\n             uint32_t titleLoc);\n\nint32_t\nfindClosestSymbol(virt_addr addr,\n                  virt_ptr<uint32_t> outSymbolDistance,\n                  virt_ptr<char> symbolNameBuffer,\n                  uint32_t symbolNameBufferLength,\n                  virt_ptr<char> moduleNameBuffer,\n                  uint32_t moduleNameBufferLength);\n\nint32_t\nfindClosestSymbol(virt_addr addr,\n                  uint32_t *outSymbolDistance,\n                  char *symbolNameBuffer,\n                  uint32_t symbolNameBufferLength,\n                  char *moduleNameBuffer,\n                  uint32_t moduleNameBufferLength);\n\n} // namespace internal\n\n} // namespace cafe::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_lock.cpp",
    "content": "#include \"cafe_kernel_lock.h\"\n#include <atomic>\n#include <libcpu/cpu_control.h>\n\nnamespace cafe::kernel::internal\n{\n\nstatic SpinLock\nsKernelLock { 0 };\n\nbool\nspinLockAcquire(SpinLock &spinLock,\n                uint32_t value)\n{\n   auto expected = 0u;\n\n   if (!value) {\n      return false;\n   }\n\n   while (!spinLock.value.compare_exchange_weak(expected, value, std::memory_order_acquire)) {\n      expected = 0;\n   }\n\n   return true;\n}\n\nbool\nspinLockRelease(SpinLock &spinLock,\n                uint32_t expected)\n{\n   auto value = spinLock.value.exchange(0, std::memory_order_release);\n   return (value == expected);\n}\n\n\nvoid\nkernelLockAcquire()\n{\n   spinLockAcquire(sKernelLock, cpu::this_core::id() + 1);\n}\n\nvoid\nkernelLockRelease()\n{\n   spinLockRelease(sKernelLock, cpu::this_core::id() + 1);\n}\n\n} // namespace cafe::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_lock.h",
    "content": "#pragma once\n#include <atomic>\n#include <cstdint>\n\nnamespace cafe::kernel::internal\n{\n\nstruct SpinLock\n{\n   std::atomic<uint32_t> value;\n};\n\nbool\nspinLockAcquire(SpinLock &spinLock,\n                uint32_t value);\n\nbool\nspinLockRelease(SpinLock &spinLock,\n                uint32_t expected);\n\nvoid\nkernelLockAcquire();\n\nvoid\nkernelLockRelease();\n\n} // namespace cafe::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_mcp.cpp",
    "content": "#include \"cafe_kernel_ipc.h\"\n#include \"cafe_kernel_mcp.h\"\n#include \"cafe_kernel_processid.h\"\n\n#include \"ios/mcp/ios_mcp_enum.h\"\n#include \"ios/mcp/ios_mcp_mcp_types.h\"\n#include \"ios/mcp/ios_mcp_mcp_request.h\"\n#include \"ios/mcp/ios_mcp_mcp_response.h\"\n\nnamespace cafe::kernel::internal\n{\n\nusing namespace ios::mcp;\n\nios::Error\nmcpGetFileLength(std::string_view path,\n                 uint32_t *outSize,\n                 MCPFileType fileType,\n                 uint32_t a4)\n{\n   auto buffer = ipcAllocBuffer(sizeof(MCPRequestGetFileLength));\n\n   // Prepare request\n   auto request = virt_cast<MCPRequestGetFileLength *>(buffer);\n   request->fileType = fileType;\n   request->unk0x18 = a4;\n   request->name = path;\n\n   // Send ioctl\n   auto error = IOS_Ioctl(RamPartitionId::Kernel,\n                          RamPartitionId::Invalid,\n                          getMcpHandle(),\n                          MCPCommand::GetFileLength,\n                          buffer, sizeof(MCPRequestGetFileLength),\n                          nullptr, 0u);\n   if (error >= ios::Error::OK) {\n      *outSize = static_cast<uint32_t>(error);\n   }\n\n   ipcFreeBuffer(buffer);\n   return error;\n}\n\nios::Error\nmcpLoadFile(std::string_view path,\n            virt_ptr<void> dataBuffer,\n            uint32_t size,\n            uint32_t pos,\n            MCPFileType fileType,\n            UniqueProcessId cafeProcessId)\n{\n   auto buffer = ipcAllocBuffer(sizeof(MCPRequestLoadFile));\n\n   // Prepare request\n   auto request = virt_cast<MCPRequestLoadFile *>(buffer);\n   request->fileType = fileType;\n   request->name = path;\n   request->pos = pos;\n   request->cafeProcessId = static_cast<uint32_t>(cafeProcessId);\n\n   // Send ioctl\n   auto error = IOS_Ioctl(RamPartitionId::Kernel,\n                          RamPartitionId::Invalid,\n                          getMcpHandle(),\n                          MCPCommand::LoadFile,\n                          buffer, sizeof(MCPRequestLoadFile),\n                          dataBuffer, size);\n\n   ipcFreeBuffer(buffer);\n   return error;\n}\n\nios::Error\nmcpPrepareTitle(MCPTitleId titleId,\n                virt_ptr<MCPPPrepareTitleInfo> outTitleInfo)\n{\n   auto buffer = ipcAllocBuffer(sizeof(MCPRequestPrepareTitle));\n\n   // Prepare request\n   auto request = virt_cast<MCPRequestPrepareTitle *>(buffer);\n   request->titleId = titleId;\n\n   // Send ioctl\n   auto error = IOS_Ioctl(RamPartitionId::Kernel,\n                          RamPartitionId::Invalid,\n                          getMcpHandle(),\n                          MCPCommand::PrepareTitle0x52,\n                          buffer, sizeof(MCPRequestPrepareTitle),\n                          buffer, sizeof(MCPResponsePrepareTitle));\n\n   // Handle response\n   if (error >= ios::Error::OK) {\n      auto response = virt_cast<MCPResponsePrepareTitle *>(buffer);\n      std::memcpy(outTitleInfo.get(),\n                  virt_addrof(response->titleInfo).get(),\n                  sizeof(MCPPPrepareTitleInfo));\n   }\n\n   ipcFreeBuffer(buffer);\n   return error;\n}\n\nios::Error\nmcpSwitchTitle(RamPartitionId rampid,\n               phys_addr dataStart,\n               phys_addr codeGenStart,\n               phys_addr codeEnd)\n{\n   auto buffer = ipcAllocBuffer(sizeof(MCPRequestSwitchTitle));\n\n   // Prepare request\n   auto request = virt_cast<MCPRequestSwitchTitle *>(buffer);\n   request->cafeProcessId = static_cast<uint32_t>(rampid);\n   request->dataStart = dataStart;\n   request->codeGenStart = codeGenStart;\n   request->codeEnd = codeEnd;\n\n   // Send ioctl\n   auto error = IOS_Ioctl(RamPartitionId::Kernel,\n                          RamPartitionId::Invalid,\n                          getMcpHandle(),\n                          MCPCommand::SwitchTitle,\n                          buffer, sizeof(MCPRequestSwitchTitle),\n                          nullptr, 0u);\n\n   ipcFreeBuffer(buffer);\n   return error;\n}\n\n} // namespace cafe::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_mcp.h",
    "content": "#pragma once\n#include \"cafe_kernel_processid.h\"\n\n#include \"ios/ios_error.h\"\n#include \"ios/mcp/ios_mcp_mcp_types.h\"\n\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::kernel::internal\n{\n\nios::Error\nmcpGetFileLength(std::string_view path,\n                 uint32_t *outSize,\n                 ios::mcp::MCPFileType fileType,\n                 uint32_t a4);\n\nios::Error\nmcpLoadFile(std::string_view path,\n            virt_ptr<void> buffer,\n            uint32_t size,\n            uint32_t pos,\n            ios::mcp::MCPFileType fileType,\n            UniqueProcessId cafeProcessId);\n\nios::Error\nmcpPrepareTitle(ios::mcp::MCPTitleId titleId,\n                virt_ptr<ios::mcp::MCPPPrepareTitleInfo> outTitleInfo);\n\nios::Error\nmcpSwitchTitle(RamPartitionId rampid,\n               phys_addr dataStart,\n               phys_addr codeGenStart,\n               phys_addr physEnd);\n\n} // namespace cafe::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_mmu.cpp",
    "content": "#include \"cafe_kernel_mmu.h\"\n#include \"cafe_kernel_process.h\"\n\n#include <array>\n#include <common/log.h>\n#include <fmt/core.h>\n#include <libcpu/be2_struct.h>\n#include <libcpu/cpu_control.h>\n#include <libcpu/cpu_formatters.h>\n#include <libcpu/mmu.h>\n\nnamespace cafe::kernel\n{\n\nconstexpr auto MaxCodeGenSize = 0x2000000u;\nconstexpr auto MaxTotalCodeSize = 0xE800000u;\n\nconstexpr auto OverlayArenaVirtualStart = virt_addr { 0xA0000000 };\nconstexpr auto OverlayArenaVirtualEnd = virt_addr { 0xBC000000 };\nconstexpr auto OverlayArenaPhysicalStart = phys_addr { 0x34000000 };\nconstexpr auto OverlayArenaPhysicalEnd = phys_addr { 0x50000000 };\nconstexpr auto OverlayArenaSize =\n   static_cast<uint32_t>(OverlayArenaPhysicalEnd - OverlayArenaPhysicalStart);\n\nstatic std::array<internal::AddressSpace *, 3> sCoreActiveAddressSpace =\n   { nullptr, nullptr, nullptr };\n\nnamespace internal\n{\nstatic bool loadMapping(MemoryMap &mapping);\nstatic bool unloadMapping(MemoryMap &mapping);\n}\n\nenum MemoryMapFlags\n{\n   MapUnknown1 = 1 << 1,\n   MapUnknown2 = 1 << 2,\n   MapUnknown4 = 1 << 4,\n   MapUnknown6 = 1 << 6,\n   MapCodeGen = 1 << 9,\n   MapUnknown10 = 1 << 10,\n   MapUnknown11 = 1 << 11,\n   MapMainApplication = 1 << 13,\n   MapUncached = 1 << 30,\n};\n\n// This must be kept in order with enum class VirtualMemoryRegion\nMemoryMap sMemoryMap[] = {\n   // CafeOS\n   { virt_addr { 0x01000000 },   0x800000, phys_addr { 0x32000000 }, 0x2CE08002 },\n\n   // CodeGen area\n   { virt_addr { 0x01800000 },    0x20000, phys_addr {          0 }, 0x28101200 },\n\n   // App Code\n   { virt_addr { 0x02000000 },          0, phys_addr {          0 }, 0x2CF09400 },\n\n   // App Data\n   { virt_addr { 0x10000000 },          0, phys_addr {          0 }, 0x28305800 },\n\n   // Overlay\n   { virt_addr { 0xA0000000 }, 0x40000000, phys_addr {          0 }, 0x00002000 },\n\n   // Foreground bucket\n   { virt_addr { 0xE0000000 },  0x4000000, phys_addr { 0x14000000 }, 0x28204004 },\n\n   // Tiling Apertures\n   { virt_addr { 0xE8000000 },  0x2000000, phys_addr { 0xD0000000 }, 0x78200004 },\n\n   // Loader globals which are also read/write by kernel\n   { virt_addr { 0xEFE00000 },    0x80000, phys_addr { 0x1B900000 }, 0x28109010 },\n\n   // MEM1\n   { virt_addr { 0xF4000000 },  0x2000000, phys_addr {          0 }, 0x28204004 },\n\n   // Loader bounce buffer\n   { virt_addr { 0xF6000000 },   0x800000, phys_addr { 0x1B000000 }, 0x3CA08002 },\n\n   // Shared data\n   { virt_addr { 0xF8000000 },  0x3000000, phys_addr { 0x18000000 }, 0x2CA08002 },\n\n   // Unknown\n   { virt_addr { 0xFB000000 },   0x800000, phys_addr { 0x1C800000 }, 0x28200002 },\n\n   // Registers\n   { virt_addr { 0xFC000000 },    0xC0000, phys_addr {  0xC000000 }, 0x70100022 },\n   { virt_addr { 0xFC0C0000 },   0x120000, phys_addr {  0xC0C0000 }, 0x70100022 },\n   { virt_addr { 0xFC1E0000 },    0x20000, phys_addr {  0xC1E0000 }, 0x78100024 },\n   { virt_addr { 0xFC200000 },    0x80000, phys_addr {  0xC200000 }, 0x78100024 },\n   { virt_addr { 0xFC280000 },    0x20000, phys_addr {  0xC280000 }, 0x78100024 },\n\n   // Write gather memory, 0x20000 per core\n   { virt_addr { 0xFC2A0000 },    0x20000, phys_addr {  0xC2A0000 }, 0x78100023 },\n\n   // Registers\n   { virt_addr { 0xFC300000 },    0x20000, phys_addr {  0xC300000 }, 0x78100024 },\n   { virt_addr { 0xFC320000 },    0xE0000, phys_addr {  0xC320000 }, 0x70100022 },\n   { virt_addr { 0xFD000000 },   0x400000, phys_addr {  0xD000000 }, 0x70100022 },\n\n   // Unknown\n   { virt_addr { 0xFE000000 },   0x800000, phys_addr { 0x1C000000 }, 0x20200002 },\n\n   // Kernel \"work area\" heap\n   { virt_addr { 0xFF200000 },    0x80000, phys_addr { 0x1B800000 }, 0x20100040 },\n\n   // Unknown\n   { virt_addr { 0xFF280000 },    0x80000, phys_addr { 0x1B880000 }, 0x20100040 },\n\n   // Locked Cache\n   { virt_addr { 0xFFC00000 },    0x20000, phys_addr { 0xFFC00000 }, 0x08100004 },\n   { virt_addr { 0xFFC40000 },    0x20000, phys_addr { 0xFFC40000 }, 0x08100004 },\n   { virt_addr { 0xFFC80000 },    0x20000, phys_addr { 0xFFC80000 }, 0x0810000C },\n\n   // Unknown\n   { virt_addr { 0xFFCE0000 },    0x20000, phys_addr {          0 }, 0x50100004 },\n\n   // Kernel data\n   { virt_addr { 0xFFE00000 },    0x20000, phys_addr { 0xFFE00000 }, 0x20100040 },\n   { virt_addr { 0xFFE40000 },    0x20000, phys_addr { 0xFFE40000 }, 0x20100040 },\n   { virt_addr { 0xFFE80000 },    0x60000, phys_addr { 0xFFE80000 }, 0x20100040 },\n\n   // Unknown\n   { virt_addr { 0xFFF60000 },    0x20000, phys_addr { 0xFFE20000 }, 0x20100080 },\n   { virt_addr { 0xFFF80000 },    0x20000, phys_addr { 0xFFE60000 }, 0x2C100040 },\n   { virt_addr { 0xFFFA0000 },    0x20000, phys_addr { 0xFFE60000 }, 0x20100080 },\n   { virt_addr { 0xFFFC0000 },    0x20000, phys_addr { 0x1BFE0000 }, 0x24100002 },\n   { virt_addr { 0xFFFE0000 },    0x20000, phys_addr { 0x1BF80000 }, 0x28100102 },\n};\n\nconstexpr auto sMemoryMapSize = sizeof(sMemoryMap) / sizeof(sMemoryMap[0]);\nstatic_assert(static_cast<size_t>(VirtualMemoryRegion::Max) == sMemoryMapSize);\n\nMemoryMap &\ngetVirtualMemoryMap(VirtualMemoryRegion region)\n{\n   return sMemoryMap[static_cast<size_t>(region)];\n}\n\nstd::pair<virt_addr, uint32_t>\ngetForegroundBucket()\n{\n   // TODO: Ensure process is in foreground, else return { 0, 0 }\n   return { virt_addr { 0xE0000000 },  0x4000000 };\n}\n\nstd::pair<virt_addr, uint32_t>\nenableOverlayArena()\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n\n   // TODO: In a multi process environment we will have to exit the overlay\n   // process.\n\n   if (!partitionData->addressSpace.overlayArenaEnabled) {\n      auto mapping = MemoryMap { };\n      mapping.vaddr = OverlayArenaVirtualStart;\n      mapping.paddr = OverlayArenaPhysicalStart;\n      mapping.size = OverlayArenaSize;\n      internal::loadMapping(mapping);\n\n      partitionData->ramPartitionAllocation.overlayStart = OverlayArenaPhysicalStart;\n      partitionData->ramPartitionAllocation.overlayEnd = OverlayArenaPhysicalEnd;\n      partitionData->addressSpace.overlayArenaEnabled = true;\n   }\n\n   return { OverlayArenaVirtualStart, OverlayArenaSize };\n}\n\nvoid\ndisableOverlayArena()\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n\n   if (partitionData->addressSpace.overlayArenaEnabled) {\n      auto mapping = MemoryMap { };\n      mapping.vaddr = OverlayArenaVirtualStart;\n      mapping.paddr = OverlayArenaPhysicalStart;\n      mapping.size = OverlayArenaSize;\n      internal::unloadMapping(mapping);\n\n      partitionData->ramPartitionAllocation.overlayStart = phys_addr { 0 };\n      partitionData->ramPartitionAllocation.overlayEnd = phys_addr { 0 };\n      partitionData->addressSpace.overlayArenaEnabled = false;\n   }\n}\n\nstd::pair<phys_addr, uint32_t>\ngetAvailablePhysicalAddressRange()\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   return { partitionData->addressSpace.availStart,\n            partitionData->addressSpace.availSize };\n}\n\nstd::pair<phys_addr, uint32_t>\ngetDataPhysicalAddressRange()\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   return { partitionData->addressSpace.dataStart,\n            partitionData->addressSpace.dataSize };\n}\n\nstd::pair<virt_addr, uint32_t>\ngetVirtualMapAddressRange()\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   return { partitionData->addressSpace.virtualMapStart,\n            partitionData->addressSpace.virtualMapSize };\n}\n\nstd::pair<virt_addr, uint32_t>\ngetCodeGenVirtualRange()\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   return { partitionData->addressSpace.codeGenView.mappings[0].vaddr,\n            partitionData->addressSpace.codeGenView.mappings[0].size };\n}\n\nstatic bool\ncheckInVirtualMapRange(internal::AddressSpace *addressSpace,\n                       virt_addr addr,\n                       uint32_t size)\n{\n   return\n      addr >= addressSpace->virtualMapStart &&\n      (addr + size) < (addressSpace->virtualMapStart + addressSpace->virtualMapSize);\n}\n\nvirt_addr\nallocateVirtualAddress(virt_addr addr,\n                       uint32_t size,\n                       uint32_t align)\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   if (addr && !checkInVirtualMapRange(&partitionData->addressSpace, addr, size)) {\n      return virt_addr { 0 };\n   }\n\n   if (!align) {\n      align = PageSize;\n   }\n\n   if (align & (align - 1)) {\n      return virt_addr { 0 };\n   }\n\n   if (align < PageSize) {\n      return virt_addr { 0 };\n   }\n\n   if (!addr) {\n      auto range = cpu::findFreeVirtualAddressInRange(\n         { partitionData->addressSpace.virtualMapStart, partitionData->addressSpace.virtualMapSize },\n         size, align);\n      addr = range.start;\n      size = range.size;\n   }\n\n   if (!cpu::allocateVirtualAddress(addr, size)) {\n      return virt_addr { 0 };\n   }\n\n   return addr;\n}\n\nbool\nfreeVirtualAddress(virt_addr addr,\n                   uint32_t size)\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   if (!checkInVirtualMapRange(&partitionData->addressSpace, addr, size)) {\n      return false;\n   }\n\n   return cpu::freeVirtualAddress(addr, size);\n}\n\nQueryMemoryResult\nqueryVirtualAddress(cpu::VirtualAddress addr)\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   if (!checkInVirtualMapRange(&partitionData->addressSpace, addr, 0)) {\n      return QueryMemoryResult::Invalid;\n   }\n\n   switch (cpu::queryVirtualAddress(addr)) {\n   case cpu::VirtualMemoryType::MappedReadOnly:\n      return QueryMemoryResult::MappedReadOnly;\n   case cpu::VirtualMemoryType::MappedReadWrite:\n      return QueryMemoryResult::MappedReadWrite;\n   case cpu::VirtualMemoryType::Free:\n      return QueryMemoryResult::Free;\n   case cpu::VirtualMemoryType::Allocated:\n      return QueryMemoryResult::Allocated;\n   default:\n      return QueryMemoryResult::Invalid;\n   }\n}\n\nbool\nmapMemory(virt_addr virtAddr,\n          phys_addr physAddr,\n          uint32_t size,\n          MapMemoryPermission permission)\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   if (!checkInVirtualMapRange(&partitionData->addressSpace, virtAddr, size)) {\n      return false;\n   }\n\n   auto cpuPermission = cpu::MapPermission { };\n\n   if (permission == MapMemoryPermission::ReadOnly) {\n      cpuPermission = cpu::MapPermission::ReadOnly;\n   } else if (permission == MapMemoryPermission::ReadWrite) {\n      cpuPermission = cpu::MapPermission::ReadWrite;\n   } else {\n      gLog->error(\"Unexpected mapMemory permission: {}\",\n                  static_cast<unsigned>(permission));\n      return false;\n   }\n\n   return cpu::mapMemory(virtAddr, physAddr, size, cpuPermission);\n}\n\nbool\nunmapMemory(cpu::VirtualAddress addr,\n            uint32_t size)\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   if (!checkInVirtualMapRange(&partitionData->addressSpace, addr, size)) {\n      return false;\n   }\n\n   return cpu::unmapMemory(addr, size);\n}\n\nbool\nvalidateAddressRange(virt_addr address,\n                     uint32_t size)\n{\n   auto addressSpace = internal::getActiveAddressSpace();\n   auto view = addressSpace->perCoreView[cpu::this_core::id()];\n\n   for (auto i = 0u; i < view->numMappings; ++i) {\n      if (address >= view->mappings[i].vaddr &&\n         (address + size) <= (view->mappings[i].vaddr + view->mappings[i].size)) {\n         return true;\n      }\n   }\n\n   return false;\n}\n\nphys_addr\neffectiveToPhysical(virt_addr address)\n{\n   auto addressSpace = internal::getActiveAddressSpace();\n   auto view = addressSpace->perCoreView[cpu::this_core::id()];\n\n   if (addressSpace->overlayArenaEnabled &&\n       address >= OverlayArenaVirtualStart &&\n       address < OverlayArenaVirtualEnd) {\n      return OverlayArenaPhysicalStart + (address - OverlayArenaVirtualStart);\n   }\n\n   // Lookup in our mappings\n   for (auto i = 0u; i < view->numMappings; ++i) {\n      if (address < view->mappings[i].vaddr ||\n          address >= view->mappings[i].vaddr + view->mappings[i].size) {\n         continue;\n      }\n\n      return view->mappings[i].paddr +\n         static_cast<uint32_t>(address - view->mappings[i].vaddr);\n   }\n\n   // Lookup in user mappings\n   if (checkInVirtualMapRange(addressSpace, address, 0)) {\n      auto result = phys_addr { 0 };\n      if (cpu::virtualToPhysicalAddress(address, result)) {\n         return result;\n      }\n   }\n\n   return phys_addr { 0 };\n}\n\nvirt_addr\nphysicalToEffectiveCached(phys_addr address)\n{\n   auto addressSpace = internal::getActiveAddressSpace();\n   auto view = addressSpace->perCoreView[cpu::this_core::id()];\n\n   // Lookup in our mappings only for memory regions NOT marked as uncached\n   for (auto i = 0u; i < view->numMappings; ++i) {\n      if ((view->mappings[i].flags & MapUncached) ||\n          address < view->mappings[i].paddr ||\n          address >= view->mappings[i].paddr + view->mappings[i].size) {\n         continue;\n      }\n\n      return view->mappings[i].vaddr +\n         static_cast<uint32_t>(address - view->mappings[i].paddr);\n   }\n\n   return virt_addr { 0 };\n}\n\nvirt_addr\nphysicalToEffectiveUncached(phys_addr address)\n{\n   auto addressSpace = internal::getActiveAddressSpace();\n   auto view = addressSpace->perCoreView[cpu::this_core::id()];\n\n   // Lookup in our mappings only for memory regions marked as uncached\n   for (auto i = 0u; i < view->numMappings; ++i) {\n      if (!(view->mappings[i].flags & MapUncached) ||\n          address < view->mappings[i].paddr ||\n          address >= view->mappings[i].paddr + view->mappings[i].size) {\n         continue;\n      }\n\n      return view->mappings[i].vaddr +\n         static_cast<uint32_t>(address - view->mappings[i].paddr);\n   }\n\n   return virt_addr { 0 };\n}\n\nnamespace internal\n{\n\n/**\n * KiInitAddressSpace\n */\n\nstatic void\nsetAddressSpaceView(AddressSpaceView *view,\n                    MemoryMap *mappings,\n                    uint32_t numMappings,\n                    uint32_t flags)\n{\n   view->numMappings = 0u;\n\n   for (auto i = 0u; i < numMappings; ++i) {\n      auto &mapping = mappings[i];\n      if ((mapping.flags >> 16) & flags) {\n         view->mappings[view->numMappings++] = mapping;\n      }\n   }\n}\n\nAddressSpace *\ngetActiveAddressSpace()\n{\n   return sCoreActiveAddressSpace[cpu::this_core::id()];\n}\n\nvoid\nsetActiveAddressSpace(AddressSpace *addressSpace)\n{\n   sCoreActiveAddressSpace[cpu::this_core::id()] = addressSpace;\n}\n\nbool\ninitialiseAddressSpace(AddressSpace *addressSpace,\n                       RamPartitionId rampid,\n                       phys_addr codeStart,\n                       uint32_t codeSize,\n                       phys_addr dataStart,\n                       uint32_t dataSize,\n                       uint32_t a7,\n                       uint32_t a8,\n                       phys_addr availStart,\n                       uint32_t availSize,\n                       phys_addr codeGenStart,\n                       uint32_t codeGenSize,\n                       uint32_t codeGenCore,\n                       bool overlayArenaEnabled)\n{\n   auto baseFlags = 0u;\n   if (codeGenSize > MaxCodeGenSize) {\n      return false;\n   }\n\n   if (codeGenSize + codeSize > MaxTotalCodeSize) {\n      return false;\n   }\n\n   if (rampid == RamPartitionId::MainApplication) {\n      auto overlayArenaSize = 0u;\n      if (overlayArenaEnabled) {\n         overlayArenaSize = 0x20000000u;\n      }\n\n      addressSpace->virtualMapStart = virt_addr { 0xA0000000 } + overlayArenaSize;\n      addressSpace->virtualMapSize = 0x40000000u - overlayArenaSize;\n      addressSpace->virtualPageMap.fill(0x7FFF);\n\n      addressSpace->dataStart = dataStart;\n      addressSpace->dataSize = dataSize;\n\n      addressSpace->availStart = availStart;\n      addressSpace->availSize = availSize;\n\n      baseFlags |= MapMainApplication;\n   }\n\n   sMemoryMap[2].vaddr = virt_addr { 0x10000000u } - codeSize;\n   sMemoryMap[2].paddr = codeStart;\n   sMemoryMap[2].size = codeSize;\n\n   sMemoryMap[3].paddr = dataStart;\n   sMemoryMap[3].size = dataSize;\n\n   if (codeGenSize) {\n      auto &codeGenMap = sMemoryMap[1];\n      codeGenMap.size = codeGenSize;\n      codeGenMap.paddr = codeGenStart;\n      codeGenMap.flags |= (MapUnknown10 << 16);\n      setAddressSpaceView(&addressSpace->codeGenView,\n                          &codeGenMap,\n                          1,\n                          MapCodeGen | MapUnknown10);\n   } else {\n      // Disable codegen mapping\n      auto &codeGenMap = sMemoryMap[1];\n      codeGenMap.flags = 0;\n   }\n\n   setAddressSpaceView(&addressSpace->viewKernel,\n                       sMemoryMap,\n                       sMemoryMapSize,\n                       baseFlags | MapUnknown1 |\n                       MapUnknown6 | MapUnknown10 |\n                       MapUnknown11 | MapUnknown2);\n\n   setAddressSpaceView(&addressSpace->viewUnknownKernelProcess1,\n                       sMemoryMap,\n                       sMemoryMapSize,\n                       baseFlags | MapUnknown1 |\n                       MapUnknown6 | MapUnknown10 |\n                       MapUnknown11);\n\n   setAddressSpaceView(&addressSpace->viewLoader,\n                       sMemoryMap,\n                       sMemoryMapSize,\n                       baseFlags | MapUnknown1 |\n                       MapUnknown6 | MapUnknown10 |\n                       MapUnknown11 | MapUnknown4);\n\n   // TODO: For now we hardcode to viewLoader because we do not change views\n   addressSpace->perCoreView.fill(&addressSpace->viewLoader);\n   return true;\n}\n\nbool\nloadMapping(MemoryMap &mapping)\n{\n   if (mapping.paddr >= phys_addr { 0x0C000000 } &&\n       mapping.paddr <= phys_addr { 0x0D000000 }) {\n      // XXX: Physical register access is disabled for now.\n      return true;\n   }\n\n   if (mapping.paddr == phys_addr { 0xFFE80000 }) {\n      // XXX: Unknown memory\n      return true;\n   }\n\n   if (!cpu::allocateVirtualAddress(mapping.vaddr, mapping.size)) {\n      gLog->error(\"Unexpected failure allocating virtual address {} - {}\",\n                  mapping.vaddr, mapping.vaddr + mapping.size);\n      return false;\n   }\n\n   if (!cpu::mapMemory(mapping.vaddr, mapping.paddr, mapping.size, cpu::MapPermission::ReadWrite)) {\n      gLog->error(\"Unexpected failure mapping virtual address {} to physical address {}\",\n                  mapping.vaddr, mapping.paddr);\n      return false;\n   }\n\n   return true;\n}\n\nbool\nunloadMapping(MemoryMap &mapping)\n{\n   if (mapping.paddr >= phys_addr { 0x0C000000 } &&\n       mapping.paddr <= phys_addr { 0x0D000000 }) {\n      // XXX: Physical register access is disabled for now.\n      return true;\n   }\n\n   if (mapping.paddr == phys_addr { 0xFFE80000 }) {\n      // XXX: Unknown memory\n      return true;\n   }\n\n   if (!cpu::unmapMemory(mapping.vaddr, mapping.size)) {\n      gLog->error(\"Unexpected failure unmapping virtual address {} from physical address {}\",\n                  mapping.vaddr, mapping.paddr);\n      return false;\n   }\n\n   if (!cpu::freeVirtualAddress(mapping.vaddr, mapping.size)) {\n      gLog->error(\"Unexpected failure freeing virtual address {} - {}\",\n                  mapping.vaddr, mapping.vaddr + mapping.size);\n      return false;\n   }\n\n   return true;\n}\n\nbool\nloadAddressSpace(AddressSpace *addressSpace)\n{\n   if (!cpu::resetVirtualMemory()) {\n      decaf_abort(\"Unexpected failure resetting virtual memory\");\n   }\n\n   auto coreId = cpu::this_core::id();\n   if (coreId == cpu::InvalidCoreId) {\n      // We have to load the kernel address space before we are running on a cpu\n      // core, so in that case load the address space for core 1\n      coreId = 1;\n   }\n\n   auto view = addressSpace->perCoreView[coreId];\n   for (auto i = 0u; i < view->numMappings; ++i) {\n      auto &mapping = view->mappings[i];\n      if (!mapping.vaddr || !mapping.size) {\n         continue;\n      }\n\n      // FIXME: Because we do not fully emulate the mappings yet we need to\n      // check if paddr != 0, except paddr == 0 is valid for MEM1 @ 0xF4000000\n      if (!mapping.paddr && mapping.vaddr != virt_addr { 0xF4000000 }) {\n         continue;\n      }\n\n      loadMapping(mapping);\n   }\n\n   return true;\n}\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_mmu.h",
    "content": "#pragma once\n#include \"cafe_kernel_processid.h\"\n\n#include <array>\n#include <libcpu/be2_struct.h>\n#include <libcpu/mmu.h>\n#include <utility>\n\nnamespace cafe::kernel\n{\n\nstatic constexpr auto PageSize = 128 * 1024u;\n\nstruct MemoryMap\n{\n   virt_addr vaddr;\n   uint32_t size;\n   phys_addr paddr;\n   uint32_t flags;\n};\n\n// This must be kept in order with cafe_kernel_mmu.cpp:sMemoryMap\nenum class VirtualMemoryRegion\n{\n   CafeOS,\n   CodeGenArea,\n   AppCode,\n   AppData,\n   Overlay,\n   ForegroundBucket,\n   TilingApertures,\n   LoaderGlobals,\n   MEM1,\n   LoaderBounceBuffer,\n   SharedData,\n   Unknown_0xFB000000,\n   Registers_0xFC000000,\n   Registers_0xFC0C0000,\n   Registers_0xFC1E0000,\n   Registers_0xFC200000,\n   Registers_0xFC280000,\n   WriteGatherMemory_0xFC2A0000,\n   Registers_0xFC300000,\n   Registers_0xFC320000,\n   Registers_0xFD000000,\n   Unknown_0xFE000000,\n   KernelWorkAreaHeap,\n   Unknown_0xFF280000,\n   LockedCacheCore0,\n   LockedCacheCore1,\n   LockedCacheCore2,\n   Unknown_0xFFCE0000,\n   Kernel_0xFFE00000,\n   Kernel_0xFFE40000,\n   Kernel_0xFFE80000,\n   Unknown_0xFFF60000,\n   Unknown_0xFFF80000,\n   Unknown_0xFFFA0000,\n   Unknown_0xFFFC0000,\n   Unknown_0xFFFE0000,\n   Max,\n};\n\nenum class MapMemoryPermission\n{\n   ReadOnly    = 1,\n   ReadWrite   = 2,\n};\n\nenum class QueryMemoryResult\n{\n   Invalid = 0,\n   MappedReadOnly = 1,\n   MappedReadWrite = 2,\n   Free = 3,\n   Allocated = 4,\n};\n\nMemoryMap &\ngetVirtualMemoryMap(VirtualMemoryRegion region);\n\nstd::pair<virt_addr, uint32_t>\ngetForegroundBucket();\n\nstd::pair<virt_addr, uint32_t>\nenableOverlayArena();\n\nvoid\ndisableOverlayArena();\n\nstd::pair<phys_addr, uint32_t>\ngetAvailablePhysicalAddressRange();\n\nstd::pair<phys_addr, uint32_t>\ngetDataPhysicalAddressRange();\n\nstd::pair<virt_addr, uint32_t>\ngetVirtualMapAddressRange();\n\nstd::pair<virt_addr, uint32_t>\ngetCodeGenVirtualRange();\n\nvirt_addr\nallocateVirtualAddress(virt_addr addr,\n                       uint32_t size,\n                       uint32_t align);\n\nbool\nfreeVirtualAddress(virt_addr addr,\n                   uint32_t size);\n\nQueryMemoryResult\nqueryVirtualAddress(cpu::VirtualAddress addr);\n\nbool\nmapMemory(virt_addr virtAddr,\n          phys_addr physAddr,\n          uint32_t size,\n          MapMemoryPermission permission);\n\nbool\nunmapMemory(cpu::VirtualAddress addr,\n            uint32_t size);\n\nbool\nvalidateAddressRange(virt_addr address,\n                     uint32_t size);\n\nphys_addr\neffectiveToPhysical(virt_addr address);\n\ntemplate<typename Type>\ninline phys_ptr<Type>\neffectiveToPhysical(virt_ptr<Type> ptr)\n{\n   return phys_cast<Type *>(effectiveToPhysical(virt_cast<virt_addr>(ptr)));\n}\n\ntemplate<typename Type>\ninline phys_ptr<Type>\neffectiveToPhysical(be2_virt_ptr<Type> ptr)\n{\n   return phys_cast<Type *>(effectiveToPhysical(virt_cast<virt_addr>(ptr)));\n}\n\nvirt_addr\nphysicalToEffectiveCached(phys_addr address);\n\nvirt_addr\nphysicalToEffectiveUncached(phys_addr address);\n\nnamespace internal\n{\n\nstruct AddressSpaceView\n{\n   std::array<MemoryMap, 40> mappings;\n   uint32_t numMappings;\n};\n\nstruct AddressSpace\n{\n   bool overlayArenaEnabled;\n   virt_addr virtualMapStart;\n   uint32_t virtualMapSize;\n   phys_addr dataStart;\n   uint32_t dataSize;\n   phys_addr availStart;\n   uint32_t availSize;\n\n   std::array<uint16_t, 64> virtualPageMap;\n\n   std::array<AddressSpaceView *, 3> perCoreView;\n   AddressSpaceView codeGenView;\n   AddressSpaceView viewKernel;\n   AddressSpaceView viewUnknownKernelProcess1;\n   AddressSpaceView viewLoader;\n};\n\nAddressSpace *\ngetActiveAddressSpace();\n\nvoid\nsetActiveAddressSpace(AddressSpace *addressSpace);\n\nbool\ninitialiseAddressSpace(AddressSpace *map,\n                       RamPartitionId rampid,\n                       phys_addr codeStart,\n                       uint32_t codeSize,\n                       phys_addr dataStart,\n                       uint32_t dataSize,\n                       uint32_t a7,\n                       uint32_t a8,\n                       phys_addr availStart,\n                       uint32_t availSize,\n                       phys_addr codeGenStart,\n                       uint32_t codeGenSize,\n                       uint32_t codeGenCore,\n                       bool overlayArenaEnabled);\n\nbool\nloadAddressSpace(AddressSpace *info);\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_process.cpp",
    "content": "#include \"cafe_kernel.h\"\n#include \"cafe_kernel_loader.h\"\n#include \"cafe_kernel_mmu.h\"\n#include \"cafe_kernel_mcp.h\"\n#include \"cafe_kernel_process.h\"\n\n#include \"ios/mcp/ios_mcp_mcp_types.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/libraries/cafe_hle.h\"\n#include \"cafe/loader/cafe_loader_globals.h\"\n#include \"cafe/loader/cafe_loader_loaded_rpl.h\"\n\n#include <array>\n#include <libcpu/cpu_control.h>\n#include <libcpu/cpu_config.h>\n\nnamespace cafe::kernel::internal\n{\n\nconstexpr auto MinCodeSize = 0x20000u;\nconstexpr auto MaxCodeSize = 0xE000000u;\nconstexpr auto MinDataSize = 0x700000u;\nconstexpr auto UnkReserveSize = 0x60000u;\n\nstruct CoreProcessData\n{\n   RamPartitionId rampid;\n   UniqueProcessId upid;\n   KernelProcessId pid;\n};\n\nstatic std::array<CoreProcessData, 3>\nsCoreProcessData;\n\nstatic std::array<RamPartitionData, NumRamPartitions>\nsRamPartitionData;\n\nstruct RamPartitionConfig\n{\n   RamPartitionId id;\n   phys_addr base;\n   uint32_t size;\n};\n\nstatic constexpr RamPartitionConfig DevRamPartitionConfig[] = {\n   { RamPartitionId::Kernel,           phys_addr { 0 },                    0 },\n   { RamPartitionId::OverlayMenu,      phys_addr { 0x28000000 },   0x8000000 },\n   { RamPartitionId::Root,             phys_addr { 0x30000000 },   0x2000000 },\n   { RamPartitionId::ErrorDisplay,     phys_addr { 0x33000000 },   0x1000000 },\n   { RamPartitionId::OverlayApp,       phys_addr { 0x34000000 },  0x1C000000 },\n   { RamPartitionId::MainApplication,  phys_addr { 0x50000000 },  0x80000000 },\n};\n\nstatic constexpr RamPartitionConfig RetailRamPartitionConfig[] = {\n   { RamPartitionId::Kernel,           phys_addr { 0 },                    0 },\n   { RamPartitionId::OverlayMenu,      phys_addr { 0x28000000 },   0x8000000 },\n   { RamPartitionId::Root,             phys_addr { 0x30000000 },   0x2000000 },\n   { RamPartitionId::ErrorDisplay,     phys_addr { 0x33000000 },   0x1000000 },\n   { RamPartitionId::OverlayApp,       phys_addr { 0x34000000 },  0x1C000000 },\n   { RamPartitionId::MainApplication,  phys_addr { 0x50000000 },  0x40000000 },\n};\n\nstatic const RamPartitionConfig *\nsRamPartitionConfig = RetailRamPartitionConfig;\n\nstatic void\nallocateRamPartition(RamPartitionId rampid,\n                     uint32_t max_size,\n                     uint32_t avail_size,\n                     uint32_t codegen_size,\n                     uint32_t max_codesize,\n                     uint32_t codegen_core,\n                     RamPartitionAllocation *info)\n{\n   const RamPartitionConfig *config = nullptr;\n   for (auto i = 0u; i < NumRamPartitions; ++i) {\n      if (sRamPartitionConfig[i].id == rampid) {\n         config = &sRamPartitionConfig[i];\n         break;\n      }\n   }\n\n   decaf_check(config);\n\n   max_codesize = std::max<uint32_t>(max_codesize, MinCodeSize);\n   max_codesize = std::min<uint32_t>(max_codesize, MaxCodeSize);\n   max_codesize = align_up(max_codesize, PageSize);\n   avail_size = align_up(avail_size, PageSize);\n   codegen_size = align_up(codegen_size, PageSize);\n\n   decaf_check(max_codesize < max_size);\n   decaf_check(max_codesize <= MaxCodeSize);\n\n   info->dataStart = config->base;\n   info->codeEnd = config->base + config->size;\n   info->codeStart = align_down(info->codeEnd - max_codesize, PageSize);\n   info->codeGenStart = align_down(info->codeStart - codegen_size, PageSize);\n   info->availStart = align_down(info->codeGenStart - UnkReserveSize - avail_size, PageSize);\n   info->codegen_core = codegen_core;\n   info->overlayStart = phys_addr { 0 };\n   info->overlayEnd = phys_addr { 0 };\n   decaf_check(info->availStart - info->dataStart >= MinDataSize);\n}\n\nRamPartitionData *\ngetCurrentRamPartitionData()\n{\n   return getRamPartitionData(getCurrentRamPartitionId());\n}\n\nRamPartitionId\ngetCurrentRamPartitionId()\n{\n   auto core = cpu::this_core::id();\n   if (core == cpu::InvalidCoreId) {\n      core = 1;\n   }\n\n   return sCoreProcessData[core].rampid;\n}\n\nKernelProcessId\ngetCurrentKernelProcessId()\n{\n   auto core = cpu::this_core::id();\n   if (core == cpu::InvalidCoreId) {\n      core = 1;\n   }\n\n   return getCurrentRamPartitionData()->coreKernelProcessId[core];\n}\n\nUniqueProcessId\ngetCurrentUniqueProcessId()\n{\n   return getCurrentRamPartitionData()->uniqueProcessId;\n}\n\nios::mcp::MCPTitleId\ngetCurrentTitleId()\n{\n   return getCurrentRamPartitionData()->titleInfo.titleId;\n}\n\nRamPartitionData *\ngetRamPartitionData(RamPartitionId id)\n{\n   return &sRamPartitionData[static_cast<size_t>(id)];\n}\n\nvoid\nsetCoreToProcessId(RamPartitionId ramPartitionId,\n                   KernelProcessId kernelProcessId)\n{\n   auto coreId = cpu::this_core::id();\n   auto partitionData = getRamPartitionData(ramPartitionId);\n\n   // Check if we need to set a new ram ramPartitionId\n   if (sCoreProcessData[coreId].rampid != ramPartitionId) {\n      if (coreId == 1) {\n         // TODO: When we have per-core address space then do not check coreId\n         loadAddressSpace(&partitionData->addressSpace);\n      }\n\n      setActiveAddressSpace(&partitionData->addressSpace);\n      sCoreProcessData[coreId].rampid = ramPartitionId;\n   }\n\n   partitionData->coreKernelProcessId[coreId] = kernelProcessId;\n}\n\nstatic void\ninitialisePerCoreStartInfo(ProcessPerCoreStartInfo *core0,\n                           ProcessPerCoreStartInfo *core1,\n                           ProcessPerCoreStartInfo *core2)\n{\n   auto partitionData = getCurrentRamPartitionData();\n   auto stackSize0 = std::max<uint32_t>(0x4000u, partitionData->startInfo.stackSize);\n   auto stackSize1 = std::max<uint32_t>(0x4000u, partitionData->startInfo.stackSize);\n   auto stackSize2 = std::max<uint32_t>(0x4000u, partitionData->startInfo.stackSize);\n   auto exceptionStackSize0 = 0x1000u;\n   auto exceptionStackSize1 = 0x1000u;\n   auto exceptionStackSize2 = 0x1000u;\n\n   core0->entryPoint = partitionData->startInfo.entryPoint;\n   core1->entryPoint = partitionData->startInfo.entryPoint;\n   core2->entryPoint = partitionData->startInfo.entryPoint;\n   core0->sdaBase = partitionData->startInfo.sdaBase;\n   core1->sdaBase = partitionData->startInfo.sdaBase;\n   core2->sdaBase = partitionData->startInfo.sdaBase;\n   core0->sda2Base = partitionData->startInfo.sda2Base;\n   core1->sda2Base = partitionData->startInfo.sda2Base;\n   core2->sda2Base = partitionData->startInfo.sda2Base;\n   core0->unk0x1C = partitionData->startInfo.unk0x10;\n   core1->unk0x1C = partitionData->startInfo.unk0x10;\n   core2->unk0x1C = partitionData->startInfo.unk0x10;\n\n   core0->stackEnd = align_up(partitionData->startInfo.dataAreaStart, 8);\n   core1->stackEnd = align_up(core0->stackEnd + stackSize0, 8);\n   core2->stackEnd = align_up(core1->stackEnd + stackSize1, 8);\n\n   core0->stackBase = align_down(core0->stackEnd + stackSize0, 8) - 8;\n   core1->stackBase = align_down(core1->stackEnd + stackSize1, 8) - 8;\n   core2->stackBase = align_down(core2->stackEnd + stackSize2, 8) - 8;\n\n   core0->exceptionStackEnd = align_up(core2->stackEnd + stackSize2, 8);\n   core1->exceptionStackEnd = align_up(core0->exceptionStackEnd + exceptionStackSize0, 8);\n   core2->exceptionStackEnd = align_up(core1->exceptionStackEnd + exceptionStackSize1, 8);\n\n   core0->exceptionStackBase = align_down(core0->exceptionStackEnd + exceptionStackSize0, 8) - 8;\n   core1->exceptionStackBase = align_down(core1->exceptionStackEnd + exceptionStackSize1, 8) - 8;\n   core2->exceptionStackBase = align_down(core2->exceptionStackEnd + exceptionStackSize2, 8) - 8;\n\n   auto stacksEnd = align_up(core2->exceptionStackEnd + exceptionStackSize2, 8);\n   decaf_check(stacksEnd > partitionData->startInfo.dataAreaStart &&\n               stacksEnd <= partitionData->startInfo.dataAreaEnd);\n\n   partitionData->startInfo.dataAreaStart = stacksEnd;\n}\n\nloader::RPL_STARTINFO *\ngetCurrentRamPartitionStartInfo()\n{\n   return &getCurrentRamPartitionData()->startInfo;\n}\n\nvoid\nloadGameProcess(std::string_view rpx,\n                virt_ptr<ios::mcp::MCPPPrepareTitleInfo> titleInfo)\n{\n   auto rampid = RamPartitionId::MainApplication;\n   auto partitionData = getRamPartitionData(rampid);\n   partitionData->argstr = virt_addrof(titleInfo->argstr).get();\n   partitionData->uniqueProcessId = UniqueProcessId::Game;\n   partitionData->titleId = titleInfo->titleId;\n\n   std::memcpy(&partitionData->titleInfo, titleInfo.get(),\n               sizeof(ios::mcp::MCPPPrepareTitleInfo));\n\n   // Initialise RAM partition\n   allocateRamPartition(rampid,\n                        titleInfo->max_size,\n                        titleInfo->avail_size,\n                        titleInfo->codegen_size,\n                        titleInfo->max_codesize,\n                        titleInfo->codegen_core,\n                        &partitionData->ramPartitionAllocation);\n\n   partitionData->overlayArenaEnabled = false;\n   if (rampid == RamPartitionId::MainApplication) {\n      partitionData->overlayArenaEnabled = !!titleInfo->overlay_arena;\n   }\n\n   initialiseAddressSpace(\n      &partitionData->addressSpace,\n      rampid,\n      partitionData->ramPartitionAllocation.codeStart,\n      static_cast<uint32_t>(partitionData->ramPartitionAllocation.codeEnd - partitionData->ramPartitionAllocation.codeStart),\n      partitionData->ramPartitionAllocation.dataStart,\n      static_cast<uint32_t>(partitionData->ramPartitionAllocation.availStart - partitionData->ramPartitionAllocation.dataStart),\n      0, 0,\n      partitionData->ramPartitionAllocation.availStart,\n      static_cast<uint32_t>(partitionData->ramPartitionAllocation.codeGenStart - partitionData->ramPartitionAllocation.availStart),\n      partitionData->ramPartitionAllocation.codeGenStart,\n      static_cast<uint32_t>(partitionData->ramPartitionAllocation.codeStart - partitionData->ramPartitionAllocation.codeGenStart),\n      partitionData->ramPartitionAllocation.codegen_core,\n      partitionData->overlayArenaEnabled);\n\n   // Switch to the kernel process\n   setCoreToProcessId(rampid,\n                      KernelProcessId::Loader);\n\n   initialiseCoreProcess(cpu::this_core::id(),\n                         rampid,\n                         UniqueProcessId::Game,\n                         KernelProcessId::Loader);\n\n   // Switch to the IOS MCP title\n   mcpSwitchTitle(rampid,\n                  partitionData->ramPartitionAllocation.dataStart,\n                  partitionData->ramPartitionAllocation.codeGenStart,\n                  partitionData->ramPartitionAllocation.codeEnd);\n\n   // Run the loader\n   auto num_codearea_heap_blocks = titleInfo->num_codearea_heap_blocks;\n   if (!num_codearea_heap_blocks) {\n      num_codearea_heap_blocks = 256u;\n   }\n\n   auto num_workarea_heap_blocks = titleInfo->num_workarea_heap_blocks;\n   if (!num_workarea_heap_blocks) {\n      num_workarea_heap_blocks = 512u;\n   }\n\n   cafe::loader::setLoadRpxName(rpx);\n   KiRPLStartup(\n      cafe::kernel::UniqueProcessId::Kernel,\n      cafe::kernel::UniqueProcessId::Game,\n      cafe::kernel::ProcessFlags::get(0).debugLevel(cafe::kernel::DebugLevel::Verbose),\n      num_codearea_heap_blocks + num_workarea_heap_blocks,\n      static_cast<uint32_t>(partitionData->ramPartitionAllocation.codeEnd - partitionData->ramPartitionAllocation.codeStart),\n      static_cast<uint32_t>(partitionData->ramPartitionAllocation.availStart - partitionData->ramPartitionAllocation.dataStart),\n      0);\n\n   // Notify jit of read only sections in the RPX\n   if (cpu::config()->jit.rodataReadOnly) {\n      auto loadedRpx = cafe::loader::getGlobalStorage()->loadedRpx;\n      auto shStrSection = virt_ptr<char> { nullptr };\n      if (auto shstrndx = loadedRpx->elfHeader.shstrndx) {\n         shStrSection = virt_cast<char *>(loadedRpx->sectionAddressBuffer[shstrndx]);\n      }\n\n      for (auto i = 0u; i < loadedRpx->elfHeader.shnum; ++i) {\n         auto sectionHeader =\n            virt_cast<loader::rpl::SectionHeader *>(\n               virt_cast<virt_addr>(loadedRpx->sectionHeaderBuffer) +\n               (i * loadedRpx->elfHeader.shentsize));\n         auto sectionAddress = loadedRpx->sectionAddressBuffer[i];\n         if (!sectionAddress ||\n             sectionHeader->type != loader::rpl::SHT_PROGBITS) {\n            continue;\n         }\n\n         if (shStrSection && sectionHeader->name) {\n            auto name = shStrSection + sectionHeader->name;\n            if (strcmp(name.get(), \".rodata\") == 0) {\n               cpu::addJitReadOnlyRange(sectionAddress.getAddress(),\n                                        sectionHeader->size);\n               continue;\n            }\n         }\n\n         if (!(sectionHeader->flags & loader::rpl::SHF_WRITE)) {\n            // TODO: Fix me\n            // When we have a small section, e.g. .syscall section with\n            // sectionHeader->size == 8, we seem to break binrec\n            //cpu::addJitReadOnlyRange(sectionAddress.getAddress(),\n            //                         sectionHeader->size);\n         }\n      }\n   }\n\n   // Run the HLE relocation for coreinit.\n   auto &startInfo = cafe::loader::getKernelIpcStorage()->startInfo;\n   auto coreinitRpl = startInfo.coreinit;\n   cafe::hle::relocateLibrary(\n      std::string_view { coreinitRpl->moduleNameBuffer.get(), coreinitRpl->moduleNameLen },\n      virt_cast<virt_addr>(coreinitRpl->textBuffer),\n      virt_cast<virt_addr>(coreinitRpl->dataBuffer)\n   );\n}\n\n\n/**\n * KiProcess_FinishInitAndPreload\n */\nvoid\nfinishInitAndPreload()\n{\n   auto partitionData = getCurrentRamPartitionData();\n   auto loaderIpcStorage = cafe::loader::getKernelIpcStorage();\n   partitionData->startInfo = loaderIpcStorage->startInfo;\n   partitionData->loadedRpx = loaderIpcStorage->rpxModule;\n   partitionData->loadedModuleList = loaderIpcStorage->loadedModuleList;\n\n   initialisePerCoreStartInfo(&partitionData->perCoreStartInfo[0],\n                              &partitionData->perCoreStartInfo[1],\n                              &partitionData->perCoreStartInfo[2]);\n\n   // KiProcess_Launch\n   using EntryPointFn = virt_func_ptr<void()>;\n   auto entryPoint = partitionData->perCoreStartInfo[cpu::this_core::id()].entryPoint;\n   cafe::invoke(cpu::this_core::state(),\n                virt_func_cast<EntryPointFn>(entryPoint));\n}\n\nvoid\ninitialiseCoreProcess(int coreId,\n                      RamPartitionId rampid,\n                      UniqueProcessId upid,\n                      KernelProcessId pid)\n{\n   auto &data = sCoreProcessData[coreId];\n   data.rampid = rampid;\n   data.upid = upid;\n   data.pid = pid;\n}\n\nvoid\ninitialiseProcessData()\n{\n   for (auto i = 0u; i < sRamPartitionData.size(); ++i) {\n      auto &data = sRamPartitionData[i];\n      // data.state = 0\n      // data.field_0 = -1\n      data.ramPartitionId = static_cast<RamPartitionId>(i);\n      data.coreKernelProcessId[0] = KernelProcessId::Invalid;\n      data.coreKernelProcessId[1] = KernelProcessId::Invalid;\n      data.coreKernelProcessId[2] = KernelProcessId::Invalid;\n   }\n}\n\n} // namespace cafe::kernel::internal\n\n\nnamespace cafe::kernel\n{\n\nvoid\nexitProcess(int code)\n{\n   auto partitionData = internal::getCurrentRamPartitionData();\n   partitionData->exitCode = code;\n   internal::exit();\n}\n\nint\ngetProcessExitCode(RamPartitionId rampid)\n{\n   return internal::getRamPartitionData(rampid)->exitCode;\n}\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_process.h",
    "content": "#pragma once\n#include \"cafe/kernel/cafe_kernel_mmu.h\"\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n#include \"cafe/loader/cafe_loader_init.h\"\n#include \"ios/mcp/ios_mcp_mcp_types.h\"\n\n#include <array>\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::loader\n{\nstruct RPL_STARTINFO;\n};\n\nnamespace cafe::kernel\n{\n\nvoid\nexitProcess(int code);\n\nint\ngetProcessExitCode(RamPartitionId rampid);\n\nnamespace internal\n{\n\nstruct ProcessPerCoreStartInfo\n{\n   UNKNOWN(0x8);\n   be2_val<virt_addr> stackBase;\n   be2_val<virt_addr> stackEnd;\n   be2_val<uint32_t> sda2Base;\n   be2_val<uint32_t> sdaBase;\n   UNKNOWN(0x4);\n   be2_val<uint32_t> unk0x1C;\n   be2_val<virt_addr> entryPoint;\n   be2_val<virt_addr> exceptionStackBase;\n   be2_val<virt_addr> exceptionStackEnd;\n};\nCHECK_OFFSET(ProcessPerCoreStartInfo, 0x08, stackBase);\nCHECK_OFFSET(ProcessPerCoreStartInfo, 0x0C, stackEnd);\nCHECK_OFFSET(ProcessPerCoreStartInfo, 0x10, sda2Base);\nCHECK_OFFSET(ProcessPerCoreStartInfo, 0x14, sdaBase);\nCHECK_OFFSET(ProcessPerCoreStartInfo, 0x1C, unk0x1C);\nCHECK_OFFSET(ProcessPerCoreStartInfo, 0x20, entryPoint);\nCHECK_OFFSET(ProcessPerCoreStartInfo, 0x24, exceptionStackBase);\nCHECK_OFFSET(ProcessPerCoreStartInfo, 0x28, exceptionStackEnd);\nCHECK_SIZE(ProcessPerCoreStartInfo, 0x2C);\n\nstruct RamPartitionAllocation\n{\n   phys_addr dataStart;\n   phys_addr availStart;\n   phys_addr codeGenStart;\n   phys_addr codeStart;\n   phys_addr codeEnd;\n   uint32_t codegen_core;\n   phys_addr overlayStart;\n   phys_addr overlayEnd;\n};\n\nstruct RamPartitionData\n{\n   UniqueProcessId uniqueProcessId;\n   RamPartitionId ramPartitionId;\n   ios::mcp::MCPTitleId titleId;\n   std::array<KernelProcessId, 3> coreKernelProcessId;\n   loader::RPL_STARTINFO startInfo;\n   internal::AddressSpace addressSpace;\n   RamPartitionAllocation ramPartitionAllocation;\n   std::string argstr;\n   virt_ptr<loader::LOADED_RPL> loadedRpx;\n   virt_ptr<loader::LOADED_RPL> loadedModuleList;\n   ios::mcp::MCPPPrepareTitleInfo titleInfo;\n   bool overlayArenaEnabled;\n   std::array<ProcessPerCoreStartInfo, 3> perCoreStartInfo;\n   int exitCode;\n};\n// CHECK_OFFSET(RamPartitionData, 0x04, rampId);\n// CHECK_OFFSET(RamPartitionData, 0x08, titleId);\n// CHECK_OFFSET(RamPartitionData, 0x10, sdkVersion);\n// CHECK_OFFSET(RamPartitionData, 0x14, titleVersion);\n// CHECK_OFFSET(RamPartitionData, 0x18, state);\n// CHECK_OFFSET(RamPartitionData, 0x1C, perCoreUniqueProcessId);\n// CHECK_OFFSET(RamPartitionData, 0x28, startInfo);\n// CHECK_OFFSET(RamPartitionData, 0x50, addressSpace);\n// CHECK_OFFSET(RamPartitionData, 0xE58, cmdFlags);\n//\n// CHECK_OFFSET(RamPartitionData, 0xE80, ramPartitionAllocation);\n// CHECK_OFFSET(RamPartitionData, 0xEA0, numCodeAreaHeapBlocks);\n// CHECK_OFFSET(RamPartitionData, 0xEA4, argstr);\n// CHECK_OFFSET(RamPartitionData, 0xEA8, loadedRpx);\n// CHECK_OFFSET(RamPartitionData, 0xEAC, loadedModuleList);\n//\n// CHECK_OFFSET(RamPartitionData, 0xECC, titleInfo);\n//\n// CHECK_OFFSET(RamPartitionData, 0x1118, userExceptionHandlersCore0);\n// CHECK_OFFSET(RamPartitionData, 0x11CC, userExceptionHandlersCore1);\n// CHECK_OFFSET(RamPartitionData, 0x1180, userExceptionHandlersCore2);\n//\n// CHECK_OFFSET(RamPartitionData, 0x1344, titleLoc);\n// CHECK_OFFSET(RamPartitionData, 0x1348, overlay_arena);\n//\n// CHECK_OFFSET(RamPartitionData, 0x1698, perCoreStartInfo);\n// CHECK_SIZE(RamPartitionData, 0x17a0);\n\nRamPartitionData *\ngetCurrentRamPartitionData();\n\nRamPartitionId\ngetCurrentRamPartitionId();\n\nKernelProcessId\ngetCurrentKernelProcessId();\n\nUniqueProcessId\ngetCurrentUniqueProcessId();\n\nios::mcp::MCPTitleId\ngetCurrentTitleId();\n\nloader::RPL_STARTINFO *\ngetCurrentRamPartitionStartInfo();\n\nRamPartitionData *\ngetRamPartitionData(RamPartitionId id);\n\nvoid\nsetCoreToProcessId(RamPartitionId ramPartitionId,\n                   KernelProcessId kernelProcessId);\n\nvoid\nloadGameProcess(std::string_view rpx,\n                virt_ptr<ios::mcp::MCPPPrepareTitleInfo> titleInfo);\n\nvoid\nfinishInitAndPreload();\n\nvoid\ninitialiseCoreProcess(int coreId,\n                      RamPartitionId rampid,\n                      UniqueProcessId upid,\n                      KernelProcessId pid);\n\nvoid\ninitialiseProcessData();\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_processid.cpp",
    "content": "#include \"cafe_kernel_processid.h\"\n\nnamespace cafe::kernel\n{\n\nRamPartitionId\ngetRamPartitionIdFromUniqueProcessId(UniqueProcessId id)\n{\n   switch (id) {\n   case UniqueProcessId::Kernel:\n      return RamPartitionId::Kernel;\n   case UniqueProcessId::Root:\n      return RamPartitionId::Root;\n   case UniqueProcessId::HomeMenu:\n      return RamPartitionId::MainApplication;\n   case UniqueProcessId::TV:\n      return RamPartitionId::OverlayApp;\n   case UniqueProcessId::EManual:\n      return RamPartitionId::OverlayApp;\n   case UniqueProcessId::OverlayMenu:\n      return RamPartitionId::OverlayMenu;\n   case UniqueProcessId::ErrorDisplay:\n      return RamPartitionId::ErrorDisplay;\n   case UniqueProcessId::MiniMiiverse:\n      return RamPartitionId::OverlayApp;\n   case UniqueProcessId::InternetBrowser:\n      return RamPartitionId::OverlayApp;\n   case UniqueProcessId::Miiverse:\n      return RamPartitionId::OverlayApp;\n   case UniqueProcessId::EShop:\n      return RamPartitionId::OverlayApp;\n   case UniqueProcessId::FLV:\n      return RamPartitionId::OverlayApp;\n   case UniqueProcessId::DownloadManager:\n      return RamPartitionId::OverlayApp;\n   case UniqueProcessId::Game:\n      return RamPartitionId::MainApplication;\n   default:\n      decaf_abort(fmt::format(\"Unknown UniqueProcessId {}\", static_cast<uint32_t>(id)));\n   }\n}\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_processid.h",
    "content": "#pragma once\n#include <cstdint>\n#include <common/bitfield.h>\n\nnamespace cafe::kernel\n{\n\nenum class UniqueProcessId : int32_t\n{\n   Invalid                 = -1,\n   Kernel                  = 0,\n   Root                    = 1,\n   HomeMenu                = 2,\n   TV                      = 3,\n   EManual                 = 4,\n   OverlayMenu             = 5,\n   ErrorDisplay            = 6,\n   MiniMiiverse            = 7,\n   InternetBrowser         = 8,\n   Miiverse                = 9,\n   EShop                   = 10,\n   FLV                     = 11,\n   DownloadManager         = 12,\n   Game                    = 15,\n};\n\nconstexpr auto NumRamPartitions = 8;\n\nenum class RamPartitionId : int32_t\n{\n   Invalid                 = -1,\n   Kernel                  = 0,\n   Root                    = 1,\n   Loader                  = 2,\n   OverlayApp              = 4,\n   OverlayMenu             = 5,\n   ErrorDisplay            = 6,\n   MainApplication         = 7,\n};\n\nenum class KernelProcessId : int32_t\n{\n   Invalid                 = -1,\n   Kernel                  = 0,\n   Loader                  = 2,\n};\n\nenum class DebugLevel : uint32_t\n{\n   Error                   = 0,\n   Warn                    = 1,\n   Info                    = 2,\n   Notice                  = 3,\n   Verbose                 = 7,\n};\n\nBITFIELD_BEG(ProcessFlags, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, isFirstProcess);\n   BITFIELD_ENTRY(1, 1, bool, disableSharedLibraries);\n   BITFIELD_ENTRY(9, 3, DebugLevel, debugLevel);\n   BITFIELD_ENTRY(12, 1, bool, unkBit12);\n   BITFIELD_ENTRY(27, 1, bool, isDebugMode);\n   BITFIELD_ENTRY(29, 1, bool, isColdBoot);\nBITFIELD_END\n\nusing TitleId = uint64_t;\n\nRamPartitionId\ngetRamPartitionIdFromUniqueProcessId(UniqueProcessId id);\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_shareddata.cpp",
    "content": "#include \"cafe_kernel_mcp.h\"\n#include \"cafe_kernel_shareddata.h\"\n\n#include \"decaf_config.h\"\n#include \"decaf.h\"\n\n#include <common/align.h>\n#include <fstream>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::kernel\n{\n\nstatic SharedArea sFontChinese;\nstatic SharedArea sFontKorean;\nstatic SharedArea sFontStandard;\nstatic SharedArea sFontTaiwanese;\n\nstatic uint32_t\nloadSharedData(const char *filename,\n               SharedArea &area,\n               virt_addr addr)\n{\n   auto fileSize = uint32_t { 0 };\n   auto error = internal::mcpGetFileLength(filename,\n                                           &fileSize,\n                                           ios::mcp::MCPFileType::SharedDataContent,\n                                           0);\n   if (error < ios::Error::OK) {\n      return 0;\n   }\n\n   error = internal::mcpLoadFile(filename,\n                                 virt_cast<void *>(addr),\n                                 fileSize,\n                                 0,\n                                 ios::mcp::MCPFileType::SharedDataContent,\n                                 UniqueProcessId::Kernel);\n   if (error < ios::Error::OK) {\n      return 0;\n   }\n\n   area.address = addr;\n   area.size = fileSize;\n   return fileSize;\n}\n\nstatic uint32_t\nloadResourcesFile(const char *filename,\n                  SharedArea &area,\n                  virt_addr addr)\n{\n   auto file = std::ifstream { decaf::getResourcePath(std::string(\"fonts/\") + filename),\n                               std::ifstream::in | std::ifstream::binary };\n\n   if (!file.is_open()) {\n      area.size = 0u;\n      area.address = virt_addr { 0 };\n      return 0;\n   }\n\n   file.seekg(0, std::ifstream::end);\n   area.size = static_cast<uint32_t>(file.tellg());\n   area.address = addr;\n\n   file.seekg(0, std::ifstream::beg);\n   file.read(virt_cast<char *>(area.address).get(), area.size);\n   file.close();\n\n   return area.size;\n}\n\nvoid\nloadShared()\n{\n   auto addr = virt_addr { 0xF8000000 };\n\n   // FontChinese\n   auto size = loadSharedData(\"CafeCn.ttf\", sFontChinese, addr);\n   if (!size) {\n      size = loadResourcesFile(\"CafeCn.ttf\", sFontChinese, addr);\n   }\n\n   addr = align_up(addr + size, 0x10);\n\n   // FontKorean\n   size = loadSharedData(\"CafeKr.ttf\", sFontKorean, addr);\n   if (!size) {\n      size = loadResourcesFile(\"CafeKr.ttf\", sFontKorean, addr);\n   }\n\n   addr = align_up(addr + size, 0x10);\n\n   // FontStandard\n   size = loadSharedData(\"CafeStd.ttf\", sFontStandard, addr);\n   if (!size) {\n      size = loadResourcesFile(\"CafeStd.ttf\", sFontStandard, addr);\n   }\n\n   addr = align_up(addr + size, 0x10);\n\n   // FontTaiwanese\n   size = loadSharedData(\"CafeTw.ttf\", sFontTaiwanese, addr);\n   if (!size) {\n      size = loadResourcesFile(\"CafeTw.ttf\", sFontTaiwanese, addr);\n   }\n\n   addr = align_up(addr + size, 0x10);\n}\n\nSharedArea\ngetSharedArea(SharedAreaId id)\n{\n   auto area = SharedArea { };\n\n   switch (id) {\n   case SharedAreaId::FontChinese:\n      area = sFontChinese;\n      break;\n   case SharedAreaId::FontKorean:\n      area = sFontKorean;\n      break;\n   case SharedAreaId::FontStandard:\n      area = sFontStandard;\n      break;\n   case SharedAreaId::FontTaiwanese:\n      area = sFontTaiwanese;\n      break;\n   default:\n      area.address = virt_addr { 0 };\n      area.size = 0u;\n   }\n\n   return area;\n}\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_shareddata.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::kernel\n{\n\nenum class SharedAreaId : uint32_t\n{\n   FontChinese    = 0xFFCAFE01u,\n   FontKorean     = 0xFFCAFE02u,\n   FontStandard   = 0xFFCAFE03u,\n   FontTaiwanese  = 0xFFCAFE04u,\n};\n\nstruct SharedArea\n{\n   virt_addr address;\n   uint32_t size;\n};\n\nvoid\nloadShared();\n\nSharedArea\ngetSharedArea(SharedAreaId id);\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_userdrivers.cpp",
    "content": "#include \"cafe_kernel_userdrivers.h\"\n#include \"cafe_kernel_lock.h\"\n#include \"cafe_kernel_heap.h\"\n#include \"cafe_kernel_process.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n\n#include <cctype>\n#include <common/strutils.h>\n\nnamespace cafe::kernel\n{\n\nstruct UserDriver\n{\n   be2_array<char, 64> name;\n   be2_val<UniqueProcessId> ownerUpid;\n   be2_val<uint32_t> unk0x44;\n   be2_virt_ptr<UserDriver> next;\n};\n\nstruct StaticUserDriversData\n{\n   be2_virt_ptr<UserDriver> registeredDrivers;\n};\n\nstatic virt_ptr<StaticUserDriversData>\nsUserDriversData = nullptr;\n\nstatic internal::SpinLock\nsDriverLock { 0 };\n\nint32_t\nregisterUserDriver(virt_ptr<const char> name,\n                   uint32_t nameLen,\n                   virt_ptr<UniqueProcessId> currentUpid,\n                   virt_ptr<UniqueProcessId> ownerUpid)\n{\n   // Get a lowercase copy of name\n   StackArray<char, 64> nameCopy;\n   string_copy(nameCopy.get(), nameCopy.size(), name.get(), std::max<uint32_t>(nameLen, 63u));\n   nameCopy[63] = 0;\n\n   for (auto i = 0u; i < nameCopy.size() && nameCopy[i]; ++i) {\n      nameCopy[i] = static_cast<char>(std::tolower(static_cast<unsigned char>(nameCopy[i])));\n   }\n\n   auto upid = internal::getCurrentUniqueProcessId();\n   if (currentUpid) {\n      *currentUpid = upid;\n   }\n\n   internal::spinLockAcquire(sDriverLock, cpu::this_core::id() + 1);\n\n   // Check if this driver has already been registered\n   for (auto itr = sUserDriversData->registeredDrivers; itr; itr = itr->next) {\n      if (std::strncmp(virt_addrof(itr->name).get(), nameCopy.get(), 64) == 0) {\n         if (ownerUpid) {\n            *ownerUpid = itr->ownerUpid;\n            internal::spinLockRelease(sDriverLock, cpu::this_core::id() + 1);\n            return 0;\n         }\n      }\n   }\n\n   // Add driver to list\n   auto driver = virt_cast<UserDriver *>(internal::allocFromWorkArea(sizeof(UserDriver)));\n   std::memcpy(virt_addrof(driver->name).get(), nameCopy.get(), driver->name.size());\n   driver->ownerUpid = upid;\n   driver->next = sUserDriversData->registeredDrivers;\n   sUserDriversData->registeredDrivers = driver;\n   internal::spinLockRelease(sDriverLock, cpu::this_core::id() + 1);\n\n   if (ownerUpid) {\n      *ownerUpid = upid;\n   }\n\n   return 0;\n}\n\nint32_t\nderegisterUserDriver(virt_ptr<const char> name,\n                     uint32_t nameLen)\n{\n   // Get a lowercase copy of name\n   StackArray<char, 64> nameCopy;\n   string_copy(nameCopy.get(), nameCopy.size(), name.get(), std::max<uint32_t>(nameLen, 63u));\n   nameCopy[63] = 0;\n\n   for (auto i = 0u; i < nameCopy.size() && nameCopy[i]; ++i) {\n      nameCopy[i] = static_cast<char>(std::tolower(static_cast<unsigned char>(nameCopy[i])));\n   }\n\n   // Remove driver with same name & upid from list\n   auto upid = internal::getCurrentUniqueProcessId();\n   auto prev = virt_ptr<UserDriver> { nullptr };\n   internal::spinLockAcquire(sDriverLock, cpu::this_core::id() + 1);\n   for (auto itr = sUserDriversData->registeredDrivers; itr; itr = itr->next) {\n      if (std::strncmp(virt_addrof(itr->name).get(), nameCopy.get(), 64) == 0) {\n         if (itr->ownerUpid == upid) {\n            if (prev) {\n               prev->next = itr->next;\n            } else {\n               sUserDriversData->registeredDrivers = itr->next;\n            }\n\n            internal::freeToWorkArea(itr);\n         }\n\n         break;\n      }\n\n      prev = itr;\n   }\n   internal::spinLockRelease(sDriverLock, cpu::this_core::id() + 1);\n   return 0;\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseStaticUserDriversData()\n{\n   sUserDriversData = allocStaticData<StaticUserDriversData>();\n}\n\n/**\n * These drivers are normally registered by root.rpx as part of the boot\n * process. We must register them here so when they are subsequently reigstered\n * by an application they will see the kernel driver already has been\n * associated with the root process and behave appropriately.\n *\n * Essentially this is a hack because we do not emulate the root.rpx process.\n * It's times like this where I regret not writing an LLE emulator ... :)\n */\nvoid\nregisterRootUserDrivers()\n{\n   auto registerRootDriver = [](const char *name) {\n      auto driver = virt_cast<UserDriver *>(internal::allocFromWorkArea(sizeof(UserDriver)));\n      driver->name = name;\n      driver->ownerUpid = UniqueProcessId::Root;\n      driver->next = sUserDriversData->registeredDrivers;\n      sUserDriversData->registeredDrivers = driver;\n   };\n\n   // Registered via call from root.rpx to PADInit\n   registerRootDriver(\"pad\");\n\n   // Registered via call from root.rpx to WPADInit\n   registerRootDriver(\"wpad\");\n\n   // Registered via call from root.rpx to VPADInit\n   registerRootDriver(\"vpad\");\n}\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/kernel/cafe_kernel_userdrivers.h",
    "content": "#pragma once\n#include \"cafe_kernel_processid.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::kernel\n{\n\nint32_t\nregisterUserDriver(virt_ptr<const char> name,\n                   uint32_t nameLen,\n                   virt_ptr<UniqueProcessId> currentUpid,\n                   virt_ptr<UniqueProcessId> ownerUpid);\n\nint32_t\nderegisterUserDriver(virt_ptr<const char> name,\n                     uint32_t nameLen);\n\nnamespace internal\n{\n\nvoid\ninitialiseStaticUserDriversData();\n\nvoid\nregisterRootUserDrivers();\n\n} // namespace internal\n\n} // namespace cafe::kernel\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/avm/avm.cpp",
    "content": "#include \"avm.h\"\n\nnamespace cafe::avm\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::avm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/avm/avm.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::avm\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::avm, \"avm.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::avm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle.cpp",
    "content": "#include \"cafe_hle.h\"\n\n#include \"avm/avm.h\"\n#include \"camera/camera.h\"\n#include \"coreinit/coreinit.h\"\n#include \"dc/dc.h\"\n#include \"dmae/dmae.h\"\n#include \"drmapp/drmapp.h\"\n#include \"erreula/erreula.h\"\n#include \"gx2/gx2.h\"\n#include \"h264/h264.h\"\n#include \"lzma920/lzma920.h\"\n#include \"mic/mic.h\"\n#include \"nfc/nfc.h\"\n#include \"nio_prof/nio_prof.h\"\n#include \"nlibcurl/nlibcurl.h\"\n#include \"nlibnss2/nlibnss2.h\"\n#include \"nlibnss/nlibnss.h\"\n#include \"nn_acp/nn_acp.h\"\n#include \"nn_ac/nn_ac.h\"\n#include \"nn_act/nn_act.h\"\n#include \"nn_aoc/nn_aoc.h\"\n#include \"nn_boss/nn_boss.h\"\n#include \"nn_ccr/nn_ccr.h\"\n#include \"nn_cmpt/nn_cmpt.h\"\n#include \"nn_dlp/nn_dlp.h\"\n#include \"nn_ec/nn_ec.h\"\n#include \"nn_fp/nn_fp.h\"\n#include \"nn_hai/nn_hai.h\"\n#include \"nn_hpad/nn_hpad.h\"\n#include \"nn_idbe/nn_idbe.h\"\n#include \"nn_ndm/nn_ndm.h\"\n#include \"nn_nets2/nn_nets2.h\"\n#include \"nn_nfp/nn_nfp.h\"\n#include \"nn_nim/nn_nim.h\"\n#include \"nn_olv/nn_olv.h\"\n#include \"nn_pdm/nn_pdm.h\"\n#include \"nn_save/nn_save.h\"\n#include \"nn_sl/nn_sl.h\"\n#include \"nn_spm/nn_spm.h\"\n#include \"nn_temp/nn_temp.h\"\n#include \"nn_uds/nn_uds.h\"\n#include \"nn_vctl/nn_vctl.h\"\n#include \"nsysccr/nsysccr.h\"\n#include \"nsyshid/nsyshid.h\"\n#include \"nsyskbd/nsyskbd.h\"\n#include \"nsysnet/nsysnet.h\"\n#include \"nsysuhs/nsysuhs.h\"\n#include \"nsysuvd/nsysuvd.h\"\n#include \"ntag/ntag.h\"\n#include \"padscore/padscore.h\"\n#include \"proc_ui/proc_ui.h\"\n#include \"sndcore2/sndcore2.h\"\n#include \"snd_core/snd_core.h\"\n#include \"snduser2/snduser2.h\"\n#include \"snd_user/snd_user.h\"\n#include \"swkbd/swkbd.h\"\n#include \"sysapp/sysapp.h\"\n#include \"tcl/tcl.h\"\n#include \"tve/tve.h\"\n#include \"uac/uac.h\"\n#include \"uac_rpl/uac_rpl.h\"\n#include \"usb_mic/usb_mic.h\"\n#include \"uvc/uvc.h\"\n#include \"uvd/uvd.h\"\n#include \"vpadbase/vpadbase.h\"\n#include \"vpad/vpad.h\"\n#include \"zlib125/zlib125.h\"\n\n#include \"decaf_config.h\"\n#include \"decaf_configstorage.h\"\n\n#include <common/log.h>\n#include <array>\n#include <libcpu/cpu_formatters.h>\n#include <regex>\n\nnamespace cafe::hle\n{\n\nvolatile bool FunctionTraceEnabled = false;\n\nstatic std::array<Library *, static_cast<size_t>(LibraryId::Max)>\nsLibraries;\n\nstatic void\nregisterLibrary(Library *library)\n{\n   sLibraries[static_cast<size_t>(library->id())] = library;\n   library->generate();\n}\n\nvoid\ninitialiseLibraries()\n{\n   sLibraries.fill(nullptr);\n   registerLibrary(new avm::Library { });\n   registerLibrary(new camera::Library { });\n   registerLibrary(new coreinit::Library { });\n   registerLibrary(new dc::Library { });\n   registerLibrary(new dmae::Library { });\n   registerLibrary(new drmapp::Library { });\n   registerLibrary(new nn_erreula::Library { });\n   registerLibrary(new gx2::Library { });\n   registerLibrary(new h264::Library { });\n   registerLibrary(new lzma920::Library { });\n   registerLibrary(new mic::Library { });\n   registerLibrary(new nfc::Library { });\n   registerLibrary(new nio_prof::Library { });\n   registerLibrary(new nlibcurl::Library { });\n   registerLibrary(new nlibnss2::Library { });\n   registerLibrary(new nlibnss::Library { });\n   registerLibrary(new nn_acp::Library { });\n   registerLibrary(new nn_ac::Library { });\n   registerLibrary(new nn_act::Library { });\n   registerLibrary(new nn_aoc::Library { });\n   registerLibrary(new nn_boss::Library { });\n   registerLibrary(new nn_ccr::Library { });\n   registerLibrary(new nn_cmpt::Library { });\n   registerLibrary(new nn_dlp::Library { });\n   registerLibrary(new nn_ec::Library { });\n   registerLibrary(new nn_fp::Library { });\n   registerLibrary(new nn_hai::Library { });\n   registerLibrary(new nn_hpad::Library { });\n   registerLibrary(new nn_idbe::Library { });\n   registerLibrary(new nn_ndm::Library { });\n   registerLibrary(new nn_nets2::Library { });\n   registerLibrary(new nn_nfp::Library { });\n   registerLibrary(new nn_nim::Library { });\n   registerLibrary(new nn_olv::Library { });\n   registerLibrary(new nn_pdm::Library { });\n   registerLibrary(new nn_save::Library { });\n   registerLibrary(new nn_sl::Library { });\n   registerLibrary(new nn_spm::Library { });\n   registerLibrary(new nn_temp::Library { });\n   registerLibrary(new nn_uds::Library { });\n   registerLibrary(new nn_vctl::Library { });\n   registerLibrary(new nsysccr::Library { });\n   registerLibrary(new nsyshid::Library { });\n   registerLibrary(new nsyskbd::Library { });\n   registerLibrary(new nsysnet::Library { });\n   registerLibrary(new nsysuhs::Library { });\n   registerLibrary(new nsysuvd::Library { });\n   registerLibrary(new ntag::Library { });\n   registerLibrary(new padscore::Library { });\n   registerLibrary(new proc_ui::Library { });\n   registerLibrary(new sndcore2::Library { });\n   registerLibrary(new snd_core::Library { });\n   registerLibrary(new snduser2::Library { });\n   registerLibrary(new snd_user::Library { });\n   registerLibrary(new swkbd::Library { });\n   registerLibrary(new sysapp::Library { });\n   registerLibrary(new tcl::Library { });\n   registerLibrary(new tve::Library { });\n   registerLibrary(new uac::Library { });\n   registerLibrary(new uac_rpl::Library { });\n   registerLibrary(new usb_mic::Library { });\n   registerLibrary(new uvc::Library { });\n   registerLibrary(new uvd::Library { });\n   registerLibrary(new vpadbase::Library { });\n   registerLibrary(new vpad::Library { });\n   registerLibrary(new zlib125::Library { });\n\n   // Register config change handler\n   static std::once_flag sRegisteredConfigChangeListener;\n   std::call_once(sRegisteredConfigChangeListener,\n      []() {\n         decaf::registerConfigChangeListener(\n            [](const decaf::Settings &settings) {\n               setTraceEnabled(settings.log.hle_trace);\n               applyTraceFilters(settings.log.hle_trace_filters);\n            });\n      });\n\n   // Apply trace config\n   setTraceEnabled(decaf::config()->log.hle_trace);\n   applyTraceFilters(decaf::config()->log.hle_trace_filters);\n}\n\nLibrary *\ngetLibrary(LibraryId id)\n{\n   return sLibraries[static_cast<size_t>(id)];\n}\n\nLibrary *\ngetLibrary(std::string_view name)\n{\n   for (auto library : sLibraries) {\n      if (library && library->name() == name) {\n         return library;\n      }\n   }\n\n   return nullptr;\n}\n\nvoid\nrelocateLibrary(std::string_view name,\n                virt_addr textBaseAddress,\n                virt_addr dataBaseAddress)\n{\n   auto libraryName = std::string { name } + \".rpl\";\n   auto library = getLibrary(libraryName);\n   if (library) {\n      library->relocate(textBaseAddress, dataBaseAddress);\n   }\n}\n\nvoid\napplyTraceFilters(const std::vector<std::string> &filterStrings)\n{\n   struct TraceFilter2\n   {\n      bool enable;\n      std::regex re;\n   };\n\n   std::vector<TraceFilter2> filters;\n   filters.reserve(filterStrings.size());\n\n   for (auto &filterString : filterStrings) {\n      auto &filter = filters.emplace_back();\n\n      // First character is + enable or - disable\n      if (filterString[0] == '+') {\n         filter.enable = true;\n      } else if (filterString[0] == '-') {\n         filter.enable = false;\n      } else {\n         gLog->error(fmt::format(\n            \"Invalid trace filter {}, expected to begin with + or -\",\n            filterString));\n      }\n\n      try {\n         filter.re = filterString.c_str() + 1;\n      } catch (std::regex_error e) {\n         gLog->error(fmt::format(\n            \"Invalid trace filter {}, regex error {}\",\n             filterString, e.what()));\n      }\n   }\n\n   auto buffer = std::string { };\n   buffer.reserve(512);\n\n   for (auto library : sLibraries) {\n      auto libraryNameSize = library->name().size();\n      buffer.insert(0, library->name());\n\n      const auto &symbolMap = library->getSymbolMap();\n      for (auto &[symbolName, symbol] : symbolMap) {\n         if (symbol->type != LibrarySymbol::Function) {\n            continue;\n         }\n\n         auto funcSymbol = static_cast<LibraryFunction *>(symbol.get());\n\n         // Match regex against libraryName::functionName\n         buffer.resize(libraryNameSize);\n         buffer += \"::\";\n         buffer += funcSymbol->name;\n\n         for (auto &filter : filters) {\n            if (std::regex_match(buffer, filter.re)) {\n               funcSymbol->traceEnabled = filter.enable;\n            }\n         }\n      }\n   }\n}\n\nvoid\nsetTraceEnabled(bool enabled)\n{\n   FunctionTraceEnabled = enabled;\n}\n\n} // namespace cafe::hle\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle.h",
    "content": "#pragma once\n#include \"cafe_hle_library.h\"\n\n#include <libcpu/be2_struct.h>\n#include <string_view>\n#include <string>\n#include <vector>\n\nnamespace cafe::hle\n{\n\nvoid\ninitialiseLibraries();\n\nLibrary *\ngetLibrary(LibraryId id);\n\nLibrary *\ngetLibrary(std::string_view name);\n\nvoid\nrelocateLibrary(std::string_view name,\n                virt_addr textBaseAddress,\n                virt_addr dataBaseAddress);\n\nvoid\napplyTraceFilters(const std::vector<std::string> &filters);\n\nvoid\nsetTraceEnabled(bool enabled);\n\n} // namespace cafe::hle\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle_library.cpp",
    "content": "#include \"cafe_hle.h\"\n#include \"cafe_hle_library.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"cafe/loader/cafe_loader_rpl.h\"\n#include \"decaf_config.h\"\n\n#include <common/log.h>\n#include <fmt/core.h>\n#include <fstream>\n#include <libcpu/cpu.h>\n#include <libcpu/cpu_formatters.h>\n#include <libcpu/espresso/espresso_instructionset.h>\n#include <unordered_map>\n#include <zlib.h>\n\nnamespace rpl = cafe::loader::rpl;\n\nnamespace cafe::hle\n{\n\nstatic std::unordered_map<uint32_t, UnimplementedLibraryFunction*> sUnimplementedSystemCalls;\nstatic virt_ptr<void> sUnimplementedFunctionStubMemory = nullptr;\nstatic uint32_t sUnimplementedFunctionStubPos = 0u;\nstatic uint32_t sUnimplementedFunctionStubSize = 0u;\n\nvirt_addr\nregisterUnimplementedSymbol(std::string_view module,\n                            std::string_view name)\n{\n   auto libraryName = std::string { module } + \".rpl\";\n   auto library = getLibrary(libraryName);\n   if (!library) {\n      return virt_addr { 0u };\n   }\n\n   // Check if we already created an unimplemented function stub.\n   if (auto unimpl = library->findUnimplementedFunctionExport(name)) {\n      return unimpl->value;\n   }\n\n   // Ensure we have sufficient stub memory\n   if (sUnimplementedFunctionStubPos + 8 >= sUnimplementedFunctionStubSize) {\n      gLog->error(\"Out of stub memory for unimplemented function {}::{}\",\n                  module, name);\n      return virt_addr { 0u };\n   }\n\n   // Create a new unimplemented function\n   auto unimpl = new UnimplementedLibraryFunction { };\n   unimpl->library = library;\n   unimpl->syscallID = cpu::registerIllegalSystemCall();\n   unimpl->name = name;\n   unimpl->value = virt_cast<virt_addr>(sUnimplementedFunctionStubMemory)\n                   + sUnimplementedFunctionStubPos;\n\n   // Generate a kc, blr stub\n   auto stub = virt_cast<uint32_t *>(unimpl->value);\n   auto kc = espresso::encodeInstruction(espresso::InstructionID::kc);\n   kc.kcn = unimpl->syscallID;\n   stub[0] = kc.value;\n\n   auto bclr = espresso::encodeInstruction(espresso::InstructionID::bclr);\n   bclr.bo = 0b10100;\n   stub[1] = bclr.value;\n   sUnimplementedFunctionStubPos += 8;\n\n   // Add to the list\n   library->addUnimplementedFunctionExport(unimpl);\n   sUnimplementedSystemCalls[unimpl->syscallID] = unimpl;\n   gLog->debug(\"Unimplemented function import {}::{} added at {}\",\n               module, name, unimpl->value);\n   return unimpl->value;\n}\n\nvoid\nsetUnimplementedFunctionStubMemory(virt_ptr<void> base,\n                                   uint32_t size)\n{\n   sUnimplementedFunctionStubPos = 0;\n   sUnimplementedFunctionStubSize = size;\n   sUnimplementedFunctionStubMemory = base;\n}\n\ncpu::Core *\nLibrary::handleUnknownSystemCall(cpu::Core *state,\n                                 uint32_t id)\n{\n   // Because we register explicit handlers for all of the valid kernel\n   // calls, if we get an unknown kernel call it must either be one of\n   // the unimplemented functions we registered, or something has gone\n   // awry and we can't continue anyways...\n\n   auto unimplIter = sUnimplementedSystemCalls.find(id);\n   if (unimplIter != sUnimplementedSystemCalls.end()) {\n      auto &unimpl = unimplIter->second;\n\n      gLog->warn(\"Unimplemented function call {}::{} from 0x{:08X}\",\n                 unimpl->library ? unimpl->library->name().c_str() : \"<unknown>\",\n                 unimpl->name,\n                 state->lr);\n\n      // Set r3 to some nonsense value to try and catch errors from\n      // unimplemented functions sooner.\n      state->gpr[3] = 0xC5C5C5C5u;\n\n      return state;\n   }\n\n   decaf_abort(fmt::format(\"Unexpected kernel call {} from 0x{:08X}\",\n                           id, state->lr));\n}\n\nvoid\nLibrary::registerSystemCalls()\n{\n   for (auto const &[name, symbol] : mSymbolMap) {\n      if (symbol->type == LibrarySymbol::Function) {\n         auto funcSymbol = static_cast<LibraryFunction *>(symbol.get());\n         auto newKcId = cpu::registerSystemCallHandler(funcSymbol->invokeHandler);\n         funcSymbol->syscallID = newKcId;\n      }\n   }\n}\n\nvoid\nLibrary::generate()\n{\n   registerSymbols();\n\n   if (!mTypeInfo.empty()) {\n      RegisterFunctionInternalName(\"__pure_virtual_called\",\n                                   cafe::ghs::pure_virtual_called);\n      RegisterFunctionInternalName(\"__dt__Q2_3std9type_infoFv\",\n                                   cafe::ghs::std_typeinfo_Destructor);\n   }\n\n   if (mEntryPointSymbolName.empty()) {\n      RegisterGenericEntryPoint();\n   }\n\n   registerSystemCalls();\n   generateRpl();\n}\n\nvoid\nLibrary::relocate(virt_addr textBaseAddress,\n                  virt_addr dataBaseAddress)\n{\n   for (auto const &[name, symbol] : mSymbolMap) {\n      if (symbol->type == LibrarySymbol::Function) {\n         auto funcSymbol = static_cast<LibraryFunction *>(symbol.get());\n         funcSymbol->address = textBaseAddress + funcSymbol->offset;\n\n         if (funcSymbol->hostPtr) {\n            *(funcSymbol->hostPtr) = virt_cast<void *>(funcSymbol->address);\n         }\n      } else if (symbol->type == LibrarySymbol::Data) {\n         auto dataSymbol = static_cast<LibraryData *>(symbol.get());\n         dataSymbol->address = dataBaseAddress + dataSymbol->offset;\n\n         if (dataSymbol->hostPointer) {\n            *(dataSymbol->hostPointer) = virt_cast<void *>(dataSymbol->address);\n         }\n\n         // TODO: When we relocate for a process switch we should not call this\n         // constructor - must differentiate between relocate for load and\n         // relocate for process switch.\n         if (dataSymbol->constructor) {\n            (*dataSymbol->constructor)(virt_cast<void *>(dataSymbol->address).get());\n         }\n      }\n   }\n\n   for (auto &type : mTypeInfo) {\n      if (type.hostVirtualTablePtr) {\n         *type.hostVirtualTablePtr =\n            virt_cast<ghs::VirtualTable *>(dataBaseAddress + type.virtualTableOffset);\n      }\n\n      if (type.hostTypeDescriptorPtr) {\n         *type.hostTypeDescriptorPtr =\n            virt_cast<ghs::TypeDescriptor *>(dataBaseAddress + type.typeDescriptorOffset);\n      }\n   }\n}\n\nstatic uint32_t\naddSectionString(std::vector<uint8_t> &data,\n                 std::string_view name,\n                 int align = 1)\n{\n   auto pos = data.size();\n\n   // Insert string with null terminator\n   data.insert(data.end(), name.begin(), name.end());\n   data.push_back(0);\n\n   // Pad to 4 byte alignment\n   while ((data.size() % align) != 0) {\n      data.push_back(0);\n   }\n\n   return static_cast<uint32_t>(pos);\n}\n\nstatic void\ngenerateTypeDescriptors(Library *library,\n                        std::vector<LibraryTypeInfo> &types,\n                        const uint32_t dataBaseAddr,\n                        std::vector<uint8_t> &data,\n                        std::vector<uint8_t> &relocations)\n{\n   auto stdTypeInfo = LibraryTypeInfo { };\n   auto addRelocation =\n      [&relocations](const uint32_t offset,\n                     const uint32_t symbol,\n                     const int32_t symbolAddend)\n      {\n         auto rela = rpl::Rela { };\n         rela.info = rpl::R_PPC_ADDR32 | (symbol << 8);\n         rela.addend = symbolAddend;\n         rela.offset = offset;\n         relocations.insert(relocations.end(),\n                            reinterpret_cast<uint8_t *>(&rela),\n                            reinterpret_cast<uint8_t *>(&rela) + sizeof(rpl::Rela));\n      };\n\n   // Add a relocation against symbol 1 ($TEXT)\n   auto addTextRelocation =\n      [&addRelocation, dataBaseAddr](const uint32_t offset,\n                                     const uint32_t relocationAddress)\n      {\n         addRelocation(offset + dataBaseAddr, 1, relocationAddress);\n      };\n\n   // Add a relocation against symbol 2 ($DATA)\n   auto addDataRelocation =\n      [&addRelocation, dataBaseAddr](const uint32_t offset,\n                                     const uint32_t relocationAddress)\n      {\n         addRelocation(offset + dataBaseAddr, 2, relocationAddress);\n      };\n\n   auto addTypeDescriptor =\n      [&](LibraryTypeInfo &typeInfo)\n      {\n         typeInfo.nameOffset = addSectionString(data, typeInfo.name, 4);\n\n         LibrarySymbol *typeIdSymbol = nullptr;\n         if (typeInfo.typeIdSymbol) {\n            typeIdSymbol = library->findSymbol(typeInfo.typeIdSymbol);\n         }\n\n         if (typeIdSymbol) {\n            // Use the given type id symbol\n            typeInfo.typeIdOffset = typeIdSymbol->offset;\n         } else {\n            // Allocate some memory so the address acts as a unique type id\n            typeInfo.typeIdOffset = static_cast<uint32_t>(data.size());\n            data.resize(data.size() + 4);\n         }\n\n         if (!typeInfo.baseTypes.empty()) {\n            // Reserve space for base types\n            typeInfo.baseTypeOffset = static_cast<uint32_t>(data.size());\n            data.resize(data.size() + sizeof(ghs::BaseTypeDescriptor) * typeInfo.baseTypes.size());\n\n            // Last base type flags = 0x1600\n            *reinterpret_cast<be2_val<uint32_t> *>(&data[data.size() - 4]) = 0x1600u;\n         }\n\n         // Insert type descriptor, all the values are filled via relocations\n         typeInfo.typeDescriptorOffset = static_cast<uint32_t>(data.size());\n         data.resize(data.size() + sizeof(ghs::TypeDescriptor));\n\n         // Create virtual table\n         if (!typeInfo.virtualTable.empty()) {\n            typeInfo.virtualTableOffset = static_cast<uint32_t>(data.size());\n\n            {\n               // First entry points to the type descriptor\n               auto entryOffset = static_cast<uint32_t>(data.size());\n               data.resize(data.size() + sizeof(ghs::VirtualTable));\n               addDataRelocation(entryOffset + 4, typeInfo.typeDescriptorOffset);\n            }\n\n            for (auto entry : typeInfo.virtualTable) {\n               auto entryOffset = static_cast<uint32_t>(data.size());\n               auto symbol = library->findSymbol(entry);\n               decaf_assert(symbol,\n                  fmt::format(\"Could not find vtable function {}\", entry));\n               data.resize(data.size() + sizeof(ghs::VirtualTable));\n               addTextRelocation(entryOffset + 4, symbol->offset);\n            }\n         }\n\n         // Add type descriptor relocations, must be done after setting\n         // virtualTableOffset incase typeInfo == stdTypeInfo\n         addDataRelocation(typeInfo.typeDescriptorOffset + 0x00, stdTypeInfo.virtualTableOffset);\n         addDataRelocation(typeInfo.typeDescriptorOffset + 0x04, typeInfo.nameOffset);\n         addDataRelocation(typeInfo.typeDescriptorOffset + 0x08, typeInfo.typeIdOffset);\n         if (!typeInfo.baseTypes.empty()) {\n            addDataRelocation(typeInfo.typeDescriptorOffset + 0x0C, typeInfo.baseTypeOffset);\n         }\n      };\n\n   // Generate type descriptors\n   stdTypeInfo.name = \"std::type_info\";\n   stdTypeInfo.virtualTable.push_back(\"__dt__Q2_3std9type_infoFv\");\n   addTypeDescriptor(stdTypeInfo);\n\n   for (auto &type : types) {\n      addTypeDescriptor(type);\n   }\n\n   /*\n    * Add relocations for base types.\n    *\n    * We do this here instead of in addTypeDescriptor so we do not restrict\n    * ourselves to defining base types before the types that inherit them.\n    */\n   auto findTypeInfo =\n      [&types](const char *name) -> LibraryTypeInfo *\n      {\n         for (auto &type : types) {\n            if (strcmp(type.name, name) == 0) {\n               return &type;\n            }\n         }\n\n         return nullptr;\n      };\n\n   for (auto &type : types) {\n      auto baseTypeOffset = type.baseTypeOffset;\n\n      for (auto baseType : type.baseTypes) {\n         auto baseTypeInfo = findTypeInfo(baseType);\n         decaf_assert(baseTypeInfo,\n                      fmt::format(\"Could not find base type {}\", baseType));\n         addDataRelocation(baseTypeOffset,\n                           baseTypeInfo->typeDescriptorOffset);\n         baseTypeOffset += 8;\n      }\n   }\n}\n\nconstexpr auto LibraryFunctionStubSize = 8u;\nconstexpr auto CodeBaseAddress = 0x02000000u;\nconstexpr auto DataBaseAddress = 0x10000000u;\nconstexpr auto LoadBaseAddress = 0xC0000000u;\n\nstruct Section\n{\n   rpl::SectionHeader header;\n   std::vector<uint8_t> data;\n};\n\nvoid\nLibrary::generateRpl()\n{\n   // Build up our symbol information\n   auto funcSymbols = std::vector<LibraryFunction *> { };\n   auto dataSymbols = std::vector<LibraryData *> { };\n   auto numDataExports = 0u;\n   auto numCodeExports = 0u;\n   auto dataSymbolsSize = 0u;\n   auto textSymbolSize = 0u;\n\n   for (auto const &[name, symbol] : mSymbolMap) {\n      if (symbol->type == LibrarySymbol::Function) {\n         auto funcSymbol = static_cast<LibraryFunction *>(symbol.get());\n         textSymbolSize += LibraryFunctionStubSize;\n\n         if (symbol->exported) {\n            numCodeExports++;\n         }\n\n         funcSymbols.push_back(funcSymbol);\n      } else if (symbol->type == LibrarySymbol::Data) {\n         auto dataSymbol = static_cast<LibraryData *>(symbol.get());\n         dataSymbolsSize = align_up(dataSymbolsSize, dataSymbol->align);\n         dataSymbolsSize += dataSymbol->size;\n\n         if (symbol->exported) {\n            numDataExports++;\n         }\n\n         dataSymbols.push_back(dataSymbol);\n      }\n   }\n\n   // Calculate required number of sections\n   auto numSections = 1u;\n   auto textSectionIndex = 0u;\n   auto fexportSectionIndex = 0u;\n   auto fexportRelaSectionIndex = 0u;\n   auto dataSectionIndex = 0u;\n   auto dataRelaSectionIndex = 0u;\n   auto dexportSectionIndex = 0u;\n   auto dexportRelaSectionIndex = 0u;\n   auto firstImportSectionIndex = 0u;\n\n   if (textSymbolSize) {\n      textSectionIndex = numSections++;\n   }\n\n   if (numCodeExports) {\n      fexportSectionIndex = numSections++;\n      fexportRelaSectionIndex = numSections++;\n   }\n\n   if (dataSymbolsSize || !mTypeInfo.empty()) {\n      dataSectionIndex = numSections++;\n   }\n\n   if (!mTypeInfo.empty()) {\n      dataRelaSectionIndex = numSections++;\n   }\n\n   if (numDataExports) {\n      dexportSectionIndex = numSections++;\n      dexportRelaSectionIndex = numSections++;\n   }\n\n   if (mLibraryDependencies.size()) {\n      firstImportSectionIndex = numSections;\n      numSections += static_cast<unsigned int>(mLibraryDependencies.size());\n   }\n\n   auto symTabSectionIndex = numSections++;\n   auto strTabSectionIndex = numSections++;\n   auto shStrTabSectionIndex = numSections++;\n   auto crcSectionIndex = numSections++;\n   auto fileInfoSectionIndex = numSections++;\n\n   auto sections = std::vector<Section> { };\n   sections.resize(numSections);\n\n   auto textSection = sections.begin() + textSectionIndex;\n   auto fexportSection = sections.begin() + fexportSectionIndex;\n   auto fexportRelaSection = sections.begin() + fexportRelaSectionIndex;\n   auto dataSection = sections.begin() + dataSectionIndex;\n   auto dataRelaSection = sections.begin() + dataRelaSectionIndex;\n   auto dexportSection = sections.begin() + dexportSectionIndex;\n   auto dexportRelaSection = sections.begin() + dexportRelaSectionIndex;\n   auto firstImportSection = sections.begin() + firstImportSectionIndex;\n   auto symTabSection = sections.begin() + symTabSectionIndex;\n   auto strTabSection = sections.begin() + strTabSectionIndex;\n   auto shStrTabSection = sections.begin() + shStrTabSectionIndex;\n   auto crcSection = sections.begin() + crcSectionIndex;\n   auto fileInfoSection = sections.begin() + fileInfoSectionIndex;\n   auto loadAddr = LoadBaseAddress;\n\n   // Add empty string to string sections\n   shStrTabSection->data.push_back(0);\n   strTabSection->data.push_back(0);\n\n   // Generate .text\n   if (textSectionIndex) {\n      textSection->header.name = addSectionString(shStrTabSection->data, \".text\");\n      textSection->header.type = rpl::SHT_PROGBITS;\n      textSection->header.flags = rpl::SHF_EXECINSTR | rpl::SHF_ALLOC;\n      textSection->header.addralign = 32u;\n      textSection->header.addr = CodeBaseAddress;\n      textSection->header.offset = 0u;\n      textSection->header.size = 0u;\n      textSection->header.link = 0u;\n      textSection->header.info = 0u;\n      textSection->header.entsize = 0u;\n   }\n\n   // Generate .fexports\n   if (fexportSectionIndex) {\n      fexportSection->header.name = addSectionString(shStrTabSection->data, \".fexports\");\n      fexportSection->header.type = rpl::SHT_RPL_EXPORTS;\n      fexportSection->header.flags = rpl::SHF_EXECINSTR | rpl::SHF_ALLOC;\n      fexportSection->header.addralign = 4u;\n      fexportSection->header.offset = 0u;\n      fexportSection->header.addr = align_up(loadAddr, fexportSection->header.addralign);\n      fexportSection->header.size = 8u + static_cast<uint32_t>(sizeof(rpl::Export) * numCodeExports);\n      fexportSection->header.link = 0u;\n      fexportSection->header.info = 0u;\n      fexportSection->header.entsize = 0u;\n      fexportSection->data.resize(fexportSection->header.size);\n\n      auto exports = rpl::Exports { };\n      exports.count = numCodeExports;\n      exports.signature = 0u;\n      std::memcpy(fexportSection->data.data(), &exports, sizeof(rpl::Exports));\n   }\n\n   // Generate .rela.fexports\n   if (fexportRelaSectionIndex) {\n      fexportRelaSection->header.name = addSectionString(shStrTabSection->data, \".rela.fexports\");\n      fexportRelaSection->header.type = rpl::SHT_RELA;\n      fexportRelaSection->header.flags = 0u;\n      fexportRelaSection->header.addralign = 4u;\n      fexportRelaSection->header.addr = 0u;\n      fexportRelaSection->header.offset = 0u;\n      fexportRelaSection->header.size = static_cast<uint32_t>(sizeof(rpl::Rela) * numCodeExports);\n      fexportRelaSection->header.link = symTabSectionIndex;\n      fexportRelaSection->header.info = fexportSectionIndex;\n      fexportRelaSection->header.entsize = static_cast<uint32_t>(sizeof(rpl::Rela));\n      fexportRelaSection->data.resize(fexportRelaSection->header.size);\n   }\n\n   if (textSymbolSize) {\n      auto exportIdx = 0u;\n      auto textOffset = static_cast<uint32_t>(textSection->data.size());\n      textSection->data.resize(textSection->data.size() + textSymbolSize);\n\n      for (auto &symbol : funcSymbols) {\n         // Write syscall thunk\n         auto kc = espresso::encodeInstruction(espresso::InstructionID::kc);\n         kc.kcn = symbol->syscallID;\n\n         auto bclr = espresso::encodeInstruction(espresso::InstructionID::bclr);\n         bclr.bo = 20;\n         bclr.bi = 0;\n\n         auto thunk = reinterpret_cast<be2_val<uint32_t> *>(textSection->data.data() + textOffset);\n         *(thunk + 0) = kc.value;\n         *(thunk + 1) = bclr.value;\n\n         if (symbol->exported) {\n            auto exportOffset = 8u + (sizeof(rpl::Export) * exportIdx);\n            auto relaOffset = sizeof(rpl::Rela) * exportIdx;\n\n            // Write to .fexport\n            auto fexport = rpl::Export { };\n            fexport.name = addSectionString(fexportSection->data, symbol->name);\n            fexport.value = textSection->header.addr + textOffset;\n            std::memcpy(fexportSection->data.data() + exportOffset,\n                        &fexport, sizeof(rpl::Export));\n\n            // Write to .rela.fexport\n            auto rela = rpl::Rela { };\n            rela.info = rpl::R_PPC_ADDR32 | (symbol->index << 8);\n            rela.addend = 0;\n            rela.offset = static_cast<uint32_t>(fexportSection->header.addr + exportOffset);\n            std::memcpy(fexportRelaSection->data.data() + relaOffset,\n                        &rela, sizeof(rpl::Rela));\n            ++exportIdx;\n         }\n\n         symbol->offset = textOffset;\n         textOffset += LibraryFunctionStubSize;\n      }\n\n      if (fexportSectionIndex) {\n         // Update loadAddr\n         loadAddr = fexportSection->header.addr + static_cast<uint32_t>(fexportSection->data.size());\n      }\n   }\n\n   // Generate .data\n   if (dataSectionIndex) {\n      dataSection->header.name = addSectionString(shStrTabSection->data, \".data\");\n      dataSection->header.type = rpl::SHT_PROGBITS;\n      dataSection->header.flags = rpl::SHF_WRITE | rpl::SHF_ALLOC;\n      dataSection->header.addralign = 32u;\n      dataSection->header.addr = DataBaseAddress;\n      dataSection->header.offset = 0u;\n      dataSection->header.size = 0u;\n      dataSection->header.link = 0u;\n      dataSection->header.info = 0u;\n      dataSection->header.entsize = 0u;\n   }\n\n   // Generate .rela.data\n   if (dataRelaSectionIndex) {\n      dataRelaSection->header.name = addSectionString(shStrTabSection->data, \".rela.data\");\n      dataRelaSection->header.type = rpl::SHT_RELA;\n      dataRelaSection->header.flags = 0u;\n      dataRelaSection->header.addralign = 4u;\n      dataRelaSection->header.addr = 0u;\n      dataRelaSection->header.offset = 0u;\n      dataRelaSection->header.size = 0u;\n      dataRelaSection->header.link = symTabSectionIndex;\n      dataRelaSection->header.info = dataSectionIndex;\n      dataRelaSection->header.entsize = static_cast<uint32_t>(sizeof(rpl::Rela));\n   }\n\n   // Generate .dexports\n   if (dexportSectionIndex) {\n      dexportSection->header.name = addSectionString(shStrTabSection->data, \".dexports\");\n      dexportSection->header.type = rpl::SHT_RPL_EXPORTS;\n      dexportSection->header.flags = rpl::SHF_ALLOC;\n      dexportSection->header.addralign = 4u;\n      dexportSection->header.offset = 0u;\n      dexportSection->header.addr = align_up(loadAddr, dexportSection->header.addralign);\n      dexportSection->header.size = 8u + static_cast<uint32_t>(sizeof(rpl::Export) * numDataExports);\n      dexportSection->header.link = 0u;\n      dexportSection->header.info = 0u;\n      dexportSection->header.entsize = 0u;\n      dexportSection->data.resize(dexportSection->header.size);\n\n      auto exports = rpl::Exports { };\n      exports.count = numDataExports;\n      exports.signature = 0u;\n      std::memcpy(dexportSection->data.data(), &exports, sizeof(rpl::Exports));\n   }\n\n   // Generate .rela.dexports\n   if (dexportRelaSectionIndex) {\n      dexportRelaSection->header.name = addSectionString(shStrTabSection->data, \".rela.dexports\");\n      dexportRelaSection->header.type = rpl::SHT_RELA;\n      dexportRelaSection->header.flags = 0u;\n      dexportRelaSection->header.addralign = 4u;\n      dexportRelaSection->header.addr = 0u;\n      dexportRelaSection->header.offset = 0u;\n      dexportRelaSection->header.size = static_cast<uint32_t>(sizeof(rpl::Rela) * numDataExports);\n      dexportRelaSection->header.link = symTabSectionIndex;\n      dexportRelaSection->header.info = dexportSectionIndex;\n      dexportRelaSection->header.entsize = static_cast<uint32_t>(sizeof(rpl::Rela));\n      dexportRelaSection->data.resize(dexportRelaSection->header.size);\n   }\n\n   // Write symbols and exports\n   if (dataSymbolsSize) {\n      auto exportIdx = 0;\n      auto dataOffset = static_cast<uint32_t>(dataSection->data.size());\n      dataSection->data.resize(dataSection->data.size() + dataSymbolsSize);\n\n      for (auto &symbol : dataSymbols) {\n         dataOffset = align_up(dataOffset, symbol->align);\n\n         if (symbol->exported) {\n            auto exportOffset = 8 + (sizeof(rpl::Export) * exportIdx);\n            auto relaOffset = sizeof(rpl::Rela) * exportIdx;\n\n            // Write to .dexport\n            auto dexport = rpl::Export { };\n            dexport.name = addSectionString(dexportSection->data, symbol->name);\n            dexport.value = dataSection->header.addr + dataOffset;\n            std::memcpy(dexportSection->data.data() + exportOffset,\n                        &dexport, sizeof(rpl::Export));\n\n            // Write to .rela.dexport\n            auto rela = rpl::Rela { };\n            rela.info = rpl::R_PPC_ADDR32 | (symbol->index << 8);\n            rela.addend = 0;\n            rela.offset = static_cast<uint32_t>(dexportSection->header.addr + exportOffset);\n            std::memcpy(dexportRelaSection->data.data() + relaOffset,\n                        &rela, sizeof(rpl::Rela));\n            ++exportIdx;\n         }\n\n         symbol->offset = dataOffset;\n         dataOffset += symbol->size;\n      }\n\n      decaf_check(dataOffset == dataSymbolsSize);\n\n      if (dexportSectionIndex) {\n         // Update loadAddr\n         loadAddr = dexportSection->header.addr + static_cast<uint32_t>(dexportSection->data.size());\n      }\n   }\n\n   // Generate .fimport_{}\n   if (firstImportSectionIndex) {\n      for (auto i = 0u; i < mLibraryDependencies.size(); ++i) {\n         auto importSection = firstImportSection + i;\n         importSection->header.name =\n            addSectionString(shStrTabSection->data,\n                             fmt::format(\".fimport_{}\", mLibraryDependencies[i]));\n         importSection->header.type = rpl::SHT_RPL_IMPORTS;\n         importSection->header.flags = rpl::SHF_ALLOC | rpl::SHF_EXECINSTR;\n         importSection->header.addralign = 4u;\n         importSection->header.offset = 0u;\n         importSection->header.addr = align_up(loadAddr, importSection->header.addralign);\n         importSection->header.size = 8u;\n         importSection->header.link = 0u;\n         importSection->header.info = 0u;\n         importSection->header.entsize = 0u;\n         importSection->data.resize(importSection->header.size);\n\n         auto imports = rpl::Imports { };\n         imports.count = 0u;\n         imports.signature = 0u;\n         std::memcpy(importSection->data.data(),\n                     &imports, sizeof(rpl::Imports));\n\n         loadAddr = importSection->header.addr + static_cast<uint32_t>(importSection->data.size());\n      }\n   }\n\n   if (!mTypeInfo.empty()) {\n      generateTypeDescriptors(this,\n                              mTypeInfo,\n                              dataSection->header.addr,\n                              dataSection->data,\n                              dataRelaSection->data);\n   }\n\n   // Generate symtab\n   auto symbolCount = funcSymbols.size() + dataSymbols.size() + cafe::hle::Library::BaseSymbolIndex;\n   symTabSection->data.resize(sizeof(rpl::Symbol) * symbolCount);\n\n   auto getSymbol =\n      [&symTabSection](uint32_t index)\n      {\n         return reinterpret_cast<rpl::Symbol *>(symTabSection->data.data() + (index * sizeof(rpl::Symbol)));\n      };\n\n   // Generate NULL symbol\n   {\n      auto nullSymbol = getSymbol(0);\n      std::memset(nullSymbol, 0, sizeof(rpl::Symbol));\n   }\n\n   // Generate $TEXT symbol\n   {\n      auto textSymbol = getSymbol(1);\n      textSymbol->name = addSectionString(strTabSection->data, \"$TEXT\");\n      textSymbol->value = CodeBaseAddress;\n      textSymbol->size = 0u;\n      textSymbol->info = static_cast<uint8_t>(rpl::STT_SECTION | (rpl::STB_LOCAL << 4));\n      textSymbol->shndx = static_cast<uint16_t>(textSectionIndex);\n      textSymbol->other = uint8_t { 0 };\n   }\n\n   // Generate $DATA symbol\n   {\n      auto dataSymbol = getSymbol(2);\n      dataSymbol->name = addSectionString(strTabSection->data, \"$DATA\");\n      dataSymbol->value = DataBaseAddress;\n      dataSymbol->size = 0u;\n      dataSymbol->info = static_cast<uint8_t>(rpl::STT_SECTION | (rpl::STB_LOCAL << 4));\n      dataSymbol->shndx = static_cast<uint16_t>(dataSectionIndex);\n      dataSymbol->other = uint8_t { 0 };\n   }\n\n   for (auto &dataSymbol : dataSymbols) {\n      auto symbol = getSymbol(dataSymbol->index);\n      auto binding = dataSymbol->exported ? rpl::STB_GLOBAL : rpl::STB_LOCAL;\n      symbol->name = addSectionString(strTabSection->data, dataSymbol->name);\n      symbol->value = dataSymbol->offset + dataSection->header.addr;\n      symbol->size = dataSymbol->size;\n      symbol->info = static_cast<uint8_t>(rpl::STT_OBJECT | (binding << 4));\n      symbol->shndx = static_cast<uint16_t>(dataSectionIndex);\n      symbol->other = uint8_t { 0 };\n   }\n\n   for (auto &funcSymbol : funcSymbols) {\n      auto symbol = getSymbol(funcSymbol->index);\n      auto binding = funcSymbol->exported ? rpl::STB_GLOBAL : rpl::STB_LOCAL;\n      symbol->name = addSectionString(strTabSection->data, funcSymbol->name);\n      symbol->value = funcSymbol->offset + textSection->header.addr;\n      symbol->size = LibraryFunctionStubSize;\n      symbol->info = static_cast<uint8_t>(rpl::STT_FUNC | (binding << 4));\n      symbol->shndx = static_cast<uint16_t>(textSectionIndex);\n      symbol->other = uint8_t { 0 };\n   }\n\n   symTabSection->header.name = addSectionString(shStrTabSection->data, \".symtab\");\n   symTabSection->header.type = rpl::SHT_SYMTAB;\n   symTabSection->header.flags = rpl::SHF_ALLOC;\n   symTabSection->header.addralign = 4u;\n   symTabSection->header.addr = align_up(loadAddr, symTabSection->header.addralign);\n   symTabSection->header.offset = 0u; // Set later\n   symTabSection->header.size = static_cast<uint32_t>(symTabSection->data.size());\n   symTabSection->header.link = strTabSectionIndex;\n   symTabSection->header.info = 1u;\n   symTabSection->header.entsize = static_cast<uint32_t>(sizeof(rpl::Symbol));\n   loadAddr = symTabSection->header.addr + symTabSection->header.size;\n\n   // Generate strtab\n   strTabSection->header.name = addSectionString(shStrTabSection->data, \".strtab\");\n   strTabSection->header.type = rpl::SHT_STRTAB;\n   strTabSection->header.flags = rpl::SHF_ALLOC;\n   strTabSection->header.addralign = 4u;\n   strTabSection->header.addr = align_up(loadAddr, strTabSection->header.addralign);\n   strTabSection->header.offset = 0u; // Set later\n   strTabSection->header.size = static_cast<uint32_t>(strTabSection->data.size());\n   strTabSection->header.link = 0u;\n   strTabSection->header.info = 0u;\n   strTabSection->header.entsize = 0u;\n   loadAddr = strTabSection->header.addr + strTabSection->header.size;\n\n   // Generate shstrtab\n   shStrTabSection->header.name = addSectionString(shStrTabSection->data, \".shstrtab\");\n   shStrTabSection->header.type = rpl::SHT_STRTAB;\n   shStrTabSection->header.flags = rpl::SHF_ALLOC;\n   shStrTabSection->header.addralign = 4u;\n   shStrTabSection->header.addr = align_up(loadAddr, shStrTabSection->header.addralign);\n   shStrTabSection->header.offset = 0u; // Set later\n   shStrTabSection->header.size = static_cast<uint32_t>(shStrTabSection->data.size());\n   shStrTabSection->header.link = 0u;\n   shStrTabSection->header.info = 0u;\n   shStrTabSection->header.entsize = 0u;\n   loadAddr = shStrTabSection->header.addr + shStrTabSection->header.size;\n\n   // Generate SHT_RPL_FILEINFO\n   fileInfoSection->header.name = 0u;\n   fileInfoSection->header.type = rpl::SHT_RPL_FILEINFO;\n   fileInfoSection->header.flags = 0u;\n   fileInfoSection->header.addralign = 4u;\n   fileInfoSection->header.addr = 0u;\n   fileInfoSection->header.offset = 0u; // Set later\n   fileInfoSection->header.size = static_cast<uint32_t>(sizeof(rpl::RPLFileInfo_v4_2));\n   fileInfoSection->header.link = 0u;\n   fileInfoSection->header.info = 0u;\n   fileInfoSection->header.entsize = 0u;\n   fileInfoSection->data.resize(fileInfoSection->header.size);\n\n   auto infoFileName = addSectionString(fileInfoSection->data, mName);\n   auto info = reinterpret_cast<rpl::RPLFileInfo_v4_2 *>(fileInfoSection->data.data());\n   info->version = 0xCAFE0402u;\n   info->textSize = 0u;\n   info->textAlign = 32u;\n   info->dataSize = 0u;\n   info->dataAlign = 4096u;\n   info->loadSize = 0u;\n   info->loadAlign = 4u;\n   info->tempSize = 0u;\n   info->trampAdjust = 0u;\n   info->trampAddition = 0u;\n   info->sdaBase = 0u;\n   info->sda2Base = 0u;\n   info->stackSize = 0x10000u;\n   info->filename = infoFileName;\n   info->heapSize = 0x8000u;\n   info->flags = 0u;\n   info->minVersion = 0x5078u;\n   info->compressionLevel = -1;\n   info->fileInfoPad = 0u;\n   info->cafeSdkVersion = 0x5335u;\n   info->cafeSdkRevision = 0x10D4Bu;\n   info->tlsAlignShift = uint16_t { 0u };\n   info->tlsModuleIndex = int16_t { 0 };\n   info->runtimeFileInfoSize = 0u;\n   info->tagOffset = 0u;\n\n   for (auto &section : sections) {\n      if (section.data.size()) {\n         section.header.size = static_cast<uint32_t>(section.data.size());\n      }\n\n      auto size = section.header.size;\n      if (section.header.addr >= CodeBaseAddress &&\n          section.header.addr < DataBaseAddress) {\n         auto val = section.header.addr + section.header.size - CodeBaseAddress;\n         if (val > info->textSize) {\n            info->textSize = val;\n         }\n      } else if (section.header.addr >= DataBaseAddress &&\n                 section.header.addr < LoadBaseAddress) {\n         auto val = section.header.addr + section.header.size - DataBaseAddress;\n         if (val > info->dataSize) {\n            info->dataSize = val;\n         }\n      } else if (section.header.addr >= LoadBaseAddress) {\n         auto val = section.header.addr + section.header.size - LoadBaseAddress;\n         if (val > info->loadSize) {\n            info->loadSize = val;\n         }\n      } else if (section.header.addr == 0 &&\n                 section.header.type != rpl::SHT_RPL_CRCS &&\n                 section.header.type != rpl::SHT_RPL_FILEINFO) {\n         info->tempSize += (size + 128);\n      }\n   }\n\n   info->textSize = align_up(info->textSize, info->textAlign);\n   info->dataSize = align_up(info->dataSize, info->dataAlign);\n   info->loadSize = align_up(info->loadSize, info->loadAlign);\n\n   // Generate SHT_RPL_CRCS\n   crcSection->header.name = 0u;\n   crcSection->header.type = rpl::SHT_RPL_CRCS;\n   crcSection->header.flags = 0u;\n   crcSection->header.addralign = 4u;\n   crcSection->header.addr = 0u;\n   crcSection->header.offset = 0u; // Set later\n   crcSection->header.size = static_cast<uint32_t>(4 * sections.size());\n   crcSection->header.link = 0u;\n   crcSection->header.info = 0u;\n   crcSection->header.entsize = 4u;\n   crcSection->data.resize(crcSection->header.size);\n\n   for (auto i = 0u; i < sections.size(); ++i) {\n      auto crc = uint32_t { 0u };\n      auto &section = sections[i];\n\n      if (section.data.size() && i != (sections.size() - 2)) {\n         crc = crc32(0, Z_NULL, 0);\n         crc = crc32(crc,\n                     reinterpret_cast<Bytef *>(section.data.data()),\n                     static_cast<uInt>(section.data.size()));\n      }\n\n      *reinterpret_cast<be2_val<uint32_t> *>(crcSection->data.data() + i * 4)\n         = crc;\n   }\n\n   // Generate file header\n   rpl::Header fileHeader;\n   fileHeader.magic[0] = uint8_t { 0x7F };\n   fileHeader.magic[1] = uint8_t { 'E' };\n   fileHeader.magic[2] = uint8_t { 'L' };\n   fileHeader.magic[3] = uint8_t { 'F' };\n\n   fileHeader.fileClass = rpl::ELFCLASS32;\n   fileHeader.encoding = rpl::ELFDATA2MSB;\n   fileHeader.elfVersion = rpl::EV_CURRENT;\n   fileHeader.abi = rpl::EABI_CAFE;\n   fileHeader.abiVersion = rpl::EABI_VERSION_CAFE;\n\n   fileHeader.type = uint16_t { 0xFE01 };\n   fileHeader.machine = rpl::EM_PPC;\n   fileHeader.version = 1u;\n   fileHeader.phoff = 0u;\n   fileHeader.shoff = 0x40u;\n   fileHeader.flags = 0u;\n   fileHeader.ehsize = static_cast<uint16_t>(sizeof(rpl::Header));\n   fileHeader.phentsize = uint16_t { 0 };\n   fileHeader.phnum = uint16_t { 0 };\n   fileHeader.shentsize = static_cast<uint16_t>(sizeof(rpl::SectionHeader));\n   fileHeader.shnum = static_cast<uint16_t>(sections.size());\n   fileHeader.shstrndx = static_cast<uint16_t>(shStrTabSectionIndex);\n\n   // Find and set the entry point\n   decaf_check(!mEntryPointSymbolName.empty());\n   auto entryPointItr = mSymbolMap.find(mEntryPointSymbolName);\n   decaf_check(entryPointItr != mSymbolMap.end());\n   auto entryPointAddr = textSection->header.addr + entryPointItr->second->offset;\n\n   fileHeader.entry = entryPointAddr;\n\n   // Calculate file offsets\n   auto offset = static_cast<uint32_t>(fileHeader.shoff);\n   offset +=\n      align_up(static_cast<uint32_t>(\n         sizeof(rpl::SectionHeader) * sections.size()), 64);\n\n   crcSection->header.offset = offset;\n   offset += crcSection->header.size;\n\n   fileInfoSection->header.offset = offset;\n   offset += fileInfoSection->header.size;\n\n   // Data sections\n   for (auto &section : sections) {\n      if (section.header.type == rpl::SHT_PROGBITS &&\n          !(section.header.flags & rpl::SHF_EXECINSTR)) {\n         section.header.offset = offset;\n         offset += section.header.size;\n      }\n   }\n\n   // Export sections\n   for (auto &section : sections) {\n      if (section.header.type == rpl::SHT_RPL_EXPORTS) {\n         section.header.offset = offset;\n         offset += section.header.size;\n      }\n   }\n\n   // Import sections\n   for (auto &section : sections) {\n      if (section.header.type == rpl::SHT_RPL_IMPORTS) {\n         section.header.offset = offset;\n         offset += section.header.size;\n      }\n   }\n\n   // symtab & strtab\n   for (auto &section : sections) {\n      if (section.header.type == rpl::SHT_SYMTAB ||\n          section.header.type == rpl::SHT_STRTAB) {\n         section.header.offset = offset;\n         offset += section.header.size;\n      }\n   }\n\n   // Code sections\n   for (auto &section : sections) {\n      if (section.header.type == rpl::SHT_PROGBITS &&\n         (section.header.flags & rpl::SHF_EXECINSTR)) {\n         section.header.offset = offset;\n         offset += section.header.size;\n      }\n   }\n\n   // Relocation sections\n   for (auto &section : sections) {\n      if (section.header.type == rpl::SHT_RELA) {\n         section.header.offset = offset;\n         offset += section.header.size;\n      }\n   }\n\n   // Write out the generated RPL\n   mGeneratedRpl.resize(offset, 0);\n   std::memcpy(mGeneratedRpl.data() + 0, &fileHeader, sizeof(rpl::Header));\n\n   offset = fileHeader.shoff;\n   for (auto &section : sections) {\n      std::memcpy(mGeneratedRpl.data() + offset,\n                  &section.header,\n                  sizeof(rpl::SectionHeader));\n      offset += fileHeader.shentsize;\n   }\n\n   for (auto &section : sections) {\n      if (section.header.offset && section.data.size()) {\n         std::memcpy(mGeneratedRpl.data() + section.header.offset,\n                     section.data.data(),\n                     section.data.size());\n      }\n   }\n\n   // TODO: Move this to a debug api command?\n   if (decaf::config()->system.dump_hle_rpl) {\n      std::ofstream out { mName, std::fstream::binary };\n      out.write(reinterpret_cast<const char *>(mGeneratedRpl.data()),\n                mGeneratedRpl.size());\n   }\n}\n\n} // namespace cafe::hle\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle_library.h",
    "content": "#pragma once\n#include \"cafe_hle_library_symbol.h\"\n#include \"cafe_hle_library_typeinfo.h\"\n\n#include <libcpu/state.h>\n\n#include <map>\n#include <string>\n#include <vector>\n\nnamespace cafe::hle\n{\n\nstruct UnimplementedLibraryFunction\n{\n   class Library* library = nullptr;\n   std::string name;\n   uint32_t syscallID = 0xFFFFFFFFu;\n   virt_addr value;\n};\n\nenum class LibraryId\n{\n   avm,\n   camera,\n   coreinit,\n   dc,\n   dmae,\n   drmapp,\n   erreula,\n   gx2,\n   h264,\n   lzma920,\n   mic,\n   nfc,\n   nio_prof,\n   nlibcurl,\n   nlibnss2,\n   nlibnss,\n   nn_acp,\n   nn_ac,\n   nn_act,\n   nn_aoc,\n   nn_boss,\n   nn_ccr,\n   nn_cmpt,\n   nn_dlp,\n   nn_ec,\n   nn_fp,\n   nn_hai,\n   nn_hpad,\n   nn_idbe,\n   nn_ndm,\n   nn_nets2,\n   nn_nfp,\n   nn_nim,\n   nn_olv,\n   nn_pdm,\n   nn_save,\n   nn_sl,\n   nn_spm,\n   nn_temp,\n   nn_uds,\n   nn_vctl,\n   nsysccr,\n   nsyshid,\n   nsyskbd,\n   nsysnet,\n   nsysuhs,\n   nsysuvd,\n   ntag,\n   padscore,\n   proc_ui,\n   sndcore2,\n   snd_core,\n   snduser2,\n   snd_user,\n   swkbd,\n   sysapp,\n   tcl,\n   tve,\n   uac,\n   uac_rpl,\n   usb_mic,\n   uvc,\n   uvd,\n   vpadbase,\n   vpad,\n   zlib125,\n   Max,\n};\n\nclass Library\n{\npublic:\n   // We have 3 default suymbols: NULL, .text, .data\n   static constexpr auto BaseSymbolIndex = uint32_t { 3 };\n\n   static cpu::Core *\n   handleUnknownSystemCall(cpu::Core *state,\n                           uint32_t id);\n\npublic:\n   Library(LibraryId id, std::string name) :\n      mID(id), mName(std::move(name))\n   {\n   }\n\n   virtual ~Library() = default;\n\n   LibraryId\n   id() const\n   {\n      return mID;\n   }\n\n   const std::string &\n   name() const\n   {\n      return mName;\n   }\n\n   virt_addr\n   findSymbolAddress(std::string_view name) const\n   {\n      auto itr = mSymbolMap.find(name);\n      if (itr == mSymbolMap.end()) {\n         return virt_addr { 0 };\n      }\n\n      return itr->second->address;\n   }\n\n   LibrarySymbol *\n   findSymbol(std::string_view name) const\n   {\n      auto itr = mSymbolMap.find(name);\n      if (itr == mSymbolMap.end()) {\n         return nullptr;\n      }\n\n      return itr->second.get();\n   }\n\n   LibrarySymbol *\n   findSymbol(virt_addr addr) const\n   {\n      for (auto &[name, symbol] : mSymbolMap) {\n         if (symbol->address == addr) {\n            return symbol.get();\n         }\n      }\n\n      return nullptr;\n   }\n\n   const std::vector<uint8_t> &\n   getGeneratedRpl() const\n   {\n      return mGeneratedRpl;\n   }\n\n   void\n   generate();\n\n   void\n   relocate(virt_addr textBaseAddress,\n            virt_addr dataBaseAddress);\n\n   void\n   addUnimplementedFunctionExport(UnimplementedLibraryFunction *unimpl)\n   {\n      mUnimplementedFunctionExports.push_back(unimpl);\n   }\n\n   UnimplementedLibraryFunction *\n   findUnimplementedFunctionExport(std::string_view name)\n   {\n      for (auto unimpl : mUnimplementedFunctionExports) {\n         if (unimpl->name == name) {\n            return unimpl;\n         }\n      }\n\n      return nullptr;\n   }\n\n   const auto &\n   getSymbolMap() const\n   {\n      return mSymbolMap;\n   }\n\n   void\n   registerLibraryDependency(const char *name)\n   {\n      mLibraryDependencies.push_back(name);\n   }\n\n   void\n   registerSymbol(const std::string &name,\n                  std::unique_ptr<LibrarySymbol> symbol)\n   {\n      decaf_check(mSymbolMap.find(name) == mSymbolMap.end());\n      symbol->index = BaseSymbolIndex + static_cast<uint32_t>(mSymbolMap.size());\n      symbol->name = name;\n      mSymbolMap.emplace(name, std::move(symbol));\n   }\n\n   void\n   registerTypeInfo(LibraryTypeInfo &&typeInfo)\n   {\n      mTypeInfo.emplace_back(std::move(typeInfo));\n   }\n\n   void\n   setEntryPointSymbolName(const std::string& name)\n   {\n      mEntryPointSymbolName = name;\n   }\n\nprotected:\n   virtual void\n   registerSymbols() = 0;\n\n   void\n   registerSystemCalls();\n\n   void\n   generateRpl();\n\nprivate:\n   LibraryId mID;\n   std::string mName;\n   std::vector<std::string> mLibraryDependencies;\n   std::map<std::string, std::unique_ptr<LibrarySymbol>, std::less<>> mSymbolMap;\n   std::vector<LibraryTypeInfo> mTypeInfo;\n   std::vector<uint8_t> mGeneratedRpl;\n   std::vector<UnimplementedLibraryFunction *> mUnimplementedFunctionExports;\n   std::string mEntryPointSymbolName;\n};\n\nvirt_addr\nregisterUnimplementedSymbol(std::string_view module,\n                            std::string_view name);\n\nvoid\nsetUnimplementedFunctionStubMemory(virt_ptr<void> base,\n                                   uint32_t size);\n\n} // namespace cafe::hle\n\n#include \"cafe_hle_library_register.h\"\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle_library_data.h",
    "content": "#pragma once\n#include \"cafe_hle_library_symbol.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::hle\n{\n\nstruct LibraryData : LibrarySymbol\n{\n   LibraryData() :\n      LibrarySymbol(LibrarySymbol::Data)\n   {\n   }\n\n   virtual ~LibraryData()\n   {\n   }\n\n   //! Pointer to the host pointer to guest memory which we should update\n   virt_ptr<void> *hostPointer = nullptr;\n\n   //! Host constructor to call on allocated memory\n   void (*constructor)(void *) = nullptr;\n\n   //! Size of this data symbol\n   uint32_t size = 0;\n\n   //! Align of this data symbol\n   uint32_t align = 0;\n};\n\n} // namespace cafe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle_library_function.h",
    "content": "#pragma once\n#include \"cafe_hle_library_symbol.h\"\n#include \"cafe/cafe_ppc_interface_invoke_host.h\"\n#include \"cafe/cafe_ppc_interface_trace_host.h\"\n\n#include <libcpu/cpu_control.h>\n\nnamespace cafe::hle\n{\n\nextern volatile bool FunctionTraceEnabled;\n\nusing InvokeHandler = cpu::Core * (*)(cpu::Core * core, uint32_t id);\n\nstruct LibraryFunction : public LibrarySymbol\n{\n   LibraryFunction(InvokeHandler _invokeHandler,\n                   bool& _traceEnabledRef) :\n      LibrarySymbol(LibrarySymbol::Function),\n      invokeHandler(_invokeHandler),\n      traceEnabled(_traceEnabledRef)\n   {\n   }\n\n   virtual ~LibraryFunction()\n   {\n   }\n\n   //! The actual handler for this function\n   InvokeHandler invokeHandler;\n\n   //! Reference to the underlying invoke handler trace wrapper's trace enabled\n   // value, specifying whether trace logging is enabled for this function or not.\n   bool &traceEnabled;\n\n   //! ID number of syscall.\n   uint32_t syscallID = 0xFFFFFFFFu;\n\n   //! Pointer to host function pointer, only set for internal functions.\n   virt_ptr<void> *hostPtr = nullptr;\n};\n\nnamespace internal\n{\n\ntemplate<typename FunctionType, FunctionType Func>\nstruct TracingWrapper\n{\n   static inline cpu::Core *wrapped(cpu::Core *core, uint32_t kcId)\n   {\n      if (FunctionTraceEnabled && traceEnabled) {\n         invoke_trace<FunctionType>(core, traceName.c_str());\n      }\n\n      return invoke<FunctionType, Func>(core);\n   }\n\n   static inline std::string traceName = \"_missingName\";\n   static inline bool traceEnabled = false;\n};\n\ntemplate<typename FunctionType, FunctionType Func>\ninline std::unique_ptr<LibraryFunction>\nmakeLibraryFunction(const std::string &name)\n{\n   TracingWrapper<FunctionType, Func>::traceName = name;\n\n   auto libraryFunction = new LibraryFunction(\n      TracingWrapper<FunctionType, Func>::wrapped,\n      TracingWrapper<FunctionType, Func>::traceEnabled);\n   return std::unique_ptr<LibraryFunction> { libraryFunction };\n}\n\n} // namespace internal\n\n} // cafe::hle\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle_library_register.h",
    "content": "#pragma once\n#include \"cafe_hle_library.h\"\n#include \"cafe_hle_library_function.h\"\n#include \"cafe_hle_library_data.h\"\n\n#include \"coreinit/coreinit_enum.h\"\n\nnamespace cafe::coreinit\n{\n\nusing OSDynLoad_ModuleHandle = uint32_t;\n\nnamespace internal\n{\n\nOSDynLoad_Error\nrelocateHleLibrary(OSDynLoad_ModuleHandle moduleHandle);\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n\nnamespace cafe::hle\n{\n\ntypedef int32_t(&RplEntryFunctionType)(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n                                       coreinit::OSDynLoad_EntryReason reason);\n\ntemplate<RplEntryFunctionType Fn>\nstatic int32_t\ncafe_rpl_crt(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n             coreinit::OSDynLoad_EntryReason reason)\n{\n   coreinit::internal::relocateHleLibrary(moduleHandle);\n   return Fn(moduleHandle, reason);\n}\n\ntemplate<int dummy>\nstatic int32_t\ncafe_generic_rpl_crt(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n                     coreinit::OSDynLoad_EntryReason reason)\n{\n   coreinit::internal::relocateHleLibrary(moduleHandle);\n   return 0;\n}\n\ntemplate<typename FunctionType, FunctionType Fn>\nstatic void\nregisterNoCrtEntryPoint(hle::Library *library,\n                        const std::string &name)\n{\n   auto symbol = internal::makeLibraryFunction<FunctionType, Fn>(name);\n   symbol->exported = true;\n   library->registerSymbol(name, std::move(symbol));\n   library->setEntryPointSymbolName(name);\n}\n\ntemplate<RplEntryFunctionType Fn>\nstatic void\nregisterEntryPoint(hle::Library *library,\n                   const std::string &name)\n{\n   auto symbol = internal::makeLibraryFunction<RplEntryFunctionType, Fn>(name);\n   symbol->exported = true;\n   library->registerSymbol(name, std::move(symbol));\n\n   static const std::string crtEntryName = \"__rpl_crt\";\n   registerNoCrtEntryPoint<RplEntryFunctionType, cafe_rpl_crt<Fn>>(library, crtEntryName);\n}\n\ntemplate<typename FunctionType, FunctionType Fn>\nstatic void\nregisterFunctionInternal(hle::Library *library,\n                         const char *name,\n                         virt_func_ptr<typename std::remove_pointer<FunctionType>::type>& hostPtr)\n{\n   auto symbol = internal::makeLibraryFunction<FunctionType, Fn>(name);\n   symbol->exported = false;\n   symbol->hostPtr = reinterpret_cast<virt_ptr<void>*>(&hostPtr);\n   library->registerSymbol(name, std::move(symbol));\n}\n\ntemplate<typename FunctionType, FunctionType Fn>\nstatic void\nregisterFunctionInternal(hle::Library *library,\n                         const char *name)\n{\n   auto symbol = internal::makeLibraryFunction<FunctionType, Fn>(name);\n   symbol->exported = false;\n   library->registerSymbol(name, std::move(symbol));\n}\n\ntemplate<typename FunctionType, FunctionType Fn>\nstatic void\nregisterFunctionExport(hle::Library *library,\n                       const char *name)\n{\n   auto symbol = internal::makeLibraryFunction<FunctionType, Fn>(name);\n   symbol->exported = true;\n   library->registerSymbol(name, std::move(symbol));\n}\n\ntemplate<typename DataType>\nstatic void\nregisterDataInternal(hle::Library *library,\n                     const char *name,\n                     virt_ptr<DataType> &data)\n{\n   auto symbol = std::make_unique<LibraryData>();\n   symbol->exported = false;\n   symbol->hostPointer = reinterpret_cast<virt_ptr<void>*>(&data);\n   symbol->constructor = [](void* ptr) { new (ptr) DataType(); };\n   symbol->size = sizeof(DataType);\n   symbol->align = alignof(DataType);\n   library->registerSymbol(name, std::move(symbol));\n}\n\ntemplate<typename DataType>\nstatic void\nregisterDataExport(hle::Library *library,\n                   const char *name,\n                   virt_ptr<DataType> &data)\n{\n   auto symbol = std::make_unique<LibraryData>();\n   symbol->exported = true;\n   symbol->hostPointer = reinterpret_cast<virt_ptr<void>*>(&data);\n   symbol->constructor = [](void* ptr) { new (ptr) DataType(); };\n   symbol->size = sizeof(DataType);\n   symbol->align = alignof(DataType);\n   library->registerSymbol(name, std::move(symbol));\n}\n\nnamespace detail\n{\ntemplate<typename T>\nclass has_ghs_virtual_table\n{\n    typedef char yes_type;\n    typedef long no_type;\n    template <typename U> static yes_type test(decltype(&U::VirtualTable));\n    template <typename U> static no_type  test(...);\npublic:\n    static constexpr bool value = sizeof(test<T>(0)) == sizeof(yes_type);\n};\n} // namespace detail\n\ntemplate<typename ObjectType>\nstatic void\nregisterTypeInfo(hle::Library *library,\n                 const char *typeName,\n                 std::vector<const char *> &&virtualTable,\n                 std::vector<const char *> &&baseTypes,\n                 const char *typeIdSymbol = nullptr)\n{\n   auto typeInfo = LibraryTypeInfo { };\n   typeInfo.name = typeName;\n   typeInfo.typeIdSymbol = typeIdSymbol;\n   typeInfo.hostTypeDescriptorPtr = &ObjectType::TypeDescriptor;\n\n   typeInfo.virtualTable = std::move(virtualTable);\n   typeInfo.baseTypes = std::move(baseTypes);\n\n   if constexpr (detail::has_ghs_virtual_table<ObjectType>::value) {\n      typeInfo.hostVirtualTablePtr = &ObjectType::VirtualTable;\n   }\n\n   library->registerTypeInfo(std::move(typeInfo));\n}\n\n#define fnptr_decltype(Func) \\\n    std::conditional<std::is_function<decltype(Func)>::value, std::add_pointer<decltype(Func)>::type, decltype(Func)>::type\n\n#define RegisterEntryPoint(fn) \\\n   cafe::hle::registerEntryPoint<fn>(this, #fn)\n\n#define RegisterNoCrtEntryPoint(fn) \\\n   cafe::hle::registerNoCrtEntryPoint<fnptr_decltype(fn), fn>(this, #fn)\n\n#define RegisterGenericEntryPoint() \\\n   RegisterNoCrtEntryPoint(cafe_generic_rpl_crt<0>)\n\n#define RegisterFunctionExport(fn) \\\n   cafe::hle::registerFunctionExport<fnptr_decltype(fn), fn>(this, #fn)\n\n#define RegisterFunctionExportName(name, fn) \\\n   cafe::hle::registerFunctionExport<fnptr_decltype(fn), fn>(this, name)\n\n#define RegisterDataExport(data) \\\n   cafe::hle::registerDataExport(this, #data, data)\n\n#define RegisterDataExportName(name, data) \\\n   cafe::hle::registerDataExport(this, name, data)\n\n#define RegisterFunctionInternal(fn, ptr) \\\n   cafe::hle::registerFunctionInternal<fnptr_decltype(fn), fn>(this, \"__internal__\" # fn, ptr)\n\n#define RegisterFunctionInternalName(name, fn) \\\n   cafe::hle::registerFunctionInternal<fnptr_decltype(fn), fn>(this, name)\n\n#define RegisterDataInternal(data) \\\n   cafe::hle::registerDataInternal(this, \"__internal__\" # data, data)\n\n#define RegisterTypeInfo(type, name, ...) \\\n   cafe::hle::registerTypeInfo<type>(this, name, __VA_ARGS__);\n\n} // namespace cafe::hle\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle_library_symbol.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n#include <string>\n\nnamespace cafe::hle\n{\n\nstruct LibrarySymbol\n{\n   static constexpr auto InvalidOffset = 0xCD000000;\n\n   enum Type\n   {\n      Undefined,\n      Function,\n      Data,\n   };\n\n   LibrarySymbol(Type type) :\n      type(type)\n   {\n   }\n\n   virtual ~LibrarySymbol()\n   {\n   }\n\n   //! Symbol type\n   Type type = Undefined;\n\n   //! Symbol index in library\n   uint32_t index = 0;\n\n   //! Symbol name\n   std::string name;\n\n   //! Whether the symbol is exported or not\n   bool exported = false;\n\n   //! Offset in .text or .data section\n   uint32_t offset = InvalidOffset;\n\n   //! Virtual address of this symbol\n   //! TODO: Change stuff to use offset when we go multi-process!\n   virt_addr address = virt_addr { 0 };\n};\n\n} // namespace cafe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle_library_typeinfo.h",
    "content": "#pragma once\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n\n#include <libcpu/be2_struct.h>\n#include <vector>\n\nnamespace cafe::hle\n{\n\nstruct LibraryTypeInfo\n{\n   const char *name = nullptr;\n   std::vector<const char *> virtualTable;\n   std::vector<const char *> baseTypes;\n   virt_ptr<ghs::VirtualTable> *hostVirtualTablePtr = nullptr;\n   virt_ptr<ghs::TypeDescriptor> *hostTypeDescriptorPtr = nullptr;\n\n   uint32_t nameOffset = 0u;\n   uint32_t baseTypeOffset = 0u;\n   uint32_t typeDescriptorOffset = 0u;\n   uint32_t virtualTableOffset = 0u;\n\n   uint32_t typeIdOffset = 0u;\n   const char *typeIdSymbol = nullptr;\n};\n\n} // namespace cafe::hle\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle_stub.cpp",
    "content": "#include \"cafe_hle_stub.h\"\n\n#include <common/log.h>\n\nnamespace cafe::hle\n{\n\nvoid\nwarnStubInvoked(const char *name)\n{\n   gLog->warn(\"Application invoked stubbed function `{}`\", name);\n}\n\n} // namespace cafe::hle\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/cafe_hle_stub.h",
    "content": "#pragma once\n\n#ifdef _MSC_VER\n#define PRETTY_FUNCTION_NAME __FUNCSIG__\n#else\n#define PRETTY_FUNCTION_NAME __PRETTY_FUNCTION__\n#endif\n\n#define decaf_warn_stub() \\\n   { \\\n      static bool warned = false; \\\n      if (!warned) { \\\n         cafe::hle::warnStubInvoked(PRETTY_FUNCTION_NAME); \\\n         warned = true; \\\n      } \\\n   }\n\nnamespace cafe::hle\n{\n\nvoid\nwarnStubInvoked(const char *name);\n\n} // namespace cafe::hle\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/camera/camera.cpp",
    "content": "#include \"camera.h\"\n\nnamespace cafe::camera\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerCamSymbols();\n}\n\n} // namespace cafe::camera\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/camera/camera.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::camera\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::camera, \"camera.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerCamSymbols();\n};\n\n} // namespace cafe::camera\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/camera/camera_cam.cpp",
    "content": "#include \"camera.h\"\n#include \"camera_cam.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::camera\n{\n\nCAMHandle\nCAMInit(uint32_t id,\n        virt_ptr<CAMInitInfo> info,\n        virt_ptr<CAMError> outError)\n{\n   decaf_warn_stub();\n   *outError = CAMError::OK;\n   return id;\n}\n\nvoid\nCAMExit(CAMHandle handle)\n{\n   decaf_warn_stub();\n}\n\nint32_t\nCAMOpen(CAMHandle handle)\n{\n   decaf_warn_stub();\n   return CAMError::OK;\n}\n\nint32_t\nCAMClose(CAMHandle handle)\n{\n   decaf_warn_stub();\n   return CAMError::OK;\n}\n\nint32_t\nCAMGetMemReq(virt_ptr<CAMMemoryInfo> info)\n{\n   decaf_warn_stub();\n\n   if (!info) {\n      return -1;\n   }\n\n   auto bufferSize = info->width * info->height * 10;\n   auto extraSize = 2 * 0x14 + 0x5400;\n   return bufferSize + extraSize;\n}\n\nvoid\nLibrary::registerCamSymbols()\n{\n   RegisterFunctionExport(CAMInit);\n   RegisterFunctionExport(CAMExit);\n   RegisterFunctionExport(CAMOpen);\n   RegisterFunctionExport(CAMClose);\n   RegisterFunctionExport(CAMGetMemReq);\n}\n\n} // namespace cafe::camera\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/camera/camera_cam.h",
    "content": "#pragma once\n#include \"camera_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::camera\n{\n\n#pragma pack(push, 1)\n\nusing CAMHandle = int32_t;\n\nstruct CAMMemoryInfo\n{\n   be2_val<uint32_t> unk0;\n   be2_val<uint32_t> width;\n   be2_val<uint32_t> height;\n};\nCHECK_OFFSET(CAMMemoryInfo, 0x00, unk0);\nCHECK_OFFSET(CAMMemoryInfo, 0x04, width);\nCHECK_OFFSET(CAMMemoryInfo, 0x08, height);\nCHECK_SIZE(CAMMemoryInfo, 0x0C);\n\nstruct CAMInitInfo\n{\n   CAMMemoryInfo memInfo;\n   be2_virt_ptr<void> workMemory;\n   be2_val<uint32_t> workMemorySize;\n   // 0x14 = some function pointer\n   UNKNOWN(0x20);\n};\nCHECK_OFFSET(CAMInitInfo, 0x00, memInfo);\nCHECK_OFFSET(CAMInitInfo, 0x0C, workMemory);\nCHECK_OFFSET(CAMInitInfo, 0x10, workMemorySize);\nCHECK_SIZE(CAMInitInfo, 0x34);\n\n#pragma pack(pop)\n\nCAMHandle\nCAMInit(uint32_t id,\n        virt_ptr<CAMInitInfo> info,\n        virt_ptr<CAMError> outError);\n\nvoid\nCAMExit(CAMHandle handle);\n\nint32_t\nCAMOpen(CAMHandle handle);\n\nint32_t\nCAMClose(CAMHandle handle);\n\nint32_t\nCAMGetMemReq(virt_ptr<CAMMemoryInfo> info);\n\n} // namespace cafe::camera\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/camera/camera_enum.h",
    "content": "#ifndef CAFE_CAMERA_ENUM_H\n#define CAFE_CAMERA_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(camera)\n\nENUM_BEG(CAMError, int32_t)\n   ENUM_VALUE(OK,                      0)\n   ENUM_VALUE(GenericError,            -1)\n   ENUM_VALUE(AlreadyInitialised,      -12)\nENUM_END(CAMError)\n\nENUM_NAMESPACE_EXIT(camera)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_CAMERA_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_alarm.h\"\n#include \"coreinit_appio.h\"\n#include \"coreinit_bsp.h\"\n#include \"coreinit_device.h\"\n#include \"coreinit_driver.h\"\n#include \"coreinit_dynload.h\"\n#include \"coreinit_exception.h\"\n#include \"coreinit_ghs.h\"\n#include \"coreinit_im.h\"\n#include \"coreinit_interrupts.h\"\n#include \"coreinit_ipcdriver.h\"\n#include \"coreinit_lockedcache.h\"\n#include \"coreinit_mcp.h\"\n#include \"coreinit_memallocator.h\"\n#include \"coreinit_memory.h\"\n#include \"coreinit_memheap.h\"\n#include \"coreinit_scheduler.h\"\n#include \"coreinit_systeminfo.h\"\n#include \"coreinit_systemmessagequeue.h\"\n#include \"coreinit_thread.h\"\n#include \"coreinit_time.h\"\n#include <common/log.h>\n\n#include \"cafe/libraries/cafe_hle.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\nnamespace cafe::coreinit\n{\n\nstatic void\ncoreinit_entry(/* no args for coreinit entry point */)\n{\n   auto coreId = cpu::this_core::id();\n\n   // Always initialise time then system info first, other things depend on it.\n   internal::initialiseTime();\n   internal::initialiseSystemInfo();\n\n   internal::initialiseIci();\n   internal::initialiseSystemMessageQueue();\n   internal::initialiseExceptionHandlers();\n   internal::initialiseScheduler();\n   internal::initialiseThreads();\n   internal::initialiseAlarmThread();\n   internal::initialiseLockedCache(coreId);\n   internal::initialiseMemory();\n   internal::initialiseMemHeap();\n   internal::initialiseAllocatorStaticData();\n   IPCDriverInit();\n   IPCDriverOpen();\n   internal::initialiseAppIoThreads();\n   internal::initialiseDeviceTable();\n   bspInitializeShimInterface();\n   internal::initialiseMcp();\n\n   auto entryPoint = internal::initialiseDynLoad();\n\n   // registerMemDriver\n   // registerCacheDriver\n   // registerIpcDriver\n   // registerInputDriver\n   internal::initialiseIm(); // Actually called from registerInputDriver\n   // registerTestDriver\n   // registerAcpLoadDriver\n   // registerButtonDriver\n   // registerClipboardDriver\n   // driver on init\n   internal::driverOnInit();\n\n   auto entryFunc =\n      virt_func_cast<int32_t(uint32_t argc, virt_ptr<void> argv)>(entryPoint);\n   auto result = cafe::invoke(cpu::this_core::state(),\n                              entryFunc,\n                              uint32_t { 0u }, virt_ptr<void> { nullptr });\n   ghs_exit(result);\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterNoCrtEntryPoint(coreinit_entry);\n\n   registerAlarmSymbols();\n   registerAppIoSymbols();\n   registerAtomicSymbols();\n   registerAtomic64Symbols();\n   registerBspSymbols();\n   registerCacheSymbols();\n   registerClipboardSymbols();\n   registerCodeGenSymbols();\n   registerContextSymbols();\n   registerCoreSymbols();\n   registerCoroutineSymbols();\n   registerCosReportSymbols();\n   registerDeviceSymbols();\n   registerDriverSymbols();\n   registerDynLoadSymbols();\n   registerEventSymbols();\n   registerExceptionSymbols();\n   registerFastMutexSymbols();\n   registerFiberSymbols();\n   registerFsSymbols();\n   registerFsClientSymbols();\n   registerFsCmdSymbols();\n   registerFsCmdBlockSymbols();\n   registerFsDriverSymbols();\n   registerFsStateMachineSymbols();\n   registerFsaSymbols();\n   registerFsaCmdSymbols();\n   registerFsaShimSymbols();\n   registerGhsSymbols();\n   registerHandleSymbols();\n   registerImSymbols();\n   registerInterruptSymbols();\n   registerIosSymbols();\n   registerIpcBufPoolSymbols();\n   registerIpcDriverSymbols();\n   registerLockedCacheSymbols();\n   registerMcpSymbols();\n   registerMemAllocatorSymbols();\n   registerMemBlockHeapSymbols();\n   registerMemDefaultHeapSymbols();\n   registerMemExpHeapSymbols();\n   registerMemFrmHeapSymbols();\n   registerMemHeapSymbols();\n   registerMemListSymbols();\n   registerMemorySymbols();\n   registerMemUnitHeapSymbols();\n   registerMessageQueueSymbols();\n   registerMutexSymbols();\n   registerOsReportSymbols();\n   registerOverlayArenaSymbols();\n   registerRendezvousSymbols();\n   registerSchedulerSymbols();\n   registerScreenSymbols();\n   registerSemaphoreSymbols();\n   registerSnprintfSymbols();\n   registerSpinLockSymbols();\n   registerSystemHeapSymbols();\n   registerSystemInfoSymbols();\n   registerSystemMessageQueueSymbols();\n   registerTaskQueueSymbols();\n   registerThreadSymbols();\n   registerTimeSymbols();\n   registerUserConfigSymbols();\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::coreinit\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::coreinit, \"coreinit.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerAlarmSymbols();\n   void registerAppIoSymbols();\n   void registerAtomicSymbols();\n   void registerAtomic64Symbols();\n   void registerBspSymbols();\n   void registerCacheSymbols();\n   void registerClipboardSymbols();\n   void registerCodeGenSymbols();\n   void registerContextSymbols();\n   void registerCoreSymbols();\n   void registerCoroutineSymbols();\n   void registerCosReportSymbols();\n   void registerDeviceSymbols();\n   void registerDriverSymbols();\n   void registerDynLoadSymbols();\n   void registerEventSymbols();\n   void registerExceptionSymbols();\n   void registerFastMutexSymbols();\n   void registerFiberSymbols();\n   void registerFsSymbols();\n   void registerFsClientSymbols();\n   void registerFsCmdSymbols();\n   void registerFsCmdBlockSymbols();\n   void registerFsDriverSymbols();\n   void registerFsStateMachineSymbols();\n   void registerFsaSymbols();\n   void registerFsaCmdSymbols();\n   void registerFsaShimSymbols();\n   void registerGhsSymbols();\n   void registerHandleSymbols();\n   void registerImSymbols();\n   void registerInterruptSymbols();\n   void registerIosSymbols();\n   void registerIpcBufPoolSymbols();\n   void registerIpcDriverSymbols();\n   void registerLockedCacheSymbols();\n   void registerMcpSymbols();\n   void registerMemAllocatorSymbols();\n   void registerMemBlockHeapSymbols();\n   void registerMemDefaultHeapSymbols();\n   void registerMemExpHeapSymbols();\n   void registerMemFrmHeapSymbols();\n   void registerMemHeapSymbols();\n   void registerMemListSymbols();\n   void registerMemorySymbols();\n   void registerMemUnitHeapSymbols();\n   void registerMessageQueueSymbols();\n   void registerMutexSymbols();\n   void registerOsReportSymbols();\n   void registerOverlayArenaSymbols();\n   void registerRendezvousSymbols();\n   void registerSchedulerSymbols();\n   void registerScreenSymbols();\n   void registerSemaphoreSymbols();\n   void registerSnprintfSymbols();\n   void registerSpinLockSymbols();\n   void registerSystemHeapSymbols();\n   void registerSystemInfoSymbols();\n   void registerSystemMessageQueueSymbols();\n   void registerTaskQueueSymbols();\n   void registerThreadSymbols();\n   void registerTimeSymbols();\n   void registerUserConfigSymbols();\n};\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_alarm.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_alarm.h\"\n#include \"coreinit_core.h\"\n#include \"coreinit_spinlock.h\"\n#include \"coreinit_interrupts.h\"\n#include \"coreinit_scheduler.h\"\n#include \"coreinit_thread.h\"\n#include \"coreinit_memheap.h\"\n#include \"coreinit_memory.h\"\n#include \"coreinit_time.h\"\n#include \"coreinit_internal_queue.h\"\n#include \"coreinit_internal_idlock.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\n#include <libcpu/cpu_control.h>\n#include <array>\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n\nnamespace cafe::coreinit\n{\n\nconstexpr auto AlarmThreadStackSize = 0x8000u;\n\nstruct StaticAlarmData\n{\n   internal::IdLock lock;\n\n   struct PerCoreAlarmData\n   {\n      be2_struct<OSThread> thread;\n      be2_array<char, 16> threadName;\n      be2_array<uint8_t, AlarmThreadStackSize> threadStack;\n      be2_struct<OSAlarmQueue> alarmQueue;\n      be2_struct<OSAlarmQueue> callbackAlarmQueue;\n      be2_struct<OSThreadQueue> callbackThreadQueue;\n   };\n\n   be2_array<PerCoreAlarmData, OSGetCoreCount()> perCoreData;\n};\n\nstatic virt_ptr<StaticAlarmData>\nsAlarmData = nullptr;\n\nstatic OSThreadEntryPointFn\nsAlarmCallbackThreadEntry;\n\nnamespace internal\n{\n\nusing AlarmQueue = Queue<OSAlarmQueue, OSAlarmLink, OSAlarm, &OSAlarm::link>;\nvoid updateCpuAlarmNoALock();\n\n} // namespace internal\n\n/**\n * Internal alarm cancel.\n *\n * Reset the alarm state to cancelled.\n * Wakes up all threads waiting on the alarm.\n * Removes the alarm from any queue it is in.\n */\nstatic BOOL\ncancelAlarmNoAlarmLock(virt_ptr<OSAlarm> alarm)\n{\n   // We are technically supposed to try and remove the alarm\n   //  from the callback queue as well.\n   if (alarm->state != OSAlarmState::Set) {\n      return FALSE;\n   }\n\n   alarm->state = OSAlarmState::Idle;\n   alarm->nextFire = 0;\n   alarm->period = 0;\n\n   if (alarm->alarmQueue) {\n      internal::AlarmQueue::erase(alarm->alarmQueue, alarm);\n      alarm->alarmQueue = nullptr;\n   }\n\n   return TRUE;\n}\n\n\n/**\n * Cancel an alarm.\n */\nBOOL\nOSCancelAlarm(virt_ptr<OSAlarm> alarm)\n{\n   // First cancel the alarm whilst holding alarm lock\n   internal::acquireIdLock(sAlarmData->lock, alarm);\n   auto result = cancelAlarmNoAlarmLock(alarm);\n   internal::releaseIdLock(sAlarmData->lock, alarm);\n\n   if (!result) {\n      return FALSE;\n   }\n\n   // Now wakeup any waiting threads\n   internal::lockScheduler();\n\n   for (auto thread = alarm->threadQueue.head; thread; thread = thread->link.next) {\n      thread->alarmCancelled = TRUE;\n   }\n\n   internal::wakeupThreadNoLock(virt_addrof(alarm->threadQueue));\n   internal::rescheduleAllCoreNoLock();\n   internal::unlockScheduler();\n   return TRUE;\n}\n\n\n/**\n * Cancel all alarms which have a matching tag.\n */\nvoid\nOSCancelAlarms(uint32_t group)\n{\n   internal::lockScheduler();\n   internal::acquireIdLock(sAlarmData->lock, static_cast<uint32_t>(-2));\n\n   for (auto &perCoreData : sAlarmData->perCoreData) {\n      for (auto alarm = perCoreData.alarmQueue.head; alarm; ) {\n         auto next = alarm->link.next;\n\n         if (alarm->group == group) {\n            if (cancelAlarmNoAlarmLock(alarm)) {\n               for (auto thread = alarm->threadQueue.head; thread; thread = thread->link.next) {\n                  thread->alarmCancelled = TRUE;\n               }\n\n               internal::wakeupThreadNoLock(virt_addrof(alarm->threadQueue));\n            }\n         }\n\n         alarm = next;\n      }\n   }\n\n   internal::releaseIdLock(sAlarmData->lock, static_cast<uint32_t>(-2));\n   internal::rescheduleAllCoreNoLock();\n   internal::unlockScheduler();\n}\n\n\n/**\n * Initialise an alarm structure.\n */\nvoid\nOSCreateAlarm(virt_ptr<OSAlarm> alarm)\n{\n   OSCreateAlarmEx(alarm, nullptr);\n}\n\n\n/**\n * Initialise an alarm structure.\n */\nvoid\nOSCreateAlarmEx(virt_ptr<OSAlarm> alarm,\n                virt_ptr<const char> name)\n{\n   // Holding the alarm here is neccessary since its valid to call\n   // Create on an already active alarm, as long as its not set.\n   internal::acquireIdLock(sAlarmData->lock, alarm);\n\n   memset(alarm, 0, sizeof(OSAlarm));\n   alarm->tag = OSAlarm::Tag;\n   alarm->name = name;\n   OSInitThreadQueueEx(virt_addrof(alarm->threadQueue), alarm);\n\n   internal::releaseIdLock(sAlarmData->lock, alarm);\n}\n\n\n/**\n * Return the user data stored in the alarm using OSSetAlarmUserData\n */\nvirt_ptr<void>\nOSGetAlarmUserData(virt_ptr<OSAlarm> alarm)\n{\n   return alarm->userData;\n}\n\n\n/**\n * Initialise an alarm queue structure\n */\nvoid\nOSInitAlarmQueue(virt_ptr<OSAlarmQueue> queue)\n{\n   OSInitAlarmQueueEx(queue, nullptr);\n}\n\n\n/**\n * Initialise an alarm queue structure with a name\n */\nvoid\nOSInitAlarmQueueEx(virt_ptr<OSAlarmQueue> queue,\n                   virt_ptr<const char> name)\n{\n   memset(queue, 0, sizeof(OSAlarmQueue));\n   queue->tag = OSAlarmQueue::Tag;\n   queue->name = name;\n}\n\n\n/**\n * Set a one shot alarm to perform a callback after an amount of time.\n */\nBOOL\nOSSetAlarm(virt_ptr<OSAlarm> alarm,\n           OSTime time,\n           AlarmCallbackFn callback)\n{\n   return OSSetPeriodicAlarm(alarm, OSGetTime() + time, 0, callback);\n}\n\n\n/**\n * Set a repeated alarm to execute a callback every interval from start.\n *\n * \\param alarm\n * The alarm to set.\n *\n * \\param start\n * The absolute time the alarm should first be triggered.\n *\n * \\param interval\n * The interval between triggers after the first trigger.\n *\n * \\param callback\n * The alarm callback to call when the alarm is triggered.\n *\n * \\return\n * Returns TRUE if the alarm was succesfully set, FALSE otherwise.\n */\nBOOL\nOSSetPeriodicAlarm(virt_ptr<OSAlarm> alarm,\n                   OSTime start,\n                   OSTime interval,\n                   AlarmCallbackFn callback)\n{\n   internal::acquireIdLock(sAlarmData->lock, alarm);\n\n   if (!start) {\n      start = OSGetTime() + interval;\n   }\n\n   // Set alarm\n   alarm->nextFire = start;\n   alarm->callback = callback;\n   alarm->period = interval;\n   alarm->context = nullptr;\n   alarm->state = OSAlarmState::Set;\n\n   // Erase from old alarm queue\n   if (alarm->alarmQueue) {\n      internal::AlarmQueue::erase(alarm->alarmQueue, alarm);\n      alarm->alarmQueue = nullptr;\n   }\n\n   // Add to this core's alarm queue\n   auto queue = virt_addrof(sAlarmData->perCoreData[OSGetCoreId()].alarmQueue);\n   alarm->alarmQueue = queue;\n   internal::AlarmQueue::append(queue, alarm);\n\n   // Set the interrupt timer in processor\n   // TODO: Store the last set CPU alarm time, and simply check this\n   // alarm against that time to make finding the soonest alarm cheaper.\n   internal::updateCpuAlarmNoALock();\n\n   internal::releaseIdLock(sAlarmData->lock, alarm);\n   return TRUE;\n}\n\n\n/**\n * Set an alarm tag which is used in OSCancelAlarms for bulk cancellation.\n */\nvoid\nOSSetAlarmTag(virt_ptr<OSAlarm> alarm,\n              uint32_t group)\n{\n   internal::acquireIdLock(sAlarmData->lock, alarm);\n   alarm->group = group;\n   internal::releaseIdLock(sAlarmData->lock, alarm);\n}\n\n\n/**\n * Set alarm user data which is returned by OSGetAlarmUserData.\n */\nvoid\nOSSetAlarmUserData(virt_ptr<OSAlarm> alarm,\n                   virt_ptr<void> data)\n{\n   internal::acquireIdLock(sAlarmData->lock, alarm);\n   alarm->userData = data;\n   internal::releaseIdLock(sAlarmData->lock, alarm);\n}\n\n\n/**\n * Sleep the current thread until the alarm has been triggered or cancelled.\n *\n * \\return\n * Returns TRUE if alarm expired, FALSE if alarm cancelled\n */\nBOOL\nOSWaitAlarm(virt_ptr<OSAlarm> alarm)\n{\n   internal::lockScheduler();\n   internal::acquireIdLock(sAlarmData->lock, alarm);\n\n   decaf_check(alarm);\n   decaf_check(alarm->tag == OSAlarm::Tag);\n\n   if (alarm->state != OSAlarmState::Set) {\n      internal::releaseIdLock(sAlarmData->lock, alarm);\n      internal::unlockScheduler();\n      return FALSE;\n   }\n\n   OSGetCurrentThread()->alarmCancelled = false;\n   internal::sleepThreadNoLock(virt_addrof(alarm->threadQueue));\n\n   internal::releaseIdLock(sAlarmData->lock, alarm);\n   internal::rescheduleSelfNoLock();\n\n   auto cancelled = OSGetCurrentThread()->alarmCancelled;\n   internal::unlockScheduler();\n\n   if (cancelled) {\n      return FALSE;\n   } else {\n      return TRUE;\n   }\n}\n\nstatic void\ninsertAlarm(virt_ptr<OSAlarm> alarm,\n            AlarmCallbackFn callback)\n{\n   decaf_check(alarm);\n   decaf_check(alarm->state == OSAlarmState::Idle || alarm->state == OSAlarmState::Invalid);\n   decaf_check(alarm->link.prev == nullptr && alarm->link.next == nullptr);\n   decaf_check(!OSIsInterruptEnabled());\n   decaf_check(internal::isLockHeldBySomeone(sAlarmData->lock));\n\n   OSGetSystemTime();\n}\n\nint\nOSGetAlarmFromQueue(virt_ptr<OSAlarmQueue> queue,\n                    virt_ptr<virt_ptr<OSAlarm>> outAlarm,\n                    virt_ptr<virt_ptr<OSContext>> outCallbackContext,\n                    uint32_t r6)\n{\n   OSDisableInterrupts();\n\n   while (true) {\n      internal::acquireIdLock(sAlarmData->lock, queue);\n\n      if (queue->head) {\n         auto alarm = queue->head;\n         queue->head = alarm->link.next;\n\n         if (!queue->head) {\n            queue->tail = nullptr;\n         } else {\n            queue->head->link.prev = nullptr;\n         }\n\n         alarm->link.prev = nullptr;\n         alarm->link.next = nullptr;\n         alarm->state = OSAlarmState::Invalid;\n\n         if (alarm->period) {\n            insertAlarm(alarm, alarm->callback);\n\n         }\n      }\n\n      internal::releaseIdLock(sAlarmData->lock, queue);\n   }\n}\n\nstatic uint32_t\nalarmCallbackThreadEntry(uint32_t coreId,\n                         virt_ptr<void> arg2)\n{\n   auto queue = virt_addrof(sAlarmData->perCoreData[coreId].alarmQueue);\n   auto cbQueue = virt_addrof(sAlarmData->perCoreData[coreId].callbackAlarmQueue);\n   auto threadQueue = virt_addrof(sAlarmData->perCoreData[coreId].callbackThreadQueue);\n\n   while (true) {\n      internal::lockScheduler();\n      internal::acquireIdLock(sAlarmData->lock, arg2);\n\n      virt_ptr<OSAlarm> alarm = internal::AlarmQueue::popFront(cbQueue);\n      if (alarm == nullptr) {\n         // No alarms currently pending for callback\n         internal::sleepThreadNoLock(threadQueue);\n         internal::releaseIdLock(sAlarmData->lock, arg2);\n\n         internal::rescheduleSelfNoLock();\n         internal::unlockScheduler();\n         continue;\n      }\n\n      if (alarm->period) {\n         alarm->nextFire = alarm->nextFire + alarm->period;\n         alarm->state = OSAlarmState::Set;\n         internal::AlarmQueue::append(queue, alarm);\n         alarm->alarmQueue = queue;\n         internal::updateCpuAlarmNoALock();\n      }\n\n      internal::releaseIdLock(sAlarmData->lock, arg2);\n      internal::unlockScheduler();\n\n      if (alarm->callback) {\n         cafe::invoke(cpu::this_core::state(), alarm->callback, alarm, alarm->context);\n      }\n   }\n   return 0;\n}\n\nnamespace internal\n{\n\nBOOL\nsetAlarmInternal(virt_ptr<OSAlarm> alarm,\n                 OSTime time,\n                 AlarmCallbackFn callback,\n                 virt_ptr<void> userData)\n{\n   alarm->group = 0xFFFFFFFF;\n   alarm->userData = userData;\n   return OSSetAlarm(alarm, time, callback);\n}\n\nbool\ncancelAlarm(virt_ptr<OSAlarm> alarm)\n{\n   internal::acquireIdLock(sAlarmData->lock, alarm);\n   auto result = cancelAlarmNoAlarmLock(alarm);\n   internal::releaseIdLock(sAlarmData->lock, alarm);\n   return result == TRUE;\n}\n\nvoid\nupdateCpuAlarmNoALock()\n{\n   auto &queue = sAlarmData->perCoreData[cpu::this_core::id()].alarmQueue;\n   auto next = std::chrono::steady_clock::time_point::max();\n\n   for (virt_ptr<OSAlarm> alarm = queue.head; alarm; ) {\n      auto nextAlarm = alarm->link.next;\n\n      // Update next if its not past yet\n      if (alarm->state == OSAlarmState::Set && alarm->nextFire) {\n         auto nextFire = cpu::tbToTimePoint(alarm->nextFire - internal::getBaseTime());\n\n         if (nextFire < next) {\n            next = nextFire;\n         }\n      }\n\n      alarm = nextAlarm;\n   }\n\n   cpu::this_core::setNextAlarm(next);\n}\n\nvoid\nhandleAlarmInterrupt(virt_ptr<OSContext> context)\n{\n   auto &coreAlarmData = sAlarmData->perCoreData[cpu::this_core::id()];\n   auto queue = virt_addrof(coreAlarmData.alarmQueue);\n   auto cbQueue = virt_addrof(coreAlarmData.callbackAlarmQueue);\n   auto cbThreadQueue = virt_addrof(coreAlarmData.callbackThreadQueue);\n   auto now = OSGetTime();\n\n   internal::lockScheduler();\n   acquireIdLockWithCoreId(sAlarmData->lock);\n\n   for (virt_ptr<OSAlarm> alarm = queue->head; alarm; ) {\n      auto nextAlarm = alarm->link.next;\n\n      // Expire it if its past its nextFire time\n      if (alarm->nextFire <= now) {\n         decaf_check(alarm->state == OSAlarmState::Set);\n\n         internal::AlarmQueue::erase(queue, alarm);\n         alarm->alarmQueue = nullptr;\n\n         alarm->state = OSAlarmState::Expired;\n         alarm->context = context;\n\n         if (alarm->threadQueue.head) {\n            wakeupThreadNoLock(virt_addrof(alarm->threadQueue));\n            rescheduleOtherCoreNoLock();\n         }\n\n         if (alarm->group == 0xFFFFFFFF) {\n            // System-internal alarm\n            if (alarm->callback) {\n               auto originalMask = cpu::this_core::setInterruptMask(0);\n               cafe::invoke(cpu::this_core::state(), alarm->callback, alarm, context);\n               cpu::this_core::setInterruptMask(originalMask);\n            }\n         } else {\n            internal::AlarmQueue::append(cbQueue, alarm);\n            alarm->alarmQueue = cbQueue;\n\n            wakeupThreadNoLock(cbThreadQueue);\n         }\n      }\n\n      alarm = nextAlarm;\n   }\n\n   internal::updateCpuAlarmNoALock();\n\n   releaseIdLockWithCoreId(sAlarmData->lock);\n   internal::unlockScheduler();\n}\n\nvoid\ninitialiseAlarmThread()\n{\n   auto coreId = cpu::this_core::id();\n   auto &coreData = sAlarmData->perCoreData[coreId];\n\n   // Iniitalise data\n   coreData.threadName = fmt::format(\"Alarm Thread {}\", coreId);\n   OSInitAlarmQueue(virt_addrof(coreData.alarmQueue));\n   OSInitAlarmQueue(virt_addrof(coreData.callbackAlarmQueue));\n   OSInitThreadQueue(virt_addrof(coreData.callbackThreadQueue));\n\n   // Start alarm thread\n   auto thread = virt_addrof(coreData.thread);\n   auto stack = virt_addrof(coreData.threadStack);\n   coreinit__OSCreateThreadType(thread,\n                                sAlarmCallbackThreadEntry,\n                                coreId,\n                                nullptr,\n                                virt_cast<uint32_t *>(stack + coreData.threadStack.size()),\n                                coreData.threadStack.size(),\n                                1,\n                                static_cast<OSThreadAttributes>(1 << coreId),\n                                OSThreadType::AppIo);\n   OSSetThreadName(thread, virt_addrof(coreData.threadName));\n   OSResumeThread(thread);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerAlarmSymbols()\n{\n   RegisterFunctionExport(OSCancelAlarm);\n   RegisterFunctionExport(OSCancelAlarms);\n   RegisterFunctionExport(OSCreateAlarm);\n   RegisterFunctionExport(OSCreateAlarmEx);\n   RegisterFunctionExport(OSGetAlarmUserData);\n   RegisterFunctionExport(OSInitAlarmQueue);\n   RegisterFunctionExport(OSInitAlarmQueueEx);\n   RegisterFunctionExport(OSSetAlarm);\n   RegisterFunctionExport(OSSetPeriodicAlarm);\n   RegisterFunctionExport(OSSetAlarmTag);\n   RegisterFunctionExport(OSSetAlarmUserData);\n   RegisterFunctionExport(OSWaitAlarm);\n\n   RegisterDataInternal(sAlarmData);\n   RegisterFunctionInternal(alarmCallbackThreadEntry, sAlarmCallbackThreadEntry);\n}\n\n} // namespace coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_alarm.h",
    "content": "#pragma once\n#include \"coreinit_context.h\"\n#include \"coreinit_enum.h\"\n#include \"coreinit_thread.h\"\n#include \"coreinit_time.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_alarms Alarms\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSAlarm;\nusing AlarmCallbackFn = virt_func_ptr<void(virt_ptr<OSAlarm> alarm,\n                                           virt_ptr<OSContext> context)>;\n\nstruct OSAlarmQueue\n{\n   static constexpr uint32_t Tag = 0x614C6D51;\n\n   //! Should always be set to the value OSAlarmQueue::Tag.\n   be2_val<uint32_t> tag;\n\n   //! Name set from OSInitAlarmQueueEx.\n   be2_virt_ptr<const char> name;\n\n   UNKNOWN(4);\n\n   //! List of threads waiting on this alarm queue.\n   be2_struct<OSThreadQueue> threadQueue;\n\n   //! First alarm in the queue.\n   be2_virt_ptr<OSAlarm> head;\n\n   //! Last alarm in the queue.\n   be2_virt_ptr<OSAlarm> tail;\n};\nCHECK_OFFSET(OSAlarmQueue, 0x00, tag);\nCHECK_OFFSET(OSAlarmQueue, 0x04, name);\nCHECK_OFFSET(OSAlarmQueue, 0x0c, threadQueue);\nCHECK_OFFSET(OSAlarmQueue, 0x1c, head);\nCHECK_OFFSET(OSAlarmQueue, 0x20, tail);\nCHECK_SIZE(OSAlarmQueue, 0x24);\n\nstruct OSAlarmLink\n{\n   //! Previous alarm in the queue.\n   be2_virt_ptr<OSAlarm> prev;\n\n   //! Next alarm in the queue.\n   be2_virt_ptr<OSAlarm> next;\n};\nCHECK_OFFSET(OSAlarmLink, 0x00, prev);\nCHECK_OFFSET(OSAlarmLink, 0x04, next);\nCHECK_SIZE(OSAlarmLink, 0x08);\n\nstruct OSAlarm\n{\n   static constexpr uint32_t Tag = 0x614C724D;\n\n   //! Should always be set to the value OSAlarm::Tag.\n   be2_val<uint32_t> tag;\n\n   //! Name set from OSCreateAlarmEx.\n   be2_virt_ptr<const char> name;\n\n   UNKNOWN(4);\n\n   //! The callback to execute once the alarm is triggered.\n   be2_val<AlarmCallbackFn> callback;\n\n   //! Used with OSCancelAlarms for bulk cancellation of alarms.\n   be2_val<uint32_t> group;\n\n   UNKNOWN(4);\n\n   //! The time when the alarm will next be triggered.\n   be2_val<OSTime> nextFire;\n\n   //! Link used for when this OSAlarm object is inside an OSAlarmQueue\n   be2_struct<OSAlarmLink> link;\n\n   //! The period between alarm triggers, this is only set for periodic alarms.\n   be2_val<OSTime> period;\n\n   //! The time the alarm was started.\n   be2_val<OSTime> tbrStart;\n\n   //! User data set with OSSetAlarmUserData and retrieved with OSGetAlarmUserData.\n   be2_virt_ptr<void> userData;\n\n   //! The current state of the alarm.\n   be2_val<OSAlarmState> state;\n\n   //! Queue of threads currently waiting for the alarm to trigger with OSWaitAlarm.\n   be2_struct<OSThreadQueue> threadQueue;\n\n   //! The queue that this alarm is currently in.\n   be2_virt_ptr<OSAlarmQueue> alarmQueue;\n\n   //! The context the alarm was triggered on.\n   be2_virt_ptr<OSContext> context;\n};\nCHECK_OFFSET(OSAlarm, 0x00, tag);\nCHECK_OFFSET(OSAlarm, 0x04, name);\nCHECK_OFFSET(OSAlarm, 0x0c, callback);\nCHECK_OFFSET(OSAlarm, 0x10, group);\nCHECK_OFFSET(OSAlarm, 0x18, nextFire);\nCHECK_OFFSET(OSAlarm, 0x20, link);\nCHECK_OFFSET(OSAlarm, 0x28, period);\nCHECK_OFFSET(OSAlarm, 0x30, tbrStart);\nCHECK_OFFSET(OSAlarm, 0x38, userData);\nCHECK_OFFSET(OSAlarm, 0x3c, state);\nCHECK_OFFSET(OSAlarm, 0x40, threadQueue);\nCHECK_OFFSET(OSAlarm, 0x50, alarmQueue);\nCHECK_OFFSET(OSAlarm, 0x54, context);\nCHECK_SIZE(OSAlarm, 0x58);\n\n#pragma pack(pop)\n\nBOOL\nOSCancelAlarm(virt_ptr<OSAlarm> alarm);\n\nvoid\nOSCancelAlarms(uint32_t alarmTag);\n\nvoid\nOSCreateAlarm(virt_ptr<OSAlarm> alarm);\n\nvoid\nOSCreateAlarmEx(virt_ptr<OSAlarm> alarm,\n                virt_ptr<const char> name);\n\nvirt_ptr<void>\nOSGetAlarmUserData(virt_ptr<OSAlarm> alarm);\n\nvoid\nOSInitAlarmQueue(virt_ptr<OSAlarmQueue> queue);\n\nvoid\nOSInitAlarmQueueEx(virt_ptr<OSAlarmQueue> queue,\n                   virt_ptr<const char> name);\n\nBOOL\nOSSetAlarm(virt_ptr<OSAlarm> alarm,\n           OSTime time,\n           AlarmCallbackFn callback);\n\nBOOL\nOSSetPeriodicAlarm(virt_ptr<OSAlarm> alarm,\n                   OSTime start,\n                   OSTime interval,\n                   AlarmCallbackFn callback);\n\nvoid\nOSSetAlarmTag(virt_ptr<OSAlarm> alarm,\n              uint32_t alarmTag);\n\nvoid\nOSSetAlarmUserData(virt_ptr<OSAlarm> alarm,\n                   virt_ptr<void> data);\n\nBOOL\nOSWaitAlarm(virt_ptr<OSAlarm> alarm);\n\nnamespace internal\n{\n\nBOOL\nsetAlarmInternal(virt_ptr<OSAlarm> alarm,\n                 OSTime time,\n                 AlarmCallbackFn callback,\n                 virt_ptr<void> userData);\n\nbool\ncancelAlarm(virt_ptr<OSAlarm> alarm);\n\nvoid\nhandleAlarmInterrupt(virt_ptr<OSContext> context);\n\nvoid\ninitialiseAlarmThread();\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_appio.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_appio.h\"\n#include \"coreinit_core.h\"\n#include \"coreinit_fsa.h\"\n#include \"coreinit_fs_cmdblock.h\"\n#include \"coreinit_thread.h\"\n#include \"coreinit_messagequeue.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\n#include <fmt/core.h>\n#include <libcpu/state.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nconstexpr auto MessagesPerCore = 256u;\nconstexpr auto AppIoThreadStackSize = 0x2000u;\n\nstruct StaticAppIoData\n{\n   struct PerCoreData\n   {\n      be2_array<char, 16> threadName;\n      be2_struct<OSThread> thread;\n      be2_struct<OSMessageQueue> queue;\n      be2_array<OSMessage, MessagesPerCore> messages;\n      be2_array<uint8_t, AppIoThreadStackSize> stack;\n   };\n\n\n   be2_array<PerCoreData, CoreCount> perCoreData;\n};\n\nstatic virt_ptr<StaticAppIoData>\nsAppIoData = nullptr;\n\nstatic OSThreadEntryPointFn\nsAppIoThreadEntry;\n\nvirt_ptr<OSMessageQueue>\nOSGetDefaultAppIOQueue()\n{\n   return virt_addrof(sAppIoData->perCoreData[OSGetCoreId()].queue);\n}\n\nstatic uint32_t\nappIoThreadEntry(uint32_t coreId,\n                 virt_ptr<void> arg2)\n{\n   auto msg = StackObject<OSMessage> { };\n   auto coreData = virt_addrof(sAppIoData->perCoreData[coreId]);\n   auto queue = virt_addrof(coreData->queue);\n   OSInitMessageQueue(queue,\n                      virt_addrof(coreData->messages),\n                      coreData->messages.size());\n\n   while (true) {\n      OSReceiveMessage(queue, msg, OSMessageFlags::Blocking);\n\n      auto funcType = static_cast<OSFunctionType>(msg->args[2].value());\n      switch (funcType) {\n      case OSFunctionType::FsaCmdAsync:\n      {\n         auto result = FSAGetAsyncResult(msg);\n         if (result->userCallback) {\n            cafe::invoke(cpu::this_core::state(),\n                         result->userCallback,\n                         result->error,\n                         result->command,\n                         result->request,\n                         result->response,\n                         result->userContext);\n         }\n         break;\n      }\n      case OSFunctionType::FsCmdAsync:\n      {\n         auto result = FSGetAsyncResult(msg);\n         if (result->asyncData.userCallback) {\n            cafe::invoke(cpu::this_core::state(),\n                         result->asyncData.userCallback,\n                         result->client,\n                         result->block,\n                         result->status,\n                         result->asyncData.userContext);\n         }\n         break;\n      }\n      case OSFunctionType::FsCmdHandler:\n      {\n         internal::fsCmdBlockHandleResult(virt_cast<FSCmdBlockBody *>(msg->message));\n         break;\n      }\n      default:\n         decaf_abort(fmt::format(\"Unimplemented OSFunctionType {}\", funcType));\n      }\n   }\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseAppIoThreads()\n{\n   for (auto i = 0u; i < sAppIoData->perCoreData.size(); ++i) {\n      auto &coreData = sAppIoData->perCoreData[i];\n      auto thread = virt_addrof(coreData.thread);\n      auto stack = virt_addrof(coreData.stack);\n      coreData.threadName = fmt::format(\"I/O Thread {}\", i);\n\n      coreinit__OSCreateThreadType(thread,\n                                   sAppIoThreadEntry,\n                                   i,\n                                   nullptr,\n                                   virt_cast<uint32_t *>(stack + coreData.stack.size()),\n                                   coreData.stack.size(),\n                                   16,\n                                   static_cast<OSThreadAttributes>(1 << i),\n                                   OSThreadType::AppIo);\n      OSSetThreadName(thread, virt_addrof(coreData.threadName));\n      OSResumeThread(thread);\n   }\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerAppIoSymbols()\n{\n   RegisterFunctionExport(OSGetDefaultAppIOQueue);\n\n   RegisterDataInternal(sAppIoData);\n   RegisterFunctionInternal(appIoThreadEntry, sAppIoThreadEntry);\n}\n\n} // namespace cafe::coreinit\n\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_appio.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_appio App IO\n * \\ingroup coreinit\n * @{\n */\n\nstruct OSMessageQueue;\n\nvirt_ptr<OSMessageQueue>\nOSGetDefaultAppIOQueue();\n\nnamespace internal\n{\n\nvoid\ninitialiseAppIoThreads();\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_atomic.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_atomic.h\"\n\n#include <atomic>\n#include <common/byte_swap.h>\n\nnamespace cafe::coreinit\n{\n\nBOOL\nOSCompareAndSwapAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n                       uint32_t compare,\n                       uint32_t value)\n{\n   return atomic->compare_exchange_strong(compare, value) ? TRUE : FALSE;\n}\n\nBOOL\nOSCompareAndSwapAtomicEx(virt_ptr<be2_atomic<uint32_t>> atomic,\n                         uint32_t compare,\n                         uint32_t value,\n                         virt_ptr<uint32_t> old)\n{\n   auto result = atomic->compare_exchange_strong(compare, value);\n   *old = compare;\n   return result ? TRUE : FALSE;\n}\n\nuint32_t\nOSSwapAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n             uint32_t value)\n{\n   return atomic->exchange(value);\n}\n\nint32_t\nOSAddAtomic(virt_ptr<be2_atomic<int32_t>> atomic,\n            int32_t value)\n{\n   return atomic->fetch_add(value);\n}\n\nuint32_t\nOSAndAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n            uint32_t value)\n{\n   return atomic->fetch_and(value);\n}\n\nuint32_t\nOSOrAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n           uint32_t value)\n{\n   return atomic->fetch_or(value);\n}\n\nuint32_t\nOSXorAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n            uint32_t value)\n{\n   return atomic->fetch_xor(value);\n}\n\nBOOL\nOSTestAndClearAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n                     uint32_t bit)\n{\n   auto previous = OSAndAtomic(atomic, ~(1 << bit));\n\n   if (previous & (1 << bit)) {\n      return TRUE;\n   } else {\n      return FALSE;\n   }\n}\n\nBOOL\nOSTestAndSetAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n                   uint32_t bit)\n{\n   auto previous = OSOrAtomic(atomic, 1 << bit);\n\n   if (previous & (1 << bit)) {\n      return TRUE;\n   } else {\n      return FALSE;\n   }\n}\n\nvoid\nLibrary::registerAtomicSymbols()\n{\n   RegisterFunctionExport(OSCompareAndSwapAtomic);\n   RegisterFunctionExport(OSCompareAndSwapAtomicEx);\n   RegisterFunctionExport(OSSwapAtomic);\n   RegisterFunctionExport(OSAddAtomic);\n   RegisterFunctionExport(OSAndAtomic);\n   RegisterFunctionExport(OSOrAtomic);\n   RegisterFunctionExport(OSXorAtomic);\n   RegisterFunctionExport(OSTestAndClearAtomic);\n   RegisterFunctionExport(OSTestAndSetAtomic);\n}\n\n} // namespace coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_atomic.h",
    "content": "#pragma once\n#include <libcpu/be2_atomic.h>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_atomic Atomic\n * \\ingroup coreinit\n * @{\n */\n\nBOOL\nOSCompareAndSwapAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n                       uint32_t compare,\n                       uint32_t value);\n\nBOOL\nOSCompareAndSwapAtomicEx(virt_ptr<be2_atomic<uint32_t>> atomic,\n                         uint32_t compare,\n                         uint32_t value,\n                         virt_ptr<uint32_t> old);\n\nuint32_t\nOSSwapAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n             uint32_t value);\n\nint32_t\nOSAddAtomic(virt_ptr<be2_atomic<int32_t>> atomic,\n            int32_t value);\n\nuint32_t\nOSAndAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n            uint32_t value);\n\nuint32_t\nOSOrAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n           uint32_t value);\n\nuint32_t\nOSXorAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n            uint32_t value);\n\nBOOL\nOSTestAndClearAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n                     uint32_t bit);\n\nBOOL\nOSTestAndSetAtomic(virt_ptr<be2_atomic<uint32_t>> atomic,\n                   uint32_t bit);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_atomic64.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_atomic64.h\"\n#include \"coreinit_internal_idlock.h\"\n\n#include <common/bitutils.h>\n\nnamespace cafe::coreinit\n{\n\nstatic internal::IdLock sAtomic64Lock;\n\nuint64_t\nOSGetAtomic64(virt_ptr<uint64_t> ptr)\n{\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n   auto value = *ptr;\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return value;\n}\n\nuint64_t\nOSSetAtomic64(virt_ptr<uint64_t> ptr,\n              uint64_t value)\n{\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n   auto old = *ptr;\n   *ptr = value;\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return old;\n}\n\nBOOL\nOSCompareAndSwapAtomic64(virt_ptr<uint64_t> ptr,\n                         uint64_t compare,\n                         uint64_t value)\n{\n   BOOL result = FALSE;\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n\n   if (*ptr == compare) {\n      *ptr = value;\n      result = TRUE;\n   }\n\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return result;\n}\n\nBOOL\nOSCompareAndSwapAtomicEx64(virt_ptr<uint64_t> ptr,\n                           uint64_t compare,\n                           uint64_t value,\n                           virt_ptr<uint64_t> old)\n{\n   BOOL result = FALSE;\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n   *old = *ptr;\n\n   if (*ptr == compare) {\n      *ptr = value;\n      result = TRUE;\n   }\n\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return result;\n}\n\nuint64_t\nOSSwapAtomic64(virt_ptr<uint64_t> ptr,\n               uint64_t value)\n{\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n   auto old = *ptr;\n   *ptr = value;\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return old;\n}\n\nint64_t\nOSAddAtomic64(virt_ptr<int64_t> ptr,\n              int64_t value)\n{\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n   auto result = (*ptr += value);\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return result;\n}\n\nuint64_t\nOSAndAtomic64(virt_ptr<uint64_t> ptr,\n              uint64_t value)\n{\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n   auto result = (*ptr &= value);\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return result;\n}\n\nuint64_t\nOSOrAtomic64(virt_ptr<uint64_t> ptr,\n             uint64_t value)\n{\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n   auto result = (*ptr |= value);\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return result;\n}\n\nuint64_t\nOSXorAtomic64(virt_ptr<uint64_t> ptr,\n              uint64_t value)\n{\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n   auto result = (*ptr ^= value);\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return result;\n}\n\nBOOL\nOSTestAndClearAtomic64(virt_ptr<uint64_t> ptr,\n                       uint32_t bit)\n{\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n   auto result = get_bit<uint64_t>(*ptr, bit) ? TRUE : FALSE;\n   *ptr = clear_bit<uint64_t>(*ptr, bit);\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return result;\n}\n\nBOOL\nOSTestAndSetAtomic64(virt_ptr<uint64_t> ptr,\n                     uint32_t bit)\n{\n   internal::acquireIdLockWithCoreId(sAtomic64Lock);\n   auto result = get_bit<uint64_t>(*ptr, bit) ? TRUE : FALSE;\n   *ptr = set_bit<uint64_t>(*ptr, bit);\n   internal::releaseIdLockWithCoreId(sAtomic64Lock);\n   return result;\n}\n\nvoid\nLibrary::registerAtomic64Symbols()\n{\n   RegisterFunctionExport(OSGetAtomic64);\n   RegisterFunctionExport(OSSetAtomic64);\n   RegisterFunctionExport(OSCompareAndSwapAtomic64);\n   RegisterFunctionExport(OSCompareAndSwapAtomicEx64);\n   RegisterFunctionExport(OSSwapAtomic64);\n   RegisterFunctionExport(OSAddAtomic64);\n   RegisterFunctionExport(OSAndAtomic64);\n   RegisterFunctionExport(OSOrAtomic64);\n   RegisterFunctionExport(OSXorAtomic64);\n   RegisterFunctionExport(OSTestAndClearAtomic64);\n   RegisterFunctionExport(OSTestAndSetAtomic64);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_atomic64.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_atomic64 Atomic64\n * \\ingroup coreinit\n * @{\n */\n\nuint64_t\nOSGetAtomic64(virt_ptr<uint64_t> ptr);\n\nuint64_t\nOSSetAtomic64(virt_ptr<uint64_t> ptr,\n              uint64_t value);\n\nBOOL\nOSCompareAndSwapAtomic64(virt_ptr<uint64_t> ptr,\n                         uint64_t compare,\n                         uint64_t value);\n\nBOOL\nOSCompareAndSwapAtomicEx64(virt_ptr<uint64_t> ptr,\n                           uint64_t compare,\n                           uint64_t value,\n                           virt_ptr<uint64_t> old);\n\nuint64_t\nOSSwapAtomic64(virt_ptr<uint64_t> ptr,\n               uint64_t value);\n\nint64_t\nOSAddAtomic64(virt_ptr<int64_t> ptr,\n              int64_t value);\n\nuint64_t\nOSAndAtomic64(virt_ptr<uint64_t> ptr,\n              uint64_t value);\n\nuint64_t\nOSOrAtomic64(virt_ptr<uint64_t> ptr,\n             uint64_t value);\n\nuint64_t\nOSXorAtomic64(virt_ptr<uint64_t> ptr,\n              uint64_t value);\n\nBOOL\nOSTestAndClearAtomic64(virt_ptr<uint64_t> ptr,\n                       uint32_t bit);\n\nBOOL\nOSTestAndSetAtomic64(virt_ptr<uint64_t> ptr,\n                     uint32_t bit);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_bsp.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_bsp.h\"\n#include \"coreinit_ios.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"ios/bsp/ios_bsp_bsp_request.h\"\n#include \"ios/bsp/ios_bsp_bsp_response.h\"\n\nnamespace cafe::coreinit\n{\n\nusing ios::bsp::BSPCommand;\nusing ios::bsp::BSPRequest;\nusing ios::bsp::BSPResponse;\nusing ios::bsp::BSPResponseGetHardwareVersion;\n\nstruct BSPIpcBuffer\n{\n   be2_struct<BSPRequest> request;\n   UNKNOWN(0x80 - 0x48);\n   be2_struct<BSPResponse> response;\n};\n\nstruct StaticBspData\n{\n   be2_val<IOSHandle> bspHandle;\n};\n\nstatic virt_ptr<StaticBspData>\nsBspData = nullptr;\n\nnamespace internal\n{\n\nstatic BSPError\nprepareIpcBuffer(std::size_t responseSize,\n                 virt_ptr<BSPIpcBuffer> buffer);\n\n}\n\nBSPError\nbspInitializeShimInterface()\n{\n   auto result = IOS_Open(make_stack_string(\"/dev/bsp\"),\n                          ios::OpenMode::None);\n\n   if (IOS_FAILED(result)) {\n      return BSPError::IosError;\n   }\n\n   sBspData->bspHandle = static_cast<IOSHandle>(result);\n   return BSPError::OK;\n}\n\nBSPError\nbspGetHardwareVersion(virt_ptr<BSPHardwareVersion> version)\n{\n   auto buffer = StackObject<BSPIpcBuffer> { };\n   auto error = internal::prepareIpcBuffer(sizeof(BSPResponseGetHardwareVersion),\n                                           buffer);\n   if (error) {\n      return error;\n   }\n\n   auto result = IOS_Ioctl(sBspData->bspHandle,\n                           BSPCommand::GetHardwareVersion,\n                           virt_addrof(buffer->request),\n                           sizeof(BSPRequest),\n                           virt_addrof(buffer->response.getHardwareVersion),\n                           sizeof(BSPResponseGetHardwareVersion));\n\n   if (IOS_FAILED(result)) {\n      return BSPError::IosError;\n   }\n\n   *version = buffer->response.getHardwareVersion.hardwareVersion;\n   return  BSPError::OK;\n}\n\nnamespace internal\n{\n\nstatic BSPError\nprepareIpcBuffer(std::size_t responseSize,\n                 virt_ptr<BSPIpcBuffer> buffer)\n{\n   if (responseSize > sizeof(ios::bsp::BSPResponse)) {\n      return BSPError::ResponseTooLarge;\n   }\n\n   std::memset(virt_addrof(buffer->request).get(),\n               0,\n               sizeof(BSPRequest));\n   return BSPError::OK;\n}\n\nBSPError\nbspShutdownShimInterface()\n{\n   auto result = IOS_Close(sBspData->bspHandle);\n\n   if (IOS_FAILED(result)) {\n      return BSPError::IosError;\n   }\n\n   sBspData->bspHandle = IOSError::Invalid;\n   return BSPError::OK;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerBspSymbols()\n{\n   RegisterFunctionExport(bspInitializeShimInterface);\n   RegisterFunctionExport(bspGetHardwareVersion);\n\n   RegisterDataInternal(sBspData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_bsp.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"ios/bsp/ios_bsp_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nusing BSPHardwareVersion = ios::bsp::HardwareVersion;\n\nBSPError\nbspInitializeShimInterface();\n\nBSPError\nbspGetHardwareVersion(virt_ptr<BSPHardwareVersion> version);\n\nnamespace internal\n{\n\nBSPError\nbspShutdownShimInterface();\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_cache.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_cache.h\"\n#include \"coreinit_memory.h\"\n#include \"cafe/libraries/gx2/gx2_internal_flush.h\"\n\n#include <atomic>\n#include <common/align.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * Equivalent to dcbi instruction.\n */\nvoid\nDCInvalidateRange(virt_addr address,\n                  uint32_t size)\n{\n   // Also signal the GPU to update the memory range.\n   gx2::internal::notifyGpuFlush(OSEffectiveToPhysical(address), size);\n}\n\n\n/**\n * Equivalent to dcbf, sync, eieio.\n */\nvoid\nDCFlushRange(virt_addr address,\n             uint32_t size)\n{\n   // Also signal the memory store to the GPU.\n   gx2::internal::notifyCpuFlush(OSEffectiveToPhysical(address), size);\n}\n\n\n/**\n * Equivalent to dcbst, sync, eieio.\n */\nvoid\nDCStoreRange(virt_addr address,\n             uint32_t size)\n{\n   // Also signal the memory store to the GPU.\n   gx2::internal::notifyCpuFlush(OSEffectiveToPhysical(address), size);\n}\n\n\n/**\n * Equivalent to dcbf.\n */\nvoid\nDCFlushRangeNoSync(virt_addr address,\n                   uint32_t size)\n{\n   // TODO: DCFlushRangeNoSync\n}\n\n\n/**\n * Equivalent to dcbst.\n */\nvoid\nDCStoreRangeNoSync(virt_addr address,\n                   uint32_t size)\n{\n   // TODO: DCStoreRangeNoSync\n}\n\n\n/**\n * Equivalent to dcbz instruction.\n */\nvoid\nDCZeroRange(virt_addr address,\n            uint32_t size)\n{\n   auto alignedAddr = align_down(address, 32);\n   auto alignedSize = align_up(size, 32);\n   std::memset(virt_cast<void *>(alignedAddr).get(), 0, alignedSize);\n}\n\n\n/**\n * Equivalent to dcbt instruction.\n */\nvoid\nDCTouchRange(virt_addr address,\n             uint32_t size)\n{\n   // TODO: DCTouchRange\n}\n\n\n/**\n * Equivalent to a sync instruction.\n */\nvoid\nOSCoherencyBarrier()\n{\n}\n\n/**\n * Equivalent to an eieio instruction.\n */\nvoid\nOSEnforceInorderIO()\n{\n}\n\n\n/**\n * Checks if a range of memory is \"DC Valid\"?\n *\n * Fails when the range is between 0xE8000000 or 0xEC000000\n */\nBOOL\nOSIsAddressRangeDCValid(virt_addr address,\n                        uint32_t size)\n{\n   auto beg = address;\n   auto end = beg + size - 1;\n\n   if (beg > virt_addr { 0xEC000000 } && end > virt_addr { 0xEC000000 }) {\n      return TRUE;\n   }\n\n   if (beg < virt_addr { 0xE8000000 } && end < virt_addr { 0xE8000000 }) {\n      return TRUE;\n   }\n\n   return FALSE;\n}\n\n\n/**\n * Equivalent to a eieio, sync.\n */\nvoid\nOSMemoryBarrier()\n{\n   std::atomic_thread_fence(std::memory_order_seq_cst);\n}\n\n\nvoid\nLibrary::registerCacheSymbols()\n{\n   RegisterFunctionExport(DCInvalidateRange);\n   RegisterFunctionExport(DCFlushRange);\n   RegisterFunctionExport(DCStoreRange);\n   RegisterFunctionExport(DCFlushRangeNoSync);\n   RegisterFunctionExport(DCStoreRangeNoSync);\n   RegisterFunctionExport(DCZeroRange);\n   RegisterFunctionExport(DCTouchRange);\n   RegisterFunctionExport(OSCoherencyBarrier);\n   RegisterFunctionExport(OSEnforceInorderIO);\n   RegisterFunctionExport(OSIsAddressRangeDCValid);\n   RegisterFunctionExport(OSMemoryBarrier);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_cache.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_cache Cache\n * \\ingroup coreinit\n * @{\n */\n\nvoid\nDCInvalidateRange(virt_addr address,\n                  uint32_t size);\n\nvoid\nDCFlushRange(virt_addr address,\n             uint32_t size);\n\nvoid\nDCStoreRange(virt_addr address,\n             uint32_t size);\n\nvoid\nDCFlushRangeNoSync(virt_addr address,\n                   uint32_t size);\n\nvoid\nDCStoreRangeNoSync(virt_addr address,\n                   uint32_t size);\n\nvoid\nDCZeroRange(virt_addr address,\n            uint32_t size);\n\nvoid\nDCTouchRange(virt_addr address,\n             uint32_t size);\n\nvoid\nOSCoherencyBarrier();\n\nvoid\nOSEnforceInorderIO();\n\nBOOL\nOSIsAddressRangeDCValid(virt_addr address,\n                        uint32_t size);\n\nvoid\nOSMemoryBarrier();\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_clipboard.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_clipboard.h\"\n#include \"coreinit_driver.h\"\n#include \"coreinit_memory.h\"\n#include \"cafe/cafe_stackobject.h\"\n\n#include <vector>\n\nnamespace cafe::coreinit\n{\n\nconstexpr auto MaxClipboardSize = 0x800u;\n\nstruct ClipboardSaveData\n{\n   be2_val<uint32_t> size;\n   UNKNOWN(0xC);\n   be2_array<uint8_t, MaxClipboardSize> buffer;\n};\n\nstatic virt_ptr<ClipboardSaveData>\nsClipboardSaveData = nullptr;\n\nBOOL\nOSCopyFromClipboard(virt_ptr<void> buffer,\n                    virt_ptr<uint32_t> size)\n{\n   if (!size) {\n      return FALSE;\n   }\n\n   if (buffer && *size == 0) {\n      return FALSE;\n   }\n\n   if (!OSGetForegroundBucket(nullptr, nullptr)) {\n      // Can only use clipboard when application is in foreground.\n      return FALSE;\n   }\n\n   if (OSDriver_CopyFromSaveArea(10,\n                                 sClipboardSaveData,\n                                 sizeof(ClipboardSaveData)) != OSDriver_Error::OK) {\n      return FALSE;\n   }\n\n   if (buffer && sClipboardSaveData->size) {\n      std::memcpy(buffer.get(),\n                  virt_addrof(sClipboardSaveData->buffer).get(),\n                  std::min<size_t>(sClipboardSaveData->size, *size));\n   }\n\n   *size = sClipboardSaveData->size;\n   return TRUE;\n}\n\nBOOL\nOSCopyToClipboard(virt_ptr<const void> buffer,\n                  uint32_t size)\n{\n   if (!OSGetForegroundBucket(nullptr, nullptr)) {\n      // Can only use clipboard when application is in foreground.\n      return FALSE;\n   }\n\n   if (size > MaxClipboardSize) {\n      return FALSE;\n   }\n\n   if (buffer) {\n      sClipboardSaveData->size = size;\n\n      std::memcpy(virt_addrof(sClipboardSaveData->buffer).get(),\n                  buffer.get(),\n                  size);\n   } else {\n      if (size != 0) {\n         return FALSE;\n      }\n\n      sClipboardSaveData->size = 0u;\n   }\n\n   if (OSDriver_CopyToSaveArea(10,\n                               sClipboardSaveData,\n                               sizeof(ClipboardSaveData)) != OSDriver_Error::OK) {\n      return FALSE;\n   }\n\n   return TRUE;\n}\n\nvoid\nLibrary::registerClipboardSymbols()\n{\n   RegisterFunctionExport(OSCopyFromClipboard);\n   RegisterFunctionExport(OSCopyToClipboard);\n\n   RegisterDataInternal(sClipboardSaveData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_clipboard.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_clipboard Clipboard\n * \\ingroup coreinit\n * @{\n */\n\nBOOL\nOSCopyFromClipboard(virt_ptr<void> buffer,\n                    virt_ptr<uint32_t> size);\n\nBOOL\nOSCopyToClipboard(virt_ptr<const void> buffer,\n                  uint32_t size);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_codegen.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_codegen.h\"\n\n#include \"cafe/kernel/cafe_kernel_mmu.h\"\n\nnamespace cafe::coreinit\n{\n\nvoid\nOSGetCodegenVirtAddrRange(virt_ptr<virt_addr> outAddress,\n                          virt_ptr<uint32_t> outSize)\n{\n   auto range = kernel::getCodeGenVirtualRange();\n\n   if (outAddress) {\n      *outAddress = range.first;\n   }\n\n   if (outSize) {\n      *outSize = range.second;\n   }\n}\n\nvoid\nLibrary::registerCodeGenSymbols()\n{\n   RegisterFunctionExport(OSGetCodegenVirtAddrRange);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_codegen.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nvoid\nOSGetCodegenVirtAddrRange(virt_ptr<virt_addr> outAddress,\n                          virt_ptr<uint32_t> outSize);\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_context.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_context.h\"\n#include \"coreinit_core.h\"\n#include \"coreinit_systeminfo.h\"\n\n#include \"cafe/kernel/cafe_kernel_context.h\"\n\nnamespace cafe::coreinit\n{\n\nstruct StaticContextData\n{\n   be2_array<virt_ptr<OSContext>, 3> userContext;\n};\n\nstatic virt_ptr<StaticContextData> sContextData = nullptr;\n\nvoid\nOSInitContext(virt_ptr<OSContext> context,\n              virt_addr nia,\n              virt_addr stackBase)\n{\n   context->tag = OSContext::Tag;\n   context->gpr.fill(0u);\n   context->gpr[1] = stackBase;\n   context->gpr[2] = internal::getSda2Base();\n   context->gpr[13] = internal::getSdaBase();\n   context->cr = 0u;\n   context->lr = 0u;\n   context->ctr = 0u;\n   context->xer = 0u;\n   context->nia = nia;\n   context->cia = 0xFFFFFFFFu;\n   context->gqr.fill(0u);\n   context->spinLockCount = uint16_t { 0 };\n   context->hostContext = nullptr;\n   context->mmcr0 = 0u;\n   context->mmcr1 = 0u;\n   context->state = uint16_t { 0u };\n   context->coretime.fill(0);\n   context->starttime = 0ll;\n   context->srr0 = 0u;\n   context->srr1 = 0u;\n   context->dsisr = 0u;\n   context->dar = 0u;\n   context->exceptionType = 0u;\n}\n\nvoid\nOSSetCurrentContext(virt_ptr<OSContext> context)\n{\n   auto coreId = OSGetCoreId();\n   auto prevContext = virt_ptr<OSContext> { nullptr };\n\n   if (sContextData->userContext[coreId] &&\n      (sContextData->userContext[coreId]->state & 8)) {\n      prevContext = sContextData->userContext[coreId];\n      kernel::copyContextFromCpu(prevContext);\n   }\n\n   sContextData->userContext[coreId] = context;\n   kernel::copyContextToCpu(context);\n}\n\nvoid\nOSSetCurrentFPUContext(uint32_t unk)\n{\n}\n\nvoid\nOSSetCurrentUserContext(virt_ptr<OSContext> context)\n{\n   sContextData->userContext[OSGetCoreId()] = context;\n}\n\nvoid\nLibrary::registerContextSymbols()\n{\n   RegisterFunctionExport(OSInitContext);\n   RegisterFunctionExport(OSSetCurrentContext);\n   RegisterFunctionExport(OSSetCurrentFPUContext);\n   RegisterFunctionExportName(\"__OSSetCurrentUserContext\", OSSetCurrentUserContext);\n   RegisterDataInternal(sContextData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_context.h",
    "content": "#pragma once\n#include \"cafe/kernel/cafe_kernel_context.h\"\n\nnamespace cafe::coreinit\n{\n\nusing OSContext = cafe::kernel::Context;\nCHECK_SIZE(OSContext, 0x320);\n\nvoid\nOSInitContext(virt_ptr<OSContext> context,\n              virt_addr nia,\n              virt_addr stackBase);\n\nvoid\nOSSetCurrentContext(virt_ptr<OSContext> context);\n\nvoid\nOSSetCurrentFPUContext(uint32_t unk);\n\nvoid\nOSSetCurrentUserContext(virt_ptr<OSContext> context);\n\n} // cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_core.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_core.h\"\n#include <libcpu/state.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * Returns the ID of the core currently executing this thread.\n */\nuint32_t\nOSGetCoreId()\n{\n   return cpu::this_core::id();\n}\n\n\n/**\n * Returns true if the current core is the main core.\n */\nBOOL\nOSIsMainCore()\n{\n   return OSGetCoreId() == OSGetMainCoreId();\n}\n\n\nvoid\nLibrary::registerCoreSymbols()\n{\n   RegisterFunctionExport(OSGetCoreCount);\n   RegisterFunctionExport(OSGetCoreId);\n   RegisterFunctionExport(OSGetMainCoreId);\n   RegisterFunctionExport(OSIsMainCore);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_core.h",
    "content": "#pragma once\n#include <cstdint>\n#include <common/cbool.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_core Core Identification\n * \\ingroup coreinit\n * @{\n */\n\nstatic constexpr auto MainCoreId = 1u;\nstatic constexpr auto CoreCount = 3u;\n\nconstexpr uint32_t\nOSGetCoreCount()\n{\n   return CoreCount;\n}\n\nconstexpr uint32_t\nOSGetMainCoreId()\n{\n   return MainCoreId;\n}\n\nuint32_t\nOSGetCoreId();\n\nBOOL\nOSIsMainCore();\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_coroutine.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_coroutine.h\"\n#include \"coreinit_thread.h\"\n#include \"cafe/kernel/cafe_kernel_context.h\"\n\nnamespace cafe::coreinit\n{\n\nvoid\nOSInitCoroutine(virt_ptr<OSCoroutine> context,\n                uint32_t entry,\n                uint32_t stack)\n{\n   context->lr = entry;\n   context->gpr1 = stack;\n}\n\nuint32_t\nOSLoadCoroutine(virt_ptr<OSCoroutine> coroutine,\n                uint32_t returnValue)\n{\n   auto thread = OSGetCurrentThread();\n   auto context = virt_addrof(thread->context);\n\n   // Switch to new coroutine state\n   context->lr = coroutine->lr;\n   context->cr = coroutine->cr;\n   context->gqr[1] = coroutine->gqr1;\n   context->gpr[1] = coroutine->gpr1;\n   context->gpr[2] = coroutine->gpr2;\n   context->gpr[3] = returnValue;\n   context->gpr[13] = coroutine->gpr13_31[0];\n   for (auto i = 14; i < 32; i++) {\n      context->gpr[i] = coroutine->gpr13_31[i - 13];\n      context->fpr[i] = coroutine->fpr14_31[i - 14];\n      context->psf[i] = static_cast<double>(coroutine->ps14_31[i - 14][1]);\n   }\n\n   // Copy context to CPU registers\n   cafe::kernel::copyContextToCpu(context);\n   return returnValue;\n}\n\nuint32_t\nOSSaveCoroutine(virt_ptr<OSCoroutine> coroutine)\n{\n   // Copy CPU registers to context\n   auto thread = OSGetCurrentThread();\n   auto context = virt_addrof(thread->context);\n   cafe::kernel::copyContextFromCpu(context);\n\n   // Update the coroutine with context registers\n   coroutine->lr = context->lr;\n   coroutine->cr = context->cr;\n   coroutine->gqr1 = context->gqr[1];\n   coroutine->gpr1 = context->gpr[1];\n   coroutine->gpr2 = context->gpr[2];\n   coroutine->gpr13_31[0] = context->gpr[13];\n   for (auto i = 14; i < 32; i++) {\n      coroutine->gpr13_31[i - 13] = context->gpr[i];\n      coroutine->fpr14_31[i - 14] = context->fpr[i];\n      coroutine->ps14_31[i - 14][0] = static_cast<float>(context->fpr[i]);\n      coroutine->ps14_31[i - 14][1] = static_cast<float>(context->psf[i]);\n   }\n\n   return 0;\n}\n\nvoid\nOSSwitchCoroutine(virt_ptr<OSCoroutine> from,\n                  virt_ptr<OSCoroutine> to)\n{\n   if (OSSaveCoroutine(from) == 0) {\n      OSLoadCoroutine(to, 1);\n   }\n}\n\nvoid\nLibrary::registerCoroutineSymbols()\n{\n   RegisterFunctionExport(OSInitCoroutine);\n   RegisterFunctionExport(OSLoadCoroutine);\n   RegisterFunctionExport(OSSaveCoroutine);\n   RegisterFunctionExport(OSSwitchCoroutine);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_coroutine.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_coroutine Coroutines\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSCoroutine\n{\n   be2_val<uint32_t> lr;\n   be2_val<uint32_t> cr;\n   be2_val<uint32_t> gqr1;\n   be2_val<uint32_t> gpr1;\n   be2_val<uint32_t> gpr2;\n   be2_val<uint32_t> gpr13_31[19];\n   be2_val<double> fpr14_31[18];\n   be2_val<float> ps14_31[18][2];\n};\nCHECK_OFFSET(OSCoroutine, 0x000, lr);\nCHECK_OFFSET(OSCoroutine, 0x004, cr);\nCHECK_OFFSET(OSCoroutine, 0x008, gqr1);\nCHECK_OFFSET(OSCoroutine, 0x00C, gpr1);\nCHECK_OFFSET(OSCoroutine, 0x010, gpr2);\nCHECK_OFFSET(OSCoroutine, 0x014, gpr13_31);\nCHECK_OFFSET(OSCoroutine, 0x060, fpr14_31);\nCHECK_OFFSET(OSCoroutine, 0x0F0, ps14_31);\nCHECK_SIZE(OSCoroutine, 0x180);\n\n#pragma pack(pop)\n\nvoid\nOSInitCoroutine(virt_ptr<OSCoroutine> context,\n                uint32_t entry,\n                uint32_t stack);\n\nuint32_t\nOSLoadCoroutine(virt_ptr<OSCoroutine> coroutine,\n                uint32_t returnValue);\n\nuint32_t\nOSSaveCoroutine(virt_ptr<OSCoroutine> coroutine);\n\nvoid\nOSSwitchCoroutine(virt_ptr<OSCoroutine> from,\n                  virt_ptr<OSCoroutine> to);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_cosreport.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_cosreport.h\"\n#include \"coreinit_snprintf.h\"\n#include \"coreinit_systeminfo.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/cafe_ppc_interface_varargs.h\"\n\n#include <common/strutils.h>\n#include <cstdarg>\n#include <cstdio>\n#include <common/log.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nstatic void\nhandleReport(COSReportModule module,\n             COSReportLevel level,\n             virt_ptr<const char> msg)\n{\n   // TODO: This actually goes down to a kernel syscall\n   switch (level) {\n   case COSReportLevel::Error:\n      gLog->error(\"COSVReport {}: {}\", module, msg.get());\n      break;\n   case COSReportLevel::Warn:\n      gLog->warn(\"COSVReport {}: {}\", module, msg.get());\n      break;\n   case COSReportLevel::Info:\n      gLog->info(\"COSVReport {}: {}\", module, msg.get());\n      break;\n   case COSReportLevel::Verbose:\n      gLog->debug(\"COSVReport {}: {}\", module, msg.get());\n      break;\n   }\n}\n\nvoid\nCOSVReport(COSReportModule module,\n           COSReportLevel level,\n           virt_ptr<const char> fmt,\n           virt_ptr<va_list> vaList)\n{\n   auto buffer = StackArray<char, 128> { };\n   internal::formatStringV(buffer, buffer.size(), fmt, vaList);\n   handleReport(module, level, buffer);\n}\n\nvoid\nCOSError(COSReportModule module,\n         virt_ptr<const char> fmt,\n         var_args args)\n{\n   auto vaList = make_va_list(args);\n   COSVReport(module, COSReportLevel::Error, fmt, vaList);\n   free_va_list(vaList);\n}\n\nvoid\nCOSInfo(COSReportModule module,\n        virt_ptr<const char> fmt,\n        var_args args)\n{\n   if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Info) {\n      auto vaList = make_va_list(args);\n      COSVReport(module, COSReportLevel::Info, fmt, vaList);\n      free_va_list(vaList);\n   }\n}\n\nvoid\nCOSVerbose(COSReportModule module,\n           virt_ptr<const char> fmt,\n           var_args args)\n{\n   if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Verbose) {\n      auto vaList = make_va_list(args);\n      COSVReport(module, COSReportLevel::Verbose, fmt, vaList);\n      free_va_list(vaList);\n   }\n}\n\nvoid\nCOSWarn(COSReportModule module,\n        virt_ptr<const char> fmt,\n        var_args args)\n{\n   if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Warn) {\n      auto vaList = make_va_list(args);\n      COSVReport(module, COSReportLevel::Warn, fmt, vaList);\n      free_va_list(vaList);\n   }\n}\n\nnamespace internal\n{\n\nvoid\nCOSVReport(COSReportModule module,\n           COSReportLevel level,\n           const std::string_view &msg)\n{\n   auto buffer = StackArray<char, 128> { };\n   string_copy(buffer.get(), msg.data(), buffer.size());\n   buffer[127] = char { 0 };\n   handleReport(module, level, buffer);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerCosReportSymbols()\n{\n   RegisterFunctionExport(COSError);\n   RegisterFunctionExport(COSInfo);\n   RegisterFunctionExport(COSVerbose);\n   RegisterFunctionExport(COSVReport);\n   RegisterFunctionExport(COSWarn);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_cosreport.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_systeminfo.h\"\n\n#include \"cafe/cafe_ppc_interface_varargs.h\"\n\n#include <cstdarg>\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::coreinit\n{\n\nvoid\nCOSVReport(COSReportModule module,\n           COSReportLevel level,\n           virt_ptr<const char> fmt,\n           virt_ptr<va_list> vaList);\n\nvoid\nCOSError(COSReportModule module,\n         virt_ptr<const char> fmt,\n         var_args va);\n\nvoid\nCOSInfo(COSReportModule module,\n        virt_ptr<const char> fmt,\n        var_args va);\n\nvoid\nCOSVerbose(COSReportModule module,\n           virt_ptr<const char> fmt,\n           var_args va);\n\nvoid\nCOSWarn(COSReportModule module,\n        virt_ptr<const char> fmt,\n        var_args va);\n\nnamespace internal\n{\n\nvoid\nCOSVReport(COSReportModule module,\n           COSReportLevel level,\n           const std::string_view &msg);\n\ninline void\nCOSError(COSReportModule module,\n         const std::string_view &msg)\n{\n   COSVReport(module, COSReportLevel::Error, msg);\n}\n\ninline void\nCOSWarn(COSReportModule module,\n        const std::string_view &msg)\n{\n   if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Warn) {\n      COSVReport(module, COSReportLevel::Warn, msg);\n   }\n}\n\ninline void\nCOSInfo(COSReportModule module,\n        const std::string_view &msg)\n{\n   if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Info) {\n      COSVReport(module, COSReportLevel::Info, msg);\n   }\n}\n\ninline void\nCOSVerbose(COSReportModule module,\n           const std::string_view &msg)\n{\n   if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Verbose) {\n      COSVReport(module, COSReportLevel::Verbose, msg);\n   }\n}\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_device.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_device.h\"\n#include \"coreinit_memory.h\"\n\nnamespace cafe::coreinit\n{\n\nconstexpr auto NumDevices = 16u;\n\nstruct StaticDeviceData\n{\n   be2_array<virt_addr, NumDevices> deviceTable;\n};\n\nstatic virt_ptr<StaticDeviceData>\nsDeviceData = nullptr;\n\nuint16_t\nOSReadRegister16(OSDeviceID device,\n                 uint32_t id)\n{\n   auto deviceBaseAddr = sDeviceData->deviceTable[device];\n   if (!deviceBaseAddr) {\n      return 0;\n   }\n\n   return *virt_cast<uint16_t *>(deviceBaseAddr + id * 4);\n}\n\nuint32_t\nOSReadRegister32Ex(OSDeviceID device,\n                   uint32_t id)\n{\n   auto deviceBaseAddr = sDeviceData->deviceTable[device];\n   if (!deviceBaseAddr) {\n      return 0;\n   }\n\n   return *virt_cast<uint32_t *>(deviceBaseAddr + id * 4);\n}\n\nvoid\nOSWriteRegister16(OSDeviceID device,\n                  uint32_t id,\n                  uint16_t value)\n{\n   auto deviceBaseAddr = sDeviceData->deviceTable[device];\n   if (deviceBaseAddr) {\n      *virt_cast<uint16_t *>(deviceBaseAddr + id * 4) = value;\n   }\n}\n\nvoid\nOSWriteRegister32Ex(OSDeviceID device,\n                    uint32_t id,\n                    uint32_t value)\n{\n   auto deviceBaseAddr = sDeviceData->deviceTable[device];\n   if (deviceBaseAddr) {\n      *virt_cast<uint32_t *>(deviceBaseAddr + id * 4) = value;\n   }\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseDeviceTable()\n{\n   sDeviceData->deviceTable.fill(virt_addr { 0 });\n\n   // Registers are disabled for now.\n#if 0\n   sDeviceData->deviceTable[OSDeviceID::VI] = OSPhysicalToEffectiveUncached(phys_addr { 0x0C1E0000 });\n   sDeviceData->deviceTable[OSDeviceID::DSP] = OSPhysicalToEffectiveUncached(phys_addr { 0x0C280000 });\n   sDeviceData->deviceTable[OSDeviceID::GFXSP] = OSPhysicalToEffectiveUncached(phys_addr { 0x0C200000 });\n\n   sDeviceData->deviceTable[OSDeviceID::LATTE_REGS] = OSPhysicalToEffectiveUncached(phys_addr { 0x0D000000 });\n   sDeviceData->deviceTable[OSDeviceID::LEGACY_SI] = OSPhysicalToEffectiveUncached(phys_addr { 0x0D006400 });\n   sDeviceData->deviceTable[OSDeviceID::LEGACY_AI_I2S3] = OSPhysicalToEffectiveUncached(phys_addr { 0x0D006C00 });\n   sDeviceData->deviceTable[OSDeviceID::LEGACY_AI_I2S5] = OSPhysicalToEffectiveUncached(phys_addr { 0x0D006E00 });\n   sDeviceData->deviceTable[OSDeviceID::LEGACY_EXI] = OSPhysicalToEffectiveUncached(phys_addr { 0x0D006800 });\n#endif\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerDeviceSymbols()\n{\n   RegisterFunctionExport(OSReadRegister16);\n   RegisterFunctionExport(OSWriteRegister16);\n   RegisterFunctionExportName(\"__OSReadRegister32Ex\", OSReadRegister32Ex);\n   RegisterFunctionExportName(\"__OSWriteRegister32Ex\", OSWriteRegister32Ex);\n\n   RegisterDataInternal(sDeviceData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_device.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n\n#include <cstdint>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_device Device\n * \\ingroup coreinit\n * @{\n */\n\nuint16_t\nOSReadRegister16(OSDeviceID device,\n                 uint32_t id);\n\nvoid\nOSWriteRegister16(OSDeviceID device,\n                  uint32_t id,\n                  uint16_t value);\n\nuint32_t\nOSReadRegister32Ex(OSDeviceID device,\n                   uint32_t id);\n\nvoid\nOSWriteRegister32Ex(OSDeviceID device,\n                    uint32_t id,\n                    uint32_t value);\n\nnamespace internal\n{\n\nvoid\ninitialiseDeviceTable();\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_driver.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_cosreport.h\"\n#include \"coreinit_driver.h\"\n#include \"coreinit_dynload.h\"\n#include \"coreinit_log.h\"\n#include \"coreinit_memory.h\"\n#include \"coreinit_osreport.h\"\n#include \"coreinit_spinlock.h\"\n#include \"coreinit_systemheap.h\"\n#include \"coreinit_systeminfo.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/kernel/cafe_kernel_userdrivers.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\n#include <common/decaf_assert.h>\n#include <common/strutils.h>\n#include <libcpu/state.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nstruct StaticDriverData\n{\n   be2_val<uint32_t> minUnk0x10;\n   be2_val<uint32_t> maxUnk0x10;\n   be2_struct<OSSpinLock> lock;\n   be2_val<BOOL> didInit;\n   be2_val<uint32_t> numRegistered;\n   be2_virt_ptr<OSDriver> registeredDrivers;\n   be2_virt_ptr<OSDriver> actionCallbackDriver;\n};\n\nstatic virt_ptr<StaticDriverData> sDriverData = nullptr;\n\n\n/**\n * Register a driver.\n *\n * \\param moduleHandle\n * The module handle to associate this driver with.\n *\n * \\param unk1\n * Unknown, maybe something like a priority?\n *\n * \\param driverInterface\n * Implementation of the driver interface functions.\n *\n * \\param userDriverId\n * A user provided unique id to identify this driver.\n *\n * \\param[out] outCurrentUpid\n * The unique process id of the current process.\n *\n * \\param[out] outOwnerUpid\n * The unique process id of the process which first registered the driver with\n * the kernel.\n *\n * \\param[out] outDidInit\n * Set to 1 after internal:driverInitialise has been called, 0 before.\n *\n * \\return\n * Returns OSDriver_Error::OK on success, an error code otherwise.\n */\nOSDriver_Error\nOSDriver_Register(OSDynLoad_ModuleHandle moduleHandle,\n                  uint32_t unk1, // Something like a priority\n                  virt_ptr<OSDriverInterface> driverInterface,\n                  OSDriver_UserDriverId userDriverId,\n                  virt_ptr<kernel::UniqueProcessId> outRegisteredUpid,\n                  virt_ptr<kernel::UniqueProcessId> outOwnerUpid,\n                  virt_ptr<BOOL> outDidInit)\n{\n   auto callerModule = StackObject<OSDynLoad_ModuleHandle> { };\n   auto error = OSDriver_Error::OK;\n   auto dynloadError = OSDynLoad_Error::OK;\n\n   if (outRegisteredUpid) {\n      *outRegisteredUpid = kernel::UniqueProcessId::Kernel;\n   }\n\n   if (outOwnerUpid) {\n      *outOwnerUpid = kernel::UniqueProcessId::Kernel;\n   }\n\n   if (outDidInit) {\n      *outDidInit = sDriverData->didInit;\n   }\n\n   if (!driverInterface || !driverInterface->getName) {\n      return OSDriver_Error::InvalidArgument;\n   }\n\n   // Get the drivers name through the driverInterface\n   auto name = cafe::invoke(cpu::this_core::state(),\n                            driverInterface->getName,\n                            userDriverId);\n   if (!name) {\n      return OSDriver_Error::InvalidArgument;\n   }\n\n   auto nameLen = static_cast<uint32_t>(strlen(name.get()));\n   if (nameLen == 0 || nameLen >= 64) {\n      return OSDriver_Error::InvalidArgument;\n   }\n\n   // Note on real coreinit.rpl it will try acquire callerModule from LR\n   // regardless of whether moduleHandle is set, however that makes it\n   // difficult for our HLE functions to call this function.\n   if (!moduleHandle) {\n      // Get the module handle from the caller of this function\n      dynloadError =\n         OSDynLoad_AcquireContainingModule(\n            virt_cast<void *>(virt_addr { cpu::this_core::state()->lr }),\n            OSDynLoad_SectionType::CodeOnly,\n            callerModule);\n      if (dynloadError != OSDynLoad_Error::OK) {\n         internal::OSPanic(\"OSDrivers.c\", 288,\n                           \"***OSDriver_Register could not find caller module.\\n\");\n      }\n\n      moduleHandle = *callerModule;\n   }\n\n   // Allocate memory for the driver structure\n   auto driver = virt_cast<OSDriver *>(OSAllocFromSystem(sizeof(OSDriver), 4));\n   if (internal::isAppDebugLevelNotice()) {\n      internal::COSInfo(\n         COSReportModule::Unknown2,\n         fmt::format(\n            \"RPL_SYSHEAP:DRIVER_REG,ALLOC,=\\\"{}\\\",-{}\",\n            driver, sizeof(OSDriver)));\n   }\n\n   if (!driver) {\n      if (internal::isAppDebugLevelNotice()) {\n         internal::dumpSystemHeap();\n      }\n\n      OSDynLoad_Release(*callerModule);\n      return OSDriver_Error::OutOfSysMemory;\n   }\n\n   memset(driver, 0, sizeof(OSDriver));\n\n   // Get module handle for driverInterface.getName\n   dynloadError =\n      OSDynLoad_AcquireContainingModule(\n         virt_cast<void *>(driverInterface->getName.getAddress()),\n         OSDynLoad_SectionType::CodeOnly,\n         virt_addrof(driver->interfaceModuleHandles[0]));\n   if (dynloadError != OSDynLoad_Error::OK) {\n      internal::COSWarn(\n         COSReportModule::Unknown1,\n         fmt::format(\n            \"*** OSDriver_Register - failed to acquire containing module for driver \\\"{}\\\" Name() @ {}\",\n            name, driverInterface->getName));\n      error = static_cast<OSDriver_Error>(dynloadError);\n      goto error;\n   }\n\n   // Get module handle for driverInterface.onInit\n   if (driverInterface->onInit) {\n      dynloadError =\n         OSDynLoad_AcquireContainingModule(\n            virt_cast<void *>(driverInterface->onInit.getAddress()),\n            OSDynLoad_SectionType::CodeOnly,\n            virt_addrof(driver->interfaceModuleHandles[1]));\n      if (dynloadError != OSDynLoad_Error::OK) {\n         internal::COSWarn(\n            COSReportModule::Unknown1,\n            fmt::format(\n               \"*** OSDriver_Register - failed to acquire containing module for driver \\\"{}\\\" AutoInit() @ {}\",\n               name, driverInterface->onInit));\n         error = static_cast<OSDriver_Error>(dynloadError);\n         goto error;\n      }\n   }\n\n   // Get module handle for driverInterface.onAcquiredForeground\n   if (driverInterface->onAcquiredForeground) {\n      dynloadError =\n         OSDynLoad_AcquireContainingModule(\n            virt_cast<void *>(driverInterface->onAcquiredForeground.getAddress()),\n            OSDynLoad_SectionType::CodeOnly,\n            virt_addrof(driver->interfaceModuleHandles[2]));\n      if (dynloadError != OSDynLoad_Error::OK) {\n         internal::COSWarn(\n            COSReportModule::Unknown1,\n            fmt::format(\n               \"*** OSDriver_Register - failed to acquire containing module for driver \\\"{}\\\" OnAcquiredForeground() @ {}\",\n               name, driverInterface->onAcquiredForeground));\n         error = static_cast<OSDriver_Error>(dynloadError);\n         goto error;\n      }\n   }\n\n   // Get module handle for driverInterface.onReleasedForeground\n   if (driverInterface->onReleasedForeground) {\n      dynloadError =\n         OSDynLoad_AcquireContainingModule(\n            virt_cast<void *>(driverInterface->onReleasedForeground.getAddress()),\n            OSDynLoad_SectionType::CodeOnly,\n            virt_addrof(driver->interfaceModuleHandles[3]));\n      if (dynloadError != OSDynLoad_Error::OK) {\n         internal::COSWarn(\n            COSReportModule::Unknown1,\n            fmt::format(\n               \"*** OSDriver_Register - failed to acquire containing module for driver \\\"{}\\\" OnReleasedForeground() @ {}\",\n               name, driverInterface->onReleasedForeground));\n         error = static_cast<OSDriver_Error>(dynloadError);\n         goto error;\n      }\n   }\n\n   // Get module handle for driverInterface.onDone\n   if (driverInterface->onDone) {\n      dynloadError =\n         OSDynLoad_AcquireContainingModule(\n            virt_cast<void *>(driverInterface->onDone.getAddress()),\n            OSDynLoad_SectionType::CodeOnly,\n            virt_addrof(driver->interfaceModuleHandles[4]));\n      if (dynloadError != OSDynLoad_Error::OK) {\n         internal::COSWarn(\n            COSReportModule::Unknown1,\n            fmt::format(\n               \"*** OSDriver_Register - failed to acquire containing module for driver \\\"{}\\\" AutoDone() @ {}\",\n               name, driverInterface->onDone));\n         error = static_cast<OSDriver_Error>(dynloadError);\n         goto error;\n      }\n   }\n\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(sDriverData->lock));\n\n   // Check if a driver has already been registered with the same name\n   for (auto other = sDriverData->registeredDrivers; other; other = other->next) {\n      auto otherName = cafe::invoke(cpu::this_core::state(),\n                                    other->interfaceFunctions.getName,\n                                    other->userDriverId);\n      if (iequals(otherName.get(), name.get())) {\n         error = OSDriver_Error::AlreadyRegistered;\n         OSUninterruptibleSpinLock_Release(virt_addrof(sDriverData->lock));\n         goto error;\n      }\n   }\n\n   dynloadError = static_cast<OSDynLoad_Error>(\n      kernel::registerUserDriver(name,\n                               nameLen,\n                               virt_addrof(driver->registeredUpid),\n                               virt_addrof(driver->ownerUpid)));\n   OSLogPrintf(0, 1, 0, \"%s: Reg=%s, ms=%d\", \"OSDriver_Register\", name, 0);\n   if (dynloadError == OSDynLoad_Error::OK) {\n      // Initialise driver data\n      if (sDriverData->didInit) {\n         driver->unk0x08 = 1u;\n         driver->inForeground = OSGetForegroundBucket(nullptr, nullptr);\n      }\n\n      driver->coreID = cpu::this_core::id();\n      driver->moduleHandle = moduleHandle;\n      driver->interfaceFunctions = *driverInterface;\n      driver->userDriverId = userDriverId;\n      driver->unk0x10 = unk1;\n\n      if (driver->unk0x10 < sDriverData->minUnk0x10) {\n         sDriverData->minUnk0x10 = driver->unk0x10;\n      }\n\n      if (driver->unk0x10 > sDriverData->maxUnk0x10) {\n         sDriverData->maxUnk0x10 = driver->unk0x10;\n      }\n\n      // Insert to front of list\n      driver->next = sDriverData->registeredDrivers;\n      sDriverData->registeredDrivers = driver;\n\n      if (outRegisteredUpid) {\n         *outRegisteredUpid = driver->registeredUpid;\n      }\n\n      if (outOwnerUpid) {\n         *outOwnerUpid = driver->ownerUpid;\n      }\n\n      sDriverData->numRegistered++;\n   }\n   OSUninterruptibleSpinLock_Release(virt_addrof(sDriverData->lock));\n\n   // Deduplicate dynload allocated handles\n   for (auto i = 0u; i < driver->interfaceModuleHandles.size(); ++i) {\n      if (driver->interfaceModuleHandles[i] == moduleHandle) {\n         OSDynLoad_Release(driver->interfaceModuleHandles[i]);\n         driver->interfaceModuleHandles[i] = 0u;\n      }\n   }\n\n   OSDynLoad_Release(*callerModule);\n   return OSDriver_Error::OK;\n\nerror:\n   for (auto i = 0u; i < driver->interfaceModuleHandles.size(); ++i) {\n      if (driver->interfaceModuleHandles[i]) {\n         OSDynLoad_Release(driver->interfaceModuleHandles[i]);\n      }\n   }\n\n   OSDynLoad_Release(*callerModule);\n\n   if (internal::isAppDebugLevelNotice()) {\n      internal::COSInfo(\n         COSReportModule::Unknown2,\n         fmt::format(\n            \"RPL_SYSHEAP:DRIVER_REG, FREE, =\\\"{}\\\",{}\",\n            driver, sizeof(OSDriver)));\n   }\n   OSFreeToSystem(driver);\n   return error;\n}\n\n\n/**\n * Deregister a driver.\n *\n * \\param moduleHandle\n * Module handle the driver was registered under.\n *\n * \\param userDriverId\n * The user's driver id for the driver to deregister.\n *\n * \\return\n * Returns OSDriver_Error::OK on success, an error code otherwise.\n */\nOSDriver_Error\nOSDriver_Deregister(OSDynLoad_ModuleHandle moduleHandle,\n                    OSDriver_UserDriverId userDriverId)\n{\n   if (!sDriverData->numRegistered) {\n      return OSDriver_Error::DriverNotFound;\n   }\n\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(sDriverData->lock));\n\n   // Find the driver in the registered driver linked list\n   auto prev = virt_ptr<OSDriver> { nullptr };\n   auto driver = sDriverData->registeredDrivers;\n   while (driver) {\n      if (driver->moduleHandle == moduleHandle &&\n          driver->userDriverId == userDriverId) {\n         break;\n      }\n\n      prev = driver;\n      driver = driver->next;\n   }\n\n   if (!driver) {\n      OSUninterruptibleSpinLock_Release(virt_addrof(sDriverData->lock));\n      return OSDriver_Error::DriverNotFound;\n   }\n\n   // Check if we are trying to deregister from \"inside action callback\"\n   if (sDriverData->actionCallbackDriver == driver) {\n      internal::COSWarn(COSReportModule::Unknown1,\n                        \"***OSDriver_Deregister() of self from inside action callback!\\n\");\n      sDriverData->actionCallbackDriver = nullptr;\n   }\n\n   // Remove the driver from the linked list\n   if (prev) {\n      prev->next = driver->next;\n   } else {\n      sDriverData->registeredDrivers = driver->next;\n   }\n\n   // Grab the drivers name for kernel deregister\n   auto name = cafe::invoke(cpu::this_core::state(),\n                            driver->interfaceFunctions.getName,\n                            userDriverId);\n\n   OSUninterruptibleSpinLock_Release(virt_addrof(sDriverData->lock));\n\n   auto startTicks = OSGetTick();\n\n   // Deregister driver with the kernel\n   if (driver->registeredUpid == driver->ownerUpid) {\n      kernel::deregisterUserDriver(name,\n                                   static_cast<uint32_t>(strlen(name.get())));\n   }\n\n   // Free the dynload handles\n   for (auto handle : driver->interfaceModuleHandles) {\n      if (handle) {\n         OSDynLoad_Release(handle);\n      }\n   }\n\n   if (internal::isAppDebugLevelNotice()) {\n      internal::COSInfo(\n         COSReportModule::Unknown2,\n         fmt::format(\n            \"RPL_SYSHEAP:DRIVER_REG, FREE, =\\\"{}\\\",{}\\n\",\n            driver, sizeof(OSDriver)));\n   }\n\n   // Free the driver\n   OSFreeToSystem(driver);\n\n   OSLogPrintf(0, 1, 0, \"%s: Reg=%s, ms=%d\", \"OSDriver_Deregister\",\n               name,\n               internal::ticksToMs(OSGetTick() - startTicks));\n   return OSDriver_Error::OK;\n}\n\nOSDriver_Error\nOSDriver_CopyFromSaveArea(OSDriver_UserDriverId driverId,\n                          virt_ptr<void> data,\n                          uint32_t size)\n{\n   decaf_warn_stub();\n   std::memset(data.get(), 0, size);\n   return OSDriver_Error::OK;\n}\n\nOSDriver_Error\nOSDriver_CopyToSaveArea(OSDriver_UserDriverId driverId,\n                        virt_ptr<const void> data,\n                        uint32_t size)\n{\n   decaf_warn_stub();\n   return OSDriver_Error::OK;\n}\n\nnamespace internal\n{\n\nvoid\ndriverOnInit()\n{\n   sDriverData->didInit = TRUE;\n\n   if (!sDriverData->numRegistered) {\n      return;\n   }\n\n   // TODO: Driver init should be called from driver action thread\n   for (auto driver = sDriverData->registeredDrivers; driver; driver = driver->next) {\n      if (driver->interfaceFunctions.onInit) {\n         cafe::invoke(cpu::this_core::state(),\n                      driver->interfaceFunctions.onInit,\n                      driver->userDriverId);\n      }\n   }\n}\n\nvoid\ndriverOnDone()\n{\n   // TODO: OSDriver OnDone\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerDriverSymbols()\n{\n   RegisterFunctionExport(OSDriver_Register);\n   RegisterFunctionExport(OSDriver_Deregister);\n   RegisterFunctionExport(OSDriver_CopyFromSaveArea);\n   RegisterFunctionExport(OSDriver_CopyToSaveArea);\n\n   RegisterDataInternal(sDriverData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_driver.h",
    "content": "#pragma once\n#include \"coreinit_dynload.h\"\n#include \"coreinit_enum.h\"\n\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n#pragma pack(push, 1)\n\nusing OSDriver_UserDriverId = uint32_t;\n\nusing OSDriver_GetNameFn = virt_func_ptr<virt_ptr<const char>(OSDriver_UserDriverId)>;\nusing OSDriver_OnInitFn = virt_func_ptr<void(OSDriver_UserDriverId)>;\nusing OSDriver_OnAcquiredForegroundFn = virt_func_ptr<void(OSDriver_UserDriverId)>;\nusing OSDriver_OnReleasedForegroundFn = virt_func_ptr<void(OSDriver_UserDriverId)>;\nusing OSDriver_OnDoneFn = virt_func_ptr<void(OSDriver_UserDriverId)>;\n\nstruct OSDriverInterface\n{\n   //! Return the driver name\n   be2_val<OSDriver_GetNameFn> getName;\n\n   //! Called to initialise the driver.\n   be2_val<OSDriver_OnInitFn> onInit;\n\n   //! Called when application is brought to foreground.\n   be2_val<OSDriver_OnAcquiredForegroundFn> onAcquiredForeground;\n\n   //! Called when application is sent to background.\n   be2_val<OSDriver_OnReleasedForegroundFn> onReleasedForeground;\n\n   //! Called when driver is done.\n   be2_val<OSDriver_OnDoneFn> onDone;\n};\n\nstruct OSDriver\n{\n   //! Module handle of current RPL.\n   be2_val<OSDynLoad_ModuleHandle> moduleHandle;\n\n   //! First argument passed to all driver interface functions.\n   be2_val<OSDriver_UserDriverId> userDriverId;\n\n   //! Unknown, set to 1 in OSDriver_Register.\n   be2_val<uint32_t> unk0x08;\n\n   //! Whether OSDriver_Register was called when process is in foreground.\n   be2_val<BOOL> inForeground;\n\n   //! Unknown, value set from r4 of OSDriver_Register.\n   be2_val<uint32_t> unk0x10;\n\n   //!Core on which OSDriver_Register was called.\n   be2_val<uint32_t> coreID;\n\n   //! Interface function pointers.\n   be2_struct<OSDriverInterface> interfaceFunctions;\n\n   //! Module handles for each interface function.\n   be2_array<OSDynLoad_ModuleHandle, 5> interfaceModuleHandles;\n\n   //! The current upid\n   be2_val<kernel::UniqueProcessId> registeredUpid;\n\n   //! The current owner of this driver reigstered with the kernel\n   be2_val<kernel::UniqueProcessId> ownerUpid;\n\n   //! Pointer to next OSDriver in linked list.\n   be2_virt_ptr<OSDriver> next;\n};\nCHECK_OFFSET(OSDriver, 0x00, moduleHandle);\nCHECK_OFFSET(OSDriver, 0x04, userDriverId);\nCHECK_OFFSET(OSDriver, 0x08, unk0x08);\nCHECK_OFFSET(OSDriver, 0x0C, inForeground);\nCHECK_OFFSET(OSDriver, 0x10, unk0x10);\nCHECK_OFFSET(OSDriver, 0x14, coreID);\nCHECK_OFFSET(OSDriver, 0x18, interfaceFunctions);\nCHECK_OFFSET(OSDriver, 0x2C, interfaceModuleHandles);\nCHECK_OFFSET(OSDriver, 0x40, registeredUpid);\nCHECK_OFFSET(OSDriver, 0x44, ownerUpid);\nCHECK_OFFSET(OSDriver, 0x48, next);\nCHECK_SIZE(OSDriver, 0x4C);\n\n#pragma pack(pop)\n\nOSDriver_Error\nOSDriver_Register(OSDynLoad_ModuleHandle moduleHandle,\n                  uint32_t unk1,\n                  virt_ptr<OSDriverInterface> driverInterface,\n                  OSDriver_UserDriverId userDriverId,\n                  virt_ptr<kernel::UniqueProcessId> outRegisteredUpid,\n                  virt_ptr<kernel::UniqueProcessId> outOwnerUpid,\n                  virt_ptr<BOOL> outDidOSDriverInit);\n\nOSDriver_Error\nOSDriver_Deregister(OSDynLoad_ModuleHandle moduleHandle,\n                    OSDriver_UserDriverId userDriverId);\n\nOSDriver_Error\nOSDriver_CopyFromSaveArea(OSDriver_UserDriverId driverId,\n                          virt_ptr<void> data,\n                          uint32_t size);\n\nOSDriver_Error\nOSDriver_CopyToSaveArea(OSDriver_UserDriverId driverId,\n                        virt_ptr<const void> data,\n                        uint32_t size);\n\nnamespace internal\n{\n\nvoid\ndriverOnInit();\n\nvoid\ndriverOnDone();\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_dynload.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_cosreport.h\"\n#include \"coreinit_dynload.h\"\n#include \"coreinit_ghs.h\"\n#include \"coreinit_handle.h\"\n#include \"coreinit_mutex.h\"\n#include \"coreinit_memheap.h\"\n#include \"coreinit_osreport.h\"\n#include \"coreinit_systemheap.h\"\n#include \"coreinit_systeminfo.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/cafe_hle.h\"\n#include \"cafe/loader/cafe_loader_rpl.h\"\n#include \"cafe/kernel/cafe_kernel_info.h\"\n#include \"cafe/kernel/cafe_kernel_loader.h\"\n#include \"cafe/kernel/cafe_kernel_mmu.h\"\n#include \"debug_api/debug_api_controller.h\"\n\n#include <common/strutils.h>\n#include <fmt/core.h>\n#include <gsl/gsl-lite.hpp>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nusing OSDynLoad_RplEntryFn = virt_func_ptr<\n   int32_t (OSDynLoad_ModuleHandle moduleHandle,\n            OSDynLoad_EntryReason reason)>;\n\nusing OSDynLoad_InitDefaultHeapFn = virt_func_ptr<\n   void(virt_ptr<MEMHeapHandle> mem1,\n        virt_ptr<MEMHeapHandle> foreground,\n        virt_ptr<MEMHeapHandle> mem2)>;\n\nconstexpr auto MaxTlsModuleIndex = uint32_t { 0x7fff };\n\nstruct RPL_DATA\n{\n   be2_val<OSHandle> handle;\n   be2_virt_ptr<void> loaderHandle;\n   be2_virt_ptr<char> moduleName;\n   be2_val<uint32_t> moduleNameLen;\n   be2_val<uint32_t> sectionInfoCount;\n   be2_virt_ptr<loader::LOADER_SectionInfo> sectionInfo;\n   be2_virt_ptr<virt_ptr<RPL_DATA>> importModules;\n   be2_val<uint32_t> importModuleCount;\n   be2_val<uint32_t> userFileInfoSize;\n   be2_virt_ptr<loader::LOADER_UserFileInfo> userFileInfo;\n   be2_virt_ptr<OSDynLoad_NotifyData> notifyData;\n   be2_val<virt_addr> entryPoint;\n   be2_val<uint32_t> dataSectionSize;\n   be2_virt_ptr<void> dataSection;\n   be2_val<uint32_t> loadSectionSize;\n   be2_virt_ptr<void> loadSection;\n   be2_val<OSDynLoad_FreeFn> dynLoadFreeFn;\n   be2_val<virt_addr> codeExports;\n   be2_val<uint32_t> numCodeExports;\n   be2_val<virt_addr> dataExports;\n   be2_val<uint32_t> numDataExports;\n   be2_virt_ptr<RPL_DATA> next;\n   UNKNOWN(0x94 - 0x58);\n};\n\nstruct StaticDynLoadData\n{\n   be2_array<char, 32> loaderLockName;\n\n   be2_val<OSDynLoad_AllocFn> allocFn;\n   be2_val<OSDynLoad_FreeFn> freeFn;\n\n   be2_val<BOOL> tlsAllocLocked;\n   be2_val<uint32_t> tlsHeader;\n   be2_val<uint32_t> tlsModuleIndex;\n   be2_val<OSDynLoad_AllocFn> tlsAllocFn;\n   be2_val<OSDynLoad_FreeFn> tlsFreeFn;\n\n   be2_virt_ptr<OSDynLoad_NotifyCallback> notifyCallbacks;\n\n   be2_struct<OSHandleTable> handleTable;\n\n   be2_array<char, 16> coreinitModuleName;\n   be2_virt_ptr<RPL_DATA> rplDataList;\n\n   be2_virt_ptr<RPL_DATA> rpxData;\n   be2_virt_ptr<const char> rpxName;\n   be2_val<uint32_t> rpxNameLength;\n\n   be2_val<BOOL> inModuleEntryPoint;\n   be2_virt_ptr<RPL_DATA> linkingRplList;\n\n   be2_val<MEMHeapHandle> preInitMem1Heap;\n   be2_val<MEMHeapHandle> preInitMem2Heap;\n   be2_val<MEMHeapHandle> preInitForegroundHeap;\n\n   be2_val<uint32_t> numAllocations;\n   be2_val<uint32_t> totalAllocatedBytes;\n\n   be2_val<uint32_t> numModules;\n   be2_virt_ptr<virt_ptr<RPL_DATA>> modules;\n\n   struct FatalErrorInfo\n   {\n      be2_val<uint32_t> msgType;\n      be2_val<uint32_t> error;\n      be2_val<uint32_t> loaderError;\n      be2_val<uint32_t> line;\n      be2_array<char, 64> funcName;\n   };\n\n   be2_struct<FatalErrorInfo> fatalError;\n};\n\nstatic virt_ptr<StaticDynLoadData>\nsDynLoadData = nullptr;\n\nstatic virt_ptr<OSMutex>\nsDynLoad_LoaderLock = nullptr;\n\nnamespace internal\n{\n\nstatic OSDynLoad_Error\ndoImports(virt_ptr<RPL_DATA> rplData);\n\nstatic void\nrelease(OSDynLoad_ModuleHandle moduleHandle,\n        virt_ptr<virt_ptr<RPL_DATA>> unloadedModuleList);\n\n/**\n * Perform an allocation for the OSDYNLOAD_HEAP heap.\n */\nstatic OSDynLoad_Error\ndynLoadHeapAlloc(const char *name,\n                 OSDynLoad_AllocFn allocFn,\n                 uint32_t size,\n                 int32_t align,\n                 virt_ptr<virt_ptr<void>> outPtr)\n{\n   auto error = cafe::invoke(cpu::this_core::state(), allocFn, size, align,\n                             outPtr);\n\n   if (isAppDebugLevelNotice()) {\n      COSInfo(COSReportModule::Unknown2, fmt::format(\n         \"OSDYNLOAD_HEAP:{},ALLOC,=\\\"{}\\\",-{}\",\n         name, *outPtr, size));\n   }\n\n   return error;\n}\n\n\n/**\n * Perform a free for the OSDYNLOAD_HEAP heap.\n */\nstatic void\ndynLoadHeapFree(const char *name,\n                OSDynLoad_FreeFn freeFn,\n                virt_ptr<void> ptr,\n                uint32_t size)\n{\n   if (isAppDebugLevelNotice()) {\n      COSInfo(COSReportModule::Unknown2, fmt::format(\n         \"OSDYNLOAD_HEAP:{},FREE,=\\\"{}\\\",-{}\",\n         name, ptr, size));\n   }\n\n   cafe::invoke(cpu::this_core::state(), freeFn, ptr);\n}\n\n\n/**\n * Perform an allocation for the RPL_SYSHEAP heap.\n */\nstatic virt_ptr<void>\nrplSysHeapAlloc(const char *name,\n                uint32_t size,\n                int32_t align)\n{\n   auto ptr = OSAllocFromSystem(size, align);\n\n   if (isAppDebugLevelNotice()) {\n      COSInfo(COSReportModule::Unknown2, fmt::format(\n         \"RPL_SYSHEAP:{},ALLOC,=\\\"{}\\\",-{}\",\n         name, ptr, size));\n   }\n\n   return ptr;\n}\n\n\n/**\n * Perform a free for the RPL_SYSHEAP heap.\n */\nstatic void\nrplSysHeapFree(const char *name,\n               virt_ptr<void> ptr,\n               uint32_t size)\n{\n   if (isAppDebugLevelNotice()) {\n      COSInfo(COSReportModule::Unknown2, fmt::format(\n         \"RPL_SYSHEAP:{},FREE,=\\\"{}\\\",-{}\",\n         name, ptr, size));\n   }\n\n   OSFreeToSystem(ptr);\n}\n\n\n/**\n * Set dynload fatal error info.\n */\nstatic void\nsetFatalErrorInfo(uint32_t fatalMsgType,\n                  uint32_t fatalErr,\n                  uint32_t loaderError,\n                  uint32_t fatalLine,\n                  virt_ptr<const char> fatalFunction)\n{\n   if (sDynLoadData->fatalError.error) {\n      return;\n   }\n\n   sDynLoadData->fatalError.msgType = fatalMsgType;\n   sDynLoadData->fatalError.error = fatalErr;\n   sDynLoadData->fatalError.loaderError = loaderError;\n   sDynLoadData->fatalError.line = fatalLine;\n   sDynLoadData->fatalError.funcName = fatalFunction.get();\n}\n\n\n/**\n * Set dynload fatal error info with some sort of deviceLocation magic.\n */\nstatic void\nsetFatalErrorInfo2(uint32_t deviceLocation,\n                   uint32_t loaderError,\n                   bool a3,\n                   uint32_t fatalLine,\n                   std::string_view fatalFunction)\n{\n   if (sDynLoadData->fatalError.error) {\n      return;\n   }\n\n   switch (deviceLocation) {\n   case 0:\n   {\n      if (a3) {\n         sDynLoadData->fatalError.msgType = 2u;\n         sDynLoadData->fatalError.error = 1603203u;\n      } else {\n         sDynLoadData->fatalError.msgType = 1u;\n         sDynLoadData->fatalError.error = 1603200u;\n      }\n\n      sDynLoadData->fatalError.loaderError = loaderError;\n      sDynLoadData->fatalError.line = fatalLine;\n      sDynLoadData->fatalError.funcName = fatalFunction;\n      break;\n   }\n   case 1:\n   {\n      if (a3) {\n         sDynLoadData->fatalError.msgType = 8u;\n         sDynLoadData->fatalError.error = 1603204u;\n      } else {\n         sDynLoadData->fatalError.msgType = 1u;\n         sDynLoadData->fatalError.error = 1603201u;\n      }\n\n      sDynLoadData->fatalError.loaderError = loaderError;\n      sDynLoadData->fatalError.line = fatalLine;\n      sDynLoadData->fatalError.funcName = fatalFunction;\n      break;\n   }\n   case 2:\n   {\n      if (a3) {\n         sDynLoadData->fatalError.msgType = 5u;\n         sDynLoadData->fatalError.error = 1604205u;\n      } else {\n         sDynLoadData->fatalError.msgType = 1u;\n         sDynLoadData->fatalError.error = 1602202u;\n      }\n\n      sDynLoadData->fatalError.loaderError = loaderError;\n      sDynLoadData->fatalError.line = fatalLine;\n      sDynLoadData->fatalError.funcName = fatalFunction;\n      break;\n   }\n   default:\n      OSPanic(\"OSDynLoad_DynInit.c\", 104, \"***Unknown device location error.\");\n   }\n}\n\n\n/**\n * Clear the dynload fatal error info.\n */\nstatic void\nresetFatalErrorInfo()\n{\n   std::memset(virt_addrof(sDynLoadData->fatalError).get(),\n               0,\n               sizeof(sDynLoadData->fatalError));\n}\n\n\n/**\n * Report a fatal dynload error to the kernel.\n */\nstatic void\nreportFatalError()\n{\n   auto error = StackObject<OSFatalError> { };\n\n   if (!sDynLoadData->fatalError.error) {\n      OSPanic(\"OSDynLoad_DynInit.c\", 122,\n              \"__OSDynLoad_InitFromCoreInit() - gFatalInfo unexpectantly empty!!!.\");\n   }\n\n   error->errorCode = sDynLoadData->fatalError.error;\n   error->internalErrorCode = sDynLoadData->fatalError.loaderError;\n   error->messageType = sDynLoadData->fatalError.msgType;\n   error->processId = static_cast<uint32_t>(OSGetUPID());\n   OSSendFatalError(error,\n                    virt_addrof(sDynLoadData->fatalError.funcName),\n                    sDynLoadData->fatalError.line);\n}\n\nstatic OSDynLoad_Error\ninternalAcquire(virt_ptr<const char> name,\n                virt_ptr<virt_ptr<RPL_DATA>> outRplData,\n                virt_ptr<uint32_t> outNumEntryModules,\n                virt_ptr<virt_ptr<virt_ptr<RPL_DATA>>> outEntryModules,\n                bool doLoad);\n\n/**\n * Run all the entry points in the given entry points array.\n */\nstatic int32_t\nrunEntryPoints(uint32_t numEntryModules,\n               virt_ptr<virt_ptr<RPL_DATA>> entryModules)\n{\n   auto error = 0;\n\n   for (auto i = 0u; i < numEntryModules; ++i) {\n      auto entry = virt_func_cast<OSDynLoad_RplEntryFn>(entryModules[i]->entryPoint);\n      error = cafe::invoke(cpu::this_core::state(),\n                           entry,\n                           entryModules[i]->handle,\n                           OSDynLoad_EntryReason::Loaded);\n      if (error) {\n         COSError(COSReportModule::Unknown2, fmt::format(\n            \"*** ERROR: Module \\\"{}\\\" returned error code {} ({} from its entrypoint on load.\",\n            entryModules[i]->moduleName, error, error));\n         setFatalErrorInfo2(entryModules[i]->userFileInfo->titleLocation,\n                            error, false,\n                            44, \"__OSDynLoad_RunEntryPoints\");\n      }\n\n      entryModules[i]->entryPoint = virt_addr { 0 };\n   }\n\n   if (entryModules) {\n      rplSysHeapFree(\"RPL_ENTRYPOINTS_ARRAY\", entryModules, 4 * numEntryModules);\n   }\n\n   return error;\n}\n\n\n/**\n * Load modulePath.\n */\nstatic OSDynLoad_Error\ninternalAcquire2(virt_ptr<const char> modulePath,\n                 virt_ptr<OSDynLoad_ModuleHandle> outModuleHandle,\n                 BOOL isCheckLoaded)\n{\n   auto rplData = StackObject<virt_ptr<RPL_DATA>> { };\n   auto numEntryModules = StackObject<uint32_t> { };\n   auto entryModules = StackObject<virt_ptr<virt_ptr<RPL_DATA>>> { };\n   *rplData = nullptr;\n   *numEntryModules = 0u;\n   *entryModules = nullptr;\n\n   if (isAppDebugLevelNotice()) {\n      COSInfo(COSReportModule::Unknown2, fmt::format(\n         \"RPL_SYSHEAP:{} START,{}\",\n         isCheckLoaded ? \"CHECK_LOADED\" : \"ACQUIRE\",\n         modulePath));\n   }\n\n   if (isAppDebugLevelNotice()) {\n      COSInfo(COSReportModule::Unknown2, fmt::format(\n         \"SYSTEM_HEAP:{} START,{}\",\n         isCheckLoaded ? \"CHECK_LOADED\" : \"ACQUIRE\",\n         modulePath));\n   }\n\n   if (!outModuleHandle) {\n      return OSDynLoad_Error::InvalidAcquirePtr;\n   }\n\n   if (!modulePath) {\n      return OSDynLoad_Error::InvalidModuleNamePtr;\n   }\n\n   if (!modulePath[0]) {\n      return OSDynLoad_Error::InvalidModuleName;\n   }\n\n   resetFatalErrorInfo();\n\n   if (auto error = internalAcquire(modulePath, rplData, numEntryModules,\n                                    entryModules, isCheckLoaded)) {\n      if (*entryModules) {\n         rplSysHeapFree(\"ENTRYPOINTS_ARRAY\", entryModules, isCheckLoaded);\n      }\n\n      if (!isCheckLoaded) {\n         COSError(COSReportModule::Unknown2, fmt::format(\n            \"Error: Could not load acquired RPL \\\"{}\\\".\", modulePath));\n      }\n\n      *outModuleHandle = 0u;\n      return error;\n   }\n\n   *outModuleHandle = (*rplData)->handle;\n\n   if (!isCheckLoaded) {\n      if (runEntryPoints(*numEntryModules, *entryModules) != OSDynLoad_Error::OK) {\n         if (*outModuleHandle) {\n            OSDynLoad_Release(*outModuleHandle);\n         }\n\n         *outModuleHandle = 0u;\n         return OSDynLoad_Error::RunEntryPointError;\n      }\n   }\n\n   resetFatalErrorInfo();\n\n   if (isAppDebugLevelNotice()) {\n      COSInfo(COSReportModule::Unknown2, fmt::format(\n         \"RPL_SYSHEAP:{} END,{}\",\n         isCheckLoaded ? \"CHECK_LOADED\" : \"ACQUIRE\",\n         modulePath));\n   }\n\n   if (isAppDebugLevelNotice()) {\n      COSInfo(COSReportModule::Unknown2, fmt::format(\n         \"SYSTEM_HEAP:{} END,{}\",\n         isCheckLoaded ? \"CHECK_LOADED\" : \"ACQUIRE\",\n         modulePath));\n   }\n\n   return OSDynLoad_Error::OK;\n}\n\n\n/**\n * Count the number of notify callbacks registered.\n */\nstatic uint32_t\ngetNotifyCallbackCount()\n{\n   auto count = 0u;\n   for (auto itr = sDynLoadData->notifyCallbacks; itr; itr = itr->next) {\n      ++count;\n   }\n\n   return count;\n}\n\n\n/**\n * Unload the given module.\n */\nstatic void\nunloadModule(virt_ptr<RPL_DATA> rplData,\n             virt_ptr<virt_ptr<RPL_DATA>> unloadedModuleList)\n{\n   // Run the RPL entry point with Unloaded\n   if (rplData->entryPoint) {\n      auto entry = virt_func_cast<OSDynLoad_RplEntryFn>(rplData->entryPoint);\n      cafe::invoke(cpu::this_core::state(),\n                   entry,\n                   rplData->handle,\n                   OSDynLoad_EntryReason::Unloaded);\n   }\n\n   if (rplData->notifyData) {\n      // TODO: Check for alarms, threads, interrupt callbacks, ipc callbacks\n      // which reference this module\n   }\n\n   // Release all imported modules\n   for (auto i = 0u; i < rplData->importModuleCount; ++i) {\n      if (rplData->importModules[i] &&\n          rplData->importModules[i] != virt_cast<RPL_DATA *>(virt_addr { 0xFFFFFFFFu })) {\n         release(rplData->importModules[i]->handle, unloadedModuleList);\n         rplData->importModules[i] = nullptr;\n      }\n   }\n\n   // Remove from the loaded module list\n   auto prev = virt_ptr<RPL_DATA> { nullptr };\n   for (auto itr = sDynLoadData->rplDataList; itr; itr = itr->next) {\n      if (itr == rplData) {\n         break;\n      }\n\n      prev = itr;\n   }\n\n   if (prev) {\n      prev->next = rplData->next;\n   } else {\n      sDynLoadData->rplDataList = rplData->next;\n   }\n\n   // Insert into unloaded module list\n   rplData->next = *unloadedModuleList;\n   *unloadedModuleList = rplData;\n}\n\n\n/**\n * Free allocated memory for a loaded module.\n */\nstatic void\ninternalPurge(virt_ptr<RPL_DATA> rpl)\n{\n   if (!rpl->handle) {\n      internal::OSPanic(\"OSDynLoad_Purge.c\", 23,\n                        \"OSDynLoad_InternalPurge - nonzero handle on purge.\");\n   }\n\n   if (rpl->loaderHandle) {\n      kernel::loaderPurge(rpl->loaderHandle);\n      rpl->loaderHandle = nullptr;\n   }\n\n   rpl->entryPoint = virt_addr { 0 };\n   rpl->codeExports = virt_addr { 0 };\n   rpl->dataExports = virt_addr { 0 };\n   rpl->numDataExports = virt_addr { 0 };\n   rpl->numCodeExports = virt_addr { 0 };\n\n   // Free dataSection\n   if (rpl->dataSection && rpl->dynLoadFreeFn) {\n      dynLoadHeapFree(\"RPL_DATA_AREA\", rpl->dynLoadFreeFn,\n                      rpl->dataSection, rpl->dataSectionSize);\n      sDynLoadData->totalAllocatedBytes -= rpl->dataSectionSize;\n      sDynLoadData->numAllocations--;\n   }\n\n   rpl->dataSectionSize = 0u;\n\n   // Free loadSection\n   if (rpl->loadSection && rpl->dynLoadFreeFn) {\n      dynLoadHeapFree(\"RPL_DATA_AREA\", rpl->dynLoadFreeFn,\n                      rpl->loadSection, rpl->loadSectionSize);\n      sDynLoadData->totalAllocatedBytes -= rpl->loadSectionSize;\n      sDynLoadData->numAllocations--;\n   }\n\n   rpl->loadSectionSize = 0u;\n\n   // Free notifyData\n   if (rpl->notifyData) {\n      if (rpl->notifyData->name) {\n         auto pathStringLength = 0u;\n         if (rpl->userFileInfo) {\n            pathStringLength = rpl->userFileInfo->pathStringLength;\n         }\n\n         rplSysHeapFree(\"RPL_NOTIFY_NAME\",\n                        rpl->notifyData->name, pathStringLength);\n      }\n\n      rplSysHeapFree(\"RPL_NOTIFY\",\n                     rpl->notifyData, sizeof(OSDynLoad_NotifyData));\n   }\n\n   rpl->notifyData = nullptr;\n\n   // Free file info\n   if (rpl->userFileInfo && rpl->dynLoadFreeFn) {\n      dynLoadHeapFree(\"RPL_FILE_INFO\", rpl->dynLoadFreeFn,\n                      rpl->userFileInfo, rpl->userFileInfoSize);\n      sDynLoadData->totalAllocatedBytes -= rpl->userFileInfoSize;\n      sDynLoadData->numAllocations--;\n   }\n\n   rpl->userFileInfo = nullptr;\n   rpl->userFileInfoSize = 0u;\n\n   // Free section info\n   if (rpl->sectionInfo) {\n      rplSysHeapFree(\"RPL_SEC_INFO\",\n                     rpl->sectionInfo,\n                     sizeof(loader::LOADER_SectionInfo) * rpl->sectionInfoCount);\n   }\n\n   rpl->sectionInfo = nullptr;\n   rpl->sectionInfoCount = 0u;\n\n   // Free imported modules array\n   if (rpl->importModules) {\n      rplSysHeapFree(\"RPL_SEC_INFO\",\n                     rpl->importModules, 4 * rpl->importModuleCount);\n   }\n\n   rpl->importModules = nullptr;\n   rpl->importModuleCount = 0u;\n   rpl->dynLoadFreeFn = nullptr;\n\n   // Free module name\n   if (rpl->moduleName) {\n      rplSysHeapFree(\"RPL_NAME\",\n                     rpl->moduleName,\n                     align_up(rpl->moduleNameLen, 4));\n   }\n\n   rpl->moduleName = nullptr;\n   rpl->moduleNameLen = 0u;\n\n   // And finally.. free the RPL data itself.\n   std::memset(rpl.get(), 0, sizeof(RPL_DATA));\n   rplSysHeapFree(\"RPL_DATA\", rpl, sizeof(RPL_DATA));\n}\n\n\n/**\n * Release a loaded module.\n */\nstatic void\nrelease(OSDynLoad_ModuleHandle moduleHandle,\n        virt_ptr<virt_ptr<RPL_DATA>> unloadedModuleList)\n{\n   auto userData1 = StackObject<virt_ptr<void>> { };\n   auto refCount = StackObject<uint32_t> { };\n   auto rootUnloadedModuleList = StackObject<virt_ptr<RPL_DATA>> { };\n\n   if (moduleHandle == OSDynLoad_CurrentModuleHandle ||\n       !sDynLoadData->rplDataList) {\n      return;\n   }\n\n   // First we translate and add ref to get the userData1\n   if (auto error = OSHandle_TranslateAndAddRef(virt_addrof(sDynLoadData->handleTable),\n                                                moduleHandle,\n                                                userData1,\n                                                nullptr)) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"*** OSDynLoad_Release: RPL Module handle {} was not valid@0. (err=0x{:08X})\",\n         moduleHandle, error));\n      return;\n   }\n\n   auto rplData = virt_cast<RPL_DATA *>(userData1);\n\n   // Release our just acquired reference\n   if (auto error = OSHandle_Release(virt_addrof(sDynLoadData->handleTable),\n                                     moduleHandle,\n                                     refCount)) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"*** OSDynLoad_Release: RPL Module handle {} was not valid@1. (err=0x{:08X})\",\n         moduleHandle, error));\n      return;\n   }\n\n   if (refCount == 0) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"*** OSDynLoad_Release: reference count error on OSDynLoad_ModuleHandle. ({})\",\n         *refCount));\n      return;\n   }\n\n   // Now do the actual release\n   if (auto error = OSHandle_Release(virt_addrof(sDynLoadData->handleTable),\n                                     moduleHandle,\n                                     refCount)) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"*** OSDynLoad_Release: RPL Module handle {} was not valid@2. (err=0x{:08X})\",\n         moduleHandle, error));\n      return;\n   }\n\n   if (refCount) {\n      // Nothing to do, ref count still >0\n      return;\n   }\n\n   OSLockMutex(sDynLoad_LoaderLock);\n\n   auto isFirstUnloadModule = false;\n   if (!unloadedModuleList) {\n      unloadedModuleList = rootUnloadedModuleList;\n      *rootUnloadedModuleList = nullptr;\n      isFirstUnloadModule = true;\n   }\n\n   auto notifyCallbackCount = 0u;\n   auto notifyCallbackArray = virt_ptr<OSDynLoad_NotifyCallback> { nullptr };\n\n   if (isFirstUnloadModule) {\n      notifyCallbackCount = getNotifyCallbackCount();\n      if (notifyCallbackCount) {\n         auto allocSize = static_cast<uint32_t>(\n            notifyCallbackCount * sizeof(OSDynLoad_NotifyCallback));\n\n         notifyCallbackArray = virt_cast<OSDynLoad_NotifyCallback *>(\n            rplSysHeapAlloc(\"RPL_NOTIFY_ARRAY\", allocSize, 4));\n         if (notifyCallbackArray) {\n            auto notifyCallback = sDynLoadData->notifyCallbacks;\n            for (auto i = 0u; notifyCallback; ++i) {\n               std::memcpy(virt_addrof(notifyCallbackArray[i]).get(),\n                           notifyCallback.get(),\n                           sizeof(OSDynLoad_NotifyCallback));\n\n               notifyCallback = notifyCallback->next;\n            }\n         }\n      }\n   }\n\n   unloadModule(rplData, unloadedModuleList);\n\n   if (isFirstUnloadModule) {\n      // Call notify callbacks for all the unloaded modules and free their\n      // notify data\n      for (auto rpl = *unloadedModuleList; rpl; rpl = rpl->next) {\n         if (notifyCallbackArray) {\n            for (auto i = 0u; i < notifyCallbackCount; ++i) {\n               cafe::invoke(cpu::this_core::state(),\n                            notifyCallbackArray[i].notifyFn,\n                            rpl->handle,\n                            notifyCallbackArray->userArg1,\n                            OSDynLoad_NotifyEvent::Unloaded,\n                            rpl->notifyData);\n            }\n         }\n\n         if (rpl->notifyData->name) {\n            auto pathStringLength = 0u;\n            if (rpl->userFileInfo) {\n               pathStringLength = rpl->userFileInfo->pathStringLength;\n            }\n\n            rplSysHeapFree(\"RPL_NOTIFY\",\n                           rpl->notifyData->name, pathStringLength);\n         }\n\n         rplSysHeapFree(\"RPL_NOTIFY\",\n                        rpl->notifyData, sizeof(OSDynLoad_NotifyData));\n\n         rpl->notifyData = nullptr;\n      }\n\n      if (notifyCallbackArray) {\n         rplSysHeapFree(\"RPL_NOTIFY_ARRAY\",\n                        notifyCallbackArray,\n                        notifyCallbackCount * sizeof(OSDynLoad_NotifyCallback));\n      }\n\n      for (auto rpl = *unloadedModuleList; rpl; rpl = rpl->next) {\n         internalPurge(rpl);\n      }\n   }\n\n   OSUnlockMutex(sDynLoad_LoaderLock);\n}\n\n\n/**\n * Binary search for an export.\n */\nstatic virt_ptr<loader::rpl::Export>\nbinarySearchExport(virt_ptr<loader::rpl::Export> exports,\n                   uint32_t numExports,\n                   virt_ptr<const char> name)\n{\n   auto exportNames = virt_cast<const char *>(exports) - 8;\n   auto left = 0u;\n   auto right = numExports;\n\n   while (true) {\n      auto index = left + (right - left) / 2;\n      auto exportName = exportNames.get() + (exports[index].name & 0x7FFFFFFF);\n      auto cmpValue = strcmp(name.get(), exportName);\n      if (cmpValue == 0) {\n         return exports + index;\n      } else if (cmpValue < 0) {\n         right = index;\n      } else {\n         left = index + 1;\n      }\n\n      if (left >= right) {\n         return nullptr;\n      }\n   }\n}\n\n\n/**\n * Find export sections in an RPL.\n */\nstatic void\nfindExports(virt_ptr<RPL_DATA> rplData)\n{\n   for (auto i = 0u; i < rplData->sectionInfoCount; ++i) {\n      auto &sectionInfo = rplData->sectionInfo[i];\n      if (sectionInfo.type == loader::rpl::SHT_RPL_EXPORTS) {\n         if (sectionInfo.flags & loader::rpl::SHF_EXECINSTR) {\n            rplData->codeExports = sectionInfo.address + 8;\n            rplData->numCodeExports = *virt_cast<uint32_t *>(sectionInfo.address);\n         } else {\n            rplData->dataExports = sectionInfo.address + 8;\n            rplData->numDataExports = *virt_cast<uint32_t *>(sectionInfo.address);\n         }\n      }\n   }\n}\n\n\n/**\n * Find TLS sections in an RPL.\n */\nstatic bool\nfindTlsSection(virt_ptr<RPL_DATA> rplData)\n{\n   // Find TLS section\n   if (rplData->userFileInfo->fileInfoFlags & loader::rpl::RPL_HAS_TLS) {\n      auto tlsAddressStart = virt_addr { 0xFFFFFFFFu };\n      auto tlsAddressEnd = virt_addr { 0 };\n\n      for (auto i = 0u; i < rplData->sectionInfoCount; ++i) {\n         auto &sectionInfo = rplData->sectionInfo[i];\n         if (sectionInfo.flags & loader::rpl::SHF_TLS) {\n            if (sectionInfo.address < tlsAddressStart) {\n               tlsAddressStart = sectionInfo.address;\n            }\n\n            if (sectionInfo.address + sectionInfo.size > tlsAddressEnd) {\n               tlsAddressEnd = sectionInfo.address + sectionInfo.size;\n            }\n         }\n      }\n\n      if (tlsAddressStart == virt_addr { 0xFFFFFFFFu } ||\n          tlsAddressEnd == virt_addr { 0 }) {\n         return false;\n      }\n\n      rplData->userFileInfo->tlsAddressStart = tlsAddressStart;\n      rplData->userFileInfo->tlsSectionSize =\n         static_cast<uint32_t>(tlsAddressEnd - tlsAddressStart);\n   }\n\n   return true;\n}\n\n\n/**\n * sSetupPerm\n */\nstatic int32_t\nsetupPerm(virt_ptr<RPL_DATA> rplData,\n          bool updateTlsModuleIndex)\n{\n   auto minFileInfo = StackObject<loader::LOADER_MinFileInfo> { };\n   std::memset(minFileInfo.get(), 0, sizeof(loader::LOADER_MinFileInfo));\n   minFileInfo->size = static_cast<uint32_t>(sizeof(loader::LOADER_MinFileInfo));\n   minFileInfo->version = 4u;\n   minFileInfo->outPathStringSize = virt_addrof(minFileInfo->pathStringSize);\n   minFileInfo->outNumberOfSections = virt_addrof(rplData->sectionInfoCount);\n   minFileInfo->outSizeOfFileInfo = virt_addrof(rplData->userFileInfoSize);\n\n   if (updateTlsModuleIndex) {\n      minFileInfo->inoutNextTlsModuleNumber = virt_addrof(sDynLoadData->tlsModuleIndex);\n   }\n\n   // Query to get the required buffer sizes\n   auto error = kernel::loaderQuery(rplData->loaderHandle, minFileInfo);\n   if (error) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InitFromCoreInit() - could not query loader about module (err=0x{:08X}).\",\n         error));\n\n      if (minFileInfo->fatalErr) {\n         setFatalErrorInfo(\n            minFileInfo->fatalMsgType,\n            minFileInfo->fatalErr,\n            minFileInfo->error,\n            minFileInfo->fatalLine,\n            virt_addrof(minFileInfo->fatalFunction));\n         reportFatalError();\n      } else {\n         setFatalErrorInfo2(minFileInfo->fileLocation, error, 1, 162, \"sSetupPerm\");\n         reportFatalError();\n      }\n   }\n\n   // Allocate section info\n   rplData->sectionInfo = virt_cast<loader::LOADER_SectionInfo *>(\n      rplSysHeapAlloc(\"RPL_SEC_INFO\",\n                      sizeof(loader::LOADER_SectionInfo) * rplData->sectionInfoCount,\n                      -4));\n   if (!rplData->sectionInfo) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InitFromCoreInit() - could not allocate memory to hold section info (err=0x{:08X}).\",\n         OSDynLoad_Error::OutOfSysMemory));\n      setFatalErrorInfo2(minFileInfo->fileLocation,\n                         OSDynLoad_Error::OutOfSysMemory,\n                         0, 179, \"sSetupPerm\");\n      reportFatalError();\n   }\n\n   minFileInfo->outSectionInfo = rplData->sectionInfo;\n\n   // Allocate file info\n   rplData->userFileInfo = virt_cast<loader::LOADER_UserFileInfo *>(\n      rplSysHeapAlloc(\"RPL_FILE_INFO\",\n                      rplData->userFileInfoSize, 4));\n\n   if (!rplData->userFileInfo) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InitFromCoreInit() - could not allocate memory to hold file info (err=0x{:08X}).\",\n         OSDynLoad_Error::OutOfSysMemory));\n      setFatalErrorInfo2(minFileInfo->fileLocation,\n                         OSDynLoad_Error::OutOfSysMemory,\n                         0, 196, \"sSetupPerm\");\n      reportFatalError();\n   }\n\n   minFileInfo->outFileInfo = rplData->userFileInfo;\n   std::memset(rplData->userFileInfo.get(), 0,\n               sizeof(loader::LOADER_UserFileInfo));\n\n   // Allocate path string\n   if (minFileInfo->pathStringSize) {\n      minFileInfo->pathStringBuffer = virt_cast<char *>(\n         rplSysHeapAlloc(\"RPL_NOTIFY_NAME\",\n                         minFileInfo->pathStringSize, 4));\n      if (!minFileInfo->pathStringBuffer) {\n         if (isAppDebugLevelNotice()) {\n            dumpSystemHeap();\n         }\n\n         COSError(COSReportModule::Unknown2, fmt::format(\n            \"__OSDynLoad_InitFromCoreInit() - could not allocate memory to hold path string (err=0x{:08X}).\",\n            OSDynLoad_Error::OutOfSysMemory));\n         setFatalErrorInfo2(minFileInfo->fileLocation,\n                            OSDynLoad_Error::OutOfSysMemory,\n                            0, 218, \"sSetupPerm\");\n         reportFatalError();\n      }\n\n      std::memset(minFileInfo->pathStringBuffer.get(), 0,\n                  minFileInfo->pathStringSize);\n   }\n\n   // Query to get the data\n   error = kernel::loaderQuery(rplData->loaderHandle, minFileInfo);\n   if (error) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InitFromCoreInit() - could not get file information (err=0x{:08X}).\",\n         error));\n      setFatalErrorInfo(\n         minFileInfo->fatalMsgType,\n         minFileInfo->fatalErr,\n         minFileInfo->error,\n         minFileInfo->fatalLine,\n         virt_addrof(minFileInfo->fatalFunction));\n      reportFatalError();\n   }\n\n   findExports(rplData);\n\n   if (!findTlsSection(rplData)) {\n      COSError(COSReportModule::Unknown2,\n         \"*** Can not find section for TLS data.\");\n      setFatalErrorInfo2(rplData->userFileInfo->titleLocation,\n                         OSDynLoad_Error::TLSSectionNotFound, 1,\n                         277, \"sSetupPerm\");\n      reportFatalError();\n   }\n\n   return 0;\n}\n\n\n/**\n * Call the initialise default heap function for an RPL.\n *\n * For .rpx this is __preinit_user, for coreinit it is CoreInitDefaultHeap.\n */\nstatic OSDynLoad_Error\ninitialiseDefaultHeap(virt_ptr<RPL_DATA> rplData,\n                      virt_ptr<const char> functionName)\n{\n   auto funcAddr = StackObject<virt_addr> { };\n   auto error = OSDynLoad_FindExport(rplData->handle, FALSE, functionName,\n                                     funcAddr);\n   if (error != OSDynLoad_Error::OK) {\n      return error;\n   }\n\n   cafe::invoke(cpu::this_core::state(),\n                virt_func_cast<OSDynLoad_InitDefaultHeapFn>(*funcAddr),\n                virt_addrof(sDynLoadData->preInitMem1Heap),\n                virt_addrof(sDynLoadData->preInitForegroundHeap),\n                virt_addrof(sDynLoadData->preInitMem2Heap));\n\n   auto allocFn = StackObject<OSDynLoad_AllocFn> { };\n   auto freeFn = StackObject<OSDynLoad_FreeFn> { };\n   error = OSDynLoad_GetAllocator(allocFn, freeFn);\n   if (error != OSDynLoad_Error::OK || !(*allocFn) || !(*freeFn)) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InternalInitDefaultHeap() - {} did not set OSDynLoad \"\n         \"allocator (err=0x{:08X}).\",\n         functionName, error));\n      setFatalErrorInfo2(rplData->userFileInfo->titleLocation, error, 0, 363,\n                         \"__OSDynLoad_InternalInitDefaultHeap\");\n      reportFatalError();\n   }\n\n   error = OSDynLoad_GetTLSAllocator(allocFn, freeFn);\n   if (error != OSDynLoad_Error::OK || !(*allocFn) || !(*freeFn)) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InternalInitDefaultHeap() - {} did not set OSDynLoad \"\n         \"TLS allocator (err=0x{:08X}).\",\n         functionName, error));\n      setFatalErrorInfo2(rplData->userFileInfo->titleLocation, error, 0, 363,\n                         \"__OSDynLoad_InternalInitDefaultHeap\");\n      reportFatalError();\n   }\n\n   return OSDynLoad_Error::OK;\n}\n\n\n/**\n * Resolve a module name.\n *\n * Removes any directories or file extension.\n */\nstatic std::pair<virt_ptr<const char>, uint32_t>\nresolveModuleName(virt_ptr<const char> name)\n{\n   auto str = std::string_view { name.get() };\n   auto pos = str.find_last_of(\"\\\\/\");\n   if (pos != std::string_view::npos) {\n      str = str.substr(pos);\n      name += pos;\n   }\n\n   pos = str.find_first_of('.');\n   if (pos != std::string_view::npos) {\n      str = str.substr(0, pos);\n   }\n\n   return { name, static_cast<uint32_t>(str.size()) };\n}\n\n\n/**\n * __OSDynLoad_InitCommon\n */\nstatic OSDynLoad_Error\ninitialiseCommon()\n{\n   sDynLoadData->loaderLockName = \"{ LoaderLock }\";\n   OSInitMutexEx(sDynLoad_LoaderLock,\n                 virt_addrof(sDynLoadData->loaderLockName));\n   OSHandle_InitTable(virt_addrof(sDynLoadData->handleTable));\n\n   // Parse argstr into rpx name\n   std::tie(sDynLoadData->rpxName, sDynLoadData->rpxNameLength) = resolveModuleName(getArgStr());\n   if (sDynLoadData->rpxNameLength > 63u) {\n      sDynLoadData->rpxNameLength = 63u;\n   }\n\n   // TODO: OSDynLoad_AddNotifyCallback(MasterAgent_LoadNotify, 0)\n   return OSDynLoad_Error::OK;\n}\n\n\n/**\n * __BuildKernelNotify\n */\nstatic OSDynLoad_Error\nbuildKernelNotify(virt_ptr<RPL_DATA> linkList,\n                  virt_ptr<loader::LOADER_LinkInfo> *outLinkInfo,\n                  virt_ptr<RPL_DATA> rpl)\n{\n   auto numModules = 0u;\n   *outLinkInfo = nullptr;\n\n   for (auto module = linkList; module; module = module->next) {\n      numModules++;\n   }\n\n   auto linkInfoSize =\n      static_cast<uint32_t>(sizeof(loader::LOADER_LinkModule) * numModules + 8);\n   auto linkInfo = virt_cast<loader::LOADER_LinkInfo *>(\n      rplSysHeapAlloc(\"RPL_MODULES_LINK_ARRAY\", linkInfoSize, -4));\n   if (!linkInfo) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      setFatalErrorInfo2(rpl->userFileInfo->titleLocation,\n                         OSDynLoad_Error::OutOfSysMemory, 0, 101,\n                         \"__BuildKernelNotify\");\n      return OSDynLoad_Error::OutOfSysMemory;\n   }\n\n   std::memset(linkInfo.get(), 0, linkInfoSize);\n   linkInfo->numModules = numModules;\n   linkInfo->size = linkInfoSize;\n\n   auto moduleIndex = 0u;\n   for (auto module = linkList; module; module = module->next) {\n      linkInfo->modules[moduleIndex].loaderHandle = module->loaderHandle;\n      linkInfo->modules[moduleIndex].entryPoint = virt_addr { 0 };\n      moduleIndex++;\n   }\n\n   *outLinkInfo = linkInfo;\n   return OSDynLoad_Error::OK;\n}\n\n\n/**\n * __OSDynLoad_ExecuteDynamicLink\n */\nstatic OSDynLoad_Error\nexecuteDynamicLink(virt_ptr<RPL_DATA> rpx,\n                   virt_ptr<uint32_t> outNumEntryModules,\n                   virt_ptr<virt_ptr<virt_ptr<RPL_DATA>>> outEntryModules,\n                   virt_ptr<RPL_DATA> rpl)\n{\n   auto minFileInfo = StackObject<loader::LOADER_MinFileInfo> { };\n   auto linkInfo = virt_ptr<loader::LOADER_LinkInfo> { nullptr };\n   auto error = OSDynLoad_Error::OK;\n\n   *outNumEntryModules = 0u;\n   *outEntryModules = nullptr;\n\n   std::memset(minFileInfo.get(), 0,\n               sizeof(loader::LOADER_MinFileInfo));\n   minFileInfo->size = static_cast<uint32_t>(sizeof(loader::LOADER_MinFileInfo));\n   minFileInfo->version = 4u;\n   if (auto notifyError = buildKernelNotify(sDynLoadData->linkingRplList, &linkInfo, rpl)) {\n      return notifyError;\n   }\n\n   // Call the loader to link the rpx\n   error = static_cast<OSDynLoad_Error>(kernel::loaderLink(nullptr,\n                                                           minFileInfo,\n                                                           linkInfo,\n                                                           linkInfo->size));\n   if (error) {\n      COSError(COSReportModule::Unknown2,\n               \"*** Kernel refused to link RPLs.\");\n\n      if (minFileInfo->fatalErr) {\n         setFatalErrorInfo(\n            minFileInfo->fatalMsgType,\n            minFileInfo->fatalErr,\n            minFileInfo->error,\n            minFileInfo->fatalLine,\n            virt_addrof(minFileInfo->fatalFunction));\n      }\n\n      error = OSDynLoad_Error::LoaderError;\n   } else {\n      for (auto i = 0u; i < linkInfo->numModules; ++i) {\n         auto &linkModule = linkInfo->modules[i];\n         auto rplData = virt_ptr<RPL_DATA> { nullptr };\n\n         for (rplData = sDynLoadData->linkingRplList; rplData; rplData = rplData->next) {\n            if (rplData->loaderHandle == linkModule.loaderHandle) {\n               break;\n            }\n         }\n\n         if (!rplData) {\n            OSPanic(\"OSDynLoad_Acquire.c\", 110,\n                    \"*** Error in dynamic linking.  linked module not found.\");\n            error = OSDynLoad_Error::ModuleNotFound;\n         } else {\n            // Allocate and fill out the notify data structure\n            rplData->entryPoint = linkModule.entryPoint;\n            rplData->notifyData = virt_cast<OSDynLoad_NotifyData *>(\n               rplSysHeapAlloc(\"RPL_NOTIFY\",\n                               sizeof(OSDynLoad_NotifyData), 4));\n            if (!rplData->notifyData) {\n               if (isAppDebugLevelNotice()) {\n                  dumpSystemHeap();\n               }\n\n               setFatalErrorInfo2(rpl->userFileInfo->titleLocation,\n                                  OSDynLoad_Error::OutOfSysMemory,\n                                  0, 174, \"__OSDynLoad_ExecuteDynamicLink\");\n               error = OSDynLoad_Error::OutOfSysMemory;\n               continue;\n            }\n\n            auto notifyData = rplData->notifyData;\n            std::memset(notifyData.get(), 0,\n                        sizeof(OSDynLoad_NotifyData));\n\n            notifyData->name = rplData->userFileInfo->pathString;\n            rplData->userFileInfo->pathString = nullptr;\n\n            notifyData->textAddr = linkModule.textAddr;\n            notifyData->textSize = linkModule.textSize;\n            notifyData->textOffset = linkModule.textOffset;\n\n            notifyData->dataAddr = linkModule.dataAddr;\n            notifyData->dataSize = linkModule.dataSize;\n            notifyData->dataOffset = linkModule.dataOffset;\n\n            notifyData->readAddr = linkModule.dataAddr;\n            notifyData->readSize = linkModule.dataSize;\n            notifyData->readOffset = linkModule.dataOffset;\n\n            if (!isAppDebugLevelNotice()) {\n               COSInfo(COSReportModule::Unknown2, fmt::format(\n                  \"{}: TEXT {}:{} DATA {}:{} LOAD {}:{}\",\n                  rplData->moduleName,\n                  linkModule.textAddr,\n                  linkModule.textAddr + linkModule.textSize,\n                  linkModule.dataAddr,\n                  linkModule.dataAddr + linkModule.dataSize,\n                  linkModule.loadAddr,\n                  linkModule.loadAddr + linkModule.loadSize));\n            } else {\n               COSInfo(COSReportModule::Unknown2, fmt::format(\n                  \"RPL_LAYOUT:{},TEXT,start,=\\\"{}\\\"\",\n                  rplData->moduleName,\n                  linkModule.textAddr));\n\n               COSInfo(COSReportModule::Unknown2, fmt::format(\n                  \"RPL_LAYOUT:{},TEXT,end,=\\\"{}\\\"\",\n                  rplData->moduleName,\n                  linkModule.textAddr + linkModule.textSize));\n\n               COSInfo(COSReportModule::Unknown2, fmt::format(\n                  \"RPL_LAYOUT:{},DATA,start,=\\\"{}\\\"\",\n                  rplData->moduleName,\n                  linkModule.dataAddr));\n\n               COSInfo(COSReportModule::Unknown2, fmt::format(\n                  \"RPL_LAYOUT:{},DATA,end,=\\\"{}\\\"\",\n                  rplData->moduleName,\n                  linkModule.dataAddr + linkModule.dataSize));\n\n               COSInfo(COSReportModule::Unknown2, fmt::format(\n                  \"RPL_LAYOUT:{},LOAD,start,=\\\"{}\\\"\",\n                  rplData->moduleName,\n                  linkModule.loadAddr));\n\n               COSInfo(COSReportModule::Unknown2, fmt::format(\n                  \"RPL_LAYOUT:{},LOAD,end,=\\\"{}\\\"\",\n                  rplData->moduleName,\n                  linkModule.loadAddr + linkModule.loadSize));\n            }\n         }\n      }\n   }\n\n   if (!error) {\n      // Allocate and fill up the entry modules array\n      auto entryModules = virt_ptr<virt_ptr<RPL_DATA>> { nullptr };\n      sDynLoadData->modules =\n         virt_cast<virt_ptr<RPL_DATA> *>(\n            rplSysHeapAlloc(\"RPL_MODULES_ARRAY\",\n                            4 * linkInfo->numModules, -4));\n\n      if (!sDynLoadData->modules) {\n         if (isAppDebugLevelNotice()) {\n            dumpSystemHeap();\n         }\n\n         sDynLoadData->modules = nullptr;\n         sDynLoadData->numModules = 0u;\n      } else {\n         entryModules = virt_cast<virt_ptr<RPL_DATA> *>(\n            rplSysHeapAlloc(\"RPL_ENTRYPOINTS_ARRAY\",\n                            4 * linkInfo->numModules, -4));\n\n         if (!entryModules) {\n            if (isAppDebugLevelNotice()) {\n               dumpSystemHeap();\n            }\n\n            rplSysHeapFree(\"RPL_MODULES_ARRAY\",\n                           sDynLoadData->modules,\n                           4 * linkInfo->numModules);\n            sDynLoadData->modules = nullptr;\n            sDynLoadData->numModules = 0u;\n         }\n      }\n\n      *outEntryModules = entryModules;\n\n      auto itr = sDynLoadData->linkingRplList;\n      auto prev = sDynLoadData->linkingRplList;\n      sDynLoadData->numModules = 0u;\n      while (true) {\n         if (sDynLoadData->modules) {\n            OSHandle_AddRef(virt_addrof(sDynLoadData->handleTable), itr->handle);\n            sDynLoadData->modules[sDynLoadData->numModules] = itr;\n\n            if (itr != rpx) {\n               entryModules[(*outNumEntryModules)++] = itr;\n            }\n         }\n\n         itr = itr->next;\n         sDynLoadData->numModules++;\n         if (!itr) {\n            break;\n         }\n\n         prev = itr;\n      }\n\n      if (sDynLoadData->rplDataList &&\n         (sDynLoadData->rplDataList->userFileInfo->fileInfoFlags & loader::rpl::RPL_IS_RPX)) {\n         prev->next = sDynLoadData->rplDataList->next;\n         sDynLoadData->rplDataList->next = sDynLoadData->linkingRplList;\n      } else {\n         prev->next = sDynLoadData->rplDataList;\n         sDynLoadData->rplDataList = sDynLoadData->linkingRplList;\n      }\n\n      sDynLoadData->linkingRplList = nullptr;\n   }\n\n   rplSysHeapFree(\"RPL_MODULES_LINK_ARRAY\", linkInfo, 0);\n   return error;\n}\n\n\n/**\n * Prepare an RPL for loading.\n */\nstatic OSDynLoad_Error\nprepareLoad(virt_ptr<RPL_DATA> *ptrRplData,\n            OSDynLoad_AllocFn dynLoadAlloc,\n            virt_ptr<loader::LOADER_MinFileInfo> *ptrMinFileInfo)\n{\n   auto allocPtr = StackObject<virt_ptr<void>> { };\n   auto rplData = *ptrRplData;\n   auto minFileInfo = *ptrMinFileInfo;\n\n   // Allocate section info\n   rplData->sectionInfo = virt_cast<loader::LOADER_SectionInfo *>(\n      rplSysHeapAlloc(\"RPL_SEC_INFO\",\n                      rplData->sectionInfoCount * sizeof(loader::LOADER_SectionInfo),\n                      -4));\n   if (!rplData->sectionInfo) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      setFatalErrorInfo2(minFileInfo->fileLocation,\n                         OSDynLoad_Error::OutOfSysMemory, 0,\n                         620, \"sPrepareLoad\");\n      return OSDynLoad_Error::OutOfSysMemory;\n   }\n\n   minFileInfo->outSectionInfo = rplData->sectionInfo;\n\n   // Allocate file info\n   if (auto error = dynLoadHeapAlloc(\"RPL_FILE_INFO\", dynLoadAlloc,\n                                     rplData->userFileInfoSize, 4, allocPtr)) {\n      setFatalErrorInfo2(minFileInfo->fileLocation, error, 0,\n                         637, \"sPrepareLoad\");\n      return error;\n   }\n\n   rplData->userFileInfo = virt_cast<loader::LOADER_UserFileInfo *>(*allocPtr);\n\n   if (isAppDebugLevelNotice()) {\n      COSInfo(COSReportModule::Unknown2, fmt::format(\n         \"RPL_LAYOUT:{},FILE,start,=\\\"{}\\\"\",\n         rplData->moduleName,\n         rplData->userFileInfo));\n\n      COSInfo(COSReportModule::Unknown2, fmt::format(\n         \"RPL_LAYOUT:{},FILE,end,=\\\"{}\\\"\",\n         rplData->moduleName,\n         virt_cast<virt_addr>(rplData->userFileInfo) + rplData->userFileInfoSize));\n   }\n\n   minFileInfo->outFileInfo = rplData->userFileInfo;\n   sDynLoadData->numAllocations++;\n   sDynLoadData->totalAllocatedBytes += rplData->userFileInfoSize;\n   std::memset(rplData->userFileInfo.get(), 0, rplData->userFileInfoSize);\n\n   // Allocate path string buffer\n   if (minFileInfo->pathStringSize) {\n      minFileInfo->pathStringBuffer = virt_cast<char *>(\n         rplSysHeapAlloc(\"RPL_NOTIFY_NAME\", minFileInfo->pathStringSize, 4));\n      if (!minFileInfo->pathStringBuffer) {\n         if (isAppDebugLevelNotice()) {\n            dumpSystemHeap();\n         }\n\n         setFatalErrorInfo2(minFileInfo->fileLocation,\n                            OSDynLoad_Error::OutOfSysMemory, 0,\n                            666, \"sPrepareLoad\");\n         return OSDynLoad_Error::OutOfSysMemory;\n      }\n\n      std::memset(minFileInfo->pathStringBuffer.get(), 0,\n                  minFileInfo->pathStringSize);\n   }\n\n   if (minFileInfo->fileInfoFlags & loader::rpl::RPL_FLAG_4) {\n      // Query the loader for file info\n      if (auto error = kernel::loaderQuery(rplData->loaderHandle, minFileInfo)) {\n         if (minFileInfo->fatalErr) {\n            setFatalErrorInfo(\n               minFileInfo->fatalMsgType,\n               error,\n               minFileInfo->error,\n               minFileInfo->fatalLine,\n               virt_addrof(minFileInfo->fatalFunction));\n         }\n\n         return OSDynLoad_Error::InvalidRPL;\n      }\n   } else {\n      // Allocate data section\n      if (minFileInfo->dataSize) {\n         if (auto error = dynLoadHeapAlloc(\"RPL_DATA_AREA\",\n                                           dynLoadAlloc,\n                                           minFileInfo->dataSize,\n                                           minFileInfo->dataAlign,\n                                           virt_addrof(rplData->dataSection))) {\n            setFatalErrorInfo2(minFileInfo->fileLocation, error, false,\n                               705, \"sPrepareLoad\");\n            return error;\n         }\n\n         if (!kernel::validateAddressRange(virt_cast<virt_addr>(rplData->dataSection),\n                                           minFileInfo->dataSize) ||\n             !align_check(virt_cast<virt_addr>(rplData->dataSection),\n                          minFileInfo->dataAlign)) {\n            setFatalErrorInfo2(minFileInfo->fileLocation,\n                               OSDynLoad_Error::InvalidAllocatedPtr, true,\n                               701, \"sPrepareLoad\");\n            return OSDynLoad_Error::InvalidAllocatedPtr;\n         }\n\n         rplData->dataSectionSize = minFileInfo->dataSize;\n         minFileInfo->dataBuffer = rplData->dataSection;\n         sDynLoadData->numAllocations++;\n         sDynLoadData->totalAllocatedBytes += minFileInfo->dataSize;\n      }\n\n      // Allocate load section\n      if (minFileInfo->loadSize) {\n         if (auto error = dynLoadHeapAlloc(\"RPL_LOAD_INFO_AREA\",\n                                           dynLoadAlloc,\n                                           minFileInfo->loadSize,\n                                           minFileInfo->loadAlign,\n                                           virt_addrof(rplData->loadSection))) {\n            setFatalErrorInfo2(minFileInfo->fileLocation, error, false,\n                               748, \"sPrepareLoad\");\n            return error;\n         }\n\n         if (!kernel::validateAddressRange(virt_cast<virt_addr>(rplData->loadSection),\n                                           minFileInfo->loadSize) ||\n             !align_check(virt_cast<virt_addr>(rplData->loadSection),\n                          minFileInfo->loadAlign)) {\n            setFatalErrorInfo2(minFileInfo->fileLocation,\n                               OSDynLoad_Error::InvalidAllocatedPtr, true,\n                               752, \"sPrepareLoad\");\n            return OSDynLoad_Error::InvalidAllocatedPtr;\n         }\n\n         rplData->loadSectionSize = minFileInfo->loadSize;\n         minFileInfo->loadBuffer = rplData->loadSection;\n         sDynLoadData->numAllocations++;\n         sDynLoadData->totalAllocatedBytes += minFileInfo->loadSize;\n      }\n\n      // Run the loader setup\n      if (auto error = kernel::loaderSetup(rplData->loaderHandle, minFileInfo)) {\n         if (minFileInfo->fatalErr) {\n            setFatalErrorInfo(\n               minFileInfo->fatalMsgType,\n               error,\n               minFileInfo->error,\n               minFileInfo->fatalLine,\n               virt_addrof(minFileInfo->fatalFunction));\n         }\n\n         return OSDynLoad_Error::LoaderError;\n      }\n   }\n\n   rplSysHeapFree(\"RPL_TEMP_DATA\", minFileInfo,\n                  sizeof(loader::LOADER_MinFileInfo));\n   *ptrMinFileInfo = nullptr;\n\n   findExports(rplData);\n\n   if (!findTlsSection(rplData)) {\n      COSError(COSReportModule::Unknown2,\n               \"*** Can not find section for TLS data.\");\n      setFatalErrorInfo2(rplData->userFileInfo->titleLocation,\n                         OSDynLoad_Error::TLSSectionNotFound, 0,\n                         831, \"sPrepareLoad\");\n      return OSDynLoad_Error::TLSSectionNotFound;\n   }\n\n   rplData->next = sDynLoadData->linkingRplList;\n   sDynLoadData->linkingRplList = rplData;\n   *ptrRplData = nullptr;\n   return doImports(rplData);\n}\n\n\n/**\n * Release all importing modules.\n */\nstatic void\nreleaseImports()\n{\n   auto inLinkingList =\n      [](virt_ptr<RPL_DATA> rpl)\n      {\n         for (auto itr = sDynLoadData->linkingRplList; itr; itr = itr->next) {\n            if (rpl == itr) {\n               return true;\n            }\n         }\n\n         return false;\n      };\n\n   for (auto module = sDynLoadData->linkingRplList; module; module = module->next) {\n      for (auto i = 0u; i < module->importModuleCount; ++i) {\n         auto importModule = module->importModules[i];\n         if (!importModule) {\n            continue;\n         }\n\n         if (inLinkingList(importModule)) {\n            OSHandle_Release(virt_addrof(sDynLoadData->handleTable),\n                             importModule->handle,\n                             nullptr);\n         } else {\n            OSDynLoad_Release(importModule->handle);\n         }\n      }\n   }\n}\n\n\n/**\n * Acquire a module.\n */\nstatic OSDynLoad_Error\ninternalAcquire(virt_ptr<const char> name,\n                virt_ptr<virt_ptr<RPL_DATA>> outRplData,\n                virt_ptr<uint32_t> outNumEntryModules,\n                virt_ptr<virt_ptr<virt_ptr<RPL_DATA>>> outEntryModules,\n                bool doLoad)\n{\n   auto error = OSDynLoad_Error::OK;\n\n   // Resolve module name\n   auto[moduleName, moduleNameLength] = resolveModuleName(name);\n   if (!moduleNameLength) {\n      setFatalErrorInfo2(\n         sDynLoadData->rpxData->userFileInfo->titleLocation,\n         OSDynLoad_Error::EmptyModuleName, 0,\n         949, \"__OSDynLoad_InternalAcquire\");\n      return OSDynLoad_Error::EmptyModuleName;\n   }\n\n   // Allocate rpl name\n   auto rplNameLength = align_up(moduleNameLength + 1, 4);\n   auto rplName = virt_cast<char *>(\n      rplSysHeapAlloc(\"RPL_NAME\", rplNameLength, 4));\n   if (!rplName) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      setFatalErrorInfo2(\n         sDynLoadData->rpxData->userFileInfo->titleLocation,\n         OSDynLoad_Error::OutOfSysMemory, 0,\n         964, \"__OSDynLoad_InternalAcquire\");\n      return OSDynLoad_Error::OutOfSysMemory;\n   }\n\n   for (auto i = 0u; i < moduleNameLength; ++i) {\n      rplName[i] = static_cast<char>(tolower(moduleName[i]));\n   }\n   rplName[moduleNameLength] = char { 0 };\n\n   auto rplNameCleanup =\n      gsl::finally([&]() {\n         if (rplName) {\n            rplSysHeapFree(\"RPL_NAME\", rplName, rplNameLength);\n         }\n      });\n\n   OSLockMutex(sDynLoad_LoaderLock);\n   auto unlock =\n      gsl::finally([&]() {\n         OSUnlockMutex(sDynLoad_LoaderLock);\n      });\n\n   // We cannot call acquire whilst inside a module entry point\n   if (sDynLoadData->inModuleEntryPoint) {\n      error = OSDynLoad_Error::InModuleEntryPoint;\n      setFatalErrorInfo2(\n         sDynLoadData->rpxData->userFileInfo->titleLocation,\n         error, true, 995, \"__OSDynLoad_InternalAcquire\");\n      return error;\n   }\n\n   // Check if we already loaded this module\n   for (auto rpl = sDynLoadData->rplDataList; rpl; rpl = rpl->next) {\n      if (strcmp(rpl->moduleName.get(), rplName.get()) == 0) {\n         OSHandle_AddRef(virt_addrof(sDynLoadData->handleTable), rpl->handle);\n         *outRplData = rpl;\n         return OSDynLoad_Error::OK;\n      }\n   }\n\n   // Check if we are already loading this module\n   for (auto rpl = sDynLoadData->linkingRplList; rpl; rpl = rpl->next) {\n      if (strcmp(rpl->moduleName.get(), rplName.get()) == 0) {\n         OSHandle_AddRef(virt_addrof(sDynLoadData->handleTable), rpl->handle);\n         *outRplData = rpl;\n         return OSDynLoad_Error::OK;\n      }\n   }\n\n   // Verify we're not loading coreinit, that'd be a massive fuckup\n   if (strcmp(rplName.get(), \"coreinit\") == 0) {\n      COSError(COSReportModule::Unknown2, \"*********\");\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"Error: Trying to load \\\"{}\\\".\", rplName));\n      COSError(COSReportModule::Unknown2, \"*********\");\n      OSPanic(\"OSDynLoad_Acquire.c\", 1035, \"core library conflict.\");\n   }\n\n   // Get the currently set allocator functions\n   auto dynLoadAlloc = StackObject<OSDynLoad_AllocFn> { };\n   auto dynLoadFree = StackObject<OSDynLoad_FreeFn> { };\n   error = OSDynLoad_GetAllocator(dynLoadAlloc, dynLoadFree);\n   if (error || !*dynLoadAlloc || !*dynLoadFree) {\n      if (!error) {\n         error = OSDynLoad_Error::InvalidAllocatorPtr;\n      }\n\n      setFatalErrorInfo2(\n         sDynLoadData->rpxData->userFileInfo->titleLocation,\n         error, false, 1047, \"__OSDynLoad_InternalAcquire\");\n      return error;\n   }\n\n   // Allocate the notify callback array if needed\n   auto firstLinkingRpl = !sDynLoadData->linkingRplList;\n   auto notifyCallbackCount = 0u;\n   auto notifyCallbackArray = virt_ptr<OSDynLoad_NotifyCallback> { nullptr };\n   auto notifyCallbackArrayCleanup =\n      gsl::finally([&]() {\n         if (notifyCallbackArray) {\n            rplSysHeapFree(\"RPL_NOTIFY_ARRAY\",\n                           notifyCallbackArray,\n                           notifyCallbackCount * sizeof(OSDynLoad_NotifyCallback));\n         }\n      });\n\n   if (firstLinkingRpl) {\n      notifyCallbackCount = getNotifyCallbackCount();\n      auto allocSize = static_cast<uint32_t>(notifyCallbackCount * sizeof(OSDynLoad_NotifyCallback));\n\n      notifyCallbackArray = virt_cast<OSDynLoad_NotifyCallback *>(\n         rplSysHeapAlloc(\"RPL_NOTIFY_ARRAY\",\n                         allocSize,\n                         4));\n      if (!notifyCallbackArray) {\n         setFatalErrorInfo2(\n            sDynLoadData->rpxData->userFileInfo->titleLocation,\n            OSDynLoad_Error::OutOfSysMemory, false,\n            1077, \"__OSDynLoad_InternalAcquire\");\n         return OSDynLoad_Error::OutOfSysMemory;\n      }\n\n      auto notifyCallback = sDynLoadData->notifyCallbacks;\n      for (auto i = 0u; notifyCallback; ++i) {\n         std::memcpy(virt_addrof(notifyCallbackArray[i]).get(),\n                     notifyCallback.get(),\n                     sizeof(OSDynLoad_NotifyCallback));\n\n         notifyCallback = notifyCallback->next;\n      }\n   }\n\n   // Allocate rpl data\n   auto rplData = virt_cast<RPL_DATA *>(\n      rplSysHeapAlloc(\"RPL_DATA\", sizeof(RPL_DATA), 4));\n   if (!rplData) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      setFatalErrorInfo2(\n         sDynLoadData->rpxData->userFileInfo->titleLocation,\n         OSDynLoad_Error::OutOfSysMemory, false,\n         1105, \"__OSDynLoad_InternalAcquire\");\n      return OSDynLoad_Error::OutOfSysMemory;\n   }\n\n   auto rplDataCleanup =\n      gsl::finally([&]() {\n         if (rplData) {\n            if (rplData->handle) {\n               auto handleRefCount = StackObject<uint32_t> { };\n               while (!OSHandle_Release(virt_addrof(sDynLoadData->handleTable),\n                                        rplData->handle,\n                                        handleRefCount) && *handleRefCount);\n\n               internalPurge(rplData);\n            }\n\n            rplSysHeapFree(\"RPL_DATA\", rplData, sizeof(RPL_DATA));\n         }\n      });\n\n   std::memset(rplData.get(), 0, sizeof(RPL_DATA));\n\n   // Allocate min file info\n   auto minFileInfo = virt_cast<loader::LOADER_MinFileInfo *>(\n      rplSysHeapAlloc(\"RPL_TEMP_DATA\",\n                      sizeof(loader::LOADER_MinFileInfo), 4));\n   if (!minFileInfo) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      setFatalErrorInfo2(\n         sDynLoadData->rpxData->userFileInfo->titleLocation,\n         OSDynLoad_Error::OutOfSysMemory, false,\n         1131, \"__OSDynLoad_InternalAcquire\");\n      return OSDynLoad_Error::OutOfSysMemory;\n   }\n\n   auto minFileInfoCleanup =\n      gsl::finally([&]() {\n         if (minFileInfo) {\n            rplSysHeapFree(\"RPL_TEMP_DATA\", minFileInfo,\n                           sizeof(loader::LOADER_MinFileInfo));\n         }\n      });\n\n   // Do loader prep\n   auto kernelHandle = StackObject<loader::LOADER_Handle> { };\n   std::memset(minFileInfo.get(), 0, sizeof(loader::LOADER_MinFileInfo));\n   minFileInfo->version = 4u;\n   minFileInfo->size = static_cast<uint32_t>(sizeof(loader::LOADER_MinFileInfo));\n   minFileInfo->outKernelHandle = kernelHandle;\n   minFileInfo->moduleNameBufferLen = moduleNameLength;\n   minFileInfo->moduleNameBuffer = rplName;\n   minFileInfo->inoutNextTlsModuleNumber = virt_addrof(sDynLoadData->tlsModuleIndex);\n   minFileInfo->outPathStringSize = virt_addrof(minFileInfo->pathStringSize);\n   minFileInfo->outNumberOfSections = virt_addrof(rplData->sectionInfoCount);\n   minFileInfo->outSizeOfFileInfo = virt_addrof(rplData->userFileInfoSize);\n\n   if (auto prepError = kernel::loaderPrep(minFileInfo)) {\n      if (minFileInfo->fatalErr) {\n         setFatalErrorInfo(\n            minFileInfo->fatalMsgType,\n            minFileInfo->fatalErr,\n            minFileInfo->error,\n            minFileInfo->fatalLine,\n            virt_addrof(minFileInfo->fatalFunction));\n      }\n\n      return static_cast<OSDynLoad_Error>(prepError);\n   }\n\n   auto kernelHandleCleanup =\n      gsl::finally([&]() {\n         if (*kernelHandle) {\n            kernel::loaderPurge(*kernelHandle);\n         }\n      });\n\n   // Assign tls header\n   if (sDynLoadData->tlsModuleIndex > sDynLoadData->tlsHeader) {\n      if (sDynLoadData->tlsModuleIndex > MaxTlsModuleIndex) {\n         COSError(COSReportModule::Unknown2, fmt::format(\n            \"*** Too many RPLs have tls data; maximum 32k, needed {}\",\n            sDynLoadData->tlsModuleIndex));\n         setFatalErrorInfo2(\n            minFileInfo->fileLocation,\n            OSDynLoad_Error::TLSTooManyModules, false,\n            1178, \"__OSDynLoad_InternalAcquire\");\n\n         return OSDynLoad_Error::TLSTooManyModules;\n      }\n\n      sDynLoadData->tlsHeader =\n         std::min<uint32_t>(sDynLoadData->tlsModuleIndex + 8,\n                              MaxTlsModuleIndex);\n   }\n\n   // Allocate a DynLoad handle for the new RPL\n   if (auto allocError = OSHandle_Alloc(virt_addrof(sDynLoadData->handleTable),\n                                        rplData, nullptr,\n                                        virt_addrof(rplData->handle))) {\n      setFatalErrorInfo2(minFileInfo->fileLocation, allocError, false,\n                         1195, \"__OSDynLoad_InternalAcquire\");\n      return static_cast<OSDynLoad_Error>(allocError);\n   }\n\n   // Prepare load\n   *outRplData = rplData;\n   rplData->dynLoadFreeFn = *dynLoadFree;\n   rplData->loaderHandle = *kernelHandle;\n   rplData->moduleName = rplName;\n   rplData->moduleNameLen = moduleNameLength;\n   *kernelHandle = nullptr;\n   rplName = nullptr;\n\n   error = prepareLoad(&rplData, *dynLoadAlloc, &minFileInfo);\n   if (firstLinkingRpl) {\n      if (error) {\n         // If prepareLoad returned an error then we need to clean up rplData\n         // in the case that prepareLoad did not clean it up itself\n         if (!rplData && *outRplData) {\n            auto handleRefCount = StackObject<uint32_t> { };\n            while (!OSHandle_Release(virt_addrof(sDynLoadData->handleTable),\n                                     (*outRplData)->handle,\n                                     handleRefCount) && *handleRefCount);\n         }\n      } else {\n         error = executeDynamicLink(nullptr,\n                                    outNumEntryModules,\n                                    outEntryModules,\n                                    *outRplData);\n      }\n\n      releaseImports();\n\n      // Release any remaining RPLs still in the linking list\n      while (sDynLoadData->linkingRplList) {\n         auto rpl = sDynLoadData->linkingRplList;\n         sDynLoadData->linkingRplList = rpl->next;\n         rpl->next = nullptr;\n\n         if (rpl->handle) {\n            auto handleRefCount = StackObject<uint32_t> { };\n            while(!OSHandle_Release(virt_addrof(sDynLoadData->handleTable),\n                                    rpl->handle,\n                                    handleRefCount));\n         }\n      }\n   }\n\n   return error;\n}\n\n\n/**\n * __OSDynLoad_DoImports\n */\nstatic OSDynLoad_Error\ndoImports(virt_ptr<RPL_DATA> rplData)\n{\n   // Get the section header string table\n   auto shstrndx = rplData->userFileInfo->shstrndx;\n   if (!rplData->userFileInfo->shstrndx) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"Error: Could not get section string table index for \\\"{}\\\".\",\n         rplData->moduleName));\n      setFatalErrorInfo2(rplData->userFileInfo->titleLocation,\n                         OSDynLoad_Error::InvalidShStrNdx, true,\n                         391, \"__OSDynLoad_DoImports\");\n      return OSDynLoad_Error::InvalidShStrNdx;\n   }\n\n   auto shStrSection = virt_cast<char *>(rplData->sectionInfo[shstrndx].address);\n   if (!shStrSection) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"Error: Could not get section string table for \\\"{}\\\".\",\n         rplData->moduleName));\n      setFatalErrorInfo2(rplData->userFileInfo->titleLocation,\n                         OSDynLoad_Error::InvalidShStrSection, true,\n                         383, \"__OSDynLoad_DoImports\");\n      return OSDynLoad_Error::InvalidShStrSection;\n   }\n\n   // Count the number of imported modules\n   auto numImportModules = 0u;\n   for (auto i = 0u; i < rplData->sectionInfoCount; ++i) {\n      if (rplData->sectionInfo[i].address &&\n          rplData->sectionInfo[i].name &&\n          (rplData->sectionInfo[i].flags & loader::rpl::SHF_ALLOC) &&\n          rplData->sectionInfo[i].type == loader::rpl::SHT_RPL_IMPORTS) {\n         numImportModules++;\n      }\n   }\n\n   if (!numImportModules) {\n      return OSDynLoad_Error::OK;\n   }\n\n   // Allocate imported modules array\n   rplData->importModuleCount = numImportModules;\n   rplData->importModules = virt_cast<virt_ptr<RPL_DATA> *>(\n      rplSysHeapAlloc(\"RPL_SEC_INFO\",\n                      sizeof(virt_ptr<RPL_DATA>) * numImportModules, 4));\n   if (!rplData->importModules) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      setFatalErrorInfo2(rplData->userFileInfo->titleLocation,\n                         OSDynLoad_Error::OutOfSysMemory, false,\n                         423, \"__OSDynLoad_DoImports\");\n      return OSDynLoad_Error::OutOfSysMemory;\n   }\n\n   // Acquire all imported modules\n   auto importModuleIdx = 0u;\n   for (auto i = 0u; i < rplData->sectionInfoCount; ++i) {\n      if (rplData->sectionInfo[i].address &&\n          rplData->sectionInfo[i].name &&\n          (rplData->sectionInfo[i].flags & loader::rpl::SHF_ALLOC) &&\n          rplData->sectionInfo[i].type == loader::rpl::SHT_RPL_IMPORTS) {\n         auto name = shStrSection + rplData->sectionInfo[i].name;\n\n         if (isAppDebugLevelNotice()) {\n            COSInfo(COSReportModule::Unknown2, fmt::format(\n               \"RPL_SYSHEAP:IMPORT START,{}\", name));\n            COSInfo(COSReportModule::Unknown2, fmt::format(\n               \"SYSTEM_HEAP:IMPORT START,{}\", name));\n         }\n\n         auto error =\n            internalAcquire(name + 9,\n                            virt_addrof(rplData->importModules[importModuleIdx]),\n                            0, 0, 0);\n\n         if (isAppDebugLevelNotice()) {\n            COSInfo(COSReportModule::Unknown2, fmt::format(\n               \"RPL_SYSHEAP:IMPORT END,{}\", name));\n            COSInfo(COSReportModule::Unknown2, fmt::format(\n               \"SYSTEM_HEAP:IMPORT END,{}\", name));\n         }\n\n         if (error) {\n            COSError(COSReportModule::Unknown2, fmt::format(\n               \"Error: Could not load imported RPL \\\"{}\\\".\", name));\n            return error;\n         }\n\n         auto importModule = rplData->importModules[importModuleIdx];\n         if (!importModule->entryPoint) {\n            // Ensure rplData comes after importModule in the linking list\n            auto itr = virt_ptr<RPL_DATA> { nullptr };\n            auto prev = virt_ptr<RPL_DATA> { nullptr };\n            for (itr = sDynLoadData->linkingRplList; itr != rplData; itr = itr->next) {\n               prev = itr;\n            }\n\n            while ((itr = itr->next)) {\n               if (itr == importModule) {\n                  if (prev) {\n                     prev->next = rplData->next;\n                  } else {\n                     sDynLoadData->linkingRplList = rplData->next;\n                  }\n\n                  rplData->next = importModule->next;\n                  importModule->next = rplData;\n                  break;\n               }\n            }\n         }\n\n         ++importModuleIdx;\n      }\n   }\n\n   rplSysHeapFree(\"RPL_SEC_INFO\", rplData->sectionInfo,\n                  rplData->sectionInfoCount * sizeof(loader::LOADER_SectionInfo));\n   rplData->sectionInfo = nullptr;\n   return OSDynLoad_Error::OK;\n}\n\n} // namespace internal\n\n\n/**\n * Registers a callback to be called when an RPL is loaded or unloaded.\n */\nOSDynLoad_Error\nOSDynLoad_AddNotifyCallback(OSDynLoad_NotifyCallbackFn notifyFn,\n                            virt_ptr<void> userArg1)\n{\n   if (!notifyFn) {\n      return OSDynLoad_Error::InvalidAllocatorPtr;\n   }\n\n   auto notifyCallback = virt_cast<OSDynLoad_NotifyCallback *>(\n      OSAllocFromSystem(sizeof(OSDynLoad_NotifyCallback), 4));\n   if (!notifyCallback) {\n      return OSDynLoad_Error::OutOfSysMemory;\n   }\n\n   OSLockMutex(sDynLoad_LoaderLock);\n   notifyCallback->notifyFn = notifyFn;\n   notifyCallback->userArg1 = userArg1;\n   notifyCallback->next = sDynLoadData->notifyCallbacks;\n\n   sDynLoadData->notifyCallbacks = notifyCallback;\n   OSUnlockMutex(sDynLoad_LoaderLock);\n   return OSDynLoad_Error::OK;\n}\n\n\n/**\n * Deletes a callback previously registered by OSDynLoad_AddNotifyCallback.\n */\nvoid\nOSDynLoad_DelNotifyCallback(OSDynLoad_NotifyCallbackFn notifyFn,\n                            virt_ptr<void> userArg1)\n{\n   if (!notifyFn) {\n      return;\n   }\n\n   OSLockMutex(sDynLoad_LoaderLock);\n\n   // Find the callback\n   auto prevCallback = virt_ptr<OSDynLoad_NotifyCallback> { nullptr };\n   auto callback = sDynLoadData->notifyCallbacks;\n   while (callback) {\n      if (callback->notifyFn == notifyFn && callback->userArg1 == userArg1) {\n         break;\n      }\n\n      prevCallback = callback;\n      callback = callback->next;\n   }\n\n   if (callback) {\n      // Erase it from linked list\n      if (prevCallback) {\n         prevCallback->next = callback->next;\n      } else {\n         sDynLoadData->notifyCallbacks = callback->next;\n      }\n   }\n\n   OSUnlockMutex(sDynLoad_LoaderLock);\n\n   // Free the callback\n   if (callback) {\n      OSFreeToSystem(callback);\n   }\n}\n\n\n/**\n * Retrieve the allocator functions set by OSDynLoad_SetAllocator.\n */\nOSDynLoad_Error\nOSDynLoad_GetAllocator(virt_ptr<OSDynLoad_AllocFn> outAllocFn,\n                       virt_ptr<OSDynLoad_FreeFn> outFreeFn)\n{\n   OSLockMutex(sDynLoad_LoaderLock);\n\n   if (outAllocFn) {\n      *outAllocFn = sDynLoadData->allocFn;\n   }\n\n   if (outFreeFn) {\n      *outFreeFn = sDynLoadData->freeFn;\n   }\n\n   OSUnlockMutex(sDynLoad_LoaderLock);\n   return OSDynLoad_Error::OK;\n}\n\n\n/**\n * Set the allocator which controls allocation of RPL segments.\n */\nOSDynLoad_Error\nOSDynLoad_SetAllocator(OSDynLoad_AllocFn allocFn,\n                       OSDynLoad_FreeFn freeFn)\n{\n   if (!allocFn || !freeFn) {\n      return OSDynLoad_Error::InvalidAllocatorPtr;\n   }\n\n   OSLockMutex(sDynLoad_LoaderLock);\n   sDynLoadData->allocFn = allocFn;\n   sDynLoadData->freeFn = freeFn;\n   OSUnlockMutex(sDynLoad_LoaderLock);\n   return OSDynLoad_Error::OK;\n}\n\n\n/**\n * Retrieve the allocator functions set by OSDynLoad_SetTLSAllocator.\n */\nOSDynLoad_Error\nOSDynLoad_GetTLSAllocator(virt_ptr<OSDynLoad_AllocFn> outAllocFn,\n                          virt_ptr<OSDynLoad_FreeFn> outFreeFn)\n{\n   OSLockMutex(sDynLoad_LoaderLock);\n\n   if (outAllocFn) {\n      *outAllocFn = sDynLoadData->tlsAllocFn;\n   }\n\n   if (outFreeFn) {\n      *outFreeFn = sDynLoadData->tlsFreeFn;\n   }\n\n   OSUnlockMutex(sDynLoad_LoaderLock);\n   return OSDynLoad_Error::OK;\n}\n\n\n/**\n * Set the allocator which controls allocation of TLS memory.\n */\nOSDynLoad_Error\nOSDynLoad_SetTLSAllocator(OSDynLoad_AllocFn allocFn,\n                          OSDynLoad_FreeFn freeFn)\n{\n   if (!allocFn || !freeFn) {\n      return OSDynLoad_Error::InvalidAllocatorPtr;\n   }\n\n   if (sDynLoadData->tlsAllocLocked) {\n      return OSDynLoad_Error::TLSAllocatorLocked;\n   }\n\n   OSLockMutex(sDynLoad_LoaderLock);\n   sDynLoadData->tlsAllocFn = allocFn;\n   sDynLoadData->tlsFreeFn = freeFn;\n   OSUnlockMutex(sDynLoad_LoaderLock);\n   return OSDynLoad_Error::OK;\n}\n\n\n/**\n * Acquire a module.\n *\n * If a module is already loaded this will increase it's ref count.\n * If a module is not yet loaded it will be loaded.\n */\nOSDynLoad_Error\nOSDynLoad_Acquire(virt_ptr<const char> modulePath,\n                  virt_ptr<OSDynLoad_ModuleHandle> outModuleHandle)\n{\n   return internal::internalAcquire2(modulePath, outModuleHandle, FALSE);\n}\n\n\n/**\n * Release a module.\n *\n * Decreases ref count of module and deallocates it if ref count hits 0.\n */\nvoid\nOSDynLoad_Release(OSDynLoad_ModuleHandle moduleHandle)\n{\n   internal::release(moduleHandle, nullptr);\n}\n\n\n/**\n * Acquire the module which contains ptr.\n */\nOSDynLoad_Error\nOSDynLoad_AcquireContainingModule(virt_ptr<void> ptr,\n                                  OSDynLoad_SectionType sectionType,\n                                  virt_ptr<OSDynLoad_ModuleHandle> outHandle)\n{\n   if (!outHandle) {\n      return OSDynLoad_Error::InvalidAcquirePtr;\n   }\n\n   if (!ptr) {\n      return OSDynLoad_Error::InvalidContainPtr;\n   }\n\n   OSLockMutex(sDynLoad_LoaderLock);\n   auto addr = virt_cast<virt_addr>(ptr);\n\n   for (auto itr = sDynLoadData->rplDataList; itr; itr = itr->next) {\n      if (!itr->notifyData) {\n         continue;\n      }\n\n      if (sectionType == OSDynLoad_SectionType::Any ||\n          sectionType == OSDynLoad_SectionType::DataOnly) {\n         if (addr >= itr->notifyData->dataAddr &&\n             addr <  itr->notifyData->dataAddr + itr->notifyData->dataSize) {\n            if (itr == sDynLoadData->rpxData) {\n               *outHandle = OSDynLoad_CurrentModuleHandle;\n            } else {\n               OSHandle_AddRef(virt_addrof(sDynLoadData->handleTable), itr->handle);\n               *outHandle = itr->handle;\n            }\n\n            OSUnlockMutex(sDynLoad_LoaderLock);\n            return OSDynLoad_Error::OK;\n         }\n      }\n\n      if (sectionType == OSDynLoad_SectionType::Any ||\n          sectionType == OSDynLoad_SectionType::CodeOnly) {\n         if (addr >= itr->notifyData->textAddr &&\n             addr <  itr->notifyData->textAddr + itr->notifyData->textSize) {\n            if (itr == sDynLoadData->rpxData) {\n               *outHandle = OSDynLoad_CurrentModuleHandle;\n            } else {\n               OSHandle_AddRef(virt_addrof(sDynLoadData->handleTable), itr->handle);\n               *outHandle = itr->handle;\n            }\n\n            OSUnlockMutex(sDynLoad_LoaderLock);\n            return OSDynLoad_Error::OK;\n         }\n      }\n   }\n\n   OSUnlockMutex(sDynLoad_LoaderLock);\n   return OSDynLoad_Error::ContainModuleNotFound;\n}\n\n\n/**\n * Find an export from a library handle.\n */\nOSDynLoad_Error\nOSDynLoad_FindExport(OSDynLoad_ModuleHandle moduleHandle,\n                     BOOL isData,\n                     virt_ptr<const char> name,\n                     virt_ptr<virt_addr> outAddr)\n{\n   virt_ptr<RPL_DATA> rplData = nullptr;\n   auto exportData = virt_ptr<loader::rpl::Export> { nullptr };\n   auto error = OSDynLoad_Error::OK;\n\n   if (moduleHandle == OSDynLoad_CurrentModuleHandle) {\n      rplData = sDynLoadData->rpxData;\n   } else {\n      auto handleData = StackObject<virt_ptr<void>> { };\n\n      if (OSHandle_TranslateAndAddRef(virt_addrof(sDynLoadData->handleTable),\n                                      moduleHandle,\n                                      handleData,\n                                      nullptr)) {\n         return OSDynLoad_Error::InvalidHandle;\n      }\n\n      rplData = virt_cast<RPL_DATA *>(*handleData);\n   }\n\n   OSLockMutex(sDynLoad_LoaderLock);\n\n   if (isData) {\n      if (!rplData->dataExports) {\n         error = OSDynLoad_Error::ModuleHasNoDataExports;\n      } else {\n         exportData =\n            internal::binarySearchExport(\n               virt_cast<loader::rpl::Export *>(rplData->dataExports),\n               rplData->numDataExports,\n               name);\n      }\n   } else {\n      if (!rplData->codeExports) {\n         error = OSDynLoad_Error::ModuleHasNoCodeExports;\n      } else {\n         exportData =\n            internal::binarySearchExport(\n               virt_cast<loader::rpl::Export *>(rplData->codeExports),\n               rplData->numCodeExports,\n               name);\n      }\n   }\n\n   if (error == OSDynLoad_Error::OK) {\n      if (!exportData) {\n         error = OSDynLoad_Error::ExportNotFound;\n      } else if (exportData->value & 0x80000000) {\n         error = OSDynLoad_Error::TLSFindExportInvalid;\n      } else {\n         *outAddr = exportData->value;\n      }\n   }\n\n   OSUnlockMutex(sDynLoad_LoaderLock);\n\n   if (moduleHandle != OSDynLoad_CurrentModuleHandle) {\n      OSHandle_Release(virt_addrof(sDynLoadData->handleTable), moduleHandle,\n                       nullptr);\n   }\n\n   return error;\n}\n\n\n/**\n * Find an tag from a library handle.\n */\nOSDynLoad_Error\nOSDynLoad_FindTag(OSDynLoad_ModuleHandle moduleHandle,\n                  virt_ptr<const char> tag,\n                  virt_ptr<char> buffer,\n                  virt_ptr<uint32_t> inoutBufferSize)\n{\n   return OSDynLoad_Error::InvalidParam;\n}\n\n\n/**\n * Get statistics about the loader memory heap.\n */\nOSDynLoad_Error\nOSDynLoad_GetLoaderHeapStatistics(virt_ptr<OSDynLoad_LoaderHeapStatistics> stats)\n{\n   return OSDynLoad_Error::InvalidParam;\n}\n\n\n/**\n * Get the module name for a module handle.\n */\nOSDynLoad_Error\nOSDynLoad_GetModuleName(OSDynLoad_ModuleHandle moduleHandle,\n                        virt_ptr<char> buffer,\n                        virt_ptr<uint32_t> inoutBufferSize)\n{\n   auto error = OSDynLoad_Error::OK;\n\n   if (!moduleHandle) {\n      return OSDynLoad_Error::InvalidHandle;\n   }\n\n   if (!inoutBufferSize ||\n       !kernel::validateAddressRange(virt_cast<virt_addr>(inoutBufferSize), 4)) {\n      return OSDynLoad_Error::InvalidParam;\n   }\n\n   if (moduleHandle == OSDynLoad_CurrentModuleHandle) {\n      auto bufferLength = static_cast<uint32_t>(\n         strlen(sDynLoadData->rpxName.get()) + 1);\n\n      if (bufferLength > *inoutBufferSize) {\n         *inoutBufferSize = bufferLength;\n         error = OSDynLoad_Error::BufferTooSmall;\n      } else {\n         std::memcpy(buffer.get(),\n                     sDynLoadData->rpxName.get(),\n                     bufferLength - 1);\n         buffer[bufferLength - 1] = char { 0 };\n      }\n   } else {\n      auto handleUserData1 = StackObject<virt_ptr<void>> { };\n\n      if (OSHandle_TranslateAndAddRef(virt_addrof(sDynLoadData->handleTable),\n                                      moduleHandle,\n                                      handleUserData1,\n                                      nullptr) != OSHandleError::OK) {\n         return OSDynLoad_Error::InvalidHandle;\n      }\n\n      OSLockMutex(sDynLoad_LoaderLock);\n      auto rplData = virt_cast<RPL_DATA *>(*handleUserData1);\n      auto bufferLength = rplData->moduleNameLen + 1;\n\n      if (bufferLength > *inoutBufferSize) {\n         *inoutBufferSize = bufferLength;\n         error = OSDynLoad_Error::BufferTooSmall;\n      } else {\n         std::memcpy(buffer.get(),\n                     rplData->moduleName.get(),\n                     bufferLength - 1);\n         buffer[bufferLength - 1] = char { 0 };\n      }\n\n      OSUnlockMutex(sDynLoad_LoaderLock);\n      OSHandle_Release(virt_addrof(sDynLoadData->handleTable), moduleHandle,\n                       nullptr);\n   }\n\n   return error;\n}\n\n\n/**\n * Return the number of loaded RPLs.\n *\n * Will always return 0 on non-debug builds of CafeOS.\n */\nuint32_t\nOSDynLoad_GetNumberOfRPLs()\n{\n   return 0;\n}\n\n\n/**\n * Get the info for count RPLs beginning at first.\n */\nuint32_t\nOSDynLoad_GetRPLInfo(uint32_t first,\n                     uint32_t count,\n                     virt_ptr<OSDynLoad_NotifyData> outRplInfos)\n{\n   if (!count) {\n      return 1;\n   }\n\n   return 0;\n}\n\n\n/**\n * Check if a module is loaded and return the handle of a dynamic library.\n */\nOSDynLoad_Error\nOSDynLoad_IsModuleLoaded(virt_ptr<const char> name,\n                         virt_ptr<OSDynLoad_ModuleHandle> outHandle)\n{\n   return internal::internalAcquire2(name, outHandle, TRUE);\n}\n\n\n/**\n * Find a symbol name for the given address.\n *\n * \\return\n * Returns the address of the nearest symbol.\n */\nvirt_addr\nOSGetSymbolName(virt_addr address,\n                virt_ptr<char> buffer,\n                uint32_t bufferSize)\n{\n   auto symbolDistance = StackObject<uint32_t> { };\n   auto symbolNameBuffer = StackArray<char, 256> { };\n   auto moduleNameBuffer = StackArray<char, 256> { };\n\n   *buffer = char { 0 };\n   if (bufferSize < 16) {\n      return address;\n   }\n\n   symbolNameBuffer[0] = char { 0 };\n   moduleNameBuffer[0] = char { 0 };\n\n   auto error = kernel::findClosestSymbol(address,\n                                          symbolDistance,\n                                          symbolNameBuffer,\n                                          symbolNameBuffer.size(),\n                                          moduleNameBuffer,\n                                          moduleNameBuffer.size());\n\n   if (error || (!symbolNameBuffer[0] && !moduleNameBuffer[0])) {\n      string_copy(buffer.get(), \"unknown\", bufferSize);\n      buffer[bufferSize - 1] = char { 0 };\n      return address;\n   }\n\n   auto symbolAddress = address - *symbolDistance;\n   auto moduleNameLength = strlen(moduleNameBuffer.get());\n   auto symbolNameLength = strlen(symbolNameBuffer.get());\n\n   if (moduleNameLength) {\n      string_copy(buffer.get(),\n                  moduleNameBuffer.get(),\n                  bufferSize);\n\n      if (moduleNameLength + 1 >= bufferSize) {\n         buffer[bufferSize - 1] = char { 0 };\n         return symbolAddress;\n      }\n\n      if (symbolNameLength) {\n         buffer[moduleNameLength] = char { '|' };\n         moduleNameLength++;\n      }\n   }\n\n   if (symbolNameLength) {\n      string_copy(buffer.get() + moduleNameLength,\n                  symbolNameBuffer.get(),\n                  bufferSize - moduleNameLength);\n   }\n\n   return symbolAddress;\n}\n\n\n/**\n * __tls_get_addr\n * Gets the TLS data for tls_index.\n */\nvirt_ptr<void>\ntls_get_addr(virt_ptr<tls_index> index)\n{\n   if (!sDynLoadData->tlsHeader) {\n      internal::COSError(COSReportModule::Unknown2,\n                         \"*** __OSDynLoad_gTLSHeader not initialized.\");\n      internal::OSPanic(\"OSDynLoad_Acquire.c\", 299,\n                        \"__OSDynLoad_gTLSHeader not initialized.\");\n   }\n\n   if (index->moduleIndex < 0) {\n      internal::COSError(COSReportModule::Unknown2,\n                         \"*** __OSDynLoad_gTLSHeader not initialized.\");\n      internal::OSPanic(\"OSDynLoad_Acquire.c\", 304,\n                        \"__OSDynLoad_gTLSHeader not initialized.\");\n   }\n\n   if (sDynLoadData->tlsHeader <= index->moduleIndex) {\n      internal::COSError(COSReportModule::Unknown2,\n                         \"*** __OSDynLoad_gTLSHeader not initialized.\");\n      internal::OSPanic(\"OSDynLoad_Acquire.c\", 309,\n                        \"__OSDynLoad_gTLSHeader not initialized.\");\n   }\n\n   auto thread = OSGetCurrentThread();\n   if (thread->tlsSectionCount <= index->moduleIndex) {\n      auto allocPtr = StackObject<virt_ptr<void>> { };\n\n      // Allocate new TLS section data\n      if (auto error = cafe::invoke(cpu::this_core::state(),\n                                    sDynLoadData->tlsAllocFn,\n                                    static_cast<uint32_t>(sizeof(OSTLSSection) * sDynLoadData->tlsHeader),\n                                    4,\n                                    allocPtr)) {\n         internal::COSError(COSReportModule::Unknown2,\n                            \"*** Couldn't allocate internal TLS framework blocks.\");\n         internal::OSPanic(\"OSDynLoad_Acquire.c\", 317, \"out of memory.\");\n      }\n\n      std::memset(allocPtr->get(),\n                  0,\n                  sizeof(OSTLSSection) * sDynLoadData->tlsHeader);\n\n      // Copy and free old TLS section data\n      if (thread->tlsSectionCount) {\n         std::memcpy(allocPtr->get(),\n                     thread->tlsSections.get(),\n                     sizeof(OSTLSSection) * sDynLoadData->tlsHeader);\n\n         cafe::invoke(cpu::this_core::state(),\n                      sDynLoadData->tlsFreeFn,\n                      thread->tlsSections);\n      }\n\n      thread->tlsSections = virt_cast<OSTLSSection *>(*allocPtr);\n      thread->tlsSectionCount = sDynLoadData->tlsHeader;\n   }\n\n   if (!thread->tlsSections[index->moduleIndex].data) {\n      // Find the module for this tls index\n      auto module = virt_ptr<RPL_DATA> { nullptr };\n      for (module = sDynLoadData->rplDataList; module; module = module->next) {\n         if (module->userFileInfo->tlsModuleIndex == static_cast<int16_t>(index->moduleIndex)) {\n            break;\n         }\n      }\n\n      if (!module ||\n          !(module->userFileInfo->fileInfoFlags & loader::rpl::RPL_HAS_TLS) ||\n          !module->userFileInfo->tlsAddressStart ||\n          !module->userFileInfo->tlsSectionSize) {\n         internal::COSError(COSReportModule::Unknown2,\n                            \"*** Can not find module for TLS data.\");\n         internal::OSPanic(\"OSDynLoad_Acquire.c\", 343,\n                           \"Can not find module for TLS data.\");\n      }\n\n      // Allocate tls image for this module\n      auto allocPtr = StackObject<virt_ptr<void>> { };\n      if (auto error = cafe::invoke(cpu::this_core::state(),\n                                    sDynLoadData->tlsAllocFn,\n                                    module->userFileInfo->tlsSectionSize,\n                                    1u << module->userFileInfo->tlsAlignShift,\n                                    allocPtr)) {\n         internal::COSError(COSReportModule::Unknown2,\n                            \"*** Couldn't allocate thread TLS image.\");\n         internal::OSPanic(\"OSDynLoad_Acquire.c\", 352, \"out of memory.\");\n      }\n\n      std::memcpy(allocPtr->get(),\n                  virt_cast<void *>(module->userFileInfo->tlsAddressStart).get(),\n                  module->userFileInfo->tlsSectionSize);\n      thread->tlsSections[index->moduleIndex].data = *allocPtr;\n   }\n\n   return\n      virt_cast<void *>(\n         virt_cast<virt_addr>(thread->tlsSections[index->moduleIndex].data)\n         + index->offset);\n}\n\nnamespace internal\n{\n\nvoid\ndynLoadTlsFree(virt_ptr<OSThread> thread)\n{\n   if (thread->tlsSectionCount) {\n      for (auto i = 0u; i < thread->tlsSectionCount; ++i) {\n         cafe::invoke(cpu::this_core::state(),\n                      sDynLoadData->tlsFreeFn,\n                      thread->tlsSections[i].data);\n      }\n\n      cafe::invoke(cpu::this_core::state(),\n                   sDynLoadData->tlsFreeFn,\n                   virt_cast<void *>(thread->tlsSections));\n   }\n}\n\nstatic void\ninitCoreinitNotifyData(virt_ptr<RPL_DATA> rpl)\n{\n   auto info0 = StackObject<kernel::Info0> { };\n   kernel::getInfo(kernel::InfoType::Type0, info0, sizeof(kernel::Info0));\n\n   rpl->notifyData = virt_cast<OSDynLoad_NotifyData *>(\n      rplSysHeapAlloc(\"RPL_NOTIFY\",\n                      sizeof(OSDynLoad_NotifyData), 4));\n   if (!rpl) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      COSError(COSReportModule::Unknown2,\n               fmt::format(\"sInitNotifyHdr() - mem alloc failed (err=0x{:08X}).\",\n                           OSDynLoad_Error::OutOfSysMemory));\n      setFatalErrorInfo2(rpl->userFileInfo->titleLocation,\n                         OSDynLoad_Error::OutOfSysMemory, 0,\n                         326, \"sInitNotifyHdr\");\n      reportFatalError();\n   }\n\n   std::memset(rpl->notifyData.get(), 0, sizeof(OSDynLoad_NotifyData));\n\n   // Steal the path string!\n   rpl->notifyData->name = rpl->userFileInfo->pathString;\n   rpl->userFileInfo->pathString = nullptr;\n\n   // Update section info\n   rpl->notifyData->textAddr = info0->coreinit.textAddr;\n   rpl->notifyData->textOffset = info0->coreinit.textOffset;\n   rpl->notifyData->textSize = info0->coreinit.textSize;\n\n   rpl->notifyData->dataAddr = info0->coreinit.dataAddr;\n   rpl->notifyData->dataOffset = info0->coreinit.dataOffset;\n   rpl->notifyData->dataSize = info0->coreinit.dataSize;\n\n   rpl->notifyData->readAddr = info0->coreinit.dataAddr;\n   rpl->notifyData->readOffset = info0->coreinit.dataOffset;\n   rpl->notifyData->readSize = info0->coreinit.dataSize;\n   // TODO: MasterAgent_LoadNotify\n}\n\n\n/**\n * __OSDynLoad_InitFromCoreInit\n */\nvirt_addr\ninitialiseDynLoad()\n{\n   resetFatalErrorInfo();\n   if (auto error = initialiseCommon()) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"OSDynLoad_InitCommon() failed with err 0x{:08X}\\n\", error));\n      reportFatalError();\n   }\n\n   // Allocate rpl data for coreinit\n   auto coreinitRplData = virt_cast<RPL_DATA *>(\n      rplSysHeapAlloc(\"RPL_DATA\", sizeof(RPL_DATA), 4));\n   if (!coreinitRplData) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InitFromCoreInit() - mem alloc failed (err=0x{:08X}).\",\n         OSDynLoad_Error::OutOfSysMemory));\n      setFatalErrorInfo2(0, OSDynLoad_Error::OutOfSysMemory, false,\n                         440, \"__OSDynLoad_InitFromCoreInit\");\n      reportFatalError();\n   }\n\n   std::memset(coreinitRplData.get(), 0, sizeof(RPL_DATA));\n\n   // Allocate handle for coreinit\n   if (auto handleError = OSHandle_Alloc(virt_addrof(sDynLoadData->handleTable),\n                                         coreinitRplData,\n                                         nullptr,\n                                         virt_addrof(coreinitRplData->handle))) {\n      decaf_abort(fmt::format(\"Unexpected OSHandle_Alloc error = {}\",\n                              handleError));\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InitFromCoreInit() - handle alloc failed (err=0x{:08X}.\", handleError));\n      setFatalErrorInfo2(0, handleError, 0, 452,\n                         \"__OSDynLoad_InitFromCoreInit\");\n      reportFatalError();\n   }\n\n   // Call setupPerm for coreinit\n   sDynLoadData->coreinitModuleName = \"coreinit\";\n   coreinitRplData->moduleName = virt_addrof(sDynLoadData->coreinitModuleName);\n   coreinitRplData->moduleNameLen = 8u;\n   coreinitRplData->loaderHandle = getCoreinitLoaderHandle();\n   coreinitRplData->next = sDynLoadData->rplDataList;\n   sDynLoadData->rplDataList = coreinitRplData;\n   setupPerm(coreinitRplData, false);\n\n   rplSysHeapFree(\"RPL_SEC_INFO\", coreinitRplData->sectionInfo,\n                  coreinitRplData->sectionInfoCount * sizeof(loader::LOADER_SectionInfo));\n   coreinitRplData->sectionInfo = nullptr;\n\n   sDynLoadData->tlsAllocLocked = FALSE;\n   initialiseDefaultHeap(coreinitRplData,\n                         make_stack_string(\"CoreInitDefaultHeap\"));\n\n   // Allocate rpx data\n   auto rpxData = virt_cast<RPL_DATA *>(\n      rplSysHeapAlloc(\"RPL_DATA\", sizeof(RPL_DATA), 4));\n   if (!rpxData) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InitFromCoreInit() - mem alloc failed (err=0x{:08X}).\",\n         OSDynLoad_Error::OutOfSysMemory));\n      setFatalErrorInfo2(0, OSDynLoad_Error::OutOfSysMemory, false,\n                         493, \"__OSDynLoad_InitFromCoreInit\");\n      reportFatalError();\n   }\n\n   std::memset(rpxData.get(), 0, sizeof(RPL_DATA));\n   sDynLoadData->rpxData = rpxData;\n\n   // Allocate module name\n   rpxData->handle = 0xFFFFFFFFu;\n   rpxData->moduleName = virt_cast<char *>(\n      rplSysHeapAlloc(\"RPL_NAME\", sDynLoadData->rpxNameLength + 1, 4));\n   rpxData->moduleNameLen = sDynLoadData->rpxNameLength;\n   if (!rpxData->moduleName) {\n      if (isAppDebugLevelNotice()) {\n         dumpSystemHeap();\n      }\n\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InitFromCoreInit() - mem alloc failed (err=0x{:08X}).\",\n         OSDynLoad_Error::OutOfSysMemory));\n      setFatalErrorInfo2(0, OSDynLoad_Error::OutOfSysMemory, false,\n                         514, \"__OSDynLoad_InitFromCoreInit\");\n      reportFatalError();\n   }\n\n   std::memcpy(rpxData->moduleName.get(),\n               sDynLoadData->rpxName.get(),\n               rpxData->moduleNameLen);\n   rpxData->moduleName[rpxData->moduleNameLen] = char { 0 };\n\n   // Call setupPerm for rpx\n   setupPerm(rpxData, true);\n\n   if (sDynLoadData->tlsModuleIndex) {\n      sDynLoadData->tlsHeader = sDynLoadData->tlsModuleIndex + 8;\n   }\n\n   sDynLoadData->linkingRplList = rpxData;\n   rpxData->entryPoint = virt_addr { 0u };\n   if (auto error = doImports(rpxData)) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InitFromCoreInit() - main program load failed (err=0x{:08X}).\",\n         error));\n      setFatalErrorInfo2(rpxData->userFileInfo->titleLocation, error, true,\n                         540, \"__OSDynLoad_InitFromCoreInit\");\n      reportFatalError();\n   }\n\n   auto numEntryModules = StackObject<uint32_t> { };\n   auto entryModules = StackObject<virt_ptr<virt_ptr<RPL_DATA>>> { };\n   if (auto error = executeDynamicLink(rpxData,\n                                       numEntryModules, entryModules,\n                                       rpxData)) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n         \"__OSDynLoad_InitFromCoreInit() - dynamic link of main program failed (err=0x{:08X}).\",\n         error));\n      setFatalErrorInfo2(rpxData->userFileInfo->titleLocation, error, true,\n                         549, \"__OSDynLoad_InitFromCoreInit\");\n      reportFatalError();\n   }\n\n   if (coreinitRplData->userFileInfo->pathString) {\n      initCoreinitNotifyData(coreinitRplData);\n   }\n\n   kernel::loaderUserGainControl();\n\n   // Notify debugger of entry points\n   auto preinitAddr = StackObject<virt_addr> { };\n   if (auto error = OSDynLoad_FindExport(rpxData->handle, FALSE,\n                                          make_stack_string(\"__preinit_user\"),\n                                          preinitAddr) != OSDynLoad_Error::OK) {\n      *preinitAddr = virt_addr { 0 };\n   }\n\n   decaf::debug::notifyEntry(static_cast<uint32_t>(*preinitAddr),\n                              static_cast<uint32_t>(rpxData->entryPoint));\n\n   // Run the preinit entry point\n   initialiseDefaultHeap(rpxData,\n                         make_stack_string(\"__preinit_user\"));\n\n   sDynLoadData->tlsAllocLocked = TRUE;\n\n   if (sDynLoadData->preInitMem1Heap) {\n      MEMSetBaseHeapHandle(MEMBaseHeapType::MEM1,\n                           sDynLoadData->preInitMem1Heap);\n   }\n\n   if (sDynLoadData->preInitForegroundHeap) {\n      MEMSetBaseHeapHandle(MEMBaseHeapType::FG,\n                           sDynLoadData->preInitForegroundHeap);\n   }\n\n   if (sDynLoadData->preInitMem2Heap) {\n      MEMSetBaseHeapHandle(MEMBaseHeapType::MEM2,\n                           sDynLoadData->preInitMem2Heap);\n   }\n\n   initialiseGhs();\n   if (auto error = runEntryPoints(*numEntryModules, *entryModules)) {\n      COSError(COSReportModule::Unknown2, fmt::format(\n               \"__OSDynLoad_InitFromCoreInit() - initialization functions of main program failed (err=0x{:08X}).\",\n               error));\n      reportFatalError();\n   }\n\n   if (sDynLoadData->modules) {\n      rplSysHeapFree(\"RPL_MODULES_ARRAY\", sDynLoadData->modules,\n                     sDynLoadData->numModules);\n   }\n\n   sDynLoadData->modules = nullptr;\n   sDynLoadData->numModules = 0u;\n\n   if (!rpxData->entryPoint) {\n      COSError(COSReportModule::Unknown2,\n               \"__OSDynLoad_InitFromCoreInit() - main program is NULL.\");\n      setFatalErrorInfo2(rpxData->userFileInfo->titleLocation,\n                         OSDynLoad_Error::InvalidRpxEntryPoint, true,\n                         643, \"__OSDynLoad_InitFromCoreInit\");\n      reportFatalError();\n   }\n\n   return rpxData->entryPoint;\n}\n\n\n/**\n * Relocate HLE variables to the loaded addresses for a given module.\n */\nOSDynLoad_Error\nrelocateHleLibrary(OSDynLoad_ModuleHandle moduleHandle)\n{\n   auto handleData = StackObject<virt_ptr<void>> { };\n   if (OSHandle_TranslateAndAddRef(virt_addrof(sDynLoadData->handleTable),\n                                   moduleHandle,\n                                   handleData,\n                                   nullptr)) {\n      return OSDynLoad_Error::InvalidHandle;\n   }\n\n   auto rplData = virt_cast<RPL_DATA *>(*handleData);\n   cafe::hle::relocateLibrary(\n      std::string_view { rplData->moduleName.get(),\n                         rplData->moduleNameLen },\n      rplData->notifyData->textAddr,\n      rplData->notifyData->dataAddr\n   );\n\n   return OSDynLoad_Error::OK;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerDynLoadSymbols()\n{\n   RegisterFunctionExport(OSDynLoad_AddNotifyCallback);\n   RegisterFunctionExport(OSDynLoad_DelNotifyCallback);\n   RegisterFunctionExportName(\"OSDynLoad_AddNofifyCallback\", // covfefe\n                              OSDynLoad_AddNotifyCallback);\n\n   RegisterFunctionExport(OSDynLoad_GetAllocator);\n   RegisterFunctionExport(OSDynLoad_SetAllocator);\n   RegisterFunctionExport(OSDynLoad_GetTLSAllocator);\n   RegisterFunctionExport(OSDynLoad_SetTLSAllocator);\n\n   RegisterFunctionExport(OSDynLoad_Acquire);\n   RegisterFunctionExport(OSDynLoad_AcquireContainingModule);\n   RegisterFunctionExport(OSDynLoad_FindExport);\n   RegisterFunctionExport(OSDynLoad_FindTag);\n   RegisterFunctionExport(OSDynLoad_GetLoaderHeapStatistics);\n   RegisterFunctionExport(OSDynLoad_GetModuleName);\n   RegisterFunctionExport(OSDynLoad_GetNumberOfRPLs);\n   RegisterFunctionExport(OSDynLoad_GetRPLInfo);\n   RegisterFunctionExport(OSDynLoad_IsModuleLoaded);\n   RegisterFunctionExport(OSDynLoad_Release);\n\n   RegisterDataExportName(\"OSDynLoad_gLoaderLock\", sDynLoad_LoaderLock);\n\n   RegisterFunctionExport(OSGetSymbolName);\n   RegisterFunctionExportName(\"__tls_get_addr\", tls_get_addr);\n\n   RegisterDataInternal(sDynLoadData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_dynload.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nstruct OSThread;\nstruct OSDynLoad_NotifyData;\n\nusing OSDynLoad_ModuleHandle = uint32_t;\nconstexpr OSDynLoad_ModuleHandle\nOSDynLoad_CurrentModuleHandle = OSDynLoad_ModuleHandle { 0xFFFFFFFF };\n\nusing OSDynLoad_AllocFn = virt_func_ptr<\n   OSDynLoad_Error(int32_t size,\n                   int32_t align,\n                   virt_ptr<virt_ptr<void>> out)>;\n\nusing OSDynLoad_FreeFn = virt_func_ptr<\n   void(virt_ptr<void> ptr)>;\n\nusing OSDynLoad_NotifyCallbackFn = virt_func_ptr<\n   void(OSDynLoad_ModuleHandle handle,\n        virt_ptr<void> userArg1,\n        OSDynLoad_NotifyEvent event,\n        virt_ptr<OSDynLoad_NotifyData>)>;\n\nstruct OSDynLoad_LoaderHeapStatistics;\n\nstruct OSDynLoad_NotifyData\n{\n   be2_virt_ptr<char> name;\n\n   be2_val<virt_addr> textAddr;\n   be2_val<uint32_t> textOffset;\n   be2_val<uint32_t> textSize;\n\n   be2_val<virt_addr> dataAddr;\n   be2_val<uint32_t> dataOffset;\n   be2_val<uint32_t> dataSize;\n\n   be2_val<virt_addr> readAddr;\n   be2_val<uint32_t> readOffset;\n   be2_val<uint32_t> readSize;\n};\nCHECK_OFFSET(OSDynLoad_NotifyData, 0x00, name);\nCHECK_OFFSET(OSDynLoad_NotifyData, 0x04, textAddr);\nCHECK_OFFSET(OSDynLoad_NotifyData, 0x08, textOffset);\nCHECK_OFFSET(OSDynLoad_NotifyData, 0x0C, textSize);\nCHECK_OFFSET(OSDynLoad_NotifyData, 0x10, dataAddr);\nCHECK_OFFSET(OSDynLoad_NotifyData, 0x14, dataOffset);\nCHECK_OFFSET(OSDynLoad_NotifyData, 0x18, dataSize);\nCHECK_OFFSET(OSDynLoad_NotifyData, 0x1C, readAddr);\nCHECK_OFFSET(OSDynLoad_NotifyData, 0x20, readOffset);\nCHECK_OFFSET(OSDynLoad_NotifyData, 0x24, readSize);\nCHECK_SIZE(OSDynLoad_NotifyData, 0x28);\n\nstruct OSDynLoad_NotifyCallback\n{\n   UNKNOWN(0x4);\n   be2_val<OSDynLoad_NotifyCallbackFn> notifyFn;\n   be2_virt_ptr<void> userArg1;\n   be2_virt_ptr<OSDynLoad_NotifyCallback> next;\n};\nCHECK_SIZE(OSDynLoad_NotifyCallback, 0x10);\n\nstruct tls_index\n{\n   be2_val<uint32_t> moduleIndex;\n   be2_val<uint32_t> offset;\n};\nCHECK_OFFSET(tls_index, 0x00, moduleIndex);\nCHECK_OFFSET(tls_index, 0x04, offset);\nCHECK_SIZE(tls_index, 0x08);\n\nOSDynLoad_Error\nOSDynLoad_AddNotifyCallback(OSDynLoad_NotifyCallbackFn notifyFn,\n                            virt_ptr<void> userArg1);\n\nvoid\nOSDynLoad_DelNotifyCallback(OSDynLoad_NotifyCallbackFn notifyFn,\n                            virt_ptr<void> userArg1);\n\nOSDynLoad_Error\nOSDynLoad_GetAllocator(virt_ptr<OSDynLoad_AllocFn> outAllocFn,\n                       virt_ptr<OSDynLoad_FreeFn> outFreeFn);\n\nOSDynLoad_Error\nOSDynLoad_SetAllocator(OSDynLoad_AllocFn allocFn,\n                       OSDynLoad_FreeFn freeFn);\n\nOSDynLoad_Error\nOSDynLoad_GetTLSAllocator(virt_ptr<OSDynLoad_AllocFn> outAllocFn,\n                          virt_ptr<OSDynLoad_FreeFn> outFreeFn);\n\nOSDynLoad_Error\nOSDynLoad_SetTLSAllocator(OSDynLoad_AllocFn allocFn,\n                          OSDynLoad_FreeFn freeFn);\n\nOSDynLoad_Error\nOSDynLoad_Acquire(virt_ptr<const char> modulePath,\n                  virt_ptr<OSDynLoad_ModuleHandle> outModuleHandle);\n\nOSDynLoad_Error\nOSDynLoad_AcquireContainingModule(virt_ptr<void> ptr,\n                                  OSDynLoad_SectionType sectionType,\n                                  virt_ptr<OSDynLoad_ModuleHandle> outHandle);\n\nOSDynLoad_Error\nOSDynLoad_FindExport(OSDynLoad_ModuleHandle moduleHandle,\n                     BOOL isData,\n                     virt_ptr<const char> name,\n                     virt_ptr<virt_addr> outAddr);\n\nOSDynLoad_Error\nOSDynLoad_FindTag(OSDynLoad_ModuleHandle moduleHandle,\n                  virt_ptr<const char> tag,\n                  virt_ptr<char> buffer,\n                  virt_ptr<uint32_t> inoutBufferSize);\n\nOSDynLoad_Error\nOSDynLoad_GetLoaderHeapStatistics(virt_ptr<OSDynLoad_LoaderHeapStatistics> stats);\n\nOSDynLoad_Error\nOSDynLoad_GetModuleName(OSDynLoad_ModuleHandle moduleHandle,\n                        virt_ptr<char> buffer,\n                        virt_ptr<uint32_t> inoutBufferSize);\n\nuint32_t\nOSDynLoad_GetNumberOfRPLs();\n\nuint32_t\nOSDynLoad_GetRPLInfo(uint32_t first,\n                     uint32_t count,\n                     virt_ptr<OSDynLoad_NotifyData> outRplInfos);\n\nOSDynLoad_Error\nOSDynLoad_IsModuleLoaded(virt_ptr<const char> name,\n                         virt_ptr<OSDynLoad_ModuleHandle> outHandle);\n\nvoid\nOSDynLoad_Release(OSDynLoad_ModuleHandle moduleHandle);\n\nvirt_addr\nOSGetSymbolName(virt_addr address,\n                virt_ptr<char> buffer,\n                uint32_t bufferSize);\n\nvirt_ptr<void>\ntls_get_addr(virt_ptr<tls_index> index);\n\nnamespace internal\n{\n\nvoid\ndynLoadTlsFree(virt_ptr<OSThread> thread);\n\nvirt_addr\ninitialiseDynLoad();\n\nOSDynLoad_Error\nrelocateHleLibrary(OSDynLoad_ModuleHandle moduleHandle);\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_enum.h",
    "content": "#ifndef COREINIT_ENUM_H\n#define COREINIT_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(coreinit)\n\nENUM_BEG(BSPError, uint32_t)\n   ENUM_VALUE(OK,                0)\n   ENUM_VALUE(IosError,          0x40)\n   ENUM_VALUE(ResponseTooLarge,  0x80)\nENUM_END(BSPError)\n\nENUM_BEG(COSReportLevel, uint32_t)\n   ENUM_VALUE(Error,             0)\n   ENUM_VALUE(Warn,              1)\n   ENUM_VALUE(Info,              2)\n   ENUM_VALUE(Verbose,           3)\nENUM_END(COSReportLevel)\n\nENUM_BEG(COSReportModule, uint32_t)\n   ENUM_VALUE(Unknown0,          0)\n   ENUM_VALUE(Unknown1,          1)\n   ENUM_VALUE(Unknown2,          2)\n   ENUM_VALUE(Unknown5,          5)\nENUM_END(COSReportModule)\n\nENUM_BEG(IPCDriverStatus, uint32_t)\n   ENUM_VALUE(Closed,               1)\n   ENUM_VALUE(Initialised,          2)\n   ENUM_VALUE(Open,                 3)\nENUM_END(IPCDriverStatus)\n\nENUM_BEG(OSAlarmState, uint32_t)\n   ENUM_VALUE(Idle,                 0)\n   ENUM_VALUE(Set,                  1)\n   ENUM_VALUE(Expired,              2)\n   ENUM_VALUE(Invalid,              0x79646CFF)\nENUM_END(OSAlarmState)\n\nENUM_BEG(OSDeviceID, uint32_t)\n   ENUM_VALUE(VI,                   0)\n   ENUM_VALUE(DSP,                  1)\n   ENUM_VALUE(GFXSP,                2)\n   ENUM_VALUE(SI,                   6)\n   ENUM_VALUE(LATTE_REGS,           11)\n   ENUM_VALUE(LEGACY_SI,            12)\n   ENUM_VALUE(LEGACY_AI_I2S3,       13)\n   ENUM_VALUE(LEGACY_AI_I2S5,       14)\n   ENUM_VALUE(LEGACY_EXI,           15)\nENUM_END(OSDeviceID)\n\nENUM_BEG(OSDriver_Error, uint32_t)\n   ENUM_VALUE(OK,                   0)\n   ENUM_VALUE(DriverNotFound,       0xBAD70002)\n   ENUM_VALUE(InvalidArgument,      0xBAD70003)\n   ENUM_VALUE(OutOfSysMemory,       0xBAD70004)\n   ENUM_VALUE(AlreadyRegistered,    0xBAD70005)\nENUM_END(OSDriver_Error)\n\nENUM_BEG(OSDynLoad_Error, uint32_t)\n   ENUM_VALUE(OK,                      0)\n   ENUM_VALUE(OutOfMemory,             0xBAD10002)\n   ENUM_VALUE(InvalidHandle,           0xBAD10003)\n   ENUM_VALUE(InvalidRPL,              0xBAD1000C)\n   ENUM_VALUE(InvalidNotifyPtr,        0xBAD1000E)\n   ENUM_VALUE(InvalidModuleNamePtr,    0xBAD1000F)\n   ENUM_VALUE(InvalidModuleName,       0xBAD10010)\n   ENUM_VALUE(InvalidAcquirePtr,       0xBAD10011)\n   ENUM_VALUE(EmptyModuleName,         0xBAD10012)\n   ENUM_VALUE(InvalidAllocatedPtr,     0xBAD10014)\n   ENUM_VALUE(InModuleEntryPoint,      0xBAD10016)\n   ENUM_VALUE(InvalidAllocatorPtr,     0xBAD10017)\n   ENUM_VALUE(ModuleHasNoDataExports,  0xBAD1001B)\n   ENUM_VALUE(ModuleHasNoCodeExports,  0xBAD1001C)\n   ENUM_VALUE(ExportNotFound,          0xBAD1001D)\n   ENUM_VALUE(InvalidParam,            0xBAD1001E)\n   ENUM_VALUE(BufferTooSmall,          0xBAD1001F)\n   ENUM_VALUE(LoaderError,             0xBAD10021)\n   ENUM_VALUE(ModuleNotFound,          0xBAD10023)\n   ENUM_VALUE(InvalidContainPtr,       0xBAD10027)\n   ENUM_VALUE(ContainModuleNotFound,   0xBAD10028)\n   ENUM_VALUE(RunEntryPointError,      0xBAD10029)\n   ENUM_VALUE(OutOfSysMemory,          0xBAD1002F)\n   ENUM_VALUE(TLSAllocatorLocked,      0xBAD10031)\n   ENUM_VALUE(TLSTooManyModules,       0xBAD10032)\n   ENUM_VALUE(TLSFindExportInvalid,    0xBAD10033)\n   ENUM_VALUE(InvalidShStrNdx,         0xBAD1003A)\n   ENUM_VALUE(InvalidShStrSection,     0xBAD1003B)\n   ENUM_VALUE(TLSSectionNotFound,      0xBAD1003D)\n   ENUM_VALUE(InvalidRpxEntryPoint,    0xBAD1003E)\nENUM_END(OSDynLoad_Error)\n\nENUM_BEG(OSDynLoad_EntryReason, uint32_t)\n   ENUM_VALUE(Loaded,               1)\n   ENUM_VALUE(Unloaded,             2)\nENUM_END(OSDynLoad_EntryReason)\n\nENUM_BEG(OSDynLoad_NotifyEvent, uint32_t)\n   ENUM_VALUE(Unloaded,             0)\n   ENUM_VALUE(Loaded,               1)\nENUM_END(OSDynLoad_NotifyEvent)\n\nENUM_BEG(OSDynLoad_SectionType, uint32_t)\n   ENUM_VALUE(Any,                  0)\n   ENUM_VALUE(CodeOnly,             1)\n   ENUM_VALUE(DataOnly,             2)\nENUM_END(OSDynLoad_SectionType)\n\nENUM_BEG(OSEventMode, uint32_t)\n   //! A manual event will only reset when OSResetEvent is called.\n   ENUM_VALUE(ManualReset,          0)\n\n   //! An auto event will reset everytime a thread is woken.\n   ENUM_VALUE(AutoReset,            1)\nENUM_END(OSEventMode)\n\nENUM_BEG(OSExceptionMode, uint32_t)\n   ENUM_VALUE(System,               0)\n   ENUM_VALUE(Thread,               1)\n   ENUM_VALUE(Global,               2)\n   ENUM_VALUE(ThreadAllCores,       3)\n   ENUM_VALUE(GlobalAllCores,       4)\nENUM_END(OSExceptionMode)\n\nENUM_BEG(OSExceptionType, uint32_t)\n   ENUM_VALUE(SystemReset,          0)\n   ENUM_VALUE(MachineCheck,         1)\n   ENUM_VALUE(DSI,                  2)\n   ENUM_VALUE(ISI,                  3)\n   ENUM_VALUE(ExternalInterrupt,    4)\n   ENUM_VALUE(Alignment,            5)\n   ENUM_VALUE(Program,              6)\n   ENUM_VALUE(FloatingPoint,        7)\n   ENUM_VALUE(Decrementer,          8)\n   ENUM_VALUE(SystemCall,           9)\n   ENUM_VALUE(Trace,                10)\n   ENUM_VALUE(PerformanceMonitor,   11)\n   ENUM_VALUE(Breakpoint,           12)\n   ENUM_VALUE(SystemInterrupt,      13)\n   ENUM_VALUE(ICI,                  14)\n   ENUM_VALUE(Max,                  15)\nENUM_END(OSExceptionType)\n\nENUM_BEG(OSFunctionType, uint32_t)\n   ENUM_VALUE(HioOpen,              1)\n   ENUM_VALUE(HioReadAsync,         2)\n   ENUM_VALUE(HioWriteAsync,        3)\n   ENUM_VALUE(FsaCmdAsync,          4)\n   ENUM_VALUE(FsaPrCmdAsync,        5)\n   ENUM_VALUE(FsaPrCmdAsyncNoAlloc, 6)\n   ENUM_VALUE(FsaAttachEvent,       7)\n   ENUM_VALUE(FsCmdAsync,           8)\n   ENUM_VALUE(FsCmdHandler,         9)\n   ENUM_VALUE(FsAttachEvent,        10)\n   ENUM_VALUE(FsStateChangeEvent,   11)\nENUM_END(OSFunctionType)\n\nENUM_BEG(OSHandleError, uint32_t)\n   ENUM_VALUE(OK,       0)\n   ENUM_VALUE(InvalidArgument,      0xBAD50002)\n   ENUM_VALUE(InvalidHandle,        0xBAD50004)\n   ENUM_VALUE(TableFull,            0xBAD50005)\n   ENUM_VALUE(InternalError,        0xBAD50006)\nENUM_END(OSHandleError)\n\nENUM_BEG(OSMapMemoryPermission, uint32_t)\n   ENUM_VALUE(ReadOnly,             0x1)\n   ENUM_VALUE(ReadWrite,            0x2)\nENUM_END(OSMapMemoryPermission)\n\nENUM_BEG(OSMemoryType, uint32_t)\n   ENUM_VALUE(MEM1,                 1)\n   ENUM_VALUE(MEM2,                 2)\nENUM_END(OSMemoryType)\n\nFLAGS_BEG(OSMessageFlags, uint32_t)\n   FLAGS_VALUE(None,                0)\n   FLAGS_VALUE(Blocking,            1 << 0)\n   FLAGS_VALUE(HighPriority,        1 << 1)\nFLAGS_END(OSMessageFlags)\n\nENUM_BEG(OSSharedDataType, uint32_t)\n   ENUM_VALUE(FontChinese,          0)\n   ENUM_VALUE(FontKorean,           1)\n   ENUM_VALUE(FontStandard,         2)\n   ENUM_VALUE(FontTaiwanese,        3)\n   ENUM_VALUE(Max,                  4)\nENUM_END(OSSharedDataType)\n\nENUM_BEG(OSShutdownReason, uint32_t)\n   ENUM_VALUE(NoShutdown,           0)\nENUM_END(OSShutdownReason)\n\nFLAGS_BEG(OSThreadAttributes, uint8_t)\n   //! Allow the thread to run on CPU0.\n   FLAGS_VALUE(AffinityCPU0,        1 << 0)\n\n   //! Allow the thread to run on CPU1.\n   FLAGS_VALUE(AffinityCPU1,        1 << 1)\n\n   //! Allow the thread to run on CPU2.\n   FLAGS_VALUE(AffinityCPU2,        1 << 2)\n\n   //! Allow the thread to run any CPU.\n   FLAGS_VALUE(AffinityAny,         (1 << 0) | (1 << 1) | (1 << 2))\n\n   //! Start the thread detached.\n   FLAGS_VALUE(Detached,            1 << 3)\n\n   //! Enables tracking of stack usage.\n   FLAGS_VALUE(StackUsage,          1 << 5)\nFLAGS_END(OSThreadAttributes)\n\nFLAGS_BEG(OSThreadCancelState, uint32_t)\n   //! Thread cancel is enabled\n   FLAGS_VALUE(Enabled,                      0)\n\n   //! Thread cancel is disabled by OSSetThreadCancelState\n   FLAGS_VALUE(Disabled,                     1 << 0)\n\n   //! Thread cancel is disabled because the thread owns a mutex\n   FLAGS_VALUE(DisabledByMutex,              1 << 16)\n\n   //! Thread cancel is disabled because the thread owns an uninterruptible spinlock\n   FLAGS_VALUE(DisabledBySpinlock,           1 << 17)\n\n   //! Thread cancel is disabled because the thread has a user stack pointer\n   FLAGS_VALUE(DisabledByUserStackPointer,   1 << 18)\n\n   //! Thread cancel is disabled because the thread owns a fast mutex\n   FLAGS_VALUE(DisabledByFastMutex,          1 << 19)\nFLAGS_END(OSThreadCancelState)\n\nENUM_BEG(OSThreadQuantum, uint32_t)\n   ENUM_VALUE(Infinite,             0)\n   ENUM_VALUE(MinMicroseconds,      10)\n   ENUM_VALUE(MaxMicroseconds,      0xFFFFF)\nENUM_END(OSThreadQuantum)\n\nENUM_BEG(OSThreadRequest, uint32_t)\n   ENUM_VALUE(None,                 0)\n   ENUM_VALUE(Suspend,              1)\n   ENUM_VALUE(Cancel,               2)\nENUM_END(OSThreadRequest)\n\nFLAGS_BEG(OSThreadState, uint8_t)\n   FLAGS_VALUE(None,                0)\n\n   //! Thread is ready to run\n   FLAGS_VALUE(Ready,               1 << 0)\n\n   //! Thread is running\n   FLAGS_VALUE(Running,             1 << 1)\n\n   //! Thread is waiting, i.e. on a mutex\n   FLAGS_VALUE(Waiting,             1 << 2)\n\n   //! Thread is about to terminate\n   FLAGS_VALUE(Moribund,            1 << 3)\nFLAGS_END(OSThreadState)\n\nENUM_BEG(OSThreadType, uint32_t)\n   ENUM_VALUE(Driver,               0)\n   ENUM_VALUE(AppIo,                1)\n   ENUM_VALUE(App,                  2)\nENUM_END(OSThreadType)\n\nENUM_BEG(FSAClientState, uint32_t)\n   ENUM_VALUE(Free,                 0)\n   ENUM_VALUE(Allocated,            1)\n   ENUM_VALUE(Closing,              2)\nENUM_END(FSAClientState)\n\nENUM_BEG(FSAIpcRequestType, uint16_t)\n   ENUM_VALUE(Ioctl,                   0)\n   ENUM_VALUE(Ioctlv,                  1)\nENUM_END(FSAIpcRequestType)\n\nENUM_BEG(FSCmdBlockStatus, uint32_t)\n   ENUM_VALUE(Initialised,             0xD900A21)\n   ENUM_VALUE(QueuedCommand,           0xD900A22)\n   ENUM_VALUE(DeqeuedCommand,          0xD900A23)\n   ENUM_VALUE(Cancelled,               0xD900A24)\n   ENUM_VALUE(Completed,               0xD900A26)\nENUM_END(FSCmdBlockStatus)\n\nENUM_BEG(FSCmdCancelFlags, uint32_t)\n   ENUM_VALUE(None,                    0)\n   ENUM_VALUE(Cancelling,              1)\nENUM_END(FSCmdCancelFlags)\n\nFLAGS_BEG(FSCmdQueueStatus, uint32_t)\n   FLAGS_VALUE(MaxActiveCommands,       1)\n   FLAGS_VALUE(Suspended,               1 << 4)\nFLAGS_END(FSCmdQueueStatus)\n\nFLAGS_BEG(FSErrorFlag, uint32_t)\n   FLAGS_VALUE(None,                    0x0)\n   FLAGS_VALUE(Max,                     0x1)\n   FLAGS_VALUE(AlreadyOpen,             0x2)\n   FLAGS_VALUE(Exists,                  0x4)\n   FLAGS_VALUE(NotFound,                0x8)\n   FLAGS_VALUE(NotFile,                 0x10)\n   FLAGS_VALUE(NotDir,                  0x20)\n   FLAGS_VALUE(AccessError,             0x40)\n   FLAGS_VALUE(PermissionError,         0x80)\n   FLAGS_VALUE(FileTooBig,              0x100)\n   FLAGS_VALUE(StorageFull,             0x200)\n   FLAGS_VALUE(UnsupportedCmd,          0x400)\n   FLAGS_VALUE(JournalFull,             0x800)\n   FLAGS_VALUE(All,                     0xFFFFFFFF)\nFLAGS_END(FSErrorFlag)\n\nENUM_BEG(FSMountSourceType, uint32_t)\n   ENUM_VALUE(SdCard,                  0)\n   ENUM_VALUE(HostFileIO,              1)\n   ENUM_VALUE(Bind,                    2)\nENUM_END(FSMountSourceType)\n\nENUM_BEG(FSStatus, int32_t)\n   ENUM_VALUE(OK,                   0)\n   ENUM_VALUE(Cancelled,            -1)\n   ENUM_VALUE(End,                  -2)\n   ENUM_VALUE(Max,                  -3)\n   ENUM_VALUE(AlreadyOpen,          -4)\n   ENUM_VALUE(Exists,               -5)\n   ENUM_VALUE(NotFound,             -6)\n   ENUM_VALUE(NotFile,              -7)\n   ENUM_VALUE(NotDirectory,         -8)\n   ENUM_VALUE(AccessError,          -9)\n   ENUM_VALUE(PermissionError,      -10)\n   ENUM_VALUE(FileTooBig,           -11)\n   ENUM_VALUE(StorageFull,          -12)\n   ENUM_VALUE(JournalFull,          -13)\n   ENUM_VALUE(UnsupportedCmd,       -14)\n   ENUM_VALUE(MediaNotReady,        -15)\n   ENUM_VALUE(MediaError,           -17)\n   ENUM_VALUE(Corrupted,            -18)\n   ENUM_VALUE(FatalError,           -0x400)\nENUM_END(FSStatus)\n\nENUM_BEG(FSVolumeState, uint32_t)\n   ENUM_VALUE(Initial,              0)\n   ENUM_VALUE(Ready,                1)\n   ENUM_VALUE(NoMedia,              2)\n   ENUM_VALUE(InvalidMedia,         3)\n   ENUM_VALUE(DirtyMedia,           4)\n   ENUM_VALUE(WrongMedia,           5)\n   ENUM_VALUE(MediaError,           6)\n   ENUM_VALUE(DataCorrupted,        7)\n   ENUM_VALUE(WriteProtected,       8)\n   ENUM_VALUE(JournalFull,          9)\n   ENUM_VALUE(Fatal,                10)\n   ENUM_VALUE(Invalid,              11)\nENUM_END(FSVolumeState)\n\nENUM_BEG(GHSSyscallID, uint32_t)\n   ENUM_VALUE(time,                 0x0000E)\n   ENUM_VALUE(close,                0x20005)\n   ENUM_VALUE(unlink,               0x20008)\n   ENUM_VALUE(system,               0x2000A)\n   ENUM_VALUE(creat,                0x30006)\n   ENUM_VALUE(access,               0x3000B)\n   ENUM_VALUE(read,                 0x40000)\n   ENUM_VALUE(write,                0x40001)\n   ENUM_VALUE(open,                 0x40004)\n   ENUM_VALUE(lseek,                0x40007)\n   ENUM_VALUE(fnctl,                0x40012)\n   ENUM_VALUE(fstat,                0x40018)\n   ENUM_VALUE(stat,                 0x40019)\nENUM_END(GHSSyscallID)\n\nENUM_BEG(MEMBaseHeapType, uint32_t)\n   ENUM_VALUE(MEM1,                 0)\n   ENUM_VALUE(MEM2,                 1)\n   ENUM_VALUE(FG,                   8)\n   ENUM_VALUE(Max,                  9)\n   ENUM_VALUE(Invalid,              10)\nENUM_END(MEMBaseHeapType)\n\nENUM_BEG(MEMExpHeapMode, uint32_t)\n   ENUM_VALUE(FirstFree,            0)\n   ENUM_VALUE(NearestSize,          1)\nENUM_END(MEMExpHeapMode)\n\nENUM_BEG(MEMExpHeapDirection, uint32_t)\n   ENUM_VALUE(FromStart,            0)\n   ENUM_VALUE(FromEnd,              1)\nENUM_END(MEMExpHeapDirection)\n\nFLAGS_BEG(MEMFrameHeapFreeMode, uint32_t)\n   FLAGS_VALUE(Head,                1 << 0)\n   FLAGS_VALUE(Tail,                1 << 1)\nFLAGS_END(MEMFrameHeapFreeMode)\n\nENUM_BEG(MEMHeapTag, uint32_t)\n   ENUM_VALUE(ExpandedHeap,         0x45585048)\n   ENUM_VALUE(FrameHeap,            0x46524D48)\n   ENUM_VALUE(UnitHeap,             0x554E5448)\n   ENUM_VALUE(UserHeap,             0x55535248)\n   ENUM_VALUE(BlockHeap,            0x424C4B48)\nENUM_END(MEMHeapTag)\n\nENUM_BEG(MEMHeapFillType, uint32_t)\n   ENUM_VALUE(Unused,               0x0)\n   ENUM_VALUE(Allocated,            0x1)\n   ENUM_VALUE(Freed,                0x2)\n   ENUM_VALUE(Max,                  0x3)\nENUM_END(MEMHeapFillType)\n\nFLAGS_BEG(MEMHeapFlags, uint32_t)\n   FLAGS_VALUE(None,                0)\n   FLAGS_VALUE(ZeroAllocated,       1 << 0)\n   FLAGS_VALUE(DebugMode,           1 << 1)\n   FLAGS_VALUE(ThreadSafe,          1 << 2)\nFLAGS_END(MEMHeapFlags)\n\nFLAGS_BEG(MPTaskQueueState, uint32_t)\n   FLAGS_VALUE(Initialised,         1 << 0)\n   FLAGS_VALUE(Ready,               1 << 1)\n   FLAGS_VALUE(Stopping,            1 << 2)\n   FLAGS_VALUE(Stopped,             1 << 3)\n   FLAGS_VALUE(Finished,            1 << 4)\nFLAGS_END(MPTaskQueueState)\n\nFLAGS_BEG(MPTaskState, uint32_t)\n   FLAGS_VALUE(Initialised,         1 << 0)\n   FLAGS_VALUE(Ready,               1 << 1)\n   FLAGS_VALUE(Running,             1 << 2)\n   FLAGS_VALUE(Finished,            1 << 3)\nFLAGS_END(MPTaskState)\n\nENUM_BEG(SIRegisters, uint32_t)\n   ENUM_VALUE(Controller0Command,   0)\n   ENUM_VALUE(Controller0Status0,   1)\n   ENUM_VALUE(Controller0Status1,   2)\n   ENUM_VALUE(Controller1Command,   3)\n   ENUM_VALUE(Controller1Status0,   4)\n   ENUM_VALUE(Controller1Status1,   5)\n   ENUM_VALUE(Controller2Command,   6)\n   ENUM_VALUE(Controller2Status0,   7)\n   ENUM_VALUE(Controller2Status1,   8)\n   ENUM_VALUE(Controller3Command,   9)\n   ENUM_VALUE(Controller3Status0,   10)\n   ENUM_VALUE(Controller3Status1,   11)\n   ENUM_VALUE(PollControl,          12)\n   ENUM_VALUE(DeviceStatus,         13)\n   ENUM_VALUE(ControllerError,      14)\nENUM_END(SIRegisters)\n\nENUM_NAMESPACE_EXIT(coreinit)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef COREINIT_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_enum_string.cpp",
    "content": "#include \"coreinit_enum_string.h\"\n\n#undef COREINIT_ENUM_H\n#include <common/enum_string_define.inl>\n#include \"coreinit_enum.h\"\n#include <common/enum_end.inl>\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_enum_string.h",
    "content": "#pragma once\n#include <cstdint>\n#include <string>\n#include \"coreinit_enum.h\"\n\n#undef COREINIT_ENUM_H\n#include <common/enum_string_declare.inl>\n#include \"coreinit_enum.h\"\n#include <common/enum_end.inl>\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_event.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_alarm.h\"\n#include \"coreinit_event.h\"\n#include \"coreinit_memheap.h\"\n#include \"coreinit_scheduler.h\"\n#include \"coreinit_time.h\"\n#include \"cafe/cafe_stackobject.h\"\n\nnamespace cafe::coreinit\n{\n\nstatic AlarmCallbackFn\nsEventAlarmHandler;\n\n\n/**\n * Initialises an event structure.\n */\nvoid\nOSInitEvent(virt_ptr<OSEvent> event,\n            BOOL value,\n            OSEventMode mode)\n{\n   OSInitEventEx(event, value, mode, nullptr);\n}\n\n\n/**\n * Initialises an event structure.\n */\nvoid\nOSInitEventEx(virt_ptr<OSEvent> event,\n              BOOL value,\n              OSEventMode mode,\n              virt_ptr<const char> name)\n{\n   decaf_check(event);\n   event->tag = OSEvent::Tag;\n   event->mode = mode;\n   event->value = value;\n   event->name = name;\n   OSInitThreadQueueEx(virt_addrof(event->queue), event);\n}\n\n\n/**\n * Signal an event.\n *\n * This will set the events signal value to true.\n *\n * In auto reset mode:\n * - Will wake up the first thread possible to wake\n * - If a thread is woken, the event value is reset to FALSE\n *\n * In manual reset mode:\n * - Wakes up all possible threads in the waiting queue\n * - The event value remains TRUE until the user calls OSResetEvent\n */\nvoid\nOSSignalEvent(virt_ptr<OSEvent> event)\n{\n   internal::lockScheduler();\n   decaf_check(event);\n   decaf_check(event->tag == OSEvent::Tag);\n\n   if (event->value) {\n      // Event has already been set\n      internal::unlockScheduler();\n      return;\n   }\n\n   // Set the event\n   event->value = TRUE;\n\n   if (event->mode == OSEventMode::AutoReset) {\n      if (!internal::ThreadQueue::empty(virt_addrof(event->queue))) {\n         auto wakeThread = virt_ptr<OSThread> { nullptr };\n\n         // Find the first thread that we can wake\n         for (auto thread = event->queue.head; thread; thread = thread->link.next) {\n            decaf_check(thread->queue == virt_addrof(event->queue));\n\n            if (thread->waitEventTimeoutAlarm) {\n               if (!internal::cancelAlarm(thread->waitEventTimeoutAlarm)) {\n                  // If we could not cancel the alarm, do not wake this thread\n                  continue;\n               }\n            }\n\n            wakeThread = thread;\n            break;\n         }\n\n         if (wakeThread) {\n            // Reset the event\n            event->value = FALSE;\n            internal::wakeupOneThreadNoLock(wakeThread);\n         }\n\n         internal::rescheduleAllCoreNoLock();\n      }\n   } else {\n      // Wake all possible threads\n      for (auto thread = event->queue.head; thread; ) {\n         decaf_check(thread->queue == virt_addrof(event->queue));\n\n         // Save thread->link.next as it will be reset by wakeupOneThreadNoLock\n         auto next = thread->link.next;\n\n         if (thread->waitEventTimeoutAlarm) {\n            if (!internal::cancelAlarm(thread->waitEventTimeoutAlarm)) {\n               // If we could not cancel the alarm, do not wake this thread\n               thread = next;\n               continue;\n            }\n         }\n\n         internal::wakeupOneThreadNoLock(thread);\n         thread = next;\n      }\n\n      internal::rescheduleAllCoreNoLock();\n   }\n\n   internal::unlockScheduler();\n}\n\n\n/**\n * Signal the event and wakeup all waiting threads.\n *\n * In manual reset mode:\n * - Wakes up all possible threads in the waiting queue\n * - The event value will always be set to TRUE.\n *\n * In auto reset mode:\n * - Wakes up all possible threads in the waiting queue\n * - The event value will only be set to TRUE if no threads are woken.\n */\nvoid\nOSSignalEventAll(virt_ptr<OSEvent> event)\n{\n   internal::lockScheduler();\n   decaf_check(event);\n   decaf_check(event->tag == OSEvent::Tag);\n\n   if (event->value) {\n      // Event has already been set\n      internal::unlockScheduler();\n      return;\n   }\n\n   // Manual reset always sets the event value to TRUE\n   if (event->mode == OSEventMode::ManualReset) {\n      event->value = TRUE;\n   }\n\n   if (!internal::ThreadQueue::empty(virt_addrof(event->queue))) {\n      auto threadsWoken = 0u;\n\n      // Wake any waiting threads\n      for (auto thread = event->queue.head; thread; thread = thread->link.next) {\n         decaf_check(thread->queue == virt_addrof(event->queue));\n\n         if (thread->waitEventTimeoutAlarm) {\n            if (!internal::cancelAlarm(thread->waitEventTimeoutAlarm)) {\n               // If we could not cancel the alarm, do not wake this thread\n               continue;\n            }\n         }\n\n         internal::wakeupOneThreadNoLock(thread);\n         threadsWoken++;\n      }\n\n      // Auto reset will only set the event value if no threads were woken\n      if (event->mode == OSEventMode::AutoReset && threadsWoken == 0) {\n         event->value = TRUE;\n      }\n\n      internal::rescheduleAllCoreNoLock();\n   }\n\n   internal::unlockScheduler();\n}\n\n\n/**\n * Reset the event value to FALSE\n */\nvoid\nOSResetEvent(virt_ptr<OSEvent> event)\n{\n   internal::lockScheduler();\n   decaf_check(event);\n   decaf_check(event->tag == OSEvent::Tag);\n\n   // Reset event\n   event->value = FALSE;\n\n   internal::unlockScheduler();\n}\n\n\n/**\n * Wait for the event value to become TRUE.\n *\n * If the event value is already TRUE:\n * - Returns immediately, no wait is performed.\n * - Sets event value to FALSE if in AutoReset mode.\n *\n * If the event value is FALSE:\n * - The current thread will go to sleep until the event is signalled by another thread.\n */\nvoid\nOSWaitEvent(virt_ptr<OSEvent> event)\n{\n   internal::lockScheduler();\n   decaf_check(event);\n\n   // HACK: Naughty Bayonetta not initialising event before using it.\n   // decaf_check(event->tag == OSEvent::Tag);\n   if (event->tag != OSEvent::Tag) {\n      OSInitEvent(event, false, OSEventMode::ManualReset);\n   }\n\n   // Check if the event is already set\n   if (event->value) {\n      if (event->mode == OSEventMode::AutoReset) {\n         // Reset event\n         event->value = FALSE;\n      }\n   } else {\n      // Wait for event to be set\n      internal::sleepThreadNoLock(virt_addrof(event->queue));\n      internal::rescheduleSelfNoLock();\n   }\n\n   internal::unlockScheduler();\n}\n\nstruct EventAlarmData\n{\n   virt_ptr<OSEvent> event;\n   virt_ptr<OSThread> thread;\n   BOOL timeout;\n};\n\nstatic void\nEventAlarmHandler(virt_ptr<OSAlarm> alarm,\n                  virt_ptr<OSContext> context)\n{\n   // Wakeup the thread waiting on this alarm\n   auto data = virt_cast<EventAlarmData *>(OSGetAlarmUserData(alarm));\n   data->timeout = TRUE;\n\n   // Remove this alarm from the thread\n   data->thread->waitEventTimeoutAlarm = nullptr;\n\n   // System Alarm, we already have the scheduler lock\n   internal::wakeupOneThreadNoLock(data->thread);\n}\n\n\n/**\n * Wait for an event value to be TRUE with a timeout\n *\n * Behaves the same than OSWaitEvent but with a timeout.\n *\n * Returns TRUE if the event was signalled, FALSE if wait timed out.\n */\nBOOL\nOSWaitEventWithTimeout(virt_ptr<OSEvent> event,\n                       OSTimeNanoseconds timeout)\n{\n   auto data = StackObject<EventAlarmData> { };\n   auto alarm = StackObject<OSAlarm> { };\n\n   internal::lockScheduler();\n\n   // Check if event is already set\n   if (event->value) {\n      if (event->mode == OSEventMode::AutoReset) {\n         // Reset event\n         event->value = FALSE;\n      }\n\n      internal::unlockScheduler();\n      return TRUE;\n   }\n\n   // Setup some alarm data for callback\n   auto thread = OSGetCurrentThread();\n   data->event = event;\n   data->thread = thread;\n   data->timeout = FALSE;\n\n   // Create an alarm to trigger timeout\n   auto timeoutTicks = internal::nsToTicks(timeout);\n   OSCreateAlarm(alarm);\n   internal::setAlarmInternal(alarm, timeoutTicks, sEventAlarmHandler, data);\n\n   // Set waitEventTimeoutAlarm so we can cancel it when event is signalled\n   thread->waitEventTimeoutAlarm = alarm;\n\n   // Wait for the event\n   internal::sleepThreadNoLock(virt_addrof(event->queue));\n   internal::rescheduleAllCoreNoLock();\n\n   // Clear waitEventTimeoutAlarm\n   thread->waitEventTimeoutAlarm = nullptr;\n\n   auto result = FALSE;\n\n   if (event->value) {\n      if (event->mode == OSEventMode::AutoReset) {\n         // Reset the event if its in auto-reset mode\n         event->value = FALSE;\n      }\n\n      result = TRUE;\n   } else if (!data->timeout) {\n      result = TRUE;\n   }\n\n   internal::unlockScheduler();\n   return result;\n}\n\nvoid\nLibrary::registerEventSymbols()\n{\n   RegisterFunctionExport(OSInitEvent);\n   RegisterFunctionExport(OSInitEventEx);\n   RegisterFunctionExport(OSSignalEvent);\n   RegisterFunctionExport(OSSignalEventAll);\n   RegisterFunctionExport(OSResetEvent);\n   RegisterFunctionExport(OSWaitEvent);\n   RegisterFunctionExport(OSWaitEventWithTimeout);\n\n   RegisterFunctionInternal(EventAlarmHandler, sEventAlarmHandler);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_event.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_time.h\"\n#include \"coreinit_thread.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_event Event Object\n * \\ingroup coreinit\n *\n * Standard event object implementation. There are two supported event object modes, check OSEventMode.\n *\n * Similar to Windows <a href=\"https://msdn.microsoft.com/en-us/library/windows/desktop/ms682655(v=vs.85).aspx\">Event Objects</a>.\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSEvent\n{\n   static constexpr uint32_t Tag = 0x65566E54;\n\n   //! Should always be set to the value OSEvent::Tag.\n   be2_val<uint32_t> tag;\n\n   //! Name set by OSInitEventEx.\n   be2_ptr<const char> name;\n\n   UNKNOWN(4);\n\n   //! The current value of the event object.\n   be2_val<BOOL> value;\n\n   //! The threads currently waiting on this event object with OSWaitEvent.\n   be2_struct<OSThreadQueue> queue;\n\n   //! The mode of the event object, set by OSInitEvent.\n   be2_val<OSEventMode> mode;\n};\nCHECK_OFFSET(OSEvent, 0x0, tag);\nCHECK_OFFSET(OSEvent, 0x4, name);\nCHECK_OFFSET(OSEvent, 0xC, value);\nCHECK_OFFSET(OSEvent, 0x10, queue);\nCHECK_OFFSET(OSEvent, 0x20, mode);\nCHECK_SIZE(OSEvent, 0x24);\n\n#pragma pack(pop)\n\nvoid\nOSInitEvent(virt_ptr<OSEvent> event,\n            BOOL value,\n            OSEventMode mode);\n\nvoid\nOSInitEventEx(virt_ptr<OSEvent> event,\n              BOOL value,\n              OSEventMode mode,\n              virt_ptr<const char> name);\n\nvoid\nOSSignalEvent(virt_ptr<OSEvent> event);\n\nvoid\nOSSignalEventAll(virt_ptr<OSEvent> event);\n\nvoid\nOSWaitEvent(virt_ptr<OSEvent> event);\n\nvoid\nOSResetEvent(virt_ptr<OSEvent> event);\n\nBOOL\nOSWaitEventWithTimeout(virt_ptr<OSEvent> event,\n                       OSTimeNanoseconds timeout);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_exception.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_core.h\"\n#include \"coreinit_exception.h\"\n#include \"coreinit_interrupts.h\"\n#include \"coreinit_thread.h\"\n#include \"cafe/kernel/cafe_kernel_exception.h\"\n\nnamespace cafe::coreinit\n{\n\nstruct StaticExceptionData\n{\n   be2_array<OSExceptionCallbackFn, OSGetCoreCount()> dsiCallback;\n   be2_array<OSExceptionCallbackFn, OSGetCoreCount()> isiCallback;\n   be2_array<OSExceptionCallbackFn, OSGetCoreCount()> programCallback;\n   be2_array<OSExceptionCallbackFn, OSGetCoreCount()> alignCallback;\n};\n\nstatic virt_ptr<StaticExceptionData>\nsGlobalExceptions = nullptr;\n\nstatic void\nunkSyscall0x7A00(kernel::ExceptionType type,\n                 uint32_t value)\n{\n   // TODO: I think this might be clear & enable or something?\n}\n\nOSExceptionCallbackFn\nOSSetExceptionCallback(OSExceptionType type,\n                       OSExceptionCallbackFn callback)\n{\n   return OSSetExceptionCallbackEx(OSExceptionMode::Thread, type, callback);\n}\n\nOSExceptionCallbackFn\nOSSetExceptionCallbackEx(OSExceptionMode mode,\n                         OSExceptionType type,\n                         OSExceptionCallbackFn callback)\n{\n   // Only certain exceptions are supported for callback\n   if (type != OSExceptionType::DSI &&\n       type != OSExceptionType::ISI &&\n       type != OSExceptionType::Alignment &&\n       type != OSExceptionType::Program &&\n       type != OSExceptionType::PerformanceMonitor) {\n      return nullptr;\n   }\n\n   auto restoreInterruptValue = OSDisableInterrupts();\n   auto previousCallback = internal::getExceptionCallback(mode, type);\n   internal::setExceptionCallback(mode, type, callback);\n\n   if (mode == OSExceptionMode::Thread &&\n       type == OSExceptionType::PerformanceMonitor) {\n\n   }\n\n   OSRestoreInterrupts(restoreInterruptValue);\n   return previousCallback;\n}\n\nnamespace internal\n{\n\nOSExceptionCallbackFn\ngetExceptionCallback(OSExceptionMode mode,\n                     OSExceptionType type)\n{\n   auto thread = OSGetCurrentThread();\n   auto core = OSGetCoreId();\n   auto callback = OSExceptionCallbackFn { nullptr };\n\n   if (mode == OSExceptionMode::Thread ||\n       mode == OSExceptionMode::ThreadAllCores ||\n       mode == OSExceptionMode::System) {\n      switch (type) {\n      case OSExceptionType::DSI:\n         callback = thread->dsiCallback[core];\n         break;\n      case OSExceptionType::ISI:\n         callback = thread->isiCallback[core];\n         break;\n      case OSExceptionType::Alignment:\n         callback = thread->alignCallback[core];\n         break;\n      case OSExceptionType::Program:\n         callback = thread->programCallback[core];\n         break;\n      case OSExceptionType::PerformanceMonitor:\n         callback = thread->perfMonCallback[core];\n         break;\n      }\n\n      if (callback ||\n          mode != OSExceptionMode::System) {\n         return callback;\n      }\n   }\n\n   if (mode == OSExceptionMode::Global ||\n       mode == OSExceptionMode::GlobalAllCores ||\n       mode == OSExceptionMode::System) {\n      switch (type) {\n      case OSExceptionType::DSI:\n         callback = sGlobalExceptions->dsiCallback[core];\n      case OSExceptionType::ISI:\n         callback = sGlobalExceptions->isiCallback[core];\n      case OSExceptionType::Alignment:\n         callback = sGlobalExceptions->alignCallback[core];\n      case OSExceptionType::Program:\n         callback = sGlobalExceptions->programCallback[core];\n      default:\n         return nullptr;\n      }\n   }\n\n   return callback;\n}\n\nvoid\nsetExceptionCallback(OSExceptionMode mode,\n                     OSExceptionType type,\n                     OSExceptionCallbackFn callback)\n{\n   auto thread = OSGetCurrentThread();\n   auto core = OSGetCoreId();\n\n   if (mode == OSExceptionMode::Thread) {\n      switch (type) {\n      case OSExceptionType::DSI:\n         thread->dsiCallback[core] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::DSI, 0);\n         break;\n      case OSExceptionType::ISI:\n         thread->isiCallback[core] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::ISI, 0);\n         break;\n      case OSExceptionType::Alignment:\n         thread->alignCallback[core] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::Alignment, 0);\n         break;\n      case OSExceptionType::Program:\n         thread->programCallback[core] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::Program, 0);\n         break;\n      case OSExceptionType::PerformanceMonitor:\n         thread->perfMonCallback[core] = callback;\n         break;\n      }\n   } else if (mode == OSExceptionMode::ThreadAllCores) {\n      switch (type) {\n      case OSExceptionType::DSI:\n         thread->dsiCallback[0] = callback;\n         thread->dsiCallback[1] = callback;\n         thread->dsiCallback[2] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::DSI, 0);\n         break;\n      case OSExceptionType::ISI:\n         thread->isiCallback[0] = callback;\n         thread->isiCallback[1] = callback;\n         thread->isiCallback[2] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::ISI, 0);\n         break;\n      case OSExceptionType::Alignment:\n         thread->alignCallback[0] = callback;\n         thread->alignCallback[1] = callback;\n         thread->alignCallback[2] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::Alignment, 0);\n         break;\n      case OSExceptionType::Program:\n         thread->programCallback[0] = callback;\n         thread->programCallback[1] = callback;\n         thread->programCallback[2] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::Program, 0);\n         break;\n      case OSExceptionType::PerformanceMonitor:\n         thread->perfMonCallback[0] = callback;\n         thread->perfMonCallback[1] = callback;\n         thread->perfMonCallback[2] = callback;\n         break;\n      }\n   } else if (mode == OSExceptionMode::Global) {\n      switch (type) {\n      case OSExceptionType::DSI:\n         sGlobalExceptions->dsiCallback[core] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::DSI, 0);\n         break;\n      case OSExceptionType::ISI:\n         sGlobalExceptions->isiCallback[core] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::ISI, 0);\n         break;\n      case OSExceptionType::Alignment:\n         sGlobalExceptions->alignCallback[core] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::Alignment, 0);\n         break;\n      case OSExceptionType::Program:\n         sGlobalExceptions->programCallback[core] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::Program, 0);\n         break;\n      }\n   } else if (mode == OSExceptionMode::GlobalAllCores) {\n      switch (type) {\n      case OSExceptionType::DSI:\n         sGlobalExceptions->dsiCallback[0] = callback;\n         sGlobalExceptions->dsiCallback[1] = callback;\n         sGlobalExceptions->dsiCallback[2] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::DSI, 0);\n         break;\n      case OSExceptionType::ISI:\n         sGlobalExceptions->isiCallback[0] = callback;\n         sGlobalExceptions->isiCallback[1] = callback;\n         sGlobalExceptions->isiCallback[2] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::ISI, 0);\n         break;\n      case OSExceptionType::Alignment:\n         sGlobalExceptions->alignCallback[0] = callback;\n         sGlobalExceptions->alignCallback[1] = callback;\n         sGlobalExceptions->alignCallback[2] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::Alignment, 0);\n         break;\n      case OSExceptionType::Program:\n         sGlobalExceptions->programCallback[0] = callback;\n         sGlobalExceptions->programCallback[1] = callback;\n         sGlobalExceptions->programCallback[2] = callback;\n         unkSyscall0x7A00(kernel::ExceptionType::Program, 0);\n         break;\n      }\n   }\n}\n\nvoid\ninitialiseExceptionHandlers()\n{\n   // TODO: initialiseExceptionHandlers\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerExceptionSymbols()\n{\n   RegisterFunctionExport(OSSetExceptionCallback);\n   RegisterFunctionExport(OSSetExceptionCallbackEx);\n   RegisterDataInternal(sGlobalExceptions);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_exception.h",
    "content": "#pragma once\n#include \"coreinit_context.h\"\n#include \"coreinit_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nusing OSExceptionCallbackFn = virt_func_ptr<\n   BOOL(virt_ptr<OSContext> context)>;\n\nOSExceptionCallbackFn\nOSSetExceptionCallback(OSExceptionType type,\n                       OSExceptionCallbackFn callback);\n\nOSExceptionCallbackFn\nOSSetExceptionCallbackEx(OSExceptionMode mode,\n                         OSExceptionType type,\n                         OSExceptionCallbackFn callback);\n\nnamespace internal\n{\n\nvoid\ninitialiseExceptionHandlers();\n\nOSExceptionCallbackFn\ngetExceptionCallback(OSExceptionMode mode,\n                     OSExceptionType type);\n\nvoid\nsetExceptionCallback(OSExceptionMode mode,\n                     OSExceptionType type,\n                     OSExceptionCallbackFn callback);\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fastmutex.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_fastmutex.h\"\n#include \"coreinit_scheduler.h\"\n\nnamespace cafe::coreinit\n{\n\nusing FastMutexQueue = internal::Queue<OSFastMutexQueue, OSFastMutexLink, OSFastMutex, &OSFastMutex::link>;\nusing ContendedQueue = internal::Queue<OSFastMutexQueue, OSFastMutexLink, OSFastMutex, &OSFastMutex::contendedLink>;\nusing ThreadSimpleQueue = internal::SortedQueue<OSThreadSimpleQueue, OSThreadLink, OSThread, &OSThread::link, internal::ThreadIsLess>;\n\n/**\n * Initialise a fast mutex object.\n */\nvoid\nOSFastMutex_Init(virt_ptr<OSFastMutex> mutex,\n                 virt_ptr<const char> name)\n{\n   mutex->tag = OSFastMutex::Tag;\n   mutex->name = name;\n   mutex->isContended = FALSE;\n   mutex->lock.store(0);\n   mutex->count = 0;\n   ThreadSimpleQueue::init(virt_addrof(mutex->queue));\n   FastMutexQueue::initLink(mutex);\n   ContendedQueue::initLink(mutex);\n}\n\nstatic void\nfastMutexHardLock(virt_ptr<OSFastMutex> mutex)\n{\n   auto thread = OSGetCurrentThread();\n   decaf_check(thread->state == OSThreadState::Running);\n\n   internal::lockScheduler();\n   internal::testThreadCancelNoLock();\n\n   auto lockValue = mutex->lock.load();\n\n   while (true) {\n      if (lockValue) {\n         // Check if waiter bit is set, if not we must try set it\n         if (!(lockValue & 1)) {\n            if (!mutex->lock.compare_exchange_weak(lockValue, lockValue | 1)) {\n               continue;\n            }\n         }\n\n         // We now have set the waiter bit and we were not the owner\n         auto ownerThread = virt_cast<OSThread *>(virt_addr { lockValue & ~1 });\n\n         if (!mutex->isContended) {\n            ContendedQueue::append(virt_addrof(ownerThread->contendedFastMutexes), mutex);\n            mutex->isContended = TRUE;\n         }\n\n         // Record which fast mutex we are trying to lock before we sleep\n         thread->fastMutex = mutex;\n\n         // Promote the priority of the owning thread to prevent priority inversion problems\n         internal::promoteThreadPriorityNoLock(ownerThread, thread->priority);\n\n         // Sleep on the queue waiting for a hard unlock\n         internal::sleepThreadNoLock(virt_addrof(mutex->queue));\n         internal::rescheduleSelfNoLock();\n\n         // We are no longer attempting to lock this fast mutex\n         thread->fastMutex = nullptr;\n\n         lockValue = mutex->lock.load();\n\n         if (lockValue != 0) {\n            continue;\n         }\n      }\n\n      // Try to lock the FastMutex\n      auto newValue = static_cast<uint32_t>(virt_cast<virt_addr>(thread));\n      if (!mutex->lock.compare_exchange_weak(lockValue, newValue)) {\n         continue;\n      }\n\n      decaf_check(!(lockValue & 1));\n      decaf_check(mutex->count == 0);\n\n      // Set thread as owner\n      thread->cancelState |= OSThreadCancelState::DisabledByFastMutex;\n      FastMutexQueue::append(virt_addrof(thread->fastMutexQueue), mutex);\n      mutex->count = 1;\n      break;\n   }\n\n   internal::unlockScheduler();\n}\n\n\n/**\n * Lock a fast mutex object.\n */\nvoid\nOSFastMutex_Lock(virt_ptr<OSFastMutex> mutex)\n{\n   auto thread = OSGetCurrentThread();\n\n   while (true) {\n      if (thread->cancelState == OSThreadCancelState::Enabled &&\n          thread->requestFlag != OSThreadRequest::None) {\n         internal::lockScheduler();\n         internal::testThreadCancelNoLock();\n         internal::unlockScheduler();\n         continue;\n      }\n\n      auto lockValue = mutex->lock.load();\n\n      if (lockValue) {\n         auto lockThread = virt_cast<OSThread *>(virt_addr { lockValue & ~1 });\n\n         if (lockThread == thread) {\n            // We already own this FastMutex, increase recursion count\n            mutex->count++;\n            break;\n         } else {\n            // Another thread owns this FastMutex, now we must take the slow path!\n            fastMutexHardLock(mutex);\n            break;\n         }\n      } else {\n         // Attempt to lock the thread\n         auto newValue = static_cast<uint32_t>(virt_cast<virt_addr>(thread));\n         if (!mutex->lock.compare_exchange_weak(lockValue, newValue)) {\n            continue;\n         }\n\n         // Set thread as owner\n         thread->cancelState |= OSThreadCancelState::DisabledByFastMutex;\n         FastMutexQueue::append(virt_addrof(thread->fastMutexQueue), mutex);\n         mutex->count = 1;\n         break;\n      }\n   }\n}\n\nstatic void\nfastMutexHardUnlock(virt_ptr<OSFastMutex> mutex)\n{\n   // Grab our current thread and make sure we are running\n   auto thread = OSGetCurrentThread();\n   decaf_check(thread->state == OSThreadState::Running);\n\n   // Check to make sure the mutex is locked by us\n   auto lockValue = mutex->lock.load();\n   auto lockThread = virt_cast<OSThread *>(virt_addr { lockValue & ~1 });\n   decaf_check(thread == lockThread);\n\n   internal::lockScheduler();\n\n   // Double check that the caller actually did everything properly...\n   decaf_check(lockValue & 1);\n   decaf_check(mutex->count == 0);\n\n   if (mutex->isContended) {\n      ContendedQueue::erase(virt_addrof(thread->contendedFastMutexes), mutex);\n      mutex->isContended = FALSE;\n   }\n\n   // Adjust the priority if needed\n   if (thread->priority > thread->basePriority) {\n      thread->priority = internal::calculateThreadPriorityNoLock(thread);\n   }\n\n   // Free the cancel state if we arn't holding any more locks\n   if (!thread->fastMutexQueue.head) {\n      thread->cancelState &= ~OSThreadCancelState::DisabledByFastMutex;\n   }\n\n   // Wake up anyone who is hard-lock waiting on the mutex\n   internal::wakeupThreadNoLock(virt_addrof(mutex->queue));\n\n   // Release the lock!\n   mutex->lock.store(0);\n\n   internal::testThreadCancelNoLock();\n   internal::rescheduleSelfNoLock();\n   internal::unlockScheduler();\n}\n\n\n/**\n * Unlock a fast mutex object.\n */\nvoid\nOSFastMutex_Unlock(virt_ptr<OSFastMutex> mutex)\n{\n   decaf_check(mutex->count > 0);\n\n   auto thread = OSGetCurrentThread();\n   auto lockValue = mutex->lock.load();\n   auto lockThread = virt_cast<OSThread *>(virt_addr { lockValue & ~1 });\n\n   // Lock not currently held by this thread, ignore.\n   if (lockThread != thread) {\n      return;\n   }\n\n   // Reduce mutex count\n   mutex->count--;\n\n   if (mutex->count != 0) {\n      // If count is not 0, then we have not unlocked!\n      return;\n   }\n\n   // Remove ourselves from the queue\n   FastMutexQueue::erase(virt_addrof(thread->fastMutexQueue), mutex);\n   lockValue = mutex->lock.load();\n\n   while (true) {\n      // If someone contended on their lock, we need to hardUnlock\n      if (lockValue & 1) {\n         fastMutexHardUnlock(mutex);\n         return;\n      }\n\n      // Try to clear the lock\n      if (!mutex->lock.compare_exchange_weak(lockValue, 0)) {\n         continue;\n      }\n\n      // Success!\n      break;\n   }\n\n   // Clear the cancel state if we dont hold any more mutexes\n   if (!thread->fastMutexQueue.head) {\n      thread->cancelState &= ~OSThreadCancelState::DisabledByFastMutex;\n   }\n\n   // Lock the scheduler and consider cancelling\n   if (thread->cancelState == OSThreadCancelState::Enabled) {\n      internal::lockScheduler();\n      internal::testThreadCancelNoLock();\n      internal::unlockScheduler();\n   }\n}\n\n\n/**\n * Try to lock a fast mutex object.\n *\n * \\return\n * Returns FALSE if the mutex was already locked.\n * Returns TRUE if the mutex is now locked by the current thread.\n */\nBOOL\nOSFastMutex_TryLock(virt_ptr<OSFastMutex> mutex)\n{\n   auto thread = OSGetCurrentThread();\n\n   while (true) {\n      if (thread->cancelState == OSThreadCancelState::Enabled &&\n          thread->requestFlag != OSThreadRequest::None) {\n         internal::lockScheduler();\n         internal::testThreadCancelNoLock();\n         internal::unlockScheduler();\n         continue;\n      }\n\n      auto lockValue = mutex->lock.load();\n\n      if (lockValue) {\n         auto lockThread = virt_cast<OSThread *>(virt_addr { lockValue & ~1 });\n\n         if (lockThread == thread) {\n            // We already own this FastMutex, increase recursion count\n            mutex->count++;\n            return TRUE;\n         } else {\n            // Another thread owns this FastMutex, we have failed!\n            return FALSE;\n         }\n      } else {\n         // Try to lock the FastMutex\n         auto newValue = static_cast<uint32_t>(virt_cast<virt_addr>(thread));\n         if (!mutex->lock.compare_exchange_weak(lockValue, newValue)) {\n            continue;\n         }\n\n         // Set thread as owner\n         thread->cancelState |= OSThreadCancelState::DisabledByFastMutex;\n         FastMutexQueue::append(virt_addrof(thread->fastMutexQueue), mutex);\n         mutex->count = 1;\n         return TRUE;\n      }\n   }\n}\n\n\n/**\n * Initialises a fast condition object.\n */\nvoid\nOSFastCond_Init(virt_ptr<OSFastCondition> condition,\n                virt_ptr<const char> name)\n{\n   condition->tag = OSFastCondition::Tag;\n   condition->name = name;\n   condition->unk = 0u;\n   OSInitThreadQueueEx(virt_addrof(condition->queue), condition);\n}\n\n\n/**\n * Sleep the current thread until the condition variable has been signalled.\n *\n * The mutex must be locked when entering this function.\n * Will unlock the mutex and then sleep, reacquiring the mutex when woken.\n */\nvoid\nOSFastCond_Wait(virt_ptr<OSFastCondition> condition,\n                virt_ptr<OSFastMutex> mutex)\n{\n   internal::lockScheduler();\n\n   auto thread = OSGetCurrentThread();\n   auto lockValue = mutex->lock.load();\n   auto lockThread = virt_cast<OSThread *>(virt_addr { lockValue & ~1 });\n   decaf_check(lockValue & 1);\n   decaf_check(lockThread == thread);\n\n   if (mutex->isContended) {\n      ContendedQueue::erase(virt_addrof(thread->contendedFastMutexes), mutex);\n      mutex->isContended = FALSE;\n   }\n\n   if (thread->priority > thread->basePriority) {\n      thread->priority = internal::calculateThreadPriorityNoLock(thread);\n   }\n\n   // Save the recursion count, then force an unlock of the mutex\n   auto mutexCount = mutex->count;\n   internal::disableScheduler();\n   internal::wakeupThreadNoLock(virt_addrof(mutex->queue));\n   mutex->count = 0;\n   mutex->lock.store(0);\n   internal::rescheduleAllCoreNoLock();\n   internal::enableScheduler();\n\n   // Sleep the current thread on the condition queue, wait to be signalled\n   internal::sleepThreadNoLock(virt_addrof(condition->queue));\n   internal::rescheduleSelfNoLock();\n\n   // We must release the scheduler lock before trying to do a FastMutex lock\n   internal::unlockScheduler();\n\n   // Acquire the mutex, and restore the recursion count\n   OSFastMutex_Lock(mutex);\n   mutex->count = mutexCount;\n}\n\n\n/**\n * Will wake up any threads waiting on the condition with OSFastCond_Wait.\n */\nvoid\nOSFastCond_Signal(virt_ptr<OSFastCondition> condition)\n{\n   OSWakeupThread(virt_addrof(condition->queue));\n}\n\nnamespace internal\n{\n\nvoid\nunlockAllFastMutexNoLock(virt_ptr<OSThread> thread)\n{\n   decaf_check(isSchedulerLocked());\n\n   while (thread->fastMutexQueue.head) {\n      auto mutex = thread->fastMutexQueue.head;\n\n      // Ensure thread owns the mutex\n      auto lockValue = mutex->lock.load();\n      auto lockThread = virt_cast<OSThread *>(virt_addr { lockValue & ~1 });\n      decaf_check(lockThread == thread);\n\n      // Erase mutex from thread's mutex queue\n      FastMutexQueue::erase(virt_addrof(thread->fastMutexQueue), mutex);\n\n      // Erase mutex from thread's contended queue if necessary\n      if (mutex->isContended) {\n         ContendedQueue::erase(virt_addrof(thread->contendedFastMutexes), mutex);\n         mutex->isContended = FALSE;\n      }\n\n      // Unlock the mutex\n      wakeupThreadNoLock(virt_addrof(mutex->queue));\n      mutex->count = 0;\n      mutex->lock.store(0);\n   }\n\n   decaf_check(!thread->fastMutexQueue.head);\n   decaf_check(!thread->fastMutexQueue.tail);\n   decaf_check(!thread->contendedFastMutexes.head);\n   decaf_check(!thread->contendedFastMutexes.tail);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFastMutexSymbols()\n{\n   RegisterFunctionExport(OSFastMutex_Init);\n   RegisterFunctionExport(OSFastMutex_Lock);\n   RegisterFunctionExport(OSFastMutex_TryLock);\n   RegisterFunctionExport(OSFastMutex_Unlock);\n   RegisterFunctionExport(OSFastCond_Init);\n   RegisterFunctionExport(OSFastCond_Wait);\n   RegisterFunctionExport(OSFastCond_Signal);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fastmutex.h",
    "content": "#pragma once\n#include \"coreinit_thread.h\"\n\n#include <atomic>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_fastmutex Fast Mutex\n * \\ingroup coreinit\n *\n * Similar to OSMutex but tries to acquire the mutex without using the global\n * scheduler lock, and does not test for thread cancel.\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSFastMutex;\n\nstruct OSFastMutexLink\n{\n   be2_virt_ptr<OSFastMutex> next;\n   be2_virt_ptr<OSFastMutex> prev;\n};\nCHECK_OFFSET(OSFastMutexLink, 0x00, next);\nCHECK_OFFSET(OSFastMutexLink, 0x04, prev);\nCHECK_SIZE(OSFastMutexLink, 0x08);\n\nstruct OSFastMutex\n{\n   static constexpr uint32_t Tag = 0x664D7458u;\n\n   //! Should always be set to the value OSFastMutex::Tag.\n   be2_val<uint32_t> tag;\n\n   //! Name set by OSFastMutex_Init.\n   be2_virt_ptr<const char> name;\n\n   //! Is this thread in the queue\n   be2_val<BOOL> isContended;\n\n   //! Queue of threads waiting for this mutex to unlock.\n   be2_struct<OSThreadSimpleQueue> queue;\n\n   //! Link used inside OSThread's fast mutex queue.\n   be2_struct<OSFastMutexLink> link;\n\n   //! Lock bits for the mutex, owner thread and some bits\n   std::atomic<uint32_t> lock;\n\n   //! Current recursion lock count of mutex.\n   be2_val<int32_t> count;\n\n   //! Link used for contended mutexes\n   be2_struct<OSFastMutexLink> contendedLink;\n};\nCHECK_OFFSET(OSFastMutex, 0x00, tag);\nCHECK_OFFSET(OSFastMutex, 0x04, name);\nCHECK_OFFSET(OSFastMutex, 0x08, isContended);\nCHECK_OFFSET(OSFastMutex, 0x0C, queue);\nCHECK_OFFSET(OSFastMutex, 0x14, link);\nCHECK_OFFSET(OSFastMutex, 0x1C, lock);\nCHECK_OFFSET(OSFastMutex, 0x20, count);\nCHECK_OFFSET(OSFastMutex, 0x24, contendedLink);\nCHECK_SIZE(OSFastMutex, 0x2c);\n\nstruct OSFastCondition\n{\n   static constexpr uint32_t Tag = 0x664E6456u;\n\n   //! Should always be set to the value OSFastCondition::Tag.\n   be2_val<uint32_t> tag;\n\n   //! Name set by OSFastCond_Init.\n   be2_virt_ptr<const char> name;\n\n   //! Unknown data\n   be2_val<uint32_t> unk;\n\n   //! Queue of threads waiting for this condition to signal.\n   be2_struct<OSThreadQueue> queue;\n};\nCHECK_OFFSET(OSFastCondition, 0x00, tag);\nCHECK_OFFSET(OSFastCondition, 0x04, name);\nCHECK_OFFSET(OSFastCondition, 0x0C, queue);\nCHECK_SIZE(OSFastCondition, 0x1c);\n\n#pragma pack(pop)\n\nvoid\nOSFastMutex_Init(virt_ptr<OSFastMutex> mutex,\n                 virt_ptr<const char> name);\n\nvoid\nOSFastMutex_Lock(virt_ptr<OSFastMutex> mutex);\n\nvoid\nOSFastMutex_Unlock(virt_ptr<OSFastMutex> mutex);\n\nBOOL\nOSFastMutex_TryLock(virt_ptr<OSFastMutex> mutex);\n\nvoid\nOSFastCond_Init(virt_ptr<OSFastCondition> condition,\n                virt_ptr<const char> name);\n\nvoid\nOSFastCond_Wait(virt_ptr<OSFastCondition> condition,\n                virt_ptr<OSFastMutex> mutex);\n\nvoid\nOSFastCond_Signal(virt_ptr<OSFastCondition> condition);\n\n/** @} */\n\nnamespace internal\n{\n\nvoid\nunlockAllFastMutexNoLock(virt_ptr<OSThread> thread);\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fiber.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_fiber.h\"\n#include \"coreinit_thread.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include <libcpu/state.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * Switch to fiber.\n */\nuint32_t\nOSSwitchFiber(OSFiberEntryFn entry,\n              virt_addr userStack)\n{\n\n   auto state = cpu::this_core::state();\n   auto oldStack = virt_addr { state->gpr[1] };\n\n   // Set new stack\n   internal::setUserStackPointer(virt_cast<uint32_t *>(userStack));\n   state->gpr[1] = static_cast<uint32_t>(userStack);\n\n   // Call fiber function\n   auto result = cafe::invoke(cpu::this_core::state(), entry);\n\n   // Restore old stack\n   state->gpr[1] = static_cast<uint32_t>(oldStack);\n   internal::removeUserStackPointer(virt_cast<uint32_t *>(oldStack));\n\n   return result;\n}\n\n/**\n * Switch to fiber with 4 arguments.\n */\nuint32_t\nOSSwitchFiberEx(uint32_t arg1,\n                uint32_t arg2,\n                uint32_t arg3,\n                uint32_t arg4,\n                OSFiberExEntryFn entry,\n                virt_addr userStack)\n{\n   auto state = cpu::this_core::state();\n   auto oldStack = virt_addr { state->gpr[1] };\n\n   // Set new stack\n   internal::setUserStackPointer(virt_cast<uint32_t *>(userStack));\n   state->gpr[1] = static_cast<uint32_t>(userStack);\n\n   // Call fiber function\n   auto result = cafe::invoke(cpu::this_core::state(), entry,\n                              arg1, arg2, arg3, arg4);\n\n   // Restore old stack\n   state->gpr[1] = static_cast<uint32_t>(oldStack);\n   internal::removeUserStackPointer(virt_cast<uint32_t *>(oldStack));\n\n   return result;\n}\n\nvoid\nLibrary::registerFiberSymbols()\n{\n   RegisterFunctionExport(OSSwitchFiber);\n   RegisterFunctionExport(OSSwitchFiberEx);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fiber.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_fiber Fiber\n * \\ingroup coreinit\n * @{\n */\n\nusing OSFiberEntryFn = virt_func_ptr<uint32_t()>;\nusing OSFiberExEntryFn = virt_func_ptr<uint32_t(uint32_t arg1,\n                                                uint32_t arg2,\n                                                uint32_t arg3,\n                                                uint32_t arg4)>;\n\nuint32_t\nOSSwitchFiber(OSFiberEntryFn entry,\n              virt_addr userStack);\n\nuint32_t\nOSSwitchFiberEx(uint32_t arg1,\n                uint32_t arg2,\n                uint32_t arg3,\n                uint32_t arg4,\n                OSFiberExEntryFn entry,\n                virt_addr userStack);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_appio.h\"\n#include \"coreinit_fs.h\"\n#include \"coreinit_fs_client.h\"\n#include \"coreinit_fs_driver.h\"\n#include \"coreinit_fastmutex.h\"\n\nnamespace cafe::coreinit\n{\n\nstruct StaticFsData\n{\n   be2_val<bool> initialised;\n   be2_array<char, 32> clientListMutexName;\n   be2_struct<OSFastMutex> clientListMutex;\n   be2_val<uint32_t> numClients;\n   be2_virt_ptr<FSClientBody> clientList;\n};\n\nstatic virt_ptr<StaticFsData>\nsFsData = nullptr;\n\n\n/**\n * Initialise filesystem.\n */\nvoid\nFSInit()\n{\n   if (sFsData->initialised || internal::fsDriverDone()) {\n      return;\n   }\n\n   sFsData->initialised = true;\n   sFsData->clientList = nullptr;\n   sFsData->numClients = 0u;\n   sFsData->clientListMutexName = \"{ FSClient }\";\n   OSFastMutex_Init(virt_addrof(sFsData->clientListMutex),\n                    virt_addrof(sFsData->clientListMutexName));\n   internal::initialiseFsDriver();\n}\n\n\n/**\n * Shutdown filesystem.\n */\nvoid\nFSShutdown()\n{\n}\n\n\n/**\n * Get an FSAsyncResult from an OSMessage.\n */\nvirt_ptr<FSAsyncResult>\nFSGetAsyncResult(virt_ptr<OSMessage> message)\n{\n   return virt_cast<FSAsyncResult *>(message->message);\n}\n\n\n/**\n * Get the number of registered FS clients.\n */\nuint32_t\nFSGetClientNum()\n{\n   return sFsData->numClients;\n}\n\n\nnamespace internal\n{\n\n/**\n * Returns true if filesystem has been intialised.\n */\nbool\nfsInitialised()\n{\n   return sFsData->initialised;\n}\n\n\n/**\n * Returns true if client is registered.\n */\nbool\nfsClientRegistered(virt_ptr<FSClient> client)\n{\n   return fsClientRegistered(fsClientGetBody(client));\n}\n\n\n/**\n * Returns true if client is registered.\n */\nbool\nfsClientRegistered(virt_ptr<FSClientBody> clientBody)\n{\n   auto registered = false;\n   OSFastMutex_Lock(virt_addrof(sFsData->clientListMutex));\n\n   if (sFsData->clientList) {\n      auto itr = sFsData->clientList;\n      do {\n         if (itr == clientBody) {\n            registered = true;\n            break;\n         }\n\n         itr = itr->link.next;\n      } while (itr != sFsData->clientList);\n   }\n\n   OSFastMutex_Unlock(virt_addrof(sFsData->clientListMutex));\n   return registered;\n}\n\n\n/**\n * Register a client with the filesystem.\n */\nbool\nfsRegisterClient(virt_ptr<FSClientBody> clientBody)\n{\n   OSFastMutex_Lock(virt_addrof(sFsData->clientListMutex));\n\n   if (sFsData->clientList) {\n      auto first = sFsData->clientList;\n      auto next = first->link.next;\n      first->link.next = clientBody;\n      next->link.prev = clientBody;\n      clientBody->link.next = next;\n      clientBody->link.prev = first;\n   } else {\n      sFsData->clientList = clientBody;\n      clientBody->link.next = clientBody;\n      clientBody->link.prev = clientBody;\n   }\n\n   sFsData->numClients++;\n   OSFastMutex_Unlock(virt_addrof(sFsData->clientListMutex));\n   return true;\n}\n\n\n/**\n * Deregister a client from the filesystem.\n */\nbool\nfsDeregisterClient(virt_ptr<FSClientBody> clientBody)\n{\n   OSFastMutex_Lock(virt_addrof(sFsData->clientListMutex));\n\n   // If was first item in the list, update list pointer\n   if (sFsData->clientList == clientBody) {\n      if (clientBody->link.prev != clientBody) {\n         sFsData->clientList = clientBody->link.prev;\n      } else {\n         sFsData->clientList = nullptr;\n      }\n   }\n\n   // Remove from list\n   auto next = clientBody->link.next;\n   auto prev = clientBody->link.prev;\n\n   prev->link.next = next;\n   next->link.prev = prev;\n\n   clientBody->link.next = nullptr;\n   clientBody->link.prev = nullptr;\n\n   sFsData->numClients--;\n   OSFastMutex_Unlock(virt_addrof(sFsData->clientListMutex));\n   return true;\n}\n\n\n/**\n * Initialise an FSAsyncResult structure for an FS command.\n *\n * \\retval FSStatus::OK\n * Success.\n */\nFSStatus\nfsAsyncResultInit(virt_ptr<FSClientBody> clientBody,\n                  virt_ptr<FSAsyncResult> asyncResult,\n                  virt_ptr<const FSAsyncData> asyncData)\n{\n   asyncResult->asyncData = *asyncData;\n\n   if (!asyncData->ioMsgQueue) {\n      asyncResult->asyncData.ioMsgQueue = OSGetDefaultAppIOQueue();\n   }\n\n   asyncResult->client = clientBody->client;\n   asyncResult->ioMsg.data = asyncResult;\n   asyncResult->ioMsg.type = OSFunctionType::FsCmdAsync;\n   return FSStatus::OK;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFsSymbols()\n{\n   RegisterFunctionExport(FSInit);\n   RegisterFunctionExport(FSShutdown);\n   RegisterFunctionExport(FSGetAsyncResult);\n   RegisterFunctionExport(FSGetClientNum);\n\n   RegisterDataInternal(sFsData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_messagequeue.h\"\n#include \"ios/fs/ios_fs_fsa.h\"\n\n#include <libcpu/be2_struct.h>\n\n/*\nUnimplemented functions:\nFSAppendFile\nFSAppendFileAsync\nFSCancelAllCommands\nFSCancelCommand\nFSDumpLastErrorLog\nFSFlushMultiQuota\nFSFlushMultiQuotaAsync\nFSGetEntryNum\nFSGetEntryNumAsync\nFSGetFileBlockAddress\nFSGetFileBlockAddressAsync\nFSGetFileSystemInfo\nFSGetFileSystemInfoAsync\nFSGetVolumeInfo\nFSGetVolumeInfoAsync\nFSMakeLink\nFSMakeLinkAsync\nFSMakeQuota\nFSMakeQuotaAsync\nFSOpenFileByStat\nFSOpenFileByStatAsync\nFSRegisterFlushQuota\nFSRegisterFlushQuotaAsync\nFSRemoveQuota\nFSRemoveQuotaAsync\nFSRollbackQuota\nFSRollbackQuotaAsync\nFSTimeToCalendarTime\n*/\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_fs Filesystem\n * \\ingroup coreinit\n *\n * The typical flow of a FS command looks like:\n *\n * {Game thread} FSOpenFileExAsync\n *    -> fsClientSubmitCommand (fsCmdBlockFinishCmd)\n *       -> fsCmdQueueProcessMsg\n *          -> fsClientHandleDequeuedCommand\n *             -> fsaShimSubmitRequestAsync\n *                -> IOS_IoctlAsync (fsClientHandleFsaAsyncCallback)\n *\n * {IPC interrupt} fsClientHandleFsaAsyncCallback\n *    -> SendMessage AppIOQueue FsCmdHandler\n *\n * {AppIo thread} receive FsCmdHandler\n *    -> fsCmdBlockHandleResult\n *       -> fsCmdBlockReplyResult\n *          -> blockBody->finishCmdFn (fsCmdBlockFinishCmd)\n *             -> fsCmdBlockSetResult\n *                -> SendMessage asyncData.ioMsgQueue FsCmdAsync\n *\n * {AppIo thread} receive FsCmdAsync\n *    -> FSGetAsyncResult(msg)->userParams.callback\n *\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct FSClient;\nstruct FSClientBody;\nstruct FSCmdBlock;\nstruct FSCmdBlockBody;\n\nstruct FSAsyncData;\nstruct FSAsyncResult;\nstruct FSMessage;\n\nstruct FSMountSource;\n\nusing FSDirEntry = ios::fs::FSADirEntry;\nusing FSDirHandle = ios::fs::FSADirHandle;\nusing FSEntryNum = ios::fs::FSAEntryNum;\nusing FSFileHandle = ios::fs::FSAFileHandle;\nusing FSFilePosition = ios::fs::FSAFilePosition;\nusing FSReadFlag = ios::fs::FSAReadFlag;\nusing FSStat = ios::fs::FSAStat;\nusing FSStatFlags = ios::fs::FSAStatFlags;\nusing FSWriteFlag = ios::fs::FSAWriteFlag;\n\nusing FSAsyncCallbackFn = virt_func_ptr<void(virt_ptr<FSClient>,\n                                             virt_ptr<FSCmdBlock>,\n                                             FSStatus,\n                                             virt_ptr<void>)>;\n\nstatic constexpr uint32_t\nFSMaxBytesPerRequest = 0x100000u;\n\nstatic constexpr uint32_t\nFSMaxPathLength = ios::fs::FSAPathLength;\n\nstatic constexpr uint32_t\nFSMaxMountPathLength = 0x80u;\n\nstatic constexpr uint8_t\nFSMinPriority = 0u;\n\nstatic constexpr uint8_t\nFSDefaultPriority = 16u;\n\nstatic constexpr uint8_t\nFSMaxPriority = 32u;\n\nstruct FSMessage\n{\n   //! Message data\n   be2_virt_ptr<void> data;\n\n   UNKNOWN(8);\n\n   //! Type of message\n   be2_val<OSFunctionType> type;\n};\nCHECK_OFFSET(FSMessage, 0x00, data);\nCHECK_OFFSET(FSMessage, 0x0C, type);\nCHECK_SIZE(FSMessage, 0x10);\n\n\n/**\n * Async data passed to an FS*Async function.\n */\nstruct FSAsyncData\n{\n   //! Callback to call when the command is complete.\n   be2_val<FSAsyncCallbackFn> userCallback;\n\n   //! Callback context\n   be2_virt_ptr<void> userContext;\n\n   //! Queue to put a message on when command is complete.\n   be2_virt_ptr<OSMessageQueue> ioMsgQueue;\n};\nCHECK_OFFSET(FSAsyncData, 0x00, userCallback);\nCHECK_OFFSET(FSAsyncData, 0x04, userContext);\nCHECK_OFFSET(FSAsyncData, 0x08, ioMsgQueue);\nCHECK_SIZE(FSAsyncData, 0xC);\n\n\n/**\n * Stores the result of an async FS command.\n */\nstruct FSAsyncResult\n{\n   //! User supplied async data.\n   be2_struct<FSAsyncData> asyncData;\n\n   //! Message to put into asyncdata.ioMsgQueue.\n   be2_struct<FSMessage> ioMsg;\n\n   //! FSClient which owns this result.\n   be2_virt_ptr<FSClient> client;\n\n   //! FSCmdBlock which owns this result.\n   be2_virt_ptr<FSCmdBlock> block;\n\n   //! The result of the command.\n   be2_val<FSStatus> status;\n};\nCHECK_OFFSET(FSAsyncResult, 0x00, asyncData);\nCHECK_OFFSET(FSAsyncResult, 0x0c, ioMsg);\nCHECK_OFFSET(FSAsyncResult, 0x1c, client);\nCHECK_OFFSET(FSAsyncResult, 0x20, block);\nCHECK_OFFSET(FSAsyncResult, 0x24, status);\nCHECK_SIZE(FSAsyncResult, 0x28);\n\n\n/**\n * Information about a mount.\n */\nstruct FSMountSource\n{\n   //! Mount type\n   be2_val<FSMountSourceType> sourceType;\n\n   //! Mount path\n   be2_array<char, FSMaxPathLength> path;\n};\nCHECK_OFFSET(FSMountSource, 0x0, sourceType);\nCHECK_OFFSET(FSMountSource, 0x4, path);\nCHECK_SIZE(FSMountSource, 0x283);\n\n#pragma pack(pop)\n\nvoid\nFSInit();\n\nvoid\nFSShutdown();\n\nvirt_ptr<FSAsyncResult>\nFSGetAsyncResult(virt_ptr<OSMessage> message);\n\nuint32_t\nFSGetClientNum();\n\nnamespace internal\n{\n\nbool\nfsInitialised();\n\nbool\nfsClientRegistered(virt_ptr<FSClient> client);\n\nbool\nfsClientRegistered(virt_ptr<FSClientBody> clientBody);\n\nbool\nfsRegisterClient(virt_ptr<FSClientBody> clientBody);\n\nbool\nfsDeregisterClient(virt_ptr<FSClientBody> clientBody);\n\nFSStatus\nfsAsyncResultInit(virt_ptr<FSClientBody> clientBody,\n                  virt_ptr<FSAsyncResult> asyncResult,\n                  virt_ptr<const FSAsyncData> asyncData);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_client.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_appio.h\"\n#include \"coreinit_fastmutex.h\"\n#include \"coreinit_fs.h\"\n#include \"coreinit_fs_client.h\"\n#include \"coreinit_fs_driver.h\"\n#include \"coreinit_fs_cmdblock.h\"\n#include \"coreinit_fsa_shim.h\"\n#include \"cafe/cafe_stackobject.h\"\n\n#include <common/align.h>\n#include <common/log.h>\n#include <fmt/core.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nstatic IOSAsyncCallbackFn\nsHandleFsaAsyncCallback = nullptr;\n\nstatic FSCmdQueueHandlerFn\nsHandleDequeuedCommand = nullptr;\n\n\n/**\n * Create a new FSClient.\n */\nFSStatus\nFSAddClient(virt_ptr<FSClient> client,\n            FSErrorFlag errorMask)\n{\n   return FSAddClientEx(client, nullptr, errorMask);\n}\n\n\n/**\n * Create a new FSClient.\n */\nFSStatus\nFSAddClientEx(virt_ptr<FSClient> client,\n              virt_ptr<FSAttachParams> attachParams,\n              FSErrorFlag errorMask)\n{\n   if (!internal::fsInitialised()) {\n      return FSStatus::FatalError;\n   }\n\n   if (!client) {\n      return FSStatus::FatalError;\n   }\n\n   if (internal::fsClientRegistered(client)) {\n      internal::fsClientHandleFatalError(internal::fsClientGetBody(client),\n                                         FSAStatus::AlreadyExists);\n      return FSStatus::FatalError;\n   }\n\n   std::memset(client.get(), 0, sizeof(FSClient));\n   auto clientBody = internal::fsClientGetBody(client);\n   auto clientHandle = internal::fsaShimOpen();\n\n   if (clientHandle < 0) {\n      return FSStatus::FatalError;\n   }\n\n   if (!internal::fsRegisterClient(clientBody)) {\n      internal::fsaShimClose(clientHandle);\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::MaxClients);\n      return FSStatus::FatalError;\n   }\n\n   internal::fsCmdQueueCreate(virt_addrof(clientBody->cmdQueue),\n                              sHandleDequeuedCommand, 1);\n\n   OSFastMutex_Init(virt_addrof(clientBody->mutex), nullptr);\n   OSCreateAlarm(virt_addrof(clientBody->fsmAlarm));\n\n   internal::fsmInit(virt_addrof(clientBody->fsm), clientBody);\n\n   clientBody->clientHandle = clientHandle;\n   clientBody->lastDequeuedCommand = nullptr;\n   clientBody->emulatedError = FSAStatus::OK;\n   clientBody->unk0x14CC = 0u;\n\n   // TODO: Initialise attach params related data\n   if (attachParams) {\n      // 0x1240 = 4\n\n      // 0x1248 = attachParams.callback\n      // 0x124C = this\n\n      // 0x1254 = &(this + 0x1248) // FSMessage ?\n      // 0x1260 = 0xA // OSFunctionType::FsAttach ?\n\n      // 0x1428 = 0\n\n      // 0x1440 = attachParams.context\n   } else {\n      // 0x1240 = 0\n      // 0x1428 = 0\n   }\n\n   return FSStatus::OK;\n}\n\n\n/**\n * Destroy an FSClient.\n *\n * Might block thread to wait for last command to finish.\n */\nFSStatus\nFSDelClient(virt_ptr<FSClient> client,\n            FSErrorFlag errorMask)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n\n   if (!internal::fsInitialised()) {\n      return FSStatus::FatalError;\n   }\n\n   if (!clientBody->link.prev) {\n      // Already deleted.\n      return FSStatus::FatalError;\n   }\n\n   // Prevent any new commands from being started.\n   internal::fsCmdQueueSuspend(virt_addrof(clientBody->cmdQueue));\n\n   // Wait for last active command to be completed.\n   auto sleptMS = 0;\n   auto timeoutMS = 10;\n\n   while (clientBody->cmdQueue.activeCmds) {\n      if (clientBody->fsm.clientVolumeState != FSVolumeState::Fatal) {\n         // The FS client is in an error state.\n         break;\n      }\n\n      if (clientBody->lastDequeuedCommand &&\n          clientBody->lastDequeuedCommand->status == FSCmdBlockStatus::Completed) {\n         // The last command has completed.\n         break;\n      }\n\n      if (sleptMS == timeoutMS) {\n         // We have timed out waiting for last command to complete.\n         break;\n      }\n\n      OSSleepTicks(internal::msToTicks(1));\n      sleptMS += 1;\n   }\n\n   // Cleanup.\n   internal::fsCmdQueueDestroy(virt_addrof(clientBody->cmdQueue));\n   internal::fsaShimClose(clientBody->clientHandle);\n   internal::fsDeregisterClient(clientBody);\n   return FSStatus::OK;\n}\n\n\n/**\n * Get the current active command block for a client.\n *\n * This is the command which has been sent over IOS IPC to the FSA device.\n *\n * \\return\n * Returns a pointer to FSCmdBlock or nullptr if there is no active command.\n */\nvirt_ptr<FSCmdBlock>\nFSGetCurrentCmdBlock(virt_ptr<FSClient> client)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   if (!clientBody) {\n      return nullptr;\n   }\n\n   auto lastDequeued = clientBody->lastDequeuedCommand;\n   if (!lastDequeued) {\n      return nullptr;\n   }\n\n   return lastDequeued->cmdBlock;\n}\n\n\n/**\n * Get the emulated error for a client.\n *\n * \\return\n * Emulated error code or a positive value on error.\n */\nFSAStatus\nFSGetEmulatedError(virt_ptr<FSClient> client)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   if (!clientBody) {\n      return static_cast<FSAStatus>(1);\n   }\n\n   return clientBody->emulatedError;\n}\n\n\n/**\n * Set an emulated error for a client.\n *\n * All subsequent commands will fail with this error until it is cleared with\n * FSSetEmulatedError(FSAStatus::OK).\n *\n * \\retval FSStatus::OK\n * Returned on success.\n *\n * \\retval FSStatus::FatalError\n * Returned on failure, returned if invalid client or error code.\n */\nFSStatus\nFSSetEmulatedError(virt_ptr<FSClient> client,\n                   FSAStatus error)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   if (!clientBody) {\n      return FSStatus::FatalError;\n   }\n\n   if (error >= FSAStatus::OK) {\n      return FSStatus::FatalError;\n   }\n\n   if (error == FSAStatus::MediaNotReady) {\n      return FSStatus::FatalError;\n   }\n\n   clientBody->emulatedError = error;\n   return FSStatus::OK;\n}\n\n\n/**\n * Get last error for a cmd as an error code for the ErrEula error viewer.\n */\nint32_t\nFSGetErrorCodeForViewer(virt_ptr<FSClient> client,\n                        virt_ptr<FSCmdBlock> block)\n{\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto clientBody = internal::fsClientGetBody(client);\n\n   if (!blockBody) {\n      if (clientBody->fsm.clientVolumeState == FSVolumeState::Fatal) {\n         return static_cast<FSAStatus>(FSStatus::FatalError);\n      } else {\n         return static_cast<FSAStatus>(FSStatus::OK);\n      }\n   }\n\n   if (blockBody->iosError >= IOSError::OK) {\n      return static_cast<FSAStatus>(FSStatus::OK);\n   } else {\n      // TODO: Translate error block->unk0x9f4 for FSGetErrorCodeForViewer\n      return blockBody->iosError;\n   }\n}\n\n\n/**\n * Get last error as an error code for the ErrEula error viewer.\n */\nint32_t\nFSGetLastErrorCodeForViewer(virt_ptr<FSClient> client)\n{\n   return FSGetErrorCodeForViewer(client, FSGetCurrentCmdBlock(client));\n}\n\n\n/**\n * Get the error code for the last executed command.\n */\nFSAStatus\nFSGetLastError(virt_ptr<FSClient> client)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   if (!clientBody) {\n      return static_cast<FSAStatus>(FSStatus::FatalError);\n   }\n\n   return clientBody->lastError;\n}\n\n\n/**\n * Get the volume state for a client.\n */\nFSVolumeState\nFSGetVolumeState(virt_ptr<FSClient> client)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   if (!clientBody) {\n      return FSVolumeState::Invalid;\n   }\n\n   return clientBody->fsm.clientVolumeState;\n}\n\n\nnamespace internal\n{\n\n\n/**\n * Get an aligned FSClientBody from an FSClient.\n */\nvirt_ptr<FSClientBody>\nfsClientGetBody(virt_ptr<FSClient> client)\n{\n   auto body = virt_cast<FSClientBody *>(align_up(client, 0x40));\n   body->client = client;\n   return body;\n}\n\n\n/**\n * Handle a fatal error.\n *\n * Will transition the volume state to fatal.\n */\nvoid\nfsClientHandleFatalError(virt_ptr<FSClientBody> clientBody,\n                         FSAStatus error)\n{\n   clientBody->lastError = error;\n   clientBody->isLastErrorWithoutVolume = FALSE;\n   fsmEnterState(virt_addrof(clientBody->fsm),\n                 FSVolumeState::Fatal,\n                 clientBody);\n}\n\n\n/**\n * Handle error returned from a fsaShimpPrepare* call.\n *\n * \\return\n * FSStatus error code.\n */\nFSStatus\nfsClientHandleShimPrepareError(virt_ptr<FSClientBody> clientBody,\n                               FSAStatus error)\n{\n   fsClientHandleFatalError(clientBody, error);\n   return fsaDecodeFsaStatusToFsStatus(error);\n}\n\n\n/**\n * Handle async result for a synchronous FS call.\n *\n * May block and wait for result to be received.\n *\n * \\return\n * Returns postive value on success, FSStatus error code otherwise.\n */\nFSStatus\nfsClientHandleAsyncResult(virt_ptr<FSClient> client,\n                          virt_ptr<FSCmdBlock> block,\n                          FSStatus result,\n                          FSErrorFlag errorMask)\n{\n   auto errorFlags = FSErrorFlag::All;\n\n   if (result >= 0) {\n      auto message = StackObject<OSMessage> { };\n      auto blockBody = fsCmdBlockGetBody(block);\n      OSReceiveMessage(virt_addrof(blockBody->syncQueue),\n                       message,\n                       OSMessageFlags::Blocking);\n\n      auto fsMessage = virt_cast<FSMessage *>(message);\n      if (fsMessage->type != OSFunctionType::FsCmdAsync) {\n         decaf_abort(fmt::format(\"Unsupported function type {}\",\n                                 fsMessage->type));\n      }\n\n      return FSGetAsyncResult(message)->status;\n   }\n\n   switch (result) {\n   case FSStatus::Cancelled:\n   case FSStatus::End:\n      errorFlags = FSErrorFlag::None;\n      break;\n   case FSStatus::Max:\n      errorFlags = FSErrorFlag::Max;\n      break;\n   case FSStatus::AlreadyOpen:\n      errorFlags = FSErrorFlag::AlreadyOpen;\n      break;\n   case FSStatus::Exists:\n      errorFlags = FSErrorFlag::Exists;\n      break;\n   case FSStatus::NotFound:\n      errorFlags = FSErrorFlag::NotFound;\n      break;\n   case FSStatus::NotFile:\n      errorFlags = FSErrorFlag::NotFile;\n      break;\n   case FSStatus::NotDirectory:\n      errorFlags = FSErrorFlag::NotDir;\n      break;\n   case FSStatus::AccessError:\n      errorFlags = FSErrorFlag::AccessError;\n      break;\n   case FSStatus::PermissionError:\n      errorFlags = FSErrorFlag::PermissionError;\n      break;\n   case FSStatus::FileTooBig:\n      errorFlags = FSErrorFlag::FileTooBig;\n      break;\n   case FSStatus::StorageFull:\n      errorFlags = FSErrorFlag::StorageFull;\n      break;\n   case FSStatus::JournalFull:\n      errorFlags = FSErrorFlag::JournalFull;\n      break;\n   case FSStatus::UnsupportedCmd:\n      errorFlags = FSErrorFlag::UnsupportedCmd;\n      break;\n   }\n\n   if (errorFlags != FSErrorFlag::None && (errorFlags & errorMask) == 0) {\n      auto clientBody = fsClientGetBody(client);\n      fsClientHandleFatalError(clientBody, clientBody->lastError);\n      return FSStatus::FatalError;\n   }\n\n   return result;\n}\n\n\n/**\n * Submit an FSCmdBlockBody to the client's FSCmdQueue.\n */\nvoid\nfsClientSubmitCommand(virt_ptr<FSClientBody> clientBody,\n                      virt_ptr<FSCmdBlockBody> blockBody,\n                      FSFinishCmdFn finishCmdFn)\n{\n   auto queue = virt_addrof(clientBody->cmdQueue);\n   blockBody->finishCmdFn = finishCmdFn;\n   blockBody->status = FSCmdBlockStatus::QueuedCommand;\n\n   // Enqueue command\n   OSFastMutex_Lock(virt_addrof(queue->mutex));\n   fsCmdQueueEnqueue(queue, blockBody, false);\n   OSFastMutex_Unlock(virt_addrof(queue->mutex));\n\n   // Process command queue\n   fsCmdQueueProcessCmd(queue);\n}\n\n\n/**\n * Handle the async IOS FSA IPC callback.\n */\nstatic void\nfsClientHandleFsaAsyncCallback(IOSError error,\n                               virt_ptr<void>context)\n{\n   auto blockBody = virt_cast<FSCmdBlockBody *>(context);\n   auto clientBody = blockBody->clientBody;\n   blockBody->iosError = error;\n   blockBody->status = FSCmdBlockStatus::Completed;\n   blockBody->fsaStatus =\n      FSAShimDecodeIosErrorToFsaStatus(clientBody->clientHandle,\n                                       error);\n\n   if (fsInitialised() && !fsDriverDone()) {\n      clientBody->fsCmdHandlerMsg.data = blockBody;\n      clientBody->fsCmdHandlerMsg.type = OSFunctionType::FsCmdHandler;\n      OSSendMessage(OSGetDefaultAppIOQueue(),\n                    virt_cast<OSMessage *>(virt_addrof(clientBody->fsCmdHandlerMsg)),\n                    OSMessageFlags::None);\n   }\n}\n\n\n/**\n * Handle a FS command which has been dequeued from FSCmdQueue.\n *\n * Submits the IOS FSA IPC request for the FS command.\n *\n * \\retval TRUE\n * Success.\n *\n * \\retval FALSE\n * Unexpected error occurred.\n */\nstatic BOOL\nfsClientHandleDequeuedCommand(virt_ptr<FSCmdBlockBody> blockBody)\n{\n   auto clientBody = blockBody->clientBody;\n   FSAStatus error;\n\n   do {\n      /*\n      if (blockBody->cmdData.mount.unk0x00 < 2) {\n         if (blockBody->fsaShimBuffer.command == FSACommand::Mount) {\n            // TODO: __handleDequeuedCmd FSACommand::Mount\n         } else if (blockBody->fsaShimBuffer.command == FSACommand::Unmount) {\n            // TODO: __handleDequeuedCmd FSACommand::Unmount\n         }\n      }\n      */\n\n      if (!fsInitialised() || fsDriverDone()) {\n         error = FSAStatus::NotInit;\n      } else {\n         error = fsaShimSubmitRequestAsync(virt_addrof(blockBody->fsaShimBuffer),\n                                           clientBody->emulatedError,\n                                           sHandleFsaAsyncCallback,\n                                           blockBody);\n      }\n\n      // TODO: more if cmd == mount shit\n\n      if (error == FSAStatus::OK) {\n         return TRUE;\n      } else if (error == FSAStatus::NotInit) {\n         gLog->error(\"Could not issue command {} due to uninitialised filesystem\", blockBody->fsaShimBuffer.command);\n         return TRUE;\n      }\n   } while (error == FSAStatus::Busy);\n\n   gLog->error(\"Unexpected error {} whilst handling dequeued command {}\", error, blockBody->fsaShimBuffer.command);\n   fsmEnterState(virt_addrof(clientBody->fsm), FSVolumeState::Fatal, clientBody);\n   return FALSE;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFsClientSymbols()\n{\n   RegisterFunctionExport(FSAddClient);\n   RegisterFunctionExport(FSAddClientEx);\n   RegisterFunctionExport(FSDelClient);\n   RegisterFunctionExport(FSGetCurrentCmdBlock);\n   RegisterFunctionExport(FSGetEmulatedError);\n   RegisterFunctionExport(FSSetEmulatedError);\n   RegisterFunctionExport(FSGetErrorCodeForViewer);\n   RegisterFunctionExport(FSGetLastErrorCodeForViewer);\n   RegisterFunctionExport(FSGetLastError);\n   RegisterFunctionExport(FSGetVolumeState);\n\n   RegisterFunctionInternal(internal::fsClientHandleDequeuedCommand, sHandleDequeuedCommand);\n   RegisterFunctionInternal(internal::fsClientHandleFsaAsyncCallback, sHandleFsaAsyncCallback);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_client.h",
    "content": "#pragma once\n#include \"coreinit_alarm.h\"\n#include \"coreinit_enum.h\"\n#include \"coreinit_ios.h\"\n#include \"coreinit_fastmutex.h\"\n#include \"coreinit_fs.h\"\n#include \"coreinit_fs_cmdblock.h\"\n#include \"coreinit_fs_cmdqueue.h\"\n#include \"coreinit_fs_statemachine.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\ingroup coreinit_fs\n * @{\n */\n\nstruct FSClient;\nstruct FSClientBody;\nstruct FSClientBodyLink;\nstruct FSCmdBlockBody;\n\n\n/**\n * Attach parameters passed to FSAddClientEx.\n */\nstruct FSAttachParams\n{\n   be2_virt_ptr<void> userCallback;\n   be2_virt_ptr<void> userContext;\n};\nCHECK_OFFSET(FSAttachParams, 0x00, userCallback);\nCHECK_OFFSET(FSAttachParams, 0x04, userContext);\nCHECK_SIZE(FSAttachParams, 0x8);\n\n\n/**\n * Client container, use internal::getClientBody to get the actual data.\n */\nstruct FSClient\n{\n   be2_array<uint8_t, 0x1700> data;\n};\nCHECK_SIZE(FSClient, 0x1700);\n\n\n/**\n * Link entry used for FSClientBodyQueue.\n */\nstruct FSClientBodyLink\n{\n   be2_virt_ptr<FSClientBody> next;\n   be2_virt_ptr<FSClientBody> prev;\n};\nCHECK_OFFSET(FSClientBodyLink, 0x00, next);\nCHECK_OFFSET(FSClientBodyLink, 0x04, prev);\nCHECK_SIZE(FSClientBodyLink, 0x8);\n\n\n/**\n * The actual data of an FSClient.\n */\nstruct FSClientBody\n{\n   UNKNOWN(0x1444);\n\n   //! IOSHandle returned from fsaShimOpen.\n   be2_val<IOSHandle> clientHandle;\n\n   //! State machine.\n   be2_struct<FSFsm> fsm;\n\n   //! Command queue of FS commands.\n   be2_struct<FSCmdQueue> cmdQueue;\n\n   //! The last dequeued command.\n   be2_virt_ptr<FSCmdBlockBody> lastDequeuedCommand;\n\n   //! Emulated error, set with FSSetEmulatedError.\n   be2_val<FSAStatus> emulatedError;\n\n   be2_val<uint32_t> unk0x14CC;\n   be2_val<uint32_t> unk0x14D0;\n   UNKNOWN(0x1560 - 0x14D4);\n\n   //! Mutex used to protect FSClientBody data.\n   be2_struct<OSFastMutex> mutex;\n\n   UNKNOWN(4);\n\n   //! Alarm used by fsm for unknown reasons.\n   be2_struct<OSAlarm> fsmAlarm;\n\n   //! Error of last FS command.\n   be2_val<FSAStatus> lastError;\n\n   be2_val<BOOL> isLastErrorWithoutVolume;\n\n   //! Message used to send FsCmdHandler message when FSA async callback is received.\n   be2_struct<FSMessage> fsCmdHandlerMsg;\n\n   //! Device name of the last mount source returned by FSGetMountSourceNext.\n   be2_array<char, 0x10> lastMountSourceDevice;\n\n   //! Mount source type to find with FSGetMountSourceNext.\n   be2_val<FSMountSourceType> findMountSourceType;\n\n   //! Link used for linked list of clients.\n   be2_struct<FSClientBodyLink> link;\n\n   //! Pointer to unaligned FSClient structure.\n   be2_virt_ptr<FSClient> client;\n};\nCHECK_OFFSET(FSClientBody, 0x1444, clientHandle);\nCHECK_OFFSET(FSClientBody, 0x1448, fsm);\nCHECK_OFFSET(FSClientBody, 0x1480, cmdQueue);\nCHECK_OFFSET(FSClientBody, 0x14C4, lastDequeuedCommand);\nCHECK_OFFSET(FSClientBody, 0x14C8, emulatedError);\nCHECK_OFFSET(FSClientBody, 0x14CC, unk0x14CC);\nCHECK_OFFSET(FSClientBody, 0x14D0, unk0x14D0);\nCHECK_OFFSET(FSClientBody, 0x1560, mutex);\nCHECK_OFFSET(FSClientBody, 0x1590, fsmAlarm);\nCHECK_OFFSET(FSClientBody, 0x15E8, lastError);\nCHECK_OFFSET(FSClientBody, 0x15EC, isLastErrorWithoutVolume);\nCHECK_OFFSET(FSClientBody, 0x15F0, fsCmdHandlerMsg);\nCHECK_OFFSET(FSClientBody, 0x1600, lastMountSourceDevice);\nCHECK_OFFSET(FSClientBody, 0x1610, findMountSourceType);\nCHECK_OFFSET(FSClientBody, 0x1614, link);\nCHECK_OFFSET(FSClientBody, 0x161C, client);\n\nFSStatus\nFSAddClientEx(virt_ptr<FSClient> client,\n              virt_ptr<FSAttachParams> attachParams,\n              FSErrorFlag errorMask);\n\nFSStatus\nFSAddClient(virt_ptr<FSClient> client,\n            FSErrorFlag errorMask);\n\nFSStatus\nFSDelClient(virt_ptr<FSClient> client,\n            FSErrorFlag errorMask);\n\nvirt_ptr<FSCmdBlock>\nFSGetCurrentCmdBlock(virt_ptr<FSClient> client);\n\nFSAStatus\nFSGetEmulatedError(virt_ptr<FSClient> client);\n\nFSStatus\nFSSetEmulatedError(virt_ptr<FSClient> client,\n                   FSAStatus error);\n\nint32_t\nFSGetErrorCodeForViewer(virt_ptr<FSClient> client,\n                        virt_ptr<FSCmdBlock> block);\n\nint32_t\nFSGetLastErrorCodeForViewer(virt_ptr<FSClient> client);\n\nFSAStatus\nFSGetLastError(virt_ptr<FSClient> client);\n\nFSVolumeState\nFSGetVolumeState(virt_ptr<FSClient> client);\n\nnamespace internal\n{\n\nvirt_ptr<FSClientBody>\nfsClientGetBody(virt_ptr<FSClient> client);\n\nvoid\nfsClientHandleFatalError(virt_ptr<FSClientBody> clientBody,\n                         FSAStatus error);\n\nFSStatus\nfsClientHandleShimPrepareError(virt_ptr<FSClientBody> clientBody,\n                               FSAStatus error);\n\nFSStatus\nfsClientHandleAsyncResult(virt_ptr<FSClient> client,\n                          virt_ptr<FSCmdBlock> block,\n                          FSStatus result,\n                          FSErrorFlag errorMask);\n\nvoid\nfsClientSubmitCommand(virt_ptr<FSClientBody> clientBody,\n                      virt_ptr<FSCmdBlockBody> blockBody,\n                      FSFinishCmdFn finishCmdFn);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmd.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_cosreport.h\"\n#include \"coreinit_fs_client.h\"\n#include \"coreinit_fs_cmd.h\"\n#include \"coreinit_fs_cmdblock.h\"\n#include \"coreinit_fsa_shim.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n\n#include <common/strutils.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nnamespace internal\n{\n\nstatic FSStatus\nreadFileWithPosAsync(virt_ptr<FSClient> client,\n                     virt_ptr<FSCmdBlock> block,\n                     virt_ptr<uint8_t> buffer,\n                     uint32_t size,\n                     uint32_t count,\n                     FSFilePosition pos,\n                     FSFileHandle handle,\n                     FSReadFlag readFlags,\n                     FSErrorFlag errorMask,\n                     virt_ptr<const FSAsyncData> asyncData);\n\nstatic FSStatus\nwriteFileWithPosAsync(virt_ptr<FSClient> client,\n                      virt_ptr<FSCmdBlock> block,\n                      virt_ptr<const uint8_t> buffer,\n                      uint32_t size,\n                      uint32_t count,\n                      FSFilePosition pos,\n                      FSFileHandle handle,\n                      FSWriteFlag writeFlags,\n                      FSErrorFlag errorMask,\n                      virt_ptr<const FSAsyncData> asyncData);\n\nstatic FSStatus\ngetInfoByQueryAsync(virt_ptr<FSClient> client,\n                    virt_ptr<FSCmdBlock> block,\n                    virt_ptr<const char> path,\n                    FSAQueryInfoType type,\n                    virt_ptr<void> out,\n                    FSErrorFlag errorMask,\n                    virt_ptr<const FSAsyncData> asyncData);\n\n} // namespace internal\n\n\n/**\n * Allocate space at end of file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSAppendFile(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             uint32_t size,\n             uint32_t count,\n             FSFileHandle handle,\n             FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSAppendFileAsync(client, block, size, count, handle,\n                                   errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Allocate space at end of file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSAppendFileAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  uint32_t size,\n                  uint32_t count,\n                  FSFileHandle handle,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   auto error = internal::fsaShimPrepareRequestAppendFile(virt_addrof(blockBody->fsaShimBuffer),\n                                                          clientBody->clientHandle,\n                                                          handle,\n                                                          size,\n                                                          count,\n                                                          0);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Mount source path to target path.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSBindMount(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            virt_ptr<const char> sourcePath,\n            virt_ptr<const char> targetPath,\n            FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSBindMountAsync(client, block, sourcePath, targetPath,\n                                  errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Mount source path to target path.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSBindMountAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 virt_ptr<const char> sourcePath,\n                 virt_ptr<const char> targetPath,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask | FSErrorFlag::NotFound,\n                                                  asyncData);\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!sourcePath || !targetPath) {\n      internal::COSError(COSReportModule::Unknown5,\n                         \"FS: FSBindMount: source or target is null.\");\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   if (sourcePath[0] != '/') {\n      internal::COSError(\n         COSReportModule::Unknown5,\n         fmt::format(\n            \"FS: FSBindMount: source must be absolute path, specified path is {}\",\n            sourcePath));\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   if (targetPath[0] != '/') {\n      internal::COSError(\n         COSReportModule::Unknown5,\n         fmt::format(\n            \"FS: FSBindMount: target must be absolute path, specified path is {}\",\n            targetPath));\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   if (strncmp(sourcePath.get(), \"/vol/storage_\", 13)) {\n      internal::COSError(\n         COSReportModule::Unknown5,\n         fmt::format(\n            \"FS: FSBindMount: source must start with \\\"/vol/storage_\\\", specified path is {}\",\n            sourcePath));\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::PermissionError);\n      return FSStatus::FatalError;\n   }\n\n   if (strncmp(targetPath.get(), \"/vol/external\", 13) == 0 ||\n       strncmp(targetPath.get(), \"/vol/hfio\", 9) == 0) {\n      internal::COSError(\n         COSReportModule::Unknown5,\n         fmt::format(\"FS: FSBindMount: target must not start with \\\"/vol/external\\\" or \\\"/vol/hfio\\\", specified path is {}\",\n                     targetPath));\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::PermissionError);\n      return FSStatus::FatalError;\n   }\n\n   blockBody->cmdData.mount.sourceType = FSMountSourceType::Bind;\n   auto error = internal::fsaShimPrepareRequestMount(virt_addrof(blockBody->fsaShimBuffer),\n                                                     clientBody->clientHandle,\n                                                     sourcePath,\n                                                     targetPath,\n                                                     1,\n                                                     nullptr,\n                                                     0);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Unmount target path.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSBindUnmount(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              virt_ptr<const char> targetPath,\n              FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSBindUnmountAsync(client, block, targetPath, errorMask,\n                                    asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Unmount target path.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSBindUnmountAsync(virt_ptr<FSClient> client,\n                   virt_ptr<FSCmdBlock> block,\n                   virt_ptr<const char> targetPath,\n                   FSErrorFlag errorMask,\n                   virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask | FSErrorFlag::NotFound,\n                                                  asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!targetPath) {\n      internal::COSError(COSReportModule::Unknown5,\n                         \"FS: FSBindUnmount: target is null.\");\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   if (targetPath[0] != '/') {\n      internal::COSError(\n         COSReportModule::Unknown5,\n         fmt::format(\"FS: FSBindUnmount: target must be absolute path, specified path is {}\",\n                     targetPath));\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   if (strncmp(targetPath.get(), \"/vol/external\", 13) == 0 ||\n       strncmp(targetPath.get(), \"/vol/hfio\", 9) == 0) {\n      internal::COSError(\n         COSReportModule::Unknown5,\n         fmt::format(\"FS: FSBindUnmount: target must not start with \\\"/vol/external\\\" or \\\"/vol/hfio\\\", specified path is {}\",\n                     targetPath));\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::PermissionError);\n      return FSStatus::FatalError;\n   }\n\n   blockBody->cmdData.unmount.sourceType = FSMountSourceType::Bind;\n   auto error = internal::fsaShimPrepareRequestUnmount(virt_addrof(blockBody->fsaShimBuffer),\n                                                       clientBody->clientHandle,\n                                                       targetPath,\n                                                       0x80000000);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Change the client's working directory.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSChangeDir(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            virt_ptr<const char> path,\n            FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSChangeDirAsync(client, block, path, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Change the client's working directory (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSChangeDirAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 virt_ptr<const char> path,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!path) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   auto error = internal::fsaShimPrepareRequestChangeDir(virt_addrof(blockBody->fsaShimBuffer),\n                                                         clientBody->clientHandle,\n                                                         path);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Change file mode.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSChangeMode(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             virt_ptr<const char> path,\n             uint32_t mode1,\n             uint32_t mode2,\n             FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSChangeModeAsync(client, block, path, mode1, mode2,\n                                   errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Change file mode.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSChangeModeAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  virt_ptr<const char> path,\n                  uint32_t mode1,\n                  uint32_t mode2,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!path) {\n      internal::COSError(COSReportModule::Unknown5,\n                         \"FS: FSChangeMode: path is null.\");\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   auto error =\n      internal::fsaShimPrepareRequestChangeMode(\n         virt_addrof(blockBody->fsaShimBuffer),\n         clientBody->clientHandle,\n         path,\n         mode1,\n         mode2);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Close a directory.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSCloseDir(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           FSDirHandle handle,\n           FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSCloseDirAsync(client, block, handle, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Close a directory (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSCloseDirAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                FSDirHandle handle,\n                FSErrorFlag errorMask,\n                virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   auto error = internal::fsaShimPrepareRequestCloseDir(virt_addrof(blockBody->fsaShimBuffer),\n                                                        clientBody->clientHandle,\n                                                        handle);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Close a file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSCloseFile(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            FSFileHandle handle,\n            FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSCloseFileAsync(client, block, handle, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Close a file (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSCloseFileAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 FSFileHandle handle,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   auto error = internal::fsaShimPrepareRequestCloseFile(virt_addrof(blockBody->fsaShimBuffer),\n                                                         clientBody->clientHandle,\n                                                         handle);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Flush the contents of a file to disk.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSFlushFile(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            FSFileHandle handle,\n            FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSFlushFileAsync(client, block, handle, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Flush the contents of a file to disk (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSFlushFileAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 FSFileHandle handle,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   auto error = internal::fsaShimPrepareRequestFlushFile(virt_addrof(blockBody->fsaShimBuffer),\n                                                         clientBody->clientHandle,\n                                                         handle);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * I don't know what this does :).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSFlushQuota(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             virt_ptr<const char> path,\n             FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSFlushQuotaAsync(client, block, path, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * I don't know what this does :) (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSFlushQuotaAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  virt_ptr<const char> path,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!path) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   auto error = internal::fsaShimPrepareRequestFlushQuota(virt_addrof(blockBody->fsaShimBuffer),\n                                                          clientBody->clientHandle,\n                                                          path);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Get the current working directory.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetCwd(virt_ptr<FSClient> client,\n         virt_ptr<FSCmdBlock> block,\n         virt_ptr<char> returnedPath,\n         uint32_t bytes,\n         FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSGetCwdAsync(client, block, returnedPath, bytes,\n                               errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Get the current working directory (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetCwdAsync(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              virt_ptr<char> returnedPath,\n              uint32_t bytes,\n              FSErrorFlag errorMask,\n              virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!returnedPath) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   if (bytes < FSMaxPathLength - 1) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidParam);\n      return FSStatus::FatalError;\n   }\n\n   blockBody->cmdData.getCwd.returnedPath = returnedPath;\n   blockBody->cmdData.getCwd.bytes = bytes;\n\n   auto error = internal::fsaShimPrepareRequestGetCwd(virt_addrof(blockBody->fsaShimBuffer),\n                                                      clientBody->clientHandle);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Get directory size.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n *\n * \\retval FSStatus::NotFound\n * Directory not found.\n */\nFSStatus\nFSGetDirSize(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             virt_ptr<const char> path,\n             virt_ptr<uint64_t> outDirSize,\n             FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSGetDirSizeAsync(client, block, path, outDirSize,\n                                   errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Get directory size.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetDirSizeAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  virt_ptr<const char> path,\n                  virt_ptr<uint64_t> outDirSize,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData)\n{\n   return internal::getInfoByQueryAsync(client, block, path,\n                                        FSAQueryInfoType::DirSize,\n                                        outDirSize,\n                                        errorMask, asyncData);\n}\n\n\n/**\n * Get free space for entry at path.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n *\n * \\retval FSStatus::NotFound\n * Entry not found.\n */\nFSStatus\nFSGetFreeSpaceSize(virt_ptr<FSClient> client,\n                   virt_ptr<FSCmdBlock> block,\n                   virt_ptr<const char> path,\n                   virt_ptr<uint64_t> outFreeSize,\n                   FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSGetFreeSpaceSizeAsync(client, block, path, outFreeSize,\n                                         errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Get free space for entry at path.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetFreeSpaceSizeAsync(virt_ptr<FSClient> client,\n                        virt_ptr<FSCmdBlock> block,\n                        virt_ptr<const char> path,\n                        virt_ptr<uint64_t> outFreeSize,\n                        FSErrorFlag errorMask,\n                        virt_ptr<const FSAsyncData> asyncData)\n{\n   return internal::getInfoByQueryAsync(client, block, path,\n                                        FSAQueryInfoType::FreeSpaceSize,\n                                        outFreeSize,\n                                        errorMask, asyncData);\n}\n\n\n/**\n * Get the first mount source which matches the specified type.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetMountSource(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 FSMountSourceType type,\n                 virt_ptr<FSMountSource> source,\n                 FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSGetMountSourceAsync(client, block, type, source, errorMask,\n                                       asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Get the first mount source which matches the specified type (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetMountSourceAsync(virt_ptr<FSClient> client,\n                      virt_ptr<FSCmdBlock> block,\n                      FSMountSourceType type,\n                      virt_ptr<FSMountSource> source,\n                      FSErrorFlag errorMask,\n                      virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n\n   if (!clientBody) {\n      return FSStatus::FatalError;\n   }\n\n   if (type != FSMountSourceType::SdCard && type != FSMountSourceType::HostFileIO) {\n      return FSStatus::FatalError;\n   }\n\n   clientBody->lastMountSourceDevice[0] = char { 0 };\n   clientBody->findMountSourceType = type;\n\n   return FSGetMountSourceNextAsync(client, block, source, errorMask, asyncData);\n}\n\n\n/**\n * Get the next mount source.\n *\n * This can be called repeatedly after FSGetMountSource until failure.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n *\n * \\retval FSStatus::End\n * Returned when we have iterated over all the mount sources for this type.\n */\nFSStatus\nFSGetMountSourceNext(virt_ptr<FSClient> client,\n                     virt_ptr<FSCmdBlock> block,\n                     virt_ptr<FSMountSource> source,\n                     FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSGetMountSourceNextAsync(client, block, source, errorMask,\n                                           asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Get the next mount source (asynchronously).\n *\n * This can be called repeatedly after FSGetMountSource until failure.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetMountSourceNextAsync(virt_ptr<FSClient> client,\n                          virt_ptr<FSCmdBlock> block,\n                          virt_ptr<FSMountSource> source,\n                          FSErrorFlag errorMask,\n                          virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!source) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   blockBody->cmdData.getMountSourceNext.source = source;\n   blockBody->cmdData.getMountSourceNext.dirHandle = -1;\n   auto error = internal::fsaShimPrepareRequestOpenDir(virt_addrof(blockBody->fsaShimBuffer),\n                                                       clientBody->clientHandle,\n                                                       make_stack_string(\"/dev\"));\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody,\n                                   blockBody,\n                                   internal::FinishGetMountSourceNextOpenCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Get the current read / write position of a file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetPosFile(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             FSFileHandle handle,\n             virt_ptr<FSFilePosition> outPos,\n             FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSGetPosFileAsync(client, block, handle, outPos,\n                                   errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Get the current read / write position of a file (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetPosFileAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  FSFileHandle handle,\n                  virt_ptr<FSFilePosition> outPos,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!outPos) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   blockBody->cmdData.getPosFile.pos = outPos;\n   auto error = internal::fsaShimPrepareRequestGetPosFile(virt_addrof(blockBody->fsaShimBuffer),\n                                                          clientBody->clientHandle,\n                                                          handle);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Get statistics about a filesystem entry.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n *\n * \\retval FSStatus::NotFound\n * Entry not found.\n */\nFSStatus\nFSGetStat(virt_ptr<FSClient> client,\n          virt_ptr<FSCmdBlock> block,\n          virt_ptr<const char> path,\n          virt_ptr<FSStat> outStat,\n          FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSGetStatAsync(client, block, path, outStat,\n                                errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Get statistics about a filesystem entry (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetStatAsync(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               virt_ptr<const char> path,\n               virt_ptr<FSStat> outStat,\n               FSErrorFlag errorMask,\n               virt_ptr<const FSAsyncData> asyncData)\n{\n   return internal::getInfoByQueryAsync(client, block, path,\n                                        FSAQueryInfoType::Stat, outStat,\n                                        errorMask, asyncData);\n}\n\n\n/**\n * Get statistics about an opened file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetStatFile(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              FSFileHandle handle,\n              virt_ptr<FSStat> outStat,\n              FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSGetStatFileAsync(client, block, handle, outStat,\n                                    errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Get statistics about an opened file (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSGetStatFileAsync(virt_ptr<FSClient> client,\n                   virt_ptr<FSCmdBlock> block,\n                   FSFileHandle handle,\n                   virt_ptr<FSStat> outStat,\n                   FSErrorFlag errorMask,\n                   virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   blockBody->cmdData.statFile.stat = outStat;\n\n   auto error = internal::fsaShimPrepareRequestStatFile(virt_addrof(blockBody->fsaShimBuffer),\n                                                        clientBody->clientHandle,\n                                                        handle);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Checks if current file position is at the end of the file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n *\n * \\retval FSStatus::OK\n * Returns FSStatus::OK when not at the end of the file.\n *\n * \\retval FSStatus::End\n * Returns FSStatus::End when at the end of the file.\n */\nFSStatus\nFSIsEof(virt_ptr<FSClient> client,\n        virt_ptr<FSCmdBlock> block,\n        FSFileHandle handle,\n        FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSIsEofAsync(client, block, handle, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Checks if current file position is at the end of the file (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSIsEofAsync(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             FSFileHandle handle,\n             FSErrorFlag errorMask,\n             virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   auto error = internal::fsaShimPrepareRequestIsEof(virt_addrof(blockBody->fsaShimBuffer),\n                                                     clientBody->clientHandle,\n                                                     handle);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Create a directory.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSMakeDir(virt_ptr<FSClient> client,\n          virt_ptr<FSCmdBlock> block,\n          virt_ptr<const char> path,\n          FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSMakeDirAsync(client, block, path,\n                                errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Create a directory (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSMakeDirAsync(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               virt_ptr<const char> path,\n               FSErrorFlag errorMask,\n               virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!path) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   auto error = internal::fsaShimPrepareRequestMakeDir(virt_addrof(blockBody->fsaShimBuffer),\n                                                       clientBody->clientHandle,\n                                                       path, 0x660);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Mount a mount source.\n *\n * The mounted path is returned in target.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSMount(virt_ptr<FSClient> client,\n        virt_ptr<FSCmdBlock> block,\n        virt_ptr<FSMountSource> source,\n        virt_ptr<char> target,\n        uint32_t bytes,\n        FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSMountAsync(client, block, source, target, bytes,\n                              errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Mount a mount source (asynchronously).\n *\n * The mounted path is returned in target.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSMountAsync(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             virt_ptr<FSMountSource> source,\n             virt_ptr<char> target,\n             uint32_t bytes,\n             FSErrorFlag errorMask,\n             virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  static_cast<FSErrorFlag>(errorMask | FSErrorFlag::Exists),\n                                                  asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!source || !target || bytes < FSMaxMountPathLength) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   if (source->sourceType != FSMountSourceType::SdCard &&\n       source->sourceType != FSMountSourceType::HostFileIO) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidParam);\n      return FSStatus::FatalError;\n   }\n\n   // Set target path as /vol/<source path>\n   std::memcpy(target.get(), \"/vol/\", 5);\n   string_copy(target.get() + 5,\n               virt_addrof(source->path).get(),\n               bytes - 6);\n\n   blockBody->cmdData.mount.sourceType = source->sourceType;\n   auto error = internal::fsaShimPrepareRequestMount(virt_addrof(blockBody->fsaShimBuffer),\n                                                     clientBody->clientHandle,\n                                                     virt_addrof(source->path),\n                                                     target,\n                                                     0,\n                                                     nullptr, 0);\n\n   // Correct the device path.\n   auto devicePath = virt_addrof(blockBody->fsaShimBuffer.request.mount.path);\n   auto sourcePath = virt_addrof(source->path);\n\n   if (strncmp(sourcePath.get(), \"external\", 8) == 0) {\n      // external01 to /dev/sdcard01\n      std::memcpy(devicePath.get(), \"/dev/sdcard\", 11);\n      string_copy(devicePath.get() + 11,\n                  sourcePath.get() + 8,\n                  FSMaxPathLength - 11);\n   } else {\n      // <source path> to /dev/<source path>\n      std::memcpy(devicePath.get(), \"/dev/\", 5);\n      string_copy(devicePath.get() + 5,\n                  sourcePath.get(),\n                  FSMaxPathLength - 5);\n   }\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody,\n                                   blockBody,\n                                   internal::FinishMountCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Open a directory for iterating it's content.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n *\n * \\retval FSStatus::NotFound\n * Directory not found.\n *\n * \\retval FSStatus::NotDirectory\n * Used OpenDir on a non-directory object such as a file.\n *\n * \\retval FSStatus::PermissionError\n * Did not have permission to open the directory in specified mode.\n */\nFSStatus\nFSOpenDir(virt_ptr<FSClient> client,\n          virt_ptr<FSCmdBlock> block,\n          virt_ptr<const char> path,\n          virt_ptr<FSDirHandle> outHandle,\n          FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSOpenDirAsync(client, block, path, outHandle,\n                                errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Open a directory for iterating it's content (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSOpenDirAsync(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               virt_ptr<const char> path,\n               virt_ptr<FSDirHandle> outHandle,\n               FSErrorFlag errorMask,\n               virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!outHandle) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   if (!path) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   blockBody->cmdData.openDir.handle = outHandle;\n   auto error = internal::fsaShimPrepareRequestOpenDir(virt_addrof(blockBody->fsaShimBuffer),\n                                                        clientBody->clientHandle,\n                                                        path);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Open a file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n *\n * \\retval FSStatus::NotFound\n * File not found.\n *\n * \\retval FSStatus::NotFile\n * Used OpenFile on a non-file object such as a directory.\n *\n * \\retval FSStatus::PermissionError\n * Did not have permission to open file in specified mode.\n */\nFSStatus\nFSOpenFile(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           virt_ptr<const char> path,\n           virt_ptr<const char> mode,\n           virt_ptr<FSFileHandle> outHandle,\n           FSErrorFlag errorMask)\n{\n   return FSOpenFileEx(client, block, path, mode,\n                       0x660, 0, 0,\n                       outHandle, errorMask);\n}\n\n\n/**\n * Open a file (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSOpenFileAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                virt_ptr<const char> path,\n                virt_ptr<const char> mode,\n                virt_ptr<FSFileHandle> outHandle,\n                FSErrorFlag errorMask,\n                virt_ptr<const FSAsyncData> asyncData)\n{\n   return FSOpenFileExAsync(client, block, path, mode,\n                            0x660, 0, 0,\n                            outHandle, errorMask, asyncData);\n}\n\n\n/**\n * Open a file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n *\n * \\retval FSStatus::NotFound\n * File not found.\n *\n * \\retval FSStatus::NotFile\n * Used OpenFile on a non-file object such as a directory.\n *\n * \\retval FSStatus::PermissionError\n * Did not have permission to open file in specified mode.\n */\nFSStatus\nFSOpenFileEx(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             virt_ptr<const char> path,\n             virt_ptr<const char> mode,\n             uint32_t unk1,\n             uint32_t unk2,\n             uint32_t unk3,\n             virt_ptr<FSFileHandle> outHandle,\n             FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSOpenFileExAsync(client, block, path, mode,\n                                   unk1, unk2, unk3,\n                                   outHandle, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Open a file (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSOpenFileExAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  virt_ptr<const char> path,\n                  virt_ptr<const char> mode,\n                  uint32_t unk1,\n                  uint32_t unk2,\n                  uint32_t unk3,\n                  virt_ptr<FSFileHandle> outHandle,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!outHandle) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   if (!path) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   if (!mode) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidParam);\n      return FSStatus::FatalError;\n   }\n\n   blockBody->cmdData.openFile.handle = outHandle;\n   auto error = internal::fsaShimPrepareRequestOpenFile(virt_addrof(blockBody->fsaShimBuffer),\n                                                        clientBody->clientHandle,\n                                                        path, mode,\n                                                        unk1, unk2, unk3);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Read the next entry in a directory.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSReadDir(virt_ptr<FSClient> client,\n          virt_ptr<FSCmdBlock> block,\n          FSDirHandle handle,\n          virt_ptr<FSDirEntry> outDirEntry,\n          FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSReadDirAsync(client, block, handle, outDirEntry,\n                                errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Read the next entry in a directory (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSReadDirAsync(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               FSDirHandle handle,\n               virt_ptr<FSDirEntry> outDirEntry,\n               FSErrorFlag errorMask,\n               virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!outDirEntry) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   blockBody->cmdData.readDir.entry = outDirEntry;\n\n   auto error = internal::fsaShimPrepareRequestReadDir(virt_addrof(blockBody->fsaShimBuffer),\n                                                       clientBody->clientHandle,\n                                                       handle);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Read a file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSReadFile(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           virt_ptr<uint8_t> buffer,\n           uint32_t size,\n           uint32_t count,\n           FSFileHandle handle,\n           FSReadFlag readFlags,\n           FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSReadFileAsync(client, block, buffer, size, count, handle,\n                                 readFlags, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Read a file (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSReadFileAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                virt_ptr<uint8_t> buffer,\n                uint32_t size,\n                uint32_t count,\n                FSFileHandle handle,\n                FSReadFlag readFlags,\n                FSErrorFlag errorMask,\n                virt_ptr<const FSAsyncData> asyncData)\n{\n   return internal::readFileWithPosAsync(client, block, buffer, size, count,\n                                         0, handle,\n                                         static_cast<FSReadFlag>(readFlags & ~FSReadFlag::ReadWithPos),\n                                         errorMask, asyncData);\n}\n\n\n/**\n * Read a file at a specific position.\n *\n * The files position will be set before reading.\n *\n * This is equivalent to:\n *    FSSetPosFile(file, pos)\n *    FSReadFile(file, ...)\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSReadFileWithPos(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  virt_ptr<uint8_t> buffer,\n                  uint32_t size,\n                  uint32_t count,\n                  FSFilePosition pos,\n                  FSFileHandle handle,\n                  FSReadFlag readFlags,\n                  FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSReadFileWithPosAsync(client, block, buffer, size, count,\n                                        pos, handle, readFlags, errorMask,\n                                        asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Read a file at a specific position (asynchronously).\n *\n * The files position will be set before reading.\n *\n * This is equivalent to:\n *    FSSetPosFile(file, pos)\n *    FSReadFile(file, ...)\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSReadFileWithPosAsync(virt_ptr<FSClient> client,\n                       virt_ptr<FSCmdBlock> block,\n                       virt_ptr<uint8_t> buffer,\n                       uint32_t size,\n                       uint32_t count,\n                       FSFilePosition pos,\n                       FSFileHandle handle,\n                       FSReadFlag readFlags,\n                       FSErrorFlag errorMask,\n                       virt_ptr<const FSAsyncData> asyncData)\n{\n   return internal::readFileWithPosAsync(client, block, buffer, size, count,\n                                         pos, handle,\n                                         static_cast<FSReadFlag>(readFlags | FSReadFlag::ReadWithPos),\n                                         errorMask, asyncData);\n}\n\n\n/**\n * Delete a file or directory.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSRemove(virt_ptr<FSClient> client,\n         virt_ptr<FSCmdBlock> block,\n         virt_ptr<const char> path,\n         FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSRemoveAsync(client, block, path, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Delete a file or directory (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSRemoveAsync(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              virt_ptr<const char> path,\n              FSErrorFlag errorMask,\n              virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!path) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   auto error = internal::fsaShimPrepareRequestRemove(virt_addrof(blockBody->fsaShimBuffer),\n                                                      clientBody->clientHandle,\n                                                      path);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Rename a file or directory (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n *\n * \\retval FSStatus::NotFound\n * Entry not found.\n */\nFSStatus\nFSRename(virt_ptr<FSClient> client,\n         virt_ptr<FSCmdBlock> block,\n         virt_ptr<const char> oldPath,\n         virt_ptr<const char> newPath,\n         FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSRenameAsync(client, block, oldPath, newPath,\n                               errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Rename a file or directory (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSRenameAsync(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              virt_ptr<const char> oldPath,\n              virt_ptr<const char> newPath,\n              FSErrorFlag errorMask,\n              virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!oldPath) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   if (!newPath) {\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   auto error = internal::fsaShimPrepareRequestRename(virt_addrof(blockBody->fsaShimBuffer),\n                                                      clientBody->clientHandle,\n                                                      oldPath,\n                                                      newPath);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Rewind the read directory iterator back to the beginning.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSRewindDir(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            FSDirHandle handle,\n            FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSRewindDirAsync(client, block, handle, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Rewind the read directory iterator back to the beginning (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSRewindDirAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 FSDirHandle handle,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   auto error = internal::fsaShimPrepareRequestRewindDir(virt_addrof(blockBody->fsaShimBuffer),\n                                                         clientBody->clientHandle,\n                                                         handle);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Set the current read / write position for a file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSSetPosFile(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             FSFileHandle handle,\n             FSFilePosition pos,\n             FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSSetPosFileAsync(client, block, handle, pos,\n                                   errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Set the current read / write position for a file (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSSetPosFileAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  FSFileHandle handle,\n                  FSFilePosition pos,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   auto error = internal::fsaShimPrepareRequestSetPosFile(virt_addrof(blockBody->fsaShimBuffer),\n                                                          clientBody->clientHandle,\n                                                          handle,\n                                                          pos);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Truncate a file to it's current position.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSTruncateFile(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               FSFileHandle handle,\n               FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSTruncateFileAsync(client, block, handle, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Truncate a file to it's current position (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSTruncateFileAsync(virt_ptr<FSClient> client,\n                    virt_ptr<FSCmdBlock> block,\n                    FSFileHandle handle,\n                    FSErrorFlag errorMask,\n                    virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   auto error = internal::fsaShimPrepareRequestTruncateFile(virt_addrof(blockBody->fsaShimBuffer),\n                                                            clientBody->clientHandle,\n                                                            handle);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Unmount a target which was previously mounted with FSMount.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSUnmount(virt_ptr<FSClient> client,\n          virt_ptr<FSCmdBlock> block,\n          virt_ptr<const char> target,\n          FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSUnmountAsync(client, block, target, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Unmount a target which was previously mounted with FSMount (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSUnmountAsync(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               virt_ptr<const char> target,\n               FSErrorFlag errorMask,\n               virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   auto result = internal::fsCmdBlockPrepareAsync(clientBody, blockBody,\n                                                  errorMask, asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!target) {\n      internal::COSError(COSReportModule::Unknown5,\n                         \"FS: FSUnmount: target is null.\");\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   if (strncmp(target.get(), \"/vol/external01\", 15) == 0) {\n      blockBody->cmdData.unmount.sourceType = FSMountSourceType::SdCard;\n   } else if (strncmp(target.get(), \"/vol/hfio01\", 11) == 0) {\n      blockBody->cmdData.unmount.sourceType = FSMountSourceType::HostFileIO;\n   } else {\n      internal::COSError(COSReportModule::Unknown5,\n                         \"FS: FSUnmount: target specifies invalid mount path.\");\n      internal::fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   auto error = internal::fsaShimPrepareRequestUnmount(virt_addrof(blockBody->fsaShimBuffer),\n                                                       clientBody->clientHandle,\n                                                       target,\n                                                       0x80000000);\n\n   if (error) {\n      return internal::fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   internal::fsClientSubmitCommand(clientBody, blockBody, internal::FinishCmd);\n   return FSStatus::OK;\n}\n\n\n/**\n * Write to a file.\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSWriteFile(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            virt_ptr<const uint8_t> buffer,\n            uint32_t size,\n            uint32_t count,\n            FSFileHandle handle,\n            FSWriteFlag writeFlags,\n            FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSWriteFileAsync(client, block, buffer, size, count, handle,\n                                  writeFlags, errorMask, asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Write to a file (asynchronously).\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSWriteFileAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 virt_ptr<const uint8_t> buffer,\n                 uint32_t size,\n                 uint32_t count,\n                 FSFileHandle handle,\n                 FSWriteFlag writeFlags,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData)\n{\n   return internal::writeFileWithPosAsync(client, block, buffer, size, count,\n                                          0, handle,\n                                          static_cast<FSWriteFlag>(writeFlags & ~FSWriteFlag::WriteWithPos),\n                                          errorMask, asyncData);\n}\n\n\n/**\n * Write to a file at a specific position.\n *\n * The files position will be set before writing.\n *\n * This is equivalent to:\n *    FSSetPosFile(file, pos)\n *    FSWriteFile(file, ...)\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSWriteFileWithPos(virt_ptr<FSClient> client,\n                   virt_ptr<FSCmdBlock> block,\n                   virt_ptr<const uint8_t> buffer,\n                   uint32_t size,\n                   uint32_t count,\n                   FSFilePosition pos,\n                   FSFileHandle handle,\n                   FSWriteFlag writeFlags,\n                   FSErrorFlag errorMask)\n{\n   auto asyncData = StackObject<FSAsyncData> { };\n   internal::fsCmdBlockPrepareSync(client, block, asyncData);\n\n   auto result = FSWriteFileWithPosAsync(client, block, buffer, size, count,\n                                         pos, handle, writeFlags, errorMask,\n                                         asyncData);\n\n   return internal::fsClientHandleAsyncResult(client, block, result,\n                                              errorMask);\n}\n\n\n/**\n * Write to a file at a specific position (asynchronously).\n *\n * The files position will be set before writing.\n *\n * This is equivalent to:\n *    FSSetPosFile(file, pos)\n *    FSWriteFile(file, ...)\n *\n * \\return\n * Returns negative FSStatus error code on failure, FSStatus::OK on success.\n */\nFSStatus\nFSWriteFileWithPosAsync(virt_ptr<FSClient> client,\n                        virt_ptr<FSCmdBlock> block,\n                        virt_ptr<const uint8_t> buffer,\n                        uint32_t size,\n                        uint32_t count,\n                        FSFilePosition pos,\n                        FSFileHandle handle,\n                        FSWriteFlag writeFlags,\n                        FSErrorFlag errorMask,\n                        virt_ptr<const FSAsyncData> asyncData)\n{\n   return internal::writeFileWithPosAsync(client, block, buffer, size, count,\n                                          pos, handle,\n                                          static_cast<FSWriteFlag>(writeFlags | FSWriteFlag::WriteWithPos),\n                                          errorMask, asyncData);\n}\n\n\nnamespace internal\n{\n\nFSStatus\nreadFileWithPosAsync(virt_ptr<FSClient> client,\n                     virt_ptr<FSCmdBlock> block,\n                     virt_ptr<uint8_t> buffer,\n                     uint32_t size,\n                     uint32_t count,\n                     FSFilePosition pos,\n                     FSFileHandle handle,\n                     FSReadFlag readFlags,\n                     FSErrorFlag errorMask,\n                     virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = fsClientGetBody(client);\n   auto blockBody = fsCmdBlockGetBody(block);\n   auto result = fsCmdBlockPrepareAsync(clientBody,\n                                        blockBody,\n                                        errorMask,\n                                        asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   // Ensure size * count is not > 32 bit.\n   auto bytes = uint64_t { size } * uint64_t { count };\n\n   if (bytes > 0xFFFFFFFFull) {\n      fsClientHandleFatalError(clientBody, FSAStatus::InvalidParam);\n      return FSStatus::FatalError;\n   }\n\n   auto bytesRemaining = size * count;\n   blockBody->cmdData.readFile.chunkSize = size;\n   blockBody->cmdData.readFile.bytesRemaining = bytesRemaining;\n   blockBody->cmdData.readFile.bytesRead = 0u;\n\n   // We only read up to FSMaxBytesPerRequest per request.\n   if (bytesRemaining > FSMaxBytesPerRequest) {\n      blockBody->cmdData.readFile.readSize = FSMaxBytesPerRequest;\n   } else {\n      blockBody->cmdData.readFile.readSize = bytesRemaining;\n   }\n\n   auto error = fsaShimPrepareRequestReadFile(virt_addrof(blockBody->fsaShimBuffer),\n                                              clientBody->clientHandle,\n                                              buffer,\n                                              1,\n                                              blockBody->cmdData.readFile.readSize,\n                                              pos,\n                                              handle,\n                                              readFlags);\n\n   if (error) {\n      return fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   fsClientSubmitCommand(clientBody, blockBody, FinishReadCmd);\n   return FSStatus::OK;\n}\n\nstatic FSStatus\ngetInfoByQueryAsync(virt_ptr<FSClient> client,\n                    virt_ptr<FSCmdBlock> block,\n                    virt_ptr<const char> path,\n                    FSAQueryInfoType type,\n                    virt_ptr<void> out,\n                    FSErrorFlag errorMask,\n                    virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = fsClientGetBody(client);\n   auto blockBody = fsCmdBlockGetBody(block);\n   auto result = fsCmdBlockPrepareAsync(clientBody,\n                                        blockBody,\n                                        errorMask,\n                                        asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   if (!path) {\n      fsClientHandleFatalError(clientBody, FSAStatus::InvalidPath);\n      return FSStatus::FatalError;\n   }\n\n   if (!out) {\n      fsClientHandleFatalError(clientBody, FSAStatus::InvalidBuffer);\n      return FSStatus::FatalError;\n   }\n\n   blockBody->cmdData.getInfoByQuery.out = out;\n\n   auto error = fsaShimPrepareRequestGetInfoByQuery(virt_addrof(blockBody->fsaShimBuffer),\n                                                    clientBody->clientHandle,\n                                                    path, type);\n\n   if (error) {\n      return fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   fsClientSubmitCommand(clientBody, blockBody, FinishCmd);\n   return FSStatus::OK;\n}\n\nFSStatus\nwriteFileWithPosAsync(virt_ptr<FSClient> client,\n                      virt_ptr<FSCmdBlock> block,\n                      virt_ptr<const uint8_t> buffer,\n                      uint32_t size,\n                      uint32_t count,\n                      FSFilePosition pos,\n                      FSFileHandle handle,\n                      FSWriteFlag writeFlags,\n                      FSErrorFlag errorMask,\n                      virt_ptr<const FSAsyncData> asyncData)\n{\n   auto clientBody = fsClientGetBody(client);\n   auto blockBody = fsCmdBlockGetBody(block);\n   auto result = fsCmdBlockPrepareAsync(clientBody,\n                                        blockBody,\n                                        errorMask,\n                                        asyncData);\n\n   if (result != FSStatus::OK) {\n      return result;\n   }\n\n   // Ensure size * count is not > 32 bit.\n   auto bytes = uint64_t { size } * uint64_t { count };\n\n   if (bytes > 0xFFFFFFFFull) {\n      fsClientHandleFatalError(clientBody, FSAStatus::InvalidParam);\n      return FSStatus::FatalError;\n   }\n\n   auto bytesRemaining = size * count;\n   blockBody->cmdData.writeFile.chunkSize = size;\n   blockBody->cmdData.writeFile.bytesRemaining = bytesRemaining;\n   blockBody->cmdData.writeFile.bytesWritten = 0u;\n\n   // We only read up to FSMaxBytesPerRequest per request.\n   if (bytesRemaining > FSMaxBytesPerRequest) {\n      blockBody->cmdData.writeFile.writeSize = FSMaxBytesPerRequest;\n   } else {\n      blockBody->cmdData.writeFile.writeSize = bytesRemaining;\n   }\n\n   auto error = fsaShimPrepareRequestWriteFile(virt_addrof(blockBody->fsaShimBuffer),\n                                               clientBody->clientHandle,\n                                               buffer,\n                                               1,\n                                               blockBody->cmdData.writeFile.writeSize,\n                                               pos,\n                                               handle,\n                                               writeFlags);\n\n   if (error) {\n      return fsClientHandleShimPrepareError(clientBody, error);\n   }\n\n   fsClientSubmitCommand(clientBody, blockBody, FinishWriteCmd);\n   return FSStatus::OK;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFsCmdSymbols()\n{\n   RegisterFunctionExport(FSAppendFile);\n   RegisterFunctionExport(FSAppendFileAsync);\n   RegisterFunctionExport(FSBindMount);\n   RegisterFunctionExport(FSBindMountAsync);\n   RegisterFunctionExport(FSBindUnmount);\n   RegisterFunctionExport(FSBindUnmountAsync);\n   RegisterFunctionExport(FSChangeDir);\n   RegisterFunctionExport(FSChangeDirAsync);\n   RegisterFunctionExport(FSChangeMode);\n   RegisterFunctionExport(FSChangeModeAsync);\n   RegisterFunctionExport(FSCloseDir);\n   RegisterFunctionExport(FSCloseDirAsync);\n   RegisterFunctionExport(FSCloseFile);\n   RegisterFunctionExport(FSCloseFileAsync);\n   RegisterFunctionExport(FSFlushFile);\n   RegisterFunctionExport(FSFlushFileAsync);\n   RegisterFunctionExport(FSFlushQuota);\n   RegisterFunctionExport(FSFlushQuotaAsync);\n   RegisterFunctionExport(FSGetCwd);\n   RegisterFunctionExport(FSGetCwdAsync);\n   RegisterFunctionExport(FSGetDirSize);\n   RegisterFunctionExport(FSGetDirSizeAsync);\n   RegisterFunctionExport(FSGetFreeSpaceSize);\n   RegisterFunctionExport(FSGetFreeSpaceSizeAsync);\n   RegisterFunctionExport(FSGetPosFile);\n   RegisterFunctionExport(FSGetPosFileAsync);\n   RegisterFunctionExport(FSGetStat);\n   RegisterFunctionExport(FSGetStatAsync);\n   RegisterFunctionExport(FSGetStatFile);\n   RegisterFunctionExport(FSGetStatFileAsync);\n   RegisterFunctionExport(FSGetMountSource);\n   RegisterFunctionExport(FSGetMountSourceAsync);\n   RegisterFunctionExport(FSGetMountSourceNext);\n   RegisterFunctionExport(FSGetMountSourceNextAsync);\n   RegisterFunctionExport(FSIsEof);\n   RegisterFunctionExport(FSIsEofAsync);\n   RegisterFunctionExport(FSMakeDir);\n   RegisterFunctionExport(FSMakeDirAsync);\n   RegisterFunctionExport(FSMount);\n   RegisterFunctionExport(FSMountAsync);\n   RegisterFunctionExport(FSOpenDir);\n   RegisterFunctionExport(FSOpenDirAsync);\n   RegisterFunctionExport(FSOpenFile);\n   RegisterFunctionExport(FSOpenFileAsync);\n   RegisterFunctionExport(FSOpenFileEx);\n   RegisterFunctionExport(FSOpenFileExAsync);\n   RegisterFunctionExport(FSReadDir);\n   RegisterFunctionExport(FSReadDirAsync);\n   RegisterFunctionExport(FSReadFile);\n   RegisterFunctionExport(FSReadFileAsync);\n   RegisterFunctionExport(FSReadFileWithPos);\n   RegisterFunctionExport(FSReadFileWithPosAsync);\n   RegisterFunctionExport(FSRemove);\n   RegisterFunctionExport(FSRemoveAsync);\n   RegisterFunctionExport(FSRename);\n   RegisterFunctionExport(FSRenameAsync);\n   RegisterFunctionExport(FSRewindDir);\n   RegisterFunctionExport(FSRewindDirAsync);\n   RegisterFunctionExport(FSSetPosFile);\n   RegisterFunctionExport(FSSetPosFileAsync);\n   RegisterFunctionExport(FSTruncateFile);\n   RegisterFunctionExport(FSTruncateFileAsync);\n   RegisterFunctionExport(FSUnmount);\n   RegisterFunctionExport(FSUnmountAsync);\n   RegisterFunctionExport(FSWriteFile);\n   RegisterFunctionExport(FSWriteFileAsync);\n   RegisterFunctionExport(FSWriteFileWithPos);\n   RegisterFunctionExport(FSWriteFileWithPosAsync);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmd.h",
    "content": "#pragma once\n#include \"coreinit_fs.h\"\n#include \"coreinit_fsa.h\"\n\nnamespace cafe::coreinit\n{\n\nFSStatus\nFSAppendFile(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             uint32_t size,\n             uint32_t count,\n             FSFileHandle handle,\n             FSErrorFlag errorMask);\n\nFSStatus\nFSAppendFileAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  uint32_t size,\n                  uint32_t count,\n                  FSFileHandle handle,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSBindMount(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            virt_ptr<const char> sourcePath,\n            virt_ptr<const char> targetPath,\n            FSErrorFlag errorMask);\n\nFSStatus\nFSBindMountAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 virt_ptr<const char> sourcePath,\n                 virt_ptr<const char> targetPath,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSBindUnmount(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              virt_ptr<const char> targetPath,\n              FSErrorFlag errorMask);\n\nFSStatus\nFSBindUnmountAsync(virt_ptr<FSClient> client,\n                   virt_ptr<FSCmdBlock> block,\n                   virt_ptr<const char> targetPath,\n                   FSErrorFlag errorMask,\n                   virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSChangeDir(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            virt_ptr<const char> path,\n            FSErrorFlag errorMask);\n\nFSStatus\nFSChangeDirAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 virt_ptr<const char> path,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSChangeMode(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             virt_ptr<const char> path,\n             uint32_t mode1,\n             uint32_t mode2,\n             FSErrorFlag errorMask);\n\nFSStatus\nFSChangeModeAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  virt_ptr<const char> path,\n                  uint32_t mode1,\n                  uint32_t mode2,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSCloseDir(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           FSDirHandle handle,\n           FSErrorFlag errorMask);\n\nFSStatus\nFSCloseDirAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                FSDirHandle handle,\n                FSErrorFlag errorMask,\n                virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSCloseFile(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            FSFileHandle handle,\n            FSErrorFlag errorMask);\n\nFSStatus\nFSCloseFileAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 FSFileHandle handle,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSFlushFile(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            FSFileHandle handle,\n            FSErrorFlag errorMask);\n\nFSStatus\nFSFlushFileAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 FSFileHandle handle,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSFlushQuota(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             virt_ptr<const char> path,\n             FSErrorFlag errorMask);\n\nFSStatus\nFSFlushQuotaAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  virt_ptr<const char> path,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSGetCwd(virt_ptr<FSClient> client,\n         virt_ptr<FSCmdBlock> block,\n         virt_ptr<char> returnedPath,\n         uint32_t bytes,\n         FSErrorFlag errorMask);\n\nFSStatus\nFSGetCwdAsync(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              virt_ptr<char> returnedPath,\n              uint32_t bytes,\n              FSErrorFlag errorMask,\n              virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSGetDirSize(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             virt_ptr<const char> path,\n             virt_ptr<uint64_t> returnedDirSize,\n             FSErrorFlag errorMask);\n\nFSStatus\nFSGetDirSizeAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  virt_ptr<const char> path,\n                  virt_ptr<uint64_t> returnedDirSize,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSGetFreeSpaceSize(virt_ptr<FSClient> client,\n                   virt_ptr<FSCmdBlock> block,\n                   virt_ptr<const char> path,\n                   virt_ptr<uint64_t> returnedFreeSize,\n                   FSErrorFlag errorMask);\n\nFSStatus\nFSGetFreeSpaceSizeAsync(virt_ptr<FSClient> client,\n                        virt_ptr<FSCmdBlock> block,\n                        virt_ptr<const char> path,\n                        virt_ptr<uint64_t> returnedFreeSize,\n                        FSErrorFlag errorMask,\n                        virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSGetMountSource(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 FSMountSourceType type,\n                 virt_ptr<FSMountSource> source,\n                 FSErrorFlag errorMask);\n\nFSStatus\nFSGetMountSourceAsync(virt_ptr<FSClient> client,\n                      virt_ptr<FSCmdBlock> block,\n                      FSMountSourceType type,\n                      virt_ptr<FSMountSource> outSource,\n                      FSErrorFlag errorMask,\n                      virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSGetMountSourceNext(virt_ptr<FSClient> client,\n                     virt_ptr<FSCmdBlock> block,\n                     virt_ptr<FSMountSource> outSource,\n                     FSErrorFlag errorMask);\n\nFSStatus\nFSGetMountSourceNextAsync(virt_ptr<FSClient> client,\n                          virt_ptr<FSCmdBlock> block,\n                          virt_ptr<FSMountSource> outSource,\n                          FSErrorFlag errorMask,\n                          virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSGetPosFile(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             FSFileHandle handle,\n             virt_ptr<FSFilePosition> outPos,\n             FSErrorFlag errorMask);\n\nFSStatus\nFSGetPosFileAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  FSFileHandle handle,\n                  virt_ptr<FSFilePosition> outPos,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSGetStat(virt_ptr<FSClient> client,\n          virt_ptr<FSCmdBlock> block,\n          virt_ptr<const char> path,\n          virt_ptr<FSStat> outStat,\n          FSErrorFlag errorMask);\n\nFSStatus\nFSGetStatAsync(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               virt_ptr<const char> path,\n               virt_ptr<FSStat> outStat,\n               FSErrorFlag errorMask,\n               virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSGetStatFile(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              FSFileHandle handle,\n              virt_ptr<FSStat> outStat,\n              FSErrorFlag errorMask);\n\nFSStatus\nFSGetStatFileAsync(virt_ptr<FSClient> client,\n                   virt_ptr<FSCmdBlock> block,\n                   FSFileHandle handle,\n                   virt_ptr<FSStat> outStat,\n                   FSErrorFlag errorMask,\n                   virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSIsEof(virt_ptr<FSClient> client,\n        virt_ptr<FSCmdBlock> block,\n        FSFileHandle handle,\n        FSErrorFlag errorMask);\n\nFSStatus\nFSIsEofAsync(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             FSFileHandle handle,\n             FSErrorFlag errorMask,\n             virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSMakeDir(virt_ptr<FSClient> client,\n          virt_ptr<FSCmdBlock> block,\n          virt_ptr<const char> path,\n          FSErrorFlag errorMask);\n\nFSStatus\nFSMakeDirAsync(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               virt_ptr<const char> path,\n               FSErrorFlag errorMask,\n               virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSMount(virt_ptr<FSClient> client,\n        virt_ptr<FSCmdBlock> block,\n        virt_ptr<FSMountSource> source,\n        virt_ptr<char> target,\n        uint32_t bytes,\n        FSErrorFlag errorMask);\n\nFSStatus\nFSMountAsync(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             virt_ptr<FSMountSource> source,\n             virt_ptr<char> target,\n             uint32_t bytes,\n             FSErrorFlag errorMask,\n             virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSOpenDir(virt_ptr<FSClient> client,\n          virt_ptr<FSCmdBlock> block,\n          virt_ptr<const char> path,\n          virt_ptr<FSDirHandle> outHandle,\n          FSErrorFlag errorMask);\n\nFSStatus\nFSOpenDirAsync(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               virt_ptr<const char> path,\n               virt_ptr<FSDirHandle> outHandle,\n               FSErrorFlag errorMask,\n               virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSOpenFile(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           virt_ptr<const char> path,\n           virt_ptr<const char> mode,\n           virt_ptr<FSFileHandle> outHandle,\n           FSErrorFlag errorMask);\n\nFSStatus\nFSOpenFileAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                virt_ptr<const char> path,\n                virt_ptr<const char> mode,\n                virt_ptr<FSFileHandle> outHandle,\n                FSErrorFlag errorMask,\n                virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSOpenFileEx(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             virt_ptr<const char> path,\n             virt_ptr<const char> mode,\n             uint32_t unk1,\n             uint32_t unk2,\n             uint32_t unk3,\n             virt_ptr<FSFileHandle> outHandle,\n             FSErrorFlag errorMask);\n\nFSStatus\nFSOpenFileExAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  virt_ptr<const char> path,\n                  virt_ptr<const char> mode,\n                  uint32_t unk1,\n                  uint32_t unk2,\n                  uint32_t unk3,\n                  virt_ptr<FSFileHandle> outHandle,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSReadDir(virt_ptr<FSClient> client,\n          virt_ptr<FSCmdBlock> block,\n          FSDirHandle handle,\n          virt_ptr<FSDirEntry> outDirEntry,\n          FSErrorFlag errorMask);\n\nFSStatus\nFSReadDirAsync(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               FSDirHandle handle,\n               virt_ptr<FSDirEntry> outDirEntry,\n               FSErrorFlag errorMask,\n               virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSReadFile(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           virt_ptr<uint8_t> buffer,\n           uint32_t size,\n           uint32_t count,\n           FSFileHandle handle,\n           FSReadFlag readFlags,\n           FSErrorFlag errorMask);\n\nFSStatus\nFSReadFileAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                virt_ptr<uint8_t> buffer,\n                uint32_t size,\n                uint32_t count,\n                FSFileHandle handle,\n                FSReadFlag readFlags,\n                FSErrorFlag errorMask,\n                virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSReadFileWithPos(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  virt_ptr<uint8_t> buffer,\n                  uint32_t size,\n                  uint32_t count,\n                  FSFilePosition pos,\n                  FSFileHandle handle,\n                  FSReadFlag readFlags,\n                  FSErrorFlag errorMask);\n\nFSStatus\nFSReadFileWithPosAsync(virt_ptr<FSClient> client,\n                       virt_ptr<FSCmdBlock> block,\n                       virt_ptr<uint8_t> buffer,\n                       uint32_t size,\n                       uint32_t count,\n                       FSFilePosition pos,\n                       FSFileHandle handle,\n                       FSReadFlag readFlags,\n                       FSErrorFlag errorMask,\n                       virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSRemove(virt_ptr<FSClient> client,\n         virt_ptr<FSCmdBlock> block,\n         virt_ptr<const char> path,\n         FSErrorFlag errorMask);\n\nFSStatus\nFSRemoveAsync(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              virt_ptr<const char> path,\n              FSErrorFlag errorMask,\n              virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSRename(virt_ptr<FSClient> client,\n         virt_ptr<FSCmdBlock> block,\n         virt_ptr<const char> oldPath,\n         virt_ptr<const char> newPath,\n         FSErrorFlag errorMask);\n\nFSStatus\nFSRenameAsync(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              virt_ptr<const char> oldPath,\n              virt_ptr<const char> newPath,\n              FSErrorFlag errorMask,\n              virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSRewindDir(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            FSDirHandle handle,\n            FSErrorFlag errorMask);\n\nFSStatus\nFSRewindDirAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 FSDirHandle handle,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSSetPosFile(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             FSFileHandle handle,\n             FSFilePosition pos,\n             FSErrorFlag errorMask);\n\nFSStatus\nFSSetPosFileAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  FSFileHandle handle,\n                  FSFilePosition pos,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSTruncateFile(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               FSFileHandle handle,\n               FSErrorFlag errorMask);\n\nFSStatus\nFSTruncateFileAsync(virt_ptr<FSClient> client,\n                    virt_ptr<FSCmdBlock> block,\n                    FSFileHandle handle,\n                    FSErrorFlag errorMask,\n                    virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSUnmount(virt_ptr<FSClient> client,\n          virt_ptr<FSCmdBlock> block,\n          virt_ptr<const char> target,\n          FSErrorFlag errorMask);\n\nFSStatus\nFSUnmountAsync(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               virt_ptr<const char> target,\n               FSErrorFlag errorMask,\n               virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSWriteFile(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            virt_ptr<const uint8_t> buffer,\n            uint32_t size,\n            uint32_t count,\n            FSFileHandle handle,\n            FSWriteFlag writeFlags,\n            FSErrorFlag errorMask);\n\nFSStatus\nFSWriteFileAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 virt_ptr<const uint8_t> buffer,\n                 uint32_t size,\n                 uint32_t count,\n                 FSFileHandle handle,\n                 FSWriteFlag writeFlags,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData);\n\nFSStatus\nFSWriteFileWithPos(virt_ptr<FSClient> client,\n                   virt_ptr<FSCmdBlock> block,\n                   virt_ptr<const uint8_t> buffer,\n                   uint32_t size,\n                   uint32_t count,\n                   FSFilePosition pos,\n                   FSFileHandle handle,\n                   FSWriteFlag writeFlags,\n                   FSErrorFlag errorMask);\n\nFSStatus\nFSWriteFileWithPosAsync(virt_ptr<FSClient> client,\n                        virt_ptr<FSCmdBlock> block,\n                        virt_ptr<const uint8_t> buffer,\n                        uint32_t size,\n                        uint32_t count,\n                        FSFilePosition pos,\n                        FSFileHandle handle,\n                        FSWriteFlag writeFlags,\n                        FSErrorFlag errorMask,\n                        virt_ptr<const FSAsyncData> asyncData);\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmdblock.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_appio.h\"\n#include \"coreinit_fs.h\"\n#include \"coreinit_fs_client.h\"\n#include \"coreinit_fs_driver.h\"\n#include \"coreinit_fs_cmdblock.h\"\n#include \"coreinit_fsa_shim.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\n#include <common/align.h>\n#include <common/log.h>\n#include <common/strutils.h>\n#include <cstring>\n#include <fmt/core.h>\n#include <libcpu/state.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nnamespace internal\n{\n\nFSFinishCmdFn FinishCmd = nullptr;\nFSFinishCmdFn FinishMountCmd = nullptr;\nFSFinishCmdFn FinishReadCmd = nullptr;\nFSFinishCmdFn FinishWriteCmd = nullptr;\nFSFinishCmdFn FinishGetMountSourceNextOpenCmd = nullptr;\nFSFinishCmdFn FinishGetMountSourceNextReadCmd = nullptr;\nFSFinishCmdFn FinishGetMountSourceNextCloseCmd = nullptr;\n\n}\n\n/**\n * Initialise an FSCmdBlock structure.\n */\nvoid\nFSInitCmdBlock(virt_ptr<FSCmdBlock> block)\n{\n   if (!block) {\n      return;\n   }\n\n   std::memset(block.get(), 0, sizeof(FSCmdBlock));\n\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   blockBody->status = FSCmdBlockStatus::Initialised;\n   blockBody->priority = FSDefaultPriority;\n}\n\n\n/**\n * Get the command's priority.\n *\n * \\return\n * Returns positive value on success, or FSStatus error code otherwise.\n */\nFSStatus\nFSGetCmdPriority(virt_ptr<FSCmdBlock> block)\n{\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   if (!blockBody) {\n      return FSStatus::FatalError;\n   }\n\n   return static_cast<FSStatus>(blockBody->priority.value());\n}\n\n\n/**\n * Set the command's priority.\n *\n * \\retval FSStatus::OK\n * Success.\n *\n * \\retval FSStatus::FatalError\n * FSCmdBlock in invalid state, or invalid priority.\n */\nFSStatus\nFSSetCmdPriority(virt_ptr<FSCmdBlock> block,\n                 uint32_t priority)\n{\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   if (!blockBody) {\n      return FSStatus::FatalError;\n   }\n\n   if (priority < FSMinPriority || priority > FSMaxPriority) {\n      return FSStatus::FatalError;\n   }\n\n   if (blockBody->status != FSCmdBlockStatus::Initialised &&\n       blockBody->status != FSCmdBlockStatus::Cancelled) {\n      // Cannot adjust a commands priority once it has been queued.\n      return FSStatus::FatalError;\n   }\n\n   blockBody->priority = static_cast<uint8_t>(priority);\n   return FSStatus::OK;\n}\n\n\n/**\n * Get the FSMessage structure in an FSCmdBlock.\n *\n * \\return\n * Returns the FSMessage structure from the FSCmdBlock's FSAsyncResult.\n */\nvirt_ptr<FSMessage>\nFSGetFSMessage(virt_ptr<FSCmdBlock> block)\n{\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   if (!blockBody) {\n      return nullptr;\n   }\n\n   return virt_addrof(blockBody->asyncResult.ioMsg);\n}\n\n\n/**\n * Get the value stored in FSCmdBlock by FSSetUserData.\n *\n * \\return\n * Returns pointer set by FSSetUserData.\n */\nvirt_ptr<void>\nFSGetUserData(virt_ptr<FSCmdBlock> block)\n{\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   return blockBody->userData;\n}\n\n\n/**\n * Store a user value in FSCmdBlock which can be retrieved by FSGetUserData.\n */\nvoid\nFSSetUserData(virt_ptr<FSCmdBlock> block,\n              virt_ptr<void> userData)\n{\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   blockBody->userData = userData;\n}\n\n\nnamespace internal\n{\n\n/**\n * Get an aligned FSCmdBlockBody from an FSCmdBlock.\n */\nvirt_ptr<FSCmdBlockBody>\nfsCmdBlockGetBody(virt_ptr<FSCmdBlock> cmdBlock)\n{\n   if (!cmdBlock) {\n      return nullptr;\n   }\n\n   auto body = virt_cast<FSCmdBlockBody *>(align_up(virt_cast<virt_addr>(cmdBlock), 0x40));\n   body->cmdBlock = cmdBlock;\n   return body;\n}\n\n\n/**\n * Prepare a FSCmdBlock for an asynchronous operation.\n *\n * \\return\n * Returns a positive value on success, FSStatus error code otherwise.\n */\nFSStatus\nfsCmdBlockPrepareAsync(virt_ptr<FSClientBody> clientBody,\n                       virt_ptr<FSCmdBlockBody> blockBody,\n                       FSErrorFlag errorMask,\n                       virt_ptr<const FSAsyncData> asyncData)\n{\n   decaf_check(clientBody);\n   decaf_check(blockBody);\n\n   if (!internal::fsInitialised()) {\n      return FSStatus::FatalError;\n   }\n\n   if (blockBody->status != FSCmdBlockStatus::Initialised &&\n       blockBody->status != FSCmdBlockStatus::Cancelled) {\n      gLog->error(\"Invalid FSCmdBlockData state {}\", blockBody->status.value());\n      return FSStatus::FatalError;\n   }\n\n   if (asyncData->userCallback && asyncData->ioMsgQueue) {\n      gLog->error(\"userCallback and ioMsgQueue are exclusive.\");\n      return FSStatus::FatalError;\n   }\n\n   blockBody->errorMask = errorMask;\n   blockBody->clientBody = clientBody;\n   return fsAsyncResultInit(clientBody,\n                            virt_addrof(blockBody->asyncResult),\n                            asyncData);\n}\n\n\n/**\n * Prepare a FSCmdBlock for a synchronous operation.\n */\nvoid\nfsCmdBlockPrepareSync(virt_ptr<FSClient> client,\n                      virt_ptr<FSCmdBlock> block,\n                      virt_ptr<FSAsyncData> asyncData)\n{\n   auto blockBody = internal::fsCmdBlockGetBody(block);\n   OSInitMessageQueue(virt_addrof(blockBody->syncQueue),\n                      virt_addrof(blockBody->syncQueueMsgs),\n                      1);\n\n   asyncData->ioMsgQueue = virt_addrof(blockBody->syncQueue);\n}\n\n\n/**\n * Requeues an FS command.\n */\nvoid\nfsCmdBlockRequeue(virt_ptr<FSCmdQueue> queue,\n                  virt_ptr<FSCmdBlockBody> blockBody,\n                  BOOL insertAtFront,\n                  FSFinishCmdFn finishCmdFn)\n{\n   OSFastMutex_Lock(virt_addrof(queue->mutex));\n\n   if (blockBody->cancelFlags & FSCmdCancelFlags::Cancelling) {\n      blockBody->cancelFlags &= ~FSCmdCancelFlags::Cancelling;\n      blockBody->status = FSCmdBlockStatus::Cancelled;\n      blockBody->clientBody->lastDequeuedCommand = nullptr;\n      OSFastMutex_Unlock(virt_addrof(queue->mutex));\n      fsCmdBlockReplyResult(blockBody, FSStatus::Cancelled);\n      return;\n   }\n\n   blockBody->finishCmdFn = finishCmdFn;\n   blockBody->status = FSCmdBlockStatus::QueuedCommand;\n   fsCmdQueueFinishCmd(queue);\n\n   if (insertAtFront) {\n      fsCmdQueuePushFront(queue, blockBody);\n   } else {\n      fsCmdQueueEnqueue(queue, blockBody, true);\n   }\n\n   OSFastMutex_Unlock(virt_addrof(queue->mutex));\n   fsCmdQueueProcessCmd(queue);\n}\n\n\n/**\n * Set the result for an FSCmd.\n *\n * A message will be sent to the user's ioMsgQueue if one was provided or to\n * the AppIO queue where the user's callback will be called instead.\n */\nvoid\nfsCmdBlockSetResult(virt_ptr<FSCmdBlockBody> blockBody,\n                    FSStatus status)\n{\n   blockBody->asyncResult.block = blockBody->cmdBlock;\n   blockBody->asyncResult.status = status;\n\n   if (!OSSendMessage(blockBody->asyncResult.asyncData.ioMsgQueue,\n                      virt_cast<OSMessage *>(virt_addrof(blockBody->asyncResult.ioMsg)),\n                      OSMessageFlags::None)) {\n      decaf_abort(\"fsCmdBlockReplyResult: Could not send async result message\");\n   }\n}\n\n\n/**\n * Calls the blockBody->finishCmdFn with the result of the command.\n */\nvoid\nfsCmdBlockReplyResult(virt_ptr<FSCmdBlockBody> blockBody,\n                      FSStatus status)\n{\n   if (!blockBody) {\n      return;\n   }\n\n   // Finish the current command\n   auto queue = virt_addrof(blockBody->clientBody->cmdQueue);\n   OSFastMutex_Lock(virt_addrof(queue->mutex));\n   fsCmdQueueFinishCmd(queue);\n   OSFastMutex_Unlock(virt_addrof(queue->mutex));\n\n   if (blockBody->finishCmdFn) {\n      cafe::invoke(cpu::this_core::state(),\n                   blockBody->finishCmdFn,\n                   blockBody,\n                   status);\n   }\n\n   // Start off next command\n   fsCmdQueueProcessCmd(queue);\n}\n\n\n/**\n * Called from the AppIO thread to handle the result of an FS command.\n */\nvoid\nfsCmdBlockHandleResult(virt_ptr<FSCmdBlockBody> blockBody)\n{\n   auto clientBody = blockBody->clientBody;\n   auto result = static_cast<FSStatus>(blockBody->fsaStatus.value());\n\n   if (!fsClientRegistered(clientBody)) {\n      if (blockBody->finishCmdFn) {\n         cafe::invoke(cpu::this_core::state(),\n                      blockBody->finishCmdFn,\n                      blockBody,\n                      FSStatus::Cancelled);\n      }\n\n      return;\n   }\n\n   clientBody->lastError = blockBody->fsaStatus;\n\n   if (blockBody->fsaStatus == FSAStatus::MediaNotReady) {\n      fsmSetState(virt_addrof(clientBody->fsm),\n                  FSVolumeState::WrongMedia,\n                  clientBody);\n      return;\n   } else if (blockBody->fsaStatus == FSAStatus::WriteProtected) {\n      fsmSetState(virt_addrof(clientBody->fsm),\n                  FSVolumeState::MediaError,\n                  clientBody);\n      return;\n   }\n\n   if (blockBody->fsaStatus < FSAStatus::OK) {\n      auto errorFlags = FSErrorFlag::All;\n\n      switch (blockBody->fsaStatus) {\n      case FSAStatus::Busy:\n         fsCmdBlockRequeue(virt_addrof(clientBody->cmdQueue), blockBody, TRUE,\n                           blockBody->finishCmdFn);\n         return;\n      case FSAStatus::Cancelled:\n         result = FSStatus::Cancelled;\n         errorFlags = FSErrorFlag::None;\n         break;\n      case FSAStatus::EndOfDir:\n      case FSAStatus::EndOfFile:\n         result = FSStatus::End;\n         errorFlags = FSErrorFlag::None;\n         break;\n      case FSAStatus::MaxMountpoints:\n      case FSAStatus::MaxVolumes:\n      case FSAStatus::MaxClients:\n      case FSAStatus::MaxFiles:\n      case FSAStatus::MaxDirs:\n         errorFlags = FSErrorFlag::Max;\n         result = FSStatus::Max;\n         break;\n      case FSAStatus::AlreadyOpen:\n         errorFlags = FSErrorFlag::AlreadyOpen;\n         result = FSStatus::AlreadyOpen;\n         break;\n      case FSAStatus::NotFound:\n         errorFlags = FSErrorFlag::NotFound;\n         result = FSStatus::NotFound;\n         break;\n      case FSAStatus::AlreadyExists:\n      case FSAStatus::NotEmpty:\n         errorFlags = FSErrorFlag::Exists;\n         result = FSStatus::Exists;\n         break;\n      case FSAStatus::AccessError:\n         errorFlags = FSErrorFlag::AccessError;\n         result = FSStatus::AccessError;\n         break;\n      case FSAStatus::PermissionError:\n         errorFlags = FSErrorFlag::PermissionError;\n         result = FSStatus::PermissionError;\n         break;\n      case FSAStatus::DataCorrupted:\n         // TODO: FSAStatus::DataCorrupted\n         decaf_abort(\"TODO: Reverse me.\");\n         break;\n      case FSAStatus::StorageFull:\n         errorFlags = FSErrorFlag::StorageFull;\n         result = FSStatus::StorageFull;\n         break;\n      case FSAStatus::JournalFull:\n         errorFlags = FSErrorFlag::JournalFull;\n         result = FSStatus::JournalFull;\n         break;\n      case FSAStatus::UnsupportedCmd:\n         errorFlags = FSErrorFlag::UnsupportedCmd;\n         result = FSStatus::UnsupportedCmd;\n         break;\n      case FSAStatus::NotFile:\n         errorFlags = FSErrorFlag::NotFile;\n         result = FSStatus::NotFile;\n         break;\n      case FSAStatus::NotDir:\n         errorFlags = FSErrorFlag::NotDir;\n         result = FSStatus::NotDirectory;\n         break;\n      case FSAStatus::FileTooBig:\n         errorFlags = FSErrorFlag::FileTooBig;\n         result = FSStatus::FileTooBig;\n         break;\n      case FSAStatus::MediaError:\n         // TODO: FSAStatus::MediaError\n         decaf_abort(\"TODO: Reverse me.\");\n         break;\n      default:\n         errorFlags = FSErrorFlag::All;\n      }\n\n      if (errorFlags != FSErrorFlag::None &&\n          (blockBody->errorMask & errorFlags) == 0) {\n         fsmEnterState(virt_addrof(clientBody->fsm),\n                       FSVolumeState::Fatal,\n                       clientBody);\n\n         // The game told us not to return if we receive this error, so must mean\n         // that we really fucked something up :).\n         decaf_abort(fmt::format(\"Unrecoverable FS error, command = {}, error = {}.\",\n                                 blockBody->fsaShimBuffer.command,\n                                 blockBody->fsaStatus));\n\n         return;\n      }\n   }\n\n   if (clientBody->lastDequeuedCommand == blockBody) {\n      clientBody->lastDequeuedCommand = nullptr;\n   }\n\n   fsCmdBlockReplyResult(blockBody, result);\n}\n\n\n/**\n * Copies the IOS command results to FS output.\n *\n * Set as blockBlody->finishCmdFn.\n * Called from fsCmdBlockReplyResult.\n */\nvoid\nfsCmdBlockFinishCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                    FSStatus result)\n{\n   auto clientBody = blockBody->clientBody;\n\n   OSFastMutex_Lock(virt_addrof(clientBody->mutex));\n   blockBody->cancelFlags &= ~FSCmdCancelFlags::Cancelling;\n\n   if (clientBody->lastDequeuedCommand == blockBody) {\n      clientBody->lastDequeuedCommand = nullptr;\n   }\n\n   blockBody->status = FSCmdBlockStatus::Cancelled;\n   OSFastMutex_Unlock(virt_addrof(clientBody->mutex));\n\n   if (result < 0) {\n      fsCmdBlockSetResult(blockBody, result);\n      return;\n   }\n\n   blockBody->unk0x9EA = uint8_t { 0 };\n   blockBody->unk0x9F4 = 0u;\n\n   auto &shim = blockBody->fsaShimBuffer;\n   switch (shim.command) {\n   case FSACommand::Mount:\n   case FSACommand::Unmount:\n   case FSACommand::ChangeDir:\n   case FSACommand::MakeDir:\n   case FSACommand::Remove:\n   case FSACommand::Rename:\n   case FSACommand::RewindDir:\n   case FSACommand::CloseDir:\n   case FSACommand::ReadFile:\n   case FSACommand::WriteFile:\n   case FSACommand::SetPosFile:\n   case FSACommand::IsEof:\n   case FSACommand::CloseFile:\n   case FSACommand::GetError:\n   case FSACommand::FlushFile:\n   case FSACommand::AppendFile:\n   case FSACommand::TruncateFile:\n   case FSACommand::MakeQuota:\n   case FSACommand::FlushQuota:\n   case FSACommand::RollbackQuota:\n   case FSACommand::ChangeMode:\n   case FSACommand::RegisterFlushQuota:\n   case FSACommand::FlushMultiQuota:\n   case FSACommand::RemoveQuota:\n   case FSACommand::MakeLink:\n   {\n      fsCmdBlockSetResult(blockBody, result);\n      break;\n   }\n   case FSACommand::GetVolumeInfo:\n   {\n      auto info = blockBody->cmdData.getVolumeInfo.info;\n      *info = shim.response.getVolumeInfo.volumeInfo;\n      info->unk0x0C = 0u;\n      info->unk0x10 = 0u;\n      info->unk0x14 = -1;\n      info->unk0x18 = -1;\n      fsCmdBlockSetResult(blockBody, result);\n      break;\n   }\n   case FSACommand::OpenDir:\n   {\n      auto handle = blockBody->cmdData.openDir.handle;\n      *handle = shim.response.openDir.handle;\n      fsCmdBlockSetResult(blockBody, result);\n      break;\n   }\n   case FSACommand::ReadDir:\n   {\n      auto entry = blockBody->cmdData.readDir.entry;\n      *entry = shim.response.readDir.entry;\n      fsCmdBlockSetResult(blockBody, result);\n      break;\n   }\n   case FSACommand::OpenFile:\n   case FSACommand::OpenFileByStat:\n   {\n      *blockBody->cmdData.openFile.handle = shim.response.openFile.handle;\n      fsCmdBlockSetResult(blockBody, result);\n      break;\n   }\n   case FSACommand::GetPosFile:\n   {\n      auto pos = blockBody->cmdData.getPosFile.pos;\n      *pos = shim.response.getPosFile.pos;\n      fsCmdBlockSetResult(blockBody, result);\n      break;\n   }\n   case FSACommand::StatFile:\n   {\n      auto stat = blockBody->cmdData.statFile.stat;\n      *stat = shim.response.statFile.stat;\n      fsCmdBlockSetResult(blockBody, result);\n      break;\n   }\n   case FSACommand::GetFileBlockAddress:\n   {\n      auto address = blockBody->cmdData.getFileBlockAddress.address;\n\n      if (address) {\n         *address = shim.response.getFileBlockAddress.address;\n      }\n\n      fsCmdBlockSetResult(blockBody, result);\n      break;\n   }\n   case FSACommand::GetCwd:\n   {\n      auto bytes = blockBody->cmdData.getCwd.bytes;\n      auto returnedPath = blockBody->cmdData.getCwd.returnedPath;\n\n      if (bytes) {\n         auto path = virt_addrof(shim.response.getCwd.path);\n         auto len = static_cast<uint32_t>(std::strlen(path.get()));\n         decaf_check(len < bytes);\n         string_copy(returnedPath.get(), path.get(), bytes);\n         std::memset(returnedPath.get() + len, 0, bytes - len);\n      }\n\n      fsCmdBlockSetResult(blockBody, result);\n      break;\n   }\n   case FSACommand::GetInfoByQuery:\n   {\n      switch (shim.request.getInfoByQuery.type) {\n      case FSAQueryInfoType::FreeSpaceSize:\n      {\n         auto freeSpaceSize = blockBody->cmdData.getInfoByQuery.freeSpaceSize;\n         *freeSpaceSize = shim.response.getInfoByQuery.freeSpaceSize;\n         break;\n      }\n      case FSAQueryInfoType::DirSize:\n      {\n         auto dirSize = blockBody->cmdData.getInfoByQuery.dirSize;\n         *dirSize = shim.response.getInfoByQuery.dirSize;\n         break;\n      }\n      case FSAQueryInfoType::EntryNum:\n      {\n         auto entryNum = blockBody->cmdData.getInfoByQuery.entryNum;\n         *entryNum = shim.response.getInfoByQuery.entryNum;\n         break;\n      }\n      case FSAQueryInfoType::FileSystemInfo:\n      {\n         auto fileSystemInfo = blockBody->cmdData.getInfoByQuery.fileSystemInfo;\n         *fileSystemInfo = shim.response.getInfoByQuery.fileSystemInfo;\n         break;\n      }\n      case FSAQueryInfoType::Stat:\n      {\n         auto stat = blockBody->cmdData.getInfoByQuery.stat;\n         *stat = shim.response.getInfoByQuery.stat;\n         break;\n      }\n      default:\n         decaf_abort(fmt::format(\"Unexpected QueryInfoType: {}\", shim.request.getInfoByQuery.type));\n      }\n\n      fsCmdBlockSetResult(blockBody, result);\n      break;\n   }\n   default:\n      decaf_abort(fmt::format(\"Invalid FSA command {}\", shim.command));\n   }\n}\n\n\n/**\n * Finish a FSACommand::Mount command.\n */\nvoid\nfsCmdBlockFinishMountCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                         FSStatus result)\n{\n   if (result != FSStatus::Exists) {\n      fsCmdBlockFinishCmd(blockBody, result);\n   } else {\n      fsCmdBlockFinishCmd(blockBody, FSStatus::OK);\n   }\n}\n\n\n/**\n * Finish a FSACommand::ReadFile command.\n *\n * Files are read in chunk of up to FSMaxBytesPerRequest bytes per time, this\n * finish function will keep requeuing the command until we have completed\n * the full read.\n */\nvoid\nfsCmdBlockFinishReadCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                        FSStatus result)\n{\n   auto bytesRead = static_cast<uint32_t>(result);\n\n   if (result < 0) {\n      return fsCmdBlockFinishCmd(blockBody, result);\n   }\n\n   // Update read state\n   auto &readState = blockBody->cmdData.readFile;\n   readState.bytesRead += bytesRead;\n   readState.bytesRemaining -= bytesRead;\n\n   // Check if the read is complete\n   if (readState.bytesRemaining == 0 || bytesRead < readState.readSize) {\n      auto chunksRead = readState.bytesRead / readState.chunkSize;\n      return fsCmdBlockFinishCmd(blockBody, static_cast<FSStatus>(chunksRead));\n   }\n\n   // Check if we can read the final chunk yet\n   if (readState.bytesRemaining > FSMaxBytesPerRequest) {\n      readState.readSize = FSMaxBytesPerRequest;\n   } else {\n      readState.readSize = readState.bytesRemaining;\n   }\n\n   // Queue a new read request\n   auto &readRequest = blockBody->fsaShimBuffer.request.readFile;\n   readRequest.buffer = readRequest.buffer + bytesRead;\n   readRequest.size = 1u;\n   readRequest.count = readState.readSize;\n\n   if (readRequest.readFlags & FSReadFlag::ReadWithPos) {\n      readRequest.pos += bytesRead;\n   }\n\n   auto &shim = blockBody->fsaShimBuffer;\n   shim.ioctlvVec[0].vaddr = virt_cast<virt_addr>(virt_addrof(shim.request));\n\n   shim.ioctlvVec[1].vaddr = virt_cast<virt_addr>(readRequest.buffer);\n   shim.ioctlvVec[1].len = readRequest.size;\n\n   shim.ioctlvVec[2].vaddr = virt_cast<virt_addr>(virt_addrof(shim.response));\n   fsCmdBlockRequeue(virt_addrof(blockBody->clientBody->cmdQueue),\n                     blockBody, FALSE,\n                     internal::FinishReadCmd);\n}\n\n\n/**\n * Finish a FSACommand::WriteFile command.\n *\n * Files are written in chunks of up to FSMaxBytesPerRequest bytes per time,\n * this finish function will keep requeuing the command until we have completed\n * the full write.\n */\nvoid\nfsCmdBlockFinishWriteCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                         FSStatus result)\n{\n   auto bytesWritten = static_cast<uint32_t>(result);\n\n   if (result < 0) {\n      return fsCmdBlockFinishCmd(blockBody, result);\n   }\n\n   // Update write state\n   auto &writeState = blockBody->cmdData.writeFile;\n   writeState.bytesWritten += bytesWritten;\n   writeState.bytesRemaining -= bytesWritten;\n\n   // Check if the write is complete\n   if (writeState.bytesRemaining == 0 || bytesWritten < writeState.writeSize) {\n      auto chunksWritten = writeState.bytesWritten / writeState.chunkSize;\n      return fsCmdBlockFinishCmd(blockBody, static_cast<FSStatus>(chunksWritten));\n   }\n\n   // Check if we can write the final chunk yet\n   if (writeState.bytesRemaining > FSMaxBytesPerRequest) {\n      writeState.writeSize = FSMaxBytesPerRequest;\n   } else {\n      writeState.writeSize = writeState.bytesRemaining;\n   }\n\n   // Queue a new write request\n   auto &writeRequest = blockBody->fsaShimBuffer.request.writeFile;\n   writeRequest.buffer = writeRequest.buffer + bytesWritten;\n   writeRequest.size = 1u;\n   writeRequest.count = writeState.writeSize;\n\n   if (writeRequest.writeFlags & FSWriteFlag::WriteWithPos) {\n      writeRequest.pos += bytesWritten;\n   }\n\n   auto &shim = blockBody->fsaShimBuffer;\n   shim.ioctlvVec[0].vaddr = virt_cast<virt_addr>(virt_addrof(shim.request));\n\n   shim.ioctlvVec[1].vaddr = virt_cast<virt_addr>(writeRequest.buffer);\n   shim.ioctlvVec[1].len = writeRequest.size;\n\n   shim.ioctlvVec[2].vaddr = virt_cast<virt_addr>(virt_addrof(shim.response));\n   fsCmdBlockRequeue(virt_addrof(blockBody->clientBody->cmdQueue),\n                     blockBody, FALSE,\n                     internal::FinishWriteCmd);\n}\n\n\nvoid\nfsCmdBlockFinishGetMountSourceNextOpenCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                                          FSStatus result)\n{\n   auto clientBody = blockBody->clientBody;\n\n   if (result != FSStatus::OK) {\n      return fsCmdBlockFinishCmd(blockBody, result);\n   }\n\n   auto &cmdData = blockBody->cmdData.getMountSourceNext;\n\n   if (cmdData.dirHandle == -1) {\n      auto response = virt_addrof(blockBody->fsaShimBuffer.response.openDir);\n      cmdData.dirHandle = response->handle;\n   }\n\n   fsaShimPrepareRequestReadDir(virt_addrof(blockBody->fsaShimBuffer),\n                                clientBody->clientHandle,\n                                cmdData.dirHandle);\n\n   fsCmdBlockRequeue(virt_addrof(blockBody->clientBody->cmdQueue),\n                     blockBody, TRUE,\n                     internal::FinishGetMountSourceNextReadCmd);\n}\n\n\nvoid\nfsCmdBlockFinishGetMountSourceNextReadCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                                          FSStatus result)\n{\n   auto clientBody = blockBody->clientBody;\n   auto &cmdData = blockBody->cmdData.getMountSourceNext;\n   cmdData.readError = result;\n\n   if (result == FSStatus::OK) {\n      auto mountSourceType = FSMountSourceType::Bind;\n      auto deviceName = virt_addrof(blockBody->fsaShimBuffer.response.readDir.entry.name);\n\n      // Check the mount source type\n      if (std::strncmp(deviceName.get(), \"sdcard\", 6) == 0) {\n         mountSourceType = FSMountSourceType::SdCard;\n      } else if (std::strncmp(deviceName.get(), \"hfio\", 4) == 0) {\n         mountSourceType = FSMountSourceType::HostFileIO;\n      }\n\n      if (mountSourceType != clientBody->findMountSourceType) {\n         return fsCmdBlockFinishGetMountSourceNextOpenCmd(blockBody, FSStatus::OK);\n      }\n\n      /* Note that this strncmp will rely on the fact that open / read dir\n       * returns results in a consistent alphabetical order. If that is not the\n       * case then this comparison becomes unreliable.\n       *\n       * FIXME: Unfortunately that is indeed not the case with our filesystem,\n       * but fuck it because we will ?never? have more than 1 sdcard and 1 hfio.\n       */\n      if (std::strncmp(deviceName.get(),\n                       virt_addrof(clientBody->lastMountSourceDevice).get(),\n                       0x10) <= 0) {\n         // Already returned this device, get the next one!\n         return fsCmdBlockFinishGetMountSourceNextOpenCmd(blockBody,\n                                                          FSStatus::OK);\n      }\n\n      // Write to FSMountSource output\n      auto mountSource = cmdData.source;\n      mountSource->sourceType = mountSourceType;\n\n      if (mountSourceType == FSMountSourceType::SdCard) {\n         // Map sdcardXX -> externalXX\n         string_copy(virt_addrof(mountSource->path).get(),\n                     mountSource->path.size(),\n                     \"external\",\n                     8);\n\n         string_copy(virt_addrof(mountSource->path).get() + 8,\n                     mountSource->path.size() - 8,\n                     deviceName.get() + 6,\n                     2);\n\n         mountSource->path[10] = char { 0 };\n      } else if (mountSourceType == FSMountSourceType::HostFileIO) {\n         string_copy(virt_addrof(mountSource->path).get(),\n                     deviceName.get(),\n                     mountSource->path.size());\n      }\n   }\n\n   fsaShimPrepareRequestCloseDir(virt_addrof(blockBody->fsaShimBuffer),\n                                 clientBody->clientHandle,\n                                 cmdData.dirHandle);\n\n   fsCmdBlockRequeue(virt_addrof(blockBody->clientBody->cmdQueue),\n                     blockBody,\n                     TRUE,\n                     internal::FinishGetMountSourceNextCloseCmd);\n}\n\n\nvoid\nfsCmdBlockFinishGetMountSourceNextCloseCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                                           FSStatus result)\n{\n   fsCmdBlockFinishCmd(blockBody,\n                       blockBody->cmdData.getMountSourceNext.readError);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFsCmdBlockSymbols()\n{\n   RegisterFunctionExport(FSInitCmdBlock);\n   RegisterFunctionExport(FSGetCmdPriority);\n   RegisterFunctionExport(FSSetCmdPriority);\n   RegisterFunctionExport(FSGetFSMessage);\n   RegisterFunctionExport(FSGetUserData);\n   RegisterFunctionExport(FSSetUserData);\n\n   RegisterFunctionInternal(internal::fsCmdBlockFinishCmd, internal::FinishCmd);\n   RegisterFunctionInternal(internal::fsCmdBlockFinishMountCmd, internal::FinishMountCmd);\n   RegisterFunctionInternal(internal::fsCmdBlockFinishReadCmd, internal::FinishReadCmd);\n   RegisterFunctionInternal(internal::fsCmdBlockFinishWriteCmd, internal::FinishWriteCmd);\n   RegisterFunctionInternal(internal::fsCmdBlockFinishGetMountSourceNextOpenCmd, internal::FinishGetMountSourceNextOpenCmd);\n   RegisterFunctionInternal(internal::fsCmdBlockFinishGetMountSourceNextReadCmd, internal::FinishGetMountSourceNextReadCmd);\n   RegisterFunctionInternal(internal::fsCmdBlockFinishGetMountSourceNextCloseCmd, internal::FinishGetMountSourceNextCloseCmd);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmdblock.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_fs.h\"\n#include \"coreinit_fsa.h\"\n#include \"coreinit_fsa_shim.h\"\n#include \"coreinit_messagequeue.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\ingroup coreinit_fs\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct FSClientBody;\nstruct FSCmdBlock;\nstruct FSCmdBlockBody;\nstruct FSCmdQueue;\n\nusing FSFinishCmdFn = virt_func_ptr<void(virt_ptr<FSCmdBlockBody>,\n                                         FSStatus)>;\n\nstruct FSCmdBlock\n{\n   be2_array<char, 0xA80> data;\n};\nCHECK_SIZE(FSCmdBlock, 0xA80);\n\nstruct FSCmdBlockBodyLink\n{\n   be2_virt_ptr<FSCmdBlockBody> next;\n   be2_virt_ptr<FSCmdBlockBody> prev;\n};\nCHECK_OFFSET(FSCmdBlockBodyLink, 0x00, next);\nCHECK_OFFSET(FSCmdBlockBodyLink, 0x04, prev);\nCHECK_SIZE(FSCmdBlockBodyLink, 0x8);\n\nstruct FSCmdBlockCmdDataGetCwd\n{\n   be2_virt_ptr<char> returnedPath;\n   be2_val<uint32_t> bytes;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataGetCwd, 0x0, returnedPath);\nCHECK_OFFSET(FSCmdBlockCmdDataGetCwd, 0x4, bytes);\nCHECK_SIZE(FSCmdBlockCmdDataGetCwd, 0x8);\n\nstruct FSCmdBlockCmdDataGetFileBlockAddress\n{\n   be2_virt_ptr<uint32_t> address;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataGetFileBlockAddress, 0x0, address);\nCHECK_SIZE(FSCmdBlockCmdDataGetFileBlockAddress, 0x4);\n\nstruct FSCmdBlockCmdDataGetInfoByQuery\n{\n   union\n   {\n      be2_virt_ptr<void> out;\n      be2_virt_ptr<uint64_t> dirSize;\n      be2_virt_ptr<FSEntryNum> entryNum;\n      be2_virt_ptr<FSAFileSystemInfo> fileSystemInfo;\n      be2_virt_ptr<uint64_t> freeSpaceSize;\n      be2_virt_ptr<FSStat> stat;\n   };\n};\nCHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, out);\nCHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, dirSize);\nCHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, entryNum);\nCHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, fileSystemInfo);\nCHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, freeSpaceSize);\nCHECK_OFFSET(FSCmdBlockCmdDataGetInfoByQuery, 0x0, stat);\nCHECK_SIZE(FSCmdBlockCmdDataGetInfoByQuery, 0x4);\n\nstruct FSCmdBlockCmdDataGetMountSourceNext\n{\n   be2_virt_ptr<FSMountSource> source;\n   be2_val<FSDirHandle> dirHandle;\n   be2_val<FSStatus> readError;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataGetMountSourceNext, 0x0, source);\nCHECK_OFFSET(FSCmdBlockCmdDataGetMountSourceNext, 0x4, dirHandle);\nCHECK_OFFSET(FSCmdBlockCmdDataGetMountSourceNext, 0x8, readError);\nCHECK_SIZE(FSCmdBlockCmdDataGetMountSourceNext, 0xC);\n\nstruct FSCmdBlockCmdDataGetPosFile\n{\n   be2_virt_ptr<FSFilePosition> pos;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataGetPosFile, 0x0, pos);\nCHECK_SIZE(FSCmdBlockCmdDataGetPosFile, 0x4);\n\nstruct FSCmdBlockCmdDataGetVolumeInfo\n{\n   be2_virt_ptr<FSAVolumeInfo> info;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataGetVolumeInfo, 0x0, info);\nCHECK_SIZE(FSCmdBlockCmdDataGetVolumeInfo, 0x4);\n\nstruct FSCmdBlockCmdDataMount\n{\n   UNKNOWN(0x4);\n   be2_val<FSMountSourceType> sourceType;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataMount, 0x4, sourceType);\nCHECK_SIZE(FSCmdBlockCmdDataMount, 0x8);\n\nstruct FSCmdBlockCmdDataOpenDir\n{\n   be2_virt_ptr<FSDirHandle> handle;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataOpenDir, 0x0, handle);\nCHECK_SIZE(FSCmdBlockCmdDataOpenDir, 0x4);\n\nstruct FSCmdBlockCmdDataOpenFile\n{\n   be2_virt_ptr<FSFileHandle> handle;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataOpenFile, 0x0, handle);\nCHECK_SIZE(FSCmdBlockCmdDataOpenFile, 0x4);\n\nstruct FSCmdBlockCmdDataReadDir\n{\n   be2_virt_ptr<FSDirEntry> entry;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataReadDir, 0x0, entry);\nCHECK_SIZE(FSCmdBlockCmdDataReadDir, 0x4);\n\nstruct FSCmdBlockCmdDataReadFile\n{\n   UNKNOWN(4);\n\n   //! Total number of bytes remaining to read.\n   be2_val<uint32_t> bytesRemaining;\n\n   //! Total bytes read so far.\n   be2_val<uint32_t> bytesRead;\n\n   //! The size of each read chunk (size parameter on FSReadFile).\n   be2_val<uint32_t> chunkSize;\n\n   //! The amount of bytes to read per IPC request.\n   be2_val<uint32_t> readSize;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataReadFile, 0x4, bytesRemaining);\nCHECK_OFFSET(FSCmdBlockCmdDataReadFile, 0x8, bytesRead);\nCHECK_OFFSET(FSCmdBlockCmdDataReadFile, 0xC, chunkSize);\nCHECK_OFFSET(FSCmdBlockCmdDataReadFile, 0x10, readSize);\nCHECK_SIZE(FSCmdBlockCmdDataReadFile, 0x14);\n\nstruct FSCmdBlockCmdDataStatFile\n{\n   be2_virt_ptr<FSStat> stat;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataStatFile, 0x0, stat);\nCHECK_SIZE(FSCmdBlockCmdDataStatFile, 0x4);\n\nstruct FSCmdBlockCmdDataUnmount\n{\n   UNKNOWN(0x4);\n   be2_val<FSMountSourceType> sourceType;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataUnmount, 0x4, sourceType);\nCHECK_SIZE(FSCmdBlockCmdDataUnmount, 0x8);\n\nstruct FSCmdBlockCmdDataWriteFile\n{\n   UNKNOWN(4);\n\n   //! Total number of bytes remaining to write.\n   be2_val<uint32_t> bytesRemaining;\n\n   //! Total bytes written so far.\n   be2_val<uint32_t> bytesWritten;\n\n   //! The size of each write chunk (size parameter on FSWriteFile).\n   be2_val<uint32_t> chunkSize;\n\n   //! The amount of bytes to write per IPC request.\n   be2_val<uint32_t> writeSize;\n};\nCHECK_OFFSET(FSCmdBlockCmdDataWriteFile, 0x4, bytesRemaining);\nCHECK_OFFSET(FSCmdBlockCmdDataWriteFile, 0x8, bytesWritten);\nCHECK_OFFSET(FSCmdBlockCmdDataWriteFile, 0xC, chunkSize);\nCHECK_OFFSET(FSCmdBlockCmdDataWriteFile, 0x10, writeSize);\nCHECK_SIZE(FSCmdBlockCmdDataWriteFile, 0x14);\n\n/**\n * Stores command specific data for an FSCmdBlockBody.\n */\nstruct FSCmdBlockCmdData\n{\n   union\n   {\n      be2_struct<FSCmdBlockCmdDataGetCwd> getCwd;\n      be2_struct<FSCmdBlockCmdDataGetFileBlockAddress> getFileBlockAddress;\n      be2_struct<FSCmdBlockCmdDataGetInfoByQuery> getInfoByQuery;\n      be2_struct<FSCmdBlockCmdDataGetMountSourceNext> getMountSourceNext;\n      be2_struct<FSCmdBlockCmdDataGetPosFile> getPosFile;\n      be2_struct<FSCmdBlockCmdDataGetVolumeInfo> getVolumeInfo;\n      be2_struct<FSCmdBlockCmdDataMount> mount;\n      be2_struct<FSCmdBlockCmdDataOpenDir> openDir;\n      be2_struct<FSCmdBlockCmdDataOpenFile> openFile;\n      be2_struct<FSCmdBlockCmdDataReadDir> readDir;\n      be2_struct<FSCmdBlockCmdDataReadFile> readFile;\n      be2_struct<FSCmdBlockCmdDataStatFile> statFile;\n      be2_struct<FSCmdBlockCmdDataUnmount> unmount;\n      be2_struct<FSCmdBlockCmdDataWriteFile> writeFile;\n      UNKNOWN(0x14);\n   };\n};\nCHECK_SIZE(FSCmdBlockCmdData, 0x14);\n\nstruct FSCmdBlockBody\n{\n   //! FSA shim buffer used for FSA IPC communication.\n   be2_struct<FSAShimBuffer> fsaShimBuffer;\n\n   //! Pointer to client which owns this command.\n   be2_virt_ptr<FSClientBody> clientBody;\n\n   //! State of command.\n   be2_val<FSCmdBlockStatus> status;\n\n   //! Cancel state of command.\n   be2_val<FSCmdCancelFlags> cancelFlags;\n\n   //! Command specific data.\n   be2_struct<FSCmdBlockCmdData> cmdData;\n\n   //! Link used for FSCmdQueue.\n   be2_struct<FSCmdBlockBodyLink> link;\n\n   //! FSAStatus for the current command.\n   be2_val<FSAStatus> fsaStatus;\n\n   //! IOSError for the current command.\n   be2_val<IOSError> iosError;\n\n   //! Mask used to not return certain errors.\n   be2_val<FSErrorFlag> errorMask;\n\n   //! FSAsyncResult object used for this command.\n   be2_struct<FSAsyncResult> asyncResult;\n\n   //! User data accessed with FS{Get,Set}UserData.\n   be2_virt_ptr<void> userData;\n\n   //! Queue used for synchronous FS commands to wait for finish.\n   be2_struct<OSMessageQueue> syncQueue;\n\n   //! Message used for syncQueue.\n   be2_array<OSMessage, 1> syncQueueMsgs;\n\n   //! Callback to call when command is finished.\n   be2_val<FSFinishCmdFn> finishCmdFn;\n\n   //! Priority of command, from 0 highest to 32 lowest, 16 is default.\n   be2_val<uint8_t> priority;\n\n   be2_val<uint8_t> unk0x9E9;\n   be2_val<uint8_t> unk0x9EA;\n   UNKNOWN(0x9);\n   be2_val<uint32_t> unk0x9F4;\n\n   //! Pointer to unaligned FSCmdBlock.\n   be2_virt_ptr<FSCmdBlock> cmdBlock;\n};\nCHECK_OFFSET(FSCmdBlockBody, 0x0, fsaShimBuffer);\nCHECK_OFFSET(FSCmdBlockBody, 0x938, clientBody);\nCHECK_OFFSET(FSCmdBlockBody, 0x93C, status);\nCHECK_OFFSET(FSCmdBlockBody, 0x940, cancelFlags);\nCHECK_OFFSET(FSCmdBlockBody, 0x944, cmdData);\nCHECK_OFFSET(FSCmdBlockBody, 0x958, link);\nCHECK_OFFSET(FSCmdBlockBody, 0x960, fsaStatus);\nCHECK_OFFSET(FSCmdBlockBody, 0x964, iosError);\nCHECK_OFFSET(FSCmdBlockBody, 0x968, errorMask);\nCHECK_OFFSET(FSCmdBlockBody, 0x96C, asyncResult);\nCHECK_OFFSET(FSCmdBlockBody, 0x994, userData);\nCHECK_OFFSET(FSCmdBlockBody, 0x998, syncQueue);\nCHECK_OFFSET(FSCmdBlockBody, 0x9D4, syncQueueMsgs);\nCHECK_OFFSET(FSCmdBlockBody, 0x9E4, finishCmdFn);\nCHECK_OFFSET(FSCmdBlockBody, 0x9E8, priority);\nCHECK_OFFSET(FSCmdBlockBody, 0x9E9, unk0x9E9);\nCHECK_OFFSET(FSCmdBlockBody, 0x9EA, unk0x9EA);\nCHECK_OFFSET(FSCmdBlockBody, 0x9F4, unk0x9F4);\nCHECK_OFFSET(FSCmdBlockBody, 0x9F8, cmdBlock);\n\n#pragma pack(pop)\n\nvoid\nFSInitCmdBlock(virt_ptr<FSCmdBlock> block);\n\nFSStatus\nFSGetCmdPriority(virt_ptr<FSCmdBlock> block);\n\nFSStatus\nFSSetCmdPriority(virt_ptr<FSCmdBlock> block,\n                 uint32_t priority);\n\nvirt_ptr<FSMessage>\nFSGetFSMessage(virt_ptr<FSCmdBlock> block);\n\nvirt_ptr<void>\nFSGetUserData(virt_ptr<FSCmdBlock> block);\n\nvoid\nFSSetUserData(virt_ptr<FSCmdBlock> block,\n              virt_ptr<void> userData);\n\nnamespace internal\n{\n\nextern FSFinishCmdFn FinishCmd;\nextern FSFinishCmdFn FinishMountCmd;\nextern FSFinishCmdFn FinishReadCmd;\nextern FSFinishCmdFn FinishWriteCmd;\nextern FSFinishCmdFn FinishGetMountSourceNextOpenCmd;\nextern FSFinishCmdFn FinishGetMountSourceNextReadCmd;\nextern FSFinishCmdFn FinishGetMountSourceNextCloseCmd;\n\nvirt_ptr<FSCmdBlockBody>\nfsCmdBlockGetBody(virt_ptr<FSCmdBlock> cmdBlock);\n\nFSStatus\nfsCmdBlockPrepareAsync(virt_ptr<FSClientBody> clientBody,\n                       virt_ptr<FSCmdBlockBody> blockBody,\n                       FSErrorFlag errorMask,\n                       virt_ptr<const FSAsyncData> asyncData);\n\nvoid\nfsCmdBlockPrepareSync(virt_ptr<FSClient> client,\n                      virt_ptr<FSCmdBlock> block,\n                      virt_ptr<FSAsyncData> asyncData);\n\nvoid\nfsCmdBlockRequeue(virt_ptr<FSCmdQueue> queue,\n                  virt_ptr<FSCmdBlockBody> blockBody,\n                  BOOL insertAtFront,\n                  FSFinishCmdFn finishCmdFn);\n\nvoid\nfsCmdBlockSetResult(virt_ptr<FSCmdBlockBody> blockBody,\n                    FSStatus status);\n\nvoid\nfsCmdBlockReplyResult(virt_ptr<FSCmdBlockBody> blockBody,\n                      FSStatus status);\n\nvoid\nfsCmdBlockHandleResult(virt_ptr<FSCmdBlockBody> blockBody);\n\n\nvoid\nfsCmdBlockFinishCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                    FSStatus status);\n\nvoid\nfsCmdBlockFinishReadCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                        FSStatus status);\n\nvoid\nfsCmdBlockFinishWriteCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                         FSStatus status);\n\nvoid\nfsCmdBlockFinishMountCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                         FSStatus result);\n\nvoid\nfsCmdBlockFinishGetMountSourceNextOpenCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                                          FSStatus status);\n\nvoid\nfsCmdBlockFinishGetMountSourceNextReadCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                                          FSStatus result);\n\nvoid\nfsCmdBlockFinishGetMountSourceNextCloseCmd(virt_ptr<FSCmdBlockBody> blockBody,\n                                           FSStatus result);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmdqueue.cpp",
    "content": "#include \"coreinit_fastmutex.h\"\n#include \"coreinit_fs_client.h\"\n#include \"coreinit_fs_cmdblock.h\"\n#include \"coreinit_fs_cmdqueue.h\"\n#include \"coreinit_internal_queue.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include <libcpu/state.h>\n\nnamespace cafe::coreinit\n{\n\nnamespace internal\n{\n\nstruct FSCmdSortFuncLT\n{\n   bool operator ()(virt_ptr<FSCmdBlockBody> lhs,\n                    virt_ptr<FSCmdBlockBody> rhs) const\n   {\n      return lhs->priority < rhs->priority;\n   }\n};\n\nstruct FSCmdSortFuncLE\n{\n   bool operator ()(virt_ptr<FSCmdBlockBody> lhs,\n                    virt_ptr<FSCmdBlockBody> rhs) const\n   {\n      return lhs->priority <= rhs->priority;\n   }\n};\n\nusing CmdQueueLT = internal::SortedQueue<FSCmdQueue, FSCmdBlockBodyLink,\n                                         FSCmdBlockBody, &FSCmdBlockBody::link,\n                                         FSCmdSortFuncLT>;\n\nusing CmdQueueLE = internal::SortedQueue<FSCmdQueue, FSCmdBlockBodyLink,\n                                         FSCmdBlockBody, &FSCmdBlockBody::link,\n                                          FSCmdSortFuncLE>;\n\n\n/**\n * Initialise an FSCmdQueue structure.\n */\nbool\nfsCmdQueueCreate(virt_ptr<FSCmdQueue> queue,\n                 FSCmdQueueHandlerFn dequeueCmdHandler,\n                 uint32_t maxActiveCmds)\n{\n   if (!queue || !dequeueCmdHandler) {\n      return false;\n   }\n\n   queue->head = nullptr;\n   queue->tail = nullptr;\n   queue->dequeueCmdHandler = dequeueCmdHandler;\n   queue->activeCmds = 0u;\n   queue->maxActiveCmds = maxActiveCmds;\n   OSFastMutex_Init(virt_addrof(queue->mutex), nullptr);\n   return true;\n}\n\n\n/**\n * Destroy a FSCmdQueue structure.\n */\nvoid\nfsCmdQueueDestroy(virt_ptr<FSCmdQueue> queue)\n{\n   fsCmdQueueCancelAll(queue);\n}\n\n\n/**\n * Cancels all commands in a FSCmdQueue structure.\n */\nvoid\nfsCmdQueueCancelAll(virt_ptr<FSCmdQueue> queue)\n{\n   OSFastMutex_Lock(virt_addrof(queue->mutex));\n\n   while (auto cmd = fsCmdQueuePopFront(queue)) {\n      cmd->status = FSCmdBlockStatus::Cancelled;\n   }\n\n   OSFastMutex_Unlock(virt_addrof(queue->mutex));\n}\n\n\n/**\n * Prevent the FSCmdQueue from dequeuing new commands.\n *\n * Sets the FSCmdQueueStatus::Suspended flag.\n */\nvoid\nfsCmdQueueSuspend(virt_ptr<FSCmdQueue> queue)\n{\n   queue->status |= FSCmdQueueStatus::Suspended;\n}\n\n\n/**\n * Allow the FSCmdQueue to dequeuing new commands.\n *\n * Clears the FSCmdQueueStatus::Suspended flag.\n */\nvoid\nfsCmdQueueResume(virt_ptr<FSCmdQueue> queue)\n{\n   queue->status &= ~FSCmdQueueStatus::Suspended;\n}\n\n\n/**\n * Insert a command into the queue, sorted by priority.\n */\nvoid\nfsCmdQueueEnqueue(virt_ptr<FSCmdQueue> queue,\n                  virt_ptr<FSCmdBlockBody> blockBody,\n                  bool sortLE)\n{\n   if (sortLE) {\n      CmdQueueLE::insert(queue, blockBody);\n   } else {\n      CmdQueueLT::insert(queue, blockBody);\n   }\n}\n\n\n/**\n * Insert a command at the front of the queue.\n */\nvoid\nfsCmdQueuePushFront(virt_ptr<FSCmdQueue> queue,\n                    virt_ptr<FSCmdBlockBody> blockBody)\n{\n   blockBody->link.prev = nullptr;\n   blockBody->link.next = queue->head;\n\n   if (blockBody->link.next) {\n      blockBody->link.next->link.prev = blockBody;\n   }\n\n   queue->head = blockBody;\n}\n\n\n/**\n * Pop a command from the front of the queue.\n */\nvirt_ptr<FSCmdBlockBody>\nfsCmdQueuePopFront(virt_ptr<FSCmdQueue> queue)\n{\n   return CmdQueueLT::popFront(queue);\n}\n\n\n/**\n * Begin a command.\n *\n * Increases the active command count.\n *\n * \\retval true\n * Returns true if a command can begin.\n *\n * \\retval false\n * Returns false if there is already the max amount of active commands.\n */\nbool\nfsCmdQueueBeginCmd(virt_ptr<FSCmdQueue> queue)\n{\n   if (queue->status & FSCmdQueueStatus::MaxActiveCommands ||\n       queue->status & FSCmdQueueStatus::Suspended) {\n      return false;\n   }\n\n   queue->activeCmds += 1;\n\n   if (queue->activeCmds >= queue->maxActiveCmds) {\n      queue->status |= FSCmdQueueStatus::MaxActiveCommands;\n   }\n\n   return true;\n}\n\n\n/**\n * Finish a command.\n *\n * Decreases the active command count.\n */\nvoid\nfsCmdQueueFinishCmd(virt_ptr<FSCmdQueue> queue)\n{\n   if (queue->activeCmds) {\n      queue->activeCmds -= 1;\n   }\n\n   if (queue->activeCmds < queue->maxActiveCmds) {\n      queue->status &= ~FSCmdQueueStatus::MaxActiveCommands;\n   }\n}\n\n\n/**\n * Process a command from the queue.\n *\n * Pops a command from the front of the queue and calls the dequeued command\n * handler on it.\n */\nbool\nfsCmdQueueProcessCmd(virt_ptr<FSCmdQueue> queue)\n{\n   OSFastMutex_Lock(virt_addrof(queue->mutex));\n   auto activeCmd = fsCmdQueueBeginCmd(queue);\n   OSFastMutex_Unlock(virt_addrof(queue->mutex));\n\n   if (!activeCmd) {\n      return false;\n   }\n\n   OSFastMutex_Lock(virt_addrof(queue->mutex));\n   auto cmd = fsCmdQueuePopFront(queue);\n\n   if (cmd) {\n      auto clientBody = cmd->clientBody;\n      clientBody->lastDequeuedCommand = cmd;\n      cmd->status = FSCmdBlockStatus::DeqeuedCommand;\n      OSFastMutex_Unlock(virt_addrof(queue->mutex));\n\n      // Call the dequeue command handler\n      if (cafe::invoke(cpu::this_core::state(),\n                       queue->dequeueCmdHandler,\n                       cmd)) {\n         return true;\n      }\n\n      OSFastMutex_Lock(virt_addrof(queue->mutex));\n   }\n\n   fsCmdQueueFinishCmd(queue);\n   OSFastMutex_Unlock(virt_addrof(queue->mutex));\n   return true;\n}\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmdqueue.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_fastmutex.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n#pragma pack(push, 1)\n\n/**\n * \\ingroup coreinit_fs\n * @{\n */\n\nstruct FSCmdBlockBody;\n\nusing FSCmdQueueHandlerFn = virt_func_ptr<BOOL(virt_ptr<FSCmdBlockBody>)>;\n\nstruct FSCmdQueue\n{\n   //! Head of the queue.\n   be2_virt_ptr<FSCmdBlockBody> head;\n\n   //! Tail of the queue.\n   be2_virt_ptr<FSCmdBlockBody> tail;\n\n   //! Mutex used to protect queue data.\n   be2_struct<OSFastMutex> mutex;\n\n   //! Function to call when a command is dequeued.\n   be2_val<FSCmdQueueHandlerFn> dequeueCmdHandler;\n\n   //! Number of active commands.\n   be2_val<uint32_t> activeCmds;\n\n   //! Max allowed active commands (should always be 1).\n   be2_val<uint32_t> maxActiveCmds;\n\n   //! Status of the command queue.\n   be2_val<FSCmdQueueStatus> status;\n};\nCHECK_OFFSET(FSCmdQueue, 0x0, head);\nCHECK_OFFSET(FSCmdQueue, 0x4, tail);\nCHECK_OFFSET(FSCmdQueue, 0x8, mutex);\nCHECK_OFFSET(FSCmdQueue, 0x34, dequeueCmdHandler);\nCHECK_OFFSET(FSCmdQueue, 0x38, activeCmds);\nCHECK_OFFSET(FSCmdQueue, 0x3C, maxActiveCmds);\nCHECK_OFFSET(FSCmdQueue, 0x40, status);\nCHECK_SIZE(FSCmdQueue, 0x44);\n\n#pragma pack(pop)\n\nnamespace internal\n{\n\nbool\nfsCmdQueueCreate(virt_ptr<FSCmdQueue> queue,\n                 FSCmdQueueHandlerFn dequeueCmdHandler,\n                 uint32_t maxActiveCmds);\n\nvoid\nfsCmdQueueDestroy(virt_ptr<FSCmdQueue> queue);\n\nvoid\nfsCmdQueueCancelAll(virt_ptr<FSCmdQueue> queue);\n\nvoid\nfsCmdQueueSuspend(virt_ptr<FSCmdQueue> queue);\n\nvoid\nfsCmdQueueResume(virt_ptr<FSCmdQueue> queue);\n\nvoid\nfsCmdQueueEnqueue(virt_ptr<FSCmdQueue> queue,\n                  virt_ptr<FSCmdBlockBody> blockBody,\n                  bool sortLE);\n\nvoid\nfsCmdQueuePushFront(virt_ptr<FSCmdQueue> queue,\n                    virt_ptr<FSCmdBlockBody> blockBody);\n\nvirt_ptr<FSCmdBlockBody>\nfsCmdQueuePopFront(virt_ptr<FSCmdQueue> queue);\n\nbool\nfsCmdQueueBeginCmd(virt_ptr<FSCmdQueue> queue);\n\nvoid\nfsCmdQueueFinishCmd(virt_ptr<FSCmdQueue> queue);\n\nbool\nfsCmdQueueProcessCmd(virt_ptr<FSCmdQueue> queue);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_driver.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_cosreport.h\"\n#include \"coreinit_driver.h\"\n\nnamespace cafe::coreinit\n{\n\nstruct StaticFsDriverData\n{\n   be2_struct<OSDriverInterface> driverInterface;\n   be2_array<char, 4> name;\n   be2_val<BOOL> isDone;\n};\n\nvirt_ptr<StaticFsDriverData>\nsFsDriverData = nullptr;\n\nstatic OSDriver_GetNameFn sFsDriverGetName;\nstatic OSDriver_OnInitFn sFsDriverOnInit;\nstatic OSDriver_OnAcquiredForegroundFn sFsDriverOnAcquiredForeground;\nstatic OSDriver_OnReleasedForegroundFn sFsDriverOnReleasedForeground;\nstatic OSDriver_OnDoneFn sFsDriverOnDone;\n\nnamespace internal\n{\n\nstatic virt_ptr<const char>\nfsDriverGetName(OSDriver_UserDriverId id)\n{\n   return virt_addrof(sFsDriverData->name);\n}\n\nvoid\nfsDriverOnInit(OSDriver_UserDriverId id)\n{\n}\n\nvoid\nfsDriverOnAcquiredForeground(OSDriver_UserDriverId id)\n{\n}\n\nvoid\nfsDriverOnReleasedForeground(OSDriver_UserDriverId id)\n{\n}\n\nvoid\nfsDriverOnDone(OSDriver_UserDriverId id)\n{\n   sFsDriverData->isDone = TRUE;\n}\n\nbool\nfsDriverDone()\n{\n   return !!sFsDriverData->isDone;\n}\n\nvoid\ninitialiseFsDriver()\n{\n   sFsDriverData->name = \"FS\";\n   sFsDriverData->isDone = FALSE;\n   sFsDriverData->driverInterface.getName = sFsDriverGetName;\n   sFsDriverData->driverInterface.onInit = sFsDriverOnInit;\n   sFsDriverData->driverInterface.onAcquiredForeground = sFsDriverOnAcquiredForeground;\n   sFsDriverData->driverInterface.onReleasedForeground = sFsDriverOnReleasedForeground;\n   sFsDriverData->driverInterface.onDone = sFsDriverOnDone;\n\n   auto driverError =\n      OSDriver_Register(static_cast<OSDynLoad_ModuleHandle>(-1),\n                        40,\n                        virt_addrof(sFsDriverData->driverInterface),\n                        0,\n                        nullptr,\n                        nullptr,\n                        nullptr);\n\n   COSVerbose(\n      COSReportModule::Unknown5,\n      fmt::format(\"FS: Registered to OSDriver: result {}\", driverError));\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFsDriverSymbols()\n{\n   RegisterFunctionInternal(internal::fsDriverGetName, sFsDriverGetName);\n   RegisterFunctionInternal(internal::fsDriverOnInit, sFsDriverOnInit);\n   RegisterFunctionInternal(internal::fsDriverOnAcquiredForeground, sFsDriverOnAcquiredForeground);\n   RegisterFunctionInternal(internal::fsDriverOnReleasedForeground, sFsDriverOnReleasedForeground);\n   RegisterFunctionInternal(internal::fsDriverOnDone, sFsDriverOnDone);\n\n   RegisterDataInternal(sFsDriverData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_driver.h",
    "content": "#pragma once\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\ingroup coreinit_fs\n * @{\n */\n\nnamespace internal\n{\n\nvoid\ninitialiseFsDriver();\n\nbool\nfsDriverDone();\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_statemachine.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_appio.h\"\n#include \"coreinit_fs_statemachine.h\"\n#include \"coreinit_fs_client.h\"\n#include \"coreinit_fs_cmdblock.h\"\n\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <fmt/core.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nstatic AlarmCallbackFn sFsmAlarmCallback = nullptr;\n\n\n/**\n * Get an FSStateChangeInfo from an OSMessage.\n */\nvirt_ptr<FSStateChangeInfo>\nFSGetStateChangeInfo(virt_ptr<OSMessage> message)\n{\n   return virt_cast<FSStateChangeInfo *>(message->message);\n}\n\n\n/**\n * Register a callback or message queue for state change notifications.\n */\nvoid\nFSSetStateChangeNotification(virt_ptr<FSClient> client,\n                             virt_ptr<FSStateChangeAsync> asyncData)\n{\n   auto clientBody = internal::fsClientGetBody(client);\n   auto &fsm = clientBody->fsm;\n\n   if (!asyncData) {\n      fsm.sendStateChangeNotifications = FALSE;\n   } else {\n      if (asyncData->userCallback) {\n         asyncData->ioMsgQueue = OSGetDefaultAppIOQueue();\n      }\n\n      fsm.stateChangeInfo.asyncData = *asyncData;\n      fsm.stateChangeInfo.msg.data = virt_addrof(fsm.stateChangeInfo);\n      fsm.stateChangeInfo.msg.type = OSFunctionType::FsStateChangeEvent;\n      fsm.stateChangeInfo.client = clientBody->client;\n      fsm.sendStateChangeNotifications = TRUE;\n   }\n}\n\n\nnamespace internal\n{\n\nstatic FSVolumeState\nfsmOnStateChange(virt_ptr<FSFsm> fsm,\n                 FSVolumeState state,\n                 virt_ptr<FSClientBody> clientBody);\n\nstatic FSVolumeState\nfsmOnStateChangeFromInitial(virt_ptr<FSFsm> fsm,\n                            FSVolumeState state,\n                            virt_ptr<FSClientBody> clientBody);\n\nstatic FSVolumeState\nfsmOnStateChangeFromReady(virt_ptr<FSFsm> fsm,\n                          FSVolumeState state,\n                          virt_ptr<FSClientBody> clientBody);\n\nstatic FSVolumeState\nfsmOnStateChangeFromNoMedia(virt_ptr<FSFsm> fsm,\n                            FSVolumeState state,\n                            virt_ptr<FSClientBody> clientBody);\n\nstatic FSVolumeState\nfsmOnStateChangeFromMediaError(virt_ptr<FSFsm> fsm,\n                               FSVolumeState state,\n                               virt_ptr<FSClientBody> clientBody);\n\nstatic FSVolumeState\nfsmOnStateChangeFromFatal(virt_ptr<FSFsm> fsm,\n                          FSVolumeState state,\n                          virt_ptr<FSClientBody> clientBody);\n\nstatic void\nfsmNotifyStateChange(virt_ptr<FSFsm> fsm);\n\nstatic void\nfsmStartAlarm(virt_ptr<FSClientBody> clientBody);\n\nstatic void\nfsmAlarmHandler(virt_ptr<OSAlarm> alarm,\n                virt_ptr<OSContext> context);\n\n\n/**\n * Initialise the FS State Machine.\n */\nvoid\nfsmInit(virt_ptr<FSFsm> fsm,\n        virt_ptr<FSClientBody> clientBody)\n{\n   OSFastMutex_Lock(virt_addrof(clientBody->mutex));\n   fsm->state = FSVolumeState::Initial;\n   fsm->unk0x0c = FALSE;\n\n   internal::fsmEnterState(fsm, FSVolumeState::Ready, clientBody);\n   fsm->clientVolumeState = fsm->state;\n\n   OSFastMutex_Unlock(virt_addrof(clientBody->mutex));\n}\n\n\n/**\n * Set the FSM state.\n */\nvoid\nfsmSetState(virt_ptr<FSFsm> fsm,\n            FSVolumeState state,\n            virt_ptr<FSClientBody> clientBody)\n{\n   OSFastMutex_Lock(virt_addrof(clientBody->mutex));\n\n   // Set the state\n   auto newState = fsmOnStateChange(fsm, state, clientBody);\n\n   // Handle the transition\n   fsmEnterState(fsm, newState, clientBody);\n\n   OSFastMutex_Unlock(virt_addrof(clientBody->mutex));\n}\n\n\n/**\n * Enter an FSM state.\n */\nvoid\nfsmEnterState(virt_ptr<FSFsm> fsm,\n              FSVolumeState state,\n              virt_ptr<FSClientBody> clientBody)\n{\n   OSFastMutex_Lock(virt_addrof(clientBody->mutex));\n\n   while (state != FSVolumeState::Invalid) {\n      fsmOnStateChange(fsm, FSVolumeState::NoMedia, clientBody);\n      fsm->state = state;\n      state = fsmOnStateChange(fsm, FSVolumeState::Ready, clientBody);\n   }\n\n   if (fsm->unk0x0c) {\n      fsm->unk0x0c = FALSE;\n\n      if (fsm->state != fsm->clientVolumeState) {\n         fsm->clientVolumeState = fsm->state;\n         gLog->error(\"Updated volume state of client 0x{:X} to {}\",\n                     clientBody->client, fsm->clientVolumeState);\n\n         if (fsm->clientVolumeState == FSVolumeState::Fatal ||\n             fsm->clientVolumeState == FSVolumeState::JournalFull) {\n            gLog->error(\"Shit has become fucked {}\", clientBody->lastError);\n         }\n\n         fsmNotifyStateChange(fsm);\n      }\n   }\n\n   OSFastMutex_Unlock(virt_addrof(clientBody->mutex));\n}\n\n\n/**\n * Send a state change notification message.\n */\nvoid\nfsmNotifyStateChange(virt_ptr<FSFsm> fsm)\n{\n   if (fsm->sendStateChangeNotifications) {\n      fsm->stateChangeInfo.state = fsm->state;\n\n      OSSendMessage(fsm->stateChangeInfo.asyncData.ioMsgQueue,\n                    virt_cast<OSMessage *>(virt_addrof(fsm->stateChangeInfo.msg)),\n                    OSMessageFlags::None);\n   }\n}\n\n\n/**\n * Start the FSM alarm.\n *\n * I don't really understand it's purpose.\n */\nvoid\nfsmStartAlarm(virt_ptr<FSClientBody> clientBody)\n{\n   OSCancelAlarm(virt_addrof(clientBody->fsmAlarm));\n   OSCreateAlarm(virt_addrof(clientBody->fsmAlarm));\n   OSSetAlarmUserData(virt_addrof(clientBody->fsmAlarm), clientBody);\n   OSSetAlarm(virt_addrof(clientBody->fsmAlarm),\n              internal::msToTicks(1000),\n              sFsmAlarmCallback);\n}\n\n\n/**\n * Alarm handler for the FSM alarm.\n *\n * Does things.\n */\nvoid\nfsmAlarmHandler(virt_ptr<OSAlarm> alarm,\n                virt_ptr<OSContext> context)\n{\n   auto clientBody = virt_cast<FSClientBody *>(OSGetAlarmUserData(alarm));\n   OSFastMutex_Lock(virt_addrof(clientBody->mutex));\n\n   if (clientBody->fsm.unk0x08 == FSVolumeState::Invalid) {\n      clientBody->fsm.unk0x08 = FSVolumeState::NoMedia;\n      fsmSetState(virt_addrof(clientBody->fsm),\n                  FSVolumeState::NoMedia,\n                  clientBody);\n   } else if (clientBody->fsm.unk0x08 == FSVolumeState::WrongMedia) {\n      fsmSetState(virt_addrof(clientBody->fsm),\n                  FSVolumeState::InvalidMedia,\n                  clientBody);\n   }\n\n   OSFastMutex_Unlock(virt_addrof(clientBody->mutex));\n}\n\n\n/**\n * Called when the FSM state has changed.\n *\n * \\return\n * Returns the next state.\n */\nFSVolumeState\nfsmOnStateChange(virt_ptr<FSFsm> fsm,\n                 FSVolumeState state,\n                 virt_ptr<FSClientBody> clientBody)\n{\n   switch (fsm->state) {\n   case FSVolumeState::Initial:\n      return fsmOnStateChangeFromInitial(fsm, state, clientBody);\n   case FSVolumeState::Ready:\n      return fsmOnStateChangeFromReady(fsm, state, clientBody);\n   case FSVolumeState::NoMedia:\n      return fsmOnStateChangeFromNoMedia(fsm, state, clientBody);\n   case FSVolumeState::InvalidMedia:\n   case FSVolumeState::DirtyMedia:\n   case FSVolumeState::WrongMedia:\n   case FSVolumeState::MediaError:\n   case FSVolumeState::DataCorrupted:\n   case FSVolumeState::WriteProtected:\n      return fsmOnStateChangeFromMediaError(fsm, state, clientBody);\n   case FSVolumeState::JournalFull:\n   case FSVolumeState::Fatal:\n      return fsmOnStateChangeFromFatal(fsm, state, clientBody);\n   default:\n      decaf_abort(fmt::format(\"Invalid FSM state transition from {} to {}!\", fsm->state, state));\n   }\n\n   return FSVolumeState::Invalid;\n}\n\n\n/**\n * Called when the FSM state has changed from FSVolumeState::Initial.\n *\n * \\return\n * Returns the next state.\n */\nFSVolumeState\nfsmOnStateChangeFromInitial(virt_ptr<FSFsm> fsm,\n                            FSVolumeState state,\n                            virt_ptr<FSClientBody> clientBody)\n{\n   switch (state) {\n   case FSVolumeState::NoMedia:\n      return FSVolumeState::Invalid;\n   default:\n      decaf_abort(fmt::format(\"Invalid FSM state transition from {} to {}!\", fsm->state, state));\n   }\n\n   return FSVolumeState::Invalid;\n}\n\n\n/**\n * Called when the FSM state has changed from FSVolumeState::Ready.\n *\n * \\return\n * Returns the next state.\n */\nFSVolumeState\nfsmOnStateChangeFromReady(virt_ptr<FSFsm> fsm,\n                          FSVolumeState state,\n                          virt_ptr<FSClientBody> clientBody)\n{\n   switch (state) {\n   case FSVolumeState::Ready:\n   {\n      if (clientBody->lastDequeuedCommand) {\n         fsCmdBlockRequeue(virt_addrof(clientBody->cmdQueue),\n                           clientBody->lastDequeuedCommand,\n                           TRUE,\n                           clientBody->lastDequeuedCommand->finishCmdFn);\n      }\n\n      return FSVolumeState::Invalid;\n   }\n   case FSVolumeState::WrongMedia:\n   {\n      clientBody->fsm.unk0x08 = FSVolumeState::Invalid;\n      fsmStartAlarm(clientBody);\n      return FSVolumeState::NoMedia;\n   }\n   case FSVolumeState::MediaError:\n      return FSVolumeState::WriteProtected;\n   case FSVolumeState::DataCorrupted:\n      return FSVolumeState::DataCorrupted;\n   case FSVolumeState::WriteProtected:\n      return FSVolumeState::MediaError;\n   case FSVolumeState::NoMedia:\n   case FSVolumeState::Invalid:\n      return FSVolumeState::Invalid;\n   default:\n      decaf_abort(fmt::format(\"Invalid FSM state transition from {} to {}!\", fsm->state, state));\n   }\n\n   return FSVolumeState::Invalid;\n}\n\n\n/**\n * Called when the FSM state has changed from FSVolumeState::NoMedia.\n *\n * \\return\n * Returns the next state.\n */\nFSVolumeState\nfsmOnStateChangeFromNoMedia(virt_ptr<FSFsm> fsm,\n                            FSVolumeState state,\n                            virt_ptr<FSClientBody> clientBody)\n{\n   switch (fsm->state) {\n   case FSVolumeState::Ready:\n   case FSVolumeState::Fatal:\n   {\n      return FSVolumeState::Invalid;\n   }\n   case FSVolumeState::NoMedia:\n   {\n      OSCancelAlarm(virt_addrof(clientBody->fsmAlarm));\n      fsm->unk0x0c = TRUE;\n      return FSVolumeState::Invalid;\n   }\n   case FSVolumeState::InvalidMedia:\n   {\n      fsm->unk0x0c = TRUE;\n\n      if (fsm->unk0x08 == FSVolumeState::NoMedia) {\n         return FSVolumeState::Invalid;\n      }\n\n      return FSVolumeState::NoMedia;\n   }\n   case FSVolumeState::JournalFull:\n   {\n      clientBody->unk0x14D0 = 1u;\n      return FSVolumeState::Invalid;\n   }\n   case FSVolumeState::Invalid:\n   {\n      auto lastCmd = clientBody->lastDequeuedCommand;\n      clientBody->lastDequeuedCommand = nullptr;\n      lastCmd->cancelFlags &= ~FSCmdCancelFlags::Cancelling;\n      lastCmd->status = FSCmdBlockStatus::Cancelled;\n      fsCmdBlockReplyResult(lastCmd, FSStatus::Cancelled);\n      clientBody->unk0x14CC = 0u;\n      clientBody->unk0x14D0 = 1u;\n      fsm->unk0x0c = TRUE;\n      return FSVolumeState::Ready;\n   }\n   default:\n      decaf_abort(fmt::format(\"Invalid FSM state transition from {} to {}!\", fsm->state, state));\n   }\n\n   return FSVolumeState::Invalid;\n}\n\n\n/**\n * Called when the FSM state has changed from various media error related states.\n *\n * \\return\n * Returns the next state.\n */\nFSVolumeState\nfsmOnStateChangeFromMediaError(virt_ptr<FSFsm> fsm,\n                               FSVolumeState state,\n                               virt_ptr<FSClientBody> clientBody)\n{\n   switch (fsm->state) {\n   case FSVolumeState::Ready:\n   case FSVolumeState::NoMedia:\n   {\n      return FSVolumeState::Invalid;\n   }\n   case FSVolumeState::InvalidMedia:\n   {\n      clientBody->unk0x14CC = 1u;\n      return FSVolumeState::Invalid;\n   }\n   case FSVolumeState::JournalFull:\n   {\n      fsm->unk0x0c = TRUE;\n      clientBody->unk0x14D0 = 1u;\n      return FSVolumeState::Invalid;\n   }\n   case FSVolumeState::Fatal:\n   {\n      fsm->unk0x0c = TRUE;\n      return FSVolumeState::NoMedia;\n   }\n   case FSVolumeState::Invalid:\n   {\n      auto lastCmd = clientBody->lastDequeuedCommand;\n      clientBody->lastDequeuedCommand = nullptr;\n      lastCmd->cancelFlags &= ~FSCmdCancelFlags::Cancelling;\n      lastCmd->status = FSCmdBlockStatus::Cancelled;\n      fsCmdBlockReplyResult(lastCmd, FSStatus::Cancelled);\n      clientBody->unk0x14CC = 0u;\n      clientBody->unk0x14D0 = 1u;\n      fsm->unk0x0c = TRUE;\n      return FSVolumeState::Ready;\n   }\n   default:\n      decaf_abort(fmt::format(\"Invalid FSM state transition from {} to {}!\", fsm->state, state));\n   }\n\n   return FSVolumeState::Invalid;\n}\n\n\n/**\n * Called when the FSM state has changed from FSVolumeState::Fatal or\n * FSVolumeState::JournalFull.\n *\n * \\return\n * Returns the next state.\n */\nFSVolumeState\nfsmOnStateChangeFromFatal(virt_ptr<FSFsm> fsm,\n                          FSVolumeState state,\n                          virt_ptr<FSClientBody> clientBody)\n{\n   switch (fsm->state) {\n   case FSVolumeState::Ready:\n   case FSVolumeState::JournalFull:\n   {\n      if (clientBody->isLastErrorWithoutVolume) {\n         fsm->unk0x0c = TRUE;\n      }\n\n      return FSVolumeState::Invalid;\n   }\n   case FSVolumeState::NoMedia:\n   case FSVolumeState::InvalidMedia:\n   case FSVolumeState::DirtyMedia:\n   case FSVolumeState::WrongMedia:\n   case FSVolumeState::MediaError:\n   case FSVolumeState::DataCorrupted:\n   case FSVolumeState::WriteProtected:\n   case FSVolumeState::Fatal:\n   case FSVolumeState::Invalid:\n      return FSVolumeState::Invalid;\n   default:\n      decaf_abort(fmt::format(\"Invalid FSM state transition from {} to {}!\", fsm->state, state));\n   }\n\n   return FSVolumeState::Invalid;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFsStateMachineSymbols()\n{\n   RegisterFunctionExport(FSGetStateChangeInfo);\n   RegisterFunctionExport(FSSetStateChangeNotification);\n\n   RegisterFunctionInternal(internal::fsmAlarmHandler, sFsmAlarmCallback);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_statemachine.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_fs.h\"\n#include \"coreinit_messagequeue.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\ingroup coreinit_fs\n * @{\n */\n\nstruct FSClient;\nstruct FSClientBody;\n\nusing FSStateChangeCallbackFn = virt_func_ptr<void(virt_ptr<FSClient>,\n                                                   FSVolumeState,\n                                                   virt_ptr<void>)>;\n\n\nstruct FSStateChangeAsync\n{\n   //! Callback for state change notifications.\n   be2_val<FSStateChangeCallbackFn> userCallback;\n\n   //! Context for state change notification callback.\n   be2_virt_ptr<void> userContext;\n\n   //! Message queue to insert state change message on.\n   be2_virt_ptr<OSMessageQueue> ioMsgQueue;\n};\nCHECK_OFFSET(FSStateChangeAsync, 0x0, userCallback);\nCHECK_OFFSET(FSStateChangeAsync, 0x4, userContext);\nCHECK_OFFSET(FSStateChangeAsync, 0x8, ioMsgQueue);\nCHECK_SIZE(FSStateChangeAsync, 0xC);\n\n\n/**\n * Data returned from FSGetStateChangeInfo.\n */\nstruct FSStateChangeInfo\n{\n   //! Async data.\n   be2_struct<FSStateChangeAsync> asyncData;\n\n   //! Message used for asyncData.ioMsgQueue.\n   be2_struct<FSMessage> msg;\n\n   //! Client which this notification is for.\n   be2_virt_ptr<FSClient> client;\n\n   //! Volume state at the time at which the notification was sent.\n   be2_val<FSVolumeState> state;\n};\nCHECK_OFFSET(FSStateChangeInfo, 0x00, asyncData);\nCHECK_OFFSET(FSStateChangeInfo, 0x0C, msg);\nCHECK_OFFSET(FSStateChangeInfo, 0x1C, client);\nCHECK_OFFSET(FSStateChangeInfo, 0x20, state);\nCHECK_SIZE(FSStateChangeInfo, 0x24);\n\n\n/**\n * FileSystem State Machine.\n *\n * I don't really understand the control flow of the FSM properly...\n */\nstruct FSFsm\n{\n   be2_val<FSVolumeState> state;\n   be2_val<FSVolumeState> clientVolumeState;\n   be2_val<FSVolumeState> unk0x08;\n   be2_val<BOOL> unk0x0c;\n\n   //! If TRUE then will send state change notifications.\n   be2_val<BOOL> sendStateChangeNotifications;\n\n   be2_struct<FSStateChangeInfo> stateChangeInfo;\n};\nCHECK_OFFSET(FSFsm, 0x0, state);\nCHECK_OFFSET(FSFsm, 0x4, clientVolumeState);\nCHECK_OFFSET(FSFsm, 0x8, unk0x08);\nCHECK_OFFSET(FSFsm, 0xC, unk0x0c);\nCHECK_OFFSET(FSFsm, 0x10, sendStateChangeNotifications);\nCHECK_OFFSET(FSFsm, 0x14, stateChangeInfo);\nCHECK_SIZE(FSFsm, 0x38);\n\nvirt_ptr<FSStateChangeInfo>\nFSGetStateChangeInfo(virt_ptr<OSMessage> message);\n\nvoid\nFSSetStateChangeNotification(virt_ptr<FSClient> client,\n                             virt_ptr<FSStateChangeAsync> asyncData);\n\nnamespace internal\n{\n\nvoid\nfsmInit(virt_ptr<FSFsm> fsm,\n        virt_ptr<FSClientBody> clientBody);\n\nvoid\nfsmSetState(virt_ptr<FSFsm> fsm,\n            FSVolumeState state,\n            virt_ptr<FSClientBody> clientBody);\n\nvoid\nfsmEnterState(virt_ptr<FSFsm> fsm,\n              FSVolumeState state,\n              virt_ptr<FSClientBody> clientBody);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_appio.h\"\n#include \"coreinit_fsa.h\"\n#include \"coreinit_fsa_shim.h\"\n#include \"coreinit_ipcbufpool.h\"\n#include \"coreinit_messagequeue.h\"\n#include \"coreinit_spinlock.h\"\n\nnamespace cafe::coreinit\n{\n\nstruct StaticFsaData\n{\n   be2_val<BOOL> initialised;\n\n   be2_struct<OSSpinLock> bufPoolLock;\n   be2_array<uint8_t, 0x37500> ipcPoolBuffer;\n   be2_val<uint32_t> ipcPoolNumItems;\n   be2_virt_ptr<IPCBufPool> ipcPool;\n\n   be2_struct<OSSpinLock> clientListLock;\n   be2_val<uint32_t> activeClientCount;\n   be2_array<FSAClient, 0x40> clientList;\n};\n\nstatic virt_ptr<StaticFsaData>\nsFsaData = nullptr;\n\n\n/**\n * Initialise FSA.\n */\nFSAStatus\nFSAInit()\n{\n   if (sFsaData->initialised) {\n      return FSAStatus::OK;\n   }\n\n   sFsaData->initialised = TRUE;\n\n   // Initialise buffer pool\n   OSInitSpinLock(virt_addrof(sFsaData->bufPoolLock));\n   OSAcquireSpinLock(virt_addrof(sFsaData->bufPoolLock));\n   if (!sFsaData->ipcPool) {\n      sFsaData->ipcPool =\n         IPCBufPoolCreate(virt_addrof(sFsaData->ipcPoolBuffer),\n                          sFsaData->ipcPoolBuffer.size(),\n                          sizeof(FSAShimBuffer),\n                          virt_addrof(sFsaData->ipcPoolNumItems),\n                          0);\n   }\n   OSReleaseSpinLock(virt_addrof(sFsaData->bufPoolLock));\n\n   // Initialise client list\n   OSInitSpinLock(virt_addrof(sFsaData->clientListLock));\n   OSAcquireSpinLock(virt_addrof(sFsaData->clientListLock));\n   std::memset(virt_addrof(sFsaData->clientList).get(),\n               0,\n               sizeof(FSAClient) * sFsaData->clientList.size());\n\n   auto i = 0u;\n   for (auto &client : sFsaData->clientList) {\n      client.fsaHandle = -1;\n      client.attachMessage.data = virt_addrof(client);\n      client.attachMessage.type = OSFunctionType::FsaAttachEvent;\n\n      FSAShimAllocateBuffer(virt_addrof(client.attachShimBuffer));\n      client.attachShimBuffer->command = FSACommand::GetAttach;\n      ++i;\n   }\n\n   OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock));\n   return FSAStatus::OK;\n}\n\n\n/**\n * Shutdown FSA.\n */\nFSAStatus\nFSAShutdown()\n{\n   return FSAStatus::OK;\n}\n\n\n/**\n * Create a FSA client.\n */\nFSAStatus\nFSAAddClient(virt_ptr<FSAClientAttachAsyncData> attachAsyncData)\n{\n   if (attachAsyncData) {\n      attachAsyncData->ioMsgQueue = OSGetDefaultAppIOQueue();\n   }\n\n   if (sFsaData->activeClientCount == sFsaData->clientList.size()) {\n      return FSAStatus::MaxClients;\n   }\n\n   if (attachAsyncData) {\n      if (!attachAsyncData->ioMsgQueue) {\n         return FSAStatus::InvalidParam;\n      }\n\n      if (attachAsyncData->ioMsgQueue == OSGetDefaultAppIOQueue() &&\n          !attachAsyncData->userCallback) {\n         return FSAStatus::InvalidParam;\n      }\n   }\n\n   auto error = internal::fsaShimOpen();\n   if (error < IOSError::OK) {\n      return FSAStatus::PermissionError;\n   }\n\n   auto fsaHandle = static_cast<FSAClientHandle>(error);\n\n   OSAcquireSpinLock(virt_addrof(sFsaData->clientListLock));\n   auto client = virt_ptr<FSAClient> { nullptr };\n\n   for (auto &clientItr : sFsaData->clientList) {\n      if (clientItr.state != FSAClientState::Free) {\n         continue;\n      }\n\n      client = virt_addrof(clientItr);\n   }\n\n   if (!client) {\n      OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock));\n      IOS_Close(fsaHandle);\n      return FSAStatus::MaxClients;\n   }\n\n   client->state = FSAClientState::Allocated;\n   client->fsaHandle = fsaHandle;\n   OSInitEvent(virt_addrof(client->attachEvent), FALSE, OSEventMode::AutoReset);\n\n   if (attachAsyncData) {\n      decaf_abort(\"Unimplemented FSAClient with attachAsyncData\");\n   } else {\n      client->asyncAttachData.userCallback = nullptr;\n      client->asyncAttachData.userContext = nullptr;\n      client->asyncAttachData.ioMsgQueue = nullptr;\n   }\n\n   ++sFsaData->activeClientCount;\n   OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock));\n   return static_cast<FSAStatus>(fsaHandle);\n}\n\n\n/**\n * Delete a FSA client.\n */\nFSAStatus\nFSADelClient(FSAClientHandle handle)\n{\n   auto client = FSAShimCheckClientHandle(handle);\n   if (!client) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   // Start closing the client\n   client->state = FSAClientState::Closing;\n\n   auto error = internal::fsaShimClose(client->fsaHandle);\n   if (error < IOSError::OK) {\n      // Failed to close the client...\n      client->state = FSAClientState::Allocated;\n      return FSAShimDecodeIosErrorToFsaStatus(handle, error);\n   }\n\n   if (client->asyncAttachData.userCallback || client->asyncAttachData.ioMsgQueue) {\n      // Wait for the GetAttach to callback due to handle closing\n      OSWaitEvent(virt_addrof(client->attachEvent));\n   }\n\n   OSAcquireSpinLock(virt_addrof(sFsaData->clientListLock));\n   client->state = FSAClientState::Free;\n   client->fsaHandle = -1;\n   client->asyncAttachData.userCallback = nullptr;\n   client->asyncAttachData.userContext = nullptr;\n   client->asyncAttachData.ioMsgQueue = nullptr;\n   --sFsaData->activeClientCount;\n   OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock));\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Get an FSAAsyncResult from an OSMessage.\n */\nvirt_ptr<FSAAsyncResult>\nFSAGetAsyncResult(virt_ptr<OSMessage> message)\n{\n   return virt_cast<FSAAsyncResult *>(message->message);\n}\n\n\n/**\n * __FSAShimCheckClientHandle\n *\n * Check if a FSAClientHandle is valid.\n */\nvirt_ptr<FSAClient>\nFSAShimCheckClientHandle(FSAClientHandle handle)\n{\n   if (handle < 0) {\n      return nullptr;\n   }\n\n   OSAcquireSpinLock(virt_addrof(sFsaData->clientListLock));\n\n   for (auto &client : sFsaData->clientList) {\n      if (client.state == FSAClientState::Allocated &&\n          client.fsaHandle == handle) {\n         OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock));\n         return virt_addrof(client);\n      }\n   }\n\n   OSReleaseSpinLock(virt_addrof(sFsaData->clientListLock));\n   return nullptr;\n}\n\n\n/**\n * __FSAShimAllocateBuffer\n *\n * Allocate a FSAShimBuffer object from the FSA ipc buffer pool.\n */\nFSAStatus\nFSAShimAllocateBuffer(virt_ptr<virt_ptr<FSAShimBuffer>> outBuffer)\n{\n   if (!sFsaData->ipcPool) {\n      return FSAStatus::NotInit;\n   }\n\n   OSAcquireSpinLock(virt_addrof(sFsaData->bufPoolLock));\n   auto ptr = IPCBufPoolAllocate(sFsaData->ipcPool, sizeof(FSAShimBuffer));\n   OSReleaseSpinLock(virt_addrof(sFsaData->bufPoolLock));\n\n   if (!ptr) {\n      return FSAStatus::OutOfResources;\n   }\n\n   std::memset(ptr.get(), 0, sizeof(FSAShimBuffer));\n   *outBuffer = virt_cast<FSAShimBuffer *>(ptr);\n   return FSAStatus::OK;\n}\n\n\n/**\n * __FSAShimFreeBuffer\n *\n * Allocate a FSAShimBuffer object from the FSA ipc buffer pool.\n */\nvoid\nFSAShimFreeBuffer(virt_ptr<FSAShimBuffer> buffer)\n{\n   OSAcquireSpinLock(virt_addrof(sFsaData->bufPoolLock));\n   IPCBufPoolFree(sFsaData->ipcPool, buffer);\n   OSReleaseSpinLock(virt_addrof(sFsaData->bufPoolLock));\n}\n\n\nnamespace internal\n{\n\n\n/**\n * Initialise an FSAAsyncResult object.\n */\nvoid\nfsaAsyncResultInit(virt_ptr<FSAAsyncResult> asyncResult,\n                   virt_ptr<const FSAAsyncData> asyncData,\n                   OSFunctionType func)\n{\n   std::memset(asyncResult.get(), 0, sizeof(FSAAsyncResult));\n   asyncResult->userCallback = asyncData->userCallback;\n   asyncResult->ioMsgQueue = asyncData->ioMsgQueue;\n   asyncResult->userContext = asyncData->userContext;\n   asyncResult->msg.type = func;\n   asyncResult->msg.data = asyncResult;\n}\n\n\n/**\n * Convert an FSAStatus error code to an FSStatus error code.\n */\nFSStatus\nfsaDecodeFsaStatusToFsStatus(FSAStatus error)\n{\n   switch (error) {\n   case FSAStatus::OK:\n      return FSStatus::OK;\n   case FSAStatus::NotInit:\n   case FSAStatus::Busy:\n      return FSStatus::FatalError;\n   case FSAStatus::Cancelled:\n      return FSStatus::Cancelled;\n   case FSAStatus::EndOfDir:\n   case FSAStatus::EndOfFile:\n      return FSStatus::End;\n   case FSAStatus::MaxMountpoints:\n   case FSAStatus::MaxVolumes:\n   case FSAStatus::MaxClients:\n   case FSAStatus::MaxFiles:\n   case FSAStatus::MaxDirs:\n      return FSStatus::Max;\n   case FSAStatus::AlreadyOpen:\n      return FSStatus::AlreadyOpen;\n   case FSAStatus::AlreadyExists:\n      return FSStatus::Exists;\n   case FSAStatus::NotFound:\n      return FSStatus::NotFound;\n   case FSAStatus::NotEmpty:\n      return FSStatus::Exists;\n   case FSAStatus::AccessError:\n      return FSStatus::AccessError;\n   case FSAStatus::PermissionError:\n      return FSStatus::PermissionError;\n   case FSAStatus::DataCorrupted:\n      return FSStatus::FatalError;\n   case FSAStatus::StorageFull:\n      return FSStatus::StorageFull;\n   case FSAStatus::JournalFull:\n      return FSStatus::JournalFull;\n   case FSAStatus::LinkEntry:\n      return FSStatus::FatalError;\n   case FSAStatus::UnavailableCmd:\n      return FSStatus::FatalError;\n   case FSAStatus::UnsupportedCmd:\n      return FSStatus::UnsupportedCmd;\n   case FSAStatus::InvalidParam:\n   case FSAStatus::InvalidPath:\n   case FSAStatus::InvalidBuffer:\n   case FSAStatus::InvalidAlignment:\n   case FSAStatus::InvalidClientHandle:\n   case FSAStatus::InvalidFileHandle:\n   case FSAStatus::InvalidDirHandle:\n      return FSStatus::FatalError;\n   case FSAStatus::NotFile:\n      return FSStatus::NotFile;\n   case FSAStatus::NotDir:\n      return FSStatus::NotDirectory;\n   case FSAStatus::FileTooBig:\n      return FSStatus::FileTooBig;\n   case FSAStatus::OutOfRange:\n   case FSAStatus::OutOfResources:\n      return FSStatus::FatalError;\n   case FSAStatus::MediaNotReady:\n      return FSStatus::FatalError;\n   case FSAStatus::MediaError:\n      return FSStatus::MediaError;\n   case FSAStatus::WriteProtected:\n   case FSAStatus::InvalidMedia:\n      return FSStatus::FatalError;\n   }\n\n   return FSStatus::FatalError;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFsaSymbols()\n{\n   RegisterFunctionExport(FSAInit);\n   RegisterFunctionExport(FSAShutdown);\n   RegisterFunctionExport(FSAAddClient);\n   RegisterFunctionExport(FSADelClient);\n   RegisterFunctionExport(FSAGetAsyncResult);\n   RegisterFunctionExportName(\"__FSAShimAllocateBuffer\",\n                              FSAShimAllocateBuffer);\n   RegisterFunctionExportName(\"__FSAShimFreeBuffer\",\n                              FSAShimFreeBuffer);\n\n   RegisterDataInternal(sFsaData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_event.h\"\n#include \"coreinit_fs.h\"\n#include \"coreinit_ios.h\"\n\n#include \"ios/fs/ios_fs_fsa.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_fsa FSA\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nusing FSAClientHandle = IOSHandle;\nusing FSAAttachInfo = ios::fs::FSAAttachInfo;\nusing FSABlockInfo = ios::fs::FSABlockInfo;\nusing FSACommand = ios::fs::FSACommand;\nusing FSADeviceInfo = ios::fs::FSADeviceInfo;\nusing FSAFileHandle = ios::fs::FSAFileHandle;\nusing FSAFileSystemInfo = ios::fs::FSAFileSystemInfo;\nusing FSAQueryInfoType = ios::fs::FSAQueryInfoType;\nusing FSAReadFlag = ios::fs::FSAReadFlag;\nusing FSARequest = ios::fs::FSARequest;\nusing FSAResponse = ios::fs::FSAResponse;\nusing FSAStat = ios::fs::FSAStat;\nusing FSAStatus = ios::fs::FSAStatus;\nusing FSAVolumeInfo = ios::fs::FSAVolumeInfo;\nusing FSAWriteFlag = ios::fs::FSAWriteFlag;\n\nstruct FSAShimBuffer;\nstruct OSMessage;\nstruct OSMessageQueue;\n\nusing FSAAsyncCallbackFn =\n   virt_func_ptr<void (FSAStatus result,\n                       FSACommand command,\n                       virt_ptr<FSARequest> request,\n                       virt_ptr<FSAResponse> response,\n                       virt_ptr<void> userContext)>;\n\nusing FSAClientAttachAsyncCallbackFn =\n   virt_func_ptr<void (FSAStatus result,\n                       uint32_t responseWord0,\n                       virt_ptr<FSAAttachInfo> attachInfo,\n                       virt_ptr<void> userContext)>;\n\n/**\n * Async data passed to an FSA*Async function.\n */\nstruct FSAAsyncData\n{\n   //! Callback to call when the command is complete.\n   be2_val<FSAAsyncCallbackFn> userCallback;\n\n   //! Callback context\n   be2_virt_ptr<void> userContext;\n\n   //! Queue to put a message on when command is complete.\n   be2_virt_ptr<OSMessageQueue> ioMsgQueue;\n};\nCHECK_OFFSET(FSAAsyncData, 0x00, userCallback);\nCHECK_OFFSET(FSAAsyncData, 0x04, userContext);\nCHECK_OFFSET(FSAAsyncData, 0x08, ioMsgQueue);\nCHECK_SIZE(FSAAsyncData, 0xC);\n\nstruct FSAAsyncResult\n{\n   //! Queue to put a message on when command is complete.\n   be2_virt_ptr<OSMessageQueue> ioMsgQueue;\n\n   //! Message used for ioMsgQueue.\n   be2_struct<FSMessage> msg;\n\n   //! Callback to call when the command is complete.\n   be2_val<FSAAsyncCallbackFn> userCallback;\n\n   //! Result.\n   be2_val<FSAStatus> error;\n\n   //! FSA command.\n   be2_val<FSACommand> command;\n\n   //! Pointer to allocated FSA IPC Request.\n   be2_virt_ptr<FSARequest> request;\n\n   //! Pointer to allocated FSA IPC Response.\n   be2_virt_ptr<FSAResponse> response;\n\n   //! Callback to call when the command is complete.\n   be2_virt_ptr<void> userContext;\n};\nCHECK_OFFSET(FSAAsyncResult, 0x00, ioMsgQueue);\nCHECK_OFFSET(FSAAsyncResult, 0x04, msg);\nCHECK_OFFSET(FSAAsyncResult, 0x14, userCallback);\nCHECK_OFFSET(FSAAsyncResult, 0x18, error);\nCHECK_OFFSET(FSAAsyncResult, 0x1C, command);\nCHECK_OFFSET(FSAAsyncResult, 0x20, request);\nCHECK_OFFSET(FSAAsyncResult, 0x24, response);\nCHECK_OFFSET(FSAAsyncResult, 0x28, userContext);\nCHECK_SIZE(FSAAsyncResult, 0x2C);\n\nstruct FSAClientAttachAsyncData\n{\n   //! Callback to call when an attach has happened.\n   be2_val<FSAClientAttachAsyncCallbackFn> userCallback;\n\n   //! Callback context\n   be2_virt_ptr<void> userContext;\n\n   //! Queue to put a message on when command is complete.\n   be2_virt_ptr<OSMessageQueue> ioMsgQueue;\n};\nCHECK_OFFSET(FSAClientAttachAsyncData, 0x00, userCallback);\nCHECK_OFFSET(FSAClientAttachAsyncData, 0x04, userContext);\nCHECK_OFFSET(FSAClientAttachAsyncData, 0x08, ioMsgQueue);\nCHECK_SIZE(FSAClientAttachAsyncData, 0xC);\n\nstruct FSAClient\n{\n   be2_val<FSAClientState> state;\n   be2_val<IOSHandle> fsaHandle;\n   be2_struct<FSAClientAttachAsyncData> asyncAttachData;\n   be2_struct<FSMessage> attachMessage;\n   be2_val<FSAStatus> lastGetAttachStatus;\n   be2_val<uint32_t> attachResponseWord0;\n   be2_struct<FSAAttachInfo> attachInfo;\n   be2_virt_ptr<FSAShimBuffer> attachShimBuffer;\n   be2_struct<OSEvent> attachEvent;\n};\nCHECK_OFFSET(FSAClient, 0x00, state);\nCHECK_OFFSET(FSAClient, 0x04, fsaHandle);\nCHECK_OFFSET(FSAClient, 0x08, asyncAttachData);\nCHECK_OFFSET(FSAClient, 0x14, attachMessage);\nCHECK_OFFSET(FSAClient, 0x24, lastGetAttachStatus);\nCHECK_OFFSET(FSAClient, 0x28, attachResponseWord0);\nCHECK_OFFSET(FSAClient, 0x2C, attachInfo);\nCHECK_OFFSET(FSAClient, 0x1E8, attachShimBuffer);\nCHECK_OFFSET(FSAClient, 0x1EC, attachEvent);\nCHECK_SIZE(FSAClient, 0x210);\n\n#pragma pack(pop)\n\nFSAStatus\nFSAInit();\n\nFSAStatus\nFSAShutdown();\n\nFSAStatus\nFSAAddClient(virt_ptr<FSAClientAttachAsyncData> attachAsyncData);\n\nFSAStatus\nFSADelClient(FSAClientHandle handle);\n\nvirt_ptr<FSAAsyncResult>\nFSAGetAsyncResult(virt_ptr<OSMessage> message);\n\nvirt_ptr<FSAClient>\nFSAShimCheckClientHandle(FSAClientHandle handle);\n\nFSAStatus\nFSAShimAllocateBuffer(virt_ptr<virt_ptr<FSAShimBuffer>> outBuffer);\n\nvoid\nFSAShimFreeBuffer(virt_ptr<FSAShimBuffer> buffer);\n\nnamespace internal\n{\n\nvoid\nfsaAsyncResultInit(virt_ptr<FSAAsyncResult> asyncResult,\n                   virt_ptr<const FSAAsyncData> asyncData,\n                   OSFunctionType func);\n\nFSStatus\nfsaDecodeFsaStatusToFsStatus(FSAStatus error);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa_cmd.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_fsa.h\"\n#include \"coreinit_fsa_shim.h\"\n#include \"coreinit_ios.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n\nnamespace cafe::coreinit\n{\n\nnamespace internal\n{\n\nstatic FSAStatus\nfsaGetInfoByQuery(FSAClientHandle clientHandle,\n                  virt_ptr<const char> path,\n                  FSAQueryInfoType type,\n                  virt_ptr<void> out);\n\n} // namespace internal\n\nFSAStatus\nFSAChangeDir(FSAClientHandle clientHandle,\n             virt_ptr<const char> path)\n{\n   auto shimBuffer = StackObject<virt_ptr<FSAShimBuffer>> { };\n\n   if (!FSAShimCheckClientHandle(clientHandle)) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   auto status = FSAShimAllocateBuffer(shimBuffer);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status = internal::fsaShimPrepareRequestChangeDir(*shimBuffer,\n                                                     clientHandle,\n                                                     path);\n   if (status >= FSAStatus::OK) {\n      status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK);\n   }\n\n   FSAShimFreeBuffer(*shimBuffer);\n   return status;\n}\n\nFSAStatus\nFSACloseFile(FSAClientHandle clientHandle,\n             FSAFileHandle fileHandle)\n{\n   auto shimBuffer = StackObject<virt_ptr<FSAShimBuffer>> { };\n\n   if (!FSAShimCheckClientHandle(clientHandle)) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   auto status = FSAShimAllocateBuffer(shimBuffer);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status = internal::fsaShimPrepareRequestCloseFile(*shimBuffer,\n                                                     clientHandle,\n                                                     fileHandle);\n   if (status >= FSAStatus::OK) {\n      status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK);\n   }\n\n   FSAShimFreeBuffer(*shimBuffer);\n   return status;\n}\n\nFSAStatus\nFSAGetStat(FSAClientHandle clientHandle,\n           virt_ptr<const char> path,\n           virt_ptr<FSAStat> stat)\n{\n   return internal::fsaGetInfoByQuery(clientHandle,\n                                      path,\n                                      FSAQueryInfoType::Stat,\n                                      stat);\n}\n\nFSAStatus\nFSAGetStatFile(FSAClientHandle clientHandle,\n               FSFileHandle handle,\n               virt_ptr<FSStat> outStat)\n{\n   auto shimBuffer = StackObject<virt_ptr<FSAShimBuffer>> { };\n\n   if (!FSAShimCheckClientHandle(clientHandle)) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   auto status = FSAShimAllocateBuffer(shimBuffer);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status = internal::fsaShimPrepareRequestStatFile(*shimBuffer,\n                                                    clientHandle,\n                                                    handle);\n   if (status >= FSAStatus::OK) {\n      status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK);\n\n      if (status >= FSAStatus::OK) {\n         *outStat = (*shimBuffer)->response.statFile.stat;\n      }\n   }\n\n   FSAShimFreeBuffer(*shimBuffer);\n   return status;\n}\n\nFSAStatus\nFSAMakeDir(FSAClientHandle clientHandle,\n           virt_ptr<const char> path,\n           uint32_t permissions)\n{\n   auto shimBuffer = StackObject<virt_ptr<FSAShimBuffer>> { };\n\n   if (!FSAShimCheckClientHandle(clientHandle)) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   auto status = FSAShimAllocateBuffer(shimBuffer);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status = internal::fsaShimPrepareRequestMakeDir(*shimBuffer,\n                                                   clientHandle,\n                                                   path,\n                                                   permissions);\n   if (status >= FSAStatus::OK) {\n      status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK);\n   }\n\n   FSAShimFreeBuffer(*shimBuffer);\n   return status;\n}\n\nFSAStatus\nFSAMount(FSAClientHandle clientHandle,\n         virt_ptr<const char> path,\n         virt_ptr<const char> target,\n         uint32_t unk0,\n         virt_ptr<void> unkBuf,\n         uint32_t unkBufLen)\n{\n   auto shimBuffer = StackObject<virt_ptr<FSAShimBuffer>> { };\n\n   if (!FSAShimCheckClientHandle(clientHandle)) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   auto status = FSAShimAllocateBuffer(shimBuffer);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status = internal::fsaShimPrepareRequestMount(*shimBuffer,\n                                                 clientHandle,\n                                                 path,\n                                                 target,\n                                                 unk0,\n                                                 unkBuf,\n                                                 unkBufLen);\n   if (status >= FSAStatus::OK) {\n      status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK);\n   }\n\n   FSAShimFreeBuffer(*shimBuffer);\n   return status;\n}\n\nFSAStatus\nFSAOpenFile(FSAClientHandle clientHandle,\n            virt_ptr<const char> path,\n            virt_ptr<const char> mode,\n            virt_ptr<FSAFileHandle> outHandle)\n{\n   auto shimBuffer = StackObject<virt_ptr<FSAShimBuffer>> { };\n\n   if (!FSAShimCheckClientHandle(clientHandle)) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   auto status = FSAShimAllocateBuffer(shimBuffer);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status = internal::fsaShimPrepareRequestOpenFile(*shimBuffer,\n                                                    clientHandle,\n                                                    path,\n                                                    mode,\n                                                    0x660,\n                                                    0,\n                                                    0);\n   if (status >= FSAStatus::OK) {\n      status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK);\n   }\n\n   if (status >= FSAStatus::OK) {\n      *outHandle = (*shimBuffer)->response.openFile.handle;\n   }\n\n   FSAShimFreeBuffer(*shimBuffer);\n   return status;\n}\n\nFSAStatus\nFSAReadFile(FSAClientHandle clientHandle,\n             virt_ptr<uint8_t> buffer,\n             uint32_t size,\n             uint32_t count,\n             FSAFileHandle fileHandle,\n             FSAReadFlag readFlags)\n{\n   auto shimBuffer = StackObject<virt_ptr<FSAShimBuffer>> { };\n\n   if (!FSAShimCheckClientHandle(clientHandle)) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   auto status = FSAShimAllocateBuffer(shimBuffer);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status =\n      internal::fsaShimPrepareRequestReadFile(*shimBuffer,\n                                              clientHandle,\n                                              buffer,\n                                              size,\n                                              count,\n                                              0,\n                                              fileHandle,\n                                              readFlags & ~FSAReadFlag::ReadWithPos);\n   if (status >= FSAStatus::OK) {\n      status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK);\n   }\n\n   FSAShimFreeBuffer(*shimBuffer);\n   return status;\n}\n\nFSAStatus\nFSARemove(FSAClientHandle clientHandle,\n          virt_ptr<const char> path)\n{\n   auto shimBuffer = StackObject<virt_ptr<FSAShimBuffer>> { };\n\n   if (!FSAShimCheckClientHandle(clientHandle)) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   auto status = FSAShimAllocateBuffer(shimBuffer);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status = internal::fsaShimPrepareRequestRemove(*shimBuffer,\n                                                  clientHandle,\n                                                  path);\n   if (status >= FSAStatus::OK) {\n      status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK);\n   }\n\n   FSAShimFreeBuffer(*shimBuffer);\n   return status;\n}\n\nFSAStatus\nFSAWriteFile(FSAClientHandle clientHandle,\n             virt_ptr<const uint8_t> buffer,\n             uint32_t size,\n             uint32_t count,\n             FSAFileHandle fileHandle,\n             FSAWriteFlag writeFlags)\n{\n   auto shimBuffer = StackObject<virt_ptr<FSAShimBuffer>> { };\n\n   if (!FSAShimCheckClientHandle(clientHandle)) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   auto status = FSAShimAllocateBuffer(shimBuffer);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status =\n      internal::fsaShimPrepareRequestWriteFile(*shimBuffer,\n                                               clientHandle,\n                                               buffer,\n                                               size,\n                                               count,\n                                               0,\n                                               fileHandle,\n                                               writeFlags & ~FSAWriteFlag::WriteWithPos);\n   if (status >= FSAStatus::OK) {\n      status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK);\n   }\n\n   FSAShimFreeBuffer(*shimBuffer);\n   return status;\n}\n\nnamespace internal\n{\n\nFSAStatus\nfsaGetInfoByQuery(FSAClientHandle clientHandle,\n                  virt_ptr<const char> path,\n                  FSAQueryInfoType type,\n                  virt_ptr<void> out)\n{\n   auto shimBuffer = StackObject<virt_ptr<FSAShimBuffer>> { };\n\n   if (!FSAShimCheckClientHandle(clientHandle)) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   auto status = FSAShimAllocateBuffer(shimBuffer);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status = internal::fsaShimPrepareRequestGetInfoByQuery(*shimBuffer,\n                                                          clientHandle,\n                                                          path,\n                                                          type);\n   if (status >= FSAStatus::OK) {\n      status = internal::fsaShimSubmitRequest(*shimBuffer, FSAStatus::OK);\n   }\n\n   if (status >= FSAStatus::OK) {\n      switch (type) {\n      case FSAQueryInfoType::FreeSpaceSize:\n      {\n         auto freeSpaceSize = virt_cast<uint64_t *>(out);\n         *freeSpaceSize = (*shimBuffer)->response.getInfoByQuery.freeSpaceSize;\n         break;\n      }\n      case FSAQueryInfoType::DirSize:\n      {\n         auto dirSize = virt_cast<uint64_t *>(out);\n         *dirSize = (*shimBuffer)->response.getInfoByQuery.dirSize;\n         break;\n      }\n      case FSAQueryInfoType::EntryNum:\n      {\n         auto entryNum = virt_cast<int32_t *>(out);\n         *entryNum = (*shimBuffer)->response.getInfoByQuery.entryNum;\n         break;\n      }\n      case FSAQueryInfoType::FileSystemInfo:\n      {\n         auto fileSystemInfo = virt_cast<FSAFileSystemInfo *>(out);\n         *fileSystemInfo = (*shimBuffer)->response.getInfoByQuery.fileSystemInfo;\n         break;\n      }\n      case FSAQueryInfoType::DeviceInfo:\n      {\n         auto deviceInfo = virt_cast<FSADeviceInfo *>(out);\n         *deviceInfo = (*shimBuffer)->response.getInfoByQuery.deviceInfo;\n         break;\n      }\n      case FSAQueryInfoType::Stat:\n      {\n         auto stat = virt_cast<FSAStat *>(out);\n         *stat = (*shimBuffer)->response.getInfoByQuery.stat;\n         break;\n      }\n      case FSAQueryInfoType::BadBlockInfo:\n      {\n         auto badBlockInfo = virt_cast<FSABlockInfo *>(out);\n         *badBlockInfo = (*shimBuffer)->response.getInfoByQuery.badBlockInfo;\n         break;\n      }\n      case FSAQueryInfoType::JournalFreeSpaceSize:\n      {\n         auto freeSpaceSize = virt_cast<uint64_t *>(out);\n         *freeSpaceSize = (*shimBuffer)->response.getInfoByQuery.journalFreeSpaceSize;\n         break;\n      }\n      default:\n         decaf_abort(fmt::format(\"Unexpected QueryInfoType: {}\", type));\n      }\n   }\n\n   FSAShimFreeBuffer(*shimBuffer);\n   return status;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFsaCmdSymbols()\n{\n   RegisterFunctionExport(FSAChangeDir);\n   RegisterFunctionExport(FSACloseFile);\n   RegisterFunctionExport(FSAGetStat);\n   RegisterFunctionExport(FSAGetStatFile);\n   RegisterFunctionExport(FSAMakeDir);\n   RegisterFunctionExport(FSAMount);\n   RegisterFunctionExport(FSAOpenFile);\n   RegisterFunctionExport(FSAReadFile);\n   RegisterFunctionExport(FSARemove);\n   RegisterFunctionExport(FSAWriteFile);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa_cmd.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_fsa.h\"\n\nnamespace cafe::coreinit\n{\n\nFSAStatus\nFSAChangeDir(FSAClientHandle clientHandle,\n             virt_ptr<const char> path);\n\nFSAStatus\nFSACloseFile(FSAClientHandle clientHandle,\n             FSAFileHandle fileHandle);\n\nFSAStatus\nFSAGetStat(FSAClientHandle clientHandle,\n           virt_ptr<const char> path,\n           virt_ptr<FSAStat> stat);\n\nFSAStatus\nFSAGetStatFile(FSAClientHandle clientHandle,\n               FSFileHandle handle,\n               virt_ptr<FSStat> outStat);\n\nFSAStatus\nFSAMakeDir(FSAClientHandle clientHandle,\n           virt_ptr<const char> path,\n           uint32_t permissions);\n\nFSAStatus\nFSAMount(FSAClientHandle clientHandle,\n         virt_ptr<const char> path,\n         virt_ptr<const char> target,\n         uint32_t unk0,\n         virt_ptr<void> unkBuf,\n         uint32_t unkBufLen);\n\nFSAStatus\nFSAOpenFile(FSAClientHandle clientHandle,\n            virt_ptr<const char> path,\n            virt_ptr<const char> mode,\n            virt_ptr<FSAFileHandle> outHandle);\n\nFSAStatus\nFSAReadFile(FSAClientHandle clientHandle,\n            virt_ptr<uint8_t> buffer,\n            uint32_t size,\n            uint32_t count,\n            FSAFileHandle fileHandle,\n            FSAReadFlag readFlags);\n\nFSAStatus\nFSARemove(FSAClientHandle clientHandle,\n          virt_ptr<const char> path);\n\nFSAStatus\nFSAWriteFile(FSAClientHandle clientHandle,\n             virt_ptr<const uint8_t> buffer,\n             uint32_t size,\n             uint32_t count,\n             FSAFileHandle fileHandle,\n             FSAWriteFlag writeFlags);\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa_shim.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_fsa_shim.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"ios/ios_error.h\"\n\n#include <common/decaf_assert.h>\n#include <common/strutils.h>\n#include <fmt/format.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * Using the power of magic, turns an IOSError into an FSAStatus.\n */\nFSAStatus\nFSAShimDecodeIosErrorToFsaStatus(IOSHandle handle,\n                                 IOSError error)\n{\n   auto category = ios::getErrorCategory(error);\n   auto code = ios::getErrorCode(error);\n   auto fsaStatus = static_cast<FSAStatus>(error);\n\n   if (error < 0) {\n      switch (category) {\n      case IOSErrorCategory::Kernel:\n         if (code == IOSError::Access) {\n            fsaStatus = FSAStatus::InvalidBuffer;\n         } else if (code == IOSError::Invalid || code == IOSError::NoExists) {\n            fsaStatus = FSAStatus::InvalidClientHandle;\n         } else if (code == IOSError::QFull) {\n            fsaStatus = FSAStatus::Busy;\n         } else {\n            fsaStatus = static_cast<FSAStatus>(code);\n         }\n         break;\n      case IOSErrorCategory::FSA:\n      case IOSErrorCategory::Unknown7:\n      case IOSErrorCategory::Unknown8:\n      case IOSErrorCategory::Unknown15:\n      case IOSErrorCategory::Unknown19:\n      case IOSErrorCategory::Unknown30:\n      case IOSErrorCategory::Unknown45:\n         if (ios::isKernelError(code)) {\n            fsaStatus = static_cast<FSAStatus>(code - (IOSErrorCategory::FSA << 16));\n         } else {\n            fsaStatus = static_cast<FSAStatus>(code);\n         }\n         break;\n      }\n   }\n\n   return fsaStatus;\n}\n\n\nnamespace internal\n{\n\n/**\n * Open FSA device.\n */\nIOSError\nfsaShimOpen()\n{\n   return IOS_Open(make_stack_string(\"/dev/fsa\"), IOSOpenMode::None);\n}\n\n\n/**\n * Close FSA device.\n */\nIOSError\nfsaShimClose(IOSHandle handle)\n{\n   return IOS_Close(handle);\n}\n\n\n/**\n * Submit a synchronous FSA request.\n */\nFSAStatus\nfsaShimSubmitRequest(virt_ptr<FSAShimBuffer> shim,\n                     FSAStatus emulatedError)\n{\n   auto iosError = IOSError::Invalid;\n\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->request.emulatedError = emulatedError;\n\n   if (shim->ipcReqType == FSAIpcRequestType::Ioctl) {\n      iosError = IOS_Ioctl(shim->clientHandle,\n                           shim->command,\n                           virt_addrof(shim->request),\n                           sizeof(shim->request),\n                           virt_addrof(shim->response),\n                           sizeof(shim->response));\n   } else if (shim->ipcReqType == FSAIpcRequestType::Ioctlv) {\n      iosError = IOS_Ioctlv(shim->clientHandle,\n                            shim->command,\n                            shim->ioctlvVecIn,\n                            shim->ioctlvVecOut,\n                            virt_addrof(shim->ioctlvVec));\n   } else {\n      decaf_abort(fmt::format(\"Invalid reqType {}\", shim->ipcReqType));\n   }\n\n   return FSAShimDecodeIosErrorToFsaStatus(shim->clientHandle, iosError);\n}\n\n\n/**\n * Submit an asynchronous FSA request.\n */\nFSAStatus\nfsaShimSubmitRequestAsync(virt_ptr<FSAShimBuffer> shim,\n                          FSAStatus emulatedError,\n                          IOSAsyncCallbackFn callback,\n                          virt_ptr<void> context)\n{\n   auto iosError = IOSError::Invalid;\n\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->request.emulatedError = emulatedError;\n\n   if (shim->ipcReqType == FSAIpcRequestType::Ioctl) {\n      iosError = IOS_IoctlAsync(shim->clientHandle,\n                                shim->command,\n                                virt_addrof(shim->request),\n                                sizeof(shim->request),\n                                virt_addrof(shim->response),\n                                sizeof(shim->response),\n                                callback,\n                                context);\n   } else if (shim->ipcReqType == FSAIpcRequestType::Ioctlv) {\n      iosError = IOS_IoctlvAsync(shim->clientHandle,\n                                 shim->command,\n                                 shim->ioctlvVecIn,\n                                 shim->ioctlvVecOut,\n                                 virt_addrof(shim->ioctlvVec),\n                                 callback,\n                                 context);\n   } else {\n      decaf_abort(fmt::format(\"Invalid reqType {}\", shim->ipcReqType));\n   }\n\n   return FSAShimDecodeIosErrorToFsaStatus(shim->clientHandle, iosError);\n}\n\n\n/**\n * Prepare a FSACommand::AppendFile request.\n */\nFSAStatus\nfsaShimPrepareRequestAppendFile(virt_ptr<FSAShimBuffer> shim,\n                                IOSHandle clientHandle,\n                                FSFileHandle handle,\n                                uint32_t size,\n                                uint32_t count,\n                                uint32_t unk)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::AppendFile;\n\n   auto request = virt_addrof(shim->request.appendFile);\n   request->size = size;\n   request->count = count;\n   request->handle = handle;\n   request->unk0x0C = unk;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::ChangeDir request.\n */\nFSAStatus\nfsaShimPrepareRequestChangeDir(virt_ptr<FSAShimBuffer> shim,\n                               IOSHandle clientHandle,\n                               virt_ptr<const char> path)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!path || std::strlen(path.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::ChangeDir;\n\n   auto request = virt_addrof(shim->request.changeDir);\n   string_copy(virt_addrof(request->path).get(),\n               request->path.size(),\n               path.get(),\n               FSMaxPathLength);\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::ChangeMode request.\n */\nFSAStatus\nfsaShimPrepareRequestChangeMode(virt_ptr<FSAShimBuffer> shim,\n                                IOSHandle clientHandle,\n                                virt_ptr<const char> path,\n                                uint32_t mode1,\n                                uint32_t mode2)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!path || std::strlen(path.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::ChangeMode;\n\n   auto request = virt_addrof(shim->request.changeMode);\n   string_copy(virt_addrof(request->path).get(),\n               request->path.size(),\n               path.get(),\n               FSMaxPathLength);\n   request->mode1 = mode1;\n   request->mode2 = mode2;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::CloseDir request.\n */\nFSAStatus\nfsaShimPrepareRequestCloseDir(virt_ptr<FSAShimBuffer> shim,\n                              IOSHandle clientHandle,\n                              FSDirHandle dirHandle)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::CloseDir;\n\n   auto request = virt_addrof(shim->request.closeDir);\n   request->handle = dirHandle;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::CloseFile request.\n */\nFSAStatus\nfsaShimPrepareRequestCloseFile(virt_ptr<FSAShimBuffer> shim,\n                               IOSHandle clientHandle,\n                               FSFileHandle fileHandle)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::CloseFile;\n\n   auto request = virt_addrof(shim->request.closeFile);\n   request->handle = fileHandle;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::FlushFile request.\n */\nFSAStatus\nfsaShimPrepareRequestFlushFile(virt_ptr<FSAShimBuffer> shim,\n                               IOSHandle clientHandle,\n                               FSFileHandle fileHandle)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::FlushFile;\n\n   auto request = virt_addrof(shim->request.flushFile);\n   request->handle = fileHandle;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::FlushQuota request.\n */\nFSAStatus\nfsaShimPrepareRequestFlushQuota(virt_ptr<FSAShimBuffer> shim,\n                                IOSHandle clientHandle,\n                                virt_ptr<const char> path)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!path || std::strlen(path.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::FlushQuota;\n\n   auto request = virt_addrof(shim->request.flushQuota);\n   string_copy(virt_addrof(request->path).get(),\n               request->path.size(),\n               path.get(),\n               FSMaxPathLength);\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::GetCwd request.\n */\nFSAStatus\nfsaShimPrepareRequestGetCwd(virt_ptr<FSAShimBuffer> shim,\n                            IOSHandle clientHandle)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::GetCwd;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::GetInfoByQuery request.\n */\nFSAStatus\nfsaShimPrepareRequestGetInfoByQuery(virt_ptr<FSAShimBuffer> shim,\n                                    IOSHandle clientHandle,\n                                    virt_ptr<const char> path,\n                                    FSAQueryInfoType type)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!path || std::strlen(path.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   if (type > FSAQueryInfoType::FragmentBlockInfo) {\n      return FSAStatus::InvalidParam;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::GetInfoByQuery;\n\n   auto request = virt_addrof(shim->request.getInfoByQuery);\n   string_copy(virt_addrof(request->path).get(),\n               request->path.size(),\n               path.get(),\n               FSMaxPathLength);\n   request->type = type;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::GetPosFile request.\n */\nFSAStatus\nfsaShimPrepareRequestGetPosFile(virt_ptr<FSAShimBuffer> shim,\n                                IOSHandle clientHandle,\n                                FSFileHandle fileHandle)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::GetPosFile;\n\n   auto request = virt_addrof(shim->request.getPosFile);\n   request->handle = fileHandle;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::IsEof request.\n */\nFSAStatus\nfsaShimPrepareRequestIsEof(virt_ptr<FSAShimBuffer> shim,\n                           IOSHandle clientHandle,\n                           FSFileHandle fileHandle)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::IsEof;\n\n   auto request = virt_addrof(shim->request.isEof);\n   request->handle = fileHandle;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::MakeDir request.\n */\nFSAStatus\nfsaShimPrepareRequestMakeDir(virt_ptr<FSAShimBuffer> shim,\n                             IOSHandle clientHandle,\n                             virt_ptr<const char> path,\n                             uint32_t permissions)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!path || std::strlen(path.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::MakeDir;\n\n   auto request = virt_addrof(shim->request.makeDir);\n   string_copy(virt_addrof(request->path).get(),\n               request->path.size(),\n               path.get(),\n               FSMaxPathLength);\n   request->permission = permissions;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::Mount request.\n */\nFSAStatus\nfsaShimPrepareRequestMount(virt_ptr<FSAShimBuffer> shim,\n                           IOSHandle clientHandle,\n                           virt_ptr<const char> path,\n                           virt_ptr<const char> target,\n                           uint32_t unk0,\n                           virt_ptr<void> unkBuf,\n                           uint32_t unkBufLen)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!path || std::strlen(path.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctlv;\n   shim->command = FSACommand::Mount;\n\n   auto request = virt_addrof(shim->request.mount);\n   string_copy(virt_addrof(request->path).get(),\n               request->path.size(),\n               path.get(),\n               FSMaxPathLength);\n   string_copy(virt_addrof(request->target).get(),\n               request->target.size(),\n               target.get(),\n               FSMaxPathLength);\n   request->unk0x500 = unk0;\n   request->unkBufLen = unkBufLen;\n\n   shim->ioctlvVecIn = uint8_t { 2 };\n   shim->ioctlvVecOut = uint8_t { 1 };\n\n   shim->ioctlvVec[0].vaddr = virt_cast<virt_addr>(virt_addrof(shim->request));\n   shim->ioctlvVec[0].len = static_cast<uint32_t>(sizeof(FSARequest));\n\n   shim->ioctlvVec[1].vaddr = virt_cast<virt_addr>(unkBuf);\n   shim->ioctlvVec[1].len = unkBufLen;\n\n   shim->ioctlvVec[2].vaddr = virt_cast<virt_addr>(virt_addrof(shim->response));\n   shim->ioctlvVec[2].len = static_cast<uint32_t>(sizeof(FSAResponse));\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::OpenFile request.\n */\nFSAStatus\nfsaShimPrepareRequestOpenDir(virt_ptr<FSAShimBuffer> shim,\n                             IOSHandle clientHandle,\n                             virt_ptr<const char> path)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!path || std::strlen(path.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::OpenDir;\n\n   auto request = virt_addrof(shim->request.openDir);\n   string_copy(virt_addrof(request->path).get(),\n               request->path.size(),\n               path.get(),\n               FSMaxPathLength);\n\n   auto response = virt_addrof(shim->response.openDir);\n   response->handle = -1;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::OpenFile request.\n */\nFSAStatus\nfsaShimPrepareRequestOpenFile(virt_ptr<FSAShimBuffer> shim,\n                              IOSHandle clientHandle,\n                              virt_ptr<const char> path,\n                              virt_ptr<const char> mode,\n                              uint32_t unk0x290,\n                              uint32_t unk0x294,\n                              uint32_t unk0x298)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!path || std::strlen(path.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   if (!mode || std::strlen(mode.get()) >= 15) {\n      return FSAStatus::InvalidParam;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::OpenFile;\n\n   auto &request = shim->request.openFile;\n   string_copy(virt_addrof(request.path).get(),\n               request.path.size(),\n               path.get(),\n               FSMaxPathLength);\n   string_copy(virt_addrof(request.mode).get(),\n               request.mode.size(),\n               mode.get(),\n               16);\n   request.unk0x290 = unk0x290;\n   request.unk0x294 = unk0x294;\n   request.unk0x298 = unk0x298;\n\n   auto &response = shim->response.openFile;\n   response.handle = -1;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::ReadDir request.\n */\nFSAStatus\nfsaShimPrepareRequestReadDir(virt_ptr<FSAShimBuffer> shim,\n                             IOSHandle clientHandle,\n                             FSDirHandle dirHandle)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::ReadDir;\n\n   auto &request = shim->request.readDir;\n   request.handle = dirHandle;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::ReadFile request.\n */\nFSAStatus\nfsaShimPrepareRequestReadFile(virt_ptr<FSAShimBuffer> shim,\n                              IOSHandle clientHandle,\n                              virt_ptr<uint8_t> buffer,\n                              uint32_t size,\n                              uint32_t count,\n                              uint32_t pos,\n                              FSFileHandle handle,\n                              FSAReadFlag readFlags)\n{\n   if (!shim || !buffer) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctlv;\n   shim->command = FSACommand::ReadFile;\n\n   shim->ioctlvVecIn = uint8_t { 1 };\n   shim->ioctlvVecOut = uint8_t { 2 };\n\n   shim->ioctlvVec[0].vaddr = virt_cast<virt_addr>(virt_addrof(shim->request));\n   shim->ioctlvVec[0].len = static_cast<uint32_t>(sizeof(FSARequest));\n\n   shim->ioctlvVec[1].vaddr = virt_cast<virt_addr>(buffer);\n   shim->ioctlvVec[1].len = size * count;\n\n   shim->ioctlvVec[2].vaddr = virt_cast<virt_addr>(virt_addrof(shim->response));\n   shim->ioctlvVec[2].len = static_cast<uint32_t>(sizeof(FSAResponse));\n\n   auto &request = shim->request.readFile;\n   request.buffer = virt_cast<uint8_t *>(buffer);\n   request.size = size;\n   request.count = count;\n   request.pos = pos;\n   request.handle = handle;\n   request.readFlags = readFlags;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::Remove request.\n */\nFSAStatus\nfsaShimPrepareRequestRemove(virt_ptr<FSAShimBuffer> shim,\n                            IOSHandle clientHandle,\n                            virt_ptr<const char> path)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!path || std::strlen(path.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::Remove;\n\n   auto &request = shim->request.remove;\n   string_copy(virt_addrof(request.path).get(),\n               request.path.size(),\n               path.get(),\n               FSMaxPathLength);\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::Rename request.\n */\nFSAStatus\nfsaShimPrepareRequestRename(virt_ptr<FSAShimBuffer> shim,\n                            IOSHandle clientHandle,\n                            virt_ptr<const char> oldPath,\n                            virt_ptr<const char> newPath)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!oldPath || std::strlen(oldPath.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   if (!newPath || std::strlen(newPath.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::Rename;\n\n   auto &request =shim->request.rename;\n   string_copy(virt_addrof(request.oldPath).get(),\n               request.oldPath.size(),\n               oldPath.get(),\n               FSMaxPathLength);\n   string_copy(virt_addrof(request.newPath).get(),\n               request.newPath.size(),\n               newPath.get(),\n               FSMaxPathLength);\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::RewindDir request.\n */\nFSAStatus\nfsaShimPrepareRequestRewindDir(virt_ptr<FSAShimBuffer> shim,\n                               IOSHandle clientHandle,\n                               FSDirHandle dirHandle)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::RewindDir;\n\n   auto &request = shim->request.rewindDir;\n   request.handle = dirHandle;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::SetPosFile request.\n */\nFSAStatus\nfsaShimPrepareRequestSetPosFile(virt_ptr<FSAShimBuffer> shim,\n                                IOSHandle clientHandle,\n                                FSFileHandle fileHandle,\n                                FSFilePosition pos)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::SetPosFile;\n\n   auto &request = shim->request.setPosFile;\n   request.handle = fileHandle;\n   request.pos = pos;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::StatFile request.\n */\nFSAStatus\nfsaShimPrepareRequestStatFile(virt_ptr<FSAShimBuffer> shim,\n                              IOSHandle clientHandle,\n                              FSFileHandle fileHandle)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::StatFile;\n\n   auto &request = shim->request.statFile;\n   request.handle = fileHandle;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::TruncateFile request.\n */\nFSAStatus\nfsaShimPrepareRequestTruncateFile(virt_ptr<FSAShimBuffer> shim,\n                                  IOSHandle clientHandle,\n                                  FSFileHandle fileHandle)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::TruncateFile;\n\n   auto &request = shim->request.truncateFile;\n   request.handle = fileHandle;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::Unmount request.\n */\nFSAStatus\nfsaShimPrepareRequestUnmount(virt_ptr<FSAShimBuffer> shim,\n                             IOSHandle clientHandle,\n                             virt_ptr<const char> path,\n                             uint32_t unk0x280)\n{\n   if (!shim) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   if (!path || std::strlen(path.get()) >= FSMaxPathLength) {\n      return FSAStatus::InvalidPath;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctl;\n   shim->command = FSACommand::Unmount;\n\n   auto &request = shim->request.unmount;\n   string_copy(virt_addrof(request.path).get(),\n               request.path.size(),\n               path.get(),\n               FSMaxPathLength);\n   request.unk0x280 = unk0x280;\n\n   return FSAStatus::OK;\n}\n\n\n/**\n * Prepare a FSACommand::WriteFile request.\n */\nFSAStatus\nfsaShimPrepareRequestWriteFile(virt_ptr<FSAShimBuffer> shim,\n                               IOSHandle clientHandle,\n                               virt_ptr<const uint8_t> buffer,\n                               uint32_t size,\n                               uint32_t count,\n                               uint32_t pos,\n                               FSFileHandle handle,\n                               FSAWriteFlag writeFlags)\n{\n   if (!shim || !buffer) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   shim->clientHandle = clientHandle;\n   shim->ipcReqType = FSAIpcRequestType::Ioctlv;\n   shim->command = FSACommand::WriteFile;\n\n   shim->ioctlvVecIn = uint8_t { 2 };\n   shim->ioctlvVecOut = uint8_t { 1 };\n\n   shim->ioctlvVec[0].vaddr = virt_cast<virt_addr>(virt_addrof(shim->request));\n   shim->ioctlvVec[0].len = static_cast<uint32_t>(sizeof(FSARequest));\n\n   shim->ioctlvVec[1].vaddr = virt_cast<virt_addr>(buffer);\n   shim->ioctlvVec[1].len = size * count;\n\n   shim->ioctlvVec[2].vaddr = virt_cast<virt_addr>(virt_addrof(shim->response));\n   shim->ioctlvVec[2].len = static_cast<uint32_t>(sizeof(FSAResponse));\n\n   auto &request = shim->request.writeFile;\n   request.buffer = virt_cast<const uint8_t *>(buffer);\n   request.size = size;\n   request.count = count;\n   request.pos = pos;\n   request.handle = handle;\n   request.writeFlags = writeFlags;\n\n   return FSAStatus::OK;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFsaShimSymbols()\n{\n   RegisterFunctionExportName(\"__FSAShimDecodeIosErrorToFsaStatus\",\n                              FSAShimDecodeIosErrorToFsaStatus);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_fsa_shim.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_fs.h\"\n#include \"coreinit_fsa.h\"\n#include \"coreinit_ios.h\"\n#include \"coreinit_messagequeue.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\ingroup coreinit_fsa\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct FSAShimBuffer;\n\n/**\n * Stores data regarding FSA IPC requests.\n */\nstruct FSAShimBuffer\n{\n   //! Buffer for FSA IPC request.\n   be2_struct<FSARequest> request;\n   UNKNOWN(0x60);\n\n   //! Buffer for FSA IPC response.\n   be2_struct<FSAResponse> response;\n   UNKNOWN(0x880 - 0x813);\n\n   //! Memory to use for ioctlv calls, unknown maximum count - but at least 3.\n   be2_array<IOSVec, 3> ioctlvVec;\n\n   UNKNOWN(0x900 - 0x8A4);\n\n   //! Command for FSA.\n   be2_val<FSACommand> command;\n\n   //! Handle to FSA device.\n   be2_val<IOSHandle> clientHandle;\n\n   //! IOS IPC request type to use.\n   be2_val<FSAIpcRequestType> ipcReqType;\n\n   //! Number of ioctlv input vectors.\n   be2_val<uint8_t> ioctlvVecIn;\n\n   //! Number of ioctlv output vectors.\n   be2_val<uint8_t> ioctlvVecOut;\n\n   //! FSAAsyncResult used for FSA* functions.\n   be2_struct<FSAAsyncResult> fsaAsyncResult;\n};\nCHECK_OFFSET(FSAShimBuffer, 0x0, request);\nCHECK_OFFSET(FSAShimBuffer, 0x580, response);\nCHECK_OFFSET(FSAShimBuffer, 0x880, ioctlvVec);\nCHECK_OFFSET(FSAShimBuffer, 0x900, command);\nCHECK_OFFSET(FSAShimBuffer, 0x904, clientHandle);\nCHECK_OFFSET(FSAShimBuffer, 0x908, ipcReqType);\nCHECK_OFFSET(FSAShimBuffer, 0x90A, ioctlvVecIn);\nCHECK_OFFSET(FSAShimBuffer, 0x90B, ioctlvVecOut);\nCHECK_OFFSET(FSAShimBuffer, 0x90C, fsaAsyncResult);\nCHECK_SIZE(FSAShimBuffer, 0x938);\n\n#pragma pack(pop)\n\nFSAStatus\nFSAShimDecodeIosErrorToFsaStatus(IOSHandle handle,\n                                 IOSError error);\n\nnamespace internal\n{\n\nIOSError\nfsaShimOpen();\n\nIOSError\nfsaShimClose(IOSHandle handle);\n\nFSAStatus\nfsaShimSubmitRequest(virt_ptr<FSAShimBuffer> shim,\n                     FSAStatus emulatedError);\n\nFSAStatus\nfsaShimSubmitRequestAsync(virt_ptr<FSAShimBuffer> shim,\n                          FSAStatus emulatedError,\n                          IOSAsyncCallbackFn callback,\n                          virt_ptr<void> context);\n\nFSAStatus\nfsaShimPrepareRequestAppendFile(virt_ptr<FSAShimBuffer> shim,\n                                IOSHandle clientHandle,\n                                FSFileHandle handle,\n                                uint32_t size,\n                                uint32_t count,\n                                uint32_t unk);\n\nFSAStatus\nfsaShimPrepareRequestChangeDir(virt_ptr<FSAShimBuffer> shim,\n                               IOSHandle clientHandle,\n                               virt_ptr<const char> path);\n\nFSAStatus\nfsaShimPrepareRequestChangeMode(virt_ptr<FSAShimBuffer> shim,\n                                IOSHandle clientHandle,\n                                virt_ptr<const char> path,\n                                uint32_t mode1,\n                                uint32_t mode2);\n\nFSAStatus\nfsaShimPrepareRequestCloseDir(virt_ptr<FSAShimBuffer> shim,\n                              IOSHandle clientHandle,\n                              FSDirHandle dirHandle);\n\nFSAStatus\nfsaShimPrepareRequestCloseFile(virt_ptr<FSAShimBuffer> shim,\n                               IOSHandle clientHandle,\n                               FSFileHandle fileHandle);\n\nFSAStatus\nfsaShimPrepareRequestFlushFile(virt_ptr<FSAShimBuffer> shim,\n                               IOSHandle clientHandle,\n                               FSFileHandle fileHandle);\n\nFSAStatus\nfsaShimPrepareRequestFlushQuota(virt_ptr<FSAShimBuffer> shim,\n                                IOSHandle clientHandle,\n                                virt_ptr<const char> path);\n\nFSAStatus\nfsaShimPrepareRequestGetCwd(virt_ptr<FSAShimBuffer> shim,\n                            IOSHandle clientHandle);\n\nFSAStatus\nfsaShimPrepareRequestGetInfoByQuery(virt_ptr<FSAShimBuffer> shim,\n                                    IOSHandle clientHandle,\n                                    virt_ptr<const char> path,\n                                    FSAQueryInfoType type);\n\nFSAStatus\nfsaShimPrepareRequestGetPosFile(virt_ptr<FSAShimBuffer> shim,\n                                IOSHandle clientHandle,\n                                FSFileHandle fileHandle);\n\nFSAStatus\nfsaShimPrepareRequestIsEof(virt_ptr<FSAShimBuffer> shim,\n                           IOSHandle clientHandle,\n                           FSFileHandle fileHandle);\n\nFSAStatus\nfsaShimPrepareRequestMakeDir(virt_ptr<FSAShimBuffer> shim,\n                             IOSHandle clientHandle,\n                             virt_ptr<const char> path,\n                             uint32_t permissions);\n\nFSAStatus\nfsaShimPrepareRequestMount(virt_ptr<FSAShimBuffer> shim,\n                           IOSHandle clientHandle,\n                           virt_ptr<const char> path,\n                           virt_ptr<const char> target,\n                           uint32_t unk0,\n                           virt_ptr<void> unkBuf,\n                           uint32_t unkBufLen);\n\nFSAStatus\nfsaShimPrepareRequestOpenDir(virt_ptr<FSAShimBuffer> shim,\n                             IOSHandle clientHandle,\n                             virt_ptr<const char> path);\n\nFSAStatus\nfsaShimPrepareRequestOpenFile(virt_ptr<FSAShimBuffer> shim,\n                              IOSHandle clientHandle,\n                              virt_ptr<const char> path,\n                              virt_ptr<const char> mode,\n                              uint32_t unk0x290,\n                              uint32_t unk0x294,\n                              uint32_t unk0x298);\n\nFSAStatus\nfsaShimPrepareRequestReadDir(virt_ptr<FSAShimBuffer> shim,\n                             IOSHandle clientHandle,\n                             FSDirHandle dirHandle);\n\nFSAStatus\nfsaShimPrepareRequestReadFile(virt_ptr<FSAShimBuffer> shim,\n                              IOSHandle clientHandle,\n                              virt_ptr<uint8_t> buffer,\n                              uint32_t size,\n                              uint32_t count,\n                              uint32_t pos,\n                              FSFileHandle handle,\n                              FSAReadFlag readFlags);\n\nFSAStatus\nfsaShimPrepareRequestRemove(virt_ptr<FSAShimBuffer> shim,\n                            IOSHandle clientHandle,\n                            virt_ptr<const char> path);\n\nFSAStatus\nfsaShimPrepareRequestRename(virt_ptr<FSAShimBuffer> shim,\n                            IOSHandle clientHandle,\n                            virt_ptr<const char> oldPath,\n                            virt_ptr<const char> newPath);\n\nFSAStatus\nfsaShimPrepareRequestRewindDir(virt_ptr<FSAShimBuffer> shim,\n                               IOSHandle clientHandle,\n                               FSDirHandle dirHandle);\n\nFSAStatus\nfsaShimPrepareRequestSetPosFile(virt_ptr<FSAShimBuffer> shim,\n                                IOSHandle clientHandle,\n                                FSFileHandle fileHandle,\n                                FSFilePosition pos);\n\nFSAStatus\nfsaShimPrepareRequestStatFile(virt_ptr<FSAShimBuffer> shim,\n                              IOSHandle clientHandle,\n                              FSFileHandle fileHandle);\n\nFSAStatus\nfsaShimPrepareRequestTruncateFile(virt_ptr<FSAShimBuffer> shim,\n                                  IOSHandle clientHandle,\n                                  FSFileHandle fileHandle);\n\nFSAStatus\nfsaShimPrepareRequestUnmount(virt_ptr<FSAShimBuffer> shim,\n                             IOSHandle clientHandle,\n                             virt_ptr<const char> path,\n                             uint32_t unk0x280);\n\nFSAStatus\nfsaShimPrepareRequestWriteFile(virt_ptr<FSAShimBuffer> shim,\n                               IOSHandle clientHandle,\n                               virt_ptr<const uint8_t> buffer,\n                               uint32_t size,\n                               uint32_t count,\n                               uint32_t pos,\n                               FSFileHandle handle,\n                               FSAWriteFlag writeFlags);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_ghs.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_cosreport.h\"\n#include \"coreinit_driver.h\"\n#include \"coreinit_memdefaultheap.h\"\n#include \"coreinit_mutex.h\"\n#include \"coreinit_scheduler.h\"\n#include \"coreinit_spinlock.h\"\n#include \"coreinit_interrupts.h\"\n#include \"coreinit_osreport.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/kernel/cafe_kernel_process.h\"\n\n#include <common/bitfield.h>\n#include <libcpu/state.h>\n\nnamespace cafe::coreinit\n{\n\nusing AtExitFn = virt_func_ptr<void(void)>;\nusing AtExitCleanupFn = virt_func_ptr<void(int32_t)>;\nusing StdioCleanupFn = virt_func_ptr<void(void)>;\nusing CppExceptionInitPtrFn = virt_func_ptr<void(virt_ptr<virt_ptr<void>>)>;\nusing CppExceptionCleanupPtrFn = virt_func_ptr<void(virt_ptr<void>)>;\n\nconstexpr auto GHS_FLOCK_MAX = 100u;\nconstexpr auto GHS_FOPEN_MAX = uint16_t { 20u };\n\nstruct ghs_atexit\n{\n   be2_val<AtExitFn> callback;\n   be2_virt_ptr<ghs_atexit> next;\n};\nCHECK_OFFSET(ghs_atexit, 0x0, callback);\nCHECK_OFFSET(ghs_atexit, 0x4, next);\nCHECK_SIZE(ghs_atexit, 8);\n\nBITFIELD_BEG(ghs_iobuf_bits, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, readwrite);\n   BITFIELD_ENTRY(1, 1, bool, writable);\n   BITFIELD_ENTRY(2, 1, bool, readable);\n   BITFIELD_ENTRY(18, 14, uint32_t, channel);\nBITFIELD_END\n\nstruct ghs_iobuf\n{\n   be2_virt_ptr<uint8_t> nextPtr;\n   be2_virt_ptr<uint8_t> basePtr;\n   be2_val<int32_t> bytesLeft;\n   be2_val<ghs_iobuf_bits> info;\n};\nCHECK_OFFSET(ghs_iobuf, 0x0, nextPtr);\nCHECK_OFFSET(ghs_iobuf, 0x4, basePtr);\nCHECK_OFFSET(ghs_iobuf, 0x8, bytesLeft);\nCHECK_OFFSET(ghs_iobuf, 0xc, info);\nCHECK_SIZE(ghs_iobuf, 0x10);\n\nstruct StaticGhsData\n{\n   be2_struct<OSSpinLock> ghsLock;\n   be2_virt_ptr<ghs_atexit> atExitCallbacks;\n\n   be2_struct<OSMutex> flockMutex;\n   be2_val<uint32_t> freeFlockIdx;\n   be2_array<uint8_t, GHS_FLOCK_MAX> flockInUse;\n   be2_array<OSMutex, GHS_FLOCK_MAX> flocks;\n};\n\nstatic virt_ptr<StaticGhsData>\nsGhsData = nullptr;\n\n//! __atexit_cleanup\nstatic virt_ptr<AtExitCleanupFn> atexit_cleanup = nullptr;\n\n//! __stdio_cleanup\nstatic virt_ptr<StdioCleanupFn> stdio_cleanup = nullptr;\n\n//! __cpp_exception_init_ptr\nstatic virt_ptr<CppExceptionInitPtrFn> cpp_exception_init_ptr = nullptr;\n\n//! __cpp_exception_cleanup_ptr\nstatic virt_ptr<CppExceptionCleanupPtrFn> cpp_exception_cleanup_ptr = nullptr;\n\n//! __ghs_cpp_locks\nstatic virt_ptr<OSMutex> ghs_cpp_locks = nullptr;\n\n//! __gh_FOPEN_MAX\nstatic virt_ptr<uint16_t> gh_FOPEN_MAX = nullptr;\n\n//! _iob\nstatic virt_ptr<ghs_iobuf[GHS_FOPEN_MAX]> ghs_iob = nullptr;\n\n//! _iob_lock\nstatic virt_ptr<uint32_t[GHS_FOPEN_MAX + 1]> ghs_iob_lock = nullptr;\n\n//! errno\nstatic virt_ptr<int32_t> ghs_errno = nullptr;\n\n//! environ\nstatic virt_ptr<virt_ptr<void>> ghs_environ = nullptr;\n\n/**\n * __ghsLock\n */\nvoid\nghsLock()\n{\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(sGhsData->ghsLock));\n}\n\n\n/**\n * __ghsUnlock\n */\nvoid\nghsUnlock()\n{\n   OSUninterruptibleSpinLock_Release(virt_addrof(sGhsData->ghsLock));\n}\n\n\n/**\n * __ghs_at_exit\n */\nvoid\nghs_at_exit(virt_ptr<ghs_atexit> atExitCallback)\n{\n   ghsLock();\n   atExitCallback->next = sGhsData->atExitCallbacks;\n   sGhsData->atExitCallbacks = atExitCallback;\n   ghsUnlock();\n}\n\n\n/**\n * __ghs_at_exit_cleanup\n */\nvoid\nghs_at_exit_cleanup()\n{\n   for (auto item = sGhsData->atExitCallbacks; item; item = item->next) {\n      cafe::invoke(cpu::this_core::state(),\n                   item->callback);\n   }\n}\n\n/**\n * __PPCExit\n */\nvoid\nghs_PPCExit(int32_t code)\n{\n   internal::driverOnDone();\n   internal::pauseCoreTime(true);\n   kernel::exitProcess(code);\n}\n\n/**\n * _Exit\n */\nvoid\nghs_Exit(int32_t code)\n{\n   ghs_at_exit_cleanup();\n   ghs_PPCExit(code);\n}\n\n/**\n * exit\n */\nvoid\nghs_exit(int32_t code)\n{\n   internal::COSVerbose(COSReportModule::Unknown1, \"ATEXIT: RPX (calls RPX DTORs)\");\n\n   if (*atexit_cleanup) {\n      cafe::invoke(cpu::this_core::state(),\n                   *atexit_cleanup,\n                   code);\n   }\n\n   if (*stdio_cleanup) {\n      cafe::invoke(cpu::this_core::state(),\n                   *stdio_cleanup);\n   }\n\n   ghs_Exit(code);\n}\n\n\n/**\n * __gh_errno_ptr\n */\nvirt_ptr<int32_t>\ngh_errno_ptr()\n{\n   auto thread = OSGetCurrentThread();\n   if (!thread) {\n      return ghs_errno;\n   } else {\n      return virt_addrof(thread->context.error);\n   }\n}\n\n\n/**\n * __gh_get_errno\n */\nint32_t\ngh_get_errno()\n{\n   return *gh_errno_ptr();\n}\n\n\n/**\n * __gh_set_errno\n */\nvoid\ngh_set_errno(int32_t error)\n{\n   *gh_errno_ptr() = error;\n}\n\n\n/**\n * __ghs_flock_create\n */\nvoid\nghs_flock_create(virt_ptr<uint32_t> outFlockIdx)\n{\n   auto flockIdx = sGhsData->freeFlockIdx;\n   OSLockMutex(virt_addrof(sGhsData->flockMutex));\n\n   if (flockIdx >= 101 || sGhsData->flockInUse[flockIdx]) {\n      for (flockIdx = 0u; flockIdx < sGhsData->flockInUse.size(); ++flockIdx) {\n         if (!sGhsData->flockInUse[flockIdx]) {\n            break;\n         }\n      }\n\n      if (flockIdx >= sGhsData->flockInUse.size()) {\n         internal::OSPanic(\"locks.c\", 122, \"All flocks are in use\");\n      }\n   }\n\n   OSInitMutex(virt_addrof(sGhsData->flocks[flockIdx]));\n   sGhsData->flockInUse[flockIdx] = uint8_t { 1 };\n   *outFlockIdx = flockIdx;\n   sGhsData->freeFlockIdx = flockIdx + 1;\n   OSUnlockMutex(virt_addrof(sGhsData->flockMutex));\n}\n\n\n/**\n * __ghs_flock_destroy\n */\nvoid\nghs_flock_destroy(uint32_t flockIdx)\n{\n   sGhsData->flockInUse[flockIdx] = uint8_t { 0 };\n   sGhsData->freeFlockIdx = flockIdx;\n}\n\n\n/**\n * __ghs_flock_file\n */\nvoid\nghs_flock_file(uint32_t flockIdx)\n{\n   auto mutex = virt_addrof(sGhsData->flocks[flockIdx]);\n   if (!OSIsInterruptEnabled()) {\n      if (mutex->owner && mutex->owner != OSGetCurrentThread()) {\n         internal::COSWarn(COSReportModule::Unknown1,\n                           \"***\\n\"\n                           \"*** STD LIBC FILE I/O:\\n\"\n                           \"Locking a mutex owned by another thread while interrupts are off!!\\n\"\n                           \"***\\n\");\n      }\n   }\n\n   OSLockMutex(mutex);\n}\n\n\n/**\n * __ghs_flock_ptr\n */\nvirt_ptr<uint32_t>\nghs_flock_ptr(virt_ptr<ghs_iobuf> iob)\n{\n   auto index = static_cast<uint32_t>(iob - virt_addrof(ghs_iob->at(0)));\n   if (index > *gh_FOPEN_MAX) {\n      index = *gh_FOPEN_MAX;\n   }\n\n   return virt_addrof(ghs_iob_lock->at(index));\n}\n\n\n/**\n * __ghs_ftrylock_file\n */\nint32_t\nghs_ftrylock_file(uint32_t flockIdx)\n{\n   if (OSTryLockMutex(virt_addrof(sGhsData->flocks[flockIdx]))) {\n      return 0;\n   } else {\n      return 1234;\n   }\n}\n\n\n/**\n * __gh_iob_init\n */\nvoid\ngh_iob_init()\n{\n   // stdin\n   ghs_iob->at(0).info = ghs_iob->at(0).info.value()\n      .readable(true)\n      .channel(0);\n\n   if (auto flock_ptr = ghs_flock_ptr(virt_addrof(ghs_iob->at(0)))) {\n      ghs_flock_create(flock_ptr);\n   }\n\n   // stdout\n   ghs_iob->at(1).info = ghs_iob->at(1).info.value()\n      .writable(true)\n      .channel(1);\n\n   if (auto flock_ptr = ghs_flock_ptr(virt_addrof(ghs_iob->at(1)))) {\n      ghs_flock_create(flock_ptr);\n   }\n\n   // stderr\n   ghs_iob->at(2).info = ghs_iob->at(2).info.value()\n      .writable(true)\n      .channel(2);\n\n   if (auto flock_ptr = ghs_flock_ptr(virt_addrof(ghs_iob->at(2)))) {\n      ghs_flock_create(flock_ptr);\n   }\n}\n\n\n/**\n * __gh_lock_init\n */\nvoid\ngh_lock_init()\n{\n   sGhsData->flockInUse.fill(0);\n   sGhsData->freeFlockIdx = 0u;\n\n   OSInitSpinLock(virt_addrof(sGhsData->ghsLock));\n   OSInitMutex(virt_addrof(sGhsData->flockMutex));\n   OSInitMutex(ghs_cpp_locks);\n}\n\n\n/**\n * __ghs_funlock_file\n */\nvoid\nghs_funlock_file(uint32_t flockIdx)\n{\n   OSUnlockMutex(virt_addrof(sGhsData->flocks[flockIdx]));\n}\n\n\n/**\n * __ghs_mtx_init\n */\nvoid\nghs_mtx_init(virt_ptr<virt_ptr<OSMutex>> outMutex)\n{\n   auto mutex = virt_cast<OSMutex *>(MEMAllocFromDefaultHeapEx(sizeof(OSMutex), 8));\n   OSInitMutex(mutex);\n   *outMutex = mutex;\n}\n\n\n/**\n * __ghs_mtx_dst\n */\nvoid\nghs_mtx_dst(virt_ptr<virt_ptr<OSMutex>> mutex)\n{\n   MEMFreeToDefaultHeap(*mutex);\n}\n\n\n/**\n * __ghs_mtx_lock\n */\nvoid\nghs_mtx_lock(virt_ptr<virt_ptr<OSMutex>> mutex)\n{\n   OSLockMutex(*mutex);\n}\n\n\n/**\n * __ghs_mtx_unlock\n */\nvoid\nghs_mtx_unlock(virt_ptr<virt_ptr<OSMutex>> mutex)\n{\n   OSUnlockMutex(*mutex);\n}\n\n\n/**\n * __get_eh_globals\n */\nvirt_ptr<void>\nget_eh_globals()\n{\n   return OSGetCurrentThread()->eh_globals;\n}\n\n\n/**\n * __get_eh_init_block\n */\nvirt_ptr<void>\nget_eh_init_block()\n{\n   return nullptr;\n}\n\n\n/**\n * __get_eh_mem_manage\n */\nvirt_ptr<void>\nget_eh_mem_manage()\n{\n   return virt_addrof(OSGetCurrentThread()->eh_mem_manage);\n}\n\n\n/**\n * __get_eh_store_globals\n */\nvirt_ptr<void>\nget_eh_store_globals()\n{\n   return virt_addrof(OSGetCurrentThread()->eh_store_globals);\n}\n\n\n/**\n * __get_eh_store_globals_tdeh\n */\nvirt_ptr<void>\nget_eh_store_globals_tdeh()\n{\n   return virt_addrof(OSGetCurrentThread()->eh_store_globals_tdeh);\n}\n\n\nnamespace internal\n{\n\nvoid\ninitialiseGhs()\n{\n   *gh_FOPEN_MAX = GHS_FOPEN_MAX;\n   gh_lock_init();\n   gh_iob_init();\n}\n\nvoid\nghsExceptionInit(virt_ptr<OSThread> thread)\n{\n   if (*cpp_exception_init_ptr) {\n      cafe::invoke(cpu::this_core::state(),\n                   *cpp_exception_init_ptr,\n                   virt_addrof(thread->eh_globals));\n   }\n}\n\nvoid\nghsExceptionCleanup(virt_ptr<OSThread> thread)\n{\n   if (*cpp_exception_cleanup_ptr && thread->eh_globals) {\n      cafe::invoke(cpu::this_core::state(),\n                   *cpp_exception_cleanup_ptr,\n                   thread->eh_globals);\n   }\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerGhsSymbols()\n{\n\n   RegisterFunctionExportName(\"__ghsLock\", ghsLock);\n   RegisterFunctionExportName(\"__ghsUnlock\", ghsUnlock);\n\n   RegisterFunctionExportName(\"__ghs_at_exit\", ghs_at_exit);\n   RegisterFunctionExportName(\"__ghs_at_exit_cleanup\", ghs_at_exit_cleanup);\n   RegisterFunctionExportName(\"exit\", ghs_exit);\n   RegisterFunctionExportName(\"_Exit\", ghs_Exit);\n   RegisterFunctionExportName(\"__PPCExit\", ghs_PPCExit);\n\n   RegisterFunctionExportName(\"__gh_errno_ptr\", gh_errno_ptr);\n   RegisterFunctionExportName(\"__gh_get_errno\", gh_get_errno);\n   RegisterFunctionExportName(\"__gh_set_errno\", gh_set_errno);\n\n   RegisterFunctionExportName(\"__gh_iob_init\", gh_iob_init);\n   RegisterFunctionExportName(\"__gh_lock_init\", gh_lock_init);\n\n   RegisterFunctionExportName(\"__ghs_flock_create\", ghs_flock_create);\n   RegisterFunctionExportName(\"__ghs_flock_destroy\", ghs_flock_destroy);\n   RegisterFunctionExportName(\"__ghs_flock_file\", ghs_flock_file);\n   RegisterFunctionExportName(\"__ghs_flock_ptr\", ghs_flock_ptr);\n   RegisterFunctionExportName(\"__ghs_ftrylock_file\", ghs_ftrylock_file);\n   RegisterFunctionExportName(\"__ghs_funlock_file\", ghs_funlock_file);\n\n   RegisterFunctionExportName(\"__ghs_mtx_init\", ghs_mtx_init);\n   RegisterFunctionExportName(\"__ghs_mtx_dst\", ghs_mtx_dst);\n   RegisterFunctionExportName(\"__ghs_mtx_lock\", ghs_mtx_lock);\n   RegisterFunctionExportName(\"__ghs_mtx_unlock\", ghs_mtx_unlock);\n\n   RegisterFunctionExportName(\"__get_eh_globals\", get_eh_globals);\n   RegisterFunctionExportName(\"__get_eh_init_block\", get_eh_init_block);\n   RegisterFunctionExportName(\"__get_eh_mem_manage\", get_eh_mem_manage);\n   RegisterFunctionExportName(\"__get_eh_store_globals\", get_eh_store_globals);\n   RegisterFunctionExportName(\"__get_eh_store_globals_tdeh\", get_eh_store_globals_tdeh);\n\n   RegisterDataExportName(\"__atexit_cleanup\", atexit_cleanup);\n   RegisterDataExportName(\"__stdio_cleanup\", stdio_cleanup);\n   RegisterDataExportName(\"__cpp_exception_init_ptr\", cpp_exception_init_ptr);\n   RegisterDataExportName(\"__cpp_exception_cleanup_ptr\", cpp_exception_cleanup_ptr);\n   RegisterDataExportName(\"__ghs_cpp_locks\", ghs_cpp_locks);\n   RegisterDataExportName(\"__gh_FOPEN_MAX\", gh_FOPEN_MAX);\n   RegisterDataExportName(\"_iob\", ghs_iob);\n   RegisterDataExportName(\"_iob_lock\", ghs_iob_lock);\n   RegisterDataExportName(\"errno\", ghs_errno);\n   RegisterDataExportName(\"environ\", ghs_environ);\n\n   RegisterDataInternal(sGhsData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_ghs.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nstruct OSThread;\n\nvoid\nghs_exit(int32_t code);\n\nvoid\nghs_Exit(int32_t code);\n\nvoid\nghs_PPCExit(int32_t code);\n\nint32_t\ngh_get_errno();\n\nvoid\ngh_set_errno(int32_t error);\n\nnamespace internal\n{\n\nvoid\ninitialiseGhs();\n\nvoid\nghsExceptionInit(virt_ptr<OSThread> thread);\n\nvoid\nghsExceptionCleanup(virt_ptr<OSThread> thread);\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_handle.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_handle.h\"\n#include \"coreinit_spinlock.h\"\n#include \"coreinit_systemheap.h\"\n#include \"coreinit_time.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include <cstdlib>\n#include <libcpu/state.h>\n\nnamespace cafe::coreinit\n{\n\nstatic internal::SubTableAllocFn sAllocSubTable;\nstatic internal::SubTableFreeFn sFreeSubTable;\n\nnamespace internal\n{\n\nstatic OSHandleError\nHandle_InitTable(virt_ptr<HandleTable> table,\n                 SubTableAllocFn allocSubTableFn,\n                 SubTableFreeFn freeSubTableFn)\n{\n   if (!table) {\n      return OSHandleError::InvalidArgument;\n   }\n\n   std::memset(table.get(), 0, sizeof(HandleTable));\n   table->allocSubTableFn = allocSubTableFn;\n   table->freeSubTableFn = freeSubTableFn;\n   table->handleEntropy = 0xCAFEu;\n   table->subTables[0] = virt_addrof(table->firstSubTable);\n   table->subTableFreeEntries[0] = HandleSubTable::NumEntries;\n   return OSHandleError::OK;\n}\n\nstatic OSHandleError\nHandle_Alloc(virt_ptr<HandleTable> table,\n             virt_ptr<void> userData1,\n             virt_ptr<void> userData2,\n             virt_ptr<OSHandle> outHandle)\n{\n   if (!table || !outHandle) {\n      return OSHandleError::InvalidArgument;\n   }\n\n   auto firstFreeSubTableIdx = -1;\n   auto subTableIdx = -1;\n\n   // Find a sub table with a free entry\n   for (auto i = 0; i < HandleTable::NumSubTables; ++i) {\n      if (table->subTables[i]) {\n         if (table->subTableFreeEntries[i]) {\n            subTableIdx = i;\n            break;\n         }\n      } else if (firstFreeSubTableIdx) {\n         firstFreeSubTableIdx = i;\n      }\n   }\n\n   if (subTableIdx == -1) {\n      if (firstFreeSubTableIdx == -1) {\n         // Table completely full\n         return OSHandleError::TableFull;\n      }\n\n      // Allocate a new empty sub table\n      subTableIdx = firstFreeSubTableIdx;\n      auto subTable = cafe::invoke(cpu::this_core::state(),\n                                   table->allocSubTableFn);\n\n      std::memset(subTable.get(), 0, sizeof(HandleSubTable));\n      table->subTables[subTableIdx] = subTable;\n      table->subTableFreeEntries[subTableIdx] = HandleSubTable::NumEntries;\n   }\n\n   auto subTable = table->subTables[subTableIdx];\n   auto entryIndex = static_cast<uint32_t>(rand()) % HandleSubTable::NumEntries;\n   auto firstEntryIndex = entryIndex;\n\n   while (subTable->entries[entryIndex].handle) {\n      ++entryIndex;\n\n      if (entryIndex == HandleSubTable::NumEntries) {\n         entryIndex = 0;\n      }\n\n      if (entryIndex == firstEntryIndex) {\n         // Somehow we have not found a free entry, this is a major fuckup and\n         // should actually be impossible...\n         return OSHandleError::InternalError;\n      }\n   }\n\n   auto handleEntropy = (table->handleEntropy + OSGetTick() % 0x20041) ^ 0x1A5A;\n   if (!handleEntropy) {\n      handleEntropy = 1;\n   }\n\n   table->handleEntropy = handleEntropy;\n\n   // Thanks Hex-Rays!\n   auto handleIndex = entryIndex | (subTableIdx << 9);\n   auto v21 = ((handleIndex + 1) & 0x1FFFF) | ((handleEntropy << 17) & 0x7FE0000);\n   auto v22 =\n      (((v21 & 0x55555555) + ((v21 & 0xAAAAAAAA) >> 1)) & 0x33333333)\n      + ((((v21 & 0x55555555) + ((v21 & 0xAAAAAAAA) >> 1)) & 0xCCCCCCCC) >> 2);\n   auto handle =\n      (0xF8000000 *\n         ((((v22 & 0xF0F0F0F) + ((v22 & 0xF0F0F0F0) >> 4)) & 0xFF00FF)\n            + ((((v22 & 0xF0F0F0F) + ((v22 & 0xF0F0F0F0) >> 4)) & 0xFF00FF00) >> 8)\n            + (((((v22 & 0xF0F0F0F) + ((v22 & 0xF0F0F0F0) >> 4)) & 0xFF00FF)\n            + ((((v22 & 0xF0F0F0F) + ((v22 & 0xF0F0F0F0) >> 4)) & 0xFF00FF00) >> 8)) >> 16))\n         & 0xF8000000)\n      | (v21 & 0x7FFFFFF);\n\n   --table->subTableFreeEntries[subTableIdx];\n\n   auto &entry = subTable->entries[entryIndex];\n   entry.handle = static_cast<OSHandle>(handle);\n   entry.userData1 = userData1;\n   entry.userData2 = userData2;\n   entry.refCount = 1u;\n\n   *outHandle = handle;\n   return OSHandleError::OK;\n}\n\nstatic OSHandleError\nHandle_TranslateAndAddRef(virt_ptr<HandleTable> table,\n                          OSHandle handle,\n                          virt_ptr<virt_ptr<void>> outUserData1,\n                          virt_ptr<virt_ptr<void>> outUserData2)\n{\n   if (!table || !handle) {\n      return OSHandleError::InvalidArgument;\n   }\n\n   auto handleIndex = (handle - 1) & 0x1FFFF;\n   auto subTableIndex = handleIndex >> 9;\n   auto entryIndex = handleIndex & 0x1FF;\n\n   if (subTableIndex > HandleTable::NumSubTables ||\n       entryIndex > HandleSubTable::NumEntries) {\n      return OSHandleError::InvalidHandle;\n   }\n\n   auto subTable = table->subTables[subTableIndex];\n   if (!subTable) {\n      return OSHandleError::InvalidHandle;\n   }\n\n   auto &entry = subTable->entries[entryIndex];\n   if (entry.handle != handle) {\n      return OSHandleError::InvalidHandle;\n   }\n\n   entry.refCount++;\n\n   if (outUserData1) {\n      *outUserData1 = entry.userData1;\n   }\n\n   if (outUserData2) {\n      *outUserData2 = entry.userData2;\n   }\n\n   return OSHandleError::OK;\n}\n\nstatic OSHandleError\nHandle_Release(virt_ptr<HandleTable> table,\n               OSHandle handle,\n               virt_ptr<uint32_t> outRefCount)\n{\n   if (!table || !handle) {\n      return OSHandleError::InvalidArgument;\n   }\n\n   auto handleIndex = (handle - 1) & 0x1FFFF;\n   auto subTableIndex = handleIndex >> 9;\n   auto entryIndex = handleIndex & 0x1FF;\n\n   if (subTableIndex > HandleTable::NumSubTables ||\n       entryIndex > HandleSubTable::NumEntries) {\n      return OSHandleError::InvalidHandle;\n   }\n\n   auto subTable = table->subTables[subTableIndex];\n   if (!subTable) {\n      return OSHandleError::InvalidHandle;\n   }\n\n   auto &entry = subTable->entries[entryIndex];\n   if (entry.handle != handle) {\n      return OSHandleError::InvalidHandle;\n   }\n\n   entry.refCount--;\n\n   if (outRefCount) {\n      *outRefCount = entry.refCount;\n   }\n\n   if (!entry.refCount) {\n      std::memset(virt_addrof(entry).get(), 0, sizeof(HandleEntry));\n      table->subTableFreeEntries[subTableIndex]++;\n\n      // Free the sub table if it is completely empty and was a dynamically\n      // allocated sub table (index > 0).\n      if (subTableIndex > 0 &&\n          table->subTableFreeEntries[subTableIndex] == HandleSubTable::NumEntries &&\n          table->freeSubTableFn) {\n         cafe::invoke(cpu::this_core::state(),\n                      table->freeSubTableFn,\n                      subTable);\n\n         table->subTableFreeEntries[subTableIndex] = 0u;\n         table->subTables[subTableIndex] = nullptr;\n      }\n   }\n\n   return OSHandleError::OK;\n}\n\nstatic virt_ptr<HandleSubTable>\nallocSubTable()\n{\n   return virt_cast<HandleSubTable *>(\n      OSAllocFromSystem(sizeof(HandleSubTable), 4)\n   );\n}\n\nstatic void\nfreeSubTable(virt_ptr<HandleSubTable> table)\n{\n   OSFreeToSystem(table);\n}\n\n} // namespace internal\n\n\n/**\n * Initialise a handle table.\n *\n * \\param table\n * The handle table to initialise.\n *\n * \\return\n * OSHandleError::OK on success, a OSHandleError error code otherwise.\n */\nOSHandleError\nOSHandle_InitTable(virt_ptr<OSHandleTable> table)\n{\n   if (!table) {\n      return OSHandleError::InvalidArgument;\n   }\n\n   std::memset(table.get(), 0, sizeof(OSHandleTable));\n\n   auto error = internal::Handle_InitTable(virt_addrof(table->handleTable),\n                                           sAllocSubTable,\n                                           sFreeSubTable);\n   if (error == OSHandleError::OK) {\n      OSInitSpinLock(virt_addrof(table->lock));\n   }\n\n   return error;\n}\n\n\n/**\n * Allocate a new handle from the handle table.\n *\n * \\param table\n * The handle table to allocate the handle from.\n *\n * \\param userData1\n * User data to set in the handle entry, can be read from\n * OSHandle_TranslateAndAddRef.\n *\n * \\param userData2\n * User data to set in the handle entry, can be read from\n * OSHandle_TranslateAndAddRef.\n *\n * \\param[out] outHandle\n * Output parameter, set to the handle value for the newly acquired handle.\n * Must not be nullptr.\n *\n * \\return\n * OSHandleError::OK on success, a OSHandleError error code otherwise.\n */\nOSHandleError\nOSHandle_Alloc(virt_ptr<OSHandleTable> table,\n               virt_ptr<void> userData1,\n               virt_ptr<void> userData2,\n               virt_ptr<OSHandle> outHandle)\n{\n   if (!table || !outHandle) {\n      return OSHandleError::InvalidArgument;\n   }\n\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(table->lock));\n   auto error = internal::Handle_Alloc(virt_addrof(table->handleTable),\n                                       userData1,\n                                       userData2,\n                                       outHandle);\n   OSUninterruptibleSpinLock_Release(virt_addrof(table->lock));\n   return error;\n}\n\n\n/**\n * Increase the reference count of a handle.\n *\n * \\param table\n * The handle table to acquire the handle from.\n *\n * \\param handle\n * The handle to acquire.\n *\n * \\return\n * OSHandleError::OK on success, a OSHandleError error code otherwise.\n */\nOSHandleError\nOSHandle_AddRef(virt_ptr<OSHandleTable> table,\n                OSHandle handle)\n{\n   return OSHandle_TranslateAndAddRef(table, handle, nullptr, nullptr);\n}\n\n\n/**\n * Increase the reference count of a handle and retrieve it's user data values.\n *\n * \\param table\n * The handle table to acquire the handle from.\n *\n * \\param handle\n * The handle to acquire.\n *\n * \\param[out] outUserData1\n * Optional output parameter, set to the userData1 value for the handle which\n * was passed into OSHandle_Alloc.\n *\n * \\param[out] outUserData2\n * Optional output parameter, set to the userData2 value for the handle which\n * was passed into OSHandle_Alloc.\n *\n * \\return\n * OSHandleError::OK on success, a OSHandleError error code otherwise.\n */\nOSHandleError\nOSHandle_TranslateAndAddRef(virt_ptr<OSHandleTable> table,\n                            OSHandle handle,\n                            virt_ptr<virt_ptr<void>> outUserData1,\n                            virt_ptr<virt_ptr<void>> outUserData2)\n{\n   if (!table || !handle) {\n      return OSHandleError::InvalidArgument;\n   }\n\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(table->lock));\n   auto error =\n      internal::Handle_TranslateAndAddRef(virt_addrof(table->handleTable),\n                                          handle,\n                                          outUserData1,\n                                          outUserData2);\n   OSUninterruptibleSpinLock_Release(virt_addrof(table->lock));\n   return error;\n}\n\n\n/**\n * Reduce the reference count of a handle and free it if the count reaches 0.\n *\n * \\param table\n * The handle table to release the handle from.\n *\n * \\param handle\n * The handle to release.\n *\n * \\param[out] outRefCount\n * Optional output parameter, set to the value of the new ref count.\n *\n * \\return\n * OSHandleError::OK on success, a OSHandleError error code otherwise.\n */\nOSHandleError\nOSHandle_Release(virt_ptr<OSHandleTable> table,\n                 OSHandle handle,\n                 virt_ptr<uint32_t> outRefCount)\n{\n   if (!table || !handle) {\n      return OSHandleError::InvalidArgument;\n   }\n\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(table->lock));\n   auto error =\n      internal::Handle_Release(virt_addrof(table->handleTable),\n                               handle,\n                               outRefCount);\n   OSUninterruptibleSpinLock_Release(virt_addrof(table->lock));\n   return error;\n}\n\nvoid\nLibrary::registerHandleSymbols()\n{\n   RegisterFunctionExport(OSHandle_AddRef);\n   RegisterFunctionExport(OSHandle_Alloc);\n   RegisterFunctionExport(OSHandle_InitTable);\n   RegisterFunctionExport(OSHandle_Release);\n   RegisterFunctionExport(OSHandle_TranslateAndAddRef);\n\n   RegisterFunctionInternal(internal::allocSubTable, sAllocSubTable);\n   RegisterFunctionInternal(internal::freeSubTable, sFreeSubTable);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_handle.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_spinlock.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nusing OSHandle = uint32_t;\n\nnamespace internal\n{\n\nstruct HandleEntry;\nstruct HandleSubTable;\nstruct HandleTable;\n\nusing SubTableAllocFn = virt_func_ptr<virt_ptr<HandleSubTable>()>;\nusing SubTableFreeFn = virt_func_ptr<void(virt_ptr<HandleSubTable>)>;\n\nstruct HandleEntry\n{\n   be2_val<OSHandle> handle;\n   be2_virt_ptr<void> userData1;\n   be2_virt_ptr<void> userData2;\n   be2_val<uint32_t> refCount;\n};\n\nstruct HandleSubTable\n{\n   static constexpr auto NumEntries = 0x200u;\n\n   be2_array<HandleEntry, NumEntries> entries;\n};\n\nstruct HandleTable\n{\n   static constexpr auto NumSubTables = 0x100u;\n\n   be2_val<SubTableAllocFn> allocSubTableFn;\n   be2_val<SubTableFreeFn> freeSubTableFn;\n   be2_val<uint32_t> handleEntropy;\n   be2_array<uint32_t, NumSubTables> subTableFreeEntries;\n   be2_array<virt_ptr<HandleSubTable>, NumSubTables> subTables;\n   be2_struct<HandleSubTable> firstSubTable;\n};\nCHECK_OFFSET(HandleTable, 0x00, allocSubTableFn);\nCHECK_OFFSET(HandleTable, 0x04, freeSubTableFn);\nCHECK_OFFSET(HandleTable, 0x08, handleEntropy);\nCHECK_OFFSET(HandleTable, 0x0C, subTableFreeEntries);\nCHECK_OFFSET(HandleTable, 0x40C, subTables);\nCHECK_OFFSET(HandleTable, 0x80C, firstSubTable);\nCHECK_SIZE(HandleTable, 0x280C);\n\n} // namespace internal\n\nstruct OSHandleTable\n{\n   be2_struct<internal::HandleTable> handleTable;\n   PADDING(4);\n   be2_struct<OSSpinLock> lock;\n};\nCHECK_SIZE(OSHandleTable, 0xA08 * 4);\n\nOSHandleError\nOSHandle_InitTable(virt_ptr<OSHandleTable> table);\n\nOSHandleError\nOSHandle_Alloc(virt_ptr<OSHandleTable> table,\n               virt_ptr<void> userData1,\n               virt_ptr<void> userData2,\n               virt_ptr<OSHandle> outHandle);\n\nOSHandleError\nOSHandle_AddRef(virt_ptr<OSHandleTable> table,\n                OSHandle handle);\n\nOSHandleError\nOSHandle_TranslateAndAddRef(virt_ptr<OSHandleTable> table,\n                            OSHandle handle,\n                            virt_ptr<virt_ptr<void>> outUserData1,\n                            virt_ptr<virt_ptr<void>> outUserData2);\n\nOSHandleError\nOSHandle_Release(virt_ptr<OSHandleTable> table,\n                 OSHandle handle,\n                 virt_ptr<uint32_t> outRefCount);\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_im.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_im.h\"\n#include \"coreinit_ios.h\"\n#include \"coreinit_mutex.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include <libcpu/state.h>\n\nnamespace cafe::coreinit\n{\n\nstruct StaticImData\n{\n   be2_struct<OSMutex> itbMutex;\n   be2_array<char, 16> itbMutexName;\n   be2_struct<IMRequest> sharedRequest;\n};\n\nstatic virt_ptr<StaticImData> sImData = nullptr;\nstatic IOSAsyncCallbackFn sImIosAsyncCallback = nullptr;\n\nnamespace internal\n{\n\nstatic void\nimAcquireItbMutex()\n{\n   OSLockMutex(virt_addrof(sImData->itbMutex));\n}\n\nstatic void\nimReleaseItbMutex()\n{\n   OSUnlockMutex(virt_addrof(sImData->itbMutex));\n}\n\nstatic void\nimCopyData(virt_ptr<IMRequest> request)\n{\n   if (request->copyDst && request->copySrc && request->copySize) {\n      std::memcpy(request->copyDst.get(),\n                  request->copySrc.get(),\n                  request->copySize);\n   }\n}\n\nstatic void\nimIosAsyncCallback(IOSError error,\n                   virt_ptr<void> context)\n{\n   auto request = virt_cast<IMRequest *>(context);\n   if (error == IOSError::OK) {\n      imCopyData(request);\n   }\n\n   cafe::invoke(cpu::this_core::state(),\n                request->asyncCallback,\n                error,\n                request->asyncCallbackContext);\n}\n\nstatic IMError\nimSendRequest(virt_ptr<IMRequest> request,\n              uint32_t vecIn,\n              uint32_t vecOut)\n{\n   auto error = IOSError::OK;\n\n   if (request->asyncCallback) {\n      error = IOS_IoctlvAsync(request->handle,\n                              request->request,\n                              vecIn,\n                              vecOut,\n                              virt_addrof(request->ioctlVecs),\n                              sImIosAsyncCallback,\n                              request);\n   } else {\n      error = IOS_Ioctlv(request->handle,\n                         request->request,\n                         vecIn,\n                         vecOut,\n                         virt_addrof(request->ioctlVecs));\n\n      if (vecOut > 0 && error == IOSError::OK) {\n         imCopyData(request);\n      }\n   }\n\n   return static_cast<IMError>(error);\n}\n\n} // namespace internal\n\n\nIMError\nIM_Open()\n{\n   return static_cast<IMError>(IOS_Open(cafe::make_stack_string(\"/dev/im\"),\n                                        IOSOpenMode::None));\n}\n\n\nIMError\nIM_Close(IOSHandle handle)\n{\n   return static_cast<IMError>(IOS_Close(handle));\n}\n\n\nIMError\nIM_GetHomeButtonParams(IOSHandle handle,\n                       virt_ptr<IMRequest> request,\n                       virt_ptr<void> output,\n                       IOSAsyncCallbackFn asyncCallback,\n                       virt_ptr<void> asyncCallbackContext)\n{\n   std::memset(request.get(), 0, sizeof(IMRequest));\n\n   request->ioctlVecs[0].vaddr = virt_cast<virt_addr>(virt_addrof(request->getHomeButtomParamResponse));\n   request->ioctlVecs[0].len = 8u;\n\n   request->handle = handle;\n   request->request = IMCommand::GetHomeButtonParams;\n   request->asyncCallback = asyncCallback;\n   request->asyncCallbackContext = asyncCallbackContext;\n   request->copySrc = virt_addrof(request->getHomeButtomParamResponse);\n   request->copyDst = output;\n   request->copySize = 8u;\n\n   return internal::imSendRequest(request, 0, 1);\n}\n\n\nIMError\nIM_GetParameter(IOSHandle handle,\n                virt_ptr<IMRequest> request,\n                IMParameter parameter,\n                virt_ptr<void> output,\n                IOSAsyncCallbackFn asyncCallback,\n                virt_ptr<void> asyncCallbackContext)\n{\n   std::memset(request.get(), 0, sizeof(IMRequest));\n\n   request->getParameterRequest.parameter = parameter;\n   request->ioctlVecs[0].vaddr = virt_cast<virt_addr>(virt_addrof(request->getParameterRequest));\n   request->ioctlVecs[0].len = 8u;\n\n   request->ioctlVecs[1].vaddr = virt_cast<virt_addr>(virt_addrof(request->getParameterResponse));\n   request->ioctlVecs[1].len = 8u;\n\n   request->handle = handle;\n   request->request = IMCommand::GetParameter;\n   request->asyncCallback = asyncCallback;\n   request->asyncCallbackContext = asyncCallbackContext;\n   request->copySrc = virt_addrof(request->getParameterResponse.value);\n   request->copyDst = output;\n   request->copySize = 4u;\n\n   return internal::imSendRequest(request, 1, 1);\n}\n\n\nIMError\nIM_GetParameters(virt_ptr<IMParameters> parameters)\n{\n   auto result = IM_Open();\n   if (result < 0) {\n      return result;\n   }\n\n   auto handle = static_cast<IOSHandle>(result);\n   internal::imAcquireItbMutex();\n\n   result = IM_GetParameter(handle,\n                            virt_addrof(sImData->sharedRequest),\n                            IMParameter::ResetEnable,\n                            virt_addrof(parameters->resetEnabled),\n                            nullptr,\n                            nullptr);\n   if (result != IMError::OK) {\n      goto out;\n   }\n\n   result = IM_GetParameter(handle,\n                            virt_addrof(sImData->sharedRequest),\n                            IMParameter::DimEnabled,\n                            virt_addrof(parameters->dimEnabled),\n                            nullptr,\n                            nullptr);\n   if (result != IMError::OK) {\n      goto out;\n   }\n\n   result = IM_GetParameter(handle,\n                            virt_addrof(sImData->sharedRequest),\n                            IMParameter::DimPeriod,\n                            virt_addrof(parameters->dimPeriod),\n                            nullptr,\n                            nullptr);\n   if (result != IMError::OK) {\n      goto out;\n   }\n\n   result = IM_GetParameter(handle,\n                            virt_addrof(sImData->sharedRequest),\n                            IMParameter::APDEnabled,\n                            virt_addrof(parameters->apdEnabled),\n                            nullptr,\n                            nullptr);\n   if (result != IMError::OK) {\n      goto out;\n   }\n\n   result = IM_GetParameter(handle,\n                            virt_addrof(sImData->sharedRequest),\n                            IMParameter::APDPeriod,\n                            virt_addrof(parameters->apdPeriod),\n                            nullptr,\n                            nullptr);\n   if (result != IMError::OK) {\n      goto out;\n   }\n\nout:\n   internal::imReleaseItbMutex();\n   IM_Close(handle);\n   return result;\n}\n\n\nIMError\nIM_GetNvParameter(IOSHandle handle,\n                  virt_ptr<IMRequest> request,\n                  IMParameter parameter,\n                  virt_ptr<void> output,\n                  IOSAsyncCallbackFn asyncCallback,\n                  virt_ptr<void> asyncCallbackContext)\n{\n   std::memset(request.get(), 0, sizeof(IMRequest));\n\n   request->getNvParameterRequest.parameter = parameter;\n   request->ioctlVecs[0].vaddr = virt_cast<virt_addr>(virt_addrof(request->getNvParameterRequest));\n   request->ioctlVecs[0].len = 8u;\n\n   request->ioctlVecs[1].vaddr = virt_cast<virt_addr>(virt_addrof(request->getNvParameterResponse));\n   request->ioctlVecs[1].len = 8u;\n\n   request->handle = handle;\n   request->request = IMCommand::GetNvParameter;\n   request->asyncCallback = asyncCallback;\n   request->asyncCallbackContext = asyncCallbackContext;\n   request->copySrc = virt_addrof(request->getNvParameterResponse.value);\n   request->copyDst = output;\n   request->copySize = 4u;\n\n   return internal::imSendRequest(request, 1, 1);\n}\n\n\nIMError\nIM_GetNvParameterWithoutHandleAndItb(IMParameter parameter,\n                                     virt_ptr<uint32_t> outValue)\n{\n   auto result = IM_Open();\n   if (result < 0) {\n      return result;\n   }\n\n   auto handle = static_cast<IOSHandle>(result);\n   internal::imAcquireItbMutex();\n\n   result = IM_GetNvParameter(handle,\n                              virt_addrof(sImData->sharedRequest),\n                              parameter,\n                              outValue,\n                              nullptr,\n                              nullptr);\n\n   internal::imReleaseItbMutex();\n   IM_Close(handle);\n   return result;\n}\n\n\nIMError\nIM_GetRuntimeParameter(IMParameter parameter,\n                       virt_ptr<uint32_t> outValue)\n{\n   auto result = IM_Open();\n   if (result < 0) {\n      return result;\n   }\n\n   auto handle = static_cast<IOSHandle>(result);\n   internal::imAcquireItbMutex();\n\n   result = IM_GetParameter(handle,\n                            virt_addrof(sImData->sharedRequest),\n                            parameter,\n                            outValue,\n                            nullptr,\n                            nullptr);\n\n   internal::imReleaseItbMutex();\n   IM_Close(handle);\n   return result;\n}\n\n\nIMError\nIM_GetTimerRemaining(IOSHandle handle,\n                     virt_ptr<IMRequest> request,\n                     IMTimer timer,\n                     virt_ptr<void> output,\n                     IOSAsyncCallbackFn asyncCallback,\n                     virt_ptr<void> asyncCallbackContext)\n{\n   std::memset(request.get(), 0, sizeof(IMRequest));\n\n   request->getTimerRemainingRequest.timer = timer;\n   request->ioctlVecs[0].vaddr = virt_cast<virt_addr>(virt_addrof(request->getTimerRemainingRequest));\n   request->ioctlVecs[0].len = 8u;\n\n   request->ioctlVecs[1].vaddr = virt_cast<virt_addr>(virt_addrof(request->getTimerRemainingResponse));\n   request->ioctlVecs[1].len = static_cast<uint32_t>(sizeof(IMGetTimerRemainingResponse));\n\n   request->handle = handle;\n   request->request = IMCommand::GetTimerRemaining;\n   request->asyncCallback = asyncCallback;\n   request->asyncCallbackContext = asyncCallbackContext;\n   request->copySrc = virt_addrof(request->getTimerRemainingResponse.value);\n   request->copyDst = output;\n   request->copySize = 4u;\n\n   return internal::imSendRequest(request, 1, 1);\n}\n\n\nIMError\nIM_GetTimerRemainingSeconds(IMTimer timer,\n                            virt_ptr<uint32_t> outSeconds)\n{\n   auto result = IM_Open();\n   if (result < 0) {\n      return result;\n   }\n\n   auto handle = static_cast<IOSHandle>(result);\n   internal::imAcquireItbMutex();\n\n   result = IM_GetTimerRemaining(handle,\n                                 virt_addrof(sImData->sharedRequest),\n                                 timer,\n                                 outSeconds,\n                                 nullptr,\n                                 nullptr);\n\n   internal::imReleaseItbMutex();\n   IM_Close(handle);\n   return result;\n}\n\n\nIMError\nIM_SetParameter(IOSHandle handle,\n                virt_ptr<IMRequest> request,\n                IMParameter parameter,\n                uint32_t value,\n                IOSAsyncCallbackFn asyncCallback,\n                virt_ptr<void> asyncCallbackContext)\n{\n   std::memset(request.get(), 0, sizeof(IMRequest));\n\n   request->setParameterRequest.parameter = parameter;\n   request->setParameterRequest.value = value;\n   request->ioctlVecs[0].vaddr = virt_cast<virt_addr>(virt_addrof(request->setParameterRequest));\n   request->ioctlVecs[0].len = 8u;\n\n   request->handle = handle;\n   request->request = IMCommand::SetParameter;\n   request->asyncCallback = asyncCallback;\n   request->asyncCallbackContext = asyncCallbackContext;\n\n   return internal::imSendRequest(request, 1, 0);\n}\n\n\nIMError\nIM_SetNvParameter(IOSHandle handle,\n                  virt_ptr<IMRequest> request,\n                  IMParameter parameter,\n                  uint32_t value,\n                  IOSAsyncCallbackFn asyncCallback,\n                  virt_ptr<void> asyncCallbackContext)\n{\n   std::memset(request.get(), 0, sizeof(IMRequest));\n\n   request->setNvParameterRequest.parameter = parameter;\n   request->setNvParameterRequest.value = value;\n   request->ioctlVecs[0].vaddr = virt_cast<virt_addr>(virt_addrof(request->setNvParameterRequest));\n   request->ioctlVecs[0].len = 8u;\n\n   request->handle = handle;\n   request->request = IMCommand::SetNvParameter;\n   request->asyncCallback = asyncCallback;\n   request->asyncCallbackContext = asyncCallbackContext;\n\n   return internal::imSendRequest(request, 1, 0);\n}\n\n\nIMError\nIM_SetRuntimeParameter(IMParameter parameter,\n                       uint32_t value)\n{\n   auto result = IM_Open();\n   if (result < 0) {\n      return result;\n   }\n\n   auto handle = static_cast<IOSHandle>(result);\n   internal::imAcquireItbMutex();\n\n   result = IM_SetParameter(handle,\n                            virt_addrof(sImData->sharedRequest),\n                            parameter,\n                            value,\n                            nullptr,\n                            nullptr);\n\n   internal::imReleaseItbMutex();\n   IM_Close(handle);\n   return result;\n}\n\n\nIMError\nIMDisableAPD()\n{\n   return IM_SetRuntimeParameter(IMParameter::APDEnabled, FALSE);\n}\n\n\nIMError\nIMDisableDim()\n{\n   auto result = IM_SetRuntimeParameter(IMParameter::DimEnabled, FALSE);\n   if (result != IMError::OK) {\n      return result;\n   }\n\n   return IM_SetRuntimeParameter(IMParameter::ResetEnable, FALSE);\n}\n\n\nIMError\nIMEnableAPD()\n{\n   auto prevValue = StackObject<uint32_t> { };\n   auto result = IM_GetNvParameterWithoutHandleAndItb(IMParameter::APDEnabled,\n                                                      prevValue);\n\n   if (result != IMError::OK) {\n      return result;\n   }\n\n   if (*prevValue == TRUE) {\n      return IMError::OK;\n   }\n\n   return IM_SetRuntimeParameter(IMParameter::APDEnabled, TRUE);\n}\n\n\nIMError\nIMEnableDim()\n{\n   auto prevValue = StackObject<uint32_t> { };\n   auto result = IM_GetNvParameterWithoutHandleAndItb(IMParameter::DimEnabled,\n                                                      prevValue);\n   if (result != IMError::OK) {\n      return result;\n   }\n\n   if (*prevValue == TRUE) {\n      return IMError::OK;\n   }\n\n   result = IM_SetRuntimeParameter(IMParameter::DimEnabled, TRUE);\n   if (result != IMError::OK) {\n      return result;\n   }\n\n   result = IM_GetNvParameterWithoutHandleAndItb(IMParameter::ResetEnable,\n                                                 prevValue);\n   if (result != IMError::OK) {\n      return result;\n   }\n\n   if (*prevValue == TRUE) {\n      return IMError::OK;\n   }\n\n   return IM_SetRuntimeParameter(IMParameter::ResetEnable, FALSE);\n}\n\n\nIMError\nIMIsAPDEnabled(virt_ptr<uint32_t> outValue)\n{\n   return IM_GetRuntimeParameter(IMParameter::APDEnabled, outValue);\n}\n\n\nIMError\nIMIsAPDEnabledBySysSettings(virt_ptr<uint32_t> outValue)\n{\n   return IM_GetNvParameterWithoutHandleAndItb(IMParameter::APDEnabled,\n                                               outValue);\n}\n\n\nIMError\nIMIsDimEnabled(virt_ptr<uint32_t> outValue)\n{\n   return IM_GetRuntimeParameter(IMParameter::DimEnabled, outValue);\n}\n\n\nIMError\nIMGetAPDPeriod(virt_ptr<uint32_t> outValue)\n{\n   return IM_GetRuntimeParameter(IMParameter::APDPeriod, outValue);\n}\n\n\nIMError\nIMGetDimEnableDRC(virt_ptr<uint32_t> outValue)\n{\n   return IM_GetRuntimeParameter(IMParameter::DimEnableDrc, outValue);\n}\n\n\nIMError\nIMGetDimEnableTV(virt_ptr<uint32_t> outValue)\n{\n   return IM_GetRuntimeParameter(IMParameter::DimEnableTv, outValue);\n}\n\n\nIMError\nIMGetDimPeriod(virt_ptr<uint32_t> outValue)\n{\n   return IM_GetRuntimeParameter(IMParameter::DimPeriod, outValue);\n}\n\n\nIMError\nIMGetTimeBeforeAPD(virt_ptr<uint32_t> outSeconds)\n{\n   return IM_GetTimerRemainingSeconds(IMTimer::APD, outSeconds);\n}\n\n\nIMError\nIMGetTimeBeforeDimming(virt_ptr<uint32_t> outSeconds)\n{\n   return IM_GetTimerRemainingSeconds(IMTimer::Dim, outSeconds);\n}\n\n\nIMError\nIMSetDimEnableDRC(BOOL value)\n{\n   return IM_SetRuntimeParameter(IMParameter::DimEnableTv, value);\n}\n\n\nIMError\nIMSetDimEnableTV(BOOL value)\n{\n   return IM_SetRuntimeParameter(IMParameter::DimEnableTv, value);\n}\n\n\nIMError\nIMStartAPDVideoMode()\n{\n   auto prevValue = StackObject<uint32_t> { };\n   auto result = IM_GetNvParameterWithoutHandleAndItb(IMParameter::APDPeriod,\n                                                      prevValue);\n\n   if (result != IMError::OK) {\n      return result;\n   }\n\n   if (*prevValue == 14400) {\n      return IMError::OK;\n   }\n\n   return IM_SetRuntimeParameter(IMParameter::APDPeriod, 14400);\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseIm()\n{\n   sImData->itbMutexName = \"itb_mutex\";\n   OSInitMutexEx(virt_addrof(sImData->itbMutex),\n                 virt_addrof(sImData->itbMutexName));\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerImSymbols()\n{\n   RegisterFunctionExport(IM_Open);\n   RegisterFunctionExport(IM_Close);\n   RegisterFunctionExport(IM_GetHomeButtonParams);\n   RegisterFunctionExport(IM_GetParameter);\n   RegisterFunctionExport(IM_GetParameters);\n   RegisterFunctionExport(IM_GetNvParameter);\n   RegisterFunctionExport(IM_GetNvParameterWithoutHandleAndItb);\n   RegisterFunctionExport(IM_GetRuntimeParameter);\n   RegisterFunctionExport(IM_GetTimerRemaining);\n   RegisterFunctionExport(IM_GetTimerRemainingSeconds);\n   RegisterFunctionExport(IM_SetParameter);\n   RegisterFunctionExport(IM_SetRuntimeParameter);\n\n   RegisterFunctionExport(IMDisableAPD);\n   RegisterFunctionExport(IMDisableDim);\n   RegisterFunctionExport(IMEnableAPD);\n   RegisterFunctionExport(IMEnableDim);\n   RegisterFunctionExport(IMIsAPDEnabled);\n   RegisterFunctionExport(IMIsAPDEnabledBySysSettings);\n   RegisterFunctionExport(IMIsDimEnabled);\n   RegisterFunctionExport(IMGetAPDPeriod);\n   RegisterFunctionExport(IMGetDimEnableDRC);\n   RegisterFunctionExport(IMGetDimEnableTV);\n   RegisterFunctionExport(IMGetDimPeriod);\n   RegisterFunctionExport(IMGetTimeBeforeAPD);\n   RegisterFunctionExport(IMGetTimeBeforeDimming);\n   RegisterFunctionExport(IMSetDimEnableDRC);\n   RegisterFunctionExport(IMSetDimEnableTV);\n   RegisterFunctionExport(IMStartAPDVideoMode);\n\n   RegisterDataInternal(sImData);\n   RegisterFunctionInternal(internal::imIosAsyncCallback, sImIosAsyncCallback);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_im.h",
    "content": "#pragma once\n#include \"coreinit_ios.h\"\n#include \"ios/auxil/ios_auxil_im.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_im IM\n * \\ingroup coreinit\n * @{\n */\n\nusing IMError = ios::Error;\n\nusing ios::auxil::IMCommand;\nusing ios::auxil::IMParameter;\nusing ios::auxil::IMTimer;\n\nusing ios::auxil::IMGetNvParameterRequest;\nusing ios::auxil::IMGetNvParameterResponse;\nusing ios::auxil::IMGetParameterRequest;\nusing ios::auxil::IMGetParameterResponse;\nusing ios::auxil::IMGetHomeButtonParamResponse;\nusing ios::auxil::IMSetParameterRequest;\nusing ios::auxil::IMSetNvParameterRequest;\nusing ios::auxil::IMGetTimerRemainingRequest;\nusing ios::auxil::IMGetTimerRemainingResponse;\n\nstruct IMParameters\n{\n   be2_val<uint32_t> resetEnabled;\n   be2_val<uint32_t> dimEnabled;\n   be2_val<uint32_t> dimPeriod;\n   be2_val<uint32_t> apdEnabled;\n   be2_val<uint32_t> apdPeriod;\n};\nCHECK_OFFSET(IMParameters, 0x00, resetEnabled);\nCHECK_OFFSET(IMParameters, 0x04, dimEnabled);\nCHECK_OFFSET(IMParameters, 0x08, dimPeriod);\nCHECK_OFFSET(IMParameters, 0x0C, apdEnabled);\nCHECK_OFFSET(IMParameters, 0x10, apdPeriod);\n\nstruct IMRequest\n{\n   union\n   {\n      be2_struct<IMGetNvParameterRequest> getNvParameterRequest;\n      be2_struct<IMGetNvParameterResponse> getNvParameterResponse;\n      be2_struct<IMGetParameterRequest> getParameterRequest;\n      be2_struct<IMGetParameterResponse> getParameterResponse;\n      be2_struct<IMGetHomeButtonParamResponse> getHomeButtomParamResponse;\n      be2_struct<IMSetParameterRequest> setParameterRequest;\n      be2_struct<IMSetNvParameterRequest> setNvParameterRequest;\n      be2_struct<IMGetTimerRemainingRequest> getTimerRemainingRequest;\n      be2_struct<IMGetTimerRemainingResponse> getTimerRemainingResponse;\n      be2_array<uint8_t, 0x80> args;\n   };\n\n   be2_array<IOSVec, 2> ioctlVecs;\n   be2_val<IOSHandle> handle;\n   be2_val<IMCommand> request;\n   be2_val<IOSAsyncCallbackFn> asyncCallback;\n   be2_virt_ptr<void> asyncCallbackContext;\n   be2_virt_ptr<void> copySrc;\n   be2_virt_ptr<void> copyDst;\n   be2_val<uint32_t> copySize;\n};\nCHECK_OFFSET(IMRequest, 0x80, ioctlVecs);\nCHECK_OFFSET(IMRequest, 0x98, handle);\nCHECK_OFFSET(IMRequest, 0x9C, request);\nCHECK_OFFSET(IMRequest, 0xA0, asyncCallback);\nCHECK_OFFSET(IMRequest, 0xA4, asyncCallbackContext);\nCHECK_OFFSET(IMRequest, 0xA8, copySrc);\nCHECK_OFFSET(IMRequest, 0xAC, copyDst);\nCHECK_OFFSET(IMRequest, 0xB0, copySize);\n\nIMError\nIM_Open();\n\nIMError\nIM_Close(IOSHandle handle);\n\nIMError\nIM_GetHomeButtonParams(IOSHandle handle,\n                       virt_ptr<IMRequest> request,\n                       virt_ptr<void> output,\n                       IOSAsyncCallbackFn asyncCallback,\n                       virt_ptr<void> asyncCallbackContext);\n\nIMError\nIM_GetParameter(IOSHandle handle,\n                virt_ptr<IMRequest> request,\n                IMParameter parameter,\n                virt_ptr<void> output,\n                IOSAsyncCallbackFn asyncCallback,\n                virt_ptr<void> asyncCallbackContext);\n\nIMError\nIM_GetParameters(virt_ptr<IMParameters> parameters);\n\nIMError\nIM_GetNvParameter(IOSHandle handle,\n                  virt_ptr<IMRequest> request,\n                  IMParameter parameter,\n                  virt_ptr<void> output,\n                  IOSAsyncCallbackFn asyncCallback,\n                  virt_ptr<void> asyncCallbackContext);\n\nIMError\nIM_GetNvParameterWithoutHandleAndItb(IMParameter parameter,\n                                     virt_ptr<uint32_t> outValue);\n\nIMError\nIM_GetRuntimeParameter(IMParameter parameter,\n                       virt_ptr<uint32_t> outValue);\n\nIMError\nIM_GetTimerRemaining(IOSHandle handle,\n                     virt_ptr<IMRequest> request,\n                     IMTimer timer,\n                     virt_ptr<void> output,\n                     IOSAsyncCallbackFn asyncCallback,\n                     virt_ptr<void> asyncCallbackContext);\n\nIMError\nIM_GetTimerRemainingSeconds(IMTimer timer,\n                            virt_ptr<uint32_t> outSeconds);\n\nIMError\nIM_SetParameter(IOSHandle handle,\n                virt_ptr<IMRequest> request,\n                IMParameter parameter,\n                uint32_t value,\n                IOSAsyncCallbackFn asyncCallback,\n                virt_ptr<void> asyncCallbackContext);\n\nIMError\nIM_SetRuntimeParameter(IMParameter parameter,\n                       uint32_t value);\n\nIMError\nIMDisableAPD();\n\nIMError\nIMDisableDim();\n\nIMError\nIMEnableAPD();\n\nIMError\nIMEnableDim();\n\nIMError\nIMIsAPDEnabled(virt_ptr<uint32_t> outValue);\n\nIMError\nIMIsAPDEnabledBySysSettings(virt_ptr<uint32_t> outValue);\n\nIMError\nIMIsDimEnabled(virt_ptr<uint32_t> outValue);\n\nIMError\nIMGetDimEnableDRC(virt_ptr<uint32_t> outValue);\n\nIMError\nIMGetDimEnableTV(virt_ptr<uint32_t> outValue);\n\nIMError\nIMGetDimPeriod(virt_ptr<uint32_t> outValue);\n\nIMError\nIMGetTimeBeforeAPD(virt_ptr<uint32_t> outSeconds);\n\nIMError\nIMGetTimeBeforeDimming(virt_ptr<uint32_t> outSeconds);\n\nIMError\nIMSetDimEnableDRC(BOOL value);\n\nIMError\nIMSetDimEnableTV(BOOL value);\n\nIMError\nIMStartAPDVideoMode();\n\nnamespace internal\n{\n\nvoid\ninitialiseIm();\n\n} // namespace interal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_internal_idlock.cpp",
    "content": "#include \"coreinit_internal_idlock.h\"\n#include <libcpu/cpu_control.h>\n\nnamespace cafe::coreinit::internal\n{\n\nstatic uint32_t\ngetCoreLockId()\n{\n   auto id = cpu::this_core::id();\n   auto core = 1u << id;\n\n   if (id == cpu::InvalidCoreId) {\n      core = 1u << 31;\n   }\n\n   return core;\n}\n\nbool\nacquireIdLock(IdLock &lock,\n              uint32_t id)\n{\n   auto expected = 0u;\n   if (id == 0) {\n      return false;\n   }\n\n   while (!lock.owner.compare_exchange_weak(expected, id, std::memory_order_acquire)) {\n      expected = 0;\n   }\n\n   return true;\n}\n\nbool\nacquireIdLock(IdLock &lock,\n              virt_ptr<void> owner)\n{\n   return acquireIdLock(lock,\n                        static_cast<uint32_t>(virt_cast<virt_addr>(owner)));\n}\n\nbool\nacquireIdLockWithCoreId(IdLock &lock)\n{\n   return acquireIdLock(lock, getCoreLockId());\n}\n\nbool\nreleaseIdLock(IdLock &lock,\n              uint32_t id)\n{\n   auto owner = lock.owner.exchange(0, std::memory_order_release);\n   return (owner == id);\n}\n\nbool\nreleaseIdLock(IdLock &lock,\n              virt_ptr<void> owner)\n{\n   return releaseIdLock(lock,\n                        static_cast<uint32_t>(virt_cast<virt_addr>(owner)));\n}\n\nbool\nreleaseIdLockWithCoreId(IdLock &lock)\n{\n   return releaseIdLock(lock, getCoreLockId());\n}\n\nbool\nisHoldingIdLock(IdLock &lock,\n                uint32_t id)\n{\n   return lock.owner.load(std::memory_order_acquire) == id;\n}\n\nbool\nisHoldingIdLock(IdLock &lock,\n                virt_ptr<void> owner)\n{\n   return isHoldingIdLock(lock,\n                          static_cast<uint32_t>(virt_cast<virt_addr>(owner)));\n}\n\nbool\nisHoldingIdLockWithCoreId(IdLock &lock)\n{\n   return isHoldingIdLock(lock, getCoreLockId());\n}\n\nbool\nisLockHeldBySomeone(IdLock &lock)\n{\n   return lock.owner.load(std::memory_order_acquire) != 0;\n}\n\n} // namespace namespace cafe::coreinit::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_internal_idlock.h",
    "content": "#pragma once\n#include <atomic>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit::internal\n{\n\nstruct IdLock\n{\n   std::atomic<uint32_t> owner;\n};\n\nbool\nacquireIdLock(IdLock &lock,\n              uint32_t id);\n\nbool\nacquireIdLock(IdLock &lock,\n              virt_ptr<void> owner);\n\nbool\nacquireIdLockWithCoreId(IdLock &lock);\n\nbool\nreleaseIdLock(IdLock &lock,\n              uint32_t id);\n\nbool\nreleaseIdLock(IdLock &lock,\n              virt_ptr<void> owner);\n\nbool\nreleaseIdLockWithCoreId(IdLock &lock);\n\nbool\nisHoldingIdLock(IdLock &lock,\n                uint32_t id);\n\nbool\nisHoldingIdLock(IdLock &lock,\n                virt_ptr<void> owner);\n\nbool\nisHoldingIdLockWithCoreId(IdLock &lock);\n\nbool\nisLockHeldBySomeone(IdLock &lock);\n\n} // namespace namespace cafe::coreinit::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_internal_queue.h",
    "content": "#pragma once\n#include <common/decaf_assert.h>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit::internal\n{\n\ntemplate <typename QueueType, typename LinkType, typename ItemType, be2_struct<LinkType> ItemType::*LinkField>\nclass Queue\n{\nprotected:\n   static constexpr LinkType &\n   link(virt_ptr<ItemType> item)\n   {\n      return (item.get()->*LinkField);\n   }\n\npublic:\n   static inline void\n   init(virt_ptr<QueueType> queue)\n   {\n      queue->head = nullptr;\n      queue->tail = nullptr;\n   }\n\n   static inline void\n   initLink(virt_ptr<ItemType> item)\n   {\n      link(item).prev = nullptr;\n      link(item).next = nullptr;\n   }\n\n   static inline bool\n   empty(virt_ptr<QueueType> queue)\n   {\n      return queue->head == nullptr;\n   }\n\n   static inline void\n   clear(virt_ptr<QueueType> queue)\n   {\n      for (auto item = queue->head; item; ) {\n         auto next = link(item).next;\n         link(item).next = nullptr;\n         link(item).prev = nullptr;\n         item = next;\n      }\n\n      queue->head = nullptr;\n      queue->tail = nullptr;\n   }\n\n   static inline bool\n   contains(virt_ptr<QueueType> queue,\n            virt_ptr<ItemType> item)\n   {\n      for (auto itemIter = queue->head; itemIter != nullptr; itemIter = link(itemIter).next) {\n         if (itemIter == item) {\n            return true;\n         }\n      }\n\n      return false;\n   }\n\n   static inline void\n   append(virt_ptr<QueueType> queue,\n          virt_ptr<ItemType> item)\n   {\n      decaf_check(link(item).next == nullptr);\n      decaf_check(link(item).prev == nullptr);\n\n      if (!queue->tail) {\n         decaf_check(!queue->head);\n\n         queue->head = item;\n         queue->tail = item;\n         link(item).next = nullptr;\n         link(item).prev = nullptr;\n      } else {\n         link(item).prev = queue->tail;\n         link(item).next = nullptr;\n         link(queue->tail).next = item;\n         queue->tail = item;\n      }\n   }\n\n   static inline void\n   erase(virt_ptr<QueueType> queue,\n         virt_ptr<ItemType> item)\n   {\n      if (queue->head == item) {\n         // Erase from head\n         queue->head = link(item).next;\n\n         if (queue->head) {\n            link(queue->head).prev = nullptr;\n         } else {\n            queue->tail = nullptr;\n         }\n      } else if (queue->tail == item) {\n         // Erase from tail\n         queue->tail = link(item).prev;\n\n         if (queue->tail) {\n            link(queue->tail).next = nullptr;\n         }\n      } else {\n         // Erase from middle\n         auto prev = link(item).prev;\n         auto next = link(item).next;\n\n         if (prev && next) {\n            link(prev).next = next;\n            link(next).prev = prev;\n         }\n      }\n\n      link(item).next = nullptr;\n      link(item).prev = nullptr;\n   }\n\n   static inline virt_ptr<ItemType>\n   popFront(virt_ptr<QueueType> queue)\n   {\n      auto result = queue->head;\n\n      if (result) {\n         queue->head = link(result).next;\n\n         if (queue->head) {\n            link(queue->head).prev = nullptr;\n         }\n      }\n\n      if (result == queue->tail) {\n         queue->tail = nullptr;\n      }\n\n      if (result) {\n         link(result).next = nullptr;\n         link(result).prev = nullptr;\n      }\n\n      return result;\n   }\n};\n\ntemplate <typename QueueType, typename LinkType, typename ItemType, be2_struct<LinkType> ItemType::*LinkField, typename IsLess>\nclass SortedQueue : public Queue<QueueType, LinkType, ItemType, LinkField>\n{\nprivate:\n   // Hide append as it is not valid here\n   using Queue<QueueType, LinkType, ItemType, LinkField>::append;\n   using Queue<QueueType, LinkType, ItemType, LinkField>::link;\n\npublic:\n   static void inline\n   insert(virt_ptr<QueueType> queue,\n          virt_ptr<ItemType> item)\n   {\n      decaf_check(link(item).next == nullptr);\n      decaf_check(link(item).prev == nullptr);\n\n      if (!queue->head) {\n         // Insert only item\n         queue->head = item;\n         queue->tail = item;\n      } else {\n         virt_ptr<ItemType> insertBefore = nullptr;\n\n         // Find insert location based on sort function\n         for (insertBefore = queue->head; insertBefore; insertBefore = link(insertBefore).next) {\n            if (!IsLess {}(insertBefore, item)) {\n               break;\n            }\n         }\n\n         if (!insertBefore) {\n            // Insert at tail\n            link(queue->tail).next = item;\n            link(item).next = nullptr;\n            link(item).prev = queue->tail;\n            queue->tail = item;\n         } else {\n            // Insert in head or middle\n            link(item).next = insertBefore;\n            link(item).prev = link(insertBefore).prev;\n\n            if (link(insertBefore).prev) {\n               link(link(insertBefore).prev).next = item;\n            }\n\n            link(insertBefore).prev = item;\n\n            if (queue->head == insertBefore) {\n               queue->head = item;\n            }\n         }\n      }\n   }\n};\n\n} // namespace cafe::coreinit::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_interrupts.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_context.h\"\n#include \"coreinit_interrupts.h\"\n#include \"coreinit_scheduler.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/kernel/cafe_kernel_exception.h\"\n#include \"cafe/kernel/cafe_kernel_interrupts.h\"\n\n#include <libcpu/cpu_control.h>\n\nnamespace cafe::coreinit\n{\n\nstruct StaticInterruptsData\n{\n   be2_array<OSUserInterruptHandler, OSInterruptType::Max> registeredHandlers;\n};\n\nstatic virt_ptr<StaticInterruptsData>\nsInterruptsData = nullptr;\n\n\n/**\n * Enable interrupts on current core.\n *\n * \\return Returns TRUE if interrupts were previously enabled, FALSE otherwise.\n */\nBOOL\nOSEnableInterrupts()\n{\n   return cpu::this_core::setInterruptMask(cpu::INTERRUPT_MASK) == cpu::INTERRUPT_MASK;\n}\n\n\n/**\n * Disable interrupts on current core.\n *\n * \\return Returns TRUE if interrupts were previously enabled, FALSE otherwise.\n */\nBOOL\nOSDisableInterrupts()\n{\n   // We allow BreakpointException here so that the debugger can still trace through\n   // OSDisableInterrupts calls.  This is not an issue only because internally we\n   // only care about the scheduler lock which is only used internally.\n   return cpu::this_core::setInterruptMask(cpu::DBGBREAK_INTERRUPT) == cpu::INTERRUPT_MASK;\n}\n\n\n/**\n * Sets if interrupts are enabled for current core.\n *\n * \\return Returns TRUE if interrupts were previously enabled, FALSE otherwise.\n */\nBOOL\nOSRestoreInterrupts(BOOL enable)\n{\n   if (enable) {\n      return OSEnableInterrupts();\n   } else {\n      return OSDisableInterrupts();\n   }\n}\n\n\n/**\n * Check whether interrupts are enabled for current core.\n *\n * \\return Returns TRUE if interrupts are enabled on current core.\n */\nBOOL\nOSIsInterruptEnabled()\n{\n   return cpu::this_core::interruptMask() == cpu::INTERRUPT_MASK;\n}\n\n\nstatic void\nuserInterruptHandler(OSInterruptType type,\n                     virt_ptr<OSContext> interruptedContext,\n                     virt_ptr<void> userData)\n{\n   auto userHandler = virt_func_cast<OSUserInterruptHandler>(virt_cast<virt_addr>(userData));\n   if (userHandler) {\n      internal::disableScheduler();\n      cafe::invoke(cpu::this_core::state(),\n                   userHandler,\n                   type,\n                   interruptedContext);\n      internal::enableScheduler();\n   }\n}\n\nOSUserInterruptHandler\nOSSetInterruptHandler(OSInterruptType type,\n                      OSUserInterruptHandler handler)\n{\n   auto previous = OSUserInterruptHandler { nullptr };\n   if (type < OSInterruptType::Max) {\n      previous = sInterruptsData->registeredHandlers[type];\n      sInterruptsData->registeredHandlers[type] = handler;\n   }\n\n   kernel::setUserModeInterruptHandler(type,\n                                       &userInterruptHandler,\n                                       virt_cast<void *>(virt_func_cast<virt_addr>(handler)));\n   return previous;\n}\n\n\nvoid\nOSClearAndEnableInterrupt(OSInterruptType type)\n{\n   kernel::clearAndEnableInterrupt(type);\n}\n\n\nvoid\nOSDisableInterrupt(OSInterruptType type)\n{\n   kernel::disableInterrupt(type);\n}\n\n\nnamespace internal\n{\n\nstatic void\nuserModeIciCallback(kernel::ExceptionType type,\n                    virt_ptr<kernel::Context> interruptedContext)\n{\n   lockScheduler();\n   rescheduleSelfNoLock();\n   unlockScheduler();\n}\n\nvoid\ninitialiseIci()\n{\n   kernel::setUserModeExceptionHandler(kernel::ExceptionType::ICI,\n                                       userModeIciCallback);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerInterruptSymbols()\n{\n   RegisterFunctionExport(OSEnableInterrupts);\n   RegisterFunctionExport(OSDisableInterrupts);\n   RegisterFunctionExport(OSRestoreInterrupts);\n   RegisterFunctionExport(OSIsInterruptEnabled);\n\n   RegisterFunctionExportName(\"__OSSetInterruptHandler\",\n                              OSSetInterruptHandler);\n   RegisterFunctionExportName(\"__OSClearAndEnableInterrupt\",\n                              OSClearAndEnableInterrupt);\n   RegisterFunctionExportName(\"__OSDisableInterrupt\",\n                              OSDisableInterrupt);\n\n   RegisterDataInternal(sInterruptsData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_interrupts.h",
    "content": "#pragma once\n#include \"coreinit_context.h\"\n\n#include \"cafe/kernel/cafe_kernel_interrupts.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n* \\defgroup coreinit_interrupts Interrupts\n* \\ingroup coreinit\n* @{\n*/\n\nusing OSInterruptType = kernel::InterruptType;\nusing OSUserInterruptHandler = virt_func_ptr<\n   void (OSInterruptType type, virt_ptr<OSContext> interruptedContext)\n>;\n\nBOOL\nOSEnableInterrupts();\n\nBOOL\nOSDisableInterrupts();\n\nBOOL\nOSRestoreInterrupts(BOOL enable);\n\nBOOL\nOSIsInterruptEnabled();\n\nOSUserInterruptHandler\nOSSetInterruptHandler(OSInterruptType type,\n                      OSUserInterruptHandler handler);\n\nvoid\nOSClearAndEnableInterrupt(OSInterruptType type);\n\nvoid\nOSDisableInterrupt(OSInterruptType type);\n\nnamespace internal\n{\n\nvoid\ninitialiseIci();\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_ios.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_ios.h\"\n#include \"coreinit_ipcdriver.h\"\n#include \"coreinit_thread.h\"\n\nnamespace cafe::coreinit\n{\n\nnamespace internal\n{\n\nstatic IOSError\nipcPrepareOpenRequest(virt_ptr<IPCDriver> ipcDriver,\n                      virt_ptr<IPCDriverRequest> ipcRequest,\n                      virt_ptr<const char> device,\n                      int mode);\n\nstatic IOSError\nipcPrepareIoctlRequest(virt_ptr<IPCDriver> ipcDriver,\n                       virt_ptr<IPCDriverRequest> ipcRequest,\n                       uint32_t ioctlRequest,\n                       virt_ptr<void> inBuf,\n                       uint32_t inLen,\n                       virt_ptr<void> outBuf,\n                       uint32_t outLen);\n\nstatic IOSError\nipcPrepareIoctlvRequest(virt_ptr<IPCDriver> ipcDriver,\n                        virt_ptr<IPCDriverRequest> ipcRequest,\n                        uint32_t ioctlvRequest,\n                        uint32_t vecIn,\n                        uint32_t vecOut,\n                        virt_ptr<IOSVec> vec);\n\n} // namespace internal\n\n\n/**\n * Sends an IOS Open command over IPC and waits for the response.\n *\n * \\return\n * Returns an IOSHandle (when the result is > 0) or an IOSError code otherwise.\n */\nIOSError\nIOS_Open(virt_ptr<const char> device,\n         IOSOpenMode mode)\n{\n   virt_ptr<IPCDriverRequest> ipcRequest = nullptr;\n   auto ipcDriver = internal::getIPCDriver();\n   auto affinity = internal::pinThreadAffinity();\n   auto error = IOSError::OK;\n\n   error = internal::ipcDriverAllocateRequest(ipcDriver,\n                                              &ipcRequest,\n                                              0,\n                                              IOSCommand::Open,\n                                              0,\n                                              nullptr,\n                                              nullptr);\n\n   if (error < IOSError::OK) {\n      goto fail;\n   }\n\n   error = internal::ipcPrepareOpenRequest(ipcDriver,\n                                           ipcRequest,\n                                           device,\n                                           mode);\n\n   if (error < IOSError::OK) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest);\n\n   if (error < IOSError::OK) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverWaitResponse(ipcDriver, ipcRequest);\n   ipcRequest = nullptr;\n\n   if (error < IOSError::OK) {\n      goto fail;\n   }\n\n   ipcDriver->iosOpenRequestSuccess++;\n   internal::unpinThreadAffinity(affinity);\n   return error;\n\nfail:\n   ipcDriver->iosOpenRequestFail++;\n\n   if (ipcRequest) {\n      internal::ipcDriverFreeRequest(ipcDriver, ipcRequest);\n   }\n\n   internal::unpinThreadAffinity(affinity);\n   return error;\n}\n\n\n/**\n * Sends an IOS Open command over IPC and calls callback with the result.\n *\n * \\return\n * Returns IOSError::OK on success or an IOSError code otherwise.\n */\nIOSError\nIOS_OpenAsync(virt_ptr<const char> device,\n              IOSOpenMode mode,\n              IOSAsyncCallbackFn callback,\n              virt_ptr<void> context)\n{\n   virt_ptr<IPCDriverRequest> ipcRequest = nullptr;\n   auto ipcDriver = internal::getIPCDriver();\n   auto affinity = internal::pinThreadAffinity();\n   auto error = IOSError::OK;\n\n   error = internal::ipcDriverAllocateRequest(ipcDriver,\n                                              &ipcRequest,\n                                              0,\n                                              IOSCommand::Open,\n                                              0,\n                                              callback,\n                                              context);\n\n   if (error < IOSError::OK) {\n      goto fail;\n   }\n\n   error = internal::ipcPrepareOpenRequest(ipcDriver,\n                                           ipcRequest,\n                                           device,\n                                           mode);\n\n   if (error < IOSError::OK) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest);\n\n   if (error < IOSError::OK) {\n      goto fail;\n   }\n\n   ipcDriver->iosOpenAsyncRequestSubmitSuccess++;\n   internal::unpinThreadAffinity(affinity);\n   return error;\n\nfail:\n   ipcDriver->iosOpenAsyncRequestSubmitFail++;\n\n   if (ipcRequest) {\n      internal::ipcDriverFreeRequest(ipcDriver, ipcRequest);\n   }\n\n   internal::unpinThreadAffinity(affinity);\n   return error;\n}\n\n\n/**\n * Sends an IOS Close command over IPC and waits for the reply.\n *\n * \\return\n * Returns IOSError::OK on success or an IOSError code otherwise.\n */\nIOSError\nIOS_Close(IOSHandle handle)\n{\n   virt_ptr<IPCDriverRequest> ipcRequest = nullptr;\n   auto ipcDriver = internal::getIPCDriver();\n   auto affinity = internal::pinThreadAffinity();\n   auto error = IOSError::OK;\n\n   error = internal::ipcDriverAllocateRequest(ipcDriver,\n                                              &ipcRequest,\n                                              handle,\n                                              IOSCommand::Close,\n                                              0,\n                                              nullptr,\n                                              nullptr);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverWaitResponse(ipcDriver, ipcRequest);\n   ipcRequest = nullptr;\n\n   if (error) {\n      goto fail;\n   }\n\n   ipcDriver->iosCloseRequestSuccess++;\n   internal::unpinThreadAffinity(affinity);\n   return IOSError::OK;\n\nfail:\n   ipcDriver->iosCloseRequestFail++;\n\n   if (ipcRequest) {\n      internal::ipcDriverFreeRequest(ipcDriver, ipcRequest);\n   }\n\n   internal::unpinThreadAffinity(affinity);\n   return error;\n}\n\n\n/**\n * Sends an IOS Close command over IPC and calls callback with the result.\n *\n * \\return\n * Returns IOSError::OK on success or an IOSError code otherwise.\n */\nIOSError\nIOS_CloseAsync(IOSHandle handle,\n               IOSAsyncCallbackFn callback,\n               virt_ptr<void> context)\n{\n   virt_ptr<IPCDriverRequest> ipcRequest = nullptr;\n   auto ipcDriver = internal::getIPCDriver();\n   auto affinity = internal::pinThreadAffinity();\n   auto error = IOSError::OK;\n\n   error = internal::ipcDriverAllocateRequest(ipcDriver,\n                                              &ipcRequest,\n                                              handle,\n                                              IOSCommand::Close,\n                                              0,\n                                              callback,\n                                              context);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest);\n\n   if (error) {\n      goto fail;\n   }\n\n   ipcDriver->iosCloseAsyncRequestSubmitSuccess++;\n   internal::unpinThreadAffinity(affinity);\n   return IOSError::OK;\n\nfail:\n   ipcDriver->iosCloseAsyncRequestSubmitFail++;\n\n   if (ipcRequest) {\n      internal::ipcDriverFreeRequest(ipcDriver, ipcRequest);\n   }\n\n   internal::unpinThreadAffinity(affinity);\n   return error;\n}\n\n\n/**\n * Sends an IOS Ioctl command over IPC and waits for the reply.\n *\n * \\return\n * Returns IOSError::OK on success or an IOSError code otherwise.\n */\nIOSError\nIOS_Ioctl(IOSHandle handle,\n          uint32_t request,\n          virt_ptr<void> inBuf,\n          uint32_t inLen,\n          virt_ptr<void> outBuf,\n          uint32_t outLen)\n{\n   virt_ptr<IPCDriverRequest> ipcRequest = nullptr;\n   auto ipcDriver = internal::getIPCDriver();\n   auto affinity = internal::pinThreadAffinity();\n   auto error = IOSError::OK;\n\n   error = internal::ipcDriverAllocateRequest(ipcDriver,\n                                              &ipcRequest,\n                                              handle,\n                                              IOSCommand::Ioctl,\n                                              0,\n                                              nullptr,\n                                              nullptr);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcPrepareIoctlRequest(ipcDriver,\n                                            ipcRequest,\n                                            request,\n                                            inBuf,\n                                            inLen,\n                                            outBuf,\n                                            outLen);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverWaitResponse(ipcDriver, ipcRequest);\n   ipcRequest = nullptr;\n\n   if (error) {\n      goto fail;\n   }\n\n   ipcDriver->iosIoctlRequestSuccess++;\n   internal::unpinThreadAffinity(affinity);\n   return IOSError::OK;\n\nfail:\n   ipcDriver->iosIoctlRequestFail++;\n\n   if (ipcRequest) {\n      internal::ipcDriverFreeRequest(ipcDriver, ipcRequest);\n   }\n\n   internal::unpinThreadAffinity(affinity);\n   return error;\n}\n\n\n/**\n * Sends an IOS Ioctl command over IPC and calls callback with the result.\n *\n * \\return\n * Returns IOSError::OK on success or an IOSError code otherwise.\n */\nIOSError\nIOS_IoctlAsync(IOSHandle handle,\n               uint32_t request,\n               virt_ptr<void> inBuf,\n               uint32_t inLen,\n               virt_ptr<void> outBuf,\n               uint32_t outLen,\n               IOSAsyncCallbackFn callback,\n               virt_ptr<void> context)\n{\n   virt_ptr<IPCDriverRequest> ipcRequest = nullptr;\n   auto ipcDriver = internal::getIPCDriver();\n   auto affinity = internal::pinThreadAffinity();\n   auto error = IOSError::OK;\n\n   error = internal::ipcDriverAllocateRequest(ipcDriver,\n                                              &ipcRequest,\n                                              handle,\n                                              IOSCommand::Ioctl,\n                                              0,\n                                              callback,\n                                              context);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcPrepareIoctlRequest(ipcDriver,\n                                            ipcRequest,\n                                            request,\n                                            inBuf,\n                                            inLen,\n                                            outBuf,\n                                            outLen);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest);\n\n   if (error) {\n      goto fail;\n   }\n\n   ipcDriver->iosIoctlAsyncRequestSubmitSuccess++;\n   internal::unpinThreadAffinity(affinity);\n   return IOSError::OK;\n\nfail:\n   ipcDriver->iosIoctlAsyncRequestSubmitFail++;\n\n   if (ipcRequest) {\n      internal::ipcDriverFreeRequest(ipcDriver, ipcRequest);\n   }\n\n   internal::unpinThreadAffinity(affinity);\n   return error;\n}\n\n\n/**\n * Sends an IOS Ioctlv command over IPC and waits for the reply.\n *\n * \\return\n * Returns IOSError::OK on success or an IOSError code otherwise.\n */\nIOSError\nIOS_Ioctlv(IOSHandle handle,\n           uint32_t request,\n           uint32_t vecIn,\n           uint32_t vecOut,\n           virt_ptr<IOSVec> vec)\n{\n   virt_ptr<IPCDriverRequest> ipcRequest = nullptr;\n   auto ipcDriver = internal::getIPCDriver();\n   auto affinity = internal::pinThreadAffinity();\n   auto error = IOSError::OK;\n\n   error = internal::ipcDriverAllocateRequest(ipcDriver,\n                                              &ipcRequest,\n                                              handle,\n                                              IOSCommand::Ioctlv,\n                                              0,\n                                              nullptr,\n                                              nullptr);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcPrepareIoctlvRequest(ipcDriver,\n                                             ipcRequest,\n                                             request,\n                                             vecIn,\n                                             vecOut,\n                                             vec);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverWaitResponse(ipcDriver, ipcRequest);\n   ipcRequest = nullptr;\n\n   if (error) {\n      goto fail;\n   }\n\n   ipcDriver->iosIoctlvRequestSuccess++;\n   internal::unpinThreadAffinity(affinity);\n   return IOSError::OK;\n\nfail:\n   ipcDriver->iosIoctlvRequestFail++;\n\n   if (ipcRequest) {\n      internal::ipcDriverFreeRequest(ipcDriver, ipcRequest);\n   }\n\n   internal::unpinThreadAffinity(affinity);\n   return error;\n}\n\n\n/**\n * Sends an IOS Ioctlv command over IPC and calls callback with the result.\n *\n * \\return\n * Returns IOSError::OK on success or an IOSError code otherwise.\n */\nIOSError\nIOS_IoctlvAsync(IOSHandle handle,\n                uint32_t request,\n                uint32_t vecIn,\n                uint32_t vecOut,\n                virt_ptr<IOSVec> vec,\n                IOSAsyncCallbackFn callback,\n                virt_ptr<void> context)\n{\n   virt_ptr<IPCDriverRequest> ipcRequest = nullptr;\n   auto ipcDriver = internal::getIPCDriver();\n   auto affinity = internal::pinThreadAffinity();\n   auto error = IOSError::OK;\n\n   error = internal::ipcDriverAllocateRequest(ipcDriver,\n                                              &ipcRequest,\n                                              handle,\n                                              IOSCommand::Ioctlv,\n                                              0,\n                                              callback,\n                                              context);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcPrepareIoctlvRequest(ipcDriver,\n                                             ipcRequest,\n                                             request,\n                                             vecIn,\n                                             vecOut,\n                                             vec);\n\n   if (error) {\n      goto fail;\n   }\n\n   error = internal::ipcDriverSubmitRequest(ipcDriver, ipcRequest);\n\n   if (error) {\n      goto fail;\n   }\n\n   ipcDriver->iosIoctlvAsyncRequestSubmitSuccess++;\n   internal::unpinThreadAffinity(affinity);\n   return IOSError::OK;\n\nfail:\n   ipcDriver->iosIoctlvAsyncRequestSubmitFail++;\n\n   if (ipcRequest) {\n      internal::ipcDriverFreeRequest(ipcDriver, ipcRequest);\n   }\n\n   internal::unpinThreadAffinity(affinity);\n   return error;\n}\n\n\nnamespace internal\n{\n\n\n/**\n * Prepares an IPCDriverRequest structure with the parameters for IOS_Open.\n *\n * \\retval IOSError::Max\n * The name of the device is too long.\n *\n * \\retval IOSError::OK\n * Success.\n */\nIOSError\nipcPrepareOpenRequest(virt_ptr<IPCDriver> ipcDriver,\n                      virt_ptr<IPCDriverRequest> ipcRequest,\n                      virt_ptr<const char> device,\n                      int mode)\n{\n   auto ipcBuffer = ipcRequest->ipcBuffer;\n   auto deviceLen = strlen(device.get());\n\n   if (deviceLen >= 0x20) {\n      return IOSError::Max;\n   }\n\n   ipcBuffer->nameBuffer.fill(0);\n   std::memcpy(virt_addrof(ipcBuffer->nameBuffer).get(),\n               device.get(), deviceLen);\n\n   ipcBuffer->request.args.open.name = nullptr;\n   ipcBuffer->request.args.open.nameLen = static_cast<uint32_t>(deviceLen + 1);\n   ipcBuffer->request.args.open.mode = static_cast<ios::OpenMode>(mode);\n\n   ipcBuffer->buffer1 = virt_addrof(ipcBuffer->nameBuffer);\n   return IOSError::OK;\n}\n\n\n/**\n * Prepares an IPCDriverRequest structure with the parameters for IOS_Ioctl.\n *\n * \\retval IOSError::OK\n * Success.\n */\nIOSError\nipcPrepareIoctlRequest(virt_ptr<IPCDriver> ipcDriver,\n                       virt_ptr<IPCDriverRequest> ipcRequest,\n                       uint32_t ioctlRequest,\n                       virt_ptr<void> inBuf,\n                       uint32_t inLen,\n                       virt_ptr<void> outBuf,\n                       uint32_t outLen)\n{\n   auto ipcBuffer = ipcRequest->ipcBuffer;\n   ipcBuffer->request.args.ioctl.request = ioctlRequest;\n   ipcBuffer->request.args.ioctl.inputBuffer = nullptr;\n   ipcBuffer->request.args.ioctl.inputLength = inLen;\n   ipcBuffer->request.args.ioctl.outputBuffer = nullptr;\n   ipcBuffer->request.args.ioctl.outputLength = outLen;\n\n   ipcBuffer->buffer1 = inBuf;\n   ipcBuffer->buffer2 = outBuf;\n   return IOSError::OK;\n}\n\n\n/**\n * Prepares an IPCDriverRequest structure with the parameters for IOS_Ioctlv.\n *\n * \\retval IOSError::InvalidArg\n * One of the IOSVec structures has a NULL physical address.\n *\n * \\retval IOSError::OK\n * Success.\n */\nIOSError\nipcPrepareIoctlvRequest(virt_ptr<IPCDriver> ipcDriver,\n                        virt_ptr<IPCDriverRequest> ipcRequest,\n                        uint32_t ioctlvRequest,\n                        uint32_t vecIn,\n                        uint32_t vecOut,\n                        virt_ptr<IOSVec> vec)\n{\n   auto ipcBuffer = ipcRequest->ipcBuffer;\n   ipcBuffer->request.args.ioctlv.request = ioctlvRequest;\n   ipcBuffer->request.args.ioctlv.numVecIn = vecIn;\n   ipcBuffer->request.args.ioctlv.numVecOut = vecOut;\n   ipcBuffer->request.args.ioctlv.vecs = nullptr;\n\n   ipcBuffer->buffer1 = vec;\n\n   for (auto i = 0u; i < vecIn + vecOut; ++i) {\n      if (!vec[i].vaddr && vec[i].len) {\n         return IOSError::InvalidArg;\n      }\n   }\n\n   return IOSError::OK;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerIosSymbols()\n{\n   RegisterFunctionExport(IOS_Open);\n   RegisterFunctionExport(IOS_OpenAsync);\n   RegisterFunctionExport(IOS_Close);\n   RegisterFunctionExport(IOS_CloseAsync);\n   RegisterFunctionExport(IOS_Ioctl);\n   RegisterFunctionExport(IOS_IoctlAsync);\n   RegisterFunctionExport(IOS_Ioctlv);\n   RegisterFunctionExport(IOS_IoctlvAsync);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_ios.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"ios/ios_ipc.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_ios IOS\n * \\ingroup coreinit\n * @{\n */\n\n/*\nUnimplemented IOS functions:\nIOS_OpenAsyncEx\nIOS_CloseAsyncEx\nIOS_IoctlAsyncEx\nIOS_IoctlvAsyncEx\n\nIOS_Read\nIOS_ReadAsync\nIOS_ReadAsyncEx\n\nIOS_Seek\nIOS_SeekAsync\nIOS_SeekAsyncEx\n\nIOS_Write\nIOS_WriteAsync\nIOS_WriteAsyncEx\n*/\n\n#define IOS_FAILED(error) (error < ios::Error::OK)\n#define IOS_SUCCESS(error) (error >= ios::Error::OK)\n\nusing IOSCommand = ios::Command;\nusing IOSError = ios::Error;\nusing IOSErrorCategory = ios::ErrorCategory;\nusing IOSHandle = int32_t;\nusing IOSOpenMode = ios::OpenMode;\nusing IOSVec = ios::IoctlVec;\nusing IOSAsyncCallbackFn = virt_func_ptr<void(IOSError status,\n                                              virt_ptr<void> context)>;\nstatic constexpr uint32_t IOSVecAlign = ios::IoctlVecAlign;\n\nIOSError\nIOS_Open(virt_ptr<const char> device,\n         IOSOpenMode mode);\n\nIOSError\nIOS_OpenAsync(virt_ptr<const char> device,\n              IOSOpenMode mode,\n              IOSAsyncCallbackFn callback,\n              virt_ptr<void> context);\n\nIOSError\nIOS_Close(IOSHandle handle);\n\nIOSError\nIOS_CloseAsync(IOSHandle handle,\n               IOSAsyncCallbackFn callback,\n               virt_ptr<void> context);\n\nIOSError\nIOS_Ioctl(IOSHandle handle,\n          uint32_t request,\n          virt_ptr<void> inBuf,\n          uint32_t inLen,\n          virt_ptr<void> outBuf,\n          uint32_t outLen);\n\nIOSError\nIOS_IoctlAsync(IOSHandle handle,\n               uint32_t request,\n               virt_ptr<void> inBuf,\n               uint32_t inLen,\n               virt_ptr<void> outBuf,\n               uint32_t outLen,\n               IOSAsyncCallbackFn callback,\n               virt_ptr<void> context);\n\nIOSError\nIOS_Ioctlv(IOSHandle handle,\n           uint32_t request,\n           uint32_t vecIn,\n           uint32_t vecOut,\n           virt_ptr<IOSVec> vec);\n\nIOSError\nIOS_IoctlvAsync(IOSHandle handle,\n                uint32_t request,\n                uint32_t vecIn,\n                uint32_t vecOut,\n                virt_ptr<IOSVec> vec,\n                IOSAsyncCallbackFn callback,\n                virt_ptr<void> context);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_ipcbufpool.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_ios.h\"\n#include \"coreinit_ipcbufpool.h\"\n#include \"coreinit_mutex.h\"\n\n#include <common/align.h>\n\nnamespace cafe::coreinit\n{\n\nconstexpr uint32_t IPCBufPool::MagicHeader;\n\nnamespace internal\n{\n\nstatic void\nipcBufPoolFifoInit(virt_ptr<IPCBufPoolFIFO> fifo,\n                   int32_t numMessages,\n                   virt_ptr<virt_ptr<void>> messages);\n\nstatic IOSError\nipcBufPoolFifoPush(virt_ptr<IPCBufPoolFIFO> fifo,\n                   virt_ptr<void> message);\n\nstatic IOSError\nipcBufPoolFifoPop(virt_ptr<IPCBufPoolFIFO> fifo,\n                  virt_ptr<void> *outMessage);\n\nstatic int32_t\nipcBufPoolGetMessageIndex(virt_ptr<IPCBufPool> pool,\n                          virt_ptr<void> message);\n\n} // namespace internal\n\n\n/**\n * Create a IPCBufPool structure from buffer.\n */\nvirt_ptr<IPCBufPool>\nIPCBufPoolCreate(virt_ptr<void> buffer,\n                 uint32_t size,\n                 uint32_t messageSize,\n                 virt_ptr<uint32_t> outNumMessages,\n                 uint32_t unk0x0c)\n{\n   if (!buffer || size == 0 || messageSize == 0) {\n      return nullptr;\n   }\n\n   std::memset(buffer.get(), 0, size);\n\n   // IPC messages should be 64 byte aligned\n   messageSize = align_up(messageSize, 64);\n\n   auto alignedBuffer = align_up(buffer, 64);\n   auto pool = virt_cast<IPCBufPool *>(alignedBuffer);\n   pool->magic = IPCBufPool::MagicHeader;\n   pool->buffer = buffer;\n   pool->size = size;\n   pool->unk0x0C = unk0x0c;\n   pool->unk0x10 = 0u;\n   pool->messageSize0x14 = messageSize;\n   pool->messageSize0x18 = messageSize;\n   OSInitMutexEx(virt_addrof(pool->mutex), nullptr);\n\n   auto numMessages = static_cast<uint32_t>((size - sizeof(IPCBufPool)) / messageSize);\n\n   if (numMessages <= 1) {\n      return nullptr;\n   }\n\n   auto messageIndexSize = static_cast<uint32_t>(numMessages * sizeof(be2_virt_ptr<void>));\n   numMessages -= 1 + (messageIndexSize / messageSize);\n   *outNumMessages = numMessages;\n   pool->messageCount = numMessages;\n\n   auto messageIndex = virt_cast<virt_ptr<void> *>(virt_cast<uint8_t *>(alignedBuffer) + sizeof(IPCBufPool));\n   auto messages = virt_cast<uint8_t *>(messageIndex) + messageIndexSize;\n   pool->messages = messages;\n   pool->messageIndexSize = messageIndexSize;\n\n   // Initialise FIFO.\n   internal::ipcBufPoolFifoInit(virt_addrof(pool->fifo),\n                                static_cast<int32_t>(numMessages),\n                                messageIndex);\n\n   for (auto i = 0u; i < numMessages; ++i) {\n      internal::ipcBufPoolFifoPush(virt_addrof(pool->fifo), messages + i * messageSize);\n   }\n\n   return pool;\n}\n\n\n/**\n * Allocate a message from an IPCBufPool.\n */\nvirt_ptr<void>\nIPCBufPoolAllocate(virt_ptr<IPCBufPool> pool,\n                   uint32_t size)\n{\n   if (pool->magic != IPCBufPool::MagicHeader) {\n      return nullptr;\n   }\n\n   if (size > pool->messageSize0x14) {\n      return nullptr;\n   }\n\n   auto message = virt_ptr<void> { nullptr };\n   OSLockMutex(virt_addrof(pool->mutex));\n   internal::ipcBufPoolFifoPop(virt_addrof(pool->fifo), &message);\n   OSUnlockMutex(virt_addrof(pool->mutex));\n   return message;\n}\n\n\n/**\n * Free a message back to a IPCBufPool.\n */\nIOSError\nIPCBufPoolFree(virt_ptr<IPCBufPool> pool,\n               virt_ptr<void> message)\n{\n   auto error = IOSError::OK;\n\n   if (pool->magic != IPCBufPool::MagicHeader) {\n      return IOSError::Invalid;\n   }\n\n   OSLockMutex(virt_addrof(pool->mutex));\n   auto index = internal::ipcBufPoolGetMessageIndex(pool, message);\n\n   if (index >= 0) {\n      auto messages = virt_cast<uint8_t *>(pool->messages);\n      auto ipcMessage = messages + index * pool->messageSize0x18;\n      internal::ipcBufPoolFifoPush(virt_addrof(pool->fifo), ipcMessage);\n   } else {\n      error = IOSError::Invalid;\n   }\n\n   OSUnlockMutex(virt_addrof(pool->mutex));\n   return error;\n}\n\n\n/**\n * Get some information about an IPCBufPool object.\n */\nIOSError\nIPCBufPoolGetAttributes(virt_ptr<IPCBufPool> pool,\n                        virt_ptr<IPCBufPoolAttributes> attribs)\n{\n   if (pool->magic != IPCBufPool::MagicHeader) {\n      return IOSError::Invalid;\n   }\n\n   OSLockMutex(virt_addrof(pool->mutex));\n   attribs->messageSize = pool->messageSize0x14;\n   attribs->poolSize = pool->messageCount;\n   attribs->numMessages = pool->fifo.count;\n   OSUnlockMutex(virt_addrof(pool->mutex));\n   return IOSError::OK;\n}\n\n\nnamespace internal\n{\n\n/**\n * Initialise an IPCBufPoolFIFO structure.\n */\nvoid\nipcBufPoolFifoInit(virt_ptr<IPCBufPoolFIFO> fifo,\n                   int32_t numMessages,\n                   virt_ptr<virt_ptr<void>> messages)\n{\n   fifo->pushIndex = 0;\n   fifo->popIndex = -1;\n   fifo->count = 0;\n   fifo->maxCount = numMessages;\n   fifo->messages = messages;\n}\n\n\n/**\n * Push a message into a IPCBufPoolFIFO structure.\n */\nIOSError\nipcBufPoolFifoPush(virt_ptr<IPCBufPoolFIFO> fifo,\n                   virt_ptr<void> message)\n{\n   if (fifo->pushIndex == fifo->popIndex) {\n      return IOSError::QFull;\n   }\n\n   fifo->messages[fifo->pushIndex] = message;\n\n   if (fifo->popIndex == -1) {\n      fifo->popIndex = fifo->pushIndex;\n   }\n\n   fifo->count += 1;\n   fifo->pushIndex = (fifo->pushIndex + 1) % fifo->maxCount;\n   return IOSError::OK;\n}\n\n\n/**\n * Pop a message from a IPCBufPoolFIFO structure.\n */\nIOSError\nipcBufPoolFifoPop(virt_ptr<IPCBufPoolFIFO> fifo,\n                  virt_ptr<void> *outMessage)\n{\n   if (fifo->popIndex == -1) {\n      return IOSError::QEmpty;\n   }\n\n   auto message = fifo->messages[fifo->popIndex];\n   fifo->count -= 1;\n\n   if (fifo->count == 0) {\n      fifo->popIndex = -1;\n   } else {\n      fifo->popIndex = (fifo->popIndex + 1) % fifo->maxCount;\n   }\n\n   *outMessage = message;\n   return IOSError::OK;\n}\n\n\n/**\n * Get the index of a message in an IPCBufPool by it's pointer.\n */\nint32_t\nipcBufPoolGetMessageIndex(virt_ptr<IPCBufPool> pool,\n                          virt_ptr<void> message)\n{\n   if (message < pool->messages) {\n      return -1;\n   }\n\n   auto offset = virt_cast<virt_addr>(message) - virt_cast<virt_addr>(pool->messages);\n   auto index = offset / pool->messageSize0x18;\n\n   if (index > pool->messageCount) {\n      return -1;\n   }\n\n   return static_cast<int32_t>(index);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerIpcBufPoolSymbols()\n{\n   RegisterFunctionExport(IPCBufPoolCreate);\n   RegisterFunctionExport(IPCBufPoolAllocate);\n   RegisterFunctionExport(IPCBufPoolFree);\n   RegisterFunctionExport(IPCBufPoolGetAttributes);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_ipcbufpool.h",
    "content": "#pragma once\n#include \"coreinit_ios.h\"\n#include \"coreinit_mutex.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_ipcbufpool IPC Buffer Pool\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\n/**\n * FIFO queue for IPCBufPool.\n *\n * Functions similar to a ring buffer.\n */\nstruct IPCBufPoolFIFO\n{\n   //! The current message index to push to.\n   be2_val<int32_t> pushIndex;\n\n   //! The current message index to pop from.\n   be2_val<int32_t> popIndex;\n\n   //! The number of messages in the queue.\n   be2_val<int32_t> count;\n\n   //! Tracks the total number of messages in the count.\n   be2_val<int32_t> maxCount;\n\n   //! Messages in the queue.\n   be2_virt_ptr<virt_ptr<void>> messages;\n};\nCHECK_OFFSET(IPCBufPoolFIFO, 0x00, pushIndex);\nCHECK_OFFSET(IPCBufPoolFIFO, 0x04, popIndex);\nCHECK_OFFSET(IPCBufPoolFIFO, 0x08, count);\nCHECK_OFFSET(IPCBufPoolFIFO, 0x0C, maxCount);\nCHECK_OFFSET(IPCBufPoolFIFO, 0x10, messages);\nCHECK_SIZE(IPCBufPoolFIFO, 0x14);\n\n\n/**\n * Attributes returned by IPCBufPoolGetAttributes.\n */\nstruct IPCBufPoolAttributes\n{\n   //! Size of a message in the buffer pool.\n   be2_val<uint32_t> messageSize;\n\n   //! Size of the buffer pool.\n   be2_val<uint32_t> poolSize;\n\n   //! Number of pending messages in the pool fifo.\n   be2_val<uint32_t> numMessages;\n};\nCHECK_OFFSET(IPCBufPoolAttributes, 0x00, messageSize);\nCHECK_OFFSET(IPCBufPoolAttributes, 0x04, poolSize);\nCHECK_OFFSET(IPCBufPoolAttributes, 0x08, numMessages);\nCHECK_SIZE(IPCBufPoolAttributes, 0x0C);\n\n\n/**\n * A simple message buffer pool used for IPC communication.\n */\nstruct IPCBufPool\n{\n   static constexpr uint32_t MagicHeader = 0x0BADF00Du;\n\n   //! Magic header always set to IPCBufPool::MagicHeader.\n   be2_val<uint32_t> magic;\n\n   //! Pointer to buffer used for this IPCBufPool.\n   be2_virt_ptr<void> buffer;\n\n   //! Size of buffer.\n   be2_val<uint32_t> size;\n\n   be2_val<uint32_t> unk0x0C;\n   be2_val<uint32_t> unk0x10;\n\n   //! Message size from IPCBufPoolCreate.\n   be2_val<uint32_t> messageSize0x14;\n\n   //! Message size from IPCBufPoolCreate.\n   be2_val<uint32_t> messageSize0x18;\n\n   //! Number of messages in the IPCBufPoolFIFO.\n   be2_val<uint32_t> messageCount;\n\n   //! Pointer to start of messages.\n   be2_virt_ptr<void> messages;\n\n   //! Number of bytes used for the message pointers in IPCBufPoolFIFO.\n   be2_val<uint32_t> messageIndexSize;\n\n   //! FIFO queue of messages.\n   be2_struct<IPCBufPoolFIFO> fifo;\n\n   //! Mutex used to secure access to fifo.\n   be2_struct<OSMutex> mutex;\n\n   UNKNOWN(0x4);\n};\nCHECK_OFFSET(IPCBufPool, 0x00, magic);\nCHECK_OFFSET(IPCBufPool, 0x04, buffer);\nCHECK_OFFSET(IPCBufPool, 0x08, size);\nCHECK_OFFSET(IPCBufPool, 0x0C, unk0x0C);\nCHECK_OFFSET(IPCBufPool, 0x10, unk0x10);\nCHECK_OFFSET(IPCBufPool, 0x14, messageSize0x14);\nCHECK_OFFSET(IPCBufPool, 0x18, messageSize0x18);\nCHECK_OFFSET(IPCBufPool, 0x1C, messageCount);\nCHECK_OFFSET(IPCBufPool, 0x20, messages);\nCHECK_OFFSET(IPCBufPool, 0x24, messageIndexSize);\nCHECK_OFFSET(IPCBufPool, 0x28, fifo);\nCHECK_OFFSET(IPCBufPool, 0x3C, mutex);\nCHECK_SIZE(IPCBufPool, 0x6C);\n\n#pragma pack(pop)\n\nvirt_ptr<IPCBufPool>\nIPCBufPoolCreate(virt_ptr<void> buffer,\n                 uint32_t size,\n                 uint32_t messageSize,\n                 virt_ptr<uint32_t> outNumMessages,\n                 uint32_t unk0x0c);\n\nvirt_ptr<void>\nIPCBufPoolAllocate(virt_ptr<IPCBufPool> pool,\n                   uint32_t size);\n\nIOSError\nIPCBufPoolFree(virt_ptr<IPCBufPool> pool,\n               virt_ptr<void> message);\n\nIOSError\nIPCBufPoolGetAttributes(virt_ptr<IPCBufPool> pool,\n                        virt_ptr<IPCBufPoolAttributes> attribs);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_ipcdriver.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_core.h\"\n#include \"coreinit_ipcdriver.h\"\n#include \"coreinit_messagequeue.h\"\n#include \"coreinit_scheduler.h\"\n#include \"coreinit_thread.h\"\n\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/kernel/cafe_kernel_ipckdriver.h\"\n\nnamespace cafe::coreinit\n{\n\nstruct StaticIpcDriverData\n{\n   struct PerCoreData\n   {\n      be2_struct<IPCDriver> driver;\n      be2_struct<OSMessageQueue> queue;\n      be2_array<OSMessage, 0x30> messages;\n      be2_array<IPCKDriverRequest, IPCBufferCount> ipcBuffers;\n      be2_struct<OSThread> thread;\n      be2_array<char, 16> threadName;\n      be2_array<uint8_t, 0x4000> threadStack;\n   };\n\n   be2_array<PerCoreData, CoreCount> perCoreData;\n   be2_array<char, 32> submitEventName = \"{ IPC Synchronous }\";\n};\n\nstatic virt_ptr<StaticIpcDriverData> sIpcDriverData = nullptr;\nstatic OSThreadEntryPointFn sIpcDriverThreadEntry = nullptr;\n\nnamespace internal\n{\n\nvoid\nipcDriverProcessReplies(kernel::InterruptType type,\n                        virt_ptr<kernel::Context> interruptedContext);\n\n} // namespace internal\n\n\n/**\n * Initialise the IPC driver.\n */\nvoid\nIPCDriverInit()\n{\n   auto coreId = OSGetCoreId();\n   auto &perCoreData = sIpcDriverData->perCoreData[coreId];\n\n   auto driver = virt_addrof(perCoreData.driver);\n   OSInitEvent(virt_addrof(driver->waitFreeFifoEvent), FALSE, OSEventMode::AutoReset);\n   driver->status = IPCDriverStatus::Initialised;\n   driver->coreId = coreId;\n   driver->ipcBuffers = virt_addrof(perCoreData.ipcBuffers);\n\n   OSInitMessageQueue(virt_addrof(perCoreData.queue),\n                      virt_addrof(perCoreData.messages),\n                      static_cast<int32_t>(perCoreData.messages.size()));\n\n   auto thread = virt_addrof(perCoreData.thread);\n   auto stack = virt_addrof(perCoreData.threadStack);\n   auto stackSize = perCoreData.threadStack.size();\n   coreinit__OSCreateThreadType(thread,\n                                sIpcDriverThreadEntry,\n                                driver->coreId,\n                                nullptr,\n                                virt_cast<uint32_t *>(stack + stackSize),\n                                static_cast<uint32_t>(stackSize),\n                                15,\n                                static_cast<OSThreadAttributes>(1 << driver->coreId),\n                                OSThreadType::Driver);\n\n   perCoreData.threadName = fmt::format(\"IPC Core {}\", coreId);\n   OSSetThreadName(thread, virt_addrof(perCoreData.threadName));\n   OSResumeThread(thread);\n}\n\n\n/**\n * Open the IPC driver.\n *\n * \\retval IOSError::OK\n * Success.\n *\n * \\retval IOSError::NotReady\n * The IPC driver status must be Closed or Initialised.\n */\nIOSError\nIPCDriverOpen()\n{\n   auto driver = internal::getIPCDriver();\n\n   // Verify driver state\n   if (driver->status != IPCDriverStatus::Closed &&\n       driver->status != IPCDriverStatus::Initialised) {\n      return IOSError::NotReady;\n   }\n\n   // Initialise requests\n   for (auto i = 0u; i < IPCBufferCount; ++i) {\n      auto &request = driver->requests[i];\n      request.ipcBuffer = virt_addrof(driver->ipcBuffers[i]);\n      request.asyncCallback = nullptr;\n      request.asyncContext = nullptr;\n   }\n\n   driver->initialisedRequests = TRUE;\n\n   // Initialise FIFO\n   internal::ipcDriverFifoInit(virt_addrof(driver->freeFifo));\n   internal::ipcDriverFifoInit(virt_addrof(driver->outboundFifo));\n\n   // Push all items into free queue\n   for (auto i = 0u; i < IPCBufferCount; ++i) {\n      internal::ipcDriverFifoPush(virt_addrof(driver->freeFifo),\n                                  virt_addrof(driver->requests[i]));\n   }\n\n   // Open the ipck driver\n   auto error =\n      kernel::ipckDriverUserOpen(driver->replyQueue.replies.size(),\n                                 virt_addrof(driver->replyQueue),\n                                 internal::ipcDriverProcessReplies);\n   if (error == ios::Error::OK) {\n      driver->status = IPCDriverStatus::Open;\n   }\n\n   return error;\n}\n\n\n/**\n * Close the IPC driver.\n *\n * \\retval IOSError::OK\n * Success.\n */\nIOSError\nIPCDriverClose()\n{\n   auto &driver = sIpcDriverData->perCoreData[OSGetCoreId()].driver;\n   driver.status = IPCDriverStatus::Closed;\n   return IOSError::OK;\n}\n\n\nnamespace internal\n{\n\n\n/**\n * Get the IPC driver for the current core\n */\nvirt_ptr<IPCDriver>\ngetIPCDriver()\n{\n\n   auto coreId = OSGetCoreId();\n   return virt_addrof(sIpcDriverData->perCoreData[coreId].driver);\n}\n\n\n/**\n * Initialise IPCDriverFIFO\n */\nvoid\nipcDriverFifoInit(virt_ptr<IPCDriverFIFO> fifo)\n{\n   fifo->pushIndex = 0;\n   fifo->popIndex = -1;\n   fifo->count = 0;\n   fifo->maxCount = 0;\n\n   for (auto i = 0; i < IPCBufferCount; ++i) {\n      fifo->requests[i] = nullptr;\n   }\n}\n\n\n/**\n * Push a request into an IPCDriverFIFO structure\n *\n * \\retval IOSError::OK Success\n * \\retval IOSError::QFull There was no free space in the queue to push the request.\n */\nIOSError\nipcDriverFifoPush(virt_ptr<IPCDriverFIFO> fifo,\n                  virt_ptr<IPCDriverRequest> request)\n{\n   if (fifo->pushIndex == fifo->popIndex) {\n      return IOSError::QFull;\n   }\n\n   fifo->requests[fifo->pushIndex] = request;\n\n   if (fifo->popIndex == -1) {\n      fifo->popIndex = fifo->pushIndex;\n   }\n\n   fifo->count += 1;\n   fifo->pushIndex = (fifo->pushIndex + 1) % IPCBufferCount;\n\n   if (fifo->count > fifo->maxCount) {\n      fifo->maxCount = fifo->count;\n   }\n\n   return IOSError::OK;\n}\n\n\n/**\n * Pop a request into an IPCDriverFIFO structure.\n *\n * \\retval IOSError::OK\n * Success\n *\n * \\retval IOSError::QEmpty\n * There was no requests to pop from the queue.\n */\nIOSError\nipcDriverFifoPop(virt_ptr<IPCDriverFIFO> fifo,\n                 virt_ptr<IPCDriverRequest> *requestOut)\n{\n   if (fifo->popIndex == -1) {\n      return IOSError::QEmpty;\n   }\n\n   auto request = fifo->requests[fifo->popIndex];\n   fifo->count -= 1;\n\n   if (fifo->count == 0) {\n      fifo->popIndex = -1;\n   } else {\n      fifo->popIndex = (fifo->popIndex + 1) % IPCBufferCount;\n   }\n\n   *requestOut = request;\n   return IOSError::OK;\n}\n\n\n/**\n * Allocates and initialises a IPCDriverRequest.\n *\n * This function can block with OSWaitEvent until there is a free request to\n * pop from the freeFifo queue.\n *\n * \\return\n * Returns IOSError::OK on success, an IOSError code otherwise.\n */\nIOSError\nipcDriverAllocateRequest(virt_ptr<IPCDriver> driver,\n                         virt_ptr<IPCDriverRequest> *requestOut,\n                         IOSHandle handle,\n                         IOSCommand command,\n                         uint32_t requestUnk0x04,\n                         IOSAsyncCallbackFn asyncCallback,\n                         virt_ptr<void> asyncContext)\n{\n   virt_ptr<IPCDriverRequest> request = nullptr;\n   auto error = IOSError::OK;\n\n   do {\n      error = ipcDriverFifoPop(virt_addrof(driver->freeFifo), &request);\n\n      if (error) {\n         driver->failedAllocateRequestBlock += 1;\n\n         if (error == IOSError::QEmpty) {\n            driver->waitingFreeFifo = TRUE;\n            OSWaitEvent(virt_addrof(driver->waitFreeFifoEvent));\n         }\n      }\n   } while (error == IOSError::QEmpty);\n\n   if (error != IOSError::OK) {\n      return error;\n   }\n\n   request->allocated = TRUE;\n   request->unk0x04 = requestUnk0x04;\n   request->asyncCallback = asyncCallback;\n   request->asyncContext = asyncContext;\n\n   auto ipcBuffer = request->ipcBuffer;\n   std::memset(virt_addrof(ipcBuffer->request).get(),\n               0,\n               sizeof(kernel::IpcRequest));\n   ipcBuffer->request.command = command;\n   ipcBuffer->request.handle = handle;\n   ipcBuffer->request.flags = 0u;\n   ipcBuffer->request.clientPid = 0;\n   ipcBuffer->request.reply = IOSError::OK;\n\n   *requestOut = request;\n   return IOSError::OK;\n}\n\n\n/**\n * Free a IPCDriverRequest.\n *\n * \\retval IOSError::OK\n * Success.\n *\n * \\retval IOSError::QFull\n * The driver's freeFifo queue was full thus we were unable to free the request.\n */\nIOSError\nipcDriverFreeRequest(virt_ptr<IPCDriver> driver,\n                     virt_ptr<IPCDriverRequest> request)\n{\n   auto error = ipcDriverFifoPush(virt_addrof(driver->freeFifo), request);\n   request->allocated = FALSE;\n\n   if (error != IOSError::OK) {\n      driver->failedFreeRequestBlock += 1;\n   }\n\n   return error;\n}\n\n\n/**\n * Submits an IPCDriverRequest to the kernel IPC driver.\n *\n * \\retval IOSError::OK\n * Success.\n */\nIOSError\nipcDriverSubmitRequest(virt_ptr<IPCDriver> driver,\n                       virt_ptr<IPCDriverRequest> request)\n{\n   OSInitEventEx(virt_addrof(request->finishEvent),\n                 FALSE,\n                 OSEventMode::AutoReset,\n                 virt_addrof(sIpcDriverData->submitEventName));\n   driver->requestsSubmitted++;\n\n   kernel::ipckDriverUserSubmitRequest(request->ipcBuffer);\n   return IOSError::OK;\n}\n\n\n/**\n * Blocks and waits for a response to an IPCDriverRequest.\n *\n * \\return\n * Returns IOSError::OK or an IOSHandle on success, or an IOSError code otherwise.\n */\nIOSError\nipcDriverWaitResponse(virt_ptr<IPCDriver> driver,\n                      virt_ptr<IPCDriverRequest> request)\n{\n   OSWaitEvent(virt_addrof(request->finishEvent));\n   auto response = request->ipcBuffer->request.reply;\n   ipcDriverFreeRequest(driver, request);\n   OSSignalEventAll(virt_addrof(driver->waitFreeFifoEvent));\n   return response;\n}\n\n\n/**\n * Callback by kernel IPC driver to indicate there are pending replies to process.\n */\nvoid\nipcDriverProcessReplies(kernel::InterruptType type,\n                        virt_ptr<kernel::Context> interruptedContext)\n{\n   disableScheduler();\n   auto driver = getIPCDriver();\n   auto &coreData = sIpcDriverData->perCoreData[driver->coreId];\n\n   for (auto i = 0u; i < driver->replyQueue.numReplies; ++i) {\n      auto buffer = driver->replyQueue.replies[i];\n      auto index = static_cast<uint32_t>(buffer - driver->ipcBuffers);\n      decaf_check(index >= 0);\n      decaf_check(index <= IPCBufferCount);\n\n      auto &request = driver->requests[index];\n      decaf_check(request.ipcBuffer == buffer);\n\n      if (!request.asyncCallback) {\n         OSSignalEvent(virt_addrof(request.finishEvent));\n      } else {\n         auto message = StackObject<OSMessage> { };\n         message->message = virt_cast<void *>(virt_func_cast<virt_addr>(request.asyncCallback));\n         message->args[0] = static_cast<uint32_t>(request.ipcBuffer->request.reply.value());\n         message->args[1] = static_cast<uint32_t>(virt_cast<virt_addr>(request.asyncContext));\n         message->args[2] = 0u;\n         OSSendMessage(virt_addrof(coreData.queue), message, OSMessageFlags::None);\n         ipcDriverFreeRequest(driver, virt_addrof(request));\n      }\n\n      driver->requestsProcessed++;\n      driver->replyQueue.replies[i] = nullptr;\n   }\n\n   driver->replyQueue.numReplies = 0u;\n   enableScheduler();\n}\n\n\nstatic uint32_t\nipcDriverThreadEntry(uint32_t coreId,\n                     virt_ptr<void>)\n{\n   auto msg = StackObject<OSMessage> { };\n   auto &coreData = sIpcDriverData->perCoreData[coreId];\n\n   while (true) {\n      OSReceiveMessage(virt_addrof(coreData.queue), msg, OSMessageFlags::Blocking);\n\n      if (msg->args[2]) {\n         // Received shutdown message\n         break;\n      }\n\n      // Received callback message\n      auto callback = virt_func_cast<IOSAsyncCallbackFn>(virt_cast<virt_addr>(msg->message));\n      auto error = static_cast<IOSError>(msg->args[0]);\n      auto context = virt_cast<void *>(virt_addr { msg->args[1].value() });\n      cafe::invoke(cpu::this_core::state(),\n                   callback,\n                   error,\n                   context);\n   }\n\n   IPCDriverClose();\n   return 0;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerIpcDriverSymbols()\n{\n   RegisterFunctionExport(IPCDriverInit);\n   RegisterFunctionExport(IPCDriverOpen);\n   RegisterFunctionExport(IPCDriverClose);\n\n   RegisterDataInternal(sIpcDriverData);\n   RegisterFunctionInternal(internal::ipcDriverThreadEntry, sIpcDriverThreadEntry);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_ipcdriver.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_event.h\"\n#include \"coreinit_ios.h\"\n\n#include \"cafe/kernel/cafe_kernel_ipckdriver.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_ipcdriver IPC Driver\n * \\ingroup coreinit\n * @{\n */\n\nconstexpr auto IPCBufferCount = 0x30;\n\nusing IPCKDriverRequest = kernel::IPCKDriverRequest;\nusing IPCKDriverReplyQueue = kernel::IPCKDriverReplyQueue;\n\n/**\n * Contains all data required for IPCDriver about an IPC request.\n *\n * ipcBuffer contains the actual data sent over IPC.\n */\nstruct IPCDriverRequest\n{\n   //! Whether the current request has been allocated.\n   be2_val<BOOL> allocated;\n\n   //! Unknown.\n   be2_val<uint32_t> unk0x04;\n\n   //! Callback to be called when reply is received.\n   be2_val<IOSAsyncCallbackFn> asyncCallback;\n\n   //! Context for asyncCallback.\n   be2_virt_ptr<void> asyncContext;\n\n   UNKNOWN(0x4);\n\n   //! Pointer to the IPCK request.\n   be2_virt_ptr<IPCKDriverRequest> ipcBuffer;\n\n   //! OSEvent called when request is finished.\n   be2_struct<OSEvent> finishEvent;\n};\nCHECK_OFFSET(IPCDriverRequest, 0x00, allocated);\nCHECK_OFFSET(IPCDriverRequest, 0x04, unk0x04);\nCHECK_OFFSET(IPCDriverRequest, 0x08, asyncCallback);\nCHECK_OFFSET(IPCDriverRequest, 0x0C, asyncContext);\nCHECK_OFFSET(IPCDriverRequest, 0x14, ipcBuffer);\nCHECK_OFFSET(IPCDriverRequest, 0x18, finishEvent);\nCHECK_SIZE(IPCDriverRequest, 0x3C);\n\n\n/**\n * FIFO queue for IPCDriverRequests.\n *\n * Functions similar to a ring buffer.\n */\nstruct IPCDriverFIFO\n{\n   //! The current item index to push to\n   be2_val<int32_t> pushIndex;\n\n   //! The current item index to pop from\n   be2_val<int32_t> popIndex;\n\n   //! The number of items in the queue\n   be2_val<int32_t> count;\n\n   //! Tracks the highest amount of items there has been in the queue\n   be2_val<int32_t> maxCount;\n\n   //! Items in the queue\n   be2_array<virt_ptr<IPCDriverRequest>, IPCBufferCount> requests;\n};\nCHECK_OFFSET(IPCDriverFIFO, 0x00, pushIndex);\nCHECK_OFFSET(IPCDriverFIFO, 0x04, popIndex);\nCHECK_OFFSET(IPCDriverFIFO, 0x08, count);\nCHECK_OFFSET(IPCDriverFIFO, 0x0C, maxCount);\nCHECK_OFFSET(IPCDriverFIFO, 0x10, requests);\nCHECK_SIZE(IPCDriverFIFO, 0xD0);\n\n\n/**\n * IPC driver.\n */\nstruct IPCDriver\n{\n   //! The current state of the IPCDriver\n   be2_val<IPCDriverStatus> status;\n\n   UNKNOWN(0x4);\n\n   //! The core this driver was opened on.\n   be2_val<uint32_t> coreId;\n\n   UNKNOWN(0x4);\n\n   //! A pointer to the memory used for IPCBuffers.\n   be2_virt_ptr<IPCKDriverRequest> ipcBuffers;\n\n   //! The current outgoing IPCDriverRequest.\n   be2_virt_ptr<IPCDriverRequest> currentSendTransaction;\n\n   be2_val<uint32_t> iosOpenRequestFail;\n   be2_val<uint32_t> iosOpenRequestSuccess;\n   be2_val<uint32_t> iosOpenAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosOpenAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosOpenAsyncRequestFail;\n   be2_val<uint32_t> iosOpenAsyncRequestSuccess;\n   be2_val<uint32_t> iosOpenAsyncExRequestSubmitFail;\n   be2_val<uint32_t> iosOpenAsyncExRequestSubmitSuccess;\n   be2_val<uint32_t> iosOpenAsyncExRequestFail;\n   be2_val<uint32_t> iosOpenAsyncExRequestSuccess;\n   be2_val<uint32_t> iosCloseRequestFail;\n   be2_val<uint32_t> iosCloseRequestSuccess;\n   be2_val<uint32_t> iosCloseAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosCloseAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosCloseAsyncRequestFail;\n   be2_val<uint32_t> iosCloseAsyncRequestSuccess;\n   be2_val<uint32_t> iosCloseAsyncExRequestSubmitFail;\n   be2_val<uint32_t> iosCloseAsyncExRequestSubmitSuccess;\n   be2_val<uint32_t> iosCloseAsyncExRequestFail;\n   be2_val<uint32_t> iosCloseAsyncExRequestSuccess;\n   be2_val<uint32_t> iosReadRequestFail;\n   be2_val<uint32_t> iosReadRequestSuccess;\n   be2_val<uint32_t> iosReadAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosReadAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosReadAsyncRequestFail;\n   be2_val<uint32_t> iosReadAsyncRequestSuccess;\n   be2_val<uint32_t> iosReadAsyncExRequestSubmitFail;\n   be2_val<uint32_t> iosReadAsyncExRequestSubmitSuccess;\n   be2_val<uint32_t> iosReadAsyncExRequestFail;\n   be2_val<uint32_t> iosReadAsyncExRequestSuccess;\n   be2_val<uint32_t> iosWriteRequestFail;\n   be2_val<uint32_t> iosWriteRequestSuccess;\n   be2_val<uint32_t> iosWriteAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosWriteAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosWriteAsyncRequestFail;\n   be2_val<uint32_t> iosWriteAsyncRequestSuccess;\n   be2_val<uint32_t> iosWriteAsyncExRequestSubmitFail;\n   be2_val<uint32_t> iosWriteAsyncExRequestSubmitSuccess;\n   be2_val<uint32_t> iosWriteAsyncExRequestFail;\n   be2_val<uint32_t> iosWriteAsyncExRequestSuccess;\n   be2_val<uint32_t> iosSeekRequestFail;\n   be2_val<uint32_t> iosSeekRequestSuccess;\n   be2_val<uint32_t> iosSeekAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosSeekAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosSeekAsyncRequestFail;\n   be2_val<uint32_t> iosSeekAsyncRequestSuccess;\n   be2_val<uint32_t> iosSeekAsyncExRequestSubmitFail;\n   be2_val<uint32_t> iosSeekAsyncExRequestSubmitSuccess;\n   be2_val<uint32_t> iosSeekAsyncExRequestFail;\n   be2_val<uint32_t> iosSeekAsyncExRequestSuccess;\n   be2_val<uint32_t> iosIoctlRequestFail;\n   be2_val<uint32_t> iosIoctlRequestSuccess;\n   be2_val<uint32_t> iosIoctlAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosIoctlAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosIoctlAsyncRequestFail;\n   be2_val<uint32_t> iosIoctlAsyncRequestSuccess;\n   be2_val<uint32_t> iosIoctlAsyncExRequestSubmitFail;\n   be2_val<uint32_t> iosIoctlAsyncExRequestSubmitSuccess;\n   be2_val<uint32_t> iosIoctlAsyncExRequestFail;\n   be2_val<uint32_t> iosIoctlAsyncExRequestSuccess;\n   be2_val<uint32_t> iosIoctlvRequestFail;\n   be2_val<uint32_t> iosIoctlvRequestSuccess;\n   be2_val<uint32_t> iosIoctlvAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosIoctlvAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosIoctlvAsyncRequestFail;\n   be2_val<uint32_t> iosIoctlvAsyncRequestSuccess;\n   be2_val<uint32_t> iosIoctlvAsyncExRequestSubmitFail;\n   be2_val<uint32_t> iosIoctlvAsyncExRequestSubmitSuccess;\n   be2_val<uint32_t> iosIoctlvAsyncExRequestFail;\n   be2_val<uint32_t> iosIoctlvAsyncExRequestSuccess;\n   be2_val<uint32_t> requestsProcessed;\n   be2_val<uint32_t> requestsSubmitted;\n   be2_val<uint32_t> repliesReceived;\n   be2_val<uint32_t> asyncTransactionsCompleted;\n   UNKNOWN(4);\n   be2_val<uint32_t> syncTransactionsCompleted;\n   be2_val<uint32_t> invalidReplyAddress;\n   be2_val<uint32_t> unexpectedReplyInterrupt;\n   be2_val<uint32_t> unexpectedAckInterrupt;\n   be2_val<uint32_t> invalidReplyMessagePointer;\n   be2_val<uint32_t> invalidReplyMessagePointerNotAlloc;\n   be2_val<uint32_t> invalidReplyCommand;\n   be2_val<uint32_t> failedAllocateRequestBlock;\n   be2_val<uint32_t> failedFreeRequestBlock;\n   be2_val<uint32_t> failedRequestSubmitOutboundFIFOFull;\n\n   //! FIFO of free IPCDriverRequests.\n   be2_struct<IPCDriverFIFO> freeFifo;\n\n   //! FIFO of IPCDriverRequests which have been sent over IPC and are awaiting a reply.\n   be2_struct<IPCDriverFIFO> outboundFifo;\n\n   //! An event object used to wait for a request to be available for allocation from freeFifo.\n   be2_struct<OSEvent> waitFreeFifoEvent;\n\n   //! Set to TRUE if there is a someone waiting on waitFreeFifoEvent.\n   be2_val<BOOL> waitingFreeFifo;\n\n   //! Set to TRUE once this->requests has been initialised.\n   be2_val<BOOL> initialisedRequests;\n\n   //! Pending replies from IOS to process.\n   be2_struct<IPCKDriverReplyQueue> replyQueue;\n\n   //! IPCDriverRequests memory to be used in freeFifo / outboundFifo.\n   be2_array<IPCDriverRequest, IPCBufferCount> requests;\n\n   UNKNOWN(0x1740 - 0xF3C);\n};\nCHECK_OFFSET(IPCDriver, 0x00, status);\nCHECK_OFFSET(IPCDriver, 0x08, coreId);\nCHECK_OFFSET(IPCDriver, 0x10, ipcBuffers);\nCHECK_OFFSET(IPCDriver, 0x14, currentSendTransaction);\nCHECK_OFFSET(IPCDriver, 0x18, iosOpenRequestFail);\nCHECK_OFFSET(IPCDriver, 0x1C, iosOpenRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x20, iosOpenAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0x24, iosOpenAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0x28, iosOpenAsyncRequestFail);\nCHECK_OFFSET(IPCDriver, 0x2C, iosOpenAsyncRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x30, iosOpenAsyncExRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0x34, iosOpenAsyncExRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0x38, iosOpenAsyncExRequestFail);\nCHECK_OFFSET(IPCDriver, 0x3C, iosOpenAsyncExRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x40, iosCloseRequestFail);\nCHECK_OFFSET(IPCDriver, 0x44, iosCloseRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x48, iosCloseAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0x4C, iosCloseAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0x50, iosCloseAsyncRequestFail);\nCHECK_OFFSET(IPCDriver, 0x54, iosCloseAsyncRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x58, iosCloseAsyncExRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0x5C, iosCloseAsyncExRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0x60, iosCloseAsyncExRequestFail);\nCHECK_OFFSET(IPCDriver, 0x64, iosCloseAsyncExRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x68, iosReadRequestFail);\nCHECK_OFFSET(IPCDriver, 0x6C, iosReadRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x70, iosReadAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0x74, iosReadAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0x78, iosReadAsyncRequestFail);\nCHECK_OFFSET(IPCDriver, 0x7C, iosReadAsyncRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x80, iosReadAsyncExRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0x84, iosReadAsyncExRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0x88, iosReadAsyncExRequestFail);\nCHECK_OFFSET(IPCDriver, 0x8C, iosReadAsyncExRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x90, iosWriteRequestFail);\nCHECK_OFFSET(IPCDriver, 0x94, iosWriteRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x98, iosWriteAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0x9C, iosWriteAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0xA0, iosWriteAsyncRequestFail);\nCHECK_OFFSET(IPCDriver, 0xA4, iosWriteAsyncRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0xA8, iosWriteAsyncExRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0xAC, iosWriteAsyncExRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0xB0, iosWriteAsyncExRequestFail);\nCHECK_OFFSET(IPCDriver, 0xB4, iosWriteAsyncExRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0xB8, iosSeekRequestFail);\nCHECK_OFFSET(IPCDriver, 0xBC, iosSeekRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0xC0, iosSeekAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0xC4, iosSeekAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0xC8, iosSeekAsyncRequestFail);\nCHECK_OFFSET(IPCDriver, 0xCC, iosSeekAsyncRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0xD0, iosSeekAsyncExRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0xD4, iosSeekAsyncExRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0xD8, iosSeekAsyncExRequestFail);\nCHECK_OFFSET(IPCDriver, 0xDC, iosSeekAsyncExRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0xE0, iosIoctlRequestFail);\nCHECK_OFFSET(IPCDriver, 0xE4, iosIoctlRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0xE8, iosIoctlAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0xEC, iosIoctlAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0xF0, iosIoctlAsyncRequestFail);\nCHECK_OFFSET(IPCDriver, 0xF4, iosIoctlAsyncRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0xF8, iosIoctlAsyncExRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0xFC, iosIoctlAsyncExRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0x100, iosIoctlAsyncExRequestFail);\nCHECK_OFFSET(IPCDriver, 0x104, iosIoctlAsyncExRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x108, iosIoctlvRequestFail);\nCHECK_OFFSET(IPCDriver, 0x10C, iosIoctlvRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x110, iosIoctlvAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0x114, iosIoctlvAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0x118, iosIoctlvAsyncRequestFail);\nCHECK_OFFSET(IPCDriver, 0x11C, iosIoctlvAsyncRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x120, iosIoctlvAsyncExRequestSubmitFail);\nCHECK_OFFSET(IPCDriver, 0x124, iosIoctlvAsyncExRequestSubmitSuccess);\nCHECK_OFFSET(IPCDriver, 0x128, iosIoctlvAsyncExRequestFail);\nCHECK_OFFSET(IPCDriver, 0x12C, iosIoctlvAsyncExRequestSuccess);\nCHECK_OFFSET(IPCDriver, 0x130, requestsProcessed);\nCHECK_OFFSET(IPCDriver, 0x134, requestsSubmitted);\nCHECK_OFFSET(IPCDriver, 0x138, repliesReceived);\nCHECK_OFFSET(IPCDriver, 0x13C, asyncTransactionsCompleted);\nCHECK_OFFSET(IPCDriver, 0x144, syncTransactionsCompleted);\nCHECK_OFFSET(IPCDriver, 0x148, invalidReplyAddress);\nCHECK_OFFSET(IPCDriver, 0x14C, unexpectedReplyInterrupt);\nCHECK_OFFSET(IPCDriver, 0x150, unexpectedAckInterrupt);\nCHECK_OFFSET(IPCDriver, 0x154, invalidReplyMessagePointer);\nCHECK_OFFSET(IPCDriver, 0x158, invalidReplyMessagePointerNotAlloc);\nCHECK_OFFSET(IPCDriver, 0x15C, invalidReplyCommand);\nCHECK_OFFSET(IPCDriver, 0x160, failedAllocateRequestBlock);\nCHECK_OFFSET(IPCDriver, 0x164, failedFreeRequestBlock);\nCHECK_OFFSET(IPCDriver, 0x168, failedRequestSubmitOutboundFIFOFull);\nCHECK_OFFSET(IPCDriver, 0x16C, freeFifo);\nCHECK_OFFSET(IPCDriver, 0x23C, outboundFifo);\nCHECK_OFFSET(IPCDriver, 0x30C, waitFreeFifoEvent);\nCHECK_OFFSET(IPCDriver, 0x330, waitingFreeFifo);\nCHECK_OFFSET(IPCDriver, 0x334, initialisedRequests);\nCHECK_OFFSET(IPCDriver, 0x338, replyQueue);\nCHECK_OFFSET(IPCDriver, 0x3FC, requests);\nCHECK_SIZE(IPCDriver, 0x1740);\n\nvoid\nIPCDriverInit();\n\nIOSError\nIPCDriverOpen();\n\nIOSError\nIPCDriverClose();\n\nnamespace internal\n{\n\nvirt_ptr<IPCDriver>\ngetIPCDriver();\n\nvoid\nipcDriverFifoInit(virt_ptr<IPCDriverFIFO> fifo);\n\nIOSError\nipcDriverFifoPush(virt_ptr<IPCDriverFIFO> fifo,\n                  virt_ptr<IPCDriverRequest> request);\n\nIOSError\nipcDriverFifoPop(virt_ptr<IPCDriverFIFO> fifo,\n                 virt_ptr<IPCDriverRequest> *outRequest);\n\nIOSError\nipcDriverAllocateRequest(virt_ptr<IPCDriver> driver,\n                         virt_ptr<IPCDriverRequest> *outRequest,\n                         IOSHandle handle,\n                         IOSCommand command,\n                         uint32_t requestUnk0x04,\n                         IOSAsyncCallbackFn asyncCallback,\n                         virt_ptr<void> asyncContext);\n\nIOSError\nipcDriverFreeRequest(virt_ptr<IPCDriver> driver,\n                     virt_ptr<IPCDriverRequest> request);\n\nIOSError\nipcDriverSubmitRequest(virt_ptr<IPCDriver> driver,\n                       virt_ptr<IPCDriverRequest> request);\n\nIOSError\nipcDriverWaitResponse(virt_ptr<IPCDriver> driver,\n                      virt_ptr<IPCDriverRequest> request);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_lockedcache.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_core.h\"\n#include \"coreinit_lockedcache.h\"\n#include \"coreinit_memory.h\"\n#include \"coreinit_mutex.h\"\n#include \"coreinit_systeminfo.h\"\n#include \"coreinit_thread.h\"\n\n#include <common/align.h>\n#include <common/bitutils.h>\n\nnamespace cafe::coreinit\n{\n\nconstexpr auto LCBlockSize = 512u;\nconstexpr auto LCMaxSize = 16u * 1024;\n\nstruct LockedCacheState\n{\n   //! Lock for this structure.\n   be2_struct<OSMutex> mutex;\n\n   //! Base address of locked cache memory.\n   be2_val<virt_addr> baseAddress;\n\n   //! Free size in bytes remaining in Locked Cache.\n   be2_val<uint32_t> freeSize;\n\n   //! The bitmask of the currently allocated blocks in the locked cache.\n   be2_val<uint32_t> allocBitMask;\n\n   //! Storage of the bitmasks of current allocations, only set for the first\n   //! bit index of an allocation.\n   be2_array<uint32_t, 32> allocatedMasks;\n\n   //! The size of an allocation in bytes, only set for the first bit index of\n   //! an allocation.\n   be2_array<uint32_t, 32> allocatedSize;\n\n   //! Reference count for LCEnableDMA / LCDisableDMA.\n   be2_val<uint32_t> dmaRefCount;\n};\nCHECK_OFFSET(LockedCacheState, 0x00, mutex);\nCHECK_OFFSET(LockedCacheState, 0x2C, baseAddress);\nCHECK_OFFSET(LockedCacheState, 0x30, freeSize);\nCHECK_OFFSET(LockedCacheState, 0x34, allocBitMask);\nCHECK_OFFSET(LockedCacheState, 0x38, allocatedMasks);\nCHECK_OFFSET(LockedCacheState, 0xB8, allocatedSize);\nCHECK_OFFSET(LockedCacheState, 0x138, dmaRefCount);\nCHECK_SIZE(LockedCacheState, 0x13C);\n\nstruct StaticLockedCacheData\n{\n   be2_array<LockedCacheState, OSGetCoreCount()> coreState;\n};\n\nvirt_ptr<StaticLockedCacheData>\nsLockedCacheData = nullptr;\n\nvirt_ptr<LockedCacheState>\ngetCoreLockedCacheState()\n{\n   return virt_addrof(sLockedCacheData->coreState[OSGetCoreId()]);\n}\n\n\n/**\n * Check if Locked Cache hardware is available on this process.\n */\nBOOL\nLCHardwareIsAvailable()\n{\n   if (OSGetCoreId() != 2) {\n      return OSGetForegroundBucket(nullptr, nullptr);\n   }\n\n   auto upid = OSGetUPID();\n   if (upid == kernel::UniqueProcessId::Game ||\n       upid == kernel::UniqueProcessId::HomeMenu) {\n      return TRUE;\n   }\n\n   return FALSE;\n}\n\n\n/**\n * Allocate some memory from the current core's Locked Cache.\n */\nvirt_ptr<void>\nLCAlloc(uint32_t size)\n{\n   if (size > LCMaxSize) {\n      return  nullptr;\n   }\n\n   auto lcState = getCoreLockedCacheState();\n   auto result = virt_ptr<void> { nullptr };\n   OSLockMutex(virt_addrof(lcState->mutex));\n\n   if (lcState->freeSize >= size) {\n      auto numBlocks = align_up(size, LCBlockSize) / LCBlockSize;\n      auto bitMask = make_bitmask(numBlocks);\n      auto index = 0u;\n\n      // Find a free spot in the allocBitMask which can fit bitMask\n      while (lcState->allocBitMask & (bitMask << index)) {\n         if (index >= 32 - numBlocks) {\n            break;\n         }\n\n         index++;\n      }\n\n      if ((index < 32 - numBlocks) ||\n          (index == 0 && numBlocks == 32)) {\n         // Do the allocation!\n         auto mask = bitMask << index;\n         auto blockSize = numBlocks * LCBlockSize;\n\n         lcState->allocBitMask |= mask;\n         lcState->freeSize -= blockSize;\n\n         lcState->allocatedMasks[index] = mask;\n         lcState->allocatedSize[index] = blockSize;\n\n         result = virt_cast<void *>(lcState->baseAddress + index * LCBlockSize);\n      }\n   }\n\n   OSUnlockMutex(virt_addrof(lcState->mutex));\n   return result;\n}\n\n\n/**\n * Free some memory to the current core's Locked Cache.\n */\nvoid\nLCDealloc(virt_ptr<void> ptr)\n{\n   auto lcState = getCoreLockedCacheState();\n   auto addr = virt_cast<virt_addr>(ptr);\n\n   if (addr < lcState->baseAddress ||\n       addr >= lcState->baseAddress + LCMaxSize) {\n      return;\n   }\n\n   OSLockMutex(virt_addrof(lcState->mutex));\n\n   auto index = static_cast<uint32_t>((addr - lcState->baseAddress) / LCBlockSize);\n   auto mask = lcState->allocatedMasks[index];\n   auto size = lcState->allocatedSize[index];\n\n   lcState->allocBitMask &= ~mask;\n   lcState->freeSize += size;\n\n   lcState->allocatedMasks[index] = 0u;\n   lcState->allocatedSize[index] = 0u;\n\n   OSUnlockMutex(virt_addrof(lcState->mutex));\n}\n\n\n/**\n * Get the maximum size of the current core's Locked Cache.\n */\nuint32_t\nLCGetMaxSize()\n{\n   return LCMaxSize;\n}\n\n\n/**\n * Get the largest allocatable size of the current core's Locked Cache.\n */\nuint32_t\nLCGetAllocatableSize()\n{\n   auto lcState = getCoreLockedCacheState();\n   OSLockMutex(virt_addrof(lcState->mutex));\n\n   // Find the largest span of 0 in the allocBitMask\n   auto largestBitSpan = 0u;\n   auto currentSpanSize = 0u;\n\n   for (auto i = 0u; i < 32; ++i) {\n      if (lcState->allocBitMask & (1 << i)) {\n         largestBitSpan = std::max(currentSpanSize, largestBitSpan);\n         currentSpanSize = 0u;\n         continue;\n      }\n\n      currentSpanSize++;\n   }\n\n   OSUnlockMutex(virt_addrof(lcState->mutex));\n   return largestBitSpan * LCBlockSize;\n}\n\n\n/**\n * Get the total amount of unallocated memory in the current core's Locked Cache.\n */\nuint32_t\nLCGetUnallocated()\n{\n   auto lcState = getCoreLockedCacheState();\n   return lcState->freeSize;\n}\n\n\n/**\n * Check if DMA is enabled for the current core.\n */\nBOOL\nLCIsDMAEnabled()\n{\n   auto lcState = getCoreLockedCacheState();\n   return lcState->dmaRefCount > 0 ? TRUE : FALSE;\n}\n\n\n/**\n * Enable DMA for the current core.\n *\n * Only a thread with affinity set to run only on current core can enable DMA.\n */\nBOOL\nLCEnableDMA()\n{\n   auto thread = OSGetCurrentThread();\n   auto core = OSGetCoreId();\n   auto affinity = thread->attr & OSThreadAttributes::AffinityAny;\n\n   // Ensure thread can only execute on current core\n   if (core == 0 && affinity != OSThreadAttributes::AffinityCPU0) {\n      return FALSE;\n   }\n\n   if (core == 1 && affinity != OSThreadAttributes::AffinityCPU1) {\n      return FALSE;\n   }\n\n   if (core == 2 && affinity != OSThreadAttributes::AffinityCPU2) {\n      return FALSE;\n   }\n\n   auto lcState = getCoreLockedCacheState();\n   lcState->dmaRefCount++;\n   return TRUE;\n}\n\n\n/**\n * Disable DMA for current core.\n */\nvoid\nLCDisableDMA()\n{\n   auto lcState = getCoreLockedCacheState();\n   lcState->dmaRefCount--;\n\n   if (lcState->dmaRefCount == 0) {\n      LCWaitDMAQueue(0);\n   }\n}\n\n\n/**\n * Get the total number of pending DMA requests.\n */\nuint32_t\nLCGetDMAQueueLength()\n{\n   return 0;\n}\n\n\n/**\n * Add a DMA load request to the queue.\n *\n * We fake this by performing the load immediately.\n */\nvoid\nLCLoadDMABlocks(virt_ptr<void> dst,\n                virt_ptr<const void> src,\n                uint32_t size)\n{\n   // TODO: Notify GPU\n   // Signal the GPU to update the source range if necessary, as with\n   //  DCInvalidateRange().\n   // gx2::internal::notifyGpuFlush(const_cast<void *>(src), size);\n\n   if (size == 0) {\n      size = 128;\n   }\n\n   std::memcpy(dst.get(), src.get(), size * 32);\n}\n\n\n/**\n * Add a DMA store request to the queue.\n *\n * We fake this by performing the store immediately.\n */\nvoid\nLCStoreDMABlocks(virt_ptr<void> dst,\n                 virt_ptr<const void> src,\n                 uint32_t size)\n{\n   if (size == 0) {\n      size = 128;\n   }\n\n   std::memcpy(dst.get(), src.get(), size * 32);\n\n   // TODO: Notify GPU\n   // Also signal the memory store to the GPU, as with DCFlushRange().\n   // gx2::internal::notifyCpuFlush(dst, size);\n}\n\n\n/**\n * Wait until the DMA queue is a certain length.\n *\n * As we fake DMA this can return immediately.\n */\nvoid\nLCWaitDMAQueue(uint32_t queueLength)\n{\n}\n\n\nnamespace internal\n{\n\nvoid\ninitialiseLockedCache(uint32_t coreId)\n{\n   auto &state = sLockedCacheData->coreState[coreId];\n   OSInitMutex(virt_addrof(state.mutex));\n\n   state.baseAddress = getLockedCacheBaseAddress(coreId);\n   state.freeSize = 0x4000u;\n   state.allocBitMask = 0u;\n   state.allocatedMasks.fill(0u);\n   state.allocatedSize.fill(0u);\n   state.dmaRefCount = 0u;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerLockedCacheSymbols()\n{\n   RegisterFunctionExport(LCHardwareIsAvailable);\n   RegisterFunctionExport(LCAlloc);\n   RegisterFunctionExport(LCDealloc);\n   RegisterFunctionExport(LCGetMaxSize);\n   RegisterFunctionExport(LCGetAllocatableSize);\n   RegisterFunctionExport(LCGetUnallocated);\n   RegisterFunctionExport(LCIsDMAEnabled);\n   RegisterFunctionExport(LCEnableDMA);\n   RegisterFunctionExport(LCDisableDMA);\n   RegisterFunctionExport(LCGetDMAQueueLength);\n   RegisterFunctionExport(LCLoadDMABlocks);\n   RegisterFunctionExport(LCStoreDMABlocks);\n   RegisterFunctionExport(LCWaitDMAQueue);\n\n   RegisterDataInternal(sLockedCacheData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_lockedcache.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_lockedcache Locked Cache\n * \\ingroup coreinit\n * @{\n */\n\nBOOL\nLCHardwareIsAvailable();\n\nvirt_ptr<void>\nLCAlloc(uint32_t size);\n\nvoid\nLCDealloc(virt_ptr<void> addr);\n\nuint32_t\nLCGetMaxSize();\n\nuint32_t\nLCGetAllocatableSize();\n\nuint32_t\nLCGetUnallocated();\n\nBOOL\nLCIsDMAEnabled();\n\nBOOL\nLCEnableDMA();\n\nvoid\nLCDisableDMA();\n\nuint32_t\nLCGetDMAQueueLength();\n\nvoid\nLCLoadDMABlocks(virt_ptr<void> dst,\n                virt_ptr<const void> src,\n                uint32_t size);\n\nvoid\nLCStoreDMABlocks(virt_ptr<void> dst,\n                 virt_ptr<const void> src,\n                 uint32_t size);\n\nvoid\nLCWaitDMAQueue(uint32_t queueLength);\n\nnamespace internal\n{\n\nvoid\ninitialiseLockedCache(uint32_t coreId);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_log.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_log.h\"\n\nnamespace cafe::coreinit\n{\n\nvoid\nOSLogPrintf(uint32_t unk1,\n            uint32_t unk2,\n            uint32_t unk3,\n            const char *fmt,\n            ...)\n{\n   // TODO: OSLogPrintf\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_log.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace cafe::coreinit\n{\n\nvoid\nOSLogPrintf(uint32_t unk1,\n            uint32_t unk2,\n            uint32_t unk3,\n            const char *fmt,\n            ...);\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_mcp.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_fsa.h\"\n#include \"coreinit_ios.h\"\n#include \"coreinit_ipcbufpool.h\"\n#include \"coreinit_mcp.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"ios/ios_error.h\"\n\nnamespace cafe::coreinit\n{\n\nusing ios::mcp::MCPCommand;\nusing ios::mcp::MCPDeviceFlags;\nusing ios::mcp::MCPResponseGetTitleId;\nusing ios::mcp::MCPResponseGetOwnTitleInfo;\nusing ios::mcp::MCPResponseUpdateCheckContext;\nusing ios::mcp::MCPResponseUpdateCheckResume;\nusing ios::mcp::MCPRequestDeviceList;\nusing ios::mcp::MCPRequestGetOwnTitleInfo;\nusing ios::mcp::MCPRequestSearchTitleList;\nusing ios::mcp::MCPTitleListSearchFlags;\n\nstatic constexpr uint32_t SmallMessageCount = 0x100;\nstatic constexpr uint32_t SmallMessageSize = 0x80;\n\nstatic constexpr uint32_t LargeMessageCount = 4;\nstatic constexpr uint32_t LargeMessageSize = 0x1000;\n\nstruct StaticMcpData\n{\n   be2_val<BOOL> initialised;\n\n   be2_virt_ptr<IPCBufPool> smallMessagePool;\n   be2_virt_ptr<IPCBufPool> largeMessagePool;\n\n   be2_array<uint8_t, SmallMessageCount * SmallMessageSize> smallMessageBuffer;\n   be2_array<uint8_t, LargeMessageCount * LargeMessageSize> largeMessageBuffer;\n\n   be2_val<uint32_t> smallMessageCount;\n   be2_val<uint32_t> largeMessageCount;\n};\n\n\nstatic virt_ptr<StaticMcpData>\nsMcpData = nullptr;\n\n\nIOSError\nMCP_Open()\n{\n   return IOS_Open(make_stack_string(\"/dev/mcp\"),\n                   IOSOpenMode::None);\n}\n\n\nvoid\nMCP_Close(IOSHandle handle)\n{\n   IOS_Close(handle);\n}\n\nIOSError\nMCP_DeviceList(IOSHandle handle,\n               virt_ptr<int32_t> numDevices,\n               virt_ptr<MCPDevice> deviceList,\n               uint32_t deviceListSizeBytes)\n{\n   if (!numDevices || !deviceList) {\n      return static_cast<IOSError>(MCPError::InvalidParam);\n   }\n\n   auto request = virt_cast<MCPRequestDeviceList *>(\n                     internal::mcpAllocateMessage(sizeof(MCPRequestDeviceList)));\n   if (!request) {\n      return static_cast<IOSError>(MCPError::Alloc);\n   }\n\n   request->flags = MCPDeviceFlags::Unk1;\n\n   auto result = IOS_Ioctl(handle,\n                           MCPCommand::DeviceList,\n                           request,\n                           sizeof(uint32_t),\n                           deviceList,\n                           deviceListSizeBytes);\n   if (result >= 0) {\n      *numDevices = static_cast<int32_t>(result);\n   }\n\n   internal::mcpFreeMessage(request);\n   return result; // This function does not translate result to MCPError\n\n}\n\nIOSError\nMCP_FullDeviceList(IOSHandle handle,\n                   virt_ptr<int32_t> numDevices,\n                   virt_ptr<MCPDevice> deviceList,\n                   uint32_t deviceListSizeBytes)\n{\n   if (!numDevices || !deviceList) {\n      return static_cast<IOSError>(MCPError::InvalidParam);\n   }\n\n   auto request = virt_cast<MCPRequestDeviceList *>(\n                     internal::mcpAllocateMessage(sizeof(MCPRequestDeviceList)));\n   if (!request) {\n      return static_cast<IOSError>(MCPError::Alloc);\n   }\n\n   request->flags = MCPDeviceFlags::Unk1 | MCPDeviceFlags::Unk2 | MCPDeviceFlags::Unk8;\n\n   auto result = IOS_Ioctl(handle,\n                           MCPCommand::DeviceList,\n                           request,\n                           sizeof(uint32_t),\n                           deviceList,\n                           deviceListSizeBytes);\n   if (result >= 0) {\n      *numDevices = static_cast<int32_t>(result);\n   }\n\n   internal::mcpFreeMessage(request);\n   return result; // This function does not translate result to MCPError\n}\n\nint32_t\nMCP_GetErrorCodeForViewer(MCPError error)\n{\n   if (error >= 0) {\n      return 1629999;\n   }\n\n   auto group = (~static_cast<uint32_t>(error) >> 16) & 0x3FF;\n   if (group != 4) {\n      return 1629999;\n   }\n\n   if (error & 0x8000) {\n      return -0x47E0 - (error | 0xFFFF0000) + 0x190000;\n   } else {\n      return -0x47E0 - (error & 0xFFFF) + 0x190000;\n   }\n}\n\n\nMCPError\nMCP_GetOwnTitleInfo(IOSHandle handle,\n                    virt_ptr<MCPTitleListType> titleInfo)\n{\n   auto result = MCPError::OK;\n   auto request = virt_cast<MCPRequestGetOwnTitleInfo *>(\n                     internal::mcpAllocateMessage(sizeof(MCPRequestGetOwnTitleInfo)));\n\n   if (!request) {\n      return MCPError::Alloc;\n   }\n\n   auto response = virt_cast<MCPResponseGetOwnTitleInfo *>(\n                      internal::mcpAllocateMessage(sizeof(MCPResponseGetOwnTitleInfo)));\n\n   if (!response) {\n      internal::mcpFreeMessage(request);\n      return MCPError::Alloc;\n   }\n\n   // TODO: __KernelGetInfo(0, &request->unk0x00, 0xA8, 0);\n   request->unk0x00 = 0u;\n\n   auto iosError = IOS_Ioctl(handle,\n                             MCPCommand::GetOwnTitleInfo,\n                             request,\n                             sizeof(uint32_t),\n                             response,\n                             sizeof(MCPResponseGetOwnTitleInfo));\n\n   result = internal::mcpDecodeIosErrorToMcpError(iosError);\n\n   if (result >= 0) {\n      std::memcpy(titleInfo.get(),\n                  virt_addrof(response->titleInfo).get(),\n                  sizeof(MCPTitleListType));\n   }\n\n   internal::mcpFreeMessage(request);\n   internal::mcpFreeMessage(response);\n   return result;\n}\n\n\nMCPError\nMCP_GetSysProdSettings(IOSHandle handle,\n                       virt_ptr<MCPSysProdSettings> settings)\n{\n   if (!settings) {\n      return MCPError::InvalidParam;\n   }\n\n   auto message = internal::mcpAllocateMessage(sizeof(IOSVec));\n\n   if (!message) {\n      return MCPError::Alloc;\n   }\n\n   auto outVecs = virt_cast<IOSVec *>(message);\n   outVecs[0].vaddr = virt_cast<virt_addr>(settings);\n   outVecs[0].len = static_cast<uint32_t>(sizeof(MCPSysProdSettings));\n\n   auto iosError = IOS_Ioctlv(handle, MCPCommand::GetSysProdSettings, 0, 1, outVecs);\n   auto mcpError = internal::mcpDecodeIosErrorToMcpError(iosError);\n\n   internal::mcpFreeMessage(message);\n   return mcpError;\n}\n\n\nMCPError\nMCP_GetTitleId(IOSHandle handle,\n               virt_ptr<uint64_t> outTitleId)\n{\n   auto result = MCPError::OK;\n   auto output = internal::mcpAllocateMessage(sizeof(MCPResponseGetTitleId));\n\n   if (!output) {\n      return MCPError::Alloc;\n   }\n\n   auto iosError = IOS_Ioctl(handle,\n                             MCPCommand::GetTitleId,\n                             nullptr,\n                             0,\n                             output,\n                             sizeof(MCPResponseGetTitleId));\n\n   result = internal::mcpDecodeIosErrorToMcpError(iosError);\n\n   if (result >= 0) {\n      auto response = virt_cast<MCPResponseGetTitleId *>(output);\n      *outTitleId = response->titleId;\n   }\n\n   internal::mcpFreeMessage(output);\n   return result;\n}\n\n\nMCPError\nMCP_GetTitleInfo(IOSHandle handle,\n                 uint64_t titleId,\n                 virt_ptr<MCPTitleListType> titleInfo)\n{\n   auto searchTitle = StackObject<MCPTitleListType> { };\n   std::memset(searchTitle.get(), 0, sizeof(MCPTitleListType));\n   searchTitle->titleId = titleId;\n\n   auto iosError = internal::mcpSearchTitleList(handle,\n                                                searchTitle,\n                                                MCPTitleListSearchFlags::TitleId,\n                                                titleInfo,\n                                                1);\n\n   if (iosError != 1) {\n      return MCPError::System;\n   }\n\n   return MCPError::OK;\n}\n\n\nMCPError\nMCP_TitleCount(IOSHandle handle)\n{\n   auto result = IOS_Ioctl(handle,\n                           MCPCommand::TitleCount,\n                           nullptr, 0,\n                           nullptr, 0);\n\n   if (result < 0) {\n      return internal::mcpDecodeIosErrorToMcpError(result);\n   }\n\n   return static_cast<MCPError>(result);\n}\n\n\nMCPError\nMCP_TitleList(IOSHandle handle,\n              virt_ptr<uint32_t> outTitleCount,\n              virt_ptr<MCPTitleListType> titleList,\n              uint32_t titleListSizeBytes)\n{\n   auto result = IOSError::OK;\n\n   if (!titleList || !titleListSizeBytes) {\n      result = static_cast<IOSError>(MCP_TitleCount(handle));\n   } else {\n      auto searchTitle = StackObject<MCPTitleListType> { };\n      std::memset(searchTitle.get(), 0, sizeof(MCPTitleListType));\n\n      result = internal::mcpSearchTitleList(handle,\n                                            searchTitle,\n                                            MCPTitleListSearchFlags::None,\n                                            titleList,\n                                            titleListSizeBytes / sizeof(MCPTitleListType));\n   }\n\n   if (result < 0) {\n      return internal::mcpDecodeIosErrorToMcpError(result);\n   }\n\n   *outTitleCount = static_cast<uint32_t>(result);\n   return MCPError::OK;\n}\n\n\nMCPError\nMCP_TitleListByAppType(IOSHandle handle,\n                       MCPAppType appType,\n                       virt_ptr<uint32_t> outTitleCount,\n                       virt_ptr<MCPTitleListType> titleList,\n                       uint32_t titleListSizeBytes)\n{\n   auto searchTitle = StackObject<MCPTitleListType> { };\n   std::memset(searchTitle.get(), 0, sizeof(MCPTitleListType));\n   searchTitle->appType = appType;\n\n   auto result = internal::mcpSearchTitleList(handle,\n                                              searchTitle,\n                                              MCPTitleListSearchFlags::AppType,\n                                              titleList,\n                                              titleListSizeBytes / sizeof(MCPTitleListType));\n\n   if (result < 0) {\n      return internal::mcpDecodeIosErrorToMcpError(result);\n   }\n\n   *outTitleCount = static_cast<uint32_t>(result);\n   return MCPError::OK;\n}\n\n\nMCPError\nMCP_TitleListByUniqueId(IOSHandle handle,\n                        uint32_t uniqueId,\n                        virt_ptr<uint32_t> outTitleCount,\n                        virt_ptr<MCPTitleListType> titleList,\n                        uint32_t titleListSizeBytes)\n{\n   auto searchTitle = StackObject<MCPTitleListType> { };\n   std::memset(searchTitle.get(), 0, sizeof(MCPTitleListType));\n   searchTitle->titleId = uniqueId << 8;\n   searchTitle->appType = MCPAppType::Unk0x0800000E;\n\n   auto searchFlags = MCPTitleListSearchFlags::UniqueId\n                    | MCPTitleListSearchFlags::AppType;\n\n   auto result = internal::mcpSearchTitleList(handle,\n                                              searchTitle,\n                                              searchFlags,\n                                              titleList,\n                                              titleListSizeBytes / sizeof(MCPTitleListType));\n\n   if (result < 0) {\n      return internal::mcpDecodeIosErrorToMcpError(result);\n   }\n\n   *outTitleCount = static_cast<uint32_t>(result);\n   return MCPError::OK;\n}\n\n\nMCPError\nMCP_TitleListByUniqueIdAndIndexedDeviceAndAppType(IOSHandle handle,\n                                                  uint32_t uniqueId,\n                                                  virt_ptr<const char> indexedDevice,\n                                                  uint8_t unk0x60,\n                                                  MCPAppType appType,\n                                                  virt_ptr<uint32_t> outTitleCount,\n                                                  virt_ptr<MCPTitleListType> titleList,\n                                                  uint32_t titleListSizeBytes)\n{\n   auto searchTitle = StackObject<MCPTitleListType> { };\n   std::memset(searchTitle.get(), 0, sizeof(MCPTitleListType));\n   searchTitle->titleId = uniqueId << 8;\n   searchTitle->appType = appType;\n   searchTitle->unk0x60 = unk0x60;\n   std::memcpy(virt_addrof(searchTitle->indexedDevice).get(),\n               indexedDevice.get(),\n               4);\n\n   auto searchFlags = MCPTitleListSearchFlags::UniqueId\n                    | MCPTitleListSearchFlags::AppType\n                    | MCPTitleListSearchFlags::Unk0x60\n                    | MCPTitleListSearchFlags::IndexedDevice;\n\n   auto result = internal::mcpSearchTitleList(handle,\n                                              searchTitle,\n                                              searchFlags,\n                                              titleList,\n                                              titleListSizeBytes / sizeof(MCPTitleListType));\n\n   if (result < 0) {\n      return internal::mcpDecodeIosErrorToMcpError(result);\n   }\n\n   *outTitleCount = static_cast<uint32_t>(result);\n   return MCPError::OK;\n}\n\n\nMCPError\nMCP_UpdateCheckContext(IOSHandle handle,\n                       virt_ptr<uint32_t> outResult)\n{\n   auto message = internal::mcpAllocateMessage(sizeof(MCPResponseUpdateCheckContext));\n   if (!message) {\n      return MCPError::Alloc;\n   }\n\n   auto response = virt_cast<MCPResponseUpdateCheckContext *>(message);\n   auto result = IOS_Ioctl(handle,\n                           MCPCommand::UpdateCheckContext,\n                           nullptr, 0,\n                           response, sizeof(MCPResponseUpdateCheckContext));\n\n   if (result < 0) {\n      return internal::mcpDecodeIosErrorToMcpError(result);\n   }\n\n   internal::mcpFreeMessage(message);\n   *outResult = response->result;\n   return MCPError::OK;\n}\n\n\nMCPError\nMCP_UpdateCheckResume(IOSHandle handle,\n                      virt_ptr<uint32_t> outResult)\n{\n   auto message = internal::mcpAllocateMessage(sizeof(MCPResponseUpdateCheckResume));\n   if (!message) {\n      return MCPError::Alloc;\n   }\n\n   auto response = virt_cast<MCPResponseUpdateCheckResume *>(message);\n   auto result = IOS_Ioctl(handle,\n                           MCPCommand::UpdateCheckResume,\n                           nullptr, 0,\n                           response, sizeof(MCPResponseUpdateCheckResume));\n\n   if (result < 0) {\n      return internal::mcpDecodeIosErrorToMcpError(result);\n   }\n\n   internal::mcpFreeMessage(message);\n   *outResult = response->result;\n   return MCPError::OK;\n}\n\n\nMCPError\nMCP_UpdateGetProgress(IOSHandle handle,\n                      virt_ptr<MCPUpdateProgress> outUpdateProgress)\n{\n   if (!outUpdateProgress || !align_check(outUpdateProgress.get(), 64)) {\n      return MCPError::InvalidParam;\n   }\n\n   auto message = internal::mcpAllocateMessage(sizeof(IOSVec));\n   if (!message) {\n      return MCPError::Alloc;\n   }\n\n   auto outVecs = virt_cast<IOSVec *>(message);\n   outVecs[0].vaddr = virt_cast<virt_addr>(outUpdateProgress);\n   outVecs[0].len = static_cast<uint32_t>(sizeof(MCPUpdateProgress));\n\n   auto iosError = IOS_Ioctlv(handle, MCPCommand::UpdateGetProgress, 0, 1, outVecs);\n   auto mcpError = internal::mcpDecodeIosErrorToMcpError(iosError);\n\n   internal::mcpFreeMessage(message);\n   return mcpError;\n}\n\n\nnamespace internal\n{\n\n\nvirt_ptr<void>\nmcpAllocateMessage(uint32_t size)\n{\n   auto message = virt_ptr<void> { nullptr };\n\n   if (size == 0) {\n      return nullptr;\n   } else if (size <= SmallMessageSize) {\n      message = IPCBufPoolAllocate(sMcpData->smallMessagePool, size);\n   } else {\n      message = IPCBufPoolAllocate(sMcpData->largeMessagePool, size);\n   }\n\n   if (message) {\n      std::memset(message.get(), 0, size);\n   }\n\n   return message;\n}\n\n\nMCPError\nmcpFreeMessage(virt_ptr<void> message)\n{\n   if (IPCBufPoolFree(sMcpData->smallMessagePool, message) == IOSError::OK) {\n      return MCPError::OK;\n   }\n\n   if (IPCBufPoolFree(sMcpData->largeMessagePool, message) == IOSError::OK) {\n      return MCPError::OK;\n   }\n\n   return MCPError::Opcode;\n}\n\n\nMCPError\nmcpDecodeIosErrorToMcpError(IOSError error)\n{\n   auto category = ios::getErrorCategory(error);\n   auto code = ios::getErrorCode(error);\n   auto mcpError = static_cast<MCPError>(error);\n\n   if (error < 0) {\n      switch (category) {\n      case IOSErrorCategory::Kernel:\n         if (code > -1000) {\n            mcpError = static_cast<MCPError>(code + MCPError::KernelErrorBase);\n         } else if(code < -1999) {\n            mcpError = static_cast<MCPError>(code - (IOSErrorCategory::MCP << 16));\n         }\n         break;\n      case IOSErrorCategory::FSA:\n         if (code == FSAStatus::AlreadyOpen) {\n            mcpError = MCPError::AlreadyOpen;\n         } else if (code == FSAStatus::DataCorrupted) {\n            mcpError = MCPError::DataCorrupted;\n         } else if (code == FSAStatus::StorageFull) {\n            mcpError = MCPError::StorageFull;\n         } else if (code == FSAStatus::WriteProtected) {\n            mcpError = MCPError::WriteProtected;\n         } else {\n            mcpError = static_cast<MCPError>(error + 0xFFFF0000 - 4000);\n         }\n         break;\n      case IOSErrorCategory::MCP:\n         mcpError = static_cast<MCPError>(error);\n         break;\n      }\n   }\n\n   return mcpError;\n}\n\n\nIOSError\nmcpSearchTitleList(IOSHandle handle,\n                   virt_ptr<MCPTitleListType> searchTitle,\n                   MCPTitleListSearchFlags searchFlags,\n                   virt_ptr<MCPTitleListType> titleList,\n                   uint32_t titleListLength)\n{\n   auto message = mcpAllocateMessage(sizeof(MCPRequestSearchTitleList));\n\n   if (!message) {\n      return static_cast<IOSError>(MCPError::Alloc);\n   }\n\n   auto request = virt_cast<MCPRequestSearchTitleList *>(message);\n   request->searchTitle = *searchTitle;\n   request->searchFlags = searchFlags;\n\n   auto iosError = IOS_Ioctl(handle,\n                             MCPCommand::SearchTitleList,\n                             request,\n                             sizeof(MCPRequestSearchTitleList),\n                             titleList,\n                             titleListLength * sizeof(MCPTitleListType));\n\n   mcpFreeMessage(message);\n   return iosError;\n}\n\n\nvoid\ninitialiseMcp()\n{\n   sMcpData->smallMessagePool = IPCBufPoolCreate(virt_addrof(sMcpData->smallMessageBuffer),\n                                                 static_cast<uint32_t>(sMcpData->smallMessageBuffer.size()),\n                                                 SmallMessageSize,\n                                                 virt_addrof(sMcpData->smallMessageCount),\n                                                 1);\n\n   sMcpData->largeMessagePool = IPCBufPoolCreate(virt_addrof(sMcpData->largeMessageBuffer),\n                                                 static_cast<uint32_t>(sMcpData->largeMessageBuffer.size()),\n                                                 LargeMessageSize,\n                                                 virt_addrof(sMcpData->largeMessageCount),\n                                                 1);\n\n   sMcpData->initialised = true;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerMcpSymbols()\n{\n   RegisterFunctionExport(MCP_Open);\n   RegisterFunctionExport(MCP_Close);\n   RegisterFunctionExport(MCP_DeviceList);\n   RegisterFunctionExport(MCP_FullDeviceList);\n   RegisterFunctionExport(MCP_GetErrorCodeForViewer);\n   RegisterFunctionExport(MCP_GetOwnTitleInfo);\n   RegisterFunctionExport(MCP_GetSysProdSettings);\n   RegisterFunctionExport(MCP_GetTitleId);\n   RegisterFunctionExport(MCP_GetTitleInfo);\n   RegisterFunctionExport(MCP_TitleCount);\n   RegisterFunctionExport(MCP_TitleList);\n   RegisterFunctionExport(MCP_TitleListByAppType);\n   RegisterFunctionExport(MCP_TitleListByUniqueId);\n   RegisterFunctionExport(MCP_TitleListByUniqueIdAndIndexedDeviceAndAppType);\n   RegisterFunctionExport(MCP_UpdateCheckContext);\n   RegisterFunctionExport(MCP_UpdateCheckResume);\n   RegisterFunctionExport(MCP_UpdateGetProgress);\n\n   RegisterDataInternal(sMcpData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_mcp.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_ios.h\"\n#include \"ios/mcp/ios_mcp_mcp.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_mcp MCP\n * \\ingroup coreinit\n * @{\n */\n\nusing ios::mcp::MCPAppType;\nusing ios::mcp::MCPCountryCode;\nusing ios::mcp::MCPDevice;\nusing ios::mcp::MCPError;\nusing ios::mcp::MCPRegion;\nusing ios::mcp::MCPSysProdSettings;\nusing ios::mcp::MCPTitleListType;\nusing ios::mcp::MCPTitleListSearchFlags;\nusing ios::mcp::MCPUpdateProgress;\n\nIOSError\nMCP_Open();\n\nvoid\nMCP_Close(IOSHandle handle);\n\nIOSError\nMCP_DeviceList(IOSHandle handle,\n               virt_ptr<int32_t> numDevices,\n               virt_ptr<MCPDevice> deviceList,\n               uint32_t deviceListSizeBytes);\n\nIOSError\nMCP_FullDeviceList(IOSHandle handle,\n                   virt_ptr<int32_t> numDevices,\n                   virt_ptr<MCPDevice> deviceList,\n                   uint32_t deviceListSizeBytes);\n\nint32_t\nMCP_GetErrorCodeForViewer(MCPError error);\n\nMCPError\nMCP_GetOwnTitleInfo(IOSHandle handle,\n                    virt_ptr<MCPTitleListType> titleInfo);\n\nMCPError\nMCP_GetSysProdSettings(IOSHandle handle,\n                       virt_ptr<MCPSysProdSettings> settings);\n\nMCPError\nMCP_GetTitleId(IOSHandle handle,\n               virt_ptr<uint64_t> outTitleId);\n\nMCPError\nMCP_GetTitleInfo(IOSHandle handle,\n                 uint64_t titleId,\n                 virt_ptr<MCPTitleListType> titleInfo);\n\nMCPError\nMCP_TitleCount(IOSHandle handle);\n\nMCPError\nMCP_TitleList(IOSHandle handle,\n              virt_ptr<uint32_t> outTitleCount,\n              virt_ptr<MCPTitleListType> titleList,\n              uint32_t titleListSizeBytes);\n\nMCPError\nMCP_TitleListByAppType(IOSHandle handle,\n                       MCPAppType appType,\n                       virt_ptr<uint32_t> outTitleCount,\n                       virt_ptr<MCPTitleListType> titleList,\n                       uint32_t titleListSizeBytes);\n\nMCPError\nMCP_TitleListByUniqueId(IOSHandle handle,\n                        uint32_t uniqueId,\n                        virt_ptr<uint32_t> outTitleCount,\n                        virt_ptr<MCPTitleListType> titleList,\n                        uint32_t titleListSizeBytes);\n\nMCPError\nMCP_TitleListByUniqueIdAndIndexedDeviceAndAppType(IOSHandle handle,\n                                                  uint32_t uniqueId,\n                                                  virt_ptr<const char> indexedDevice,\n                                                  uint8_t unk0x60,\n                                                  MCPAppType appType,\n                                                  virt_ptr<uint32_t> outTitleCount,\n                                                  virt_ptr<MCPTitleListType> titleList,\n                                                  uint32_t titleListSizeBytes);\n\nMCPError\nMCP_UpdateCheckContext(IOSHandle handle,\n                       virt_ptr<uint32_t> outResult);\n\nMCPError\nMCP_UpdateCheckResume(IOSHandle handle,\n                      virt_ptr<uint32_t> outResult);\n\nMCPError\nMCP_UpdateGetProgress(IOSHandle handle,\n                      virt_ptr<MCPUpdateProgress> outUpdateProgress);\n\nnamespace internal\n{\n\nvoid\ninitialiseMcp();\n\nvirt_ptr<void>\nmcpAllocateMessage(uint32_t size);\n\nMCPError\nmcpFreeMessage(virt_ptr<void> message);\n\nMCPError\nmcpDecodeIosErrorToMcpError(IOSError error);\n\nIOSError\nmcpSearchTitleList(IOSHandle handle,\n                   virt_ptr<MCPTitleListType> searchTitle,\n                   MCPTitleListSearchFlags searchFlags,\n                   virt_ptr<MCPTitleListType> titleListOut,\n                   uint32_t titleListLength);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memallocator.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_enum.h\"\n#include \"coreinit_memallocator.h\"\n#include \"coreinit_memblockheap.h\"\n#include \"coreinit_memdefaultheap.h\"\n#include \"coreinit_memexpheap.h\"\n#include \"coreinit_memframeheap.h\"\n#include \"coreinit_memunitheap.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <libcpu/state.h>\n\nnamespace cafe::coreinit\n{\n\nstruct StaticAllocatorData\n{\n   be2_struct<MEMAllocatorFunctions> defaultHeapFunctions;\n   be2_struct<MEMAllocatorFunctions> blockHeapFunctions;\n   be2_struct<MEMAllocatorFunctions> expHeapFunctions;\n   be2_struct<MEMAllocatorFunctions> frameHeapFunctions;\n   be2_struct<MEMAllocatorFunctions> unitHeapFunctions;\n};\n\nstatic virt_ptr<StaticAllocatorData>\nsAllocatorData = nullptr;\n\nstatic MEMAllocatorAllocFn sDefaultHeapAlloc = nullptr;\nstatic MEMAllocatorFreeFn sDefaultHeapFree = nullptr;\nstatic MEMAllocatorAllocFn sBlockHeapAlloc = nullptr;\nstatic MEMAllocatorFreeFn sBlockHeapFree = nullptr;\nstatic MEMAllocatorAllocFn sExpHeapAlloc = nullptr;\nstatic MEMAllocatorFreeFn sExpHeapFree = nullptr;\nstatic MEMAllocatorAllocFn sFrameHeapAlloc = nullptr;\nstatic MEMAllocatorFreeFn sFrameHeapFree = nullptr;\nstatic MEMAllocatorAllocFn sUnitHeapAlloc = nullptr;\nstatic MEMAllocatorFreeFn sUnitHeapFree = nullptr;\n\n/**\n * Initialise an Allocator struct for the default heap.\n */\nvoid\nMEMInitAllocatorForDefaultHeap(virt_ptr<MEMAllocator> allocator)\n{\n   allocator->heap = MEMGetBaseHeapHandle(MEMBaseHeapType::MEM2);\n   allocator->align = 0;\n   allocator->funcs = virt_addrof(sAllocatorData->defaultHeapFunctions);\n}\n\n\n/**\n * Initialise an Allocator struct for a block heap.\n */\nvoid\nMEMInitAllocatorForBlockHeap(virt_ptr<MEMAllocator> allocator,\n                             MEMHeapHandle handle,\n                             int32_t alignment)\n{\n   allocator->heap = handle;\n   allocator->align = alignment;\n   allocator->funcs = virt_addrof(sAllocatorData->blockHeapFunctions);\n}\n\n\n/**\n * Initialise an Allocator struct for an expanded heap.\n */\nvoid\nMEMInitAllocatorForExpHeap(virt_ptr<MEMAllocator> allocator,\n                           MEMHeapHandle handle,\n                           int32_t alignment)\n{\n   allocator->heap = handle;\n   allocator->align = alignment;\n   allocator->funcs = virt_addrof(sAllocatorData->expHeapFunctions);\n}\n\n\n/**\n * Initialise an Allocator struct for a frame heap.\n */\nvoid\nMEMInitAllocatorForFrmHeap(virt_ptr<MEMAllocator> allocator,\n                           MEMHeapHandle handle,\n                           int32_t alignment)\n{\n   allocator->heap = handle;\n   allocator->align = alignment;\n   allocator->funcs = virt_addrof(sAllocatorData->frameHeapFunctions);\n}\n\n\n/**\n * Initialise an Allocator struct for a unit heap.\n */\nvoid\nMEMInitAllocatorForUnitHeap(virt_ptr<MEMAllocator> allocator,\n                            MEMHeapHandle handle)\n{\n   allocator->heap = handle;\n   allocator->align = 0;\n   allocator->funcs = virt_addrof(sAllocatorData->unitHeapFunctions);\n}\n\n\n/**\n * Allocate memory from an Allocator.\n *\n * \\return Returns pointer to new allocated memory.\n */\nvirt_ptr<void>\nMEMAllocFromAllocator(virt_ptr<MEMAllocator> allocator,\n                      uint32_t size)\n{\n   return cafe::invoke(cpu::this_core::state(),\n                       allocator->funcs->alloc,\n                       allocator, size);\n}\n\n\n/**\n * Free memory from an Allocator.\n */\nvoid\nMEMFreeToAllocator(virt_ptr<MEMAllocator> allocator,\n                   virt_ptr<void> block)\n{\n   return cafe::invoke(cpu::this_core::state(),\n                       allocator->funcs->free,\n                       allocator, block);\n}\n\n\nstatic virt_ptr<void>\nallocatorDefaultHeapAlloc(virt_ptr<MEMAllocator> allocator,\n                          uint32_t size)\n{\n   return MEMAllocFromDefaultHeap(size);\n}\n\nstatic void\nallocatorDefaultHeapFree(virt_ptr<MEMAllocator> allocator,\n                         virt_ptr<void> block)\n{\n   MEMFreeToDefaultHeap(block);\n}\n\nstatic virt_ptr<void>\nallocatorBlockHeapAlloc(virt_ptr<MEMAllocator> allocator,\n                        uint32_t size)\n{\n   return MEMAllocFromBlockHeapEx(allocator->heap,\n                                  size,\n                                  allocator->align);\n}\n\nstatic void\nallocatorBlockHeapFree(virt_ptr<MEMAllocator> allocator,\n                       virt_ptr<void> block)\n{\n   MEMFreeToBlockHeap(allocator->heap, block);\n}\n\nstatic virt_ptr<void>\nallocatorExpHeapAlloc(virt_ptr<MEMAllocator> allocator,\n                      uint32_t size)\n{\n   return MEMAllocFromExpHeapEx(allocator->heap,\n                                size,\n                                allocator->align);\n}\n\nstatic void\nallocatorExpHeapFree(virt_ptr<MEMAllocator> allocator,\n                     virt_ptr<void> block)\n{\n   MEMFreeToExpHeap(allocator->heap, block);\n}\n\nstatic virt_ptr<void>\nallocatorFrameHeapAlloc(virt_ptr<MEMAllocator> allocator,\n                      uint32_t size)\n{\n   return MEMAllocFromFrmHeapEx(allocator->heap,\n                                size,\n                                allocator->align);\n}\n\nstatic void\nallocatorFrameHeapFree(virt_ptr<MEMAllocator> allocator,\n                     virt_ptr<void> block)\n{\n   /* Woooowwww I sure hope no one uses frame heap in an allocator...\n    *\n    * coreinit.rpl does not actually free memory here, probably because\n    * using a frame heap for an allocator where you do not know the exact\n    * order of alloc and free is a really dumb idea\n    */\n   gLog->warn(\"Allocator did not free memory allocated from frame heap\");\n}\n\nstatic virt_ptr<void>\nallocatorUnitHeapAlloc(virt_ptr<MEMAllocator> allocator,\n                       uint32_t size)\n{\n   return MEMAllocFromUnitHeap(allocator->heap);\n}\n\nstatic void\nallocatorUnitHeapFree(virt_ptr<MEMAllocator> allocator,\n                      virt_ptr<void> block)\n{\n   MEMFreeToUnitHeap(allocator->heap, block);\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseAllocatorStaticData()\n{\n   sAllocatorData->defaultHeapFunctions.alloc = sDefaultHeapAlloc;\n   sAllocatorData->defaultHeapFunctions.free = sDefaultHeapFree;\n   sAllocatorData->blockHeapFunctions.alloc = sBlockHeapAlloc;\n   sAllocatorData->blockHeapFunctions.free = sBlockHeapFree;\n   sAllocatorData->expHeapFunctions.alloc = sExpHeapAlloc;\n   sAllocatorData->expHeapFunctions.free = sExpHeapFree;\n   sAllocatorData->frameHeapFunctions.alloc = sFrameHeapAlloc;\n   sAllocatorData->frameHeapFunctions.free = sFrameHeapFree;\n   sAllocatorData->unitHeapFunctions.alloc = sUnitHeapAlloc;\n   sAllocatorData->unitHeapFunctions.free = sUnitHeapFree;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerMemAllocatorSymbols()\n{\n   RegisterFunctionExport(MEMInitAllocatorForDefaultHeap);\n   RegisterFunctionExport(MEMInitAllocatorForBlockHeap);\n   RegisterFunctionExport(MEMInitAllocatorForExpHeap);\n   RegisterFunctionExport(MEMInitAllocatorForFrmHeap);\n   RegisterFunctionExport(MEMInitAllocatorForUnitHeap);\n   RegisterFunctionExport(MEMAllocFromAllocator);\n   RegisterFunctionExport(MEMFreeToAllocator);\n\n   RegisterDataInternal(sAllocatorData);\n   RegisterFunctionInternal(allocatorDefaultHeapAlloc, sDefaultHeapAlloc);\n   RegisterFunctionInternal(allocatorDefaultHeapFree, sDefaultHeapFree);\n   RegisterFunctionInternal(allocatorBlockHeapAlloc, sBlockHeapAlloc);\n   RegisterFunctionInternal(allocatorBlockHeapFree, sBlockHeapFree);\n   RegisterFunctionInternal(allocatorExpHeapAlloc, sExpHeapAlloc);\n   RegisterFunctionInternal(allocatorExpHeapFree, sExpHeapFree);\n   RegisterFunctionInternal(allocatorFrameHeapAlloc, sFrameHeapAlloc);\n   RegisterFunctionInternal(allocatorFrameHeapFree, sFrameHeapFree);\n   RegisterFunctionInternal(allocatorUnitHeapAlloc, sUnitHeapAlloc);\n   RegisterFunctionInternal(allocatorUnitHeapFree, sUnitHeapFree);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memallocator.h",
    "content": "#pragma once\n#include \"coreinit_memheap.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nstruct MEMAllocator;\nstruct MEMBlockHeap;\nstruct MEMExpandedHeap;\nstruct MEMFrameHeap;\nstruct MEMUnitHeap;\n\n/**\n * \\defgroup coreinit_allocator Allocator\n * \\ingroup coreinit\n * @{\n */\n\nusing MEMAllocatorAllocFn = virt_func_ptr<virt_ptr<void>(virt_ptr<MEMAllocator>, uint32_t)>;\nusing MEMAllocatorFreeFn = virt_func_ptr<void(virt_ptr<MEMAllocator>, virt_ptr<void>)>;\n\nstruct MEMAllocatorFunctions\n{\n   be2_val<MEMAllocatorAllocFn> alloc;\n   be2_val<MEMAllocatorFreeFn> free;\n};\nCHECK_OFFSET(MEMAllocatorFunctions, 0x0, alloc);\nCHECK_OFFSET(MEMAllocatorFunctions, 0x4, free);\nCHECK_SIZE(MEMAllocatorFunctions, 0x8);\n\nstruct MEMAllocator\n{\n   be2_virt_ptr<MEMAllocatorFunctions> funcs;\n   be2_val<MEMHeapHandle> heap;\n   be2_val<int32_t> align;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(MEMAllocator, 0x0, funcs);\nCHECK_OFFSET(MEMAllocator, 0x4, heap);\nCHECK_OFFSET(MEMAllocator, 0x8, align);\nCHECK_SIZE(MEMAllocator, 0x10);\n\nvoid\nMEMInitAllocatorForDefaultHeap(virt_ptr<MEMAllocator> allocator);\n\nvoid\nMEMInitAllocatorForBlockHeap(virt_ptr<MEMAllocator> allocator,\n                             MEMHeapHandle handle,\n                             int32_t alignment);\n\nvoid\nMEMInitAllocatorForExpHeap(virt_ptr<MEMAllocator> allocator,\n                           MEMHeapHandle handle,\n                           int32_t alignment);\n\nvoid\nMEMInitAllocatorForFrmHeap(virt_ptr<MEMAllocator> allocator,\n                           MEMHeapHandle handle,\n                           int32_t alignment);\n\nvoid\nMEMInitAllocatorForUnitHeap(virt_ptr<MEMAllocator> allocator,\n                            MEMHeapHandle handle);\n\nvirt_ptr<void>\nMEMAllocFromAllocator(virt_ptr<MEMAllocator> allocator,\n                      uint32_t size);\n\nvoid\nMEMFreeToAllocator(virt_ptr<MEMAllocator> allocator,\n                   virt_ptr<void> block);\n\nnamespace internal\n{\n\nvoid\ninitialiseAllocatorStaticData();\n\n}\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memblockheap.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_memheap.h\"\n#include \"coreinit_memblockheap.h\"\n#include \"coreinit_memory.h\"\n\n#include <common/align.h>\n#include <common/log.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * Initialise block heap.\n */\nMEMHeapHandle\nMEMInitBlockHeap(virt_ptr<void> base,\n                 virt_ptr<void> start,\n                 virt_ptr<void> end,\n                 virt_ptr<MEMBlockHeapTracking> tracking,\n                 uint32_t size,\n                 uint32_t flags)\n{\n   auto heap = virt_cast<MEMBlockHeap *>(base);\n   if (!heap || !start || !end || start >= end) {\n      return nullptr;\n   }\n\n   decaf_check(base);\n   auto dataStart = virt_cast<uint8_t *>(start);\n   auto dataEnd = virt_cast<uint8_t *>(end);\n\n   // Register heap\n   internal::registerHeap(virt_addrof(heap->header),\n                          MEMHeapTag::BlockHeap,\n                          dataStart,\n                          dataEnd,\n                          static_cast<MEMHeapFlags>(flags));\n\n   // Setup default tracker\n   heap->defaultTrack.blockCount = 1u;\n   heap->defaultTrack.blocks = virt_addrof(heap->defaultBlock);\n\n   // Setup default block\n   heap->defaultBlock.start = dataStart;\n   heap->defaultBlock.end = dataEnd;\n   heap->defaultBlock.isFree = TRUE;\n   heap->defaultBlock.next = nullptr;\n   heap->defaultBlock.prev = nullptr;\n\n   // Add default block to block list\n   heap->firstBlock = virt_addrof(heap->defaultBlock);\n   heap->lastBlock = virt_addrof(heap->defaultBlock);\n\n   auto handle = virt_cast<MEMHeapHeader *>(heap);\n   MEMAddBlockHeapTracking(handle, tracking, size);\n   return handle;\n}\n\n\n/**\n * Destroy block heap.\n */\nvirt_ptr<void>\nMEMDestroyBlockHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMBlockHeap *>(handle);\n   if (!heap || heap->header.tag != MEMHeapTag::BlockHeap) {\n      return nullptr;\n   }\n\n   memset(heap, 0, sizeof(MEMBlockHeap));\n   internal::unregisterHeap(virt_addrof(heap->header));\n   return heap;\n}\n\n\n/**\n * Adds more tracking block memory to block heap.\n */\nint\nMEMAddBlockHeapTracking(MEMHeapHandle handle,\n                        virt_ptr<MEMBlockHeapTracking> tracking,\n                        uint32_t size)\n{\n   auto heap = virt_cast<MEMBlockHeap *>(handle);\n   if (!heap || !tracking || heap->header.tag != MEMHeapTag::BlockHeap) {\n      return -4;\n   }\n\n   // Size must be enough to contain at least 1 tracking structure and 1 block\n   if (size < sizeof(MEMBlockHeapTracking) + sizeof(MEMBlockHeapBlock)) {\n      return -4;\n   }\n\n   auto blockCount = static_cast<uint32_t>((size - sizeof(MEMBlockHeapTracking)) / sizeof(MEMBlockHeapBlock));\n   auto blocks = virt_cast<MEMBlockHeapBlock *>(tracking + 1);\n\n   // Setup tracking data\n   tracking->blockCount = blockCount;\n   tracking->blocks = blocks;\n\n   // Setup block linked list\n   for (auto i = 0u; i < blockCount; ++i) {\n      auto &block = blocks[i];\n      block.prev = nullptr;\n      block.next = virt_addrof(blocks[i + 1]);\n   }\n\n   // Insert at start of block list\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   blocks[blockCount - 1].next = heap->firstFreeBlock;\n   heap->firstFreeBlock = blocks;\n   heap->numFreeBlocks += blockCount;\n\n   return 0;\n}\n\nstatic virt_ptr<MEMBlockHeapBlock>\nfindBlockOwning(virt_ptr<MEMBlockHeap> heap,\n                virt_ptr<void> data)\n{\n   auto addr = virt_cast<uint8_t *>(data);\n\n   if (addr < heap->header.dataStart) {\n      return nullptr;\n   }\n\n   if (addr >= heap->header.dataEnd) {\n      return nullptr;\n   }\n\n   auto distFromEnd = heap->header.dataEnd - addr;\n   auto distFromStart = addr - heap->header.dataStart;\n\n   if (distFromStart < distFromEnd) {\n      // Look forward from firstBlock\n      auto block = heap->firstBlock;\n\n      while (block) {\n         if (block->end > addr) {\n            return block;\n         }\n\n         block = block->next;\n      }\n   } else {\n      // Go backwards from lastBlock\n      auto block = heap->lastBlock;\n\n      while (block) {\n         if (block->start <= addr) {\n            return block;\n         }\n\n         block = block->prev;\n      }\n   }\n\n   return nullptr;\n}\n\nstatic bool\nallocInsideBlock(virt_ptr<MEMBlockHeap> heap,\n                 virt_ptr<MEMBlockHeapBlock> block,\n                 virt_ptr<uint8_t> start,\n                 uint32_t size)\n{\n   // Ensure we are actually inside this block\n   auto end = start + size;\n\n   if (size == 0 || end > block->end) {\n      return false;\n   }\n\n   // First lets check we have enough free blocks\n   auto needFreeBlocks = 0u;\n\n   if (start != block->start) {\n      needFreeBlocks++;\n   }\n\n   if (end != block->end) {\n      needFreeBlocks++;\n   }\n\n   if (heap->numFreeBlocks < needFreeBlocks) {\n      return false;\n   }\n\n   // Create free block for remaining memory at start of block\n   if (start != block->start) {\n      // Get a new free block\n      auto freeBlock = heap->firstFreeBlock;\n      heap->firstFreeBlock = freeBlock->next;\n      heap->numFreeBlocks--;\n\n      // Setup free block\n      freeBlock->start = block->start;\n      freeBlock->end = start;\n      freeBlock->isFree = TRUE;\n      freeBlock->prev = block->prev;\n      freeBlock->next = block;\n\n      if (freeBlock->prev) {\n         freeBlock->prev->next = freeBlock;\n      } else {\n         heap->firstBlock = freeBlock;\n      }\n\n      // Adjust current block\n      block->start = start;\n      block->prev = freeBlock;\n   }\n\n   // Create free block for remaining memory at end of block\n   if (end != block->end) {\n      // Get a new free block\n      auto freeBlock = heap->firstFreeBlock;\n      heap->firstFreeBlock = freeBlock->next;\n      heap->numFreeBlocks--;\n\n      // Setup free block\n      freeBlock->start = end;\n      freeBlock->end = block->end;\n      freeBlock->isFree = TRUE;\n      freeBlock->prev = block;\n      freeBlock->next = block->next;\n\n      if (block->next) {\n         block->next->prev = freeBlock;\n      } else {\n         heap->lastBlock = freeBlock;\n      }\n\n      // Adjust current block\n      block->end = end;\n      block->next = freeBlock;\n   }\n\n   // Set intial memory values\n   if (heap->header.flags & MEMHeapFlags::ZeroAllocated) {\n      memset(block->start, 0, size);\n   } else if (heap->header.flags & MEMHeapFlags::DebugMode) {\n      auto value = MEMGetFillValForHeap(MEMHeapFillType::Allocated);\n      memset(block->start, value, size);\n   }\n\n   // Set block to allocated and return success!\n   block->isFree = FALSE;\n   return true;\n}\n\n\n/**\n * Try to allocate from block heap at a specific address.\n */\nvirt_ptr<void>\nMEMAllocFromBlockHeapAt(MEMHeapHandle handle,\n                        virt_ptr<void> addr,\n                        uint32_t size)\n{\n   auto heap = virt_cast<MEMBlockHeap *>(handle);\n   if (!heap || !addr || !size || heap->header.tag != MEMHeapTag::BlockHeap) {\n      return nullptr;\n   }\n\n   if (!heap->firstFreeBlock) {\n      return nullptr;\n   }\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n\n   auto block = findBlockOwning(heap, addr);\n\n   if (!block) {\n      gLog->warn(\"MEMAllocFromBlockHeapAt: Could not find block containing addr 0x{:08X}\",\n                 addr);\n      return nullptr;\n   }\n\n   if (!block->isFree) {\n      gLog->warn(\"MEMAllocFromBlockHeapAt: Requested address is not free 0x{:08X}\",\n                 addr);\n      return nullptr;\n   }\n\n   if (!allocInsideBlock(heap, block, virt_cast<uint8_t *>(addr), size)) {\n      return nullptr;\n   }\n\n   return addr;\n}\n\n\n/**\n * Allocate from block heap.\n */\nvirt_ptr<void>\nMEMAllocFromBlockHeapEx(MEMHeapHandle handle,\n                        uint32_t size,\n                        int32_t align)\n{\n   auto heap = virt_cast<MEMBlockHeap *>(handle);\n   auto block = virt_ptr<MEMBlockHeapBlock> { nullptr };\n   auto alignedStart = virt_ptr<uint8_t> { nullptr };\n   auto result = virt_ptr<void> { nullptr };\n\n   if (!heap || !size || heap->header.tag != MEMHeapTag::BlockHeap) {\n      return nullptr;\n   }\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n\n   if (align == 0) {\n      align = 4;\n   }\n\n   if (align >= 0) {\n      // Find first free block with enough size\n      for (block = heap->firstBlock; block; block = block->next) {\n         if (block->isFree) {\n            alignedStart = align_up(block->start, align);\n\n            if (alignedStart + size < block->end) {\n               break;\n            }\n         }\n      }\n   } else {\n      // Find last free block with enough size\n      for (block = heap->lastBlock; block; block = block->prev) {\n         if (block->isFree) {\n            alignedStart = align_down(block->end - size, -align);\n\n            if (alignedStart >= block->start) {\n               break;\n            }\n         }\n      }\n   }\n\n   if (!block) {\n      gLog->warn(\"MEMAllocFromBlockHeapEx: Could not find free block size: 0x{:X} align: 0x{:X}, allocatable: 0x{:X} free: 0x{:X}\",\n                 size,\n                 align,\n                 MEMGetAllocatableSizeForBlockHeapEx(virt_cast<MEMHeapHeader *>(heap), align),\n                 MEMGetTotalFreeSizeForBlockHeap(virt_cast<MEMHeapHeader *>(heap)));\n   } else if (allocInsideBlock(heap, block, alignedStart, size)) {\n      result = alignedStart;\n   }\n\n   return result;\n}\n\n\n/**\n * Free memory back to block heap.\n */\nvoid\nMEMFreeToBlockHeap(MEMHeapHandle handle,\n                   virt_ptr<void> data)\n{\n   auto heap = virt_cast<MEMBlockHeap *>(handle);\n   if (!heap || !data || heap->header.tag != MEMHeapTag::BlockHeap) {\n      return;\n   }\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto block = findBlockOwning(heap, data);\n\n   if (!block) {\n      gLog->warn(\"MEMFreeToBlockHeap: Could not find block containing data 0x{:08X}\",\n                 data);\n      return;\n   }\n\n   if (block->isFree) {\n      gLog->warn(\"MEMFreeToBlockHeap: Tried to free an already free block\");\n      return;\n   }\n\n   if (block->start != data) {\n      gLog->warn(\"MEMFreeToBlockHeap: Tried to free block 0x{:08X} from middle 0x{:08X}\",\n                 block->start, data);\n      return;\n   }\n\n   if (heap->header.flags & MEMHeapFlags::DebugMode) {\n      auto fill = MEMGetFillValForHeap(MEMHeapFillType::Freed);\n      auto size = block->end - block->start;\n      std::memset(block->start.get(), fill, size);\n   }\n\n   // Merge with previous free block if possible\n   if (auto prev = block->prev) {\n      if (prev->isFree) {\n         prev->end = block->end;\n         prev->next = block->next;\n\n         if (auto next = prev->next) {\n            next->prev = prev;\n         } else {\n            heap->lastBlock = prev;\n         }\n\n         block->prev = nullptr;\n         block->next = heap->firstFreeBlock;\n         heap->numFreeBlocks++;\n         heap->firstFreeBlock = block;\n\n         block = prev;\n      }\n   }\n\n   block->isFree = TRUE;\n\n   // Merge with next free block if possible\n   if (auto next = block->next) {\n      if (next->isFree) {\n         block->end = next->end;\n         block->next = next->next;\n\n         if (next->next) {\n            next->next->prev = block;\n         } else {\n            heap->lastBlock = block;\n         }\n\n         next->next = heap->firstFreeBlock;\n         heap->firstFreeBlock = next;\n         heap->numFreeBlocks++;\n      }\n   }\n}\n\n\n/**\n * Find the largest possible allocatable size in block heap for an alignment.\n */\nuint32_t\nMEMGetAllocatableSizeForBlockHeapEx(MEMHeapHandle handle,\n                                    int32_t align)\n{\n   auto heap = virt_cast<MEMBlockHeap *>(handle);\n   if (!heap || heap->header.tag != MEMHeapTag::BlockHeap) {\n      return 0;\n   }\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto allocatableSize = 0u;\n\n   // Adjust align\n   if (align < 0) {\n      align = -align;\n   } else if (align == 0) {\n      align = 4;\n   }\n\n   for (auto block = heap->firstBlock; block; block = block->next) {\n      if (!block->isFree) {\n         continue;\n      }\n\n      // Align start address and check it is still inside block\n      auto startAddr = block->start;\n      auto endAddr = block->end;\n      auto alignedStart = align_up(startAddr, align);\n\n      if (alignedStart >= endAddr) {\n         continue;\n      }\n\n      // See if this block is largest free block so far\n      auto freeSize = static_cast<uint32_t>(endAddr - alignedStart);\n\n      if (freeSize > allocatableSize) {\n         allocatableSize = freeSize;\n      }\n   }\n\n   return allocatableSize;\n}\n\n\n/**\n * Return number of tracking blocks remaining in heap.\n */\nuint32_t\nMEMGetTrackingLeftInBlockHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMBlockHeap *>(handle);\n   if (!heap || heap->header.tag != MEMHeapTag::BlockHeap) {\n      return 0;\n   }\n\n   return heap->numFreeBlocks;\n}\n\n/**\n * Return total free size in the heap.\n */\nuint32_t\nMEMGetTotalFreeSizeForBlockHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMBlockHeap *>(handle);\n   if (!heap || heap->header.tag != MEMHeapTag::BlockHeap) {\n      return 0;\n   }\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto freeSize = 0u;\n\n   for (auto block = heap->firstBlock; block; block = block->next) {\n      if (!block->isFree) {\n         continue;\n      }\n\n      auto startAddr = block->start;\n      auto endAddr = block->end;\n      freeSize += static_cast<uint32_t>(endAddr - startAddr);\n   }\n\n   return freeSize;\n}\n\nvoid\nLibrary::registerMemBlockHeapSymbols()\n{\n   RegisterFunctionExport(MEMInitBlockHeap);\n   RegisterFunctionExport(MEMDestroyBlockHeap);\n   RegisterFunctionExport(MEMAddBlockHeapTracking);\n   RegisterFunctionExport(MEMAllocFromBlockHeapAt);\n   RegisterFunctionExport(MEMAllocFromBlockHeapEx);\n   RegisterFunctionExport(MEMFreeToBlockHeap);\n   RegisterFunctionExport(MEMGetAllocatableSizeForBlockHeapEx);\n   RegisterFunctionExport(MEMGetTrackingLeftInBlockHeap);\n   RegisterFunctionExport(MEMGetTotalFreeSizeForBlockHeap);\n}\n\n} // cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memblockheap.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_memheap.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_blockheap Block Heap\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct MEMBlockHeapBlock;\n\nstruct MEMBlockHeapTracking\n{\n   UNKNOWN(0x8);\n\n   //! Pointer to first memory block\n   be2_virt_ptr<MEMBlockHeapBlock> blocks;\n\n   //! Number of blocks in this tracking heap\n   be2_val<uint32_t> blockCount;\n};\nCHECK_OFFSET(MEMBlockHeapTracking, 0x08, blocks);\nCHECK_OFFSET(MEMBlockHeapTracking, 0x0C, blockCount);\nCHECK_SIZE(MEMBlockHeapTracking, 0x10);\n\nstruct MEMBlockHeapBlock\n{\n   //! First address of the data region this block has allocated\n   be2_virt_ptr<uint8_t> start;\n\n   //! End address of the data region this block has allocated\n   be2_virt_ptr<uint8_t> end;\n\n   //! TRUE if the block is free, FALSE if allocated\n   be2_val<BOOL> isFree;\n\n   //! Link to previous block, note that this is only set for allocated blocks\n   be2_virt_ptr<MEMBlockHeapBlock> prev;\n\n   //! Link to next block, always set\n   be2_virt_ptr<MEMBlockHeapBlock> next;\n};\nCHECK_OFFSET(MEMBlockHeapBlock, 0x00, start);\nCHECK_OFFSET(MEMBlockHeapBlock, 0x04, end);\nCHECK_OFFSET(MEMBlockHeapBlock, 0x08, isFree);\nCHECK_OFFSET(MEMBlockHeapBlock, 0x0c, prev);\nCHECK_OFFSET(MEMBlockHeapBlock, 0x10, next);\nCHECK_SIZE(MEMBlockHeapBlock, 0x14);\n\nstruct MEMBlockHeap\n{\n   be2_struct<MEMHeapHeader> header;\n\n   //! Default tracking heap, tracks only defaultBlock\n   be2_struct<MEMBlockHeapTracking> defaultTrack;\n\n   //! Default block, used so we don't have an empty block list\n   be2_struct<MEMBlockHeapBlock> defaultBlock;\n\n   //! First block in this heap\n   be2_virt_ptr<MEMBlockHeapBlock> firstBlock;\n\n   //! Last block in this heap\n   be2_virt_ptr<MEMBlockHeapBlock> lastBlock;\n\n   //! First free block\n   be2_virt_ptr<MEMBlockHeapBlock> firstFreeBlock;\n\n   //! Free block count\n   be2_val<uint32_t> numFreeBlocks;\n};\nCHECK_OFFSET(MEMBlockHeap, 0x00, header);\nCHECK_OFFSET(MEMBlockHeap, 0x40, defaultTrack);\nCHECK_OFFSET(MEMBlockHeap, 0x50, defaultBlock);\nCHECK_OFFSET(MEMBlockHeap, 0x64, firstBlock);\nCHECK_OFFSET(MEMBlockHeap, 0x68, lastBlock);\nCHECK_OFFSET(MEMBlockHeap, 0x6C, firstFreeBlock);\nCHECK_OFFSET(MEMBlockHeap, 0x70, numFreeBlocks);\nCHECK_SIZE(MEMBlockHeap, 0x74);\n\n#pragma pack(pop)\n\nMEMHeapHandle\nMEMInitBlockHeap(virt_ptr<void> base,\n                 virt_ptr<void> start,\n                 virt_ptr<void> end,\n                 virt_ptr<MEMBlockHeapTracking> tracking,\n                 uint32_t size,\n                 uint32_t flags);\n\nvirt_ptr<void>\nMEMDestroyBlockHeap(MEMHeapHandle handle);\n\nint\nMEMAddBlockHeapTracking(MEMHeapHandle handle,\n                        virt_ptr<MEMBlockHeapTracking> tracking,\n                        uint32_t size);\n\nvirt_ptr<void>\nMEMAllocFromBlockHeapAt(MEMHeapHandle handle,\n                        virt_ptr<void> ptr,\n                        uint32_t size);\n\nvirt_ptr<void>\nMEMAllocFromBlockHeapEx(MEMHeapHandle handle,\n                        uint32_t size,\n                        int32_t align);\n\nvoid\nMEMFreeToBlockHeap(MEMHeapHandle handle,\n                   virt_ptr<void> data);\n\nuint32_t\nMEMGetAllocatableSizeForBlockHeapEx(MEMHeapHandle handle,\n                                    int32_t align);\n\nuint32_t\nMEMGetTrackingLeftInBlockHeap(MEMHeapHandle handle);\n\nuint32_t\nMEMGetTotalFreeSizeForBlockHeap(MEMHeapHandle handle);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memdefaultheap.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_dynload.h\"\n#include \"coreinit_memdefaultheap.h\"\n#include \"coreinit_memexpheap.h\"\n#include \"coreinit_memframeheap.h\"\n#include \"coreinit_memheap.h\"\n#include \"coreinit_memory.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\n#include <libcpu/state.h>\n\nnamespace cafe::coreinit\n{\n\nusing MEMAllocFromDefaultHeapFn = virt_func_ptr<virt_ptr<void>(uint32_t size)>;\nusing MEMAllocFromDefaultHeapExFn = virt_func_ptr<virt_ptr<void>(uint32_t size, int32_t align)>;\nusing MEMFreeToDefaultHeapFn = virt_func_ptr<void(virt_ptr<void>)>;\n\nstruct StaticDefaultHeapData\n{\n   be2_val<MEMHeapHandle> defaultHeapHandle;\n   be2_val<uint32_t> defaultHeapAllocCount;\n   be2_val<uint32_t> defaultHeapFreeCount;\n};\n\nstatic virt_ptr<StaticDefaultHeapData>\nsDefaultHeapData = nullptr;\n\nstatic virt_ptr<MEMAllocFromDefaultHeapFn> sMEMAllocFromDefaultHeap = nullptr;\nstatic virt_ptr<MEMAllocFromDefaultHeapExFn> sMEMAllocFromDefaultHeapEx = nullptr;\nstatic virt_ptr<MEMFreeToDefaultHeapFn> sMEMFreeToDefaultHeap = nullptr;\n\nstatic MEMAllocFromDefaultHeapFn sDefaultAllocFromDefaultHeap = nullptr;\nstatic MEMAllocFromDefaultHeapExFn sDefaultAllocFromDefaultHeapEx = nullptr;\nstatic MEMFreeToDefaultHeapFn sDefaultFreeToDefaultHeap = nullptr;\nstatic OSDynLoad_AllocFn sDefaultDynLoadAlloc = nullptr;\nstatic OSDynLoad_FreeFn sDefaultDynLoadFree = nullptr;\n\nstatic virt_ptr<void>\ndefaultAllocFromDefaultHeap(uint32_t size)\n{\n   return MEMAllocFromExpHeapEx(sDefaultHeapData->defaultHeapHandle,\n                                size,\n                                0x40u);\n}\n\nstatic virt_ptr<void>\ndefaultAllocFromDefaultHeapEx(uint32_t size,\n                              int32_t alignment)\n{\n   return MEMAllocFromExpHeapEx(sDefaultHeapData->defaultHeapHandle,\n                                size,\n                                alignment);\n}\n\nstatic void\ndefaultFreeToDefaultHeap(virt_ptr<void> block)\n{\n   return MEMFreeToExpHeap(sDefaultHeapData->defaultHeapHandle,\n                           block);\n}\n\nstatic OSDynLoad_Error\ndefaultDynLoadAlloc(int32_t size,\n                    int32_t align,\n                    virt_ptr<virt_ptr<void>> outPtr)\n{\n   if (!outPtr) {\n      return OSDynLoad_Error::InvalidAllocatorPtr;\n   }\n\n   if (align >= 0 && align < 4) {\n      align = 4;\n   } else if (align < 0 && align > -4) {\n      align = -4;\n   }\n\n   auto ptr = MEMAllocFromDefaultHeapEx(size, align);\n   *outPtr = ptr;\n\n   if (!ptr) {\n      return OSDynLoad_Error::OutOfMemory;\n   }\n\n   return OSDynLoad_Error::OK;\n}\n\nstatic void\ndefaultDynLoadFree(virt_ptr<void> ptr)\n{\n   MEMFreeToDefaultHeap(ptr);\n}\n\nvoid\nCoreInitDefaultHeap(virt_ptr<MEMHeapHandle> outHeapHandleMEM1,\n                    virt_ptr<MEMHeapHandle> outHeapHandleFG,\n                    virt_ptr<MEMHeapHandle> outHeapHandleMEM2)\n{\n   auto addr = StackObject<virt_addr> { };\n   auto size = StackObject<uint32_t> { };\n\n   *sMEMAllocFromDefaultHeap = sDefaultAllocFromDefaultHeap;\n   *sMEMAllocFromDefaultHeapEx = sDefaultAllocFromDefaultHeapEx;\n   *sMEMFreeToDefaultHeap = sDefaultFreeToDefaultHeap;\n\n   sDefaultHeapData->defaultHeapAllocCount = 0u;\n   sDefaultHeapData->defaultHeapFreeCount = 0u;\n\n   *outHeapHandleMEM1 = nullptr;\n   *outHeapHandleFG = nullptr;\n   *outHeapHandleMEM2 = nullptr;\n\n   if (OSGetForegroundBucket(nullptr, nullptr)) {\n      OSGetMemBound(OSMemoryType::MEM1, addr, size);\n      *outHeapHandleMEM1 = MEMCreateFrmHeapEx(virt_cast<void *>(*addr), *size, 0);\n\n      OSGetForegroundBucketFreeArea(addr, size);\n      *outHeapHandleFG = MEMCreateFrmHeapEx(virt_cast<void *>(*addr), *size, 0);\n   }\n\n   OSGetMemBound(OSMemoryType::MEM2, addr, size);\n   sDefaultHeapData->defaultHeapHandle =\n      MEMCreateExpHeapEx(virt_cast<void *>(*addr),\n                         *size,\n                         MEMHeapFlags::ThreadSafe);\n   *outHeapHandleMEM2 = sDefaultHeapData->defaultHeapHandle;\n\n   OSDynLoad_SetAllocator(sDefaultDynLoadAlloc,\n                          sDefaultDynLoadFree);\n\n   OSDynLoad_SetTLSAllocator(sDefaultDynLoadAlloc,\n                             sDefaultDynLoadFree);\n}\n\nvirt_ptr<void>\nMEMAllocFromDefaultHeap(uint32_t size)\n{\n   return cafe::invoke(cpu::this_core::state(),\n                       *sMEMAllocFromDefaultHeap,\n                       size);\n\n}\n\nvirt_ptr<void>\nMEMAllocFromDefaultHeapEx(uint32_t size,\n                          int32_t align)\n{\n   return cafe::invoke(cpu::this_core::state(),\n                       *sMEMAllocFromDefaultHeapEx,\n                       size,\n                       align);\n\n}\n\nvoid\nMEMFreeToDefaultHeap(virt_ptr<void> ptr)\n{\n   return cafe::invoke(cpu::this_core::state(),\n                       *sMEMFreeToDefaultHeap,\n                       ptr);\n\n}\n\nvoid\nLibrary::registerMemDefaultHeapSymbols()\n{\n   RegisterFunctionExport(CoreInitDefaultHeap);\n   RegisterDataExportName(\"MEMAllocFromDefaultHeap\", sMEMAllocFromDefaultHeap);\n   RegisterDataExportName(\"MEMAllocFromDefaultHeapEx\", sMEMAllocFromDefaultHeapEx);\n   RegisterDataExportName(\"MEMFreeToDefaultHeap\", sMEMFreeToDefaultHeap);\n\n   RegisterDataInternal(sDefaultHeapData);\n   RegisterFunctionInternal(defaultAllocFromDefaultHeap, sDefaultAllocFromDefaultHeap);\n   RegisterFunctionInternal(defaultAllocFromDefaultHeapEx, sDefaultAllocFromDefaultHeapEx);\n   RegisterFunctionInternal(defaultFreeToDefaultHeap, sDefaultFreeToDefaultHeap);\n   RegisterFunctionInternal(defaultDynLoadAlloc, sDefaultDynLoadAlloc);\n   RegisterFunctionInternal(defaultDynLoadFree, sDefaultDynLoadFree);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memdefaultheap.h",
    "content": "#pragma once\n#include \"coreinit_memheap.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nvoid\nCoreInitDefaultHeap(virt_ptr<MEMHeapHandle> outHeapHandleMEM1,\n                    virt_ptr<MEMHeapHandle> outHeapHandleFG,\n                    virt_ptr<MEMHeapHandle> outHeapHandleMEM2);\n\nvirt_ptr<void>\nMEMAllocFromDefaultHeap(uint32_t size);\n\nvirt_ptr<void>\nMEMAllocFromDefaultHeapEx(uint32_t size,\n                          int32_t align);\n\nvoid\nMEMFreeToDefaultHeap(virt_ptr<void> ptr);\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memexpheap.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_memexpheap.h\"\n#include \"coreinit_memory.h\"\n\n#include <common/log.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nstatic constexpr auto\nFreeTag = uint16_t { 0x4654 }; // 'FR'\n\nstatic constexpr auto\nUsedTag = uint16_t { 0x5544 }; // 'UD'\n\nstatic virt_ptr<uint8_t>\ngetBlockMemStart(virt_ptr<MEMExpHeapBlock> block)\n{\n   auto attribs = block->attribs.value();\n   return virt_cast<uint8_t *>(block) - attribs.alignment();\n}\n\nstatic virt_ptr<uint8_t>\ngetBlockMemEnd(virt_ptr<MEMExpHeapBlock> block)\n{\n   return virt_cast<uint8_t *>(block) + sizeof(MEMExpHeapBlock) + block->blockSize;\n}\n\nstatic virt_ptr<uint8_t>\ngetBlockDataStart(virt_ptr<MEMExpHeapBlock> block)\n{\n   return virt_cast<uint8_t *>(block) + sizeof(MEMExpHeapBlock);\n}\n\nstatic virt_ptr<MEMExpHeapBlock>\ngetUsedMemBlock(virt_ptr<void> mem)\n{\n   auto block = virt_cast<MEMExpHeapBlock *>(mem) - 1;\n   decaf_check(block->tag == UsedTag);\n   return block;\n}\n\nstatic bool\nlistContainsBlock(virt_ptr<MEMExpHeapBlockList> list,\n                  virt_ptr<MEMExpHeapBlock> block)\n{\n   for (auto i = list->head; i; i = i->next) {\n      if (i == block) {\n         return true;\n      }\n   }\n\n   return false;\n}\n\nstatic void\ninsertBlock(virt_ptr<MEMExpHeapBlockList> list,\n            virt_ptr<MEMExpHeapBlock> prev,\n            virt_ptr<MEMExpHeapBlock> block)\n{\n   decaf_check(!block->prev);\n   decaf_check(!block->next);\n\n   if (!prev) {\n      block->next = list->head;\n      block->prev = nullptr;\n\n      list->head = block;\n   } else {\n      block->next = prev->next;\n      block->prev = prev;\n\n      prev->next = block;\n   }\n\n   if (block->next) {\n      block->next->prev = block;\n   } else {\n      list->tail = block;\n   }\n}\n\nstatic void\nremoveBlock(virt_ptr<MEMExpHeapBlockList> list,\n            virt_ptr<MEMExpHeapBlock> block)\n{\n   decaf_check(listContainsBlock(list, block));\n\n   if (block->prev) {\n      block->prev->next = block->next;\n   } else {\n      list->head = block->next;\n   }\n\n   if (block->next) {\n      block->next->prev = block->prev;\n   } else {\n      list->tail = block->prev;\n   }\n\n   block->prev = nullptr;\n   block->next = nullptr;\n}\n\nstatic uint32_t\ngetAlignedBlockSize(virt_ptr<MEMExpHeapBlock> block,\n                    uint32_t alignment,\n                    MEMExpHeapDirection dir)\n{\n   if (dir == MEMExpHeapDirection::FromStart) {\n      auto dataStart = virt_cast<uint8_t *>(block) + sizeof(MEMExpHeapBlock);\n      auto dataEnd = dataStart + block->blockSize;\n      auto alignedDataStart = align_up(dataStart, alignment);\n\n      if (alignedDataStart >= dataEnd) {\n         return 0;\n      }\n\n      return static_cast<uint32_t>(dataEnd - alignedDataStart);\n   } else if (dir == MEMExpHeapDirection::FromEnd) {\n      auto dataStart = virt_cast<uint8_t *>(block) + sizeof(MEMExpHeapBlock);\n      auto dataEnd = dataStart + block->blockSize;\n      auto alignedDataEnd = align_down(dataEnd, alignment);\n\n      if (alignedDataEnd <= dataStart) {\n         return 0;\n      }\n\n      return static_cast<uint32_t>(alignedDataEnd - dataStart);\n   } else {\n      decaf_abort(\"Unexpected ExpHeap direction\");\n   }\n}\n\nstatic virt_ptr<MEMExpHeapBlock>\ncreateUsedBlockFromFreeBlock(virt_ptr<MEMExpHeap> heap,\n                             virt_ptr<MEMExpHeapBlock> freeBlock,\n                             uint32_t size,\n                             uint32_t alignment,\n                             MEMExpHeapDirection dir)\n{\n   auto expHeapAttribs = heap->attribs.value();\n   auto freeBlockAttribs = freeBlock->attribs.value();\n\n   auto freeBlockPrev = freeBlock->prev;\n   auto freeMemStart = getBlockMemStart(freeBlock);\n   auto freeMemEnd = getBlockMemEnd(freeBlock);\n\n   // Free blocks should never have alignment...\n   decaf_check(!freeBlockAttribs.alignment());\n   removeBlock(virt_addrof(heap->freeList), freeBlock);\n\n   // Find where we are going to start\n   auto alignedDataStart = virt_ptr<uint8_t> { };\n\n   if (dir == MEMExpHeapDirection::FromStart) {\n      alignedDataStart = align_up(freeMemStart + sizeof(MEMExpHeapBlock), alignment);\n   } else if (dir == MEMExpHeapDirection::FromEnd) {\n      alignedDataStart = align_down(freeMemEnd - size, alignment);\n   } else {\n      decaf_abort(\"Unexpected ExpHeap direction\");\n   }\n\n   // Grab the block header pointer and validate everything is sane\n   auto alignedBlock = virt_cast<MEMExpHeapBlock *>(alignedDataStart) - 1;\n   decaf_check(alignedDataStart - sizeof(MEMExpHeapBlock) >= freeMemStart);\n   decaf_check(alignedDataStart + size <= freeMemEnd);\n\n   // Calculate the alignment waste\n   auto topSpaceRemain = (alignedDataStart - freeMemStart) - sizeof(MEMExpHeapBlock);\n   auto bottomSpaceRemain = static_cast<uint32_t>((freeMemEnd - alignedDataStart) - size);\n\n   if (expHeapAttribs.reuseAlignSpace() || dir == MEMExpHeapDirection::FromEnd) {\n      // If the user wants to reuse the alignment space, or we allocated from the bottom,\n      //  we should try to release the top space back to the heap free list.\n      if (topSpaceRemain > sizeof(MEMExpHeapBlock) + 4) {\n         // We have enough room to put some of the memory back to the free list\n         freeBlock = virt_cast<MEMExpHeapBlock *>(freeMemStart);\n         freeBlock->attribs = MEMExpHeapBlockAttribs::get(0);\n         freeBlock->blockSize = static_cast<uint32_t>(topSpaceRemain - sizeof(MEMExpHeapBlock));\n         freeBlock->next = nullptr;\n         freeBlock->prev = nullptr;\n         freeBlock->tag = FreeTag;\n\n         insertBlock(virt_addrof(heap->freeList), freeBlockPrev, freeBlock);\n         topSpaceRemain = 0;\n      }\n   }\n\n   if (expHeapAttribs.reuseAlignSpace() || dir == MEMExpHeapDirection::FromStart) {\n      // If the user wants to reuse the alignment space, or we allocated from the top,\n      //  we should try to release the bottom space back to the heap free list.\n      if (bottomSpaceRemain > sizeof(MEMExpHeapBlock) + 4) {\n         // We have enough room to put some of the memory back to the free list\n         freeBlock = virt_cast<MEMExpHeapBlock *>(freeMemEnd - bottomSpaceRemain);\n         freeBlock->attribs = MEMExpHeapBlockAttribs::get(0);\n         freeBlock->blockSize = static_cast<uint32_t>(bottomSpaceRemain - sizeof(MEMExpHeapBlock));\n         freeBlock->next = nullptr;\n         freeBlock->prev = nullptr;\n         freeBlock->tag = FreeTag;\n\n         insertBlock(virt_addrof(heap->freeList), freeBlockPrev, freeBlock);\n         bottomSpaceRemain = 0;\n      }\n   }\n\n   // Update the structure with the new allocation\n   alignedBlock->attribs = MEMExpHeapBlockAttribs::get(0)\n      .alignment(static_cast<uint32_t>(topSpaceRemain))\n      .allocDir(dir);\n   alignedBlock->blockSize = size + bottomSpaceRemain;\n   alignedBlock->prev = nullptr;\n   alignedBlock->next = nullptr;\n   alignedBlock->tag = UsedTag;\n\n   insertBlock(virt_addrof(heap->usedList), nullptr, alignedBlock);\n\n   if (heap->header.flags & MEMHeapFlags::ZeroAllocated) {\n      memset(alignedDataStart, 0, size);\n   } else if (heap->header.flags & MEMHeapFlags::DebugMode) {\n      auto fillVal = MEMGetFillValForHeap(MEMHeapFillType::Allocated);\n      memset(alignedDataStart, fillVal, size);\n   }\n\n   return alignedBlock;\n}\n\nstatic void\nreleaseMemory(virt_ptr<MEMExpHeap> heap,\n              virt_ptr<uint8_t> memStart,\n              virt_ptr<uint8_t> memEnd)\n{\n   decaf_check(memEnd - memStart >= sizeof(MEMExpHeapBlock) + 4);\n\n   // Fill the released memory with debug data if needed\n   if (heap->header.flags & MEMHeapFlags::DebugMode) {\n      auto fillVal = MEMGetFillValForHeap(MEMHeapFillType::Freed);\n      std::memset(memStart.get(), fillVal, memEnd - memStart);\n   }\n\n   // Find the preceeding block to the memory we are releasing\n   virt_ptr<MEMExpHeapBlock> prevBlock = nullptr;\n   virt_ptr<MEMExpHeapBlock> nextBlock = heap->freeList.head;\n\n   for (auto block = heap->freeList.head; block; block = block->next) {\n      if (getBlockMemStart(block) < memStart) {\n         prevBlock = block;\n         nextBlock = block->next;\n      } else if (block >= prevBlock) {\n         break;\n      }\n   }\n\n   virt_ptr<MEMExpHeapBlock> freeBlock = nullptr;\n   if (prevBlock) {\n      // If there is a previous block, we need to check if we\n      //  should just steal that block rather than making one.\n      auto prevMemEnd = getBlockMemEnd(prevBlock);\n\n      if (memStart == prevMemEnd) {\n         // Previous block absorbs the new memory\n         prevBlock->blockSize += static_cast<uint32_t>(memEnd - memStart);\n\n         // Our free block becomes the previous one\n         freeBlock = prevBlock;\n      }\n   }\n\n   if (!freeBlock) {\n      // We did not steal the previous block to free into,\n      //  we need to allocate our own here.\n      freeBlock = virt_cast<MEMExpHeapBlock *>(memStart);\n      freeBlock->attribs = MEMExpHeapBlockAttribs::get(0);\n      freeBlock->blockSize = static_cast<uint32_t>((memEnd - memStart) - sizeof(MEMExpHeapBlock));\n      freeBlock->next = nullptr;\n      freeBlock->prev = nullptr;\n      freeBlock->tag = FreeTag;\n\n      insertBlock(virt_addrof(heap->freeList), prevBlock, freeBlock);\n   }\n\n   if (nextBlock) {\n      // If there is a next block, we need to possibly merge it down\n      //  into this one.\n      auto nextBlockStart = getBlockMemStart(nextBlock);\n\n      if (nextBlockStart == memEnd) {\n         // The next block needs to be merged into the freeBlock, as they\n         //  are directly adjacent to each other in memory.\n         auto nextBlockEnd = getBlockMemEnd(nextBlock);\n         freeBlock->blockSize += static_cast<uint32_t>(nextBlockEnd - nextBlockStart);\n\n         removeBlock(virt_addrof(heap->freeList), nextBlock);\n      }\n   }\n}\n\nMEMHeapHandle\nMEMCreateExpHeapEx(virt_ptr<void> base,\n                   uint32_t size,\n                   uint32_t flags)\n{\n   auto heapData = virt_cast<uint8_t *>(base);\n   auto alignedStart = align_up(heapData, 4);\n   auto alignedEnd = align_down(heapData + size, 4);\n\n   if (alignedEnd < alignedStart || alignedEnd - alignedStart < 0x6C) {\n      // Not enough room for the header\n      return nullptr;\n   }\n\n   decaf_check(base);\n\n   // Get our heap header\n   auto heap = virt_cast<MEMExpHeap *>(alignedStart);\n\n   // Register Heap\n   internal::registerHeap(virt_addrof(heap->header),\n                          MEMHeapTag::ExpandedHeap,\n                          alignedStart + sizeof(MEMExpHeap),\n                          alignedEnd,\n                          static_cast<MEMHeapFlags>(flags));\n\n   // Create an initial block of the data\n   auto dataStart = alignedStart + sizeof(MEMExpHeap);\n   auto firstBlock = virt_cast<MEMExpHeapBlock *>(dataStart);\n\n   firstBlock->attribs = MEMExpHeapBlockAttribs::get(0);\n   firstBlock->blockSize = static_cast<uint32_t>((alignedEnd - dataStart) - sizeof(MEMExpHeapBlock));\n   firstBlock->next = nullptr;\n   firstBlock->prev = nullptr;\n   firstBlock->tag = FreeTag;\n\n   heap->freeList.head = firstBlock;\n   heap->freeList.tail = firstBlock;\n   heap->usedList.head = nullptr;\n   heap->usedList.tail = nullptr;\n\n   heap->groupId = uint16_t { 0 };\n   heap->attribs = MEMExpHeapAttribs::get(0);\n\n   return virt_cast<MEMHeapHeader *>(heap);\n}\n\nvirt_ptr<void>\nMEMDestroyExpHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::ExpandedHeap);\n   internal::unregisterHeap(virt_addrof(heap->header));\n   return heap;\n}\n\nvirt_ptr<void>\nMEMAllocFromExpHeapEx(MEMHeapHandle handle,\n                      uint32_t size,\n                      int32_t alignment)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   decaf_check(heap->header.tag == MEMHeapTag::ExpandedHeap);\n   auto expHeapFlags = heap->attribs.value();\n\n   if (size == 0) {\n      size = 1;\n   }\n\n   decaf_check(alignment != 0);\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   virt_ptr<MEMExpHeapBlock> newBlock = nullptr;\n\n   size = align_up(size, 4);\n\n   if (alignment > 0) {\n      auto foundBlock = virt_ptr<MEMExpHeapBlock> { nullptr };\n      auto bestAlignedSize = 0xFFFFFFFFu;\n\n      alignment = std::max(4, alignment);\n      decaf_check((alignment & 0x3) == 0);\n\n      for (auto block = heap->freeList.head; block; block = block->next) {\n         auto alignedSize = getAlignedBlockSize(block,\n                                                alignment,\n                                                MEMExpHeapDirection::FromStart);\n\n         if (alignedSize >= size) {\n            if (expHeapFlags.allocMode() == MEMExpHeapMode::FirstFree) {\n               foundBlock = block;\n               break;\n            } else {\n               if (alignedSize < bestAlignedSize) {\n                  foundBlock = block;\n                  bestAlignedSize = alignedSize;\n               }\n            }\n         }\n      }\n\n      if (foundBlock) {\n         newBlock = createUsedBlockFromFreeBlock(heap,\n                                                 foundBlock,\n                                                 size,\n                                                 alignment,\n                                                 MEMExpHeapDirection::FromStart);\n      }\n   } else {\n      alignment = std::max(4, -alignment);\n      decaf_check((alignment & 0x3) == 0);\n\n      auto foundBlock = virt_ptr<MEMExpHeapBlock> { nullptr };\n      auto bestAlignedSize = 0xFFFFFFFFu;\n\n      for (auto block = heap->freeList.head; block; block = block->next) {\n         auto alignedSize = getAlignedBlockSize(block,\n                                                alignment,\n                                                MEMExpHeapDirection::FromEnd);\n\n         if (alignedSize >= size) {\n            if (expHeapFlags.allocMode() == MEMExpHeapMode::FirstFree) {\n               foundBlock = block;\n               break;\n            } else {\n               if (alignedSize < bestAlignedSize) {\n                  foundBlock = block;\n                  bestAlignedSize = alignedSize;\n               }\n            }\n         }\n      }\n\n      if (foundBlock) {\n         newBlock = createUsedBlockFromFreeBlock(heap,\n                                                 foundBlock,\n                                                 size,\n                                                 alignment,\n                                                 MEMExpHeapDirection::FromEnd);\n      }\n   }\n\n   if (!newBlock) {\n      MEMDumpHeap(virt_addrof(heap->header));\n      return nullptr;\n   }\n\n   return getBlockDataStart(newBlock);\n}\n\nvoid\nMEMFreeToExpHeap(MEMHeapHandle handle,\n                 virt_ptr<void> mem)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   decaf_check(heap->header.tag == MEMHeapTag::ExpandedHeap);\n\n   if (!mem) {\n      return;\n   }\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n\n   // Find the block\n   auto dataStart = virt_cast<uint8_t *>(mem);\n   auto block = virt_cast<MEMExpHeapBlock *>(dataStart - sizeof(MEMExpHeapBlock));\n\n   // Get the bounding region for this block\n   auto memStart = getBlockMemStart(block);\n   auto memEnd = getBlockMemEnd(block);\n\n   // Remove the block from the used list\n   removeBlock(virt_addrof(heap->usedList), block);\n\n   // Release the memory back to the heap free list\n   releaseMemory(heap, memStart, memEnd);\n}\n\nMEMExpHeapMode\nMEMSetAllocModeForExpHeap(MEMHeapHandle handle,\n                          MEMExpHeapMode mode)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   internal::HeapLock lock { virt_addrof(heap->header) };\n\n   auto expHeapAttribs = heap->attribs.value();\n   heap->attribs = expHeapAttribs.allocMode(mode);\n   return expHeapAttribs.allocMode();\n}\n\nMEMExpHeapMode\nMEMGetAllocModeForExpHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   internal::HeapLock lock { virt_addrof(heap->header) };\n\n   auto expHeapAttribs = heap->attribs.value();\n   return expHeapAttribs.allocMode();\n}\n\nuint32_t\nMEMAdjustExpHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto lastFreeBlock = heap->freeList.tail;\n\n   if (!lastFreeBlock) {\n      return 0;\n   }\n\n   auto blockData = virt_cast<uint8_t *>(lastFreeBlock) + sizeof(MEMExpHeapBlock);\n\n   if (blockData + lastFreeBlock->blockSize != heap->header.dataEnd) {\n      // This block is not for the end of the heap\n      return 0;\n   }\n\n   // Remove the block from the free list\n   decaf_check(!lastFreeBlock->next);\n\n   if (lastFreeBlock->prev) {\n      lastFreeBlock->prev->next = nullptr;\n   }\n\n   // Move the heaps end pointer to the true start point of this block\n   heap->header.dataEnd = getBlockMemStart(lastFreeBlock);\n\n   auto heapMemStart = virt_cast<uint8_t *>(heap);\n   auto heapMemEnd = virt_cast<uint8_t *>(heap->header.dataEnd);\n   return static_cast<uint32_t>(heapMemEnd - heapMemStart);\n}\n\nuint32_t\nMEMResizeForMBlockExpHeap(MEMHeapHandle handle,\n                          virt_ptr<void> ptr,\n                          uint32_t size)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   size = align_up(size, 4);\n\n   auto block = getUsedMemBlock(ptr);\n\n   if (size < block->blockSize) {\n      auto releasedSpace = block->blockSize - size;\n\n      if (releasedSpace > sizeof(MEMExpHeapBlock) + 0x4) {\n         auto releasedMemEnd = getBlockMemEnd(block);\n         auto releasedMemStart = releasedMemEnd - releasedSpace;\n\n         block->blockSize -= releasedSpace;\n         releaseMemory(heap, releasedMemStart, releasedMemEnd);\n      }\n   } else if (size > block->blockSize) {\n      auto blockMemEnd = getBlockMemEnd(block);\n      auto freeBlock = virt_ptr<MEMExpHeapBlock> { nullptr };\n\n      for (auto i = heap->freeList.head; i; i = i->next) {\n         auto freeBlockMemStart = getBlockMemStart(i);\n\n         if (freeBlockMemStart == blockMemEnd) {\n            freeBlock = i;\n            break;\n         }\n\n         // Free list is sorted, so we only need to search a little bit\n         if (freeBlockMemStart > blockMemEnd) {\n            break;\n         }\n      }\n\n      if (!freeBlock) {\n         return 0;\n      }\n\n      // Grab the data we need from the free block\n      auto freeBlockMemStart = getBlockMemStart(freeBlock);\n      auto freeBlockMemEnd = getBlockMemEnd(freeBlock);\n      auto freeMemSize = static_cast<uint32_t>(freeBlockMemEnd - freeBlockMemStart);\n\n      // Drop the free block from the list of free regions\n      removeBlock(virt_addrof(heap->freeList), freeBlock);\n\n      // Adjust the sizing of the free area and the block\n      auto newAllocSize = (size - block->blockSize);\n      freeMemSize -= newAllocSize;\n      block->blockSize = size;\n\n      if (heap->header.flags & MEMHeapFlags::ZeroAllocated) {\n         memset(freeBlockMemStart, 0, newAllocSize);\n      } else if(heap->header.flags & MEMHeapFlags::DebugMode) {\n         auto fillVal = MEMGetFillValForHeap(MEMHeapFillType::Allocated);\n         memset(freeBlockMemStart, fillVal, newAllocSize);\n      }\n\n      // If we have enough room to create a new free block, lets release\n      //  the memory back to the heap.  Otherwise we just tack the remainder\n      //  onto the end of the block we resized.\n      if (freeMemSize >= sizeof(MEMExpHeapBlock) + 0x4) {\n         releaseMemory(heap, freeBlockMemEnd - freeMemSize, freeBlockMemEnd);\n      } else {\n         block->blockSize += freeMemSize;\n      }\n   }\n\n   return block->blockSize;\n}\n\nuint32_t\nMEMGetTotalFreeSizeForExpHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   auto freeSize = 0u;\n   internal::HeapLock lock { virt_addrof(heap->header) };\n\n   for (auto block = heap->freeList.head; block; block = block->next) {\n      freeSize += block->blockSize;\n   }\n\n   return freeSize;\n}\n\nuint32_t\nMEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle handle,\n                                  int32_t alignment)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   auto largestFree = 0u;\n   internal::HeapLock lock { virt_addrof(heap->header) };\n\n   if (alignment > 0) {\n      decaf_check((alignment & 0x3) == 0);\n\n      for (auto block = heap->freeList.head; block; block = block->next) {\n         auto alignedSize = getAlignedBlockSize(block, alignment, MEMExpHeapDirection::FromStart);\n\n         if (alignedSize > largestFree) {\n            largestFree = alignedSize;\n         }\n      }\n   } else {\n      alignment = -alignment;\n\n      decaf_check((alignment & 0x3) == 0);\n\n      for (auto block = heap->freeList.head; block; block = block->next) {\n         auto alignedSize = getAlignedBlockSize(block, alignment, MEMExpHeapDirection::FromEnd);\n\n         if (alignedSize > largestFree) {\n            largestFree = alignedSize;\n         }\n      }\n   }\n\n   return largestFree;\n}\n\nuint16_t\nMEMSetGroupIDForExpHeap(MEMHeapHandle handle,\n                        uint16_t id)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto originalGroupId = heap->groupId;\n   heap->groupId = id;\n   return originalGroupId;\n}\n\nuint16_t\nMEMGetGroupIDForExpHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMExpHeap *>(handle);\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   return heap->groupId;\n}\n\nuint32_t\nMEMGetSizeForMBlockExpHeap(virt_ptr<void> ptr)\n{\n   auto addr = virt_cast<virt_addr>(ptr);\n   auto block = virt_cast<MEMExpHeapBlock *>(addr - sizeof(MEMExpHeapBlock));\n   return block->blockSize;\n}\n\nuint16_t\nMEMGetGroupIDForMBlockExpHeap(virt_ptr<void> ptr)\n{\n   auto addr = virt_cast<virt_addr>(ptr);\n   auto block = virt_cast<MEMExpHeapBlock *>(addr - sizeof(MEMExpHeapBlock));\n   return block->attribs.value().groupId();\n}\n\nMEMExpHeapDirection\nMEMGetAllocDirForMBlockExpHeap(virt_ptr<void> ptr)\n{\n   auto addr = virt_cast<virt_addr>(ptr);\n   auto block = virt_cast<MEMExpHeapBlock *>(addr - sizeof(MEMExpHeapBlock));\n   return block->attribs.value().allocDir();\n}\n\nnamespace internal\n{\n\nvoid\ndumpExpandedHeap(virt_ptr<MEMExpHeap> heap)\n{\n   internal::HeapLock lock { virt_addrof(heap->header) };\n\n   gLog->debug(\"MEMExpHeap({})\", heap);\n   gLog->debug(\"Status Address   Size       Group\");\n\n   for (auto block = heap->freeList.head; block; block = block->next) {\n      auto attribs = static_cast<MEMExpHeapBlockAttribs>(block->attribs);\n\n      gLog->debug(\"FREE  {} 0x{:8x} {:d}\",\n                  block,\n                  static_cast<uint32_t>(block->blockSize),\n                  attribs.groupId());\n   }\n\n   for (auto block = heap->usedList.head; block; block = block->next) {\n      auto attribs = static_cast<MEMExpHeapBlockAttribs>(block->attribs);\n\n      gLog->debug(\"USED  {} 0x{:8x} {:d}\",\n                  block,\n                  static_cast<uint32_t>(block->blockSize),\n                  attribs.groupId());\n   }\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerMemExpHeapSymbols()\n{\n   RegisterFunctionExport(MEMCreateExpHeapEx);\n   RegisterFunctionExport(MEMDestroyExpHeap);\n   RegisterFunctionExport(MEMAllocFromExpHeapEx);\n   RegisterFunctionExport(MEMFreeToExpHeap);\n   RegisterFunctionExport(MEMSetAllocModeForExpHeap);\n   RegisterFunctionExport(MEMGetAllocModeForExpHeap);\n   RegisterFunctionExport(MEMAdjustExpHeap);\n   RegisterFunctionExport(MEMResizeForMBlockExpHeap);\n   RegisterFunctionExport(MEMGetTotalFreeSizeForExpHeap);\n   RegisterFunctionExport(MEMGetAllocatableSizeForExpHeapEx);\n   RegisterFunctionExport(MEMSetGroupIDForExpHeap);\n   RegisterFunctionExport(MEMGetGroupIDForExpHeap);\n   RegisterFunctionExport(MEMGetSizeForMBlockExpHeap);\n   RegisterFunctionExport(MEMGetGroupIDForMBlockExpHeap);\n   RegisterFunctionExport(MEMGetAllocDirForMBlockExpHeap);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memexpheap.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_memheap.h\"\n\n#include <common/bitfield.h>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_expheap Expanded Heap\n * \\ingroup coreinit\n * @{\n */\n\nBITFIELD_BEG(MEMExpHeapAttribs, uint16_t)\n   BITFIELD_ENTRY(0, 1, MEMExpHeapMode, allocMode);\n   BITFIELD_ENTRY(1, 1, bool, reuseAlignSpace);\nBITFIELD_END\n\nBITFIELD_BEG(MEMExpHeapBlockAttribs, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, groupId);\n   BITFIELD_ENTRY(8, 23, uint32_t, alignment);\n   BITFIELD_ENTRY(31, 1, MEMExpHeapDirection, allocDir);\nBITFIELD_END\n\n#pragma pack(push, 1)\n\nstruct MEMExpHeapBlock\n{\n   be2_val<MEMExpHeapBlockAttribs> attribs;\n   be2_val<uint32_t> blockSize;\n   be2_virt_ptr<MEMExpHeapBlock> prev;\n   be2_virt_ptr<MEMExpHeapBlock> next;\n   be2_val<uint16_t> tag;\n   PADDING(0x02);\n};\nCHECK_OFFSET(MEMExpHeapBlock, 0x00, attribs);\nCHECK_OFFSET(MEMExpHeapBlock, 0x04, blockSize);\nCHECK_OFFSET(MEMExpHeapBlock, 0x08, prev);\nCHECK_OFFSET(MEMExpHeapBlock, 0x0c, next);\nCHECK_OFFSET(MEMExpHeapBlock, 0x10, tag);\nCHECK_SIZE(MEMExpHeapBlock, 0x14);\n\nstruct MEMExpHeapBlockList\n{\n   be2_virt_ptr<MEMExpHeapBlock> head;\n   be2_virt_ptr<MEMExpHeapBlock> tail;\n};\nCHECK_OFFSET(MEMExpHeapBlockList, 0x00, head);\nCHECK_OFFSET(MEMExpHeapBlockList, 0x04, tail);\nCHECK_SIZE(MEMExpHeapBlockList, 0x08);\n\nstruct MEMExpHeap\n{\n   be2_struct<MEMHeapHeader> header;\n   be2_struct<MEMExpHeapBlockList> freeList;\n   be2_struct<MEMExpHeapBlockList> usedList;\n   be2_val<uint16_t> groupId;\n   be2_val<MEMExpHeapAttribs> attribs;\n};\nCHECK_OFFSET(MEMExpHeap, 0x00, header);\nCHECK_OFFSET(MEMExpHeap, 0x40, freeList);\nCHECK_OFFSET(MEMExpHeap, 0x48, usedList);\nCHECK_OFFSET(MEMExpHeap, 0x50, groupId);\nCHECK_OFFSET(MEMExpHeap, 0x52, attribs);\nCHECK_SIZE(MEMExpHeap, 0x54);\n\n#pragma pack(pop)\n\nMEMHeapHandle\nMEMCreateExpHeapEx(virt_ptr<void> base,\n                   uint32_t size,\n                   uint32_t flags);\n\nvirt_ptr<void>\nMEMDestroyExpHeap(MEMHeapHandle handle);\n\nvirt_ptr<void>\nMEMAllocFromExpHeapEx(MEMHeapHandle handle,\n                      uint32_t size,\n                      int32_t alignment);\n\nvoid\nMEMFreeToExpHeap(MEMHeapHandle handle,\n                 virt_ptr<void> block);\n\nMEMExpHeapMode\nMEMSetAllocModeForExpHeap(MEMHeapHandle handle,\n                          MEMExpHeapMode mode);\n\nMEMExpHeapMode\nMEMGetAllocModeForExpHeap(MEMHeapHandle handle);\n\nuint32_t\nMEMAdjustExpHeap(MEMHeapHandle handle);\n\nuint32_t\nMEMResizeForMBlockExpHeap(MEMHeapHandle handle,\n                          virt_ptr<void> block,\n                          uint32_t size);\n\nuint32_t\nMEMGetTotalFreeSizeForExpHeap(MEMHeapHandle handle);\n\nuint32_t\nMEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle handle,\n                                  int32_t alignment);\n\nuint16_t\nMEMSetGroupIDForExpHeap(MEMHeapHandle handle,\n                        uint16_t id);\n\nuint16_t\nMEMGetGroupIDForExpHeap(MEMHeapHandle handle);\n\nuint32_t\nMEMGetSizeForMBlockExpHeap(virt_ptr<void> block);\n\nuint16_t\nMEMGetGroupIDForMBlockExpHeap(virt_ptr<void> block);\n\nMEMExpHeapDirection\nMEMGetAllocDirForMBlockExpHeap(virt_ptr<void> block);\n\nnamespace internal\n{\n\nvoid\ndumpExpandedHeap(virt_ptr<MEMExpHeap> handle);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memframeheap.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_memheap.h\"\n#include \"coreinit_memframeheap.h\"\n#include \"coreinit_memory.h\"\n\nnamespace cafe::coreinit\n{\n\nMEMHeapHandle\nMEMCreateFrmHeapEx(virt_ptr<void> base,\n                   uint32_t size,\n                   uint32_t flags)\n{\n   auto baseMem = virt_cast<uint8_t *>(base);\n\n   // Align start and end to 4 byte boundary\n   auto start = align_up(baseMem, 4);\n   auto end = align_down(baseMem + size, 4);\n\n   if (start >= end) {\n      return nullptr;\n   }\n\n   if (end - start < sizeof(MEMFrameHeap)) {\n      return nullptr;\n   }\n\n   decaf_check(base);\n\n   // Setup the frame heap\n   auto heap = virt_cast<MEMFrameHeap *>(start);\n\n   internal::registerHeap(virt_addrof(heap->header),\n                          MEMHeapTag::FrameHeap,\n                          start + sizeof(MEMFrameHeap),\n                          end,\n                          static_cast<MEMHeapFlags>(flags));\n\n   heap->head = heap->header.dataStart;\n   heap->tail = heap->header.dataEnd;\n   heap->previousState = nullptr;\n   return virt_cast<MEMHeapHeader *>(heap);\n}\n\nvirt_ptr<void>\nMEMDestroyFrmHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMFrameHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::FrameHeap);\n   internal::unregisterHeap(virt_addrof(heap->header));\n   return heap;\n}\n\nvirt_ptr<void>\nMEMAllocFromFrmHeapEx(MEMHeapHandle handle,\n                      uint32_t size,\n                      int alignment)\n{\n   auto heap = virt_cast<MEMFrameHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::FrameHeap);\n\n   // Yes coreinit.rpl actually does this\n   if (size == 0) {\n      size = 1;\n   }\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto block = virt_ptr<void> { nullptr };\n\n   if (alignment < 0) {\n      // Allocate from bottom\n      auto tail = align_down(heap->tail - size, -alignment);\n\n      if (tail < heap->head) {\n         // Not enough space!\n         return nullptr;\n      }\n\n      heap->tail = tail;\n      block = tail;\n   } else {\n      // Allocate from head\n      auto addr = align_up(heap->head, alignment);\n      auto head = addr + size;\n\n      if (head > heap->tail) {\n         // Not enough space!\n         return nullptr;\n      }\n\n      heap->head = head;\n      block = addr;\n   }\n\n   lock.unlock();\n\n   if (heap->header.flags & MEMHeapFlags::ZeroAllocated) {\n      memset(block, 0, size);\n   } else if (heap->header.flags & MEMHeapFlags::DebugMode) {\n      auto value = MEMGetFillValForHeap(MEMHeapFillType::Allocated);\n      memset(block, value, size);\n   }\n\n   return block;\n}\n\n\nvoid\nMEMFreeToFrmHeap(MEMHeapHandle handle,\n                 MEMFrameHeapFreeMode mode)\n{\n   auto heap = virt_cast<MEMFrameHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::FrameHeap);\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n\n   if (mode & MEMFrameHeapFreeMode::Head) {\n      if (heap->header.flags & MEMHeapFlags::DebugMode) {\n         auto value = MEMGetFillValForHeap(MEMHeapFillType::Freed);\n         std::memset(heap->header.dataStart.get(),\n                     value,\n                     heap->head - heap->header.dataStart);\n      }\n\n      heap->head = heap->header.dataStart;\n      heap->previousState = nullptr;\n   }\n\n   if (mode & MEMFrameHeapFreeMode::Tail) {\n      if (heap->header.flags & MEMHeapFlags::DebugMode) {\n         auto value = MEMGetFillValForHeap(MEMHeapFillType::Freed);\n         std::memset(heap->tail.get(),\n                     value,\n                     heap->header.dataEnd - heap->tail);\n      }\n\n      heap->tail = heap->header.dataEnd;\n      heap->previousState = nullptr;\n   }\n}\n\nBOOL\nMEMRecordStateForFrmHeap(MEMHeapHandle handle,\n                         uint32_t tag)\n{\n   auto result = FALSE;\n   auto heap = virt_cast<MEMFrameHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::FrameHeap);\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto state = virt_cast<MEMFrameHeapState *>(\n      MEMAllocFromFrmHeapEx(handle, sizeof(MEMFrameHeapState), 4));\n\n   if (state) {\n      state->tag = tag;\n      state->head = heap->head;\n      state->tail = heap->tail;\n      state->previous = heap->previousState;\n      heap->previousState = state;\n\n      result = TRUE;\n   }\n\n   return result;\n}\n\nBOOL\nMEMFreeByStateToFrmHeap(MEMHeapHandle handle,\n                        uint32_t tag)\n{\n   auto result = FALSE;\n   auto heap = virt_cast<MEMFrameHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::FrameHeap);\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n\n   // Find the state to reset to\n   auto state = heap->previousState;\n\n   if (tag != 0) {\n      while (state) {\n         if (state->tag == tag) {\n            break;\n         }\n\n         state = state->previous;\n      }\n   }\n\n   // Reset to state\n   if (state) {\n      if (heap->header.flags & MEMHeapFlags::DebugMode) {\n         auto value = MEMGetFillValForHeap(MEMHeapFillType::Freed);\n         std::memset(state->head.get(), value, heap->head - state->head);\n         std::memset(heap->tail.get(), value, state->tail - heap->tail);\n      }\n\n      heap->head = state->head;\n      heap->tail = state->tail;\n      heap->previousState = state->previous;\n      result = TRUE;\n   }\n\n   return result;\n}\n\nuint32_t\nMEMAdjustFrmHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMFrameHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::FrameHeap);\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto result = 0u;\n\n   // We can only adjust the heap if we have no tail allocated memory\n   if (heap->tail == heap->header.dataEnd) {\n      heap->header.dataEnd = heap->head;\n      heap->tail = heap->head;\n\n      auto heapMemStart = virt_cast<uint8_t *>(heap);\n      result = static_cast<uint32_t>(heap->header.dataEnd - heapMemStart);\n   }\n\n   return result;\n}\n\nuint32_t\nMEMResizeForMBlockFrmHeap(MEMHeapHandle handle,\n                          virt_ptr<void> address,\n                          uint32_t size)\n{\n   auto heap = virt_cast<MEMFrameHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::FrameHeap);\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto result = 0u;\n\n   decaf_check(address > heap->head);\n   decaf_check(address < heap->tail);\n   decaf_check(heap->previousState == nullptr || heap->previousState < address);\n\n   if (size == 0) {\n      size = 1;\n   }\n\n   auto addrMem = virt_cast<uint8_t *>(address);\n   auto end = align_up(addrMem + size, 4);\n\n   if (end > heap->tail) {\n      // Not enough free space\n      result = 0;\n   } else if (end == heap->head) {\n      // Same size\n      result = size;\n   } else if (end < heap->head) {\n      // Decrease size\n      if (heap->header.flags & MEMHeapFlags::DebugMode) {\n         auto value = MEMGetFillValForHeap(MEMHeapFillType::Freed);\n         std::memset(end.get(), value, heap->head - addrMem);\n      }\n\n      heap->head = end;\n      result = size;\n   } else if (end > heap->head) {\n      // Increase size\n      if (heap->header.flags & MEMHeapFlags::ZeroAllocated) {\n         std::memset(heap->head.get(), 0, addrMem - heap->head);\n      } else if (heap->header.flags & MEMHeapFlags::DebugMode) {\n         auto value = MEMGetFillValForHeap(MEMHeapFillType::Allocated);\n         std::memset(heap->head.get(), value, addrMem - heap->head);\n      }\n\n      heap->head = end;\n      result = size;\n   }\n\n   return result;\n}\n\nuint32_t\nMEMGetAllocatableSizeForFrmHeapEx(MEMHeapHandle handle,\n                                  int alignment)\n{\n   auto heap = virt_cast<MEMFrameHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::FrameHeap);\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto alignedHead = align_up(heap->head, alignment);\n   auto result = 0u;\n\n   if (alignedHead < heap->tail) {\n      result = static_cast<uint32_t>(heap->tail - alignedHead);\n   }\n\n   return result;\n}\n\nvoid\nLibrary::registerMemFrmHeapSymbols()\n{\n   RegisterFunctionExport(MEMCreateFrmHeapEx);\n   RegisterFunctionExport(MEMDestroyFrmHeap);\n   RegisterFunctionExport(MEMAllocFromFrmHeapEx);\n   RegisterFunctionExport(MEMFreeToFrmHeap);\n   RegisterFunctionExport(MEMRecordStateForFrmHeap);\n   RegisterFunctionExport(MEMFreeByStateToFrmHeap);\n   RegisterFunctionExport(MEMAdjustFrmHeap);\n   RegisterFunctionExport(MEMResizeForMBlockFrmHeap);\n   RegisterFunctionExport(MEMGetAllocatableSizeForFrmHeapEx);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memframeheap.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_memheap.h\"\n\n#include <libcpu/be2_val.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_frameheap Frame Heap\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct MEMFrameHeapState\n{\n   //! Tag used to identify the state for MEMFreeByStateToFrmHeap.\n   be2_val<uint32_t> tag;\n\n   //! Saved head address for frame heap.\n   be2_virt_ptr<uint8_t> head;\n\n   //! Saved tail address for frame heap.\n   be2_virt_ptr<uint8_t> tail;\n\n   //! Pointer to the previous recorded frame heap state.\n   be2_virt_ptr<MEMFrameHeapState> previous;\n};\nCHECK_OFFSET(MEMFrameHeapState, 0x00, tag);\nCHECK_OFFSET(MEMFrameHeapState, 0x04, head);\nCHECK_OFFSET(MEMFrameHeapState, 0x08, tail);\nCHECK_OFFSET(MEMFrameHeapState, 0x0C, previous);\nCHECK_SIZE(MEMFrameHeapState, 0x10);\n\nstruct MEMFrameHeap\n{\n   be2_struct<MEMHeapHeader> header;\n\n   //! Current address of the head of the frame heap.\n   be2_virt_ptr<uint8_t> head;\n\n   //! Current address of the tail of the frame heap.\n   be2_virt_ptr<uint8_t> tail;\n\n   //! Pointer to the previous recorded frame heap state.\n   be2_virt_ptr<MEMFrameHeapState> previousState;\n};\nCHECK_OFFSET(MEMFrameHeap, 0x00, header);\nCHECK_OFFSET(MEMFrameHeap, 0x40, head);\nCHECK_OFFSET(MEMFrameHeap, 0x44, tail);\nCHECK_OFFSET(MEMFrameHeap, 0x48, previousState);\nCHECK_SIZE(MEMFrameHeap, 0x4C);\n\n#pragma pack(pop)\n\nMEMHeapHandle\nMEMCreateFrmHeapEx(virt_ptr<void> base,\n                   uint32_t size,\n                   uint32_t flags);\n\nvirt_ptr<void>\nMEMDestroyFrmHeap(MEMHeapHandle heap);\n\nvirt_ptr<void>\nMEMAllocFromFrmHeapEx(MEMHeapHandle heap,\n                      uint32_t size,\n                      int alignment);\n\nvoid\nMEMFreeToFrmHeap(MEMHeapHandle heap,\n                 MEMFrameHeapFreeMode mode);\n\nBOOL\nMEMRecordStateForFrmHeap(MEMHeapHandle heap,\n                         uint32_t tag);\n\nBOOL\nMEMFreeByStateToFrmHeap(MEMHeapHandle heap,\n                        uint32_t tag);\n\nuint32_t\nMEMAdjustFrmHeap(MEMHeapHandle heap);\n\nuint32_t\nMEMResizeForMBlockFrmHeap(MEMHeapHandle heap,\n                          virt_ptr<void> address,\n                          uint32_t size);\n\nuint32_t\nMEMGetAllocatableSizeForFrmHeapEx(MEMHeapHandle heap,\n                                  int alignment);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memheap.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_memexpheap.h\"\n#include \"coreinit_memheap.h\"\n#include \"coreinit_memlist.h\"\n#include \"coreinit_memory.h\"\n#include \"coreinit_memunitheap.h\"\n#include \"coreinit_spinlock.h\"\n#include \"cafe/cafe_stackobject.h\"\n\n#include <algorithm>\n#include <array>\n#include <common/log.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nstruct StaticMemHeapData\n{\n   be2_val<BOOL> initialisedLock;\n   be2_struct<OSSpinLock> lock;\n\n   be2_val<BOOL> initialisedLists;\n   be2_struct<MEMList> foregroundList;\n   be2_struct<MEMList> mem1List;\n   be2_struct<MEMList> mem2List;\n\n   be2_array<MEMHeapHandle, MEMBaseHeapType::Max> arenas;\n   be2_array<uint32_t, MEMHeapFillType::Max> fillValues;\n};\n\nstatic virt_ptr<StaticMemHeapData>\nsMemHeapData = nullptr;\n\nstatic virt_ptr<MEMList>\nfindListContainingHeap(virt_ptr<MEMHeapHeader> heap)\n{\n   auto start = StackObject<virt_addr> { };\n   auto end = StackObject<virt_addr> { };\n   auto size = StackObject<uint32_t> { };\n   OSGetForegroundBucket(start, size);\n   *end = *start + *size;\n\n   if (virt_cast<virt_addr>(heap->dataStart) >= *start &&\n       virt_cast<virt_addr>(heap->dataEnd) <= *end) {\n      return virt_addrof(sMemHeapData->foregroundList);\n   }\n\n   OSGetMemBound(OSMemoryType::MEM1, start, size);\n   *end = *start + *size;\n\n   if (virt_cast<virt_addr>(heap->dataStart) >= *start &&\n       virt_cast<virt_addr>(heap->dataEnd) <= *end) {\n      return virt_addrof(sMemHeapData->mem1List);\n   } else {\n      return virt_addrof(sMemHeapData->mem2List);\n   }\n}\n\nstatic virt_ptr<MEMList>\nfindListContainingBlock(virt_ptr<void> block)\n{\n   auto start = StackObject<virt_addr> { };\n   auto end = StackObject<virt_addr> { };\n   auto size = StackObject<uint32_t> { };\n   OSGetForegroundBucket(start, size);\n   *end = *start + *size;\n\n   if (virt_cast<virt_addr>(block) >= *start &&\n       virt_cast<virt_addr>(block) <= *end) {\n      return virt_addrof(sMemHeapData->foregroundList);\n   }\n\n   OSGetMemBound(OSMemoryType::MEM1, start, size);\n   *end = *start + *size;\n\n   if (virt_cast<virt_addr>(block) >= *start &&\n       virt_cast<virt_addr>(block) <= *end) {\n      return virt_addrof(sMemHeapData->mem1List);\n   } else {\n      return virt_addrof(sMemHeapData->mem2List);\n   }\n}\n\nstatic virt_ptr<MEMHeapHeader>\nfindHeapContainingBlock(virt_ptr<MEMList> list,\n                        virt_ptr<void> block)\n{\n   virt_ptr<MEMHeapHeader> heap = nullptr;\n\n   while ((heap = virt_cast<MEMHeapHeader *>(MEMGetNextListObject(list, heap)))) {\n      if (virt_cast<virt_addr>(block) >= virt_cast<virt_addr>(heap->dataStart) &&\n          virt_cast<virt_addr>(block) < virt_cast<virt_addr>(heap->dataEnd)) {\n         auto child = findHeapContainingBlock(virt_addrof(heap->list), block);\n         return child ? child : heap;\n      }\n   }\n\n   return nullptr;\n}\n\nvoid\nMEMDumpHeap(virt_ptr<MEMHeapHeader> heap)\n{\n   switch (heap->tag) {\n   case MEMHeapTag::ExpandedHeap:\n      internal::dumpExpandedHeap(virt_cast<MEMExpHeap *>(heap));\n      break;\n   case MEMHeapTag::UnitHeap:\n      internal::dumpUnitHeap(virt_cast<MEMUnitHeap *>(heap));\n      break;\n   case MEMHeapTag::FrameHeap:\n   case MEMHeapTag::UserHeap:\n   case MEMHeapTag::BlockHeap:\n      gLog->warn(\"Unimplemented MEMDumpHeap for tag {:08x}\", heap->tag);\n   }\n}\n\nvirt_ptr<MEMHeapHeader>\nMEMFindContainHeap(virt_ptr<void> block)\n{\n   if (auto list = findListContainingBlock(block)) {\n      return findHeapContainingBlock(list, block);\n   }\n\n   return nullptr;\n}\n\nMEMBaseHeapType\nMEMGetArena(virt_ptr<MEMHeapHeader> heap)\n{\n   for (auto i = 0u; i < sMemHeapData->arenas.size(); ++i) {\n      if (sMemHeapData->arenas[i] == heap) {\n         return static_cast<MEMBaseHeapType>(i);\n      }\n   }\n\n   return MEMBaseHeapType::Invalid;\n}\n\nMEMHeapHandle\nMEMGetBaseHeapHandle(MEMBaseHeapType type)\n{\n   if (type < sMemHeapData->arenas.size()) {\n      return sMemHeapData->arenas[type];\n   } else {\n      return nullptr;\n   }\n}\n\nMEMHeapHandle\nMEMSetBaseHeapHandle(MEMBaseHeapType type,\n                     MEMHeapHandle heap)\n{\n   if (type < sMemHeapData->arenas.size()) {\n      auto previous = sMemHeapData->arenas[type];\n      sMemHeapData->arenas[type] = heap;\n      return previous;\n   } else {\n      return nullptr;\n   }\n}\n\nMEMHeapHandle\nMEMCreateUserHeapHandle(virt_ptr<MEMHeapHeader> heap,\n                        uint32_t size)\n{\n   auto dataStart = virt_cast<uint8_t *>(heap) + sizeof(MEMHeapHeader);\n   auto dataEnd = dataStart + size;\n\n   internal::registerHeap(heap,\n                          coreinit::MEMHeapTag::UserHeap,\n                          dataStart,\n                          dataEnd,\n                          MEMHeapFlags::None);\n\n   return heap;\n}\n\nuint32_t\nMEMGetFillValForHeap(MEMHeapFillType type)\n{\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(sMemHeapData->lock));\n   auto value = sMemHeapData->fillValues[type];\n   OSUninterruptibleSpinLock_Release(virt_addrof(sMemHeapData->lock));\n   return value;\n}\n\nvoid\nMEMSetFillValForHeap(MEMHeapFillType type,\n                     uint32_t value)\n{\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(sMemHeapData->lock));\n   sMemHeapData->fillValues[type] = value;\n   OSUninterruptibleSpinLock_Release(virt_addrof(sMemHeapData->lock));\n}\n\nnamespace internal\n{\n\nvoid\nregisterHeap(virt_ptr<MEMHeapHeader> heap,\n             MEMHeapTag tag,\n             virt_ptr<uint8_t> dataStart,\n             virt_ptr<uint8_t> dataEnd,\n             MEMHeapFlags flags)\n{\n   // Setup heap header\n   heap->tag = tag;\n   heap->dataStart = dataStart;\n   heap->dataEnd = dataEnd;\n   heap->flags = flags;\n\n   if (heap->flags & MEMHeapFlags::DebugMode) {\n      auto fillVal = MEMGetFillValForHeap(MEMHeapFillType::Unused);\n      std::memset(dataStart.get(), fillVal, dataEnd - dataStart);\n   }\n\n   MEMInitList(virt_addrof(heap->list), offsetof(MEMHeapHeader, link));\n\n   if (!sMemHeapData->initialisedLock) {\n      OSInitSpinLock(virt_addrof(sMemHeapData->lock));\n      sMemHeapData->initialisedLock = TRUE;\n   }\n\n   if (!sMemHeapData->initialisedLists) {\n      MEMInitList(virt_addrof(sMemHeapData->foregroundList), offsetof(MEMHeapHeader, link));\n      MEMInitList(virt_addrof(sMemHeapData->mem1List), offsetof(MEMHeapHeader, link));\n      MEMInitList(virt_addrof(sMemHeapData->mem2List), offsetof(MEMHeapHeader, link));\n      sMemHeapData->initialisedLists = TRUE;\n   }\n\n   OSInitSpinLock(virt_addrof(heap->lock));\n\n   // Add to heap list\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(sMemHeapData->lock));\n\n   if (auto list = findListContainingHeap(heap)) {\n      MEMAppendListObject(list, heap);\n   }\n\n   OSUninterruptibleSpinLock_Release(virt_addrof(sMemHeapData->lock));\n}\n\nvoid\nunregisterHeap(virt_ptr<MEMHeapHeader> heap)\n{\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(sMemHeapData->lock));\n\n   if (auto list = findListContainingHeap(heap)) {\n      MEMRemoveListObject(list, heap);\n   }\n\n   OSUninterruptibleSpinLock_Release(virt_addrof(sMemHeapData->lock));\n}\n\nvoid\ninitialiseMemHeap()\n{\n   OSInitSpinLock(virt_addrof(sMemHeapData->lock));\n   sMemHeapData->arenas.fill(nullptr);\n   sMemHeapData->fillValues[0] = 0xC3C3C3C3u;\n   sMemHeapData->fillValues[1] = 0xF3F3F3F3u;\n   sMemHeapData->fillValues[2] = 0xD3D3D3D3u;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerMemHeapSymbols()\n{\n   RegisterFunctionExport(MEMGetBaseHeapHandle);\n   RegisterFunctionExport(MEMSetBaseHeapHandle);\n   RegisterFunctionExport(MEMCreateUserHeapHandle);\n   RegisterFunctionExport(MEMGetArena);\n   RegisterFunctionExport(MEMFindContainHeap);\n   RegisterFunctionExport(MEMDumpHeap);\n   RegisterFunctionExport(MEMGetFillValForHeap);\n   RegisterFunctionExport(MEMSetFillValForHeap);\n\n   RegisterDataInternal(sMemHeapData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memheap.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_memlist.h\"\n#include \"coreinit_spinlock.h\"\n\n#include <common/bitfield.h>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_memheap Memory Heap\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct MEMHeapHeader\n{\n   //! Tag indicating which type of heap this is\n   be2_val<MEMHeapTag> tag;\n\n   //! Link for list this heap is in\n   be2_struct<MEMListLink> link;\n\n   //! List of all child heaps in this heap\n   be2_struct<MEMList> list;\n\n   //! Start address of allocatable memory\n   be2_virt_ptr<uint8_t> dataStart;\n\n   //! End address of allocatable memory\n   be2_virt_ptr<uint8_t> dataEnd;\n\n   //! Lock used when MEM_HEAP_FLAG_USE_LOCK is set.\n   be2_struct<OSSpinLock> lock;\n\n   //! Flags set during heap creation.\n   be2_val<MEMHeapFlags> flags;\n\n   UNKNOWN(0x0C);\n};\nCHECK_OFFSET(MEMHeapHeader, 0x00, tag);\nCHECK_OFFSET(MEMHeapHeader, 0x04, link);\nCHECK_OFFSET(MEMHeapHeader, 0x0C, list);\nCHECK_OFFSET(MEMHeapHeader, 0x18, dataStart);\nCHECK_OFFSET(MEMHeapHeader, 0x1C, dataEnd);\nCHECK_OFFSET(MEMHeapHeader, 0x20, lock);\nCHECK_OFFSET(MEMHeapHeader, 0x30, flags);\nCHECK_SIZE(MEMHeapHeader, 0x40);\n\nusing MEMHeapHandle = virt_ptr<MEMHeapHeader>;\n\n#pragma pack(pop)\n\nvoid\nMEMDumpHeap(virt_ptr<MEMHeapHeader> heap);\n\nvirt_ptr<MEMHeapHeader>\nMEMFindContainHeap(virt_ptr<void> block);\n\nMEMBaseHeapType\nMEMGetArena(virt_ptr<MEMHeapHeader> heap);\n\nMEMHeapHandle\nMEMGetBaseHeapHandle(MEMBaseHeapType type);\n\nMEMHeapHandle\nMEMSetBaseHeapHandle(MEMBaseHeapType type,\n                     MEMHeapHandle handle);\n\nMEMHeapHandle\nMEMCreateUserHeapHandle(virt_ptr<MEMHeapHeader> heap,\n                        uint32_t size);\n\nuint32_t\nMEMGetFillValForHeap(MEMHeapFillType type);\n\nvoid\nMEMSetFillValForHeap(MEMHeapFillType type,\n                     uint32_t value);\n\n/** @} */\n\nnamespace internal\n{\n\nclass HeapLock\n{\npublic:\n   HeapLock(virt_ptr<MEMHeapHeader> header)\n   {\n      if (header->flags & MEMHeapFlags::ThreadSafe) {\n         OSUninterruptibleSpinLock_Acquire(virt_addrof(header->lock));\n         mHeap = header;\n      } else {\n         mHeap = nullptr;\n      }\n   }\n\n   ~HeapLock()\n   {\n      if (mHeap) {\n         OSUninterruptibleSpinLock_Release(virt_addrof(mHeap->lock));\n         mHeap = nullptr;\n      }\n   }\n\n   void unlock()\n   {\n      if (mHeap) {\n         OSUninterruptibleSpinLock_Release(virt_addrof(mHeap->lock));\n         mHeap = nullptr;\n      }\n   }\n\n\nprivate:\n   virt_ptr<MEMHeapHeader> mHeap;\n};\n\nvoid\nregisterHeap(virt_ptr<MEMHeapHeader> heap,\n             MEMHeapTag tag,\n             virt_ptr<uint8_t> dataStart,\n             virt_ptr<uint8_t> dataEnd,\n             MEMHeapFlags flags);\n\nvoid\nunregisterHeap(virt_ptr<MEMHeapHeader> heap);\n\nvoid\ninitialiseMemHeap();\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memlist.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_memlist.h\"\n\nnamespace cafe::coreinit\n{\n\nstatic virt_ptr<MEMListLink>\ngetLink(virt_ptr<MEMList> list,\n        virt_ptr<void> object)\n{\n   return virt_cast<MEMListLink *>(virt_cast<uint8_t *>(object) + list->offsetToMEMListLink);\n}\n\nstatic void\nsetFirstObject(virt_ptr<MEMList> list,\n               virt_ptr<void> object)\n{\n   auto link = getLink(list, object);\n   list->head = object;\n   list->tail = object;\n   link->next = nullptr;\n   link->prev = nullptr;\n   list->count = uint16_t { 1 };\n}\n\nvoid\nMEMInitList(virt_ptr<MEMList> list,\n            uint16_t offsetToMEMListLink)\n{\n   list->head = nullptr;\n   list->tail = nullptr;\n   list->count = uint16_t { 0 };\n   list->offsetToMEMListLink = offsetToMEMListLink;\n}\n\nvoid\nMEMAppendListObject(virt_ptr<MEMList> list,\n                    virt_ptr<void> object)\n{\n   if (!list->tail) {\n      setFirstObject(list, object);\n   } else {\n      auto link = getLink(list, object);\n      auto tail = getLink(list, list->tail);\n      tail->next = object;\n      link->prev = list->tail;\n      link->next = nullptr;\n      list->tail = object;\n      list->count++;\n   }\n}\n\nvoid\nMEMPrependListObject(virt_ptr<MEMList> list,\n                     virt_ptr<void> object)\n{\n   if (!list->head) {\n      setFirstObject(list, object);\n   } else {\n      auto link = getLink(list, object);\n      auto head = getLink(list, list->head);\n      head->prev = object;\n      link->prev = nullptr;\n      link->next = list->head;\n      list->head = object;\n      list->count++;\n   }\n}\n\nvoid\nMEMInsertListObject(virt_ptr<MEMList> list,\n                    virt_ptr<void> before,\n                    virt_ptr<void> object)\n{\n   if (!before) {\n      // Insert at end\n      MEMAppendListObject(list, object);\n      return;\n   }\n\n   if (list->head == before) {\n      // Insert before head\n      MEMPrependListObject(list, object);\n      return;\n   }\n\n   // Insert to middle of list\n   auto link = getLink(list, object);\n   auto other = getLink(list, before);\n   link->prev = other->prev;\n   link->next = before;\n   other->prev = object;\n   list->count++;\n}\n\nvoid\nMEMRemoveListObject(virt_ptr<MEMList> list,\n                    virt_ptr<void> object)\n{\n   virt_ptr<void> head = nullptr;\n\n   if (!object) {\n      return;\n   }\n\n   if (list->head == object && list->tail == object) {\n      // Clear list\n      list->head = nullptr;\n      list->tail = nullptr;\n      list->count = uint16_t { 0 };\n      return;\n   }\n\n   if (list->head == object) {\n      // Remove from head\n      list->head = MEMGetNextListObject(list, list->head);\n\n      if (list->head) {\n         getLink(list, list->head)->prev = nullptr;\n      }\n\n      list->count--;\n      return;\n   }\n\n   if (list->tail == object) {\n      // Remove from tail\n      list->tail = MEMGetPrevListObject(list, list->tail);\n\n      if (list->tail) {\n         getLink(list, list->tail)->next = nullptr;\n      }\n\n      list->count--;\n      return;\n   }\n\n   do {\n      head = MEMGetNextListObject(list, head);\n   } while (head && head != object);\n\n   if (head == object) {\n      // Remove from middle of list\n      auto link = getLink(list, object);\n      auto next = link->next;\n      auto prev = link->prev;\n      getLink(list, prev)->next = next;\n      getLink(list, next)->prev = prev;\n      list->count--;\n   }\n}\n\nvirt_ptr<void>\nMEMGetNextListObject(virt_ptr<MEMList> list,\n                     virt_ptr<void> object)\n{\n   if (!object) {\n      return list->head;\n   }\n\n   return getLink(list, object)->next;\n}\n\nvirt_ptr<void>\nMEMGetPrevListObject(virt_ptr<MEMList> list,\n                     virt_ptr<void> object)\n{\n   if (!object) {\n      return list->tail;\n   }\n\n   return getLink(list, object)->prev;\n}\n\nvirt_ptr<void>\nMEMGetNthListObject(virt_ptr<MEMList> list,\n                    uint16_t n)\n{\n   auto head = list->head;\n\n   for (auto i = 0u; i < n && head; ++i) {\n      head = MEMGetNextListObject(list, head);\n   }\n\n   return head;\n}\n\nvoid\nLibrary::registerMemListSymbols()\n{\n   RegisterFunctionExport(MEMInitList);\n   RegisterFunctionExport(MEMAppendListObject);\n   RegisterFunctionExport(MEMPrependListObject);\n   RegisterFunctionExport(MEMInsertListObject);\n   RegisterFunctionExport(MEMRemoveListObject);\n   RegisterFunctionExport(MEMGetNextListObject);\n   RegisterFunctionExport(MEMGetPrevListObject);\n   RegisterFunctionExport(MEMGetNthListObject);\n}\n\n} // namespace cafe::coreinit"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memlist.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_memlist Memory List\n * \\ingroup coreinit\n *\n * A linked list used for memory heaps.\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct MEMListLink\n{\n   be2_virt_ptr<void> prev;\n   be2_virt_ptr<void> next;\n};\n\nCHECK_OFFSET(MEMListLink, 0x0, prev);\nCHECK_OFFSET(MEMListLink, 0x4, next);\nCHECK_SIZE(MEMListLink, 0x8);\n\nstruct MEMList\n{\n   be2_virt_ptr<void> head;\n   be2_virt_ptr<void> tail;\n   be2_val<uint16_t> count;\n   be2_val<uint16_t> offsetToMEMListLink;\n};\nCHECK_OFFSET(MEMList, 0x0, head);\nCHECK_OFFSET(MEMList, 0x4, tail);\nCHECK_OFFSET(MEMList, 0x8, count);\nCHECK_OFFSET(MEMList, 0xa, offsetToMEMListLink);\nCHECK_SIZE(MEMList, 0xc);\n\n#pragma pack(pop)\n\nvoid\nMEMInitList(virt_ptr<MEMList> list,\n            uint16_t offsetToMEMListLink);\n\nvoid\nMEMAppendListObject(virt_ptr<MEMList> list,\n                    virt_ptr<void> object);\n\nvoid\nMEMPrependListObject(virt_ptr<MEMList> list,\n                     virt_ptr<void> object);\n\nvoid\nMEMInsertListObject(virt_ptr<MEMList> list,\n                    virt_ptr<void> before,\n                    virt_ptr<void> object);\n\nvoid\nMEMRemoveListObject(virt_ptr<MEMList> list,\n                    virt_ptr<void> object);\n\nvirt_ptr<void>\nMEMGetNextListObject(virt_ptr<MEMList> list,\n                     virt_ptr<void> object);\n\nvirt_ptr<void>\nMEMGetPrevListObject(virt_ptr<MEMList> list,\n                     virt_ptr<void> object);\n\nvirt_ptr<void>\nMEMGetNthListObject(virt_ptr<MEMList> list,\n                    uint16_t n);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memory.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_internal_idlock.h\"\n#include \"coreinit_memory.h\"\n#include \"coreinit_systemheap.h\"\n#include \"coreinit_systeminfo.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/kernel/cafe_kernel_mmu.h\"\n#include \"cafe/kernel/cafe_kernel_shareddata.h\"\n\n#include <atomic>\n#include <cstring>\n\nnamespace cafe::coreinit\n{\n\nstruct StaticMemoryData\n{\n   internal::IdLock boundsLock;\n   be2_val<phys_addr> foregroundPhysicalAddress;\n   be2_val<virt_addr> foregroundBaseAddress;\n   be2_val<uint32_t> foregroundSize;\n\n   be2_val<virt_addr> mem1BaseAddress;\n   be2_val<uint32_t> mem1Size;\n\n   be2_val<virt_addr> mem2BaseAddress;\n   be2_val<uint32_t> mem2Size;\n};\n\nstatic virt_ptr<StaticMemoryData>\nsMemoryData = nullptr;\n\nenum ForegroundAreaId\n{\n   Application = 0,\n   TransitionAudioBuffer = 1,\n   SavedFrameUnk2 = 2,\n   SavedFrameUnk3 = 3,\n   SavedFrameUnk4 = 4,\n   SavedFrameUnk5 = 5,\n   Unknown6 = 6,\n   CopyArea = 7,\n};\n\nstruct ForegroundArea\n{\n   ForegroundAreaId id;\n   uint32_t offset;\n   uint32_t size;\n};\n\nconstexpr std::array<ForegroundArea, 8> ForegroundAreas {\n   ForegroundArea { ForegroundAreaId::Application,                   0, 0x2800000 },\n   ForegroundArea { ForegroundAreaId::CopyArea,              0x2800000,  0x400000 },\n   ForegroundArea { ForegroundAreaId::TransitionAudioBuffer, 0x2C00000,  0x900000 },\n   ForegroundArea { ForegroundAreaId::SavedFrameUnk2,        0x3500000,  0x3C0000 },\n   ForegroundArea { ForegroundAreaId::SavedFrameUnk3,        0x38C0000,  0x1C0000 },\n   ForegroundArea { ForegroundAreaId::SavedFrameUnk4,        0x3A80000,  0x3C0000 },\n   ForegroundArea { ForegroundAreaId::SavedFrameUnk5,        0x3E40000,  0x1BF000 },\n   ForegroundArea { ForegroundAreaId::Unknown6,              0x3FFF000,    0x1000 },\n};\n\nstatic virt_ptr<void>\ngetForegroundAreaPointer(ForegroundAreaId id)\n{\n   for (auto &area : ForegroundAreas) {\n      if (area.id == id) {\n         return virt_cast<void *>(sMemoryData->foregroundBaseAddress + area.offset);\n      }\n   }\n\n   return nullptr;\n}\n\nstatic uint32_t\ngetForegroundAreaSize(ForegroundAreaId id)\n{\n   for (auto &area : ForegroundAreas) {\n      if (area.id == id) {\n         return area.size;\n      }\n   }\n\n   return 0;\n}\n\nvirt_ptr<void>\nOSBlockMove(virt_ptr<void> dst,\n            virt_ptr<const void> src,\n            uint32_t size,\n            BOOL flush)\n{\n   memmove(dst, src, size);\n   return dst;\n}\n\nvirt_ptr<void>\nOSBlockSet(virt_ptr<void> dst,\n           int val,\n           uint32_t size)\n{\n   memset(dst, val, size);\n   return dst;\n}\n\n\n/**\n * Get the foreground memory bucket address and size.\n *\n * \\return\n * Returns TRUE if the current process is in the foreground.\n */\nBOOL\nOSGetForegroundBucket(virt_ptr<virt_addr> addr,\n                      virt_ptr<uint32_t> size)\n{\n   auto range = kernel::getForegroundBucket();\n\n   if (addr) {\n      *addr = range.first;\n   }\n\n   if (size) {\n      *size = range.second;\n   }\n\n   return range.first && range.second;\n}\n\n\n/**\n * Get the area of the foreground bucket which the application can use.\n *\n * \\return\n * Returns TRUE if the current process is in the foreground.\n */\nBOOL\nOSGetForegroundBucketFreeArea(virt_ptr<virt_addr> addr,\n                              virt_ptr<uint32_t> size)\n{\n   if (addr) {\n      *addr = virt_cast<virt_addr>(getForegroundAreaPointer(ForegroundAreaId::Application));\n   }\n\n   if (size) {\n      *size = getForegroundAreaSize(ForegroundAreaId::Application);\n   }\n\n   return !!sMemoryData->foregroundBaseAddress;\n}\n\nint32_t\nOSGetMemBound(OSMemoryType type,\n              virt_ptr<virt_addr> addr,\n              virt_ptr<uint32_t> size)\n{\n   if (addr) {\n      *addr = virt_addr { 0u };\n   }\n\n   if (size) {\n      *size = 0u;\n   }\n\n   internal::acquireIdLockWithCoreId(sMemoryData->boundsLock);\n   switch (type) {\n   case OSMemoryType::MEM1:\n      if (addr) {\n         *addr = sMemoryData->mem1BaseAddress;\n      }\n\n      if (size) {\n         *size = sMemoryData->mem1Size;\n      }\n      break;\n   case OSMemoryType::MEM2:\n      if (addr) {\n         *addr = sMemoryData->mem2BaseAddress;\n      }\n\n      if (size) {\n         *size = sMemoryData->mem2Size;\n      }\n      break;\n   default:\n      internal::releaseIdLockWithCoreId(sMemoryData->boundsLock);\n      return -1;\n   }\n\n   internal::releaseIdLockWithCoreId(sMemoryData->boundsLock);\n   return 0;\n}\n\nvoid\nOSGetAvailPhysAddrRange(virt_ptr<phys_addr> start,\n                        virt_ptr<uint32_t> size)\n{\n   auto range = kernel::getAvailablePhysicalAddressRange();\n\n   if (start) {\n      *start = range.first;\n   }\n\n   if (size) {\n      *size = range.second;\n   }\n}\n\nvoid\nOSGetDataPhysAddrRange(virt_ptr<phys_addr> start,\n                       virt_ptr<uint32_t> size)\n{\n   auto range = kernel::getDataPhysicalAddressRange();\n\n   if (start) {\n      *start = range.first;\n   }\n\n   if (size) {\n      *size = range.second;\n   }\n}\n\nvoid\nOSGetMapVirtAddrRange(virt_ptr<virt_addr> start,\n                      virt_ptr<uint32_t> size)\n{\n   auto range = kernel::getVirtualMapAddressRange();\n\n   if (start) {\n      *start = range.first;\n   }\n\n   if (size) {\n      *size = range.second;\n   }\n}\n\nBOOL\nOSGetSharedData(OSSharedDataType type,\n                uint32_t unk_r4,\n                virt_ptr<virt_ptr<void>> outPtr,\n                virt_ptr<uint32_t> outSize)\n{\n   auto area = kernel::SharedArea { };\n\n   if (!outPtr || !outSize) {\n      return FALSE;\n   }\n\n   switch (type) {\n   case OSSharedDataType::FontChinese:\n      area = kernel::getSharedArea(kernel::SharedAreaId::FontChinese);\n      break;\n   case OSSharedDataType::FontKorean:\n      area = kernel::getSharedArea(kernel::SharedAreaId::FontKorean);\n      break;\n   case OSSharedDataType::FontStandard:\n      area = kernel::getSharedArea(kernel::SharedAreaId::FontStandard);\n      break;\n   case OSSharedDataType::FontTaiwanese:\n      area = kernel::getSharedArea(kernel::SharedAreaId::FontTaiwanese);\n      break;\n   default:\n      return FALSE;\n   }\n\n   *outPtr = virt_cast<void *>(area.address);\n   *outSize = area.size;\n   return TRUE;\n}\n\nvirt_addr\nOSAllocVirtAddr(virt_addr address,\n                uint32_t size,\n                uint32_t alignment)\n{\n   return kernel::allocateVirtualAddress(address, size, alignment);\n}\n\nBOOL\nOSFreeVirtAddr(virt_addr address,\n               uint32_t size)\n{\n   return kernel::freeVirtualAddress(address, size) ? TRUE : FALSE;\n}\n\nint32_t\nOSQueryVirtAddr(virt_addr address)\n{\n   return static_cast<int32_t>(kernel::queryVirtualAddress(address));\n}\n\nBOOL\nOSMapMemory(virt_addr virtAddress,\n            phys_addr physAddress,\n            uint32_t size,\n            int permission)\n{\n   return\n      kernel::mapMemory(virtAddress, physAddress, size,\n                        static_cast<kernel::MapMemoryPermission>(permission))\n      ? TRUE : FALSE;\n}\n\nBOOL\nOSUnmapMemory(virt_addr virtAddress,\n              uint32_t size)\n{\n   return kernel::unmapMemory(virtAddress, size) ? TRUE : FALSE;\n}\n\n\n/**\n * Translates a virtual (effective) address to a physical address.\n */\nphys_addr\nOSEffectiveToPhysical(virt_addr address)\n{\n   if (address >= virt_addr { 0x10000000 } &&\n       address < internal::getMem2EndAddress()) {\n      return internal::getMem2PhysAddress() +\n         static_cast<uint32_t>(address - 0x10000000);\n   }\n\n   if (address >= virt_addr { 0xF4000000 } &&\n       address <  virt_addr { 0xF6000000 }) {\n      return phys_addr { static_cast<uint32_t>(address - 0xF4000000) };\n   }\n\n   if (sMemoryData->foregroundBaseAddress &&\n       address >= sMemoryData->foregroundBaseAddress &&\n       address <  sMemoryData->foregroundBaseAddress + sMemoryData->foregroundSize) {\n      return sMemoryData->foregroundPhysicalAddress +\n         static_cast<uint32_t>(address - sMemoryData->foregroundBaseAddress);\n   }\n\n   return kernel::effectiveToPhysical(address);\n}\n\n\n/**\n * Translates a physical address to a virtual (effective) address.\n */\nvirt_addr\nOSPhysicalToEffectiveCached(phys_addr address)\n{\n   return kernel::physicalToEffectiveCached(address);\n}\n\n\n/**\n * Translates a physical address to a virtual (effective) address.\n */\nvirt_addr\nOSPhysicalToEffectiveUncached(phys_addr address)\n{\n   return kernel::physicalToEffectiveUncached(address);\n}\n\n\n/**\n * memcpy for virtual memory.\n */\nvirt_ptr<void>\nmemcpy(virt_ptr<void> dst,\n       virt_ptr<const void> src,\n       uint32_t size)\n{\n   std::memcpy(dst.get(), src.get(), size);\n   return dst;\n}\n\n\n/**\n * memmove for virtual memory.\n */\nvirt_ptr<void>\nmemmove(virt_ptr<void> dst,\n        virt_ptr<const void> src,\n        uint32_t size)\n{\n   std::memmove(dst.get(), src.get(), size);\n   return dst;\n}\n\n\n/**\n * memset for virtual memory.\n */\nvirt_ptr<void>\nmemset(virt_ptr<void> dst,\n       int value,\n       uint32_t size)\n{\n   std::memset(dst.get(), value, size);\n   return dst;\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseMemory()\n{\n   sMemoryData->mem1BaseAddress = virt_addr { 0xF4000000 };\n   sMemoryData->mem1Size = 0x2000000u;\n\n   auto mem2BaseAddress = align_up(getMem2BaseAddress(), 4096);\n   auto mem2EndAddress = align_down(getMem2EndAddress(), 4096);\n\n   auto systemHeapBaseAddress = mem2BaseAddress;\n   auto systemHeapSize = getSystemHeapSize();\n   initialiseSystemHeap(virt_cast<void *>(systemHeapBaseAddress),\n                        systemHeapSize);\n\n   sMemoryData->mem2BaseAddress = align_up(mem2BaseAddress + systemHeapSize, 4096);\n   sMemoryData->mem2Size = static_cast<uint32_t>(mem2EndAddress - sMemoryData->mem2BaseAddress);\n\n   OSGetForegroundBucket(virt_addrof(sMemoryData->foregroundBaseAddress),\n                         virt_addrof(sMemoryData->foregroundSize));\n\n   sMemoryData->foregroundPhysicalAddress =\n      kernel::effectiveToPhysical(sMemoryData->foregroundBaseAddress);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerMemorySymbols()\n{\n   RegisterFunctionExport(OSBlockMove);\n   RegisterFunctionExport(OSBlockSet);\n   RegisterFunctionExport(OSGetMemBound);\n   RegisterFunctionExport(OSGetForegroundBucket);\n   RegisterFunctionExport(OSGetForegroundBucketFreeArea);\n   RegisterFunctionExport(OSGetAvailPhysAddrRange);\n   RegisterFunctionExport(OSGetDataPhysAddrRange);\n   RegisterFunctionExport(OSGetMapVirtAddrRange);\n   RegisterFunctionExport(OSGetSharedData);\n   RegisterFunctionExport(OSAllocVirtAddr);\n   RegisterFunctionExport(OSFreeVirtAddr);\n   RegisterFunctionExport(OSQueryVirtAddr);\n   RegisterFunctionExport(OSMapMemory);\n   RegisterFunctionExport(OSUnmapMemory);\n   RegisterFunctionExport(OSEffectiveToPhysical);\n   RegisterFunctionExportName(\"__OSPhysicalToEffectiveCached\",\n                              OSPhysicalToEffectiveCached);\n   RegisterFunctionExportName(\"__OSPhysicalToEffectiveUncached\",\n                              OSPhysicalToEffectiveUncached);\n   RegisterFunctionExport(memcpy);\n   RegisterFunctionExport(memmove);\n   RegisterFunctionExport(memset);\n\n   RegisterDataInternal(sMemoryData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memory.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nvirt_ptr<void>\nOSBlockMove(virt_ptr<void> dst,\n            virt_ptr<const void> src,\n            uint32_t size,\n            BOOL flush);\n\nvirt_ptr<void>\nOSBlockSet(virt_ptr<void> dst,\n           int val,\n           uint32_t size);\n\nBOOL\nOSGetForegroundBucket(virt_ptr<virt_addr> addr,\n                      virt_ptr<uint32_t> size);\n\nBOOL\nOSGetForegroundBucketFreeArea(virt_ptr<virt_addr> addr,\n                              virt_ptr<uint32_t> size);\n\nint\nOSGetMemBound(OSMemoryType type,\n              virt_ptr<virt_addr> addr,\n              virt_ptr<uint32_t> size);\n\nvoid\nOSGetAvailPhysAddrRange(virt_ptr<phys_addr> start,\n                        virt_ptr<uint32_t> size);\n\nvoid\nOSGetDataPhysAddrRange(virt_ptr<phys_addr> start,\n                       virt_ptr<uint32_t> size);\n\nvoid\nOSGetMapVirtAddrRange(virt_ptr<virt_addr> start,\n                      virt_ptr<uint32_t> size);\n\nBOOL\nOSGetSharedData(OSSharedDataType type,\n                uint32_t unk_r4,\n                virt_ptr<virt_ptr<void>> outPtr,\n                virt_ptr<uint32_t> outSize);\n\nvirt_addr\nOSAllocVirtAddr(virt_addr address,\n                uint32_t size,\n                uint32_t alignment);\n\nBOOL\nOSFreeVirtAddr(virt_addr address,\n               uint32_t size);\n\nint\nOSQueryVirtAddr(virt_addr virtAddress);\n\nBOOL\nOSMapMemory(virt_addr virtAddress,\n            phys_addr physAddress,\n            uint32_t size,\n            int permission);\n\nBOOL\nOSUnmapMemory(virt_addr virtAddress,\n              uint32_t size);\n\nphys_addr\nOSEffectiveToPhysical(virt_addr address);\n\nvirt_addr\nOSPhysicalToEffectiveCached(phys_addr address);\n\nvirt_addr\nOSPhysicalToEffectiveUncached(phys_addr address);\n\nvirt_ptr<void>\nmemcpy(virt_ptr<void> dst,\n       virt_ptr<const void> src,\n       uint32_t size);\n\nvirt_ptr<void>\nmemmove(virt_ptr<void> dst,\n        virt_ptr<const void> src,\n        uint32_t size);\n\nvirt_ptr<void>\nmemset(virt_ptr<void> dst,\n       int value,\n       uint32_t size);\n\nnamespace internal\n{\n\nvoid\ninitialiseMemory();\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memunitheap.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_memunitheap.h\"\n#include \"coreinit_memory.h\"\n\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * Initialise a unit heap.\n *\n * Adds it to the list of active heaps.\n */\nMEMHeapHandle\nMEMCreateUnitHeapEx(virt_ptr<void> base,\n                    uint32_t size,\n                    uint32_t blockSize,\n                    int32_t alignment,\n                    uint32_t flags)\n{\n   auto baseMem = virt_cast<uint8_t *>(base);\n\n   // Align start and end to 4 byte boundary\n   auto start = align_up(baseMem, 4);\n   auto end = align_down(baseMem + size, 4);\n\n   if (start >= end) {\n      return nullptr;\n   }\n\n   // Get first block aligned start\n   auto dataStart = align_up(start + sizeof(MEMUnitHeap), alignment);\n\n   if (dataStart >= end) {\n      return nullptr;\n   }\n\n   // Calculate aligned block size and count\n   auto alignedBlockSize = align_up(blockSize, alignment);\n   auto blockCount = (end - dataStart) / alignedBlockSize;\n\n   if (blockCount == 0) {\n      return nullptr;\n   }\n\n   decaf_check(base);\n   auto heap = virt_cast<MEMUnitHeap *>(start);\n\n   // Register Heap\n   internal::registerHeap(virt_addrof(heap->header),\n                          MEMHeapTag::UnitHeap,\n                          dataStart,\n                          dataStart + alignedBlockSize * blockCount,\n                          static_cast<MEMHeapFlags>(flags));\n\n   // Setup the MEMUnitHeap\n   auto firstBlock = virt_cast<MEMUnitHeapFreeBlock *>(dataStart);\n   heap->freeBlocks = firstBlock;\n   heap->blockSize = alignedBlockSize;\n\n   // Setup free block linked list\n   auto prev = virt_ptr<MEMUnitHeapFreeBlock> { nullptr };\n\n   for (auto i = 0u; i < blockCount; ++i) {\n      auto block = virt_cast<MEMUnitHeapFreeBlock *>(dataStart + alignedBlockSize * i);\n\n      if (prev) {\n         prev->next = block;\n      }\n\n      prev = block;\n   }\n\n   if (prev) {\n      prev->next = nullptr;\n   }\n\n   return virt_cast<MEMHeapHeader *>(heap);\n}\n\n\n/**\n * Destroy unit heap.\n *\n * Remove it from the list of active heaps.\n */\nvirt_ptr<void>\nMEMDestroyUnitHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMUnitHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::UnitHeap);\n   internal::unregisterHeap(virt_addrof(heap->header));\n   return heap;\n}\n\n\n/**\n * Allocate a memory block from a unit heap\n */\nvirt_ptr<void>\nMEMAllocFromUnitHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMUnitHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::UnitHeap);\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto block = heap->freeBlocks;\n\n   if (block) {\n      heap->freeBlocks = block->next;\n   }\n\n   lock.unlock();\n\n   if (block) {\n      if (heap->header.flags & MEMHeapFlags::ZeroAllocated) {\n         memset(block, 0, heap->blockSize);\n      } else if (heap->header.flags & MEMHeapFlags::DebugMode) {\n         auto value = MEMGetFillValForHeap(MEMHeapFillType::Allocated);\n         memset(block, value, heap->blockSize);\n      }\n   }\n\n   return block;\n}\n\n\n/**\n * Free a memory block in a unit heap\n */\nvoid\nMEMFreeToUnitHeap(MEMHeapHandle handle,\n                  virt_ptr<void> block)\n{\n   auto heap = virt_cast<MEMUnitHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::UnitHeap);\n\n   if (!block) {\n      return;\n   }\n\n   if (heap->header.flags & MEMHeapFlags::DebugMode) {\n      auto value = MEMGetFillValForHeap(MEMHeapFillType::Freed);\n      memset(block, value, heap->blockSize);\n   }\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto freeBlock = virt_cast<MEMUnitHeapFreeBlock *>(block);\n   freeBlock->next = heap->freeBlocks;\n   heap->freeBlocks = freeBlock;\n}\n\n\n/**\n * Count the number of free blocks in a unit heap.\n */\nuint32_t\nMEMCountFreeBlockForUnitHeap(MEMHeapHandle handle)\n{\n   auto heap = virt_cast<MEMUnitHeap *>(handle);\n   decaf_check(heap);\n   decaf_check(heap->header.tag == MEMHeapTag::UnitHeap);\n\n   internal::HeapLock lock { virt_addrof(heap->header) };\n   auto count = 0u;\n\n   for (auto block = heap->freeBlocks; block; block = block->next) {\n      count++;\n   }\n\n   return count;\n}\n\n\n/**\n * Calculate the size required for a unit heap containing blockCount blocks of blockSize.\n */\nuint32_t\nMEMCalcHeapSizeForUnitHeap(uint32_t blockSize,\n                           uint32_t blockCount,\n                           int alignment)\n{\n   auto alignedBlockSize = align_up(blockSize, alignment);\n   auto totalBlockSize = alignedBlockSize * blockCount;\n   auto headerSize = alignment - 4 + static_cast<uint32_t>(sizeof(MEMUnitHeap));\n\n   return headerSize + totalBlockSize;\n}\n\nnamespace internal\n{\n\n/**\n * Print debug information about the unit heap.\n */\nvoid\ndumpUnitHeap(virt_ptr<MEMUnitHeap> heap)\n{\n   auto handle = virt_cast<MEMHeapHeader *>(heap);\n   auto freeBlocks = MEMCountFreeBlockForUnitHeap(handle);\n   auto freeSize = heap->blockSize * freeBlocks;\n   auto totalSize = heap->header.dataEnd - heap->header.dataStart;\n   auto usedSize = totalSize - freeSize;\n   auto percent = static_cast<float>(usedSize) / static_cast<float>(totalSize);\n\n   gLog->debug(\"MEMUnitHeap(0x{:8x})\", heap);\n   gLog->debug(\"{} out of {} bytes ({}%) used\", usedSize, totalSize, percent);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerMemUnitHeapSymbols()\n{\n   RegisterFunctionExport(MEMCreateUnitHeapEx);\n   RegisterFunctionExport(MEMDestroyUnitHeap);\n   RegisterFunctionExport(MEMAllocFromUnitHeap);\n   RegisterFunctionExport(MEMFreeToUnitHeap);\n   RegisterFunctionExport(MEMCountFreeBlockForUnitHeap);\n   RegisterFunctionExport(MEMCalcHeapSizeForUnitHeap);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_memunitheap.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_memheap.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_unitheap Unit Heap\n * \\ingroup coreinit\n *\n * A unit heap is a memory heap where every allocation is of a fixed size\n * determined at heap creation.\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct MEMUnitHeapFreeBlock\n{\n   be2_ptr<MEMUnitHeapFreeBlock> next;\n};\nCHECK_OFFSET(MEMUnitHeapFreeBlock, 0x00, next);\nCHECK_SIZE(MEMUnitHeapFreeBlock, 0x04);\n\nstruct MEMUnitHeap\n{\n   be2_struct<MEMHeapHeader> header;\n   be2_ptr<MEMUnitHeapFreeBlock> freeBlocks;\n   be2_val<uint32_t> blockSize;\n};\nCHECK_OFFSET(MEMUnitHeap, 0x00, header);\nCHECK_OFFSET(MEMUnitHeap, 0x40, freeBlocks);\nCHECK_OFFSET(MEMUnitHeap, 0x44, blockSize);\nCHECK_SIZE(MEMUnitHeap, 0x48);\n\n#pragma pack(pop)\n\nMEMHeapHandle\nMEMCreateUnitHeapEx(virt_ptr<void> base,\n                    uint32_t size,\n                    uint32_t blockSize,\n                    int32_t alignment,\n                    uint32_t flags);\n\nvirt_ptr<void>\nMEMDestroyUnitHeap(MEMHeapHandle handle);\n\nvirt_ptr<void>\nMEMAllocFromUnitHeap(MEMHeapHandle handle);\n\nvoid\nMEMFreeToUnitHeap(MEMHeapHandle handle,\n                  virt_ptr<void> block);\n\nuint32_t\nMEMCountFreeBlockForUnitHeap(MEMHeapHandle handle);\n\nuint32_t\nMEMCalcHeapSizeForUnitHeap(uint32_t blockSize,\n                           uint32_t count,\n                           int alignment);\n\nnamespace internal\n{\n\nvoid\ndumpUnitHeap(virt_ptr<MEMUnitHeap> heap);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_messagequeue.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_memory.h\"\n#include \"coreinit_messagequeue.h\"\n#include \"coreinit_scheduler.h\"\n\n#include <common/decaf_assert.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * Initialise a message queue structure.\n */\nvoid\nOSInitMessageQueue(virt_ptr<OSMessageQueue> queue,\n                   virt_ptr<OSMessage> messages,\n                   uint32_t size)\n{\n   OSInitMessageQueueEx(queue, messages, size, nullptr);\n}\n\n\n/**\n * Initialise a message queue structure with a name.\n */\nvoid\nOSInitMessageQueueEx(virt_ptr<OSMessageQueue> queue,\n                     virt_ptr<OSMessage> messages,\n                     uint32_t size,\n                     virt_ptr<const char> name)\n{\n   queue->tag = OSMessageQueue::Tag;\n   queue->name = name;\n   queue->messages = messages;\n   queue->size = size;\n   queue->first = 0u;\n   queue->used = 0u;\n   OSInitThreadQueueEx(virt_addrof(queue->sendQueue), queue);\n   OSInitThreadQueueEx(virt_addrof(queue->recvQueue), queue);\n}\n\n\n/**\n * Insert a message into the queue.\n *\n * If the OSMessageFlags::HighPriority flag is set then the current thread will\n * block until there is space in the queue to insert the message, else it will\n * return immediately with the return value of FALSE.\n *\n * If the OSMessageFlags::HighPriority flag is set then the message will be\n * inserted at the front of the queue, otherwise it will be inserted at the back.\n *\n * \\return Returns TRUE if the message was inserted in the queue.\n */\nBOOL\nOSSendMessage(virt_ptr<OSMessageQueue> queue,\n              virt_ptr<OSMessage> message,\n              OSMessageFlags flags)\n{\n   unsigned index;\n   internal::lockScheduler();\n   decaf_check(queue && queue->tag == OSMessageQueue::Tag);\n   decaf_check(message);\n\n   if (!(flags & OSMessageFlags::Blocking) && queue->used == queue->size) {\n      // Do not block waiting for space to insert message.\n      internal::unlockScheduler();\n      return FALSE;\n   }\n\n   // Wait for space in the message queue.\n   while (queue->used == queue->size) {\n      internal::sleepThreadNoLock(virt_addrof(queue->sendQueue));\n      internal::rescheduleSelfNoLock();\n   }\n\n   if (flags & OSMessageFlags::HighPriority) {\n      // High priorty messages are pushed to the front of the queue.\n      if (queue->first == 0) {\n         queue->first = queue->size - 1;\n      } else {\n         queue->first--;\n      }\n\n      index = queue->first;\n   } else {\n      // Normal messages are pushed to back of the queue.\n      index = (queue->first + queue->used) % queue->size;\n   }\n\n   memcpy(queue->messages + index, message, sizeof(OSMessage));\n   queue->used++;\n\n   // Wakeup threads waiting to read message\n   internal::wakeupThreadNoLock(virt_addrof(queue->recvQueue));\n   internal::rescheduleAllCoreNoLock();\n\n   internal::unlockScheduler();\n   return TRUE;\n}\n\n\n/**\n * Read and remove a message from the queue.\n *\n * If flags has OSMessageFlags::Blocking then the current thread will block\n * until there is a mesasge in the queue to read, else it will return\n * immediately with the return value of FALSE.\n *\n * \\return Returns TRUE if a message was read from the queue.\n */\nBOOL\nOSReceiveMessage(virt_ptr<OSMessageQueue> queue,\n                 virt_ptr<OSMessage> message,\n                 OSMessageFlags flags)\n{\n   internal::lockScheduler();\n   decaf_check(queue && queue->tag == OSMessageQueue::Tag);\n   decaf_check(message);\n\n   if (!(flags & OSMessageFlags::Blocking) && queue->used == 0) {\n      // Do not block waiting for a message to arrive\n      internal::unlockScheduler();\n      return FALSE;\n   }\n\n   // Wait for a message to appear in queue\n   while (queue->used == 0) {\n      internal::sleepThreadNoLock(virt_addrof(queue->recvQueue));\n      internal::rescheduleSelfNoLock();\n   }\n\n   decaf_check(queue->used > 0);\n\n   // Copy into message array\n   memcpy(message, queue->messages + queue->first, sizeof(OSMessage));\n   queue->first = (queue->first + 1) % queue->size;\n   queue->used--;\n\n   // Wakeup threads waiting for space to send message\n   internal::wakeupThreadNoLock(virt_addrof(queue->sendQueue));\n   internal::rescheduleAllCoreNoLock();\n\n   internal::unlockScheduler();\n   return TRUE;\n}\n\n\n/**\n * Read and do NOT remove a message from the queue.\n *\n * \\return Returns TRUE if a message was read from the queue.\n */\nBOOL\nOSPeekMessage(virt_ptr<OSMessageQueue> queue,\n              virt_ptr<OSMessage> message)\n{\n   internal::lockScheduler();\n   decaf_check(queue && queue->tag == OSMessageQueue::Tag);\n   decaf_check(message);\n\n   if (queue->used == 0) {\n      internal::unlockScheduler();\n      return FALSE;\n   }\n\n   memcpy(message, queue->messages + queue->first, sizeof(OSMessage));\n   internal::unlockScheduler();\n   return TRUE;\n}\n\nvoid\nLibrary::registerMessageQueueSymbols()\n{\n   RegisterFunctionExport(OSInitMessageQueue);\n   RegisterFunctionExport(OSInitMessageQueueEx);\n   RegisterFunctionExport(OSSendMessage);\n   RegisterFunctionExport(OSReceiveMessage);\n   RegisterFunctionExport(OSPeekMessage);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_messagequeue.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_thread.h\"\n#include \"coreinit_mutex.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_messagequeue Message Queue\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSMessage\n{\n   be2_virt_ptr<void> message;\n   be2_val<uint32_t> args[3];\n};\nCHECK_OFFSET(OSMessage, 0x00, message);\nCHECK_OFFSET(OSMessage, 0x04, args);\nCHECK_SIZE(OSMessage, 0x10);\n\nstruct OSMessageQueue\n{\n   static constexpr auto Tag = 0x6D536751u;\n\n   be2_val<uint32_t> tag;\n   be2_virt_ptr<const char> name;\n   UNKNOWN(4);\n   be2_struct<OSThreadQueue> sendQueue;\n   be2_struct<OSThreadQueue> recvQueue;\n   be2_virt_ptr<OSMessage> messages;\n   be2_val<uint32_t> size;\n   be2_val<uint32_t> first;\n   be2_val<uint32_t> used;\n};\nCHECK_OFFSET(OSMessageQueue, 0x00, tag);\nCHECK_OFFSET(OSMessageQueue, 0x04, name);\nCHECK_OFFSET(OSMessageQueue, 0x0c, sendQueue);\nCHECK_OFFSET(OSMessageQueue, 0x1c, recvQueue);\nCHECK_OFFSET(OSMessageQueue, 0x2c, messages);\nCHECK_OFFSET(OSMessageQueue, 0x30, size);\nCHECK_OFFSET(OSMessageQueue, 0x34, first);\nCHECK_OFFSET(OSMessageQueue, 0x38, used);\nCHECK_SIZE(OSMessageQueue, 0x3c);\n\n#pragma pack(pop)\n\nvoid\nOSInitMessageQueue(virt_ptr<OSMessageQueue> queue,\n                   virt_ptr<OSMessage> messages,\n                   uint32_t size);\n\nvoid\nOSInitMessageQueueEx(virt_ptr<OSMessageQueue> queue,\n                     virt_ptr<OSMessage> messages,\n                     uint32_t size,\n                     virt_ptr<const char> name);\n\nBOOL\nOSSendMessage(virt_ptr<OSMessageQueue> queue,\n              virt_ptr<OSMessage> message,\n              OSMessageFlags flags);\n\nBOOL\nOSReceiveMessage(virt_ptr<OSMessageQueue> queue,\n                 virt_ptr<OSMessage> message,\n                 OSMessageFlags flags);\n\nBOOL\nOSPeekMessage(virt_ptr<OSMessageQueue> queue,\n              virt_ptr<OSMessage> message);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_mutex.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_mutex.h\"\n#include \"coreinit_scheduler.h\"\n#include \"coreinit_thread.h\"\n#include \"coreinit_internal_queue.h\"\n\n#include <common/decaf_assert.h>\n\nnamespace cafe::coreinit\n{\n\nusing MutexQueue = internal::Queue<OSMutexQueue, OSMutexLink, OSMutex, &OSMutex::link>;\n\n\n/**\n * Initialise a mutex structure.\n */\nvoid\nOSInitMutex(virt_ptr<OSMutex> mutex)\n{\n   OSInitMutexEx(mutex, nullptr);\n}\n\n\n/**\n * Initialise a mutex structure with a name.\n */\nvoid\nOSInitMutexEx(virt_ptr<OSMutex> mutex,\n              virt_ptr<const char> name)\n{\n   mutex->tag = OSMutex::Tag;\n   mutex->name = name;\n   mutex->owner = nullptr;\n   mutex->count = 0;\n   OSInitThreadQueueEx(virt_addrof(mutex->queue), mutex);\n   MutexQueue::initLink(mutex);\n}\n\n\nstatic void\nlockMutexNoLock(virt_ptr<OSMutex> mutex)\n{\n   auto thread = OSGetCurrentThread();\n   decaf_check(thread->state == OSThreadState::Running);\n\n   while (mutex->owner) {\n      if (mutex->owner == thread) {\n         mutex->count++;\n         return;\n      }\n\n      // Mark this thread as waiting on the mutex\n      thread->mutex = mutex;\n\n      // Promote mutex owner priority\n      internal::promoteThreadPriorityNoLock(mutex->owner, thread->priority);\n\n      // Wait for other owner to unlock\n      internal::sleepThreadNoLock(virt_addrof(mutex->queue));\n      internal::rescheduleSelfNoLock();\n\n      // We are no longer waiting on the mutex\n      thread->mutex = nullptr;\n   }\n\n   // Set current thread to owner of mutex\n   mutex->count++;\n   mutex->owner = thread;\n   MutexQueue::append(virt_addrof(thread->mutexQueue), mutex);\n   thread->cancelState |= OSThreadCancelState::DisabledByMutex;\n}\n\n\n/**\n * Lock the mutex.\n *\n * If no one owns the mutex, set current thread as owner.\n * If the lock is owned by the current thread, increase the recursion count.\n * If the lock is owned by another thread, the current thread will sleep until\n * the owner has unlocked this mutex.\n *\n * Similar to <a href=\"http://en.cppreference.com/w/cpp/thread/recursive_mutex/lock\">std::recursive_mutex::lock</a>.\n */\nvoid\nOSLockMutex(virt_ptr<OSMutex> mutex)\n{\n   internal::lockScheduler();\n   internal::testThreadCancelNoLock();\n   lockMutexNoLock(mutex);\n   internal::unlockScheduler();\n}\n\n\n/**\n * Try to lock a mutex.\n *\n * If no one owns the mutex, set current thread as owner.\n * If the lock is owned by the current thread, increase the recursion count.\n * If the lock is owned by another thread, do not block, return FALSE.\n *\n * \\return TRUE if the mutex is locked, FALSE if the mutex is owned by another thread.\n *\n * Similar to <a href=\"http://en.cppreference.com/w/cpp/thread/recursive_mutex/try_lock\">std::recursive_mutex::try_lock</a>.\n */\nBOOL\nOSTryLockMutex(virt_ptr<OSMutex> mutex)\n{\n   internal::lockScheduler();\n   auto thread = OSGetCurrentThread();\n   decaf_check(thread->state == OSThreadState::Running);\n\n   internal::testThreadCancelNoLock();\n\n   if (mutex->owner == thread) {\n      mutex->count++;\n      internal::unlockScheduler();\n      return TRUE;\n   } else if (mutex->owner) {\n      internal::unlockScheduler();\n      return FALSE;\n   }\n\n   // Set thread to owner of mutex\n   mutex->count++;\n   mutex->owner = thread;\n   MutexQueue::append(virt_addrof(thread->mutexQueue), mutex);\n   thread->cancelState |= OSThreadCancelState::DisabledByMutex;\n\n   internal::unlockScheduler();\n   return TRUE;\n}\n\n\n/**\n * Unlocks the mutex.\n *\n * Will decrease the recursion count, will only unlock the mutex when the\n * recursion count reaches 0.\n * If any other threads are waiting to lock the mutex they will be woken.\n *\n * Similar to <a href=\"http://en.cppreference.com/w/cpp/thread/recursive_mutex/unlock\">std::recursive_mutex::unlock</a>.\n */\nvoid\nOSUnlockMutex(virt_ptr<OSMutex> mutex)\n{\n   internal::lockScheduler();\n   auto thread = OSGetCurrentThread();\n   decaf_check(thread->state == OSThreadState::Running);\n\n   // Not the owner, ignore this call.\n   if (mutex->owner != thread) {\n      internal::unlockScheduler();\n      return;\n   }\n\n   // Decrement the mutexes lock count\n   mutex->count--;\n\n   // If we still own the mutex, lets just leave now\n   if (mutex->count > 0) {\n      internal::unlockScheduler();\n      return;\n   }\n\n   // Remove mutex from thread's mutex queue\n   MutexQueue::erase(virt_addrof(thread->mutexQueue), mutex);\n\n   // Clear the mutex owner\n   mutex->owner = nullptr;\n\n   // If we have a promoted priority, reset it.\n   if (thread->priority < thread->basePriority) {\n      thread->priority = internal::calculateThreadPriorityNoLock(thread);\n   }\n\n   // Clear the cancelState flag if we don't have any more mutexes locked\n   if (!thread->mutexQueue.head) {\n      thread->cancelState &= ~OSThreadCancelState::DisabledByMutex;\n   }\n\n   // Wakeup any threads trying to lock this mutex\n   internal::wakeupThreadNoLock(virt_addrof(mutex->queue));\n\n   // Check if we are meant to cancel now\n   internal::testThreadCancelNoLock();\n\n   // Reschedule everyone\n   internal::rescheduleAllCoreNoLock();\n\n   // Unlock our scheduler and continue\n   internal::unlockScheduler();\n}\n\n\n/**\n * Initialise a condition variable structure.\n */\nvoid\nOSInitCond(virt_ptr<OSCondition> condition)\n{\n   OSInitCondEx(condition, nullptr);\n}\n\n\n/**\n * Initialise a condition variable structure with a name.\n */\nvoid\nOSInitCondEx(virt_ptr<OSCondition> condition,\n             virt_ptr<const char> name)\n{\n   condition->tag = OSCondition::Tag;\n   condition->name = name;\n   OSInitThreadQueueEx(virt_addrof(condition->queue), condition);\n}\n\n\n/**\n * Sleep the current thread until the condition variable has been signalled.\n *\n * The mutex must be locked when entering this function.\n * Will unlock the mutex and then sleep, reacquiring the mutex when woken.\n *\n * Similar to <a href=\"http://en.cppreference.com/w/cpp/thread/condition_variable/wait\">std::condition_variable::wait</a>.\n */\nvoid\nOSWaitCond(virt_ptr<OSCondition> condition,\n           virt_ptr<OSMutex> mutex)\n{\n   internal::lockScheduler();\n   auto thread = OSGetCurrentThread();\n   decaf_check(thread->state == OSThreadState::Running);\n   decaf_check(mutex->owner == thread);\n\n   // Save the count and then unlock the mutex\n   auto mutexCount = mutex->count;\n   mutex->count = 0;\n   mutex->owner = nullptr;\n\n   // Remove mutex from thread's mutex queue\n   MutexQueue::erase(virt_addrof(thread->mutexQueue), mutex);\n\n   // If we have a promoted priority, reset it.\n   if (thread->priority < thread->basePriority) {\n      thread->priority = internal::calculateThreadPriorityNoLock(thread);\n   }\n\n   // Wake anyone waiting on the mutex\n   internal::disableScheduler();\n   internal::wakeupThreadNoLock(virt_addrof(mutex->queue));\n   internal::rescheduleAllCoreNoLock();\n   internal::enableScheduler();\n\n   // Sleep on the condition\n   internal::sleepThreadNoLock(virt_addrof(condition->queue));\n   internal::rescheduleSelfNoLock();\n\n   // Relock the mutex\n   lockMutexNoLock(mutex);\n   mutex->count = mutexCount;\n\n   internal::unlockScheduler();\n}\n\n\n/**\n * Will wake up any threads waiting on the condition with OSWaitCond.\n *\n * Similar to <a href=\"http://en.cppreference.com/w/cpp/thread/condition_variable/notify_all\">std::condition_variable::notify_all</a>.\n */\nvoid\nOSSignalCond(virt_ptr<OSCondition> condition)\n{\n   OSWakeupThread(virt_addrof(condition->queue));\n}\n\nnamespace internal\n{\n\nvoid\nunlockAllMutexNoLock(virt_ptr<OSThread> thread)\n{\n   while (auto mutex = thread->mutexQueue.head) {\n      // Remove this mutex from our queue\n      MutexQueue::erase(virt_addrof(thread->mutexQueue), mutex);\n\n      // Release this mutex\n      mutex->count = 0;\n      mutex->owner = nullptr;\n\n      // Wakeup any threads trying to lock this mutex\n      internal::wakeupThreadNoLock(virt_addrof(mutex->queue));\n   }\n}\n\n} // namespace internal\n\n\nvoid\nLibrary::registerMutexSymbols()\n{\n   RegisterFunctionExport(OSInitMutex);\n   RegisterFunctionExport(OSInitMutexEx);\n   RegisterFunctionExport(OSLockMutex);\n   RegisterFunctionExport(OSTryLockMutex);\n   RegisterFunctionExport(OSUnlockMutex);\n   RegisterFunctionExport(OSInitCond);\n   RegisterFunctionExport(OSInitCondEx);\n   RegisterFunctionExport(OSWaitCond);\n   RegisterFunctionExport(OSSignalCond);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_mutex.h",
    "content": "#pragma once\n#include \"coreinit_thread.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_mutex Mutex\n * \\ingroup coreinit\n *\n * Standard mutex and condition variable implementation.\n *\n * Similar to <a href=\"http://en.cppreference.com/w/cpp/thread/condition_variable\">std::condition_variable</a>.\n * Similar to <a href=\"http://en.cppreference.com/w/cpp/thread/recursive_mutex\">std::recursive_mutex</a>.\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSMutex;\n\nstruct OSMutexLink\n{\n   be2_virt_ptr<OSMutex> next;\n   be2_virt_ptr<OSMutex> prev;\n};\nCHECK_OFFSET(OSMutexLink, 0x00, next);\nCHECK_OFFSET(OSMutexLink, 0x04, prev);\nCHECK_SIZE(OSMutexLink, 0x8);\n\nstruct OSMutex\n{\n   static constexpr auto Tag = 0x6D557458u;\n\n   //! Should always be set to the value OSMutex::Tag.\n   be2_val<uint32_t> tag;\n\n   //! Name set by OSInitMutexEx.\n   be2_virt_ptr<const char> name;\n\n   UNKNOWN(4);\n\n   //! Queue of threads waiting for this mutex to unlock.\n   be2_struct<OSThreadQueue> queue;\n\n   //! Current owner of mutex.\n   be2_virt_ptr<OSThread> owner;\n\n   //! Current recursion lock count of mutex.\n   be2_val<int32_t> count;\n\n   //! Link used inside OSThread's mutex queue.\n   be2_struct<OSMutexLink> link;\n};\nCHECK_OFFSET(OSMutex, 0x00, tag);\nCHECK_OFFSET(OSMutex, 0x04, name);\nCHECK_OFFSET(OSMutex, 0x0c, queue);\nCHECK_OFFSET(OSMutex, 0x1c, owner);\nCHECK_OFFSET(OSMutex, 0x20, count);\nCHECK_OFFSET(OSMutex, 0x24, link);\nCHECK_SIZE(OSMutex, 0x2c);\n\nstruct OSCondition\n{\n   static constexpr auto Tag = 0x634E6456u;\n\n   //! Should always be set to the value OSCondition::Tag.\n   be2_val<uint32_t> tag;\n\n   //! Name set by OSInitCondEx.\n   be2_virt_ptr<const char> name;\n\n   UNKNOWN(4);\n\n   //! Queue of threads currently waiting on condition with OSWaitCond.\n   be2_struct<OSThreadQueue> queue;\n};\nCHECK_OFFSET(OSCondition, 0x00, tag);\nCHECK_OFFSET(OSCondition, 0x04, name);\nCHECK_OFFSET(OSCondition, 0x0c, queue);\nCHECK_SIZE(OSCondition, 0x1c);\n\n#pragma pack(pop)\n\nvoid\nOSInitMutex(virt_ptr<OSMutex> mutex);\n\nvoid\nOSInitMutexEx(virt_ptr<OSMutex> mutex,\n              virt_ptr<const char> name);\n\nvoid\nOSLockMutex(virt_ptr<OSMutex> mutex);\n\nvoid\nOSUnlockMutex(virt_ptr<OSMutex> mutex);\n\nBOOL\nOSTryLockMutex(virt_ptr<OSMutex> mutex);\n\nvoid\nOSInitCond(virt_ptr<OSCondition> condition);\n\nvoid\nOSInitCondEx(virt_ptr<OSCondition> condition,\n             virt_ptr<const char> name);\n\nvoid\nOSWaitCond(virt_ptr<OSCondition> condition,\n           virt_ptr<OSMutex> mutex);\n\nvoid\nOSSignalCond(virt_ptr<OSCondition> condition);\n\n/** @} */\n\nnamespace internal\n{\n\nvoid\nunlockAllMutexNoLock(virt_ptr<OSThread> thread);\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_osreport.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_cosreport.h\"\n#include \"coreinit_dynload.h\"\n#include \"coreinit_ghs.h\"\n#include \"coreinit_osreport.h\"\n#include \"coreinit_snprintf.h\"\n#include \"coreinit_systeminfo.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n\n#include <fmt/format.h>\n#include <common/log.h>\n#include <common/strutils.h>\n#include <iterator>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nvoid\nOSReport(virt_ptr<const char> fmt,\n         var_args args)\n{\n   auto vaList = make_va_list(args);\n   COSVReport(COSReportModule::Unknown0, COSReportLevel::Error, fmt, vaList);\n   free_va_list(vaList);\n}\n\nvoid\nOSReportInfo(virt_ptr<const char> fmt,\n             var_args args)\n{\n   if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Info) {\n      auto vaList = make_va_list(args);\n      COSVReport(COSReportModule::Unknown0, COSReportLevel::Info, fmt, vaList);\n      free_va_list(vaList);\n   }\n}\n\nvoid\nOSReportVerbose(virt_ptr<const char> fmt,\n                var_args args)\n{\n   if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Verbose) {\n      auto vaList = make_va_list(args);\n      COSVReport(COSReportModule::Unknown0, COSReportLevel::Verbose, fmt, vaList);\n      free_va_list(vaList);\n   }\n}\n\nvoid\nOSReportWarn(virt_ptr<const char> fmt,\n             var_args args)\n{\n   if (OSGetAppFlags().debugLevel() >= OSAppFlagsDebugLevel::Warn) {\n      auto vaList = make_va_list(args);\n      COSVReport(COSReportModule::Unknown0, COSReportLevel::Warn, fmt, vaList);\n      free_va_list(vaList);\n   }\n}\n\nvoid\nOSVReport(virt_ptr<const char> fmt,\n          virt_ptr<va_list> vaList)\n{\n   COSVReport(COSReportModule::Unknown0, COSReportLevel::Error, fmt, vaList);\n}\n\nvoid\nOSPanic(virt_ptr<const char> file,\n        int32_t line,\n        virt_ptr<const char> fmt,\n        var_args args)\n{\n   auto buffer = StackArray<char, 1024> { };\n\n   auto vaList = make_va_list(args);\n   size_t size = internal::formatStringV(buffer, buffer.size(), fmt, vaList);\n   free_va_list(vaList);\n\n   internal::OSPanic(file.get(), line,\n                     std::string_view { &buffer[0], size });\n}\n\nvoid\nOSSendFatalError(virt_ptr<OSFatalError> error,\n                 virt_ptr<const char> functionName,\n                 uint32_t line)\n{\n   if (error) {\n      if (functionName) {\n         string_copy(virt_addrof(error->functionName).get(),\n                     error->functionName.size(),\n                     functionName.get(),\n                     error->functionName.size());\n         error->functionName[error->functionName.size() - 1] = char { 0 };\n      } else {\n         error->functionName[0] = char { 0 };\n      }\n\n      error->line = line;\n   }\n\n   // TODO: Kernel call 0x6C00 systemFatal\n   gLog->error(\"SystemFatal: messageType:       {}\", error->messageType);\n   gLog->error(\"SystemFatal: errorCode:         {}\", error->errorCode);\n   gLog->error(\"SystemFatal: internalErrorCode: {}\", error->internalErrorCode);\n   gLog->error(\"SystemFatal: processId:         {}\", error->processId);\n   gLog->error(\"SystemFatal: functionName:      {}\", virt_addrof(error->functionName));\n   gLog->error(\"SystemFatal: line:              {}\", error->line);\n   ghs_exit(-1);\n}\n\nvoid\nOSConsoleWrite(virt_ptr<const char> msg,\n               uint32_t size)\n{\n   gLog->info(\"[OSConsoleWrite] {}\",\n              std::string_view { msg.get(), size });\n}\n\nnamespace internal\n{\n\nvoid\nOSPanic(std::string_view file,\n        unsigned line,\n        std::string_view msg)\n{\n   auto symbolNameBuffer = StackArray<char, 256> { };\n   gLog->error(\"OSPanic in \\\"{}\\\" at line {}: {}.\", file, line, msg);\n\n   // Format a guest stack trace\n   auto core = cpu::this_core::state();\n   auto stackAddress = virt_addr { core->systemCallStackHead };\n   auto stackTraceBuffer = fmt::memory_buffer { };\n   fmt::format_to(std::back_inserter(stackTraceBuffer), \"Guest stack trace:\\n\");\n\n   for (auto i = 0; i < 16; ++i) {\n      if (!stackAddress || stackAddress == virt_addr { 0xFFFFFFFF }) {\n         break;\n      }\n\n      auto backchain = virt_cast<virt_addr *>(stackAddress)[0];\n      auto address = virt_cast<virt_addr *>(stackAddress)[1];\n      fmt::format_to(std::back_inserter(stackTraceBuffer),\n                     \"{}: {}\", stackAddress, address);\n\n      auto symbolAddress = OSGetSymbolName(address, symbolNameBuffer, 256);\n      if (symbolAddress) {\n         fmt::format_to(std::back_inserter(stackTraceBuffer),\n                        \" {}+0x{:X}\", symbolNameBuffer.get(),\n                        static_cast<uint32_t>(address - symbolAddress));\n      }\n\n      fmt::format_to(std::back_inserter(stackTraceBuffer), \"\\n\");\n      stackAddress = backchain;\n   }\n\n   gLog->error(\"{}\", fmt::to_string(stackTraceBuffer));\n   ghs_PPCExit(-1);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerOsReportSymbols()\n{\n   RegisterFunctionExport(OSReport);\n   RegisterFunctionExport(OSReportWarn);\n   RegisterFunctionExport(OSReportInfo);\n   RegisterFunctionExport(OSReportVerbose);\n   RegisterFunctionExport(OSVReport);\n   RegisterFunctionExport(OSPanic);\n   RegisterFunctionExport(OSSendFatalError);\n   RegisterFunctionExport(OSConsoleWrite);\n   RegisterFunctionExportName(\"__OSConsoleWrite\", OSConsoleWrite);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_osreport.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::coreinit\n{\n\n// TODO@: Enum with unknown values\nusing OSFatalErrorMessageType = uint32_t;\n\nstruct OSFatalError\n{\n   be2_val<OSFatalErrorMessageType> messageType;\n   be2_val<uint32_t> errorCode;\n   be2_val<uint32_t> processId;\n   be2_val<uint32_t> internalErrorCode;\n   be2_val<uint32_t> line;\n   be2_array<char, 64> functionName;\n   UNKNOWN(0xD4 - 0x54);\n};\nCHECK_OFFSET(OSFatalError, 0x00, messageType);\nCHECK_OFFSET(OSFatalError, 0x04, errorCode);\nCHECK_OFFSET(OSFatalError, 0x08, processId);\nCHECK_OFFSET(OSFatalError, 0x0C, internalErrorCode);\nCHECK_OFFSET(OSFatalError, 0x10, line);\nCHECK_OFFSET(OSFatalError, 0x14, functionName);\nCHECK_SIZE(OSFatalError, 0xD4);\n\nvoid\nOSSendFatalError(virt_ptr<OSFatalError> error,\n                 virt_ptr<const char> functionName,\n                 uint32_t line);\n\nnamespace internal\n{\n\nvoid\nOSPanic(std::string_view file,\n        unsigned line,\n        std::string_view msg);\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_overlayarena.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_overlayarena.h\"\n#include \"cafe/kernel/cafe_kernel_mmu.h\"\n\nnamespace cafe::coreinit\n{\n\nstruct StaticOverlayArenaData\n{\n   be2_val<BOOL> enabled;\n   be2_val<virt_addr> baseAddress;\n   be2_val<uint32_t> size;\n};\n\nstatic virt_ptr<StaticOverlayArenaData>\nsOverlayArenaData = nullptr;\n\nBOOL\nOSIsEnabledOverlayArena()\n{\n   return sOverlayArenaData->enabled;\n}\n\nvoid\nOSEnableOverlayArena(uint32_t unk,\n                     virt_ptr<virt_addr> outAddr,\n                     virt_ptr<uint32_t> outSize)\n{\n   if (!sOverlayArenaData->enabled) {\n      auto overlayArena = kernel::enableOverlayArena();\n      sOverlayArenaData->baseAddress = overlayArena.first;\n      sOverlayArenaData->size = overlayArena.second;\n      sOverlayArenaData->enabled = TRUE;\n   }\n\n   OSGetOverlayArenaRange(outAddr, outSize);\n}\n\nvoid\nOSDisableOverlayArena()\n{\n   if (sOverlayArenaData->enabled) {\n      kernel::disableOverlayArena();\n      sOverlayArenaData->baseAddress = cpu::VirtualAddress { 0u };\n      sOverlayArenaData->size = 0u;\n      sOverlayArenaData->enabled = FALSE;\n   }\n}\n\nvoid\nOSGetOverlayArenaRange(virt_ptr<virt_addr> outAddr,\n                       virt_ptr<uint32_t> outSize)\n{\n   if (outAddr) {\n      *outAddr = sOverlayArenaData->baseAddress;\n   }\n\n   if (outSize) {\n      *outSize = sOverlayArenaData->size;\n   }\n}\n\nvoid\nLibrary::registerOverlayArenaSymbols()\n{\n   RegisterFunctionExport(OSIsEnabledOverlayArena);\n   RegisterFunctionExport(OSEnableOverlayArena);\n   RegisterFunctionExport(OSDisableOverlayArena);\n   RegisterFunctionExport(OSGetOverlayArenaRange);\n\n   RegisterDataInternal(sOverlayArenaData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_overlayarena.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nBOOL\nOSIsEnabledOverlayArena();\n\nvoid\nOSEnableOverlayArena(uint32_t unk,\n                     virt_ptr<virt_addr> outAddr,\n                     virt_ptr<uint32_t> outSize);\n\nvoid\nOSDisableOverlayArena();\n\nvoid\nOSGetOverlayArenaRange(virt_ptr<virt_addr> outAddr,\n                       virt_ptr<uint32_t> outSize);\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_rendezvous.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_rendezvous.h\"\n#include \"coreinit_time.h\"\n#include <libcpu/cpu_control.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * Initialise a rendezvous structure.\n */\nvoid\nOSInitRendezvous(virt_ptr<OSRendezvous> rendezvous)\n{\n   rendezvous->core[0].store(0, std::memory_order_release);\n   rendezvous->core[1].store(0, std::memory_order_release);\n   rendezvous->core[2].store(0, std::memory_order_release);\n}\n\n\n/**\n * Wait on a rendezvous with infinite timeout.\n */\nBOOL\nOSWaitRendezvous(virt_ptr<OSRendezvous> rendezvous,\n                 uint32_t coreMask)\n{\n   return OSWaitRendezvousWithTimeout(rendezvous, coreMask, -1);\n}\n\n\n/**\n * Wait on a rendezvous with a timeout.\n *\n * This will wait with a timeout until all cores matching coreMask have\n * reached the rendezvous point.\n *\n * \\return Returns TRUE on success, FALSE on timeout.\n */\nBOOL\nOSWaitRendezvousWithTimeout(virt_ptr<OSRendezvous> rendezvous,\n                            uint32_t coreMask,\n                            OSTimeNanoseconds timeoutNS)\n{\n   auto core = OSGetCoreId();\n   auto success = FALSE;\n   auto endTime = OSGetTime() + internal::nsToTicks(timeoutNS);\n\n   auto waitCore0 = (coreMask & (1 << 0)) != 0;\n   auto waitCore1 = (coreMask & (1 << 1)) != 0;\n   auto waitCore2 = (coreMask & (1 << 2)) != 0;\n\n   // Set our core flag\n   rendezvous->core[core].store(1, std::memory_order_release);\n\n   do {\n      if (waitCore0 && rendezvous->core[0].load(std::memory_order_acquire)) {\n         waitCore0 = false;\n      }\n\n      if (waitCore1 && rendezvous->core[1].load(std::memory_order_acquire)) {\n         waitCore1 = false;\n      }\n\n      if (waitCore2 && rendezvous->core[2].load(std::memory_order_acquire)) {\n         waitCore2 = false;\n      }\n\n      if (!waitCore0 && !waitCore1 && !waitCore2) {\n         success = TRUE;\n         break;\n      }\n\n      if (timeoutNS != -1 && OSGetTime() >= endTime) {\n         break;\n      }\n\n      // We must manually check for interrupts here, as we are busy-looping.\n      //  Note that this is only safe as no locks are held during the wait.\n      cpu::this_core::checkInterrupts();\n      // TODO: Change this to something like cafe::kernel::checkInterrupts\n   } while (true);\n\n   return success;\n}\n\n\nvoid\nLibrary::registerRendezvousSymbols()\n{\n   RegisterFunctionExport(OSInitRendezvous);\n   RegisterFunctionExport(OSWaitRendezvous);\n   RegisterFunctionExport(OSWaitRendezvousWithTimeout);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_rendezvous.h",
    "content": "#pragma once\n#include \"coreinit_core.h\"\n#include \"coreinit_time.h\"\n#include <atomic>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_fiber Fiber\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSRendezvous\n{\n   std::atomic<uint32_t> core[CoreCount];\n   UNKNOWN(4);\n};\nCHECK_OFFSET(OSRendezvous, 0x00, core);\nCHECK_SIZE(OSRendezvous, 0x10);\n\n#pragma pack(pop)\n\nvoid\nOSInitRendezvous(virt_ptr<OSRendezvous> rendezvous);\n\nBOOL\nOSWaitRendezvous(virt_ptr<OSRendezvous> rendezvous,\n                 uint32_t coreMask);\n\nBOOL\nOSWaitRendezvousWithTimeout(virt_ptr<OSRendezvous> rendezvous,\n                            uint32_t coreMask,\n                            OSTimeNanoseconds timeout);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_scheduler.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_fastmutex.h\"\n#include \"coreinit_interrupts.h\"\n#include \"coreinit_internal_idlock.h\"\n#include \"coreinit_internal_queue.h\"\n#include \"coreinit_mutex.h\"\n#include \"coreinit_scheduler.h\"\n#include \"coreinit_thread.h\"\n\n#include \"cafe/kernel/cafe_kernel_context.h\"\n#include \"debugger/debugger.h\"\n\n#include <array>\n#include <atomic>\n#include <chrono>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <fmt/format.h>\n#include <iterator>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nstruct StaticSchedulerData\n{\n   struct PerCoreData\n   {\n      be2_val<bool> schedulerEnabled;\n      be2_struct<OSThreadQueue> runQueue;\n      be2_virt_ptr<OSThread> currentThread;\n      std::chrono::time_point<std::chrono::high_resolution_clock> lastSwitchTime;\n      std::chrono::time_point<std::chrono::high_resolution_clock> pauseTime;\n   };\n\n   internal::IdLock schedulerLock;\n   be2_struct<OSThreadQueue> activeThreadQueue;\n   be2_array<PerCoreData, 3> perCoreData;\n};\n\nstatic virt_ptr<StaticSchedulerData>\nsSchedulerData = nullptr;\n\nnamespace internal\n{\n\nusing ActiveQueue = Queue<OSThreadQueue, OSThreadLink, OSThread, &OSThread::activeLink>;\nusing CoreRunQueue0 = SortedQueue<OSThreadQueue, OSThreadLink, OSThread, &OSThread::coreRunQueueLink0, ThreadIsLess>;\nusing CoreRunQueue1 = SortedQueue<OSThreadQueue, OSThreadLink, OSThread, &OSThread::coreRunQueueLink1, ThreadIsLess>;\nusing CoreRunQueue2 = SortedQueue<OSThreadQueue, OSThreadLink, OSThread, &OSThread::coreRunQueueLink2, ThreadIsLess>;\n\nvirt_ptr<OSThread>\ngetCoreRunningThread(uint32_t coreId)\n{\n   if (!sSchedulerData) {\n      return nullptr;\n   }\n\n   return sSchedulerData->perCoreData[coreId].currentThread;\n}\n\nuint64_t\ngetCoreThreadRunningTime(uint32_t coreId)\n{\n   auto &perCoreData = sSchedulerData->perCoreData[coreId];\n   auto now = std::chrono::high_resolution_clock::now();\n\n   if (perCoreData.pauseTime != std::chrono::time_point<std::chrono::high_resolution_clock>::max()) {\n      now = perCoreData.pauseTime;\n   }\n\n   return (now - perCoreData.lastSwitchTime).count();\n}\n\nvoid\npauseCoreTime(bool isPaused)\n{\n   auto coreId = cpu::this_core::id();\n   auto &perCoreData = sSchedulerData->perCoreData[coreId];\n   auto now = std::chrono::high_resolution_clock::now();\n\n   if (isPaused) {\n      perCoreData.pauseTime = now;\n   } else {\n      perCoreData.lastSwitchTime += now - perCoreData.pauseTime;\n      perCoreData.pauseTime = std::chrono::time_point<std::chrono::high_resolution_clock>::max();\n   }\n}\n\nvirt_ptr<OSThread>\ngetFirstActiveThread()\n{\n   if (!sSchedulerData) {\n      return nullptr;\n   }\n\n   return sSchedulerData->activeThreadQueue.head;\n}\n\nvirt_ptr<OSThread>\ngetCurrentThread()\n{\n   return getCoreRunningThread(cpu::this_core::id());\n}\n\nvoid\nlockScheduler()\n{\n   internal::acquireIdLockWithCoreId(sSchedulerData->schedulerLock);\n}\n\nbool\nisSchedulerLocked()\n{\n   return internal::isHoldingIdLockWithCoreId(sSchedulerData->schedulerLock);\n}\n\nvoid\nunlockScheduler()\n{\n   internal::releaseIdLockWithCoreId(sSchedulerData->schedulerLock);\n}\n\nbool\nisSchedulerEnabled()\n{\n   auto coreId = cpu::this_core::id();\n   return sSchedulerData->perCoreData[coreId].schedulerEnabled;\n}\n\nvoid\nenableScheduler()\n{\n   auto coreId = cpu::this_core::id();\n   sSchedulerData->perCoreData[coreId].schedulerEnabled = true;\n}\n\nvoid\ndisableScheduler()\n{\n   auto coreId = cpu::this_core::id();\n   sSchedulerData->perCoreData[coreId].schedulerEnabled = false;\n}\n\nvoid\nmarkThreadActiveNoLock(virt_ptr<OSThread> thread)\n{\n   auto activeThreadQueue = virt_addrof(sSchedulerData->activeThreadQueue);\n   decaf_check(!ActiveQueue::contains(activeThreadQueue, thread));\n   ActiveQueue::append(activeThreadQueue, thread);\n   checkActiveThreadsNoLock();\n}\n\nvoid\nmarkThreadInactiveNoLock(virt_ptr<OSThread> thread)\n{\n   auto activeThreadQueue = virt_addrof(sSchedulerData->activeThreadQueue);\n   decaf_check(ActiveQueue::contains(activeThreadQueue, thread));\n   ActiveQueue::erase(activeThreadQueue, thread);\n   checkActiveThreadsNoLock();\n}\n\nbool\nisThreadActiveNoLock(virt_ptr<OSThread> thread)\n{\n   if (thread->state == OSThreadState::None) {\n      return false;\n   }\n\n   auto activeThreadQueue = virt_addrof(sSchedulerData->activeThreadQueue);\n   return ActiveQueue::contains(activeThreadQueue, thread);\n}\n\nstatic void\nqueueThreadNoLock(virt_ptr<OSThread> thread)\n{\n   decaf_check(isSchedulerLocked());\n   decaf_check(!OSIsThreadSuspended(thread));\n   decaf_check(thread->state == OSThreadState::Ready);\n\n   // Schedule this thread on any cores which can run it!\n   if (thread->attr & OSThreadAttributes::AffinityCPU0) {\n      CoreRunQueue0::insert(virt_addrof(sSchedulerData->perCoreData[0].runQueue), thread);\n   }\n\n   if (thread->attr & OSThreadAttributes::AffinityCPU1) {\n      CoreRunQueue1::insert(virt_addrof(sSchedulerData->perCoreData[1].runQueue), thread);\n   }\n\n   if (thread->attr & OSThreadAttributes::AffinityCPU2) {\n      CoreRunQueue2::insert(virt_addrof(sSchedulerData->perCoreData[2].runQueue), thread);\n   }\n}\n\nstatic void\nunqueueThreadNoLock(virt_ptr<OSThread> thread)\n{\n   CoreRunQueue0::erase(virt_addrof(sSchedulerData->perCoreData[0].runQueue), thread);\n   CoreRunQueue1::erase(virt_addrof(sSchedulerData->perCoreData[1].runQueue), thread);\n   CoreRunQueue2::erase(virt_addrof(sSchedulerData->perCoreData[2].runQueue), thread);\n}\n\nvoid\nsetThreadAffinityNoLock(virt_ptr<OSThread> thread, uint32_t affinity)\n{\n   thread->attr &= ~OSThreadAttributes::AffinityAny;\n   thread->attr |= affinity;\n\n   if (thread->state == OSThreadState::Ready) {\n      if (thread->suspendCounter == 0) {\n         unqueueThreadNoLock(thread);\n         queueThreadNoLock(thread);\n      }\n   }\n}\n\nstatic virt_ptr<OSThread>\npeekNextThreadNoLock(uint32_t core)\n{\n   decaf_check(isSchedulerLocked());\n   auto thread = sSchedulerData->perCoreData[core].runQueue.head;\n\n   if (thread) {\n      decaf_check(thread->state == OSThreadState::Ready);\n      decaf_check(thread->suspendCounter == 0);\n      decaf_check(thread->attr & static_cast<OSThreadAttributes>(1 << core));\n   }\n\n   return thread;\n}\n\nstatic void\nvalidateThread(virt_ptr<OSThread> thread)\n{\n   decaf_check(*thread->stackEnd == 0xDEADBABE);\n   decaf_check((thread->attr & OSThreadAttributes::AffinityAny) != 0);\n}\n\nint32_t\ncheckActiveThreadsNoLock()\n{\n   auto threadCount = 0;\n\n   // Count threads before this one\n   for (virt_ptr<OSThread> threadIter = sSchedulerData->activeThreadQueue.head; threadIter; threadIter = threadIter->activeLink.next) {\n      validateThread(threadIter);\n      threadCount++;\n   }\n\n   return threadCount;\n}\n\nvoid\ncheckRunningThreadNoLock(bool yielding)\n{\n   decaf_check(isSchedulerLocked());\n   auto coreId = cpu::this_core::id();\n   auto &perCoreData = sSchedulerData->perCoreData[coreId];\n   checkActiveThreadsNoLock();\n\n   if (!perCoreData.schedulerEnabled) {\n      return;\n   }\n\n   auto currThread = perCoreData.currentThread;\n   auto nextThread = peekNextThreadNoLock(coreId);\n   if (!currThread && !nextThread) {\n      // No coreinit threads running, we must be in the kernel WFI loop.\n      return;\n   }\n\n   auto switchTime = std::chrono::high_resolution_clock::now();\n   if (currThread) {\n      if (currThread->state == OSThreadState::Running) {\n         // If we're not ready to suspend check the priority vs next thread\n         if (currThread->suspendCounter <= 0) {\n            if (!nextThread) {\n               // There is no other viable thread, keep running current.\n               return;\n            }\n\n            if (currThread->priority < nextThread->priority) {\n               // Next thread has lower priority, keep running current.\n               return;\n            } else if (!yielding && currThread->priority == nextThread->priority) {\n               // Next thread has same priority, but we are not yielding.\n               return;\n            }\n         }\n\n         // Add thread back to run queue\n         currThread->state = OSThreadState::Ready;\n         queueThreadNoLock(currThread);\n      }\n\n      // Update thread run time\n      auto diff = switchTime - perCoreData.lastSwitchTime;\n      currThread->coreTimeConsumedNs += diff.count();\n   }\n\n   // Trace log the thread switch\n   if (gLog->should_log(Logger::Level::trace)) {\n      fmt::memory_buffer out;\n      fmt::format_to(std::back_inserter(out), \"Core {} leaving\", coreId);\n\n      if (currThread) {\n         fmt::format_to(std::back_inserter(out), \" thread {}\", currThread->id);\n\n         if (currThread->name) {\n            fmt::format_to(std::back_inserter(out), \" [{}]\", currThread->name);\n         }\n      } else {\n         fmt::format_to(std::back_inserter(out), \" idle\");\n      }\n\n      fmt::format_to(std::back_inserter(out), \" to\");\n\n      if (nextThread) {\n         fmt::format_to(std::back_inserter(out), \" thread {}\", nextThread->id);\n\n         if (nextThread->name) {\n            fmt::format_to(std::back_inserter(out), \" [{}]\", nextThread->name);\n         }\n      } else {\n         fmt::format_to(std::back_inserter(out), \" idle\");\n      }\n\n      gLog->trace(\"{}\", std::string_view { out.data(), out.size() });\n   }\n\n   if (nextThread) {\n      // Remove next thread from Run Queue\n      nextThread->state = OSThreadState::Running;\n      nextThread->wakeCount++;\n      unqueueThreadNoLock(nextThread);\n   }\n\n   // Switch thread\n   perCoreData.currentThread = nextThread;\n   perCoreData.lastSwitchTime = switchTime;\n\n   // Make sure interrupts are enabled\n   auto prevState = coreinit::OSEnableInterrupts();\n\n   internal::unlockScheduler();\n   kernel::switchContext(nextThread ? virt_addrof(nextThread->context) : nullptr);\n   internal::lockScheduler();\n\n   // Restore interrupts to whatever state they were in\n   coreinit::OSRestoreInterrupts(prevState);\n   checkActiveThreadsNoLock();\n}\n\nvoid\nrescheduleSelfNoLock()\n{\n   checkRunningThreadNoLock(false);\n}\n\nvoid\nrescheduleNoLock(uint32_t core)\n{\n   if (core == cpu::this_core::id()) {\n      rescheduleSelfNoLock();\n   } else {\n      cpu::interrupt(core, cpu::GENERIC_INTERRUPT);\n   }\n}\n\nvoid\nrescheduleOtherCoreNoLock()\n{\n   auto core = cpu::this_core::id();\n\n   for (auto i = 0u; i < 3; ++i) {\n      if (i != core) {\n         rescheduleNoLock(i);\n      }\n   }\n}\n\nvoid\nrescheduleAllCoreNoLock()\n{\n   // Reschedule other cores first, or we might exit early!\n   rescheduleOtherCoreNoLock();\n   rescheduleSelfNoLock();\n}\n\nint32_t\nresumeThreadNoLock(virt_ptr<OSThread> thread,\n                   int32_t counter)\n{\n   decaf_check(isThreadActiveNoLock(thread));\n\n   auto old = thread->suspendCounter;\n   thread->suspendCounter -= counter;\n\n   if (thread->suspendCounter < 0) {\n      thread->suspendCounter = 0;\n      return old;\n   }\n\n   if (thread->suspendCounter == 0) {\n      if (thread->state == OSThreadState::Ready) {\n         thread->priority = calculateThreadPriorityNoLock(thread);\n         queueThreadNoLock(thread);\n      }\n   }\n\n   return old;\n}\n\nvoid\nsetCoreRunningThread(uint32_t coreId,\n                     virt_ptr<OSThread> thread)\n{\n   sSchedulerData->perCoreData[coreId].currentThread = thread;\n}\n\nbool\nsetThreadRunQuantumNoLock(virt_ptr<OSThread> thread,\n                          OSTime ticks)\n{\n   decaf_check(isSchedulerLocked());\n   decaf_abort(\"Unsupported call to setThreadRunQuantumNoLock\");\n}\n\nvoid\nsleepThreadNoLock(virt_ptr<OSThreadQueue> queue)\n{\n   auto thread = OSGetCurrentThread();\n   decaf_check(thread->queue == nullptr);\n   decaf_check(thread->state == OSThreadState::Running);\n\n   thread->queue = queue;\n   thread->state = OSThreadState::Waiting;\n\n   if (queue) {\n      ThreadQueue::insert(queue, thread);\n   }\n}\n\nvoid\nsleepThreadNoLock(virt_ptr<OSThreadSimpleQueue> queue)\n{\n   // This is super-strange, it is used by OSFastMutex, and after a few\n   //  comparisons, I'm 99% sure they just cast...  I cast it here instead\n   //  of inside OSFastMutex so that its closer to the use above to help\n   //  ensure nobody mistakenly breaks it...\n   sleepThreadNoLock(virt_cast<OSThreadQueue *>(queue));\n}\n\nvoid\nsuspendThreadNoLock(virt_ptr<OSThread> thread)\n{\n   thread->requestFlag = OSThreadRequest::None;\n   thread->suspendCounter += thread->needSuspend;\n   thread->needSuspend = 0;\n   thread->state = OSThreadState::Ready;\n   wakeupThreadNoLock(virt_addrof(thread->suspendQueue));\n}\n\nvoid\ntestThreadCancelNoLock()\n{\n   auto thread = OSGetCurrentThread();\n\n   if (thread->cancelState == OSThreadCancelState::Enabled) {\n      if (thread->requestFlag == OSThreadRequest::Suspend) {\n         suspendThreadNoLock(thread);\n         rescheduleAllCoreNoLock();\n      }\n\n      if (thread->requestFlag == OSThreadRequest::Cancel) {\n         unlockScheduler();\n         OSExitThread(-1);\n      }\n   }\n}\n\nvoid\nwakeupOneThreadNoLock(virt_ptr<OSThread> thread)\n{\n   if (thread->state == OSThreadState::Running ||\n       thread->state == OSThreadState::Ready) {\n      // This thread is already running or ready\n      return;\n   }\n\n   decaf_check(thread->queue);\n\n   thread->state = OSThreadState::Ready;\n   ThreadQueue::erase(thread->queue, thread);\n   thread->queue = nullptr;\n   queueThreadNoLock(thread);\n}\n\nvoid\nwakeupThreadNoLock(virt_ptr<OSThreadQueue> queue)\n{\n   auto next = queue->head;\n\n   for (auto thread = next; next; thread = next) {\n      next = thread->link.next;\n      wakeupOneThreadNoLock(thread);\n   }\n}\n\nvoid\nwakeupThreadNoLock(virt_ptr<OSThreadSimpleQueue> queue)\n{\n   // See sleepThreadNoLock(OSSimpleQueue*) for more details on this hack.\n   wakeupThreadNoLock(virt_cast<OSThreadQueue *>(queue));\n}\n\nvoid\nwakeupThreadWaitForSuspensionNoLock(virt_ptr<OSThreadQueue> queue, int32_t suspendResult)\n{\n   for (auto thread = queue->head; thread; thread = thread->link.next) {\n      thread->suspendResult = suspendResult;\n      wakeupOneThreadNoLock(thread);\n   }\n\n   ThreadQueue::clear(queue);\n}\n\nint32_t\ncalculateThreadPriorityNoLock(virt_ptr<OSThread> thread)\n{\n   decaf_check(isSchedulerLocked());\n   auto priority = thread->basePriority;\n\n   // If thread is holding a spinlock, it is always highest priority\n   if (thread->context.spinLockCount > 0) {\n      return 0;\n   }\n\n   // For all mutex we own, boost our priority over anyone waiting to own our mutex\n   for (auto mutex = thread->mutexQueue.head; mutex; mutex = mutex->link.next) {\n      // We only need to check the head of mutex thread queue as it is in priority order\n      auto other = mutex->queue.head;\n\n      if (other && other->priority < priority) {\n         priority = other->priority;\n      }\n   }\n\n   // For all fast mutex we own, boost our priority over anyone waiting to own our fast mutex\n   for (auto fastMutex = thread->fastMutexQueue.head; fastMutex; fastMutex = fastMutex->link.next) {\n      // We only need to check the head of mutex thread queue as it is in priority order\n      auto other = fastMutex->queue.head;\n\n      if (other && other->priority < priority) {\n         priority = other->priority;\n      }\n   }\n\n   return priority;\n}\n\nvirt_ptr<OSThread>\nsetThreadActualPriorityNoLock(virt_ptr<OSThread> thread, int32_t priority)\n{\n   decaf_check(isSchedulerLocked());\n   thread->priority = priority;\n\n   if (thread->state == OSThreadState::Ready) {\n      if (thread->suspendCounter == 0) {\n         unqueueThreadNoLock(thread);\n         queueThreadNoLock(thread);\n      }\n   } else if (thread->state == OSThreadState::Waiting) {\n      // Move towards head of queue if needed\n      while (thread->link.prev && priority < thread->link.prev->priority) {\n         auto prev = thread->link.prev;\n         auto next = thread->link.next;\n\n         thread->link.prev = prev->link.prev;\n         thread->link.next = prev;\n\n         prev->link.prev = thread;\n         prev->link.next = next;\n\n         if (next) {\n            next->link.prev = prev;\n         }\n      }\n\n      // Move towards tail of queue if needed\n      while (thread->link.next && thread->link.next->priority < priority) {\n         auto prev = thread->link.prev;\n         auto next = thread->link.next;\n\n         thread->link.prev = next;\n         thread->link.next = next->link.next;\n\n         next->link.prev = prev;\n         next->link.next = thread;\n\n         if (prev) {\n            prev->link.next = next;\n         }\n      }\n\n      // If we are waiting for a mutex, return its owner\n      if (thread->mutex) {\n         return thread->mutex->owner;\n      }\n   }\n\n   return nullptr;\n}\n\nvoid\nupdateThreadPriorityNoLock(virt_ptr<OSThread> thread)\n{\n   // Update the threads priority, and any thread chain of mutex owners\n   while (thread) {\n      auto priority = calculateThreadPriorityNoLock(thread);\n      thread = setThreadActualPriorityNoLock(thread, priority);\n   }\n}\n\nvoid\npromoteThreadPriorityNoLock(virt_ptr<OSThread> thread,\n                            int32_t priority)\n{\n   while (thread && priority < thread->priority) {\n      thread = setThreadActualPriorityNoLock(thread, priority);\n   }\n}\n\nvoid\ninitialiseScheduler()\n{\n   OSInitThreadQueue(virt_addrof(sSchedulerData->activeThreadQueue));\n\n   for (auto i = 0u; i < sSchedulerData->perCoreData.size(); ++i) {\n      auto &perCoreData = sSchedulerData->perCoreData[i];\n      perCoreData.schedulerEnabled = true;\n      perCoreData.currentThread = nullptr;\n\n      OSInitThreadQueue(virt_addrof(perCoreData.runQueue));\n\n      perCoreData.lastSwitchTime = std::chrono::high_resolution_clock::now();\n      perCoreData.pauseTime = std::chrono::time_point<std::chrono::high_resolution_clock>::max();\n   }\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerSchedulerSymbols()\n{\n   RegisterDataInternal(sSchedulerData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_scheduler.h",
    "content": "#pragma once\n#include \"coreinit_time.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nstruct OSThread;\nstruct OSThreadQueue;\nstruct OSThreadSimpleQueue;\n\nnamespace internal\n{\n\nvirt_ptr<OSThread>\ngetCoreRunningThread(uint32_t coreId);\n\nuint64_t\ngetCoreThreadRunningTime(uint32_t coreId);\n\nvoid\npauseCoreTime(bool isPaused);\n\nvirt_ptr<OSThread>\ngetFirstActiveThread();\n\nvirt_ptr<OSThread>\ngetCurrentThread();\n\nvoid\nlockScheduler();\n\nbool\nisSchedulerLocked();\n\nvoid\nunlockScheduler();\n\nbool\nisSchedulerEnabled();\n\nvoid\nenableScheduler();\n\nvoid\ndisableScheduler();\n\nvoid\nmarkThreadActiveNoLock(virt_ptr<OSThread> thread);\n\nvoid\nmarkThreadInactiveNoLock(virt_ptr<OSThread> thread);\n\nbool\nisThreadActiveNoLock(virt_ptr<OSThread> thread);\n\nvoid\nsetThreadAffinityNoLock(virt_ptr<OSThread> thread,\n                        uint32_t affinity);\n\nint32_t\ncheckActiveThreadsNoLock();\n\nvoid\ncheckRunningThreadNoLock(bool yielding);\n\nvoid\nrescheduleNoLock(uint32_t core);\n\nvoid\nrescheduleSelfNoLock();\n\nvoid\nrescheduleOtherCoreNoLock();\n\nvoid\nrescheduleAllCoreNoLock();\n\nint32_t\nresumeThreadNoLock(virt_ptr<OSThread> thread,\n                   int32_t counter);\n\nvoid\nsetCoreRunningThread(uint32_t coreId,\n                     virt_ptr<OSThread> thread);\n\nbool\nsetThreadRunQuantumNoLock(virt_ptr<OSThread> thread,\n                          OSTime ticks);\n\nvoid\nsleepThreadNoLock(virt_ptr<OSThreadQueue> queue);\n\nvoid\nsleepThreadNoLock(virt_ptr<OSThreadSimpleQueue> queue);\n\nvoid\nsuspendThreadNoLock(virt_ptr<OSThread> thread);\n\nvoid\ntestThreadCancelNoLock();\n\nvoid\nwakeupOneThreadNoLock(virt_ptr<OSThread> thread);\n\nvoid\nwakeupThreadNoLock(virt_ptr<OSThreadQueue> queue);\n\nvoid\nwakeupThreadNoLock(virt_ptr<OSThreadSimpleQueue> queue);\n\nvoid\nwakeupThreadWaitForSuspensionNoLock(virt_ptr<OSThreadQueue> queue,\n                                    int32_t suspendResult);\n\nint32_t\ncalculateThreadPriorityNoLock(virt_ptr<OSThread> thread);\n\nvirt_ptr<OSThread>\nsetThreadActualPriorityNoLock(virt_ptr<OSThread> thread,\n                              int32_t priority);\n\nvoid\nupdateThreadPriorityNoLock(virt_ptr<OSThread> thread);\n\nvoid\npromoteThreadPriorityNoLock(virt_ptr<OSThread> thread,\n                            int32_t priority);\n\nvoid\ninitialiseScheduler();\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_screen.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_memory.h\"\n#include \"coreinit_screen.h\"\n#include \"coreinit_screenfont.h\"\n\n#include \"cafe/libraries/gx2/gx2_display.h\"\n#include \"cafe/libraries/gx2/gx2_event.h\"\n#include \"cafe/libraries/gx2/gx2_cbpool.h\"\n#include \"cafe/libraries/gx2/gx2_state.h\"\n\n#include <array>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <libgpu/latte/latte_pm4_commands.h>\n\nnamespace cafe::coreinit\n{\n\nstruct ScreenSize\n{\n   uint32_t width;\n   uint32_t height;\n   uint32_t pitch;\n};\n\nstatic const auto\nBytesPerPixel = 4;\n\nstatic const ScreenSize\nsScreenSizes[] =\n{\n   ScreenSize { 1280, 720, 1280 },\n   ScreenSize { 854, 480, 854 }\n};\n\nstruct StaticScreenData\n{\n   be2_array<virt_ptr<uint32_t>, OSScreenID::Max> buffers;\n};\n\nstatic virt_ptr<StaticScreenData>\nsScreenData = nullptr;\n\nvoid\nOSScreenInit()\n{\n   gx2::GX2Init(nullptr);\n\n   gx2::GX2SetTVBuffer(nullptr,\n                       OSScreenGetBufferSizeEx(OSScreenID::TV),\n                       gx2::GX2TVRenderMode::Wide720p,\n                       gx2::GX2SurfaceFormat::UNORM_R8_G8_B8_A8,\n                       gx2::GX2BufferingMode::Single);\n\n   gx2::GX2SetDRCBuffer(nullptr,\n                       OSScreenGetBufferSizeEx(OSScreenID::TV),\n                       gx2::GX2DrcRenderMode::Single,\n                       gx2::GX2SurfaceFormat::UNORM_R8_G8_B8_A8,\n                       gx2::GX2BufferingMode::Single);\n\n   sScreenData->buffers.fill(nullptr);\n}\n\nuint32_t\nOSScreenGetBufferSizeEx(OSScreenID id)\n{\n   decaf_check(id < OSScreenID::Max);\n   auto &size = sScreenSizes[id];\n   return size.pitch * size.height * BytesPerPixel;\n}\n\nvoid\nOSScreenEnableEx(OSScreenID id,\n                 BOOL enable)\n{\n}\n\nvoid\nOSScreenClearBufferEx(OSScreenID id,\n                      uint32_t colour)\n{\n   decaf_check(id < OSScreenID::Max);\n   auto size = OSScreenGetBufferSizeEx(id) / 4;\n   auto buffer = sScreenData->buffers[id];\n\n   // Force alpha to 255\n   colour |= 0xff000000;\n\n   for (auto i = 0u; i < size; ++i) {\n      buffer[i] = colour;\n   }\n}\n\nvoid\nOSScreenSetBufferEx(OSScreenID id,\n                    virt_ptr<void> addr)\n{\n   decaf_check(id < OSScreenID::Max);\n   sScreenData->buffers[id] = virt_cast<uint32_t *>(addr);\n}\n\nvoid\nOSScreenPutPixelEx(OSScreenID id,\n                   uint32_t x,\n                   uint32_t y,\n                   uint32_t colour)\n{\n   decaf_check(id < OSScreenID::Max);\n   auto buffer = sScreenData->buffers[id];\n   auto size = sScreenSizes[id];\n\n   // Force alpha to 255\n   colour |= 0xff000000;\n\n   if (buffer && x < size.width && y < size.height) {\n      auto offset = x + y * size.pitch;\n      buffer[offset] = colour;\n   }\n}\n\nstatic void\nputChar(OSScreenID id,\n        uint32_t x,\n        uint32_t y,\n        char chr)\n{\n   auto index = chr & 0x7F;\n\n   if (index < ' ') {\n      index = 0;\n   } else {\n      index -= ' ';\n   }\n\n   auto font = sScreenFontBitmap + index * sScreenFontPitch;\n\n   for (auto v = 0; v < sScreenFontHeight; ++v) {\n      for (auto h = 0; h < sScreenFontWidth; ++h) {\n         auto bitmap = font[v * 2 + h / 8];\n         auto bit = bitmap >> (h % 8);\n\n         if (bit & 1) {\n            OSScreenPutPixelEx(id, x + h, y + v, 0xFFFFFFFF);\n         }\n      }\n   }\n}\n\nvoid\nOSScreenPutFontEx(OSScreenID id,\n                  uint32_t row,\n                  uint32_t column,\n                  virt_ptr<const char> msg)\n{\n   static const auto offsetX = 50;\n   static const auto offsetY = 32;\n   static const auto adjustX = 12;\n   static const auto adjustY = 24;\n   auto x = offsetX + row * adjustX;\n   auto y = offsetY + column * adjustY;\n\n   while (msg && *msg) {\n      putChar(id, x, y, *msg);\n      x += adjustX;\n      msg++;\n   }\n}\n\nvoid\nOSScreenFlipBuffersEx(OSScreenID id)\n{\n   decaf_check(id < OSScreenID::Max);\n   auto buffer = sScreenData->buffers[id];\n\n   // Send the custom flip command\n   gx2::internal::writePM4(latte::pm4::DecafOSScreenFlip {\n      static_cast<uint32_t>(id),\n      OSEffectiveToPhysical(virt_cast<virt_addr>(buffer))\n   });\n\n   // Wait until flip\n   gx2::GX2Flush();\n   gx2::GX2WaitForFlip();\n}\n\nvoid\nLibrary::registerScreenSymbols()\n{\n   RegisterFunctionExport(OSScreenInit);\n   RegisterFunctionExport(OSScreenGetBufferSizeEx);\n   RegisterFunctionExport(OSScreenEnableEx);\n   RegisterFunctionExport(OSScreenClearBufferEx);\n   RegisterFunctionExport(OSScreenSetBufferEx);\n   RegisterFunctionExport(OSScreenPutPixelEx);\n   RegisterFunctionExport(OSScreenPutFontEx);\n   RegisterFunctionExport(OSScreenFlipBuffersEx);\n\n   RegisterDataInternal(sScreenData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_screen.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nenum OSScreenID : uint32_t\n{\n   TV = 0,\n   DRC = 1,\n   Max\n};\n\nvoid\nOSScreenInit();\n\nuint32_t\nOSScreenGetBufferSizeEx(OSScreenID id);\n\nvoid\nOSScreenEnableEx(OSScreenID id,\n                 BOOL enable);\nvoid\nOSScreenClearBufferEx(OSScreenID id,\n                      uint32_t colour);\nvoid\nOSScreenSetBufferEx(OSScreenID id,\n                    virt_ptr<void> addr);\nvoid\nOSScreenPutPixelEx(OSScreenID id,\n                   uint32_t x,\n                   uint32_t y,\n                   uint32_t colour);\n\nvoid\nOSScreenPutFontEx(OSScreenID id,\n                  uint32_t row,\n                  uint32_t column,\n                  virt_ptr<const char> msg);\nvoid\nOSScreenFlipBuffersEx(OSScreenID id);\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_screenfont.h",
    "content": "#pragma once\n#include <cstdint>\n\nstatic const uint32_t\nsScreenFontWidth = 12;\n\nstatic const uint32_t\nsScreenFontHeight = 19;\n\nstatic const uint32_t\nsScreenFontPitch = 38;\n\n// Character bitmaps for Deja Vu Sans Mono 16pt generated by The Dot Factory\nstatic const uint8_t\nsScreenFontBitmap[] =\n{\n   // @0 ' ' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @38 '!' (12 pixels wide)\n   0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @76 '\"' (12 pixels wide)\n   0x00, 0x00, 0x98, 0x01, 0x98, 0x01, 0x98, 0x01, 0x98, 0x01, 0x98, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @114 '#' (12 pixels wide)\n   0x20, 0x03, 0x30, 0x01, 0x30, 0x01, 0x90, 0x01, 0xFE, 0x07, 0xFE, 0x07, 0x98, 0x00, 0x88, 0x00, 0xC8, 0x00, 0xFF, 0x03, 0xFF, 0x03, 0x4C, 0x00, 0x44, 0x00, 0x64, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @152 '$' (12 pixels wide)\n   0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0xF8, 0x03, 0x4C, 0x02, 0x4C, 0x00, 0x4C, 0x00, 0x78, 0x00, 0xC0, 0x03, 0x40, 0x06, 0x40, 0x06, 0x44, 0x06, 0xFC, 0x03, 0xF8, 0x01, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00,\n\n   // @190 '%' (12 pixels wide)\n   0x00, 0x00, 0x1E, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x33, 0x00, 0x1E, 0x03, 0xE0, 0x00, 0x38, 0x00, 0xC6, 0x03, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @228 '&' (12 pixels wide)\n   0x00, 0x00, 0xF0, 0x01, 0xF8, 0x01, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x30, 0x00, 0x78, 0x00, 0xEC, 0x0C, 0xC6, 0x0C, 0x86, 0x0D, 0x86, 0x07, 0x0E, 0x03, 0xFC, 0x06, 0x78, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @266 ''' (12 pixels wide)\n   0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @304 '(' (12 pixels wide)\n   0x00, 0x00, 0xC0, 0x00, 0x40, 0x00, 0x60, 0x00, 0x60, 0x00, 0x20, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x20, 0x00, 0x60, 0x00, 0x60, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x00, 0x00,\n\n   // @342 ')' (12 pixels wide)\n   0x00, 0x00, 0x30, 0x00, 0x20, 0x00, 0x60, 0x00, 0x60, 0x00, 0x40, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x40, 0x00, 0x60, 0x00, 0x60, 0x00, 0x20, 0x00, 0x30, 0x00, 0x00, 0x00,\n\n   // @380 '*' (12 pixels wide)\n   0x00, 0x00, 0x20, 0x00, 0x22, 0x02, 0xAC, 0x01, 0x70, 0x00, 0x70, 0x00, 0xAC, 0x01, 0x22, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @418 '+' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xFE, 0x07, 0xFE, 0x07, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @456 ',' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @494 '-' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @532 '.' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @570 '/' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x03, 0x80, 0x01, 0x80, 0x01, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x30, 0x00, 0x30, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @608 '0' (12 pixels wide)\n   0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8C, 0x01, 0x0E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x66, 0x03, 0x66, 0x03, 0x06, 0x03, 0x06, 0x03, 0x0E, 0x03, 0x8C, 0x01, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @646 '1' (12 pixels wide)\n   0x00, 0x00, 0x70, 0x00, 0x7C, 0x00, 0x6C, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xFC, 0x03, 0xFC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @684 '2' (12 pixels wide)\n   0x00, 0x00, 0xFC, 0x00, 0xFE, 0x01, 0x82, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0x70, 0x00, 0x18, 0x00, 0x0C, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @722 '3' (12 pixels wide)\n   0x00, 0x00, 0xFC, 0x00, 0xFE, 0x01, 0x82, 0x03, 0x00, 0x03, 0x00, 0x03, 0x80, 0x03, 0xF8, 0x01, 0xF8, 0x01, 0x80, 0x03, 0x00, 0x03, 0x00, 0x03, 0x82, 0x03, 0xFE, 0x01, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @760 '4' (12 pixels wide)\n   0x00, 0x00, 0xC0, 0x01, 0xC0, 0x01, 0xE0, 0x01, 0xA0, 0x01, 0x90, 0x01, 0x98, 0x01, 0x88, 0x01, 0x8C, 0x01, 0x86, 0x01, 0xFE, 0x07, 0xFE, 0x07, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @798 '5' (12 pixels wide)\n   0x00, 0x00, 0xFC, 0x01, 0xFC, 0x01, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0xFC, 0x00, 0xFC, 0x01, 0x84, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x82, 0x03, 0xFE, 0x01, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @836 '6' (12 pixels wide)\n   0x00, 0x00, 0xF0, 0x00, 0xF8, 0x01, 0x1C, 0x01, 0x0E, 0x00, 0x06, 0x00, 0xF6, 0x00, 0xFE, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8C, 0x03, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @874 '7' (12 pixels wide)\n   0x00, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xC0, 0x00, 0xC0, 0x00, 0xE0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @912 '8' (12 pixels wide)\n   0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8C, 0x01, 0xF8, 0x00, 0xFC, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @950 '9' (12 pixels wide)\n   0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8E, 0x01, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x03, 0x78, 0x03, 0x00, 0x03, 0x80, 0x03, 0xC4, 0x01, 0xFC, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @988 ':' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1026 ';' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1064 '<' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x80, 0x07, 0xF0, 0x01, 0x3C, 0x00, 0x06, 0x00, 0x3C, 0x00, 0xF0, 0x01, 0x80, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1102 '=' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x07, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x07, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1140 '>' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1E, 0x00, 0xF8, 0x00, 0xC0, 0x03, 0x00, 0x06, 0xC0, 0x03, 0xF8, 0x00, 0x1E, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1178 '?' (12 pixels wide)\n   0x00, 0x00, 0xF0, 0x00, 0xF8, 0x01, 0x08, 0x03, 0x00, 0x03, 0x80, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1216 '@' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x98, 0x01, 0x0C, 0x03, 0x0C, 0x03, 0xC6, 0x03, 0x66, 0x03, 0x66, 0x03, 0x66, 0x03, 0x66, 0x03, 0x66, 0x03, 0x66, 0x03, 0xC6, 0x03, 0x0C, 0x00, 0x0C, 0x00, 0x18, 0x00, 0xF0, 0x00, 0x00, 0x00,\n\n   // @1254 'A' (12 pixels wide)\n   0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0x8C, 0x01, 0xFC, 0x01, 0xFC, 0x01, 0x8C, 0x01, 0x06, 0x03, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1292 'B' (12 pixels wide)\n   0x00, 0x00, 0xFE, 0x00, 0xFE, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x03, 0x86, 0x03, 0xFE, 0x01, 0xFE, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x03, 0x86, 0x03, 0xFE, 0x01, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1330 'C' (12 pixels wide)\n   0x00, 0x00, 0xF0, 0x01, 0xF8, 0x03, 0x1C, 0x02, 0x0C, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x1C, 0x02, 0xF8, 0x03, 0xF0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1368 'D' (12 pixels wide)\n   0x00, 0x00, 0x7E, 0x00, 0xFE, 0x00, 0xC6, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x86, 0x03, 0xC6, 0x01, 0xFE, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1406 'E' (12 pixels wide)\n   0x00, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1444 'F' (12 pixels wide)\n   0x00, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xFE, 0x01, 0xFE, 0x01, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1482 'G' (12 pixels wide)\n   0x00, 0x00, 0xF0, 0x01, 0xF8, 0x03, 0x1C, 0x02, 0x0C, 0x00, 0x06, 0x00, 0x06, 0x00, 0xC6, 0x03, 0xC6, 0x03, 0x06, 0x03, 0x06, 0x03, 0x0C, 0x03, 0x0C, 0x03, 0xF8, 0x03, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1520 'H' (12 pixels wide)\n   0x00, 0x00, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0xFE, 0x03, 0xFE, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1558 'I' (12 pixels wide)\n   0x00, 0x00, 0xFC, 0x03, 0xFC, 0x03, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xFC, 0x03, 0xFC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1596 'J' (12 pixels wide)\n   0x00, 0x00, 0xF8, 0x01, 0xF8, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xC2, 0x01, 0xFE, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1634 'K' (12 pixels wide)\n   0x00, 0x00, 0x06, 0x07, 0x86, 0x03, 0xC6, 0x01, 0xC6, 0x00, 0x66, 0x00, 0x36, 0x00, 0x3E, 0x00, 0x7E, 0x00, 0xE6, 0x00, 0xC6, 0x00, 0xC6, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1672 'L' (12 pixels wide)\n   0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1710 'M' (12 pixels wide)\n   0x00, 0x00, 0x8E, 0x03, 0x8E, 0x03, 0x8E, 0x03, 0xDE, 0x03, 0x56, 0x03, 0x56, 0x03, 0x56, 0x03, 0x76, 0x03, 0x26, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1748 'N' (12 pixels wide)\n   0x00, 0x00, 0x0E, 0x03, 0x0E, 0x03, 0x1E, 0x03, 0x1E, 0x03, 0x16, 0x03, 0x36, 0x03, 0x26, 0x03, 0x26, 0x03, 0x66, 0x03, 0x46, 0x03, 0xC6, 0x03, 0xC6, 0x03, 0x86, 0x03, 0x86, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1786 'O' (12 pixels wide)\n   0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8C, 0x01, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8C, 0x01, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1824 'P' (12 pixels wide)\n   0x00, 0x00, 0xFE, 0x00, 0xFE, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x03, 0x86, 0x03, 0xFE, 0x01, 0xFE, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1862 'Q' (12 pixels wide)\n   0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8C, 0x01, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8C, 0x01, 0xFC, 0x01, 0xF8, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00,\n\n   // @1900 'R' (12 pixels wide)\n   0x00, 0x00, 0xFE, 0x00, 0xFE, 0x01, 0x86, 0x03, 0x06, 0x03, 0x06, 0x03, 0x86, 0x03, 0xFE, 0x01, 0xFE, 0x00, 0xC6, 0x01, 0x86, 0x01, 0x06, 0x03, 0x06, 0x03, 0x06, 0x06, 0x06, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1938 'S' (12 pixels wide)\n   0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x0E, 0x01, 0x06, 0x00, 0x06, 0x00, 0x1E, 0x00, 0xFC, 0x00, 0xF0, 0x01, 0x80, 0x03, 0x00, 0x03, 0x00, 0x03, 0x82, 0x03, 0xFE, 0x01, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @1976 'T' (12 pixels wide)\n   0x00, 0x00, 0xFE, 0x07, 0xFE, 0x07, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2014 'U' (12 pixels wide)\n   0x00, 0x00, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2052 'V' (12 pixels wide)\n   0x00, 0x00, 0x06, 0x03, 0x06, 0x03, 0x8C, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0xD8, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2090 'W' (12 pixels wide)\n   0x00, 0x00, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x07, 0x07, 0x76, 0x03, 0x76, 0x03, 0x56, 0x03, 0x56, 0x03, 0x56, 0x03, 0xDE, 0x03, 0x8E, 0x03, 0x8C, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2128 'X' (12 pixels wide)\n   0x00, 0x00, 0x8E, 0x03, 0x8C, 0x01, 0xDC, 0x01, 0xD8, 0x00, 0x78, 0x00, 0x70, 0x00, 0x20, 0x00, 0x70, 0x00, 0x70, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0x8C, 0x01, 0x8C, 0x01, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2166 'Y' (12 pixels wide)\n   0x00, 0x00, 0x0E, 0x07, 0x0C, 0x03, 0x9C, 0x03, 0x98, 0x01, 0xF8, 0x01, 0xF0, 0x00, 0xF0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2204 'Z' (12 pixels wide)\n   0x00, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x80, 0x01, 0xC0, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x60, 0x00, 0x30, 0x00, 0x38, 0x00, 0x18, 0x00, 0x1C, 0x00, 0x0C, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2242 '[' (12 pixels wide)\n   0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00,\n\n   // @2280 '\\' (12 pixels wide)\n   0x00, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x30, 0x00, 0x30, 0x00, 0x60, 0x00, 0x60, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,\n\n   // @2318 ']' (12 pixels wide)\n   0x00, 0x00, 0x78, 0x00, 0x78, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x78, 0x00, 0x78, 0x00, 0x00, 0x00,\n\n   // @2356 '^' (12 pixels wide)\n   0x00, 0x00, 0x60, 0x00, 0xF0, 0x00, 0x98, 0x01, 0x0C, 0x03, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2394 '_' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x07, 0xFF, 0x07,\n\n   // @2432 '`' (12 pixels wide)\n   0x18, 0x00, 0x30, 0x00, 0x20, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2470 'a' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x03, 0x04, 0x03, 0x00, 0x03, 0xF8, 0x03, 0xFE, 0x03, 0x06, 0x03, 0x86, 0x03, 0xFE, 0x03, 0x38, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2508 'b' (12 pixels wide)\n   0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xF6, 0x00, 0xFE, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFE, 0x01, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2546 'c' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFC, 0x01, 0x0C, 0x01, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0C, 0x01, 0xFC, 0x01, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2584 'd' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x78, 0x03, 0xFC, 0x03, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x03, 0x78, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2622 'e' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8C, 0x03, 0x06, 0x03, 0xFE, 0x03, 0xFE, 0x03, 0x06, 0x00, 0x0C, 0x02, 0xFC, 0x03, 0xF0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2660 'f' (12 pixels wide)\n   0x00, 0x00, 0xC0, 0x07, 0xE0, 0x07, 0x60, 0x00, 0x60, 0x00, 0xFC, 0x07, 0xFC, 0x07, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2698 'g' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x03, 0xFC, 0x03, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x03, 0x78, 0x03, 0x00, 0x03, 0x84, 0x03, 0xFC, 0x01, 0xF8, 0x00,\n\n   // @2736 'h' (12 pixels wide)\n   0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0xE6, 0x00, 0xFE, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2774 'i' (12 pixels wide)\n   0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x7C, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xFC, 0x03, 0xFC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2812 'j' (12 pixels wide)\n   0x00, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x7C, 0x00, 0x3C, 0x00,\n\n   // @2850 'k' (12 pixels wide)\n   0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x86, 0x03, 0xC6, 0x01, 0xE6, 0x00, 0x76, 0x00, 0x3E, 0x00, 0x7E, 0x00, 0x66, 0x00, 0xC6, 0x00, 0xC6, 0x01, 0x86, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2888 'l' (12 pixels wide)\n   0x00, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0xE0, 0x03, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2926 'm' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB6, 0x03, 0xFE, 0x07, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x66, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @2964 'n' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x00, 0xFE, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @3002 'o' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @3040 'p' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0x00, 0xFE, 0x01, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFE, 0x01, 0xF6, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00,\n\n   // @3078 'q' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x03, 0xFC, 0x03, 0x8E, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x03, 0x78, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03,\n\n   // @3116 'r' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0x01, 0xD8, 0x03, 0x38, 0x02, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @3154 's' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x01, 0xFE, 0x03, 0x06, 0x02, 0x3E, 0x00, 0xFC, 0x01, 0xC0, 0x03, 0x00, 0x03, 0x02, 0x03, 0xFE, 0x03, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @3192 't' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0xF0, 0x03, 0xE0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @3230 'u' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x8E, 0x03, 0xFC, 0x03, 0x38, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @3268 'v' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x8C, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0xD8, 0x00, 0xD8, 0x00, 0xD8, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @3306 'w' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x03, 0x06, 0x06, 0x03, 0x26, 0x03, 0x76, 0x03, 0x56, 0x03, 0xDC, 0x01, 0xDC, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @3344 'x' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, 0x03, 0xDC, 0x01, 0xD8, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0xF8, 0x00, 0xD8, 0x00, 0x8C, 0x01, 0x8E, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @3382 'y' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x8C, 0x01, 0x8C, 0x01, 0x9C, 0x01, 0xD8, 0x00, 0xD8, 0x00, 0xF0, 0x00, 0x70, 0x00, 0x60, 0x00, 0x70, 0x00, 0x30, 0x00, 0x30, 0x00, 0x3C, 0x00, 0x1C, 0x00,\n\n   // @3420 'z' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x18, 0x00, 0x1C, 0x00, 0xFE, 0x03, 0xFE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\n   // @3458 '{' (12 pixels wide)\n   0x00, 0x00, 0xC0, 0x03, 0xE0, 0x03, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x70, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x03, 0xC0, 0x03,\n\n   // @3496 '|' (12 pixels wide)\n   0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00,\n\n   // @3534 '}' (12 pixels wide)\n   0x00, 0x00, 0x3C, 0x00, 0x7C, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xC0, 0x03, 0xC0, 0x03, 0xE0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x7C, 0x00, 0x3C, 0x00,\n\n   // @3572 '~' (12 pixels wide)\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x04, 0xFE, 0x07, 0xC2, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n};\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_semaphore.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_semaphore.h\"\n#include \"coreinit_scheduler.h\"\n#include <common/decaf_assert.h>\n\nnamespace cafe::coreinit\n{\n\n\n/**\n * Initialise semaphore object with count.\n */\nvoid\nOSInitSemaphore(virt_ptr<OSSemaphore> semaphore,\n                int32_t count)\n{\n   OSInitSemaphoreEx(semaphore, count, nullptr);\n}\n\n\n/**\n * Initialise semaphore object with count and name.\n */\nvoid\nOSInitSemaphoreEx(virt_ptr<OSSemaphore> semaphore,\n                  int32_t count,\n                  virt_ptr<const char> name)\n{\n   semaphore->tag = OSSemaphore::Tag;\n   semaphore->name = name;\n   semaphore->count = count;\n   OSInitThreadQueueEx(virt_addrof(semaphore->queue), semaphore);\n}\n\n\n/**\n * Decrease the semaphore value.\n *\n * If the value is less than or equal to zero the current thread will be put to\n * sleep until the count is above zero and it can decrement it safely.\n */\nint32_t\nOSWaitSemaphore(virt_ptr<OSSemaphore> semaphore)\n{\n   internal::lockScheduler();\n\n   // Wait until we can decrease semaphore\n   while (semaphore->count <= 0) {\n      internal::sleepThreadNoLock(virt_addrof(semaphore->queue));\n      internal::rescheduleSelfNoLock();\n   }\n\n   auto previous = semaphore->count;\n\n   // Decrease semaphore\n   semaphore->count--;\n\n   internal::unlockScheduler();\n   return previous;\n}\n\n\n/**\n * Try to decrease the semaphore value.\n *\n * If the value is greater than zero then it will be decremented, else the function\n * will return immediately with a value <= 0 indicating a failure.\n *\n * \\return Returns previous semaphore count, before the decrement in this function.\n *         If the value is >0 then it means the call was succesful.\n */\nint32_t\nOSTryWaitSemaphore(virt_ptr<OSSemaphore> semaphore)\n{\n   internal::lockScheduler();\n   auto previous = semaphore->count;\n\n   // Try to decrease semaphore\n   if (semaphore->count > 0) {\n      semaphore->count--;\n   }\n\n   internal::unlockScheduler();\n   return previous;\n}\n\n\n/**\n * Increase the semaphore value.\n *\n * If any threads are waiting for semaphore, they are woken.\n */\nint32_t\nOSSignalSemaphore(virt_ptr<OSSemaphore> semaphore)\n{\n   internal::lockScheduler();\n   auto previous = semaphore->count;\n\n   // Increase semaphore\n   semaphore->count++;\n\n   // Wakeup any waiting threads\n   internal::wakeupThreadNoLock(virt_addrof(semaphore->queue));\n   internal::rescheduleAllCoreNoLock();\n\n   internal::unlockScheduler();\n   return previous;\n}\n\n\n/**\n * Get the current semaphore count.\n */\nint32_t\nOSGetSemaphoreCount(virt_ptr<OSSemaphore> semaphore)\n{\n   internal::lockScheduler();\n   auto count = semaphore->count;\n   internal::unlockScheduler();\n   return count;\n}\n\n\nvoid\nLibrary::registerSemaphoreSymbols()\n{\n   RegisterFunctionExport(OSInitSemaphore);\n   RegisterFunctionExport(OSInitSemaphoreEx);\n   RegisterFunctionExport(OSWaitSemaphore);\n   RegisterFunctionExport(OSTryWaitSemaphore);\n   RegisterFunctionExport(OSSignalSemaphore);\n   RegisterFunctionExport(OSGetSemaphoreCount);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_semaphore.h",
    "content": "#pragma once\n#include \"coreinit_thread.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_semaphore Semaphore\n * \\ingroup coreinit\n *\n * Similar to Windows <a href=\"https://msdn.microsoft.com/en-us/library/windows/desktop/ms685129(v=vs.85).aspx\">Semaphore Objects</a>.\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSSemaphore\n{\n   static constexpr uint32_t Tag = 0x73506852u;\n\n   //! Should always be set to the value OSSemaphore::Tag.\n   be2_val<uint32_t> tag;\n\n   //! Name set by OSInitMutexEx.\n   be2_virt_ptr<const char> name;\n\n   UNKNOWN(4);\n\n   //! Current count of semaphore\n   be2_val<int32_t> count;\n\n   //! Queue of threads waiting on semaphore object with OSWaitSemaphore\n   be2_struct<OSThreadQueue> queue;\n};\nCHECK_OFFSET(OSSemaphore, 0x00, tag);\nCHECK_OFFSET(OSSemaphore, 0x04, name);\nCHECK_OFFSET(OSSemaphore, 0x0C, count);\nCHECK_OFFSET(OSSemaphore, 0x10, queue);\nCHECK_SIZE(OSSemaphore, 0x20);\n\n#pragma pack(pop)\n\nvoid\nOSInitSemaphore(virt_ptr<OSSemaphore> semaphore,\n                int32_t count);\n\nvoid\nOSInitSemaphoreEx(virt_ptr<OSSemaphore> semaphore,\n                  int32_t count,\n                  virt_ptr<const char> name);\n\nint32_t\nOSWaitSemaphore(virt_ptr<OSSemaphore> semaphore);\n\nint32_t\nOSTryWaitSemaphore(virt_ptr<OSSemaphore> semaphore);\n\nint32_t\nOSSignalSemaphore(virt_ptr<OSSemaphore> semaphore);\n\nint32_t\nOSGetSemaphoreCount(virt_ptr<OSSemaphore> semaphore);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_snprintf.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_snprintf.h\"\n#include \"cafe/cafe_ppc_interface_varargs.h\"\n\n#include <common/log.h>\n#include <common/make_array.h>\n#include <common/strutils.h>\n#include <fmt/format.h>\n#include <fmt/printf.h>\n#include <iterator>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nstatic int32_t\nos_snprintf(virt_ptr<char> buffer,\n            uint32_t size,\n            virt_ptr<const char> fmt,\n            var_args va_args)\n{\n   auto list = make_va_list(va_args);\n   auto result = internal::formatStringV(buffer, size, fmt, list);\n   free_va_list(list);\n   return result;\n}\n\nnamespace internal\n{\n\nstatic const char c_flags[] = {\n   '-', '+', ' ', '#', '0'\n};\n\nstatic const char c_width[] = {\n   '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'\n};\n\nstatic const char c_precision[] = {\n   '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'\n};\n\nstatic const char c_length[] = {\n   'h', 'l', 'j', 'z', 't', 'L'\n};\n\nstatic const char c_specifier[] = {\n   'd', 'i', 'u', 'o', 'x', 'X', 'f', 'F', 'e',\n   'E', 'g', 'G', 'a', 'A', 'c', 's', 'p', 'n'\n};\n\nbool\nformatStringV(virt_ptr<const char> fmt,\n              virt_ptr<va_list> list,\n              fmt::memory_buffer &output)\n{\n   std::string flags, width, length, precision, formatter;\n   auto args = list->begin();\n\n   for (auto i = 0; fmt[i]; ) {\n      if (fmt[i] != '%') {\n         output.push_back(fmt[i++]);\n         continue;\n      }\n\n      ++i;\n\n      if (fmt[i] == '%') {\n         // %% becomes %\n         output.push_back('%');\n         ++i;\n         continue;\n      }\n\n      char specifier = 0;\n      flags.clear();\n      width.clear();\n      length.clear();\n      precision.clear();\n      formatter.clear();\n\n      while (std::find(std::begin(c_flags), std::end(c_flags), fmt[i]) != std::end(c_flags)) {\n         flags.push_back(fmt[i]);\n         ++i;\n      }\n\n      while (std::find(std::begin(c_width), std::end(c_width), fmt[i]) != std::end(c_width)) {\n         width.push_back(fmt[i]);\n         ++i;\n      }\n\n      if (fmt[i] == '.') {\n         while (std::find(std::begin(c_precision), std::end(c_precision), fmt[i]) != std::end(c_precision)) {\n            precision.push_back(fmt[i]);\n            ++i;\n         }\n      }\n\n      while (std::find(std::begin(c_length), std::end(c_length), fmt[i]) != std::end(c_length)) {\n         length.push_back(fmt[i]);\n         ++i;\n      }\n\n      if (std::find(std::begin(c_specifier), std::end(c_specifier), fmt[i]) != std::end(c_specifier)) {\n         specifier = fmt[i];\n         ++i;\n      }\n\n      switch (specifier) {\n      case 'd':\n      case 'i':\n      case 'u':\n      case 'o':\n      case 'x':\n      case 'X':\n      case 'c':\n         formatter = \"%\" + flags + width + precision + length + specifier;\n         if (length.compare(\"ll\") == 0 && specifier != 'c') {\n            fmt::format_to(std::back_inserter(output), \"{}\", fmt::sprintf(formatter, args.next<uint64_t>()));\n         } else {\n            fmt::format_to(std::back_inserter(output), \"{}\", fmt::sprintf(formatter, args.next<uint32_t>()));\n         }\n         break;\n      case 'g':\n      case 'G':\n      case 'f':\n      case 'F':\n      case 'e':\n      case 'E':\n      case 'a':\n      case 'A':\n         formatter = \"%\" + flags + width + precision + length + specifier;\n         if (length.compare(\"L\") == 0) {\n            fmt::format_to(std::back_inserter(output), \"{}\", fmt::sprintf(formatter, static_cast<long double>(args.next<double>())));\n         } else {\n            fmt::format_to(std::back_inserter(output), \"{}\", fmt::sprintf(formatter, args.next<double>()));\n         }\n         break;\n      case 'p':\n         // We actually ignore formatter and just use %08X for %p\n         formatter = \"%\" + flags + width + precision + length + specifier;\n         fmt::format_to(std::back_inserter(output), \"{:08X}\", static_cast<uint32_t>(virt_cast<virt_addr>(args.next<virt_ptr<void>>())));\n         break;\n      case 's': {\n         auto s = args.next<virt_ptr<const char>>();\n         if (s) {\n            fmt::format_to(std::back_inserter(output), \"{}\", s.get());\n         } else {\n            fmt::format_to(std::back_inserter(output), \"<NULL>\");\n         }\n      } break;\n      case 'n':\n         if (length.compare(\"hh\") == 0) {\n            *(args.next<virt_ptr<int8_t>>()) = static_cast<int8_t>(output.size());\n         } else if (length.compare(\"h\") == 0) {\n            *(args.next<virt_ptr<int16_t>>()) = static_cast<int16_t>(output.size());\n         } else if (length.compare(\"ll\") == 0) {\n            *(args.next<virt_ptr<int64_t>>()) = static_cast<int64_t>(output.size());\n         } else {\n            *(args.next<virt_ptr<int32_t>>()) = static_cast<int32_t>(output.size());\n         }\n         break;\n      default:\n         gLog->error(\"Unimplemented format specifier: {}\", specifier);\n         return false;\n      }\n   }\n\n   return true;\n}\n\nint32_t\nformatStringV(virt_ptr<char> buffer,\n              uint32_t len,\n              virt_ptr<const char> fmt,\n              virt_ptr<va_list> list)\n{\n   auto str = fmt::memory_buffer { };\n   if (!formatStringV(fmt, list, str)) {\n      return -1;\n   }\n\n   if (str.size() >= len - 1) {\n      // Copy as much as possible\n      std::memcpy(buffer.get(), str.data(), len - 2);\n      buffer[len - 1] = char { 0 };\n      len -= 1;\n   } else {\n      // Copy whole string into buffer\n      std::memcpy(buffer.get(), str.data(), str.size());\n      buffer[str.size()] = char { 0 };\n      len = static_cast<uint32_t>(str.size());\n   }\n\n   return static_cast<int32_t>(len);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerSnprintfSymbols()\n{\n   RegisterFunctionExportName(\"__os_snprintf\", os_snprintf);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_snprintf.h",
    "content": "#include \"cafe/cafe_ppc_interface_varargs.h\"\n\n#include <libcpu/be2_struct.h>\n#include <fmt/core.h>\n\nnamespace cafe::coreinit\n{\n\nnamespace internal\n{\n\nint32_t\nformatStringV(virt_ptr<char> buffer,\n              uint32_t len,\n              virt_ptr<const char> fmt,\n              virt_ptr<va_list> list);\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_spinlock.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_interrupts.h\"\n#include \"coreinit_spinlock.h\"\n#include \"coreinit_scheduler.h\"\n#include \"coreinit_thread.h\"\n#include \"libcpu/mem.h\"\n#include <common/decaf_assert.h>\n#include <atomic>\n\nnamespace cafe::coreinit\n{\n\nstatic void\nincreaseSpinLockCount(virt_ptr<OSThread> thread)\n{\n   internal::lockScheduler();\n   thread->context.spinLockCount++;\n   thread->priority = 0;\n   internal::unlockScheduler();\n}\n\nstatic void\ndecreaseSpinLockCount(virt_ptr<OSThread> thread)\n{\n   internal::lockScheduler();\n   thread->context.spinLockCount--;\n   thread->priority = internal::calculateThreadPriorityNoLock(thread);\n   internal::unlockScheduler();\n}\n\nstatic bool\nspinAcquireLock(virt_ptr<OSSpinLock> spinlock)\n{\n   auto thread = OSGetCurrentThread();\n   if (spinlock->owner.load(std::memory_order_acquire) == thread) {\n      ++spinlock->recursion;\n      return false;\n   }\n\n   auto expected = virt_ptr<OSThread> { nullptr };\n   auto owner = virt_ptr<OSThread> { thread };\n\n   while (!spinlock->owner.compare_exchange_weak(expected, owner, std::memory_order_release, std::memory_order_relaxed)) {\n      expected = nullptr;\n   }\n\n   increaseSpinLockCount(thread);\n   return true;\n}\n\nstatic bool\nspinTryLock(virt_ptr<OSSpinLock> spinlock)\n{\n   auto thread = OSGetCurrentThread();\n   if (spinlock->owner.load(std::memory_order_acquire) == thread) {\n      ++spinlock->recursion;\n      return true;\n   }\n\n   auto expected = virt_ptr<OSThread> { nullptr };\n   auto owner = virt_ptr<OSThread> { thread };\n\n   if (spinlock->owner.compare_exchange_weak(expected, owner, std::memory_order_release, std::memory_order_relaxed)) {\n      increaseSpinLockCount(thread);\n      return true;\n   } else {\n      return false;\n   }\n}\n\nstatic bool\nspinTryLockWithTimeout(virt_ptr<OSSpinLock> spinlock,\n                       OSTime durationTicks)\n{\n   auto thread = OSGetCurrentThread();\n   if (spinlock->owner.load(std::memory_order_acquire) == thread) {\n      ++spinlock->recursion;\n      return true;\n   }\n\n   auto expected = virt_ptr<OSThread> { nullptr };\n   auto owner = virt_ptr<OSThread> { thread };\n   auto timeout = OSGetSystemTime() + durationTicks;\n\n   while (!spinlock->owner.compare_exchange_weak(expected, owner, std::memory_order_release, std::memory_order_relaxed)) {\n      if (OSGetSystemTime() >= timeout) {\n         return false;\n      }\n\n      expected = nullptr;\n   }\n\n   increaseSpinLockCount(thread);\n   return true;\n}\n\nstatic bool\nspinReleaseLock(virt_ptr<OSSpinLock> spinlock)\n{\n   auto thread = OSGetCurrentThread();\n   if (spinlock->recursion > 0u) {\n      --spinlock->recursion;\n      return false;\n   }\n\n   auto owner = virt_ptr<OSThread> { thread };\n   if (spinlock->owner.load(std::memory_order_acquire) == owner) {\n      spinlock->owner.store(virt_ptr<OSThread> { nullptr });\n      decreaseSpinLockCount(thread);\n      return true;\n   }\n\n   decaf_abort(\"Attempt to release spin lock which is not owned.\");\n   return true;\n}\n\nvoid\nOSInitSpinLock(virt_ptr<OSSpinLock> spinlock)\n{\n   spinlock->owner.store(virt_ptr<OSThread> { nullptr });\n   spinlock->recursion = 0u;\n}\n\nBOOL\nOSAcquireSpinLock(virt_ptr<OSSpinLock> spinlock)\n{\n   OSTestThreadCancel();\n   spinAcquireLock(spinlock);\n   return TRUE;\n}\n\nBOOL\nOSTryAcquireSpinLock(virt_ptr<OSSpinLock> spinlock)\n{\n   OSTestThreadCancel();\n   return spinTryLock(spinlock) ? TRUE : FALSE;\n}\n\nBOOL\nOSTryAcquireSpinLockWithTimeout(virt_ptr<OSSpinLock> spinlock,\n                                OSTimeNanoseconds timeoutNS)\n{\n   auto timeoutTicks = internal::nsToTicks(timeoutNS);\n   OSTestThreadCancel();\n   return spinTryLockWithTimeout(spinlock, timeoutTicks) ? TRUE : FALSE;\n}\n\nBOOL\nOSReleaseSpinLock(virt_ptr<OSSpinLock> spinlock)\n{\n   spinReleaseLock(spinlock);\n   OSTestThreadCancel();\n   return TRUE;\n}\n\n\n/**\n * Acquires an Uninterruptible Spin Lock.\n *\n * Will block until spin lock is acquired.\n * Disables interrupts before returning, only if non recursive lock.\n *\n * \\return Returns TRUE if the lock was acquired.\n */\nBOOL\nOSUninterruptibleSpinLock_Acquire(virt_ptr<OSSpinLock> spinlock)\n{\n   if (spinAcquireLock(spinlock)) {\n      spinlock->restoreInterruptState = OSDisableInterrupts();\n   }\n\n   // Update thread's cancel state\n   if (auto thread = OSGetCurrentThread()) {\n      thread->cancelState |= OSThreadCancelState::DisabledBySpinlock;\n   }\n\n   return TRUE;\n}\n\n\n/**\n * Try acquire an Uninterruptible Spin Lock.\n *\n * Will return immediately if the lock has already been acquired by another thread.\n * If lock is acquired, interrupts will be disabled.\n *\n * \\return Returns TRUE if the lock was acquired.\n */\nBOOL\nOSUninterruptibleSpinLock_TryAcquire(virt_ptr<OSSpinLock> spinlock)\n{\n   if (!spinTryLock(spinlock)) {\n      return FALSE;\n   }\n\n   spinlock->restoreInterruptState = OSDisableInterrupts();\n\n   // Update thread's cancel state\n   if (auto thread = OSGetCurrentThread()) {\n      thread->cancelState |= OSThreadCancelState::DisabledBySpinlock;\n   }\n\n   return TRUE;\n}\n\n\n/**\n * Try acquire an Uninterruptible Spin Lock with a timeout.\n *\n * Will return after a timeout if unable to acquire lock.\n * If lock is acquired, interrupts will be disabled.\n *\n * \\return Returns TRUE if the lock was acquired.\n */\nBOOL\nOSUninterruptibleSpinLock_TryAcquireWithTimeout(virt_ptr<OSSpinLock> spinlock,\n                                                OSTimeNanoseconds timeoutNS)\n{\n   auto timeoutTicks = internal::nsToTicks(timeoutNS);\n\n   if (!spinTryLockWithTimeout(spinlock, timeoutTicks)) {\n      return FALSE;\n   }\n\n   spinlock->restoreInterruptState = OSDisableInterrupts();\n\n   // Update thread's cancel state\n   if (auto thread = OSGetCurrentThread()) {\n      thread->cancelState |= OSThreadCancelState::DisabledBySpinlock;\n   }\n\n   return TRUE;\n}\n\n\n/**\n * Release an Uninterruptible Spin Lock.\n *\n * Interrupts will be restored to their previous state before\n * the lock was acquired.\n *\n * \\return Returns TRUE if the lock was released.\n */\nBOOL\nOSUninterruptibleSpinLock_Release(virt_ptr<OSSpinLock> spinlock)\n{\n   if (spinReleaseLock(spinlock)) {\n      OSRestoreInterrupts(spinlock->restoreInterruptState);\n   }\n\n   // Update thread's cancel state\n   if (auto thread = OSGetCurrentThread()) {\n      thread->cancelState &= ~OSThreadCancelState::DisabledBySpinlock;\n   }\n\n   return TRUE;\n}\n\n\nvoid\nLibrary::registerSpinLockSymbols()\n{\n   RegisterFunctionExport(OSInitSpinLock);\n   RegisterFunctionExport(OSAcquireSpinLock);\n   RegisterFunctionExport(OSTryAcquireSpinLock);\n   RegisterFunctionExport(OSTryAcquireSpinLockWithTimeout);\n   RegisterFunctionExport(OSReleaseSpinLock);\n   RegisterFunctionExport(OSUninterruptibleSpinLock_Acquire);\n   RegisterFunctionExport(OSUninterruptibleSpinLock_TryAcquire);\n   RegisterFunctionExport(OSUninterruptibleSpinLock_TryAcquireWithTimeout);\n   RegisterFunctionExport(OSUninterruptibleSpinLock_Release);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_spinlock.h",
    "content": "#pragma once\n#include \"coreinit_time.h\"\n\n#include <libcpu/be2_atomic.h>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_spinlock Spinlock\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSThread;\n\nstruct OSSpinLock\n{\n   //! Address of OSThread* for owner of this lock.\n   be2_atomic<virt_ptr<OSThread>> owner;\n\n   UNKNOWN(0x4);\n\n   //! Recursion count of spin lock.\n   be2_val<uint32_t> recursion;\n\n   //! Used by OSUninterruptibleSpinLock_{Acquire,Release} to restore previous.\n   //! state of interrupts.\n   be2_val<BOOL> restoreInterruptState;\n};\nCHECK_OFFSET(OSSpinLock, 0x0, owner);\nCHECK_OFFSET(OSSpinLock, 0x8, recursion);\nCHECK_OFFSET(OSSpinLock, 0xC, restoreInterruptState);\nCHECK_SIZE(OSSpinLock, 0x10);\n\n#pragma pack(pop)\n\nvoid\nOSInitSpinLock(virt_ptr<OSSpinLock> spinlock);\n\nBOOL\nOSAcquireSpinLock(virt_ptr<OSSpinLock> spinlock);\n\nBOOL\nOSTryAcquireSpinLock(virt_ptr<OSSpinLock> spinlock);\n\nBOOL\nOSTryAcquireSpinLockWithTimeout(virt_ptr<OSSpinLock> spinlock,\n                                OSTimeNanoseconds timeoutNS);\n\nBOOL\nOSReleaseSpinLock(virt_ptr<OSSpinLock> spinlock);\n\nBOOL\nOSUninterruptibleSpinLock_Acquire(virt_ptr<OSSpinLock> spinlock);\n\nBOOL\nOSUninterruptibleSpinLock_TryAcquire(virt_ptr<OSSpinLock> spinlock);\n\nBOOL\nOSUninterruptibleSpinLock_TryAcquireWithTimeout(virt_ptr<OSSpinLock> spinlock,\n                                                OSTimeNanoseconds timeoutNS);\n\nBOOL\nOSUninterruptibleSpinLock_Release(virt_ptr<OSSpinLock> spinlock);\n\nstruct ScopedSpinLock\n{\n   ScopedSpinLock(virt_ptr<OSSpinLock> lock_) :\n      lock(lock_)\n   {\n      OSUninterruptibleSpinLock_Acquire(lock);\n   }\n\n   ~ScopedSpinLock()\n   {\n      OSUninterruptibleSpinLock_Release(lock);\n   }\n\n   virt_ptr<OSSpinLock> lock;\n};\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_systemheap.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_cache.h\"\n#include \"coreinit_cosreport.h\"\n#include \"coreinit_memexpheap.h\"\n#include \"coreinit_systemheap.h\"\n#include \"coreinit_systeminfo.h\"\n\n#include <fmt/core.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::coreinit\n{\n\nstruct StaticSystemHeapData\n{\n   be2_val<MEMHeapHandle> handle;\n   be2_val<uint32_t> numAllocs;\n   be2_val<uint32_t> numFrees;\n};\n\nstatic virt_ptr<StaticSystemHeapData>\nsSystemHeapData = nullptr;\n\n\n/**\n * Allocate memory from the system heap.\n *\n * \\param size\n * The size of the memory block to allocate.\n *\n * \\param align\n * The alignment of the memory block, see MEMAllocFromExpHeapEx for more info.\n *\n * \\return\n * NULL on error, or pointer to newly allocated memory on success.\n */\nvirt_ptr<void>\nOSAllocFromSystem(uint32_t size,\n                  int32_t align)\n{\n   auto ptr = MEMAllocFromExpHeapEx(sSystemHeapData->handle, size, align);\n\n   if (internal::isAppDebugLevelVerbose()) {\n      internal::COSInfo(\n         COSReportModule::Unknown2,\n         fmt::format(\"SYSTEM_HEAP:{},ALLOC,=\\\"{}\\\",-{}\",\n                     sSystemHeapData->numAllocs, ptr, size));\n      ++sSystemHeapData->numAllocs;\n   }\n\n   return ptr;\n}\n\n\n/**\n * Free memory to the system heap.\n *\n * \\param ptr\n * The memory to free, this pointer must have previously be returned by\n * OSAllocFromSystem.\n */\nvoid\nOSFreeToSystem(virt_ptr<void> ptr)\n{\n   if (internal::isAppDebugLevelVerbose()) {\n      internal::COSInfo(\n         COSReportModule::Unknown2,\n         fmt::format(\"SYSTEM_HEAP:{},FREE,=\\\"{}\\\",{}\",\n                     sSystemHeapData->numAllocs, ptr, 0));\n      ++sSystemHeapData->numAllocs;\n   }\n\n   MEMFreeToExpHeap(sSystemHeapData->handle, ptr);\n}\n\nnamespace internal\n{\n\nvoid\ndumpSystemHeap()\n{\n   MEMDumpHeap(sSystemHeapData->handle);\n}\n\nvoid\ninitialiseSystemHeap(virt_ptr<void> base,\n                     uint32_t size)\n{\n   if (internal::isAppDebugLevelVerbose()) {\n      COSInfo(COSReportModule::Unknown2,\n              \"RPL_SYSHEAP:Event,Change,Hex Addr,Bytes,Available\");\n      COSInfo(\n         COSReportModule::Unknown2,\n         fmt::format(\"RPL_SYSHEAP:SYSHEAP START,CREATE,=\\\"{}\\\",{}\",\n                     base, size));\n   }\n\n   sSystemHeapData->handle = MEMCreateExpHeapEx(base,\n                                                size,\n                                                MEMHeapFlags::ThreadSafe);\n   sSystemHeapData->numAllocs = 0u;\n   sSystemHeapData->numFrees = 0u;\n   OSMemoryBarrier();\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerSystemHeapSymbols()\n{\n   RegisterFunctionExport(OSAllocFromSystem);\n   RegisterFunctionExport(OSFreeToSystem);\n\n   RegisterDataInternal(sSystemHeapData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_systemheap.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\nvirt_ptr<void>\nOSAllocFromSystem(uint32_t size,\n                  int32_t align);\n\nvoid\nOSFreeToSystem(virt_ptr<void> ptr);\n\nnamespace internal\n{\n\nvoid\ndumpSystemHeap();\n\nvoid\ninitialiseSystemHeap(virt_ptr<void> base,\n                     uint32_t size);\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_systeminfo.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_systeminfo.h\"\n#include \"coreinit_time.h\"\n\n#include \"cafe/kernel/cafe_kernel_info.h\"\n\n#include <chrono>\n#include <common/platform_time.h>\n\nnamespace cafe::coreinit\n{\n\nstruct StaticSystemInfoData\n{\n   be2_struct<OSSystemInfo> systemInfo;\n   be2_val<BOOL> screenCapturePermission;\n   be2_val<BOOL> enableHomeButtonMenu;\n   be2_struct<kernel::Info0> kernelInfo0;\n   be2_struct<kernel::Info6> kernelInfo6;\n   be2_array<char, 4096> argstr;\n};\n\nstatic virt_ptr<StaticSystemInfoData>\nsSystemInfoData = nullptr;\n\nvirt_ptr<OSSystemInfo>\nOSGetSystemInfo()\n{\n   return virt_addrof(sSystemInfoData->systemInfo);\n}\n\nOSSystemMode\nOSGetSystemMode()\n{\n   return kernel::getSystemMode();\n}\n\nBOOL\nOSSetScreenCapturePermission(BOOL enabled)\n{\n   auto old = sSystemInfoData->screenCapturePermission;\n   sSystemInfoData->screenCapturePermission = enabled;\n   return old;\n}\n\nBOOL\nOSGetScreenCapturePermission()\n{\n   return sSystemInfoData->screenCapturePermission;\n}\n\nuint32_t\nOSGetConsoleType()\n{\n   // Value from a WiiU retail v4.0.0\n   return 0x3000050;\n}\n\nuint32_t\nOSGetSecurityLevel()\n{\n   return 0;\n}\n\nBOOL\nOSIsHomeButtonMenuEnabled()\n{\n   return sSystemInfoData->enableHomeButtonMenu;\n}\n\nBOOL\nOSEnableHomeButtonMenu(BOOL enable)\n{\n   sSystemInfoData->enableHomeButtonMenu = enable;\n   return TRUE;\n}\n\nvoid\nOSBlockThreadsOnExit()\n{\n   // TODO: OSBlockThreadsOnExit\n}\n\nuint64_t\nOSGetTitleID()\n{\n   return sSystemInfoData->kernelInfo0.titleId;\n}\n\nuint64_t\nOSGetOSID()\n{\n   return sSystemInfoData->kernelInfo6.osTitleId;\n}\n\nkernel::UniqueProcessId\nOSGetUPID()\n{\n   return sSystemInfoData->kernelInfo0.upid;\n}\n\nOSAppFlags\nOSGetAppFlags()\n{\n   return sSystemInfoData->kernelInfo0.appFlags;\n}\n\nOSShutdownReason\nOSGetShutdownReason()\n{\n   return OSShutdownReason::NoShutdown;\n}\n\nvoid\nOSGetArgcArgv(virt_ptr<uint32_t> argc,\n              virt_ptr<virt_ptr<const char>> argv)\n{\n   *argc = 0u;\n   *argv = nullptr;\n}\n\nBOOL\nOSIsDebuggerPresent()\n{\n   return FALSE;\n}\n\nBOOL\nOSIsDebuggerInitialized()\n{\n   return FALSE;\n}\n\nint32_t\nENVGetEnvironmentVariable(virt_ptr<const char> key,\n                          virt_ptr<char> buffer,\n                          uint32_t length)\n{\n   if (buffer) {\n      *buffer = char { 0 };\n   }\n\n   return 0;\n}\n\nnamespace internal\n{\n\nvirt_ptr<char>\ngetArgStr()\n{\n   return virt_addrof(sSystemInfoData->argstr);\n}\n\nvirt_ptr<void>\ngetCoreinitLoaderHandle()\n{\n   return sSystemInfoData->kernelInfo0.coreinit.loaderHandle;\n}\n\nvirt_addr\ngetDefaultThreadStackBase(uint32_t coreId)\n{\n   if (coreId == 0) {\n      return sSystemInfoData->kernelInfo0.stackBase0;\n   } else if (coreId == 1) {\n      return sSystemInfoData->kernelInfo0.stackBase1;\n   } else if (coreId == 2) {\n      return sSystemInfoData->kernelInfo0.stackBase2;\n   } else {\n      decaf_abort(fmt::format(\"Invalid coreId {}\", coreId));\n   }\n}\n\nvirt_addr\ngetDefaultThreadStackEnd(uint32_t coreId)\n{\n   if (coreId == 0) {\n      return sSystemInfoData->kernelInfo0.stackEnd0;\n   } else if (coreId == 1) {\n      return sSystemInfoData->kernelInfo0.stackEnd1;\n   } else if (coreId == 2) {\n      return sSystemInfoData->kernelInfo0.stackEnd2;\n   } else {\n      decaf_abort(fmt::format(\"Invalid coreId {}\", coreId));\n   }\n}\n\nvirt_addr\ngetLockedCacheBaseAddress(uint32_t coreId)\n{\n   if (coreId == 0) {\n      return sSystemInfoData->kernelInfo0.lockedCacheBase0;\n   } else if (coreId == 1) {\n      return sSystemInfoData->kernelInfo0.lockedCacheBase1;\n   } else if (coreId == 2) {\n      return sSystemInfoData->kernelInfo0.lockedCacheBase2;\n   } else {\n      decaf_abort(fmt::format(\"Invalid coreId {}\", coreId));\n   }\n}\n\nvirt_addr\ngetMem2BaseAddress()\n{\n   return sSystemInfoData->kernelInfo0.dataAreaStart;\n}\n\nvirt_addr\ngetMem2EndAddress()\n{\n   return sSystemInfoData->kernelInfo0.dataAreaEnd;\n}\n\nphys_addr\ngetMem2PhysAddress()\n{\n   return sSystemInfoData->kernelInfo0.physDataAreaStart;\n}\n\nvirt_addr\ngetSdaBase()\n{\n   return sSystemInfoData->kernelInfo0.sdaBase;\n}\n\nvirt_addr\ngetSda2Base()\n{\n   return sSystemInfoData->kernelInfo0.sda2Base;\n}\n\nuint32_t\ngetSystemHeapSize()\n{\n   return sSystemInfoData->kernelInfo0.systemHeapSize;\n}\n\nbool\nisAppDebugLevelVerbose()\n{\n   return sSystemInfoData->kernelInfo0.appFlags.value()\n      .debugLevel() >= OSAppFlagsDebugLevel::Verbose;\n}\n\nbool\nisAppDebugLevelNotice()\n{\n   return sSystemInfoData->kernelInfo0.appFlags.value()\n      .debugLevel() >= OSAppFlagsDebugLevel::Notice;\n}\n\nvoid\ninitialiseSystemInfo()\n{\n   sSystemInfoData->systemInfo.busSpeed = cpu::busClockSpeed;\n   sSystemInfoData->systemInfo.coreSpeed = cpu::coreClockSpeed;\n   sSystemInfoData->systemInfo.baseTime = internal::getBaseTime();\n   sSystemInfoData->systemInfo.l2CacheSize[0] = 512 * 1024u;\n   sSystemInfoData->systemInfo.l2CacheSize[1] = 2 * 1024 * 1024u;\n   sSystemInfoData->systemInfo.l2CacheSize[2] = 512 * 1024u;\n   sSystemInfoData->systemInfo.cpuRatio = 5u;\n\n   sSystemInfoData->screenCapturePermission = TRUE;\n   sSystemInfoData->enableHomeButtonMenu = TRUE;\n\n   kernel::getInfo(kernel::InfoType::Type0,\n                   virt_addrof(sSystemInfoData->kernelInfo0),\n                   sizeof(kernel::Info0));\n\n   kernel::getInfo(kernel::InfoType::Type6,\n                   virt_addrof(sSystemInfoData->kernelInfo6),\n                   sizeof(kernel::Info6));\n\n   kernel::getInfo(kernel::InfoType::ArgStr,\n                   virt_addrof(sSystemInfoData->argstr),\n                   sSystemInfoData->argstr.size());\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerSystemInfoSymbols()\n{\n   RegisterFunctionExport(OSGetSystemInfo);\n   RegisterFunctionExport(OSGetSystemMode);\n   RegisterFunctionExport(OSSetScreenCapturePermission);\n   RegisterFunctionExport(OSGetScreenCapturePermission);\n   RegisterFunctionExport(OSGetConsoleType);\n   RegisterFunctionExport(OSGetSecurityLevel);\n   RegisterFunctionExport(OSEnableHomeButtonMenu);\n   RegisterFunctionExport(OSIsHomeButtonMenuEnabled);\n   RegisterFunctionExport(OSBlockThreadsOnExit);\n   RegisterFunctionExport(OSGetTitleID);\n   RegisterFunctionExport(OSGetOSID);\n   RegisterFunctionExport(OSGetUPID);\n   RegisterFunctionExport(OSGetShutdownReason);\n   RegisterFunctionExport(OSGetArgcArgv);\n   RegisterFunctionExport(OSIsDebuggerPresent);\n   RegisterFunctionExport(OSIsDebuggerInitialized);\n   RegisterFunctionExport(ENVGetEnvironmentVariable);\n   RegisterFunctionExportName(\"_OSGetAppFlags\", OSGetAppFlags);\n\n   RegisterDataInternal(sSystemInfoData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_systeminfo.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_time.h\"\n#include \"cafe/kernel/cafe_kernel_info.h\"\n#include \"cafe/kernel/cafe_kernel_process.h\"\n\n#include <common/bitfield.h>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n#pragma pack(push, 1)\n\nstruct OSSystemInfo\n{\n   be2_val<uint32_t> busSpeed;\n   be2_val<uint32_t> coreSpeed;\n   be2_val<OSTime> baseTime;\n   be2_array<uint32_t, 3> l2CacheSize;\n   be2_val<uint32_t> cpuRatio;\n};\nCHECK_OFFSET(OSSystemInfo, 0x0, busSpeed);\nCHECK_OFFSET(OSSystemInfo, 0x4, coreSpeed);\nCHECK_OFFSET(OSSystemInfo, 0x8, baseTime);\nCHECK_OFFSET(OSSystemInfo, 0x10, l2CacheSize);\nCHECK_OFFSET(OSSystemInfo, 0x1C, cpuRatio);\nCHECK_SIZE(OSSystemInfo, 0x20);\n\nusing OSAppFlags = kernel::ProcessFlags;\nusing OSAppFlagsDebugLevel = kernel::DebugLevel;\nusing OSSystemMode = kernel::SystemMode;\n\n#pragma pack(pop)\n\nvirt_ptr<OSSystemInfo>\nOSGetSystemInfo();\n\nOSSystemMode\nOSGetSystemMode();\n\nBOOL\nOSSetScreenCapturePermission(BOOL enabled);\n\nBOOL\nOSGetScreenCapturePermission();\n\nuint32_t\nOSGetConsoleType();\n\nuint32_t\nOSGetSecurityLevel();\n\nBOOL\nOSEnableHomeButtonMenu(BOOL enable);\n\nBOOL\nOSIsHomeButtonMenuEnabled();\n\nvoid\nOSBlockThreadsOnExit();\n\nuint64_t\nOSGetTitleID();\n\nuint64_t\nOSGetOSID();\n\nkernel::UniqueProcessId\nOSGetUPID();\n\nOSAppFlags\nOSGetAppFlags();\n\nOSShutdownReason\nOSGetShutdownReason();\n\nvoid\nOSGetArgcArgv(virt_ptr<uint32_t> argc,\n              virt_ptr<virt_ptr<const char>> argv);\n\nBOOL\nOSIsDebuggerPresent();\n\nBOOL\nOSIsDebuggerInitialized();\n\nint32_t\nENVGetEnvironmentVariable(virt_ptr<const char> key,\n                          virt_ptr<char> buffer,\n                          uint32_t length);\n\nnamespace internal\n{\n\nvirt_ptr<char>\ngetArgStr();\n\nvirt_ptr<void>\ngetCoreinitLoaderHandle();\n\nvirt_addr\ngetDefaultThreadStackBase(uint32_t coreId);\n\nvirt_addr\ngetDefaultThreadStackEnd(uint32_t coreId);\n\nvirt_addr\ngetLockedCacheBaseAddress(uint32_t coreId);\n\nvirt_addr\ngetMem2BaseAddress();\n\nvirt_addr\ngetMem2EndAddress();\n\nphys_addr\ngetMem2PhysAddress();\n\nvirt_addr\ngetSdaBase();\n\nvirt_addr\ngetSda2Base();\n\nuint32_t\ngetSystemHeapSize();\n\nbool\nisAppDebugLevelVerbose();\n\nbool\nisAppDebugLevelNotice();\n\nvoid\ninitialiseSystemInfo();\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_systemmessagequeue.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_systemmessagequeue.h\"\n\nnamespace cafe::coreinit\n{\n\nconstexpr auto SystemMessageQueueLength = 16u;\n\nstruct StaticSystemMessageQueueData\n{\n   be2_struct<OSMessageQueue> queue;\n   be2_array<char, 32> queueName;\n   be2_array<OSMessage, SystemMessageQueueLength> messages;\n};\n\nstatic virt_ptr<StaticSystemMessageQueueData>\nsSystemMessageQueueData = nullptr;\n\n\n/**\n * Get a pointer to the system message queue.\n *\n * Used for acquiring & releasing foreground messages.\n */\nvirt_ptr<OSMessageQueue>\nOSGetSystemMessageQueue()\n{\n   return virt_addrof(sSystemMessageQueueData->queue);\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseSystemMessageQueue()\n{\n   sSystemMessageQueueData->queueName = \"{ SystemMQ }\";\n   OSInitMessageQueueEx(virt_addrof(sSystemMessageQueueData->queue),\n                        virt_addrof(sSystemMessageQueueData->messages),\n                        sSystemMessageQueueData->messages.size(),\n                        virt_addrof(sSystemMessageQueueData->queueName));\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerSystemMessageQueueSymbols()\n{\n   RegisterFunctionExport(OSGetSystemMessageQueue);\n   RegisterDataInternal(sSystemMessageQueueData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_systemmessagequeue.h",
    "content": "#pragma once\n#include \"coreinit_messagequeue.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_systemmessagequeue System Message Queue\n * \\ingroup coreinit\n * @{\n */\n\nvirt_ptr<OSMessageQueue>\nOSGetSystemMessageQueue();\n\nnamespace internal\n{\n\nvoid\ninitialiseSystemMessageQueue();\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_taskqueue.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_core.h\"\n#include \"coreinit_spinlock.h\"\n#include \"coreinit_taskqueue.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\nnamespace cafe::coreinit\n{\n\n/**\n * Initialise a task queue structure.\n */\nvoid\nMPInitTaskQ(virt_ptr<MPTaskQueue> queue,\n            virt_ptr<virt_ptr<MPTask>> taskBuffer,\n            uint32_t taskBufferLen)\n{\n   OSInitSpinLock(virt_addrof(queue->lock));\n   queue->self = queue;\n   queue->state.store(MPTaskQueueState::Initialised);\n   queue->tasks = 0u;\n   queue->tasksReady = 0u;\n   queue->tasksRunning = 0u;\n   queue->tasksFinished = 0u;\n   queue->queueIndex = 0u;\n   queue->queueSize = 0u;\n   queue->queue = taskBuffer;\n   queue->queueMaxSize = taskBufferLen;\n}\n\n\n/**\n * Terminates a task queue.\n *\n * Yes this really does only return TRUE in coreinit.rpl\n */\nBOOL\nMPTermTaskQ(virt_ptr<MPTaskQueue> queue)\n{\n   return TRUE;\n}\n\n\n/**\n * Get the status of a task queue.\n */\nBOOL\nMPGetTaskQInfo(virt_ptr<MPTaskQueue> queue,\n               virt_ptr<MPTaskQueueInfo> info)\n{\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n   info->state = queue->state.load();\n   info->tasks = queue->tasks;\n   info->tasksReady = queue->tasksReady;\n   info->tasksRunning = queue->tasksRunning;\n   info->tasksFinished = queue->tasksFinished;\n   OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n   return TRUE;\n}\n\n\n/**\n * Starts a task queue.\n *\n * Sets the task state to Ready.\n *\n * \\return Returns true if state was previously Initialised or Stopped.\n */\nBOOL\nMPStartTaskQ(virt_ptr<MPTaskQueue> queue)\n{\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n\n   if (queue->state.load() == MPTaskQueueState::Initialised\n    || queue->state.load() == MPTaskQueueState::Stopped) {\n      queue->state.store(MPTaskQueueState::Ready);\n      OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n      return TRUE;\n   }\n\n   OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n   return FALSE;\n}\n\n\n/**\n * Stops a task queue.\n *\n * If there are tasks running the state is set to  Stopping.\n * If there are no tasks running the state is set to Stopped.\n *\n * \\return Returns FALSE if the task queue is not in the Ready state.\n */\nBOOL\nMPStopTaskQ(virt_ptr<MPTaskQueue> queue)\n{\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n\n   if (queue->state.load() != MPTaskQueueState::Ready) {\n      OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n      return FALSE;\n   }\n\n   if (queue->tasksRunning == 0) {\n      queue->state.store(MPTaskQueueState::Stopped);\n   } else {\n      queue->state.store(MPTaskQueueState::Stopping);\n   }\n\n   OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n   return TRUE;\n}\n\n\n/**\n * Resets the state of the task queue.\n *\n * This does not remove any tasks from the queue. It just resets the task queue\n * such that all tasks are ready to be run again.\n */\nBOOL\nMPResetTaskQ(virt_ptr<MPTaskQueue> queue)\n{\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n\n   if (queue->state.load() != MPTaskQueueState::Finished &&\n       queue->state.load() != MPTaskQueueState::Stopped) {\n      OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n      return FALSE;\n   }\n\n   queue->state.store(MPTaskQueueState::Initialised);\n   queue->tasks = queue->queueSize;\n   queue->tasksReady = queue->queueSize;\n   queue->tasksRunning = 0u;\n   queue->tasksFinished = 0u;\n   queue->queueIndex = 0u;\n\n   for (auto i = 0u; i < queue->tasks; ++i) {\n      auto task = queue->queue[i];\n      task->result = 0u;\n      task->coreID = 3u;\n      task->duration = 0u;\n      task->state = MPTaskState::Ready;\n   }\n\n   OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n   return TRUE;\n}\n\n\n/**\n * Add a task to the end of the queue.\n *\n * Returns FALSE if the task state is not set to Initialised.\n * Returns FALSE if the task queue is full.\n * Returns FALSE if the task queue state is not valid.\n *\n * \\return Returns TRUE if the task was added to the queue.\n */\nBOOL\nMPEnqueTask(virt_ptr<MPTaskQueue> queue,\n            virt_ptr<MPTask> task)\n{\n   if (task->state != MPTaskState::Initialised) {\n      return FALSE;\n   }\n\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n\n   if (queue->queueSize >= queue->queueMaxSize) {\n      OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n      return FALSE;\n   }\n\n   if (queue->state.load() < MPTaskQueueState::Initialised\n    || queue->state.load() > MPTaskQueueState::Finished) {\n      OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n      return FALSE;\n   }\n\n   task->queue = queue;\n   task->state = MPTaskState::Ready;\n\n   queue->tasks++;\n   queue->tasksReady++;\n   queue->queue[queue->queueSize] = task;\n   queue->queueSize++;\n\n   if (queue->state.load() == MPTaskQueueState::Finished) {\n      queue->state.store(MPTaskQueueState::Ready);\n   }\n\n   OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n   return TRUE;\n}\n\n\n/**\n * Dequeue 1 task at queueIndex\n *\n * Does not remove tasks from queue buffer.\n *\n * \\return Returns dequeued task.\n */\nvirt_ptr<MPTask>\nMPDequeTask(virt_ptr<MPTaskQueue> queue)\n{\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n\n   if (queue->state.load() != MPTaskQueueState::Ready\n    || queue->queueIndex == queue->queueSize) {\n      OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n      return nullptr;\n   }\n\n   auto task = queue->queue[queue->queueIndex];\n   queue->queueIndex++;\n   OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n   return task;\n}\n\n\n/**\n * Dequeue N tasks from queueIndex\n *\n * Does not remove tasks from queue buffer.\n *\n * \\return Returns number of tasks dequeued.\n */\nuint32_t\nMPDequeTasks(virt_ptr<MPTaskQueue> queue,\n             virt_ptr<virt_ptr<MPTask>>  taskBuffer,\n             uint32_t taskBufferLen)\n{\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n   uint32_t count, available;\n\n   if (queue->state.load() != MPTaskQueueState::Ready) {\n      OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n      return 0;\n   }\n\n   available = queue->queueSize - queue->queueIndex;\n   count = std::min(available, taskBufferLen);\n\n   for (auto i = 0u; i < count; ++i) {\n      taskBuffer[i] = queue->queue[queue->queueIndex];\n      queue->queueIndex++;\n   }\n\n   OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n   return count;\n}\n\n\n/**\n * Busy wait until state matches mask.\n *\n * \\return Always returns TRUE.\n */\nBOOL\nMPWaitTaskQ(virt_ptr<MPTaskQueue> queue,\n            MPTaskQueueState mask)\n{\n   while ((queue->state.load() & mask) == 0);\n   return TRUE;\n}\n\n\n/**\n * Busy wait with timeout until state matches mask.\n *\n * \\return Returns FALSE if wait timed out.\n */\nBOOL\nMPWaitTaskQWithTimeout(virt_ptr<MPTaskQueue> queue,\n                       MPTaskQueueState mask,\n                       OSTimeNanoseconds timeoutNS)\n{\n   auto start = OSGetTime();\n   auto end = start + internal::nsToTicks(timeoutNS);\n\n   while ((queue->state.load() & mask) == 0) {\n      if (OSGetTime() >= end) {\n         break;\n      }\n   }\n\n   return (queue->state.load() & mask) != 0;\n}\n\n\n/**\n * Print debug information about task queue.\n */\nBOOL\nMPPrintTaskQStats(virt_ptr<MPTaskQueue> queue,\n                  uint32_t unk)\n{\n   // TODO: Implement MPPrintTaskQStats\n   return TRUE;\n}\n\n\n/**\n * Initialises a task structure.\n */\nvoid\nMPInitTask(virt_ptr<MPTask> task,\n           MPTaskFunc func,\n           uint32_t userArg1,\n           uint32_t userArg2)\n{\n   task->self = task;\n   task->queue = nullptr;\n   task->state = MPTaskState::Initialised;\n   task->func = func;\n   task->userArg1 = userArg1;\n   task->userArg2 = userArg2;\n   task->result = 0u;\n   task->coreID = 3u;\n   task->duration = 0u;\n   task->userData = nullptr;\n}\n\n\n/**\n * Terminates a task.\n *\n * Yes this really does only return TRUE in coreinit.rpl\n */\nBOOL\nMPTermTask(virt_ptr<MPTask> task)\n{\n   return TRUE;\n}\n\n\n/**\n * Get information about a task.\n *\n * \\return Returns TRUE if successful.\n */\nBOOL\nMPGetTaskInfo(virt_ptr<MPTask> task,\n              virt_ptr<MPTaskInfo> info)\n{\n   info->coreID = task->coreID;\n   info->duration = task->duration;\n   info->result = task->result;\n   info->state = task->state;\n   return TRUE;\n}\n\n\n/**\n * Returns a task's user data which can be set with MPSetTaskUserData.\n */\nvirt_ptr<void>\nMPGetTaskUserData(virt_ptr<MPTask> task)\n{\n   return task->userData;\n}\n\n\n/**\n * Sets a task's user data which can be retrieved with MPGetTaskUserData.\n */\nvoid\nMPSetTaskUserData(virt_ptr<MPTask> task,\n                  virt_ptr<void> userData)\n{\n   task->userData = userData;\n}\n\n\n/**\n * Run N tasks from queue.\n *\n * \\param tasks Number of tasks to dequeue and run at once\n *\n * Does not remove tasks from queue.\n * Can be run from multiple threads at once.\n *\n * Side Effects:\n * - Sets state to Stopped if state is Stopping and tasksRunning reaches 0.\n * - Sets state to Finished if all tasks are finished.\n * - TasksReady -> TasksRunning -> TasksFinished.\n *\n * Returns TRUE if at least 1 task is run.\n */\nBOOL\nMPRunTasksFromTaskQ(virt_ptr<MPTaskQueue> queue,\n                    uint32_t tasks)\n{\n   BOOL result = FALSE;\n\n   while (queue->state.load() == MPTaskQueueState::Ready) {\n      OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n      auto available = queue->queueSize - queue->queueIndex;\n      auto count = std::min(available, tasks);\n      auto first = queue->queueIndex;\n\n      queue->tasksReady -= count;\n      queue->tasksRunning += count;\n      queue->queueIndex += count;\n      OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n\n      if (count == 0) {\n         // Nothing to run, lets go home!\n         break;\n      }\n\n      // Result is TRUE if at least 1 task is run\n      result = TRUE;\n\n      // Mark all tasks as running\n      for (auto i = 0u; i < count; ++i) {\n         auto task = queue->queue[first + i];\n         task->state = MPTaskState::Running;\n         task->coreID = OSGetCoreId();\n      }\n\n      // Run all tasks\n      for (auto i = 0u; i < count; ++i) {\n         auto task = queue->queue[first + i];\n         auto start = OSGetTime();\n         task->result = cafe::invoke(cpu::this_core::state(),\n                                     task->func,\n                                     task->userArg1,\n                                     task->userArg2);\n         task->state = MPTaskState::Finished;\n         task->duration = OSGetTime() - start;\n      }\n\n      OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n      queue->tasksRunning -= count;\n      queue->tasksFinished += count;\n\n      if (queue->state.load() == MPTaskQueueState::Stopping && queue->tasksRunning == 0) {\n         queue->state.store(MPTaskQueueState::Stopped);\n      }\n\n      if (queue->tasks == queue->tasksFinished) {\n         queue->state.store(MPTaskQueueState::Finished);\n      }\n\n      OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n   }\n\n   return result;\n}\n\n\n/**\n * Run a specific task.\n *\n * The task must belong to a queue.\n * The task must be in the Ready state.\n *\n * \\return Returns TRUE if task was run.\n */\nBOOL\nMPRunTask(virt_ptr<MPTask> task)\n{\n   auto queue = task->queue;\n\n   if (task->state != MPTaskState::Ready) {\n      return FALSE;\n   }\n\n   if (!queue\n     || queue->state.load() == MPTaskQueueState::Stopping\n     || queue->state.load() == MPTaskQueueState::Stopped) {\n      return FALSE;\n   }\n\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n   queue->tasksReady--;\n   queue->tasksRunning++;\n   OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n\n   task->state = MPTaskState::Running;\n   task->coreID = OSGetCoreId();\n\n   auto start = OSGetTime();\n   task->result = cafe::invoke(cpu::this_core::state(),\n                               task->func,\n                               task->userArg1,\n                               task->userArg2);\n   task->duration = OSGetTime() - start;\n\n   task->state = MPTaskState::Finished;\n\n   OSUninterruptibleSpinLock_Acquire(virt_addrof(queue->lock));\n   queue->tasksRunning--;\n   queue->tasksFinished++;\n\n   if (queue->state.load() == MPTaskQueueState::Stopping && queue->tasksRunning == 0) {\n      queue->state.store(MPTaskQueueState::Stopped);\n   }\n\n   if (queue->tasks == queue->tasksFinished) {\n      queue->state.store(MPTaskQueueState::Finished);\n   }\n\n   OSUninterruptibleSpinLock_Release(virt_addrof(queue->lock));\n   return TRUE;\n}\n\nvoid\nLibrary::registerTaskQueueSymbols()\n{\n   RegisterFunctionExport(MPInitTaskQ);\n   RegisterFunctionExport(MPTermTaskQ);\n   RegisterFunctionExport(MPGetTaskQInfo);\n   RegisterFunctionExport(MPStartTaskQ);\n   RegisterFunctionExport(MPStopTaskQ);\n   RegisterFunctionExport(MPResetTaskQ);\n   RegisterFunctionExport(MPEnqueTask);\n   RegisterFunctionExport(MPDequeTask);\n   RegisterFunctionExport(MPDequeTasks);\n   RegisterFunctionExport(MPWaitTaskQ);\n   RegisterFunctionExport(MPWaitTaskQWithTimeout);\n   RegisterFunctionExport(MPPrintTaskQStats);\n   RegisterFunctionExport(MPInitTask);\n   RegisterFunctionExport(MPTermTask);\n   RegisterFunctionExport(MPGetTaskInfo);\n   RegisterFunctionExport(MPGetTaskUserData);\n   RegisterFunctionExport(MPSetTaskUserData);\n   RegisterFunctionExport(MPRunTasksFromTaskQ);\n   RegisterFunctionExport(MPRunTask);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_taskqueue.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_spinlock.h\"\n#include \"coreinit_time.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_taskqueue Task Queue\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct MPTaskInfo;\nstruct MPTask;\nstruct MPTaskQueueInfo;\nstruct MPTaskQueue;\n\nusing MPTaskFunc = virt_func_ptr<uint32_t(uint32_t arg1,\n                                          uint32_t arg2)>;\n\nstruct MPTaskInfo\n{\n   be2_val<MPTaskState> state;\n   be2_val<uint32_t> result;\n   be2_val<uint32_t> coreID;\n   be2_val<OSTime> duration;\n};\nCHECK_OFFSET(MPTaskInfo, 0x00, state);\nCHECK_OFFSET(MPTaskInfo, 0x04, result);\nCHECK_OFFSET(MPTaskInfo, 0x08, coreID);\nCHECK_OFFSET(MPTaskInfo, 0x0C, duration);\nCHECK_SIZE(MPTaskInfo, 0x14);\n\nstruct MPTask\n{\n   be2_virt_ptr<MPTask> self;\n   be2_virt_ptr<MPTaskQueue> queue;\n   be2_val<MPTaskState> state;\n   be2_val<MPTaskFunc> func;\n   be2_val<uint32_t> userArg1;\n   be2_val<uint32_t> userArg2;\n   be2_val<uint32_t> result;\n   be2_val<uint32_t> coreID;\n   be2_val<OSTime> duration;\n   be2_virt_ptr<void> userData;\n};\nCHECK_OFFSET(MPTask, 0x00, self);\nCHECK_OFFSET(MPTask, 0x04, queue);\nCHECK_OFFSET(MPTask, 0x08, state);\nCHECK_OFFSET(MPTask, 0x0C, func);\nCHECK_OFFSET(MPTask, 0x10, userArg1);\nCHECK_OFFSET(MPTask, 0x14, userArg2);\nCHECK_OFFSET(MPTask, 0x18, result);\nCHECK_OFFSET(MPTask, 0x1C, coreID);\nCHECK_OFFSET(MPTask, 0x20, duration);\nCHECK_OFFSET(MPTask, 0x28, userData);\nCHECK_SIZE(MPTask, 0x2C);\n\nstruct MPTaskQueueInfo\n{\n   be2_val<MPTaskQueueState> state;\n   be2_val<uint32_t> tasks;\n   be2_val<uint32_t> tasksReady;\n   be2_val<uint32_t> tasksRunning;\n   be2_val<uint32_t> tasksFinished;\n};\nCHECK_OFFSET(MPTaskQueueInfo, 0x00, state);\nCHECK_OFFSET(MPTaskQueueInfo, 0x04, tasks);\nCHECK_OFFSET(MPTaskQueueInfo, 0x08, tasksReady);\nCHECK_OFFSET(MPTaskQueueInfo, 0x0C, tasksRunning);\nCHECK_OFFSET(MPTaskQueueInfo, 0x10, tasksFinished);\nCHECK_SIZE(MPTaskQueueInfo, 0x14);\n\nstruct MPTaskQueue\n{\n   be2_virt_ptr<MPTaskQueue> self;\n   be2_atomic<MPTaskQueueState> state;\n   be2_val<uint32_t> tasks;\n   be2_val<uint32_t> tasksReady;\n   be2_val<uint32_t> tasksRunning;\n   UNKNOWN(4);\n   be2_val<uint32_t> tasksFinished;\n   UNKNOWN(8);\n   be2_val<uint32_t> queueIndex;\n   UNKNOWN(8);\n   be2_val<uint32_t> queueSize;\n   UNKNOWN(4);\n   be2_virt_ptr<virt_ptr<MPTask>> queue;\n   be2_val<uint32_t> queueMaxSize;\n   be2_struct<OSSpinLock> lock;\n};\nCHECK_OFFSET(MPTaskQueue, 0x00, self);\nCHECK_OFFSET(MPTaskQueue, 0x04, state);\nCHECK_OFFSET(MPTaskQueue, 0x08, tasks);\nCHECK_OFFSET(MPTaskQueue, 0x0C, tasksReady);\nCHECK_OFFSET(MPTaskQueue, 0x10, tasksRunning);\nCHECK_OFFSET(MPTaskQueue, 0x18, tasksFinished);\nCHECK_OFFSET(MPTaskQueue, 0x24, queueIndex);\nCHECK_OFFSET(MPTaskQueue, 0x30, queueSize);\nCHECK_OFFSET(MPTaskQueue, 0x38, queue);\nCHECK_OFFSET(MPTaskQueue, 0x3C, queueMaxSize);\nCHECK_OFFSET(MPTaskQueue, 0x40, lock);\nCHECK_SIZE(MPTaskQueue, 0x50);\n\n#pragma pack(pop)\n\nvoid\nMPInitTaskQ(virt_ptr<MPTaskQueue> queue,\n            virt_ptr<virt_ptr<MPTask>> taskBuffer,\n            uint32_t taskBufferLen);\n\nBOOL\nMPTermTaskQ(virt_ptr<MPTaskQueue> queue);\n\nBOOL\nMPGetTaskQInfo(virt_ptr<MPTaskQueue> queue,\n               virt_ptr<MPTaskQueueInfo> info);\n\nBOOL\nMPStartTaskQ(virt_ptr<MPTaskQueue> queue);\n\nBOOL\nMPStopTaskQ(virt_ptr<MPTaskQueue> queue);\n\nBOOL\nMPResetTaskQ(virt_ptr<MPTaskQueue> queue);\n\nBOOL\nMPEnqueTask(virt_ptr<MPTaskQueue> queue,\n            virt_ptr<MPTask> task);\n\nvirt_ptr<MPTask>\nMPDequeTask(virt_ptr<MPTaskQueue> queue);\n\nuint32_t\nMPDequeTasks(virt_ptr<MPTaskQueue> queue,\n             virt_ptr<virt_ptr<MPTask>> taskBuffer,\n             uint32_t taskBufferLen);\n\nBOOL\nMPWaitTaskQ(virt_ptr<MPTaskQueue> queue,\n            MPTaskQueueState mask);\n\nBOOL\nMPWaitTaskQWithTimeout(virt_ptr<MPTaskQueue> queue,\n                       MPTaskQueueState wmask,\n                       OSTimeNanoseconds timeoutNS);\n\nBOOL\nMPPrintTaskQStats(virt_ptr<MPTaskQueue> queue,\n                  uint32_t unk);\n\nvoid\nMPInitTask(virt_ptr<MPTask> task,\n           MPTaskFunc func,\n           uint32_t userArg1,\n           uint32_t userArg2);\n\nBOOL\nMPTermTask(virt_ptr<MPTask> task);\n\nBOOL\nMPGetTaskInfo(virt_ptr<MPTask> task,\n              virt_ptr<MPTaskInfo> info);\n\nvirt_ptr<void>\nMPGetTaskUserData(virt_ptr<MPTask> task);\n\nvoid\nMPSetTaskUserData(virt_ptr<MPTask> task,\n                  virt_ptr<void> userData);\n\nBOOL\nMPRunTasksFromTaskQ(virt_ptr<MPTaskQueue> queue,\n                    uint32_t count);\n\nBOOL\nMPRunTask(virt_ptr<MPTask> task);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_thread.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_alarm.h\"\n#include \"coreinit_core.h\"\n#include \"coreinit_context.h\"\n#include \"coreinit_cosreport.h\"\n#include \"coreinit_dynload.h\"\n#include \"coreinit_enum_string.h\"\n#include \"coreinit_fastmutex.h\"\n#include \"coreinit_ghs.h\"\n#include \"coreinit_lockedcache.h\"\n#include \"coreinit_ipcdriver.h\"\n#include \"coreinit_interrupts.h\"\n#include \"coreinit_memheap.h\"\n#include \"coreinit_memory.h\"\n#include \"coreinit_mutex.h\"\n#include \"coreinit_rendezvous.h\"\n#include \"coreinit_scheduler.h\"\n#include \"coreinit_systeminfo.h\"\n#include \"coreinit_thread.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/kernel/cafe_kernel.h\"\n#include \"cafe/kernel/cafe_kernel_context.h\"\n#include \"cafe/libraries/cafe_hle.h\"\n\n#include <array>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <fmt/format.h>\n#include <iterator>\n#include <libcpu/state.h>\n#include <libcpu/cpu_formatters.h>\n#include <limits>\n\nnamespace cafe::coreinit\n{\n\nstatic uint16_t\nsThreadId = 1;\n\nstatic AlarmCallbackFn\nsSleepAlarmHandler = nullptr;\n\nstatic OSThreadEntryPointFn\nsThreadEntryPoint = nullptr;\n\nstatic OSThreadEntryPointFn\nsDeallocatorThreadEntryPoint = nullptr;\n\nstatic OSThreadEntryPointFn\nsDefaultThreadEntryPoint = nullptr;\n\nconstexpr auto DeallocatorThreadSize = 0x2000u;\n\nstruct StaticThreadData\n{\n   struct PerCoreData\n   {\n      be2_struct<OSThread> defaultThread;\n      be2_array<char, 32> defaultThreadName;\n      be2_struct<OSAlarm> timeSliceAlarm;\n      be2_struct<OSThreadQueue> deallocationQueue;\n      be2_struct<OSThreadQueue> deallocationThreadQueue;\n      be2_struct<OSThread> deallocatorThread;\n      be2_array<uint8_t, DeallocatorThreadSize> deallocatorThreadStack;\n      be2_array<char, 40> deallocatorThreadName;\n   };\n\n   be2_array<PerCoreData, OSGetCoreCount()> perCoreData;\n   be2_struct<OSRendezvous> defaultThreadInitRendezvous;\n   be2_val<uint32_t> defaultThreadInitRendezvousWaitMask;\n};\n\nstatic virt_ptr<StaticThreadData>\nsThreadData = nullptr;\n\nstatic void\nclearThreadStackWithValue(virt_ptr<OSThread> thread,\n                          uint32_t value)\n{\n   auto clearStart = virt_ptr<uint32_t> { nullptr };\n   auto clearEnd = virt_ptr<uint32_t> { nullptr };\n\n   if (OSGetCurrentThread() == thread) {\n      clearStart = thread->stackEnd + 4;\n      clearEnd = virt_cast<uint32_t *>(OSGetStackPointer());\n   } else {\n      // We assume that the thread must be paused while this is happening...\n      // This might be a bad assumption to make, but otherwise we run\n      // into some sketchy race conditions.\n      clearStart = thread->stackEnd + 4;\n      clearEnd = virt_cast<uint32_t *>(virt_addr { thread->context.gpr[1].value() });\n   }\n\n   for (auto addr = clearStart; addr < clearEnd; addr += 4) {\n      *addr = value;\n   }\n}\n\n\n/**\n * Cancels a thread.\n *\n * This sets the threads requestFlag to OSThreadRequest::Cancel, the thread will\n * be terminated next time OSTestThreadCancel is called.\n */\nvoid\nOSCancelThread(virt_ptr<OSThread> thread)\n{\n   bool reschedule = false;\n   internal::lockScheduler();\n\n   if (thread->requestFlag == OSThreadRequest::Suspend) {\n      internal::wakeupThreadWaitForSuspensionNoLock(\n         virt_addrof(thread->suspendQueue),\n         -1);\n      reschedule = true;\n   }\n\n   if (thread->suspendCounter != 0) {\n      if (thread->cancelState == OSThreadCancelState::Enabled) {\n         internal::resumeThreadNoLock(thread, thread->suspendCounter);\n         reschedule = true;\n      }\n   }\n\n   if (reschedule) {\n      internal::rescheduleAllCoreNoLock();\n   }\n\n   thread->suspendCounter = 0;\n   thread->needSuspend = 0;\n   thread->requestFlag = OSThreadRequest::Cancel;\n   internal::unlockScheduler();\n\n   if (OSGetCurrentThread() == thread) {\n      if (thread->cancelState == OSThreadCancelState::Enabled) {\n         OSExitThread(-1);\n      }\n   }\n}\n\n\n/**\n * Returns the count of active threads.\n */\nint32_t\nOSCheckActiveThreads()\n{\n   internal::lockScheduler();\n   auto threadCount = internal::checkActiveThreadsNoLock();\n   internal::unlockScheduler();\n   return threadCount;\n}\n\n\n/**\n * Get the maximum amount of stack the thread has used.\n */\nint32_t\nOSCheckThreadStackUsage(virt_ptr<OSThread> thread)\n{\n   auto addr = virt_ptr<uint32_t> { nullptr };\n   internal::lockScheduler();\n\n   for (addr = thread->stackEnd + 4; addr < thread->stackStart; addr += 4) {\n      if (*addr != 0xFEFEFEFE) {\n         break;\n      }\n   }\n\n   auto result = virt_cast<virt_addr>(thread->stackStart) - virt_cast<virt_addr>(addr);\n   internal::unlockScheduler();\n   return static_cast<int32_t>(result);\n}\n\n\n/**\n * Clear current stack with a value.\n */\nvoid\nOSClearStack(uint32_t value)\n{\n   auto thread = OSGetCurrentThread();\n   auto stackTop = OSGetStackPointer();\n   for (auto ptr = thread->stackEnd + 1; ptr < stackTop; ++ptr) {\n      *ptr = value;\n   }\n}\n\n\n/**\n * Disable tracking of thread stack usage\n */\nvoid\nOSClearThreadStackUsage(virt_ptr<OSThread> thread)\n{\n   internal::lockScheduler();\n\n   if (!thread) {\n      thread = OSGetCurrentThread();\n   }\n\n   thread->attr &= ~OSThreadAttributes::StackUsage;\n   internal::unlockScheduler();\n}\n\n\n/**\n * Clears a thread's suspend counter and resumes it.\n */\nvoid\nOSContinueThread(virt_ptr<OSThread> thread)\n{\n   internal::lockScheduler();\n   internal::resumeThreadNoLock(thread, thread->suspendCounter);\n   internal::rescheduleAllCoreNoLock();\n   internal::unlockScheduler();\n}\n\n\n/**\n * Thread entry.\n */\nstatic uint32_t\nthreadEntry(uint32_t argc,\n            virt_ptr<void> argv)\n{\n   auto thread = OSGetCurrentThread();\n   auto interruptsState = OSDisableInterrupts();\n   internal::ghsExceptionInit(thread);\n   OSRestoreInterrupts(interruptsState);\n   return cafe::invoke(cpu::this_core::state(),\n                       thread->entryPoint,\n                       argc,\n                       argv);\n}\n\n\n/**\n * Setup thread run state, shared by OSRunThread and OSCreateThread\n */\nstatic void\ninitialiseThreadState(virt_ptr<OSThread> thread,\n                      OSThreadEntryPointFn entry,\n                      uint32_t argc,\n                      virt_ptr<void> argv,\n                      virt_ptr<void> stack,\n                      uint32_t stackSize,\n                      int32_t priority,\n                      uint32_t pir,\n                      OSThreadType type)\n{\n   // Setup thread state\n   thread->priority = priority;\n   thread->basePriority = priority;\n   thread->tag = OSThread::Tag;\n   thread->suspendResult = -1;\n   thread->needSuspend = 0;\n   thread->exitValue = -1;\n   thread->type = type;\n   thread->state = entry ? OSThreadState::Ready : OSThreadState::None;\n   thread->mutex = nullptr;\n   thread->deallocator = nullptr;\n   thread->coreTimeConsumedNs = 0ull;\n   thread->cleanupCallback = nullptr;\n   thread->requestFlag = OSThreadRequest::None;\n   thread->fastMutex = nullptr;\n   thread->waitEventTimeoutAlarm = nullptr;\n   thread->runQuantumTicks = 0ll;\n   thread->cancelState = OSThreadCancelState::Enabled;\n   thread->entryPoint = entry;\n   thread->suspendCounter = entry ? 1 : 0;\n   thread->eh_globals = nullptr;\n   thread->eh_mem_manage.fill(nullptr);\n   thread->eh_store_globals.fill(nullptr);\n   thread->eh_store_globals_tdeh.fill(nullptr);\n   thread->tlsSectionCount = uint16_t { 0u };\n   thread->tlsSections = nullptr;\n   thread->contendedFastMutexes.head = nullptr;\n   thread->contendedFastMutexes.tail = nullptr;\n   thread->mutexQueue.head = nullptr;\n   thread->mutexQueue.tail = nullptr;\n   thread->mutexQueue.parent = thread;\n   thread->alarmCancelled = 0;\n   thread->specific.fill(0u);\n   thread->wakeCount = 0ull;\n   thread->unk0x610 = 0ll;\n   thread->unk0x618 = 0ll;\n   thread->unk0x620 = 0x7FFFFFFFFFFFFFFFll;\n   thread->unk0x628 = 0ll;\n   OSInitThreadQueueEx(virt_addrof(thread->joinQueue), thread);\n   OSInitThreadQueueEx(virt_addrof(thread->suspendQueue), thread);\n\n   // Setup thread stack\n   auto stackInit = virt_cast<uint32_t *>(align_down(virt_cast<virt_addr>(stack), 8));\n   *(stackInit - 1) = 0u;\n   *(stackInit - 2) = 0u;\n\n   thread->stackStart = virt_cast<uint32_t *>(stack);\n   thread->stackEnd = virt_cast<uint32_t *>(virt_cast<virt_addr>(stack) - stackSize);\n   *thread->stackEnd = 0xDEADBABE;\n\n   // Setup thread context\n   OSInitContext(virt_addrof(thread->context),\n                 virt_func_cast<virt_addr>(sThreadEntryPoint),\n                 align_down(virt_cast<virt_addr>(stack), 8) - 8);\n\n   thread->context.pir = pir;\n   thread->context.lr = hle::getLibrary(hle::LibraryId::coreinit)->findSymbolAddress(\"OSExitThread\");\n   thread->context.gpr[3] = argc;\n   thread->context.gpr[4] = static_cast<uint32_t>(virt_cast<virt_addr>(argv));\n   thread->context.fpscr = 4u;\n   thread->context.psf.fill(0.0);\n   thread->context.fpr.fill(0.0);\n   thread->context.gqr[2] = 0x40004u;\n   thread->context.gqr[3] = 0x50005u;\n   thread->context.gqr[4] = 0x60006u;\n   thread->context.gqr[5] = 0x70007u;\n   thread->context.coretime.fill(0);\n}\n\nstatic BOOL\ncreateThread(virt_ptr<OSThread> thread,\n             OSThreadEntryPointFn entry,\n             uint32_t argc,\n             virt_ptr<void> argv,\n             virt_ptr<uint32_t> stack,\n             uint32_t stackSize,\n             int32_t priority,\n             OSThreadAttributes attributes,\n             OSThreadType type)\n{\n   auto currentThread = internal::getCurrentThread();\n\n   // If no affinity is defined, we need to copy the affinity from the calling thread\n   if ((attributes & OSThreadAttributes::AffinityAny) == 0) {\n      auto curAttr = currentThread->attr;\n      attributes = attributes | (curAttr & OSThreadAttributes::AffinityAny);\n   }\n\n   auto realPriority = priority;\n   if (type == OSThreadType::Driver) {\n      if (priority < 0 || priority >= 32) {\n         decaf_abort(\"Thread priority was out of range\");\n      }\n\n      realPriority = priority;\n   } else if (type == OSThreadType::AppIo) {\n      if (priority < 0 || priority >= 32) {\n         decaf_abort(\"Thread priority was out of range\");\n      }\n\n      realPriority = priority + 32;\n   } else if (type == OSThreadType::App) {\n      if (priority < 0 || priority >= 32) {\n         decaf_abort(\"Thread priority was out of range\");\n      }\n\n      realPriority = priority + 64;\n   } else {\n      return FALSE;\n   }\n\n   // Setup thread state\n   internal::lockScheduler();\n   std::memset(thread.get(), 0, sizeof(OSThread));\n   initialiseThreadState(thread, entry, argc, argv, stack, stackSize, realPriority,\n                         OSGetCoreId(), type);\n   thread->name = nullptr;\n   thread->context.attr = attributes & OSThreadAttributes::AffinityAny;\n   thread->attr = attributes;\n   thread->id = sThreadId++;\n   thread->dsiCallback = currentThread->dsiCallback;\n   thread->isiCallback = currentThread->isiCallback;\n   thread->programCallback = currentThread->programCallback;\n   thread->perfMonCallback = currentThread->perfMonCallback;\n   thread->alignCallback = currentThread->alignCallback;\n\n   // Copy FPU exception status\n   thread->context.fpscr |= currentThread->context.fpscr & 0xF8;\n\n   if (entry) {\n      internal::markThreadActiveNoLock(thread);\n   }\n\n   internal::unlockScheduler();\n\n   gLog->info(\"Thread Created: ptr {}, id 0x{:X}, basePriority {}, attr 0x{:02X}, entry {}, stackStart {}, stackEnd {}\",\n      thread, thread->id, thread->basePriority, thread->attr,\n      virt_func_cast<virt_addr>(entry), thread->stackStart, thread->stackEnd);\n\n   return TRUE;\n}\n\nBOOL\nOSCreateThread(virt_ptr<OSThread> thread,\n               OSThreadEntryPointFn entry,\n               uint32_t argc,\n               virt_ptr<void> argv,\n               virt_ptr<uint32_t> stack,\n               uint32_t stackSize,\n               int32_t priority,\n               OSThreadAttributes attributes)\n{\n   return createThread(thread, entry, argc, argv, stack, stackSize, priority, attributes, OSThreadType::App);\n}\n\nBOOL\nOSCreateThreadType(virt_ptr<OSThread> thread,\n                   OSThreadEntryPointFn entry,\n                   uint32_t argc,\n                   virt_ptr<void> argv,\n                   virt_ptr<uint32_t> stack,\n                   uint32_t stackSize,\n                   int32_t priority,\n                   OSThreadAttributes attributes,\n                   OSThreadType type)\n{\n   if (type != OSThreadType::AppIo && type != OSThreadType::App) {\n      return FALSE;\n   }\n\n   return createThread(thread, entry, argc, argv, stack, stackSize, priority, attributes, type);\n}\n\nBOOL\ncoreinit__OSCreateThreadType(virt_ptr<OSThread> thread,\n                             OSThreadEntryPointFn entry,\n                             uint32_t argc,\n                             virt_ptr<void> argv,\n                             virt_ptr<uint32_t> stack,\n                             uint32_t stackSize,\n                             int32_t priority,\n                             OSThreadAttributes attributes,\n                             OSThreadType type)\n{\n   return createThread(thread, entry, argc, argv, stack, stackSize, priority, attributes, type);\n}\n\n/**\n * Detach thread.\n */\nvoid\nOSDetachThread(virt_ptr<OSThread> thread)\n{\n   internal::lockScheduler();\n\n   // HACK: Unfortunately this check is not valid in all games.  One Piece performs\n   //  OSJoinThread on a thread, and then subsequently calls OSDetachThread on it\n   //  for whatever reason.  Coreinit doesnt check this, so we can't do this check.\n   //decaf_check(internal::isThreadActiveNoLock(thread));\n\n   thread->attr |= OSThreadAttributes::Detached;\n\n   if (thread->state == OSThreadState::Moribund) {\n      // Thread has already ended so we can remove it from the active list\n      internal::markThreadInactiveNoLock(thread);\n\n      if (thread->deallocator) {\n         internal::queueThreadDeallocation(thread);\n      }\n\n      thread->state = OSThreadState::None;\n      // TODO: thread->id = 0x8000;\n   }\n\n   internal::wakeupThreadNoLock(virt_addrof(thread->joinQueue));\n   internal::rescheduleAllCoreNoLock();\n   internal::unlockScheduler();\n}\n\n\n/**\n * Exit the current thread with a exit code.\n *\n * This function is implicitly called when the thread entry point returns.\n */\n[[noreturn]] void\nOSExitThread(int value)\n{\n   auto thread = OSGetCurrentThread();\n\n   // Call any thread cleanup callbacks\n   if (thread->cleanupCallback) {\n      thread->cancelState |= OSThreadCancelState::Disabled;\n      cafe::invoke(cpu::this_core::state(),\n                   thread->cleanupCallback,\n                   thread,\n                   virt_cast<void *>(thread->stackEnd));\n   }\n\n   // Cleanup the GHS exceptions we previously created\n   internal::ghsExceptionCleanup(thread);\n\n   // Free any TLS data which was allocated to this thread\n   if (thread->tlsSections) {\n      internal::dynLoadTlsFree(thread);\n   }\n\n   // Disable interrupts and lock the scheduler\n   OSDisableInterrupts();\n   internal::lockScheduler();\n\n   // Actually proccess the thread exit\n   internal::exitThreadNoLock(value);\n\n   // noreturn\n}\n\n\n/**\n * Get the next and previous thread in the thread's active queue.\n */\nvoid\nOSGetActiveThreadLink(virt_ptr<OSThread> thread,\n                      virt_ptr<OSThreadLink> link)\n{\n   *link = thread->activeLink;\n}\n\n\n/**\n * Return pointer to OSThread object for the current thread.\n */\nvirt_ptr<OSThread>\nOSGetCurrentThread()\n{\n   return internal::getCurrentThread();\n}\n\n\n/**\n * Returns the default thread for a specific core.\n */\nvirt_ptr<OSThread>\nOSGetDefaultThread(uint32_t coreID)\n{\n   if (coreID >= CoreCount) {\n      return nullptr;\n   }\n\n   return virt_addrof(sThreadData->perCoreData[coreID].defaultThread);\n}\n\n\n/**\n * Return current stack pointer, value of r1 register.\n */\nvirt_ptr<uint32_t>\nOSGetStackPointer()\n{\n   return virt_cast<uint32_t *>(virt_addr { cpu::this_core::state()->systemCallStackHead });\n}\n\n\n/**\n * Return user stack pointer.\n */\nvirt_ptr<uint32_t>\nOSGetUserStackPointer(virt_ptr<OSThread> thread)\n{\n   auto stack = virt_ptr<uint32_t> { nullptr };\n   internal::lockScheduler();\n\n   if (OSIsThreadSuspended(thread)) {\n      stack = thread->userStackPointer;\n\n      if (!stack) {\n         stack = virt_cast<uint32_t *>(virt_addr { thread->context.gpr[1].value() });\n      }\n   }\n\n   internal::unlockScheduler();\n   return stack;\n}\n\n\n/**\n * Get a thread's affinity.\n */\nuint32_t\nOSGetThreadAffinity(virt_ptr<OSThread> thread)\n{\n   return thread->attr & OSThreadAttributes::AffinityAny;\n}\n\n\n/**\n * Get a thread's name.\n */\nvirt_ptr<const char>\nOSGetThreadName(virt_ptr<OSThread> thread)\n{\n   return thread->name;\n}\n\n\n/**\n * Get a thread's base priority.\n */\nint32_t\nOSGetThreadPriority(virt_ptr<OSThread> thread)\n{\n   if (thread->type == OSThreadType::Driver) {\n      return thread->basePriority;\n   } else if(thread->type == OSThreadType::AppIo) {\n      return thread->basePriority - 32;\n   } else if (thread->type == OSThreadType::App) {\n      return thread->basePriority - 64;\n   }\n\n   decaf_abort(\"Unexpected thread type in OSGetThreadPriority\");\n}\n\n\n/**\n * Get a thread's specific value set by OSSetThreadSpecific.\n */\nuint32_t\nOSGetThreadSpecific(uint32_t id)\n{\n   decaf_check(id >= 0 && id < 0x10);\n   return OSGetCurrentThread()->specific[id];\n}\n\n\n/**\n* Initialise a thread queue object.\n*/\nvoid\nOSInitThreadQueue(virt_ptr<OSThreadQueue> queue)\n{\n   OSInitThreadQueueEx(queue, nullptr);\n}\n\n\n/**\n* Initialise a thread queue object with a parent.\n*/\nvoid\nOSInitThreadQueueEx(virt_ptr<OSThreadQueue> queue,\n                    virt_ptr<void> parent)\n{\n   queue->head = nullptr;\n   queue->tail = nullptr;\n   queue->parent = parent;\n}\n\n\n/**\n * Returns TRUE if a thread is suspended.\n */\nBOOL\nOSIsThreadSuspended(virt_ptr<OSThread> thread)\n{\n   return thread->suspendCounter > 0;\n}\n\n\n/**\n * Returns TRUE if a thread is terminated.\n */\nBOOL\nOSIsThreadTerminated(virt_ptr<OSThread> thread)\n{\n   return thread->state == OSThreadState::None\n       || thread->state == OSThreadState::Moribund;\n}\n\n\n/**\n * Wait until thread is terminated.\n *\n * \\param thread Thread to wait for\n * \\param exitValue Pointer to store thread exit value in.\n * \\returns Returns TRUE if thread has terminated, FALSE otherwise.\n */\nBOOL\nOSJoinThread(virt_ptr<OSThread> thread,\n             virt_ptr<int32_t> outExitValue)\n{\n   internal::lockScheduler();\n\n   // If the thread has not ended, let's wait for it\n   //  note only one thread is allowed in the join queue\n   if (!(thread->attr & OSThreadAttributes::Detached) &&\n       thread->state != OSThreadState::Moribund &&\n       !thread->joinQueue.head) {\n      internal::sleepThreadNoLock(virt_addrof(thread->joinQueue));\n      internal::rescheduleSelfNoLock();\n\n      if (!internal::isThreadActiveNoLock(thread)) {\n         // This would only happen for detached threads.\n         internal::unlockScheduler();\n         return FALSE;\n      }\n   }\n\n   if (thread->state != OSThreadState::Moribund) {\n      internal::unlockScheduler();\n      return FALSE;\n   }\n\n   if (outExitValue) {\n      *outExitValue = thread->exitValue;\n   }\n\n   internal::markThreadInactiveNoLock(thread);\n   thread->state = OSThreadState::None;\n\n   if (thread->deallocator) {\n      internal::queueThreadDeallocation(thread);\n      internal::rescheduleSelfNoLock();\n   }\n\n   internal::unlockScheduler();\n   return TRUE;\n}\n\n\nvoid\nOSPrintCurrentThreadState()\n{\n   auto thread = OSGetCurrentThread();\n\n   if (!thread) {\n      return;\n   }\n\n   auto state = cpu::this_core::state();\n\n   fmt::memory_buffer out;\n   fmt::format_to(std::back_inserter(out), \"id   = {}\\n\", thread->id);\n\n   if (thread->name) {\n      fmt::format_to(std::back_inserter(out), \"name  = {}\\n\", thread->name);\n   }\n\n   fmt::format_to(std::back_inserter(out), \"cia   = 0x{:08X}\\n\", state->cia);\n   fmt::format_to(std::back_inserter(out), \"lr    = 0x{:08X}\\n\", state->lr);\n   fmt::format_to(std::back_inserter(out), \"cr    = 0x{:08X}\\n\", state->cr.value);\n   fmt::format_to(std::back_inserter(out), \"xer   = 0x{:08X}\\n\", state->xer.value);\n   fmt::format_to(std::back_inserter(out), \"ctr   = 0x{:08X}\\n\", state->ctr);\n\n   for (auto i = 0u; i < 32; ++i) {\n      fmt::format_to(std::back_inserter(out), \"r{:<2}   = 0x{:08X}\\n\", i, state->gpr[i]);\n   }\n\n   fmt::format_to(std::back_inserter(out), \"fpscr = 0x{:08X}\\n\", state->fpscr.value);\n\n   for (auto i = 0u; i < 32; ++i) {\n      fmt::format_to(std::back_inserter(out), \"f{:<2}   = {}\\n\", i, state->fpr[i].value);\n   }\n\n   for (auto i = 0u; i < 32; ++i) {\n      fmt::format_to(std::back_inserter(out), \"ps{:<2}   = {:<16} ps{:<2}   = {}\\n\", i, state->fpr[i].paired0, i, state->fpr[i].paired1);\n   }\n\n   gLog->info(std::string_view { out.data(), out.size() });\n}\n\n\n/**\n * Resumes a thread.\n *\n * Decrements the thread's suspend counter, if the counter reaches 0 the thread\n * is resumed.\n *\n * \\returns Returns the previous value of the suspend counter.\n */\nint32_t\nOSResumeThread(virt_ptr<OSThread> thread)\n{\n   internal::lockScheduler();\n   auto oldSuspendCounter = internal::resumeThreadNoLock(thread, 1);\n\n   if (oldSuspendCounter - 1 == 0) {\n      internal::rescheduleAllCoreNoLock();\n   }\n\n   internal::unlockScheduler();\n   return oldSuspendCounter;\n}\n\n\n/**\n * Run a function on an already created thread.\n *\n * Can only be used on idle threads.\n */\nBOOL\nOSRunThread(virt_ptr<OSThread> thread,\n            OSThreadEntryPointFn entry,\n            uint32_t argc,\n            virt_ptr<void> argv)\n{\n   BOOL result = FALSE;\n   internal::lockScheduler();\n\n   if (OSIsThreadTerminated(thread)) {\n      if (thread->state == OSThreadState::Moribund) {\n         internal::markThreadInactiveNoLock(thread);\n      }\n\n      auto stackSize =\n         virt_cast<virt_addr>(thread->stackStart) -\n         virt_cast<virt_addr>(thread->stackEnd);\n\n      initialiseThreadState(thread,\n                            entry,\n                            argc,\n                            argv,\n                            thread->stackStart,\n                            static_cast<uint32_t>(stackSize),\n                            thread->basePriority,\n                            thread->context.pir,\n                            thread->type);\n      internal::markThreadActiveNoLock(thread);\n      internal::resumeThreadNoLock(thread, 1);\n      internal::rescheduleAllCoreNoLock();\n      result = TRUE;\n   }\n\n   internal::unlockScheduler();\n   return result;\n}\n\n\n/**\n * Set a thread's affinity.\n */\nBOOL\nOSSetThreadAffinity(virt_ptr<OSThread> thread,\n                    uint32_t affinity)\n{\n   internal::lockScheduler();\n   internal::setThreadAffinityNoLock(thread, affinity);\n\n   if (thread->state == OSThreadState::Ready && affinity != 0) {\n      internal::rescheduleAllCoreNoLock();\n   }\n\n   internal::unlockScheduler();\n   return TRUE;\n}\n\n\n/**\n * Set a thread's cancellation state.\n */\nBOOL\nOSSetThreadCancelState(BOOL cancelEnabled)\n{\n   auto thread = OSGetCurrentThread();\n   auto oldCancelEnabled = TRUE;\n\n   if (thread->cancelState & OSThreadCancelState::Disabled) {\n      oldCancelEnabled = FALSE;\n   }\n\n   if (cancelEnabled) {\n      thread->cancelState &= ~OSThreadCancelState::Disabled;\n   } else {\n      thread->cancelState |= OSThreadCancelState::Disabled;\n   }\n\n   return oldCancelEnabled;\n}\n\n\n/**\n * Set the callback to be called just before a thread is terminated.\n *\n * \\return Returns the previous callback function.\n */\nOSThreadCleanupCallbackFn\nOSSetThreadCleanupCallback(virt_ptr<OSThread> thread,\n                           OSThreadCleanupCallbackFn callback)\n{\n   internal::lockScheduler();\n   auto old = thread->cleanupCallback;\n   thread->cleanupCallback = callback;\n   internal::unlockScheduler();\n   return old;\n}\n\n\n/**\n * Set the callback to be called just after a thread is terminated.\n */\nOSThreadDeallocatorFn\nOSSetThreadDeallocator(virt_ptr<OSThread> thread,\n                       OSThreadDeallocatorFn deallocator)\n{\n   internal::lockScheduler();\n   auto old = thread->deallocator;\n   thread->deallocator = deallocator;\n   internal::unlockScheduler();\n   return old;\n}\n\n\n/**\n * Set a thread's name.\n */\nvoid\nOSSetThreadName(virt_ptr<OSThread> thread,\n                virt_ptr<const char> name)\n{\n   thread->name = name;\n}\n\n\n/**\n * Set a thread's priority.\n */\nBOOL\nOSSetThreadPriority(virt_ptr<OSThread> thread,\n                    int32_t priority)\n{\n   auto realPriority = priority;\n   if (thread->type == OSThreadType::Driver) {\n      realPriority = priority;\n   } else if (thread->type == OSThreadType::AppIo) {\n      realPriority = priority + 32;\n   } else if (thread->type == OSThreadType::App) {\n      realPriority = priority + 64;\n   } else {\n      return FALSE;\n   }\n\n   internal::lockScheduler();\n   thread->basePriority = realPriority;\n   internal::updateThreadPriorityNoLock(thread);\n   internal::rescheduleAllCoreNoLock();\n   internal::unlockScheduler();\n   return TRUE;\n}\n\n\n/**\n * Set a thread's run quantum.\n *\n * This is the maximum amount of time the thread can run for before being forced\n * to yield.\n */\nBOOL\nOSSetThreadRunQuantum(virt_ptr<OSThread> thread,\n                      uint32_t quantumUS)\n{\n   if (quantumUS != OSThreadQuantum::Infinite) {\n      if (quantumUS < OSThreadQuantum::MinMicroseconds) {\n         return FALSE;\n      }\n\n      if (quantumUS > OSThreadQuantum::MaxMicroseconds) {\n         return FALSE;\n      }\n   }\n\n   auto ticks = internal::usToTicks(quantumUS);\n   auto result = FALSE;\n\n   internal::lockScheduler();\n   result = internal::setThreadRunQuantumNoLock(thread, ticks);\n   internal::unlockScheduler();\n   return result;\n}\n\n\n/**\n * Set a thread specific value.\n *\n * Can be read with OSGetThreadSpecific.\n */\nvoid\nOSSetThreadSpecific(uint32_t id,\n                    uint32_t value)\n{\n   OSGetCurrentThread()->specific[id] = value;\n}\n\n\n/**\n * Set thread stack usage tracking.\n */\nBOOL\nOSSetThreadStackUsage(virt_ptr<OSThread> thread)\n{\n   internal::lockScheduler();\n\n   if (!thread) {\n      thread = OSGetCurrentThread();\n   } else if (thread->state == OSThreadState::Running) {\n      internal::unlockScheduler();\n      return FALSE;\n   }\n\n   clearThreadStackWithValue(thread, 0xfefefefe);\n   thread->attr |= OSThreadAttributes::StackUsage;\n   internal::unlockScheduler();\n   return TRUE;\n}\n\n\n/**\n * Sleep the current thread and add it to a thread queue.\n *\n * Will sleep until the thread queue is woken with OSWakeupThread.\n */\nvoid\nOSSleepThread(virt_ptr<OSThreadQueue> queue)\n{\n   internal::lockScheduler();\n   internal::sleepThreadNoLock(queue);\n   internal::rescheduleSelfNoLock();\n   internal::unlockScheduler();\n}\n\nstatic void\nsleepAlarmHandler(virt_ptr<OSAlarm> alarm,\n                  virt_ptr<OSContext> context)\n{\n   // Wakeup the thread waiting on this alarm\n   auto data = virt_cast<OSThread *>(OSGetAlarmUserData(alarm));\n\n   // System Alarm, we already have the scheduler lock\n   internal::wakeupOneThreadNoLock(data);\n}\n\n/**\n * Sleep the current thread for a period of time.\n */\nvoid\nOSSleepTicks(OSTime ticks)\n{\n   // Create an alarm to trigger wakeup\n   auto alarm = StackObject<OSAlarm> { };\n   auto queue = StackObject<OSThreadQueue> { };\n\n   OSCreateAlarm(alarm);\n   OSInitThreadQueue(queue);\n\n   internal::lockScheduler();\n   internal::setAlarmInternal(alarm, ticks, sSleepAlarmHandler, OSGetCurrentThread());\n\n   internal::sleepThreadNoLock(queue);\n   internal::rescheduleSelfNoLock();\n\n   internal::unlockScheduler();\n}\n\n\n/**\n * Suspend a thread.\n *\n * Increases a thread's suspend counter, if the counter is >0 then the thread is\n * suspended.\n *\n * \\returns Returns the thread's previous suspend counter value\n */\nint32_t\nOSSuspendThread(virt_ptr<OSThread> thread)\n{\n   internal::lockScheduler();\n   int32_t result = -1;\n\n   if (thread->state == OSThreadState::Moribund || thread->state == OSThreadState::None) {\n      internal::unlockScheduler();\n      return -1;\n   }\n\n   if (thread->requestFlag == OSThreadRequest::Cancel) {\n      internal::unlockScheduler();\n      return -1;\n   }\n\n   auto curThread = OSGetCurrentThread();\n\n   if (curThread == thread) {\n      if (thread->cancelState == OSThreadCancelState::Enabled) {\n         thread->needSuspend++;\n         result = thread->suspendCounter;\n         internal::suspendThreadNoLock(thread);\n         internal::rescheduleAllCoreNoLock();\n      }\n   } else {\n      if (thread->suspendCounter != 0) {\n         result = thread->suspendCounter++;\n      } else {\n         thread->needSuspend++;\n         thread->requestFlag = OSThreadRequest::Suspend;\n         internal::sleepThreadNoLock(virt_addrof(thread->suspendQueue));\n         internal::rescheduleSelfNoLock();\n         result = thread->suspendResult;\n      }\n   }\n\n   internal::unlockScheduler();\n   return result;\n}\n\n\n/**\n * Check to see if the current thread should be cancelled or suspended.\n *\n * This is implicitly called in:\n * - OSLockMutex\n * - OSTryLockMutex\n * - OSUnlockMutex\n * - OSAcquireSpinLock\n * - OSTryAcquireSpinLock\n * - OSTryAcquireSpinLockWithTimeout\n * - OSReleaseSpinLock\n * - OSCancelThread\n */\nvoid\nOSTestThreadCancel()\n{\n   internal::lockScheduler();\n   internal::testThreadCancelNoLock();\n   internal::unlockScheduler();\n}\n\n\n/**\n * Wake up all threads in queue.\n *\n * Clears the thread queue.\n */\nvoid\nOSWakeupThread(virt_ptr<OSThreadQueue> queue)\n{\n   internal::lockScheduler();\n   internal::wakeupThreadNoLock(queue);\n   internal::rescheduleAllCoreNoLock();\n   internal::unlockScheduler();\n}\n\n\n/**\n * Yield execution to waiting threads with same priority.\n *\n * This will never switch to a thread with a lower priority than the current\n * thread.\n */\nvoid\nOSYieldThread()\n{\n   internal::lockScheduler();\n   internal::checkRunningThreadNoLock(true);\n   internal::unlockScheduler();\n}\n\nnamespace internal\n{\n\n/**\n * Set a user stack pointer for the current thread.\n */\nvoid\nsetUserStackPointer(virt_ptr<uint32_t> stack)\n{\n   auto thread = OSGetCurrentThread();\n\n   if (stack >= thread->stackEnd && stack < thread->stackStart) {\n      // Cannot modify stack to within current stack frame.\n      return;\n   }\n\n   auto current = OSGetStackPointer();\n\n   if (current < thread->stackEnd || current >= thread->stackStart) {\n      // If current stack is outside stack frame, then we must already have\n      // a user stack pointer, and we shouldn't overwrite it.\n      return;\n   }\n\n   thread->userStackPointer = stack;\n   OSTestThreadCancel();\n   thread->cancelState |= OSThreadCancelState::DisabledByUserStackPointer;\n}\n\n\n/**\n * Remove the user stack pointer for the current thread.\n */\nvoid\nremoveUserStackPointer(virt_ptr<uint32_t> stack)\n{\n   auto thread = OSGetCurrentThread();\n\n   if (stack < thread->stackEnd || stack >= thread->stackStart) {\n      // If restore stack pointer is outside stack frame, then it is not\n      // really restoring the original stack.\n      return;\n   }\n\n   thread->cancelState &= ~OSThreadCancelState::DisabledByUserStackPointer;\n   thread->userStackPointer = nullptr;\n   OSTestThreadCancel();\n}\n\n\n/**\n * Set the current thread to run only on the current core.\n *\n * \\return\n * Returns the old thread affinity, to be restored with unpinThreadAffinity.\n */\nuint32_t\npinThreadAffinity()\n{\n   auto core = OSGetCoreId();\n   auto thread = OSGetCurrentThread();\n   internal::lockScheduler();\n\n   auto oldAffinity = thread->attr & OSThreadAttributes::AffinityAny;\n   thread->attr &= ~OSThreadAttributes::AffinityAny;\n   thread->attr |= 1 << core;\n\n   internal::unlockScheduler();\n   return oldAffinity;\n}\n\n\n/**\n * Restores the thread affinity.\n */\nvoid\nunpinThreadAffinity(uint32_t affinity)\n{\n   auto thread = OSGetCurrentThread();\n   internal::lockScheduler();\n   thread->attr &= ~OSThreadAttributes::AffinityAny;\n   thread->attr |= affinity;\n   internal::unlockScheduler();\n}\n\n\nvoid\nexitThreadNoLock(int32_t value)\n{\n   auto thread = OSGetCurrentThread();\n\n   decaf_check(thread->state == OSThreadState::Running);\n   decaf_check(internal::isThreadActiveNoLock(thread));\n\n   // Clear the context associated with this thread\n\n   if (thread->attr & OSThreadAttributes::Detached) {\n      internal::markThreadInactiveNoLock(thread);\n      thread->state = OSThreadState::None;\n      // TODO: thread->id = 0x8000;\n\n      if (thread->deallocator) {\n         queueThreadDeallocation(thread);\n      }\n   } else {\n      thread->exitValue = value;\n      thread->state = OSThreadState::Moribund;\n   }\n\n   internal::disableScheduler();\n   internal::unlockAllMutexNoLock(thread);\n   internal::unlockAllFastMutexNoLock(thread);\n   internal::wakeupThreadNoLock(virt_addrof(thread->joinQueue));\n   internal::wakeupThreadWaitForSuspensionNoLock(virt_addrof(thread->suspendQueue), -1);\n   internal::rescheduleAllCoreNoLock();\n   internal::enableScheduler();\n\n   cafe::kernel::exitThreadNoLock();\n   internal::rescheduleSelfNoLock();\n\n   // We do not need to unlockScheduler as OSExitThread never returns.\n   decaf_abort(\"Exited thread was rescheduled...\");\n}\n\nvoid\nqueueThreadDeallocation(virt_ptr<OSThread> thread)\n{\n   auto &perCoreData = sThreadData->perCoreData[cpu::this_core::id()];\n   ThreadQueue::insert(virt_addrof(perCoreData.deallocationQueue), thread);\n   wakeupThreadNoLock(virt_addrof(perCoreData.deallocationThreadQueue));\n}\n\nstatic uint32_t\ndeallocatorThreadEntry(uint32_t coreId,\n                       virt_ptr<void>)\n{\n   auto &perCoreData = sThreadData->perCoreData[cpu::this_core::id()];\n   auto waitQueue = virt_addrof(perCoreData.deallocationThreadQueue);\n   auto queue = virt_addrof(perCoreData.deallocationQueue);\n\n   auto oldInterrupts = OSDisableInterrupts();\n\n   while (true) {\n      auto thread = ThreadQueue::popFront(queue);\n\n      if (!thread) {\n         lockScheduler();\n         sleepThreadNoLock(waitQueue);\n         rescheduleSelfNoLock();\n         unlockScheduler();\n         continue;\n      }\n\n      if (thread->deallocator) {\n         OSRestoreInterrupts(oldInterrupts);\n\n         cafe::invoke(cpu::this_core::state(),\n                      thread->deallocator,\n                      thread,\n                      virt_cast<void *>(thread->stackEnd));\n         oldInterrupts = OSDisableInterrupts();\n      }\n   }\n\n   OSRestoreInterrupts(oldInterrupts);\n}\n\nstatic void\ninitialiseDeallocatorThread()\n{\n   auto coreId = OSGetCoreId();\n   auto &perCoreData = sThreadData->perCoreData[coreId];\n   OSInitThreadQueue(virt_addrof(perCoreData.deallocationThreadQueue));\n   OSInitThreadQueue(virt_addrof(perCoreData.deallocationQueue));\n\n   auto thread = virt_addrof(perCoreData.deallocatorThread);\n   auto stack = virt_addrof(perCoreData.deallocatorThreadStack);\n   auto stackSize = perCoreData.deallocatorThreadStack.size();\n   perCoreData.deallocatorThreadName = fmt::format(\"{{SYS Thread Terminator Core {}}}\", coreId);\n\n   coreinit__OSCreateThreadType(thread, sDeallocatorThreadEntryPoint, coreId, nullptr,\n                                virt_cast<uint32_t *>(stack + stackSize),\n                                stackSize,\n                                1,\n                                static_cast<OSThreadAttributes>(1 << coreId),\n                                OSThreadType::AppIo);\n   OSSetThreadName(thread, virt_addrof(perCoreData.deallocatorThreadName));\n   OSResumeThread(thread);\n}\n\nstatic uint32_t\ndefaultThreadEntry(uint32_t coreId,\n                   virt_ptr<void> /* unused */)\n{\n   auto thread = OSGetDefaultThread(coreId);\n\n   lockScheduler();\n   setCoreRunningThread(coreId, thread);\n   OSSetCurrentContext(virt_addrof(thread->context));\n   OSSetCurrentFPUContext(0);\n   unlockScheduler();\n\n   initialiseIci();\n   initialiseExceptionHandlers();\n   initialiseAlarmThread();\n   initialiseLockedCache(coreId);\n   IPCDriverInit();\n   IPCDriverOpen();\n\n   internal::COSWarn(COSReportModule::Unknown1,\n      fmt::format(\"  Core {} Complete, MSR 0x{:08X}, Default Thread {}\",\n                  coreId, 0, thread));\n\n   OSWaitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous),\n                    sThreadData->defaultThreadInitRendezvousWaitMask);\n   initialiseDeallocatorThread();\n   OSExitThread(0);\n}\n\nstatic void\ninitialiseThreadForCore(uint32_t coreId)\n{\n   auto currentCoreId = OSGetCoreId();\n   auto thread = virt_addrof(sThreadData->perCoreData[coreId].defaultThread);\n\n   sThreadData->perCoreData[coreId].defaultThreadName = fmt::format(\"Default Thread {}\", coreId);\n   thread->name = virt_addrof(sThreadData->perCoreData[coreId].defaultThreadName);\n   thread->tag = OSThread::Tag;\n   thread->exitValue = -1;\n   thread->type = OSThreadType::App;\n   thread->attr = OSThreadAttributes::Detached;\n   thread->state = OSThreadState::Running;\n   thread->priority = 80;\n   thread->basePriority = 80;\n   thread->id = sThreadId++;\n\n   OSInitThreadQueueEx(virt_addrof(thread->joinQueue), thread);\n   OSInitThreadQueueEx(virt_addrof(thread->suspendQueue), thread);\n   thread->mutexQueue.parent = thread;\n\n   thread->stackStart = virt_cast<uint32_t *>(internal::getDefaultThreadStackBase(coreId));\n   thread->stackEnd = virt_cast<uint32_t *>(internal::getDefaultThreadStackEnd(coreId));\n\n   if (currentCoreId == coreId) {\n      // Save and restore our host context because OSInitContext nulls it\n      auto hostContext = thread->context.hostContext;\n      OSInitContext(virt_addrof(thread->context),\n                    virt_addr { 0 },\n                    virt_cast<virt_addr>(thread->stackStart));\n      thread->context.hostContext = hostContext;\n   } else {\n      OSInitContext(virt_addrof(thread->context),\n                    virt_func_cast<virt_addr>(sDefaultThreadEntryPoint),\n                    virt_cast<virt_addr>(thread->stackStart));\n   }\n\n   thread->context.pir = coreId;\n   thread->context.starttime = OSGetSystemTime();\n   thread->context.gqr[2] = 0x40004u;\n   thread->context.gqr[3] = 0x50005u;\n   thread->context.gqr[4] = 0x60006u;\n   thread->context.gqr[5] = 0x70007u;\n\n   if (coreId == 0) {\n      thread->attr |= OSThreadAttributes::AffinityCPU0;\n      thread->context.attr |= OSThreadAttributes::AffinityCPU0;\n   } else if (coreId == 1) {\n      thread->attr |= OSThreadAttributes::AffinityCPU1;\n      thread->context.attr |= OSThreadAttributes::AffinityCPU1;\n   } else if (coreId == 2) {\n      thread->attr |= OSThreadAttributes::AffinityCPU2;\n      thread->context.attr |= OSThreadAttributes::AffinityCPU2;\n   }\n\n   clearThreadStackWithValue(thread, 0);\n   *thread->stackEnd = 0xDEADBABE;\n\n   if (currentCoreId == coreId) {\n      setCoreRunningThread(coreId, thread);\n   }\n\n   markThreadActiveNoLock(thread);\n}\n\nvoid\ninitialiseThreads()\n{\n   auto mainCoreId = OSGetMainCoreId();\n   auto mainThread = OSGetDefaultThread(mainCoreId);\n   internal::lockScheduler();\n\n   for (auto i = 0u; i < sThreadData->perCoreData.size(); ++i) {\n      auto &perCoreData = sThreadData->perCoreData[i];\n      OSCreateAlarm(virt_addrof(perCoreData.timeSliceAlarm));\n      initialiseThreadForCore(i);\n   }\n\n   // Hijack the kernel context fiber into our own.\n   kernel::hijackCurrentHostContext(virt_addrof(mainThread->context));\n   OSSetCurrentContext(virt_addrof(mainThread->context));\n   OSSetCurrentFPUContext(0);\n\n   internal::unlockScheduler();\n\n   internal::COSWarn(COSReportModule::Unknown1,\n      fmt::format(\"UserMode Core & Thread Initialization ({} Cores)\",\n                  OSGetCoreCount()));\n\n   internal::COSWarn(COSReportModule::Unknown1,\n      fmt::format(\"  Core {} Complete, Default Thread {}\",\n                  mainCoreId, mainThread));\n   sThreadData->defaultThreadInitRendezvousWaitMask = 1u << 1;\n\n   // Run default thread initilisation on Core 0\n   if (auto thread = virt_addrof(sThreadData->perCoreData[0].defaultThread)) {\n      OSInitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous));\n\n      thread->context.gpr[3] = 0u;\n      kernel::setSubCoreEntryContext(0, virt_addrof(thread->context));\n\n      OSWaitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous),\n                       1 << 0);\n   }\n\n   // Run default thread initilisation on Core 2\n   if (auto thread = virt_addrof(sThreadData->perCoreData[2].defaultThread)) {\n      OSInitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous));\n\n      thread->context.gpr[3] = 2u;\n      kernel::setSubCoreEntryContext(2, virt_addrof(thread->context));\n\n      OSWaitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous),\n                       1 << 2);\n   }\n\n   OSWaitRendezvous(virt_addrof(sThreadData->defaultThreadInitRendezvous),\n                    1 << 2);\n\n   initialiseDeallocatorThread();\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerThreadSymbols()\n{\n   RegisterFunctionExport(OSCancelThread);\n   RegisterFunctionExport(OSCheckActiveThreads);\n   RegisterFunctionExport(OSCheckThreadStackUsage);\n   RegisterFunctionExport(OSClearStack);\n   RegisterFunctionExport(OSClearThreadStackUsage);\n   RegisterFunctionExport(OSContinueThread);\n   RegisterFunctionExport(OSCreateThread);\n   RegisterFunctionExport(OSCreateThreadType);\n   RegisterFunctionExportName(\"__OSCreateThreadType\", coreinit__OSCreateThreadType);\n   RegisterFunctionExport(OSDetachThread);\n   RegisterFunctionExport(OSExitThread);\n   RegisterFunctionExport(OSGetActiveThreadLink);\n   RegisterFunctionExport(OSGetCurrentThread);\n   RegisterFunctionExport(OSGetDefaultThread);\n   RegisterFunctionExport(OSGetStackPointer);\n   RegisterFunctionExport(OSGetUserStackPointer);\n   RegisterFunctionExport(OSGetThreadAffinity);\n   RegisterFunctionExport(OSGetThreadName);\n   RegisterFunctionExport(OSGetThreadPriority);\n   RegisterFunctionExport(OSGetThreadSpecific);\n   RegisterFunctionExport(OSInitThreadQueue);\n   RegisterFunctionExport(OSInitThreadQueueEx);\n   RegisterFunctionExport(OSIsThreadSuspended);\n   RegisterFunctionExport(OSIsThreadTerminated);\n   RegisterFunctionExport(OSJoinThread);\n   RegisterFunctionExport(OSPrintCurrentThreadState);\n   RegisterFunctionExport(OSResumeThread);\n   RegisterFunctionExport(OSRunThread);\n   RegisterFunctionExport(OSSetThreadAffinity);\n   RegisterFunctionExport(OSSetThreadCancelState);\n   RegisterFunctionExport(OSSetThreadCleanupCallback);\n   RegisterFunctionExport(OSSetThreadDeallocator);\n   RegisterFunctionExport(OSSetThreadName);\n   RegisterFunctionExport(OSSetThreadPriority);\n   RegisterFunctionExport(OSSetThreadRunQuantum);\n   RegisterFunctionExport(OSSetThreadSpecific);\n   RegisterFunctionExport(OSSetThreadStackUsage);\n   RegisterFunctionExport(OSSleepThread);\n   RegisterFunctionExport(OSSleepTicks);\n   RegisterFunctionExport(OSSuspendThread);\n   RegisterFunctionExport(OSTestThreadCancel);\n   RegisterFunctionExport(OSWakeupThread);\n   RegisterFunctionExport(OSYieldThread);\n\n   RegisterDataInternal(sThreadData);\n   RegisterFunctionInternal(threadEntry, sThreadEntryPoint);\n   RegisterFunctionInternal(sleepAlarmHandler, sSleepAlarmHandler);\n   RegisterFunctionInternal(internal::deallocatorThreadEntry, sDeallocatorThreadEntryPoint);\n   RegisterFunctionInternal(internal::defaultThreadEntry, sDefaultThreadEntryPoint);\n}\n\n} // namespace Internalcafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_thread.h",
    "content": "#pragma once\n#include \"coreinit_context.h\"\n#include \"coreinit_core.h\"\n#include \"coreinit_enum.h\"\n#include \"coreinit_exception.h\"\n#include \"coreinit_internal_queue.h\"\n#include \"coreinit_time.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_thread Thread\n * \\ingroup coreinit\n *\n * The thread scheduler in the Wii U uses co-operative scheduling, this is different\n * to the usual pre-emptive scheduling that most operating systems use (such as\n * Windows, Linux, etc). In co-operative scheduling threads must voluntarily yield\n * execution to other threads. In pre-emptive threads are switched by the operating\n * system after an amount of time.\n *\n * With the Wii U's scheduling model the thread with the highest priority which\n * is in a non-waiting state will always be running (where 0 is the highest\n * priority and 31 is the lowest). Execution will only switch to other threads\n * once this thread has been forced to wait, such as when waiting to acquire a\n * mutex, or when the thread voluntarily yields execution to other threads which\n * have the same priority using OSYieldThread. OSYieldThread will never yield to\n * a thread with lower priority than the current thread.\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSAlarm;\nstruct OSThread;\n\nusing OSThreadEntryPointFn = virt_func_ptr<uint32_t(uint32_t, virt_ptr<void>)>;\nusing OSThreadCleanupCallbackFn = virt_func_ptr<void(virt_ptr<OSThread>, virt_ptr<void>)>;\nusing OSThreadDeallocatorFn = virt_func_ptr<void(virt_ptr<OSThread>, virt_ptr<void>)>;\n\nusing OSContext = cafe::kernel::Context;\nCHECK_SIZE(OSContext, 0x320);\n\nstruct OSMutex;\n\nstruct OSMutexQueue\n{\n   be2_virt_ptr<OSMutex> head;\n   be2_virt_ptr<OSMutex> tail;\n   be2_virt_ptr<void> parent;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(OSMutexQueue, 0x0, head);\nCHECK_OFFSET(OSMutexQueue, 0x4, tail);\nCHECK_OFFSET(OSMutexQueue, 0x8, parent);\nCHECK_SIZE(OSMutexQueue, 0x10);\n\nstruct OSFastMutex;\n\nstruct OSFastMutexQueue\n{\n   be2_virt_ptr<OSFastMutex> head;\n   be2_virt_ptr<OSFastMutex> tail;\n};\nCHECK_OFFSET(OSFastMutexQueue, 0x00, head);\nCHECK_OFFSET(OSFastMutexQueue, 0x04, tail);\nCHECK_SIZE(OSFastMutexQueue, 0x08);\n\nstruct OSThreadLink\n{\n   be2_virt_ptr<OSThread> next;\n   be2_virt_ptr<OSThread> prev;\n};\nCHECK_OFFSET(OSThreadLink, 0x00, next);\nCHECK_OFFSET(OSThreadLink, 0x04, prev);\nCHECK_SIZE(OSThreadLink, 0x8);\n\nstruct OSThreadQueue\n{\n   be2_virt_ptr<OSThread> head;\n   be2_virt_ptr<OSThread> tail;\n   be2_virt_ptr<void> parent;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(OSThreadQueue, 0x00, head);\nCHECK_OFFSET(OSThreadQueue, 0x04, tail);\nCHECK_OFFSET(OSThreadQueue, 0x08, parent);\nCHECK_SIZE(OSThreadQueue, 0x10);\n\nstruct OSThreadSimpleQueue\n{\n   be2_virt_ptr<OSThread> head;\n   be2_virt_ptr<OSThread> tail;\n};\nCHECK_OFFSET(OSThreadSimpleQueue, 0x00, head);\nCHECK_OFFSET(OSThreadSimpleQueue, 0x04, tail);\nCHECK_SIZE(OSThreadSimpleQueue, 0x08);\n\nstruct OSTLSSection\n{\n   be2_virt_ptr<void> data;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(OSTLSSection, 0x00, data);\nCHECK_SIZE(OSTLSSection, 0x08);\n\nstruct OSThread\n{\n   static constexpr uint32_t Tag = 0x74487244;\n\n   //! Kernel thread context\n   be2_struct<OSContext> context;\n\n   //! Should always be set to the value OSThread::Tag.\n   be2_val<uint32_t> tag;\n\n   //! Bitfield of OScpu::Core\n   be2_val<OSThreadState> state;\n\n   //! Bitfield of OSThreadAttributes\n   be2_val<OSThreadAttributes> attr;\n\n   //! Unique thread ID\n   be2_val<uint16_t> id;\n\n   //! Suspend count (increased by OSSuspendThread).\n   be2_val<int32_t> suspendCounter;\n\n   //! Actual priority of thread.\n   be2_val<int32_t> priority;\n\n   //! Base priority of thread, 0 is highest priority, 31 is lowest priority.\n   be2_val<int32_t> basePriority;\n\n   //! Exit value of the thread\n   be2_val<int32_t> exitValue;\n\n   //! Core run queue stuff\n   be2_virt_ptr<OSThreadQueue> coreRunQueue0;\n   be2_virt_ptr<OSThreadQueue> coreRunQueue1;\n   be2_virt_ptr<OSThreadQueue> coreRunQueue2;\n   be2_struct<OSThreadLink> coreRunQueueLink0;\n   be2_struct<OSThreadLink> coreRunQueueLink1;\n   be2_struct<OSThreadLink> coreRunQueueLink2;\n\n   //! Queue the thread is currently waiting on\n   be2_virt_ptr<OSThreadQueue> queue;\n\n   //! Link used for thread queue\n   be2_struct<OSThreadLink> link;\n\n   //! Queue of threads waiting to join this thread\n   be2_struct<OSThreadQueue> joinQueue;\n\n   //! Mutex this thread is waiting to lock\n   be2_virt_ptr<OSMutex> mutex;\n\n   //! Queue of mutexes this thread owns\n   be2_struct<OSMutexQueue> mutexQueue;\n\n   //! Link for global active thread queue\n   be2_struct<OSThreadLink> activeLink;\n\n   //! Stack start (top, highest address)\n   be2_virt_ptr<uint32_t> stackStart;\n\n   //! Stack end (bottom, lowest address)\n   be2_virt_ptr<uint32_t> stackEnd;\n\n   //! Thread entry point set in OSCreateThread\n   be2_val<OSThreadEntryPointFn> entryPoint;\n\n   UNKNOWN(0x408 - 0x3a0);\n\n   //! GHS Exception handling thread-specifics\n   be2_virt_ptr<void> eh_globals;\n   be2_array<virt_ptr<void>, 9> eh_mem_manage;\n   be2_array<virt_ptr<void>, 6> eh_store_globals;\n   be2_array<virt_ptr<void>, 76> eh_store_globals_tdeh;\n\n   be2_val<BOOL> alarmCancelled;\n\n   //! Thread specific values, accessed with OSSetThreadSpecific and OSGetThreadSpecific.\n   be2_array<uint32_t, 16> specific;\n\n   //! Thread type\n   be2_val<OSThreadType> type;\n\n   //! Thread name, accessed with OSSetThreadName and OSGetThreadName.\n   be2_virt_ptr<const char> name;\n\n   //! Alarm the thread is waiting on in OSWaitEventWithTimeout\n   be2_virt_ptr<OSAlarm> waitEventTimeoutAlarm;\n\n   //! The stack pointer passed in OSCreateThread.\n   be2_virt_ptr<uint32_t> userStackPointer;\n\n   //! Called just before thread is terminated, set with OSSetThreadCleanupCallback\n   be2_val<OSThreadCleanupCallbackFn> cleanupCallback;\n\n   //! Called just after a thread is terminated, set with OSSetThreadDeallocator\n   be2_val<OSThreadDeallocatorFn> deallocator;\n\n   //! Current thread cancel state, controls whether the thread is allowed to cancel or not\n   be2_val<OSThreadCancelState> cancelState;\n\n   //! Current thread request, used for cancelleing and suspending the thread.\n   be2_val<OSThreadRequest> requestFlag;\n\n   //! Pending suspend request count\n   be2_val<int32_t> needSuspend;\n\n   //! Result of thread suspend\n   be2_val<int32_t> suspendResult;\n\n   //! Queue of threads waiting for a thread to be suspended.\n   be2_struct<OSThreadQueue> suspendQueue;\n\n   UNKNOWN(0x4);\n\n   //! How many ticks the thread should run for before suspension.\n   be2_val<int64_t> runQuantumTicks;\n\n   //! The total amount of core time consumed by this thread (Does not include time while Running)\n   be2_val<uint64_t> coreTimeConsumedNs;\n\n   //! The number of times this thread has been awoken.\n   be2_val<uint64_t> wakeCount;\n\n   be2_val<OSTime> unk0x610;\n   be2_val<OSTime> unk0x618;\n   be2_val<OSTime> unk0x620;\n   be2_val<OSTime> unk0x628;\n\n   //! Callback for DSI exception\n   be2_array<OSExceptionCallbackFn, OSGetCoreCount()> dsiCallback;\n\n   //! Callback for ISI exception\n   be2_array<OSExceptionCallbackFn, OSGetCoreCount()> isiCallback;\n\n   //! Callback for Program exception\n   be2_array<OSExceptionCallbackFn, OSGetCoreCount()> programCallback;\n\n   //! Callback for PerfMon exception\n   be2_array<OSExceptionCallbackFn, OSGetCoreCount()> perfMonCallback;\n\n   UNKNOWN(0x4);\n\n   //! Number of TLS sections\n   be2_val<uint16_t> tlsSectionCount;\n\n   UNKNOWN(0x2);\n\n   //! TLS Sections\n   be2_virt_ptr<OSTLSSection> tlsSections;\n\n   //! The fast mutex we are currently waiting for\n   be2_virt_ptr<OSFastMutex> fastMutex;\n\n   //! The fast mutexes we are currently contended on\n   be2_struct<OSFastMutexQueue> contendedFastMutexes;\n\n   //! The fast mutexes we currently own locks on\n   be2_struct<OSFastMutexQueue> fastMutexQueue;\n\n   //! Callback for Alignment exception\n   be2_array<OSExceptionCallbackFn, OSGetCoreCount()> alignCallback;\n\n   UNKNOWN(0x6A0 - 0x68C);\n};\nCHECK_OFFSET(OSThread, 0x320, tag);\nCHECK_OFFSET(OSThread, 0x324, state);\nCHECK_OFFSET(OSThread, 0x325, attr);\nCHECK_OFFSET(OSThread, 0x326, id);\nCHECK_OFFSET(OSThread, 0x328, suspendCounter);\nCHECK_OFFSET(OSThread, 0x32c, priority);\nCHECK_OFFSET(OSThread, 0x330, basePriority);\nCHECK_OFFSET(OSThread, 0x334, exitValue);\nCHECK_OFFSET(OSThread, 0x338, coreRunQueue0);\nCHECK_OFFSET(OSThread, 0x33C, coreRunQueue1);\nCHECK_OFFSET(OSThread, 0x340, coreRunQueue2);\nCHECK_OFFSET(OSThread, 0x344, coreRunQueueLink0);\nCHECK_OFFSET(OSThread, 0x34C, coreRunQueueLink1);\nCHECK_OFFSET(OSThread, 0x354, coreRunQueueLink2);\nCHECK_OFFSET(OSThread, 0x35C, queue);\nCHECK_OFFSET(OSThread, 0x360, link);\nCHECK_OFFSET(OSThread, 0x368, joinQueue);\nCHECK_OFFSET(OSThread, 0x378, mutex);\nCHECK_OFFSET(OSThread, 0x37C, mutexQueue);\nCHECK_OFFSET(OSThread, 0x38C, activeLink);\nCHECK_OFFSET(OSThread, 0x394, stackStart);\nCHECK_OFFSET(OSThread, 0x398, stackEnd);\nCHECK_OFFSET(OSThread, 0x39C, entryPoint);\nCHECK_OFFSET(OSThread, 0x408, eh_globals);\nCHECK_OFFSET(OSThread, 0x40C, eh_mem_manage);\nCHECK_OFFSET(OSThread, 0x430, eh_store_globals);\nCHECK_OFFSET(OSThread, 0x448, eh_store_globals_tdeh);\nCHECK_OFFSET(OSThread, 0x578, alarmCancelled);\nCHECK_OFFSET(OSThread, 0x57C, specific);\nCHECK_OFFSET(OSThread, 0x5BC, type);\nCHECK_OFFSET(OSThread, 0x5C0, name);\nCHECK_OFFSET(OSThread, 0x5C4, waitEventTimeoutAlarm);\nCHECK_OFFSET(OSThread, 0x5C8, userStackPointer);\nCHECK_OFFSET(OSThread, 0x5CC, cleanupCallback);\nCHECK_OFFSET(OSThread, 0x5D0, deallocator);\nCHECK_OFFSET(OSThread, 0x5D4, cancelState);\nCHECK_OFFSET(OSThread, 0x5D8, requestFlag);\nCHECK_OFFSET(OSThread, 0x5DC, needSuspend);\nCHECK_OFFSET(OSThread, 0x5E0, suspendResult);\nCHECK_OFFSET(OSThread, 0x5E4, suspendQueue);\nCHECK_OFFSET(OSThread, 0x5F8, runQuantumTicks);\nCHECK_OFFSET(OSThread, 0x600, coreTimeConsumedNs);\nCHECK_OFFSET(OSThread, 0x608, wakeCount);\nCHECK_OFFSET(OSThread, 0x610, unk0x610);\nCHECK_OFFSET(OSThread, 0x618, unk0x618);\nCHECK_OFFSET(OSThread, 0x620, unk0x620);\nCHECK_OFFSET(OSThread, 0x628, unk0x628);\nCHECK_OFFSET(OSThread, 0x630, dsiCallback);\nCHECK_OFFSET(OSThread, 0x63C, isiCallback);\nCHECK_OFFSET(OSThread, 0x648, programCallback);\nCHECK_OFFSET(OSThread, 0x654, perfMonCallback);\nCHECK_OFFSET(OSThread, 0x664, tlsSectionCount);\nCHECK_OFFSET(OSThread, 0x668, tlsSections);\nCHECK_OFFSET(OSThread, 0x66C, fastMutex);\nCHECK_OFFSET(OSThread, 0x670, contendedFastMutexes);\nCHECK_OFFSET(OSThread, 0x678, fastMutexQueue);\nCHECK_OFFSET(OSThread, 0x680, alignCallback);\nCHECK_SIZE(OSThread, 0x6A0);\n\n#pragma pack(pop)\n\nvoid\nOSCancelThread(virt_ptr<OSThread> thread);\n\nint32_t\nOSCheckActiveThreads();\n\nint32_t\nOSCheckThreadStackUsage(virt_ptr<OSThread> thread);\n\nvoid\nOSClearStack(uint32_t value);\n\nvoid\nOSClearThreadStackUsage(virt_ptr<OSThread> thread);\n\nvoid\nOSContinueThread(virt_ptr<OSThread> thread);\n\nBOOL\nOSCreateThread(virt_ptr<OSThread> thread,\n               OSThreadEntryPointFn entry,\n               uint32_t argc,\n               virt_ptr<void> argv,\n               virt_ptr<uint32_t> stackTop,\n               uint32_t stackSize,\n               int32_t priority,\n               OSThreadAttributes attributes);\n\nBOOL\nOSCreateThreadType(virt_ptr<OSThread> thread,\n                   OSThreadEntryPointFn entry,\n                   uint32_t argc,\n                   virt_ptr<void> argv,\n                   virt_ptr<uint32_t> stackTop,\n                   uint32_t stackSize,\n                   int32_t priority,\n                   OSThreadAttributes attributes,\n                   OSThreadType type);\n\nBOOL\ncoreinit__OSCreateThreadType(virt_ptr<OSThread> thread,\n                             OSThreadEntryPointFn entry,\n                             uint32_t argc,\n                             virt_ptr<void> argv,\n                             virt_ptr<uint32_t> stackTop,\n                             uint32_t stackSize,\n                             int32_t priority,\n                             OSThreadAttributes attributes,\n                             OSThreadType type);\n\nvoid\nOSDetachThread(virt_ptr<OSThread> thread);\n\n[[noreturn]] void\nOSExitThread(int value);\n\nvoid\nOSGetActiveThreadLink(virt_ptr<OSThread> thread,\n                      virt_ptr<OSThreadLink> link);\n\nvirt_ptr<OSThread>\nOSGetCurrentThread();\n\nvirt_ptr<OSThread>\nOSGetDefaultThread(uint32_t coreID);\n\nvirt_ptr<uint32_t>\nOSGetStackPointer();\n\nvirt_ptr<uint32_t>\nOSGetUserStackPointer(virt_ptr<OSThread> thread);\n\nuint32_t\nOSGetThreadAffinity(virt_ptr<OSThread> thread);\n\nvirt_ptr<const char>\nOSGetThreadName(virt_ptr<OSThread> thread);\n\nint32_t\nOSGetThreadPriority(virt_ptr<OSThread> thread);\n\nuint32_t\nOSGetThreadSpecific(uint32_t id);\n\nvoid\nOSInitThreadQueue(virt_ptr<OSThreadQueue> queue);\n\nvoid\nOSInitThreadQueueEx(virt_ptr<OSThreadQueue> queue,\n                    virt_ptr<void> parent);\n\nBOOL\nOSIsThreadSuspended(virt_ptr<OSThread> thread);\n\nBOOL\nOSIsThreadTerminated(virt_ptr<OSThread> thread);\n\nBOOL\nOSJoinThread(virt_ptr<OSThread> thread,\n             virt_ptr<int32_t> exitValue);\n\nvoid\nOSPrintCurrentThreadState();\n\nint32_t\nOSResumeThread(virt_ptr<OSThread> thread);\n\nBOOL\nOSRunThread(virt_ptr<OSThread> thread,\n            OSThreadEntryPointFn entry,\n            uint32_t argc,\n            virt_ptr<void> argv);\n\nBOOL\nOSSetThreadAffinity(virt_ptr<OSThread> thread,\n                    uint32_t affinity);\n\nBOOL\nOSSetThreadCancelState(BOOL state);\n\nOSThreadCleanupCallbackFn\nOSSetThreadCleanupCallback(virt_ptr<OSThread> thread,\n                           OSThreadCleanupCallbackFn callback);\nOSThreadDeallocatorFn\nOSSetThreadDeallocator(virt_ptr<OSThread> thread,\n                       OSThreadDeallocatorFn deallocator);\n\nvoid\nOSSetThreadName(virt_ptr<OSThread> thread,\n                virt_ptr<const char> name);\n\nBOOL\nOSSetThreadPriority(virt_ptr<OSThread> thread,\n                    int32_t priority);\n\nBOOL\nOSSetThreadRunQuantum(virt_ptr<OSThread> thread,\n                      uint32_t quantumUS);\n\nvoid\nOSSetThreadSpecific(uint32_t id,\n                    uint32_t value);\n\nBOOL\nOSSetThreadStackUsage(virt_ptr<OSThread> thread);\n\nvoid\nOSSleepThread(virt_ptr<OSThreadQueue> queue);\n\nvoid\nOSSleepTicks(OSTime ticks);\n\nint32_t\nOSSuspendThread(virt_ptr<OSThread> thread);\n\nvoid\nOSTestThreadCancel();\n\nvoid\nOSWakeupThread(virt_ptr<OSThreadQueue> queue);\n\nvoid\nOSYieldThread();\n\n/** @} */\n\nnamespace internal\n{\n\nvoid\ninitialiseThreads();\n\nvoid\nsetUserStackPointer(virt_ptr<uint32_t> stack);\n\nvoid\nremoveUserStackPointer(virt_ptr<uint32_t> stack);\n\nuint32_t\npinThreadAffinity();\n\nvoid\nunpinThreadAffinity(uint32_t affinity);\n\nvoid\nqueueThreadDeallocation(virt_ptr<OSThread> thread);\n\nvoid\nexitThreadNoLock(int32_t value);\n\nstruct ThreadIsLess\n{\n   bool operator()(virt_ptr<OSThread> lhs,\n                   virt_ptr<OSThread> rhs) const\n   {\n      return lhs->priority <= rhs->priority;\n   }\n\n};\n\nusing ThreadQueue = SortedQueue<OSThreadQueue, OSThreadLink, OSThread, &OSThread::link, ThreadIsLess>;\n\n} // namespace internal\n\n} // namespace coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_time.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_time.h\"\n#include \"coreinit_systeminfo.h\"\n\n#include \"decaf_config.h\"\n#include \"decaf_configstorage.h\"\n\n#include <common/platform_time.h>\n#include <libcpu/state.h>\n#include <thread>\n\nnamespace cafe::coreinit\n{\n\nstruct TimeData\n{\n   std::chrono::time_point<std::chrono::system_clock> epochTime;\n   std::chrono::time_point<std::chrono::system_clock> baseClock;\n   cpu::TimerDuration baseTicks;\n};\n\nstatic virt_ptr<TimeData> sTimeData = nullptr;\nstatic std::atomic<bool> sTimeScaleEnabled = false;\nstatic std::atomic<double> sTimeScale = 1.0;\n\nstatic uint64_t\nscaledTimebase()\n{\n   auto timeBase = cpu::this_core::state()->tb();\n   if (!sTimeScaleEnabled.load(std::memory_order_relaxed)) {\n      return timeBase;\n   }\n\n   auto timeScale = sTimeScale.load(std::memory_order_relaxed);\n   return static_cast<uint64_t>(static_cast<double>(timeBase) * timeScale);\n}\n\n/**\n * Time since epoch\n */\nOSTime\nOSGetTime()\n{\n   return OSGetSystemTime() + sTimeData->baseTicks.count();\n}\n\n\n/**\n * Time since system start up\n */\nOSTime\nOSGetSystemTime()\n{\n   return scaledTimebase();\n}\n\n\n/**\n * Ticks since epoch\n */\nOSTick\nOSGetTick()\n{\n   return OSGetSystemTick() + static_cast<OSTick>(sTimeData->baseTicks.count());\n}\n\n\n/**\n * Ticks since system start up\n */\nOSTick\nOSGetSystemTick()\n{\n   return static_cast<uint32_t>(scaledTimebase());\n}\n\n\n/**\n * Convert OSTime to OSCalendarTime\n */\nvoid\nOSTicksToCalendarTime(OSTime time,\n                      virt_ptr<OSCalendarTime> calendarTime)\n{\n   auto chrono = coreinit::internal::toTimepoint(time);\n   std::time_t system_time_t = std::chrono::system_clock::to_time_t(chrono);\n   std::tm tm = platform::localtime(system_time_t);\n\n   calendarTime->tm_sec = tm.tm_sec;\n   calendarTime->tm_min = tm.tm_min;\n   calendarTime->tm_hour = tm.tm_hour;\n   calendarTime->tm_mday = tm.tm_mday;\n   calendarTime->tm_mon = tm.tm_mon;\n   calendarTime->tm_year = tm.tm_year + 1900; // posix tm_year is year - 1900\n   calendarTime->tm_wday = tm.tm_wday;\n   calendarTime->tm_yday = tm.tm_yday;\n\n   auto timeOffset =\n      std::chrono::duration_cast<std::chrono::microseconds>(chrono.time_since_epoch()) -\n      std::chrono::duration_cast<std::chrono::seconds>(chrono.time_since_epoch());\n   auto msOffset = std::chrono::duration_cast<std::chrono::milliseconds>(timeOffset);\n   auto uOffset = std::chrono::duration_cast<std::chrono::microseconds>(timeOffset - msOffset);\n   calendarTime->tm_msec = static_cast<int32_t>(msOffset.count());\n   calendarTime->tm_usec = static_cast<int32_t>(uOffset.count());\n}\n\n\n/**\n * Convert OSCalendarTime to OSTime\n */\nOSTime\nOSCalendarTimeToTicks(virt_ptr<OSCalendarTime> calendarTime)\n{\n   std::tm tm = { 0 };\n   tm.tm_sec = calendarTime->tm_sec;\n   tm.tm_min = calendarTime->tm_min;\n   tm.tm_hour = calendarTime->tm_hour;\n   tm.tm_mday = calendarTime->tm_mday;\n   tm.tm_mon = calendarTime->tm_mon;\n   tm.tm_year = calendarTime->tm_year - 1900;\n   tm.tm_wday = calendarTime->tm_wday;\n   tm.tm_yday = calendarTime->tm_yday;\n   tm.tm_isdst = -1;\n\n   auto system_time = platform::make_gm_time(tm);\n   auto chrono = std::chrono::system_clock::from_time_t(system_time);\n\n   // Add on tm_usec, tm_msec which is missing from std::tm\n   chrono += std::chrono::microseconds { calendarTime->tm_usec };\n   chrono += std::chrono::milliseconds { calendarTime->tm_msec };\n\n   return coreinit::internal::toOSTime(chrono);\n}\n\nnamespace internal\n{\n\nOSTime\nmsToTicks(OSTimeMilliseconds milliseconds)\n{\n   auto timerSpeed = static_cast<uint64_t>(OSGetSystemInfo()->busSpeed / 4);\n   return static_cast<uint64_t>(milliseconds) * (timerSpeed / 1000);\n}\n\nOSTime\nusToTicks(OSTimeMicroseconds microseconds)\n{\n   auto timerSpeed = static_cast<uint64_t>(OSGetSystemInfo()->busSpeed / 4);\n   return static_cast<uint64_t>(microseconds) * (timerSpeed / 1000000);\n}\n\nOSTime\nnsToTicks(OSTimeNanoseconds nanoseconds)\n{\n   // Division is done in two parts to try to maintain accuracy, 31250 * 32000 = 1*10^9\n   auto timerSpeed = static_cast<uint64_t>(OSGetSystemInfo()->busSpeed / 4);\n   return (static_cast<uint64_t>(nanoseconds) * (timerSpeed / 31250)) / 32000;\n}\n\nOSTimeMilliseconds\nticksToMs(OSTick ticks)\n{\n   auto timerSpeed = static_cast<uint64_t>(OSGetSystemInfo()->busSpeed / 4);\n   return (static_cast<OSTimeMilliseconds>(ticks) * 1000) / timerSpeed;\n}\n\nOSTime\ngetBaseTime()\n{\n   return sTimeData->baseTicks.count();\n}\n\nstd::chrono::time_point<std::chrono::system_clock>\ntoTimepoint(OSTime time)\n{\n   auto ticksSinceBaseTime = cpu::TimerDuration { time } - sTimeData->baseTicks;\n   auto clocksSinceBaseTime = std::chrono::duration_cast<std::chrono::system_clock::duration>(ticksSinceBaseTime);\n   return sTimeData->baseClock + clocksSinceBaseTime;\n}\n\nOSTime\ntoOSTime(std::chrono::time_point<std::chrono::system_clock> chrono)\n{\n   auto clocksSinceBaseTime = chrono - sTimeData->baseClock;\n   auto ticksSinceBaseTime = std::chrono::duration_cast<cpu::TimerDuration>(clocksSinceBaseTime);\n   return (sTimeData->baseTicks + ticksSinceBaseTime).count();\n}\n\nvoid\ninitialiseTime()\n{\n   static std::once_flag sRegisteredConfigChangeListener;\n   std::call_once(sRegisteredConfigChangeListener,\n      []() {\n         decaf::registerConfigChangeListener(\n            [](const decaf::Settings &settings) {\n               sTimeScale = settings.system.time_scale;\n               sTimeScaleEnabled = settings.system.time_scale_enabled;\n            });\n      });\n   sTimeScale = decaf::config()->system.time_scale;\n   sTimeScaleEnabled = decaf::config()->system.time_scale_enabled;\n\n   // Calculate the Wii U epoch (01/01/2000)\n   std::tm tm = { 0 };\n   tm.tm_sec = 0;\n   tm.tm_min = 0;\n   tm.tm_hour = 0;\n   tm.tm_mday = 1;\n   tm.tm_mon = 1;\n   tm.tm_year = 2000 - 1900;\n   tm.tm_isdst = -1;\n   sTimeData->epochTime = std::chrono::system_clock::from_time_t(platform::make_gm_time(tm));\n\n   sTimeData->baseClock = std::chrono::system_clock::now();\n   auto ticksSinceEpoch = std::chrono::duration_cast<cpu::TimerDuration>(sTimeData->baseClock - sTimeData->epochTime);\n   auto ticksSinceStart = cpu::TimerDuration { cpu::this_core::state()->tb() };\n   sTimeData->baseTicks = ticksSinceEpoch - ticksSinceStart;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerTimeSymbols()\n{\n   RegisterFunctionExport(OSGetTime);\n   RegisterFunctionExport(OSGetTick);\n   RegisterFunctionExport(OSGetSystemTime);\n   RegisterFunctionExport(OSGetSystemTick);\n   RegisterFunctionExport(OSTicksToCalendarTime);\n   RegisterFunctionExport(OSCalendarTimeToTicks);\n\n   RegisterDataInternal(sTimeData);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_time.h",
    "content": "#pragma once\n#include <chrono>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_time Time\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct OSCalendarTime\n{\n   // These are all from the POSIX tm struct, we are missing tm_isdst though.\n   be2_val<int32_t> tm_sec;\n   be2_val<int32_t> tm_min;\n   be2_val<int32_t> tm_hour;\n   be2_val<int32_t> tm_mday;\n   be2_val<int32_t> tm_mon;\n   be2_val<int32_t> tm_year;\n   be2_val<int32_t> tm_wday;\n   be2_val<int32_t> tm_yday;\n\n   // Also Wii U has some extra fields not found in posix tm!\n   be2_val<int32_t> tm_msec;\n   be2_val<int32_t> tm_usec;\n};\nCHECK_OFFSET(OSCalendarTime, 0x00, tm_sec);\nCHECK_OFFSET(OSCalendarTime, 0x04, tm_min);\nCHECK_OFFSET(OSCalendarTime, 0x08, tm_hour);\nCHECK_OFFSET(OSCalendarTime, 0x0C, tm_mday);\nCHECK_OFFSET(OSCalendarTime, 0x10, tm_mon);\nCHECK_OFFSET(OSCalendarTime, 0x14, tm_year);\nCHECK_OFFSET(OSCalendarTime, 0x18, tm_wday);\nCHECK_OFFSET(OSCalendarTime, 0x1C, tm_yday);\nCHECK_OFFSET(OSCalendarTime, 0x20, tm_msec);\nCHECK_OFFSET(OSCalendarTime, 0x24, tm_usec);\nCHECK_SIZE(OSCalendarTime, 0x28);\n\n#pragma pack(pop)\n\nusing OSTick = int32_t;\n\n//! OSTime is ticks since epoch\nusing OSTime = int64_t;\n\nusing OSTimeSeconds = int64_t;\nusing OSTimeMilliseconds = int64_t;\nusing OSTimeMicroseconds = int64_t;\nusing OSTimeNanoseconds = int64_t;\n\nOSTime\nOSGetTime();\n\nOSTime\nOSGetSystemTime();\n\nOSTick\nOSGetTick();\n\nOSTick\nOSGetSystemTick();\n\nvoid\nOSTicksToCalendarTime(OSTime time,\n                      virt_ptr<OSCalendarTime> calendarTime);\n\nOSTime\nOSCalendarTimeToTicks(virt_ptr<OSCalendarTime> calendarTime);\n\n/** @} */\n\nnamespace internal\n{\n\nOSTime\nmsToTicks(OSTimeMilliseconds milliseconds);\n\nOSTime\nusToTicks(OSTimeMicroseconds microseconds);\n\nOSTime\nnsToTicks(OSTimeNanoseconds nanoseconds);\n\nOSTimeMilliseconds\nticksToMs(OSTick ticks);\n\nOSTime\ngetBaseTime();\n\nstd::chrono::time_point<std::chrono::system_clock>\ntoTimepoint(OSTime time);\n\nOSTime\ntoOSTime(std::chrono::time_point<std::chrono::system_clock> chrono);\n\nvoid\ninitialiseTime();\n\n} // namespace internal\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_userconfig.cpp",
    "content": "#include \"coreinit.h\"\n#include \"coreinit_ios.h\"\n#include \"coreinit_ipcbufpool.h\"\n#include \"coreinit_mutex.h\"\n#include \"coreinit_userconfig.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n\n#include <fmt/core.h>\n#include <libcpu/state.h>\n\nnamespace cafe::coreinit\n{\n\nusing ios::auxil::UCDeleteSysConfigRequest;\nusing ios::auxil::UCReadSysConfigRequest;\nusing ios::auxil::UCWriteSysConfigRequest;\n\nstatic constexpr uint32_t SmallMessageCount = 0x100;\nstatic constexpr uint32_t SmallMessageSize = 0x80;\n\nstatic constexpr uint32_t LargeMessageCount = 0x40;\nstatic constexpr uint32_t LargeMessageSize = 0x1000;\n\nstruct StaticUserConfigData\n{\n   be2_struct<OSMutex> lock;\n\n   be2_val<BOOL> initialised;\n\n   be2_virt_ptr<IPCBufPool> smallMessagePool;\n   be2_virt_ptr<IPCBufPool> largeMessagePool;\n\n   be2_array<uint8_t, SmallMessageCount * SmallMessageSize> smallMessageBuffer;\n   be2_array<uint8_t, LargeMessageCount * LargeMessageSize> largeMessageBuffer;\n\n   be2_val<uint32_t> smallMessageCount;\n   be2_val<uint32_t> largeMessageCount;\n};\n\nstatic virt_ptr<StaticUserConfigData> sUserConfigData = nullptr;\nstatic IOSAsyncCallbackFn sUcIosAsyncCallback = nullptr;\n\nnamespace internal\n{\n\nstatic virt_ptr<void>\nucAllocateMessage(uint32_t size)\n{\n   auto message = virt_ptr<void> { nullptr };\n\n   if (size == 0) {\n      return nullptr;\n   } else if (size <= SmallMessageSize) {\n      message = IPCBufPoolAllocate(sUserConfigData->smallMessagePool, size);\n   } else {\n      message = IPCBufPoolAllocate(sUserConfigData->largeMessagePool, size);\n   }\n\n   std::memset(message.get(), 0, size);\n   return message;\n}\n\n\nstatic void\nucFreeMessage(virt_ptr<void> message)\n{\n   IPCBufPoolFree(sUserConfigData->smallMessagePool, message);\n   IPCBufPoolFree(sUserConfigData->largeMessagePool, message);\n}\n\n\nstatic UCError\nucSetupAsyncParams(UCCommand command,\n                   uint32_t unk_r4,\n                   uint32_t count,\n                   virt_ptr<UCSysConfig> settings,\n                   virt_ptr<IOSVec> vecs,\n                   virt_ptr<UCAsyncParams> asyncParams)\n{\n   if (!settings || !vecs || !asyncParams) {\n      return UCError::InvalidParam;\n   }\n\n   asyncParams->command = command;\n   asyncParams->unk0x0C = unk_r4;\n   asyncParams->count = count;\n   asyncParams->settings = settings;\n   asyncParams->vecs = vecs;\n   return UCError::OK;\n}\n\n\nstatic UCError\nucHandleIosResult(UCError result,\n                  UCCommand command,\n                  uint32_t unk_r5,\n                  uint32_t count,\n                  virt_ptr<UCSysConfig> settings,\n                  virt_ptr<IOSVec> vecs,\n                  virt_ptr<UCAsyncParams> asyncParams,\n                  UCAsyncCallbackFn callback,\n                  virt_ptr<void> callbackContext)\n{\n   if (!settings && !vecs) {\n      if (result == UCError::OK) {\n         return UCError::InvalidParam;\n      } else {\n         return static_cast<UCError>(result);\n      }\n   }\n\n   if (result != UCError::NoIPCBuffers) {\n      if (settings && vecs) {\n         if (result == UCError::OK && asyncParams) {\n            // Return as we have a pending async result...!\n            return UCError::OK;\n         }\n\n         if (command == UCCommand::ReadSysConfig) {\n            auto request = virt_cast<UCReadSysConfigRequest *>(vecs[0].vaddr);\n\n            for (auto i = 0u; i < count; ++i) {\n               settings[i].error = request->settings[i].error;\n\n               if (settings[i].error) {\n                  result = settings[i].error;\n                  continue;\n               }\n\n               if (settings[i].dataSize) {\n                  if (!settings[i].data) {\n                     result = UCError::InvalidParam;\n                     continue;\n                  }\n\n                  auto src = virt_cast<void *>(vecs[i + 1].vaddr);\n                  switch (settings[i].dataSize) {\n                  case 0:\n                     continue;\n                  case 1:\n                     *virt_cast<uint8_t *>(settings[i].data) = *virt_cast<uint8_t *>(src);\n                     break;\n                  case 2:\n                     *virt_cast<uint16_t *>(settings[i].data) = *virt_cast<uint16_t *>(src);\n                     break;\n                  case 4:\n                     *virt_cast<uint32_t *>(settings[i].data) = *virt_cast<uint32_t *>(src);\n                     break;\n                  default:\n                     std::memset(settings[i].data.get(), 0, 4); // why???\n                     std::memcpy(settings[i].data.get(), src.get(), settings[i].dataSize);\n                  }\n               }\n            }\n         } else if (command == UCCommand::WriteSysConfig ||\n                    command == UCCommand::DeleteSysConfig) {\n            auto request = virt_cast<UCWriteSysConfigRequest *>(vecs[0].vaddr);\n\n            for (auto i = 0u; i < count; ++i) {\n               settings[i].error = request->settings[i].error;\n\n               if (settings[i].error) {\n                  result = settings[i].error;\n               }\n            }\n         } else {\n            decaf_abort(fmt::format(\"Unimplemented result handler for UCCommand {}\", command));\n         }\n      }\n\n      if (callback) {\n         cafe::invoke(cpu::this_core::state(),\n                      callback,\n                      result,\n                      command,\n                      count,\n                      settings,\n                      callbackContext);\n      }\n   }\n\n   if (vecs) {\n      for (auto i = 0u; i < count + 1; ++i) {\n         internal::ucFreeMessage(virt_cast<void *>(vecs[i].vaddr));\n      }\n\n      internal::ucFreeMessage(vecs);\n   }\n\n   return static_cast<UCError>(result);\n}\n\n\nstatic void\nucIosAsyncCallback(IOSError status,\n                   virt_ptr<void> context)\n{\n   auto asyncParams = virt_cast<UCAsyncParams *>(context);\n   ucHandleIosResult(UCError::OK,\n                     asyncParams->command,\n                     asyncParams->unk0x0C,\n                     asyncParams->count,\n                     asyncParams->settings,\n                     asyncParams->vecs,\n                     nullptr,\n                     asyncParams->callback,\n                     asyncParams->context);\n}\n\n} // namespace internal\n\n\nUCError\nUCOpen()\n{\n   OSInitMutex(virt_addrof(sUserConfigData->lock));\n   OSLockMutex(virt_addrof(sUserConfigData->lock));\n\n   if (!sUserConfigData->initialised) {\n      if (!sUserConfigData->smallMessagePool) {\n         sUserConfigData->smallMessagePool = IPCBufPoolCreate(virt_addrof(sUserConfigData->smallMessageBuffer),\n                                                             static_cast<uint32_t>(sUserConfigData->smallMessageBuffer.size()),\n                                                             SmallMessageSize,\n                                                             virt_addrof(sUserConfigData->smallMessageCount),\n                                                             1);\n      }\n\n      if (!sUserConfigData->largeMessagePool) {\n         sUserConfigData->largeMessagePool = IPCBufPoolCreate(virt_addrof(sUserConfigData->largeMessageBuffer),\n                                                             static_cast<uint32_t>(sUserConfigData->largeMessageBuffer.size()),\n                                                             LargeMessageSize,\n                                                             virt_addrof(sUserConfigData->largeMessageCount),\n                                                             1);\n      }\n\n      if (sUserConfigData->smallMessagePool && sUserConfigData->largeMessagePool) {\n         sUserConfigData->initialised = true;\n      }\n   }\n\n   OSUnlockMutex(virt_addrof(sUserConfigData->lock));\n\n   if (!sUserConfigData->initialised) {\n      return UCError::Error;\n   }\n\n   return static_cast<UCError>(IOS_Open(make_stack_string(\"/dev/usr_cfg\"),\n                                        IOSOpenMode::None));\n}\n\n\nUCError\nUCClose(IOSHandle handle)\n{\n   return static_cast<UCError>(IOS_Close(handle));\n}\n\n\nUCError\nUCDeleteSysConfig(IOSHandle handle,\n                  uint32_t count,\n                  virt_ptr<UCSysConfig> settings)\n{\n   return UCDeleteSysConfigAsync(handle, count, settings, nullptr);\n}\n\n\nUCError\nUCDeleteSysConfigAsync(IOSHandle handle,\n                       uint32_t count,\n                       virt_ptr<UCSysConfig> settings,\n                       virt_ptr<UCAsyncParams> asyncParams)\n{\n   auto result = UCError::OK;\n   uint32_t msgBufSize = 0, vecBufSize = 0;\n   virt_ptr<void> msgBuf = nullptr, vecBuf = nullptr;\n   virt_ptr<UCDeleteSysConfigRequest> request = nullptr;\n   virt_ptr<IOSVec> vecs = nullptr;\n\n   if (!settings) {\n      result = UCError::InvalidParam;\n      goto fail;\n   }\n\n   msgBufSize = static_cast<uint32_t>(count * sizeof(UCSysConfig) + sizeof(UCDeleteSysConfigRequest));\n   msgBuf = internal::ucAllocateMessage(msgBufSize);\n   if (!msgBuf) {\n      result = UCError::NoIPCBuffers;\n      goto fail;\n   }\n\n   request = virt_cast<UCDeleteSysConfigRequest *>(msgBuf);\n   request->unk0x00 = 0u;\n   request->count = count;\n   std::memcpy(request->settings,\n               settings.get(),\n               sizeof(UCSysConfig) * count);\n\n   vecBufSize = static_cast<uint32_t>((count + 1) * sizeof(IOSVec));\n   vecBuf = internal::ucAllocateMessage(vecBufSize);\n   if (!vecBuf) {\n      result = UCError::NoIPCBuffers;\n      goto fail;\n   }\n\n   vecs = virt_cast<IOSVec *>(vecBuf);\n   vecs[0].vaddr = virt_cast<virt_addr>(msgBuf);\n   vecs[0].len = msgBufSize;\n\n   for (auto i = 0u; i < count; ++i) {\n      auto size = settings[i].dataSize;\n      vecs[1 + i].len = size;\n\n      if (size > 0) {\n         vecs[1 + i].vaddr = virt_cast<virt_addr>(internal::ucAllocateMessage(size));\n         if (!vecs[1 + i].vaddr) {\n            result = UCError::NoIPCBuffers;\n            goto fail;\n         }\n      } else {\n         vecs[1 + i].vaddr = 0u;\n      }\n   }\n\n   if (!asyncParams) {\n      result = static_cast<UCError>(IOS_Ioctlv(handle,\n                                               UCCommand::DeleteSysConfig,\n                                               0,\n                                               count + 1,\n                                               vecs));\n   } else {\n      internal::ucSetupAsyncParams(UCCommand::DeleteSysConfig,\n                                   0,\n                                   count,\n                                   settings,\n                                   vecs,\n                                   asyncParams);\n\n      result = static_cast<UCError>(IOS_IoctlvAsync(handle,\n                                                    UCCommand::DeleteSysConfig,\n                                                    0,\n                                                    count + 1,\n                                                    vecs,\n                                                    sUcIosAsyncCallback,\n                                                    asyncParams));\n   }\n\n   goto out;\n\nfail:\n   if (msgBuf) {\n      internal::ucFreeMessage(msgBuf);\n      msgBuf = nullptr;\n   }\n\n   if (vecBuf) {\n      for (auto i = 0u; i < count; ++i) {\n         if (vecs[1 + i].vaddr) {\n            internal::ucFreeMessage(virt_cast<void *>(vecs[1 + i].vaddr));\n         }\n      }\n\n      internal::ucFreeMessage(vecBuf);\n      vecBuf = nullptr;\n      vecs = nullptr;\n   }\n\nout:\n   return internal::ucHandleIosResult(result,\n                                      UCCommand::DeleteSysConfig,\n                                      0,\n                                      count,\n                                      settings,\n                                      vecs,\n                                      asyncParams,\n                                      nullptr,\n                                      nullptr);\n}\n\n\nUCError\nUCReadSysConfig(IOSHandle handle,\n                uint32_t count,\n                virt_ptr<UCSysConfig> settings)\n{\n   return UCReadSysConfigAsync(handle, count, settings, nullptr);\n}\n\n\nUCError\nUCReadSysConfigAsync(IOSHandle handle,\n                     uint32_t count,\n                     virt_ptr<UCSysConfig> settings,\n                     virt_ptr<UCAsyncParams> asyncParams)\n{\n   auto result = UCError::OK;\n   uint32_t msgBufSize = 0, vecBufSize = 0;\n   virt_ptr<void> msgBuf = nullptr, vecBuf = nullptr;\n   virt_ptr<UCReadSysConfigRequest> request = nullptr;\n   virt_ptr<IOSVec> vecs = nullptr;\n\n   if (!settings) {\n      result = UCError::InvalidParam;\n      goto fail;\n   }\n\n   msgBufSize = static_cast<uint32_t>(count * sizeof(UCSysConfig) + sizeof(UCReadSysConfigRequest));\n   msgBuf = internal::ucAllocateMessage(msgBufSize);\n   if (!msgBuf) {\n      result = UCError::NoIPCBuffers;\n      goto fail;\n   }\n\n   request = virt_cast<UCReadSysConfigRequest *>(msgBuf);\n   request->unk0x00 = 0u;\n   request->count = count;\n   std::memcpy(request->settings,\n               settings.get(),\n               sizeof(UCSysConfig) * count);\n\n   vecBufSize = static_cast<uint32_t>((count + 1) * sizeof(IOSVec));\n   vecBuf = internal::ucAllocateMessage(vecBufSize);\n   if (!vecBuf) {\n      result = UCError::NoIPCBuffers;\n      goto fail;\n   }\n\n   vecs = virt_cast<IOSVec *>(vecBuf);\n   vecs[0].vaddr = virt_cast<virt_addr>(msgBuf);\n   vecs[0].len = msgBufSize;\n\n   for (auto i = 0u; i < count; ++i) {\n      auto size = settings[i].dataSize;\n      vecs[1 + i].len = size;\n\n      if (size > 0) {\n         vecs[1 + i].vaddr = virt_cast<virt_addr>(internal::ucAllocateMessage(size));\n         if (!vecs[1 + i].vaddr) {\n            result = UCError::NoIPCBuffers;\n            goto fail;\n         }\n      } else {\n         vecs[1 + i].vaddr = 0u;\n      }\n   }\n\n   if (!asyncParams) {\n      result = static_cast<UCError>(IOS_Ioctlv(handle,\n                                               UCCommand::ReadSysConfig,\n                                               0,\n                                               count + 1,\n                                               vecs));\n   } else {\n      internal::ucSetupAsyncParams(UCCommand::ReadSysConfig,\n                                   0,\n                                   count,\n                                   settings,\n                                   vecs,\n                                   asyncParams);\n\n      result = static_cast<UCError>(IOS_IoctlvAsync(handle,\n                                                    UCCommand::ReadSysConfig,\n                                                    0,\n                                                    count + 1,\n                                                    vecs,\n                                                    sUcIosAsyncCallback,\n                                                    asyncParams));\n   }\n\n   goto out;\n\nfail:\n   if (msgBuf) {\n      internal::ucFreeMessage(msgBuf);\n      msgBuf = nullptr;\n   }\n\n   if (vecBuf) {\n      for (auto i = 0u; i < count; ++i) {\n         if (vecs[1 + i].vaddr) {\n            internal::ucFreeMessage(virt_cast<void *>(vecs[1 + i].vaddr));\n         }\n      }\n\n      internal::ucFreeMessage(vecBuf);\n      vecBuf = nullptr;\n      vecs = nullptr;\n   }\n\nout:\n   return internal::ucHandleIosResult(result,\n                                      UCCommand::ReadSysConfig,\n                                      0,\n                                      count,\n                                      settings,\n                                      vecs,\n                                      asyncParams,\n                                      nullptr,\n                                      nullptr);\n}\n\n\nUCError\nUCWriteSysConfig(IOSHandle handle,\n                 uint32_t count,\n                 virt_ptr<UCSysConfig> settings)\n{\n   return UCWriteSysConfigAsync(handle, count, settings, nullptr);\n}\n\n\nUCError\nUCWriteSysConfigAsync(IOSHandle handle,\n                      uint32_t count,\n                      virt_ptr<UCSysConfig> settings,\n                      virt_ptr<UCAsyncParams> asyncParams)\n{\n   auto result = UCError::OK;\n   uint32_t msgBufSize = 0, vecBufSize = 0;\n   virt_ptr<void> vecBuf = nullptr, msgBuf = nullptr;\n   virt_ptr<UCWriteSysConfigRequest> request = nullptr;\n   virt_ptr<IOSVec> vecs = nullptr;\n\n   if (!settings) {\n      result = UCError::InvalidParam;\n      goto fail;\n   }\n\n   msgBufSize = static_cast<uint32_t>(count * sizeof(UCSysConfig) + sizeof(UCWriteSysConfigRequest));\n   msgBuf = internal::ucAllocateMessage(msgBufSize);\n   if (!msgBuf) {\n      result = UCError::NoIPCBuffers;\n      goto fail;\n   }\n\n   request = virt_cast<UCWriteSysConfigRequest *>(msgBuf);\n   request->unk0x00 = 0u;\n   request->count = count;\n   std::memcpy(request->settings,\n               settings.get(),\n               count * sizeof(UCSysConfig));\n\n   vecBufSize = static_cast<uint32_t>((count + 1) * sizeof(IOSVec));\n   vecBuf = internal::ucAllocateMessage(vecBufSize);\n   if (!vecBuf) {\n      result = UCError::NoIPCBuffers;\n      goto fail;\n   }\n\n   vecs = virt_cast<IOSVec *>(vecBuf);\n   vecs[0].vaddr = virt_cast<virt_addr>(msgBuf);\n   vecs[0].len = msgBufSize;\n\n   for (auto i = 0u; i < count; ++i) {\n      auto size = settings[i].dataSize;\n      vecs[1 + i].len = size;\n\n      if (size > 0) {\n         vecs[1 + i].vaddr = virt_cast<virt_addr>(internal::ucAllocateMessage(size));\n         if (!vecs[1 + i].vaddr) {\n            result = UCError::NoIPCBuffers;\n            goto fail;\n         }\n\n         if (settings[i].data) {\n            std::memcpy(virt_cast<void *>(vecs[1 + i].vaddr).get(),\n                        settings[i].data.get(),\n                        settings[i].dataSize);\n         }\n      } else {\n         vecs[1 + i].vaddr = 0u;\n      }\n   }\n\n   if (!asyncParams) {\n      result = static_cast<UCError>(IOS_Ioctlv(handle,\n                                               UCCommand::WriteSysConfig,\n                                               0,\n                                               count + 1,\n                                               vecs));\n   } else {\n      internal::ucSetupAsyncParams(UCCommand::WriteSysConfig,\n                                   0,\n                                   count,\n                                   settings,\n                                   vecs,\n                                   asyncParams);\n\n      result = static_cast<UCError>(IOS_IoctlvAsync(handle,\n                                                    UCCommand::WriteSysConfig,\n                                                    0,\n                                                    count + 1,\n                                                    vecs,\n                                                    sUcIosAsyncCallback,\n                                                    asyncParams));\n   }\n\n   goto out;\n\nfail:\n   if (msgBuf) {\n      internal::ucFreeMessage(msgBuf);\n      msgBuf = nullptr;\n   }\n\n   if (vecBuf) {\n      for (auto i = 0u; i < count; ++i) {\n         if (vecs[1 + i].vaddr) {\n            internal::ucFreeMessage(virt_cast<void *>(vecs[1 + i].vaddr));\n         }\n      }\n\n      internal::ucFreeMessage(vecBuf);\n      vecBuf = nullptr;\n      vecs = nullptr;\n   }\n\nout:\n   return internal::ucHandleIosResult(result,\n                                      UCCommand::WriteSysConfig,\n                                      0,\n                                      count,\n                                      settings,\n                                      vecs,\n                                      asyncParams,\n                                      nullptr,\n                                      nullptr);\n}\n\nvoid\nLibrary::registerUserConfigSymbols()\n{\n   RegisterFunctionExport(UCOpen);\n   RegisterFunctionExport(UCClose);\n   RegisterFunctionExport(UCDeleteSysConfig);\n   RegisterFunctionExport(UCDeleteSysConfigAsync);\n   RegisterFunctionExport(UCReadSysConfig);\n   RegisterFunctionExport(UCReadSysConfigAsync);\n   RegisterFunctionExport(UCWriteSysConfig);\n   RegisterFunctionExport(UCWriteSysConfigAsync);\n\n   RegisterDataInternal(sUserConfigData);\n   RegisterFunctionInternal(internal::ucIosAsyncCallback, sUcIosAsyncCallback);\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/coreinit/coreinit_userconfig.h",
    "content": "#pragma once\n#include \"coreinit_enum.h\"\n#include \"coreinit_ios.h\"\n#include \"ios/auxil/ios_auxil_usr_cfg.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\n\n/**\n * \\defgroup coreinit_userconfig User Config\n * \\ingroup coreinit\n * @{\n */\n\n#pragma pack(push, 1)\n\nusing ios::auxil::UCCommand;\nusing ios::auxil::UCDataType;\nusing ios::auxil::UCError;\n\n// This is a copy of ios::auxil::UCSysConfig but with a virt_ptr\nstruct UCSysConfig\n{\n   be2_array<char, 64> name;\n   be2_val<uint32_t> access;\n   be2_val<UCDataType> dataType;\n   be2_val<UCError> error;\n   be2_val<uint32_t> dataSize;\n   be2_virt_ptr<void> data;\n};\nCHECK_OFFSET(UCSysConfig, 0x00, name);\nCHECK_OFFSET(UCSysConfig, 0x40, access);\nCHECK_OFFSET(UCSysConfig, 0x44, dataType);\nCHECK_OFFSET(UCSysConfig, 0x48, error);\nCHECK_OFFSET(UCSysConfig, 0x4C, dataSize);\nCHECK_OFFSET(UCSysConfig, 0x50, data);\nCHECK_SIZE(UCSysConfig, 0x54);\n\nusing UCAsyncCallbackFn = virt_func_ptr<void(UCError result,\n                                             UCCommand command,\n                                             uint32_t count,\n                                             virt_ptr<UCSysConfig> settings,\n                                             virt_ptr<void> context)>;\n\nstruct UCAsyncParams\n{\n   be2_val<UCAsyncCallbackFn> callback;\n   be2_virt_ptr<void> context;\n   be2_val<UCCommand> command;\n   be2_val<uint32_t> unk0x0C;\n   be2_val<uint32_t> count;\n   be2_virt_ptr<UCSysConfig> settings;\n   be2_virt_ptr<IOSVec> vecs;\n};\nCHECK_OFFSET(UCAsyncParams, 0x00, callback);\nCHECK_OFFSET(UCAsyncParams, 0x04, context);\nCHECK_OFFSET(UCAsyncParams, 0x08, command);\nCHECK_OFFSET(UCAsyncParams, 0x0C, unk0x0C);\nCHECK_OFFSET(UCAsyncParams, 0x10, count);\nCHECK_OFFSET(UCAsyncParams, 0x14, settings);\nCHECK_OFFSET(UCAsyncParams, 0x18, vecs);\nCHECK_SIZE(UCAsyncParams, 0x1C);\n\n#pragma pack(pop)\n\nUCError\nUCOpen();\n\nUCError\nUCClose(IOSHandle handle);\n\nUCError\nUCDeleteSysConfig(IOSHandle handle,\n                  uint32_t count,\n                  virt_ptr<UCSysConfig> settings);\n\nUCError\nUCDeleteSysConfigAsync(IOSHandle handle,\n                       uint32_t count,\n                       virt_ptr<UCSysConfig> settings,\n                       virt_ptr<UCAsyncParams> asyncParams);\n\nUCError\nUCReadSysConfig(IOSHandle handle,\n                uint32_t count,\n                virt_ptr<UCSysConfig> settings);\n\nUCError\nUCReadSysConfigAsync(IOSHandle handle,\n                     uint32_t count,\n                     virt_ptr<UCSysConfig> settings,\n                     virt_ptr<UCAsyncParams> asyncParams);\n\nUCError\nUCWriteSysConfig(IOSHandle handle,\n                 uint32_t count,\n                 virt_ptr<UCSysConfig> settings);\n\nUCError\nUCWriteSysConfigAsync(IOSHandle handle,\n                      uint32_t count,\n                      virt_ptr<UCSysConfig> settings,\n                      virt_ptr<UCAsyncParams> asyncParams);\n\n/** @} */\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/dc/dc.cpp",
    "content": "#include \"dc.h\"\n\nnamespace cafe::dc\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::dc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/dc/dc.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::dc\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::dc, \"dc.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::dc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/dmae/dmae.cpp",
    "content": "#include \"dmae.h\"\n#include \"dmae_ring.h\"\n\nnamespace cafe::dmae\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   DMAEInit();\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerRingSymbols();\n}\n\n} // namespace cafe::coreinit\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/dmae/dmae.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::dmae\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::dmae, \"dmae.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerRingSymbols();\n};\n\n} // namespace cafe::dmae\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/dmae/dmae_enum.h",
    "content": "#ifndef CAFE_DMAE_ENUM_H\n#define CAFE_DMAE_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(dmae)\n\nENUM_BEG(DMAEEndianSwapMode, uint32_t)\n   ENUM_VALUE(None,                    0)\n   ENUM_VALUE(Swap8In16,               1)\n   ENUM_VALUE(Swap8In32,               2)\nENUM_END(DMAEEndianSwapMode)\n\nENUM_NAMESPACE_EXIT(dmae)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_DMAE_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/dmae/dmae_ring.cpp",
    "content": "#include \"dmae.h\"\n#include \"dmae_ring.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_time.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include <cstring>\n\nnamespace cafe::dmae\n{\n\nstruct StaticRingData\n{\n   be2_struct<coreinit::OSMutex> mutex;\n   be2_val<uint32_t> timeout;\n   be2_val<DMAETimestamp> lastSubmittedTimestamp;\n};\n\nstatic virt_ptr<StaticRingData>\nsRingData = nullptr;\n\nvoid\nDMAEInit()\n{\n   coreinit::OSInitMutex(virt_addrof(sRingData->mutex));\n}\n\nDMAETimestamp\nDMAEGetLastSubmittedTimeStamp()\n{\n   coreinit::OSLockMutex(virt_addrof(sRingData->mutex));\n   auto timestamp = sRingData->lastSubmittedTimestamp;\n   coreinit::OSUnlockMutex(virt_addrof(sRingData->mutex));\n   return timestamp;\n}\n\nDMAETimestamp\nDMAEGetRetiredTimeStamp()\n{\n   return DMAEGetLastSubmittedTimeStamp();\n}\n\nuint32_t\nDMAEGetTimeout()\n{\n   return sRingData->timeout;\n}\n\nvoid\nDMAESetTimeout(uint32_t timeout)\n{\n   sRingData->timeout = timeout;\n}\n\nuint64_t\nDMAECopyMem(virt_ptr<void> dst,\n            virt_ptr<void> src,\n            uint32_t numWords,\n            DMAEEndianSwapMode endian)\n{\n   coreinit::OSLockMutex(virt_addrof(sRingData->mutex));\n\n   if (endian == DMAEEndianSwapMode::None) {\n      std::memcpy(dst.get(),\n                  src.get(),\n                  numWords * 4);\n   } else if (endian == DMAEEndianSwapMode::Swap8In16) {\n      auto dstWords = reinterpret_cast<uint16_t *>(dst.get());\n      auto srcWords = reinterpret_cast<uint16_t *>(src.get());\n      for (auto i = 0u; i < numWords * 2; ++i) {\n         *dstWords++ = byte_swap(*srcWords++);\n      }\n   } else if (endian == DMAEEndianSwapMode::Swap8In32) {\n      auto dstDwords = reinterpret_cast<uint32_t *>(dst.get());\n      auto srcDwords = reinterpret_cast<uint32_t *>(src.get());\n      for (auto i = 0u; i < numWords; ++i) {\n         *dstDwords++ = byte_swap(*srcDwords++);\n      }\n   }\n\n   auto timestamp = coreinit::OSGetTime();\n   sRingData->lastSubmittedTimestamp = timestamp;\n\n   coreinit::OSUnlockMutex(virt_addrof(sRingData->mutex));\n   return timestamp;\n}\n\nuint64_t\nDMAEFillMem(virt_ptr<void> dst,\n            uint32_t value,\n            uint32_t numDwords)\n{\n   coreinit::OSLockMutex(virt_addrof(sRingData->mutex));\n\n   auto dstValue = byte_swap(value);\n   auto dstDwords = reinterpret_cast<uint32_t *>(dst.get());\n   for (auto i = 0u; i < numDwords; ++i) {\n      dstDwords[i] = dstValue;\n   }\n\n   auto timestamp = coreinit::OSGetTime();\n   sRingData->lastSubmittedTimestamp = timestamp;\n\n   coreinit::OSUnlockMutex(virt_addrof(sRingData->mutex));\n   return timestamp;\n}\n\nBOOL\nDMAEWaitDone(DMAETimestamp timestamp)\n{\n   return TRUE;\n}\n\nvoid\nLibrary::registerRingSymbols()\n{\n   RegisterFunctionExport(DMAEInit);\n   RegisterFunctionExport(DMAEGetLastSubmittedTimeStamp);\n   RegisterFunctionExport(DMAEGetRetiredTimeStamp);\n   RegisterFunctionExport(DMAEGetTimeout);\n   RegisterFunctionExport(DMAESetTimeout);\n   RegisterFunctionExport(DMAECopyMem);\n   RegisterFunctionExport(DMAEFillMem);\n   RegisterFunctionExport(DMAEWaitDone);\n\n   RegisterDataInternal(sRingData);\n}\n\n} // namespace cafe::dmae\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/dmae/dmae_ring.h",
    "content": "#pragma once\n#include \"dmae_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::dmae\n{\n\nusing DMAETimestamp = int64_t;\n\nvoid\nDMAEInit();\n\nDMAETimestamp\nDMAEGetLastSubmittedTimeStamp();\n\nDMAETimestamp\nDMAEGetRetiredTimeStamp();\n\nuint32_t\nDMAEGetTimeout();\n\nvoid\nDMAESetTimeout(uint32_t timeout);\n\nuint64_t\nDMAECopyMem(virt_ptr<void> dst,\n            virt_ptr<void> src,\n            uint32_t numWords,\n            DMAEEndianSwapMode endian);\n\nuint64_t\nDMAEFillMem(virt_ptr<void> dst,\n            uint32_t value,\n            uint32_t numDwords);\n\nBOOL\nDMAEWaitDone(DMAETimestamp timestamp);\n\n} // namespace cafe::dmae\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/drmapp/drmapp.cpp",
    "content": "#include \"drmapp.h\"\n\nnamespace cafe::drmapp\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::drmapp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/drmapp/drmapp.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::drmapp\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::drmapp, \"drmapp.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::drmapp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/erreula/erreula.cpp",
    "content": "#include \"erreula.h\"\n\nnamespace cafe::nn_erreula\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerErrorViewerSymbols();\n}\n\n} // namespace cafe::nn_erreula\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/erreula/erreula.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_erreula\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::erreula, \"erreula.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerErrorViewerSymbols();\n};\n\n} // namespace cafe::nn_erreula\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/erreula/erreula_enum.h",
    "content": "#ifndef CAFE_ERREULA_ENUM_H\n#define CAFE_ERREULA_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(nn_erreula)\n\nENUM_BEG(ControllerType, uint32_t)\n   ENUM_VALUE(WiiRemote0,        0)\n   ENUM_VALUE(WiiRemote1,        1)\n   ENUM_VALUE(WiiRemote2,        2)\n   ENUM_VALUE(WiiRemote3,        3)\n   ENUM_VALUE(DrcGamepad,        4)\nENUM_END(ControllerType)\n\nENUM_BEG(ErrorType, uint32_t)\n   ENUM_VALUE(Code,              0)\n   ENUM_VALUE(Message,           1)\n   ENUM_VALUE(Message1Button,    2)\n   ENUM_VALUE(Message2Button,    3)\nENUM_END(ErrorType)\n\nENUM_BEG(ErrorViewerState, uint32_t)\n   ENUM_VALUE(Hidden,            0)\n   ENUM_VALUE(FadeIn,            1)\n   ENUM_VALUE(Visible,           2)\n   ENUM_VALUE(FadeOut,           3)\nENUM_END(ErrorViewerState)\n\nENUM_BEG(LangType, uint32_t)\n   ENUM_VALUE(Japanese,          0)\n   ENUM_VALUE(English,           1)\n   // TODO: More values..\nENUM_END(LangType)\n\nENUM_BEG(RegionType, uint32_t)\n   ENUM_VALUE(Japan,             0)\n   ENUM_VALUE(USA,               1)\n   ENUM_VALUE(Europe,            2)\n   ENUM_VALUE(China,             3)\n   ENUM_VALUE(Korea,             4)\n   ENUM_VALUE(Taiwan,            5)\nENUM_END(RegionType)\n\nENUM_BEG(RenderTarget, uint32_t)\n   ENUM_VALUE(Tv,                0)\n   ENUM_VALUE(Drc,               1)\n   ENUM_VALUE(Both,              2)\nENUM_END(RenderTarget)\n\nENUM_BEG(ResultType, uint32_t)\n   ENUM_VALUE(None,              0)\n   ENUM_VALUE(Exited,            1)\n   // TODO: More values..\nENUM_END(ResultType)\n\nENUM_NAMESPACE_EXIT(nn_erreula)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_ERREULA_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/erreula/erreula_errorviewer.cpp",
    "content": "#include \"erreula.h\"\n#include \"erreula_errorviewer.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\n#include \"decaf_erreula.h\"\n\n#include <mutex>\n\nnamespace cafe::nn_erreula\n{\n\nstruct StaticErrEulaData\n{\n   be2_val<ErrorViewerState> errorViewerState;\n   be2_val<bool> buttonPressed;\n   be2_val<bool> button2Pressed;\n   be2_virt_ptr<uint8_t> workMemory;\n};\n\nstatic virt_ptr<StaticErrEulaData> sErrEulaData = nullptr;\n\n// Uses a real mutex because we interact with outside world via ErrEulaDriver\nstatic std::mutex sMutex;\n\nvoid\nErrEulaAppearError(virt_ptr<const AppearArg> args)\n{\n   {\n      std::unique_lock<std::mutex> lock { sMutex };\n      sErrEulaData->errorViewerState = ErrorViewerState::Visible;\n   }\n\n   if (auto driver = decaf::errEulaDriver()) {\n      switch (args->errorArg.errorType) {\n      case ErrorType::Code:\n         driver->onOpenErrorCode(args->errorArg.errorCode);\n         break;\n      case ErrorType::Message:\n         driver->onOpenErrorMessage(args->errorArg.errorMessage.getRawPointer());\n         break;\n      case ErrorType::Message1Button:\n         driver->onOpenErrorMessage(args->errorArg.errorMessage.getRawPointer(),\n                                    args->errorArg.button1Label.getRawPointer());\n         break;\n      case ErrorType::Message2Button:\n         driver->onOpenErrorMessage(args->errorArg.errorMessage.getRawPointer(),\n                                    args->errorArg.button1Label.getRawPointer(),\n                                    args->errorArg.button2Label.getRawPointer());\n         break;\n      }\n   }\n}\n\nvoid\nErrEulaCalc(virt_ptr<const ControllerInfo> info)\n{\n}\n\nBOOL\nErrEulaCreate(virt_ptr<uint8_t> workMemory,\n              RegionType region,\n              LangType language,\n              virt_ptr<coreinit::FSClient> fsClient)\n{\n   return TRUE;\n}\n\nvoid\nErrEulaDestroy()\n{\n}\n\nvoid\nErrEulaDisappearError()\n{\n   {\n      std::unique_lock<std::mutex> lock { sMutex };\n      sErrEulaData->errorViewerState = ErrorViewerState::Hidden;\n   }\n\n   if (auto driver = decaf::errEulaDriver()) {\n      driver->onClose();\n   }\n}\n\nvoid\nErrEulaDrawDRC()\n{\n}\n\nvoid\nErrEulaDrawTV()\n{\n}\n\nErrorViewerState\nErrEulaGetStateErrorViewer()\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   return sErrEulaData->errorViewerState;\n}\n\nBOOL\nErrEulaIsDecideSelectButtonError()\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   return sErrEulaData->buttonPressed;\n}\n\nBOOL\nErrEulaIsDecideSelectLeftButtonError()\n{\n   std::unique_lock<std::mutex> lock{ sMutex };\n   return sErrEulaData->buttonPressed;\n}\n\nBOOL\nErrEulaIsDecideSelectRightButtonError()\n{\n   std::unique_lock<std::mutex> lock{ sMutex };\n   return sErrEulaData->button2Pressed;\n}\n\nvoid\nErrEulaSetControllerRemo(ControllerType type)\n{\n   decaf_warn_stub();\n}\n\nvoid\nErrEulaAppearHomeNixSign(virt_ptr<const HomeNixSignArg> arg)\n{\n   decaf_warn_stub();\n}\n\nBOOL\nErrEulaIsAppearHomeNixSign()\n{\n   decaf_warn_stub();\n   return FALSE;\n}\n\nvoid\nErrEulaDisappearHomeNixSign()\n{\n   decaf_warn_stub();\n}\n\nvoid\nErrEulaChangeLang(LangType language)\n{\n   decaf_warn_stub();\n}\n\nBOOL\nErrEulaIsSelectCursorActive()\n{\n   decaf_warn_stub();\n   return FALSE;\n}\n\nResultType\nErrEulaGetResultType()\n{\n   decaf_warn_stub();\n   return static_cast<ResultType>(1);\n}\n\nuint32_t\nErrEulaGetResultCode()\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nuint32_t\nErrEulaGetSelectButtonNumError()\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nvoid\nErrEulaSetVersion(int32_t version)\n{\n   decaf_warn_stub();\n}\n\nvoid\nErrEulaPlayAppearSE(bool value)\n{\n   decaf_warn_stub();\n}\n\nbool\nErrEulaJump(virt_ptr<const char> a1,\n            uint32_t a2)\n{\n   decaf_warn_stub();\n   return false;\n}\n\nnamespace internal\n{\n\nvoid\nbuttonClicked()\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   if (sErrEulaData->errorViewerState != ErrorViewerState::Visible) {\n      return;\n   }\n\n   sErrEulaData->buttonPressed = true;\n}\n\nvoid\nbutton1Clicked()\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   if (sErrEulaData->errorViewerState != ErrorViewerState::Visible) {\n      return;\n   }\n\n   sErrEulaData->buttonPressed = true;\n}\n\nvoid\nbutton2Clicked()\n{\n   std::unique_lock<std::mutex> lock{ sMutex };\n   if (sErrEulaData->errorViewerState != ErrorViewerState::Visible) {\n      return;\n   }\n\n   sErrEulaData->button2Pressed = true;\n}\n\n} // internal\n\nvoid Library::registerErrorViewerSymbols()\n{\n   RegisterFunctionExportName(\"ErrEulaAppearError__3RplFRCQ3_2nn7erreula9AppearArg\",\n                              ErrEulaAppearError);\n   RegisterFunctionExportName(\"ErrEulaCalc__3RplFRCQ3_2nn7erreula14ControllerInfo\",\n                              ErrEulaCalc);\n   RegisterFunctionExportName(\"ErrEulaCreate__3RplFPUcQ3_2nn7erreula10RegionTypeQ3_2nn7erreula8LangTypeP8FSClient\",\n                              ErrEulaCreate);\n   RegisterFunctionExportName(\"ErrEulaDestroy__3RplFv\",\n                              ErrEulaDestroy);\n   RegisterFunctionExportName(\"ErrEulaDisappearError__3RplFv\",\n                              ErrEulaDisappearError);\n   RegisterFunctionExportName(\"ErrEulaDrawDRC__3RplFv\",\n                              ErrEulaDrawDRC);\n   RegisterFunctionExportName(\"ErrEulaDrawTV__3RplFv\",\n                              ErrEulaDrawTV);\n   RegisterFunctionExportName(\"ErrEulaGetStateErrorViewer__3RplFv\",\n                              ErrEulaGetStateErrorViewer);\n   RegisterFunctionExportName(\"ErrEulaIsDecideSelectButtonError__3RplFv\",\n                              ErrEulaIsDecideSelectButtonError);\n   RegisterFunctionExportName(\"ErrEulaIsDecideSelectLeftButtonError__3RplFv\",\n                              ErrEulaIsDecideSelectLeftButtonError);\n   RegisterFunctionExportName(\"ErrEulaIsDecideSelectRightButtonError__3RplFv\",\n                              ErrEulaIsDecideSelectRightButtonError);\n   RegisterFunctionExportName(\"ErrEulaSetControllerRemo__3RplFQ3_2nn7erreula14ControllerType\",\n                              ErrEulaSetControllerRemo);\n   RegisterFunctionExportName(\"ErrEulaAppearHomeNixSign__3RplFRCQ3_2nn7erreula14HomeNixSignArg\",\n                              ErrEulaAppearHomeNixSign);\n   RegisterFunctionExportName(\"ErrEulaIsAppearHomeNixSign__3RplFv\",\n                              ErrEulaIsAppearHomeNixSign);\n   RegisterFunctionExportName(\"ErrEulaDisappearHomeNixSign__3RplFv\",\n                              ErrEulaDisappearHomeNixSign);\n   RegisterFunctionExportName(\"ErrEulaChangeLang__3RplFQ3_2nn7erreula8LangType\",\n                              ErrEulaChangeLang);\n   RegisterFunctionExportName(\"ErrEulaIsSelectCursorActive__3RplFv\",\n                              ErrEulaIsSelectCursorActive);\n   RegisterFunctionExportName(\"ErrEulaGetResultType__3RplFv\",\n                              ErrEulaGetResultType);\n   RegisterFunctionExportName(\"ErrEulaGetResultCode__3RplFv\",\n                              ErrEulaGetResultCode);\n   RegisterFunctionExportName(\"ErrEulaGetSelectButtonNumError__3RplFv\",\n                              ErrEulaGetSelectButtonNumError);\n   RegisterFunctionExportName(\"ErrEulaSetVersion__3RplFi\",\n                              ErrEulaSetVersion);\n   RegisterFunctionExportName(\"ErrEulaPlayAppearSE__3RplFb\",\n                              ErrEulaPlayAppearSE);\n   RegisterFunctionExportName(\"ErrEulaJump__3RplFPCcUi\",\n                              ErrEulaJump);\n\n   RegisterDataInternal(sErrEulaData);\n}\n\n} // namespace cafe::nn_erreula\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/erreula/erreula_errorviewer.h",
    "content": "#pragma once\n#include \"erreula_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\nstruct FSClient;\n} // namespace cafe::coreinit\n\nnamespace cafe::kpad\n{\nstruct KPADStatus;\n} // namespace cafe::kpad\n\nnamespace cafe::vpad\n{\nstruct VPADStatus;\n} // namespace cafe::vpad\n\nnamespace cafe::nn_erreula\n{\n\nstruct ErrorArg\n{\n   be2_val<ErrorType> errorType;\n   be2_val<RenderTarget> renderTarget;\n   be2_val<ControllerType> controllerType;\n   be2_val<uint32_t> unknown0x0C;\n   be2_val<int32_t> errorCode;\n   be2_val<uint32_t> unknown0x14;\n   be2_virt_ptr<const char16_t> errorMessage;\n   be2_virt_ptr<const char16_t> button1Label;\n   be2_virt_ptr<const char16_t> button2Label;\n   be2_virt_ptr<const char16_t> errorTitle;\n   be2_val<bool> unknown0x28;\n   PADDING(3);\n};\nCHECK_OFFSET(ErrorArg, 0x00, errorType);\nCHECK_OFFSET(ErrorArg, 0x04, renderTarget);\nCHECK_OFFSET(ErrorArg, 0x08, controllerType);\nCHECK_OFFSET(ErrorArg, 0x0C, unknown0x0C);\nCHECK_OFFSET(ErrorArg, 0x10, errorCode);\nCHECK_OFFSET(ErrorArg, 0x14, unknown0x14);\nCHECK_OFFSET(ErrorArg, 0x18, errorMessage);\nCHECK_OFFSET(ErrorArg, 0x1C, button1Label);\nCHECK_OFFSET(ErrorArg, 0x20, button2Label);\nCHECK_OFFSET(ErrorArg, 0x24, errorTitle);\nCHECK_OFFSET(ErrorArg, 0x28, unknown0x28);\nCHECK_SIZE(ErrorArg, 0x2C);\n\nstruct AppearArg\n{\n   be2_struct<ErrorArg> errorArg;\n};\nCHECK_OFFSET(AppearArg, 0x00, errorArg);\nCHECK_SIZE(AppearArg, 0x2C);\n\nstruct ControllerInfo\n{\n   be2_virt_ptr<vpad::VPADStatus> vpad;\n   be2_array<virt_ptr<kpad::KPADStatus>, 4> kpad;\n};\nCHECK_OFFSET(ControllerInfo, 0x00, vpad);\nCHECK_OFFSET(ControllerInfo, 0x04, kpad);\nCHECK_SIZE(ControllerInfo, 0x14);\n\nstruct HomeNixSignArg\n{\n   be2_val<uint32_t> unknown0x00;\n};\nCHECK_OFFSET(HomeNixSignArg, 0x00, unknown0x00);\nCHECK_SIZE(HomeNixSignArg, 0x04);\n\nvoid\nErrEulaAppearError(virt_ptr<const AppearArg> args);\n\nvoid\nErrEulaCalc(virt_ptr<const ControllerInfo> info);\n\nBOOL\nErrEulaCreate(virt_ptr<uint8_t> workMemory,\n              RegionType region,\n              LangType language,\n              virt_ptr<coreinit::FSClient> fsClient);\n\nvoid\nErrEulaDestroy();\n\nvoid\nErrEulaDisappearError();\n\nvoid\nErrEulaDrawDRC();\n\nvoid\nErrEulaDrawTV();\n\nErrorViewerState\nErrEulaGetStateErrorViewer();\n\nBOOL\nErrEulaIsDecideSelectButtonError();\n\nBOOL\nErrEulaIsDecideSelectLeftButtonError();\n\nBOOL\nErrEulaIsDecideSelectRightButtonError();\n\nvoid\nErrEulaSetControllerRemo(ControllerType type);\n\nvoid\nErrEulaAppearHomeNixSign(virt_ptr<const HomeNixSignArg> arg);\n\nBOOL\nErrEulaIsAppearHomeNixSign();\n\nvoid\nErrEulaDisappearHomeNixSign();\n\nvoid\nErrEulaChangeLang(LangType language);\n\nBOOL\nErrEulaIsSelectCursorActive();\n\nResultType\nErrEulaGetResultType();\n\nuint32_t\nErrEulaGetResultCode();\n\nuint32_t\nErrEulaGetSelectButtonNumError();\n\nvoid\nErrEulaSetVersion(int32_t version);\n\nvoid\nErrEulaPlayAppearSE(bool value);\n\nbool\nErrEulaJump(virt_ptr<const char> a1,\n            uint32_t a2);\n\nnamespace internal\n{\n\nvoid\nbuttonClicked();\n\nvoid\nbutton1Clicked();\n\nvoid\nbutton2Clicked();\n\n} // internal\n\n} // namespace cafe::nn_erreula\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/ghs/cafe_ghs_enum.h",
    "content": "#ifndef GHS_ENUM_H\n#define GHS_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(ghs)\n\nFLAGS_BEG(DestructorFlags, uint32_t)\n   FLAGS_VALUE(None,                0)\n   FLAGS_VALUE(FreeMemory,          0x40)\nFLAGS_END(DestructorFlags)\n\nENUM_NAMESPACE_EXIT(ghs)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef GHS_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/ghs/cafe_ghs_malloc.cpp",
    "content": "#include \"cafe_ghs_malloc.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_memdefaultheap.h\"\n#include \"cafe/libraries/coreinit/coreinit_ghs.h\"\n#include \"cafe/libraries/coreinit/coreinit_osreport.h\"\n\nnamespace cafe::ghs\n{\n\nusing namespace cafe::coreinit;\n\nstruct MallocGuard\n{\n   static constexpr uint32_t GuardWord = 0xCAFE4321;\n   be2_val<uint32_t> guardWord;\n   be2_val<uint32_t> allocSize;\n};\nCHECK_OFFSET(MallocGuard, 0, guardWord);\nCHECK_OFFSET(MallocGuard, 4, allocSize);\nCHECK_SIZE(MallocGuard, 8);\n\nvirt_ptr<void>\nmalloc(uint32_t size)\n{\n   auto ptr = MEMAllocFromDefaultHeapEx(size + sizeof(MallocGuard), 8);\n   if (!ptr) {\n      gh_set_errno(12);\n      return nullptr;\n   }\n\n   auto guard = virt_cast<MallocGuard *>(ptr);\n   guard->guardWord = MallocGuard::GuardWord;\n   guard->allocSize = size;\n   return virt_cast<void *>(guard + 1);\n}\n\nvoid\nfree(virt_ptr<void> ptr)\n{\n   if (!ptr) {\n      return;\n   }\n\n\n   auto guard = virt_cast<MallocGuard *>(ptr) - 1;\n   if (guard->guardWord != MallocGuard::GuardWord) {\n      internal::OSPanic(\"cos_def_malloc.c\", 93,\n                        \"Failed assertion *rawptr == COS_DEF_MALLOC_GUARDWORD\");\n   }\n\n   MEMFreeToDefaultHeap(ptr);\n}\n\n} // namespace cafe::ghs\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/ghs/cafe_ghs_malloc.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::ghs\n{\n\nvirt_ptr<void>\nmalloc(uint32_t size);\n\nvoid\nfree(virt_ptr<void> ptr);\n\n} // namespace cafe::ghs\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/ghs/cafe_ghs_typeinfo.cpp",
    "content": "#include \"cafe_ghs_typeinfo.h\"\n#include \"cafe_ghs_malloc.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_ghs.h\"\n#include \"cafe/libraries/coreinit/coreinit_osreport.h\"\n\nnamespace cafe::ghs\n{\n\nvoid\nstd_typeinfo_Destructor(virt_ptr<void> self,\n                        ghs::DestructorFlags flags)\n{\n   if (self && (flags & ghs::DestructorFlags::FreeMemory)) {\n      ghs::free(self);\n   }\n}\n\nvoid\npure_virtual_called()\n{\n   coreinit::internal::OSPanic(\"ghs\", 0, \"__pure_virtual_called\");\n   coreinit::ghs_exit(6);\n}\n\n} // namespace cafe::ghs\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/ghs/cafe_ghs_typeinfo.h",
    "content": "#pragma once\n#include \"cafe_ghs_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::ghs\n{\n\nstruct BaseTypeDescriptor;\nstruct TypeDescriptor;\nstruct VirtualTable;\nusing TypeIDStorage = uint32_t;\n\nstruct BaseTypeDescriptor\n{\n   be2_virt_ptr<TypeDescriptor> typeDescriptor;\n   be2_val<uint32_t> flags;\n};\nCHECK_OFFSET(BaseTypeDescriptor, 0x00, typeDescriptor);\nCHECK_OFFSET(BaseTypeDescriptor, 0x04, flags);\nCHECK_SIZE(BaseTypeDescriptor, 0x08);\n\nstruct TypeDescriptor\n{\n   //! Pointer to virtual table for std::typeinfo\n   be2_virt_ptr<VirtualTable> typeInfoVTable;\n\n   //! Name of this type\n   be2_virt_ptr<const char> name;\n\n   //! Unique ID for this type\n   be2_val<uint32_t> typeID;\n\n   //! Pointer to a list of base types, the last base type has flags=0x1600\n   be2_virt_ptr<BaseTypeDescriptor> baseTypes;\n};\nCHECK_OFFSET(TypeDescriptor, 0x00, typeInfoVTable);\nCHECK_OFFSET(TypeDescriptor, 0x04, name);\nCHECK_OFFSET(TypeDescriptor, 0x08, typeID);\nCHECK_OFFSET(TypeDescriptor, 0x0C, baseTypes);\nCHECK_SIZE(TypeDescriptor, 0x10);\n\nstruct VirtualTable\n{\n   be2_val<uint32_t> flags;\n   be2_virt_ptr<void> ptr;\n};\nCHECK_OFFSET(VirtualTable, 0x00, flags);\nCHECK_OFFSET(VirtualTable, 0x04, ptr);\nCHECK_SIZE(VirtualTable, 0x08);\n\nvoid\nstd_typeinfo_Destructor(virt_ptr<void> self,\n                        ghs::DestructorFlags flags);\n\nvoid\npure_virtual_called();\n\n} // namespace cafe::ghs\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_debug.h\"\n#include \"gx2r_resource.h\"\n\nnamespace cafe::gx2\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   internal::initialiseDebug();\n   internal::initialiseGx2rAllocator();\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerLibraryDependency(\"coreinit\");\n   registerLibraryDependency(\"tcl\");\n\n   registerApertureSymbols();\n   registerCbPoolSymbols();\n   registerClearSymbols();\n   registerContextStateSymbols();\n   registerCounterSymbols();\n   registerDebugCaptureSymbols();\n   registerDisplaySymbols();\n   registerDisplayListSymbols();\n   registerDrawSymbols();\n   registerEventSymbols();\n   registerFenceSymbols();\n   registerFetchShadersSymbols();\n   registerFormatSymbols();\n   registerMemorySymbols();\n   registerQuerySymbols();\n   registerRegistersSymbols();\n   registerSamplerSymbols();\n   registerShadersSymbols();\n   registerStateSymbols();\n   registerSurfaceSymbols();\n   registerTempSymbols();\n   registerTessellationSymbols();\n   registerTextureSymbols();\n   registerGx2rBufferSymbols();\n   registerGx2rDisplayListSymbols();\n   registerGx2rDrawSymbols();\n   registerGx2rMemorySymbols();\n   registerGx2rResourceSymbols();\n   registerGx2rShadersSymbols();\n   registerGx2rSurfaceSymbols();\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::gx2\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::gx2, \"gx2.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerApertureSymbols();\n   void registerCbPoolSymbols();\n   void registerClearSymbols();\n   void registerContextStateSymbols();\n   void registerCounterSymbols();\n   void registerDebugCaptureSymbols();\n   void registerDisplaySymbols();\n   void registerDisplayListSymbols();\n   void registerDrawSymbols();\n   void registerEventSymbols();\n   void registerFenceSymbols();\n   void registerFetchShadersSymbols();\n   void registerFormatSymbols();\n   void registerMemorySymbols();\n   void registerQuerySymbols();\n   void registerRegistersSymbols();\n   void registerSamplerSymbols();\n   void registerShadersSymbols();\n   void registerStateSymbols();\n   void registerSurfaceSymbols();\n   void registerTempSymbols();\n   void registerTessellationSymbols();\n   void registerTextureSymbols();\n   void registerGx2rBufferSymbols();\n   void registerGx2rDisplayListSymbols();\n   void registerGx2rDrawSymbols();\n   void registerGx2rMemorySymbols();\n   void registerGx2rResourceSymbols();\n   void registerGx2rShadersSymbols();\n   void registerGx2rSurfaceSymbols();\n};\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_addrlib.cpp",
    "content": "#include \"gx2_addrlib.h\"\n#include \"gx2_surface.h\"\n#include \"gx2_format.h\"\n\n#include <cstdlib>\n#include <cstring>\n#include <common/align.h>\n#include <libgpu/gpu_tiling.h>\n\nnamespace cafe::gx2::internal\n{\n\nbool\ngetSurfaceInfo(GX2Surface *surface,\n               uint32_t level,\n               ADDR_COMPUTE_SURFACE_INFO_OUTPUT *output)\n{\n   ADDR_E_RETURNCODE result = ADDR_OK;\n   auto hwFormat = static_cast<latte::SQ_DATA_FORMAT>(surface->format & 0x3f);\n   auto height = 1u;\n   auto width = std::max<uint32_t>(1u, surface->width >> level);\n   auto numSlices = 1u;\n\n   switch (surface->dim) {\n   case GX2SurfaceDim::Texture1D:\n      height = 1;\n      numSlices = 1;\n      break;\n   case GX2SurfaceDim::Texture2D:\n      height = std::max<uint32_t>(1u, surface->height >> level);\n      numSlices = 1;\n      break;\n   case GX2SurfaceDim::Texture3D:\n      height = std::max<uint32_t>(1u, surface->height >> level);\n      numSlices = std::max<uint32_t>(1u, surface->depth >> level);\n      break;\n   case GX2SurfaceDim::TextureCube:\n      height = std::max<uint32_t>(1u, surface->height >> level);\n      numSlices = std::max<uint32_t>(6u, surface->depth);\n      break;\n   case GX2SurfaceDim::Texture1DArray:\n      height = 1;\n      numSlices = surface->depth;\n      break;\n   case GX2SurfaceDim::Texture2DArray:\n      height = std::max<uint32_t>(1u, surface->height >> level);\n      numSlices = surface->depth;\n      break;\n   case GX2SurfaceDim::Texture2DMSAA:\n      height = std::max<uint32_t>(1u, surface->height >> level);\n      numSlices = 1;\n      break;\n   case GX2SurfaceDim::Texture2DMSAAArray:\n      height = std::max<uint32_t>(1u, surface->height >> level);\n      numSlices = surface->depth;\n      break;\n   }\n\n   std::memset(output, 0, sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT));\n   output->size = sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT);\n\n   if (surface->tileMode == GX2TileMode::LinearSpecial) {\n      auto numSamples = 1 << surface->aa;\n      auto elemSize = 1u;\n\n      if (hwFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && hwFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n         elemSize = 4;\n      }\n\n      output->bpp = GX2GetSurfaceFormatBitsPerElement(surface->format);\n      output->pixelBits = output->bpp;\n      output->baseAlign = 1;\n      output->pitchAlign = 1;\n      output->heightAlign = 1;\n      output->depthAlign = 1;\n\n      width = align_up(width, elemSize);\n      height = align_up(height, elemSize);\n\n      output->depth = numSlices;\n      output->pitch = std::max<uint32_t>(1u, width / elemSize);\n      output->height = std::max<uint32_t>(1u, height / elemSize);\n\n      output->pixelPitch = width;\n      output->pixelHeight = height;\n\n      output->surfSize = static_cast<uint64_t>(output->height) * output->pitch * numSamples * output->depth * (output->bpp / 8);\n\n      if (surface->dim == GX2SurfaceDim::Texture3D) {\n         output->sliceSize = static_cast<uint32_t>(output->surfSize);\n      } else {\n         output->sliceSize = static_cast<uint32_t>(output->surfSize / output->depth);\n      }\n\n      output->pitchTileMax = (output->pitch / 8) - 1;\n      output->heightTileMax = (output->height / 8) - 1;\n      output->sliceTileMax = (output->height * output->pitch / 64) - 1;\n      result = ADDR_OK;\n   } else {\n      ADDR_COMPUTE_SURFACE_INFO_INPUT input;\n      memset(&input, 0, sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT));\n      input.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT);\n      input.tileMode = static_cast<AddrTileMode>(surface->tileMode & 0xF);\n      input.format = static_cast<AddrFormat>(hwFormat);\n      input.bpp = GX2GetSurfaceFormatBitsPerElement(surface->format);\n      input.width = width;\n      input.height = height;\n      input.numSlices = numSlices;\n\n      input.numSamples = 1 << surface->aa;\n      input.numFrags = input.numSamples;\n\n      input.slice = 0;\n      input.mipLevel = level;\n\n      if (surface->dim == GX2SurfaceDim::TextureCube) {\n         input.flags.cube = 1;\n      }\n\n      if (surface->use & GX2SurfaceUse::DepthBuffer) {\n         input.flags.depth = 1;\n      }\n\n      if (surface->use & GX2SurfaceUse::ScanBuffer) {\n         input.flags.display = 1;\n      }\n\n      if (surface->dim == GX2SurfaceDim::Texture3D) {\n         input.flags.volume = 1;\n      }\n\n      input.flags.inputBaseMap = (level == 0);\n      result = AddrComputeSurfaceInfo(gpu::getAddrLibHandle(), &input, output);\n   }\n\n   return (result == ADDR_OK);\n}\n\nbool\ncopySurface(GX2Surface *surfaceSrc,\n            uint32_t srcLevel,\n            uint32_t srcSlice,\n            GX2Surface *surfaceDst,\n            uint32_t dstLevel,\n            uint32_t dstSlice,\n            uint8_t *dstImage,\n            uint8_t *dstMipmap)\n{\n   ADDR_COMPUTE_SURFACE_INFO_OUTPUT srcInfoOutput;\n   ADDR_COMPUTE_SURFACE_INFO_OUTPUT dstInfoOutput;\n   ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT srcAddrInput;\n   ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT dstAddrInput;\n   ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT srcSwizzleInput;\n   ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT srcSwizzleOutput;\n   ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT dstSwizzleInput;\n   ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT dstSwizzleOutput;\n\n   auto handle = gpu::getAddrLibHandle();\n\n   // Initialise addrlib input/output structures\n   std::memset(&srcAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT));\n   std::memset(&dstAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT));\n\n   std::memset(&srcSwizzleInput, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT));\n   std::memset(&srcSwizzleOutput, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT));\n\n   std::memset(&dstSwizzleInput, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT));\n   std::memset(&dstSwizzleOutput, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT));\n\n   srcAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT);\n   dstAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT);\n\n   srcSwizzleInput.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT);\n   srcSwizzleOutput.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT);\n\n   dstSwizzleInput.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT);\n   dstSwizzleOutput.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT);\n\n   // Setup src\n   auto bpp = GX2GetSurfaceFormatBitsPerElement(surfaceSrc->format);\n   getSurfaceInfo(surfaceSrc, srcLevel, &srcInfoOutput);\n   srcAddrInput.slice = srcSlice;\n   srcAddrInput.sample = 0;\n   srcAddrInput.bpp = bpp;\n   srcAddrInput.pitch = srcInfoOutput.pitch;\n   srcAddrInput.height = srcInfoOutput.height;\n   srcAddrInput.numSlices = std::max<uint32_t>(1u, srcInfoOutput.depth);\n   srcAddrInput.numSamples = 1 << surfaceSrc->aa;\n   srcAddrInput.tileMode = static_cast<AddrTileMode>(surfaceSrc->tileMode.value());\n   srcAddrInput.isDepth = !!(surfaceSrc->use & GX2SurfaceUse::DepthBuffer);\n   srcAddrInput.tileBase = 0;\n   srcAddrInput.compBits = 0;\n   srcAddrInput.numFrags = 0;\n\n   // Setup src swizzle\n   srcSwizzleInput.base256b = (surfaceSrc->swizzle >> 8) & 0xFF;\n   AddrExtractBankPipeSwizzle(handle, &srcSwizzleInput, &srcSwizzleOutput);\n\n   srcAddrInput.bankSwizzle = srcSwizzleOutput.bankSwizzle;\n   srcAddrInput.pipeSwizzle = srcSwizzleOutput.pipeSwizzle;\n\n   // Setup dst\n   getSurfaceInfo(surfaceDst, dstLevel, &dstInfoOutput);\n   dstAddrInput.slice = dstSlice;\n   dstAddrInput.sample = 0;\n   dstAddrInput.bpp = bpp;\n   dstAddrInput.pitch = dstInfoOutput.pitch;\n   dstAddrInput.height = dstInfoOutput.height;\n   dstAddrInput.numSlices = std::max<uint32_t>(1u, dstInfoOutput.depth);\n   dstAddrInput.numSamples = 1 << surfaceDst->aa;\n   dstAddrInput.tileMode = dstInfoOutput.tileMode;\n   dstAddrInput.isDepth = !!(surfaceDst->use & GX2SurfaceUse::DepthBuffer);\n   dstAddrInput.tileBase = 0;\n   dstAddrInput.compBits = 0;\n   dstAddrInput.numFrags = 0;\n\n   // Setup dst swizzle\n   dstSwizzleInput.base256b = (surfaceDst->swizzle >> 8) & 0xFF;\n   AddrExtractBankPipeSwizzle(handle, &dstSwizzleInput, &dstSwizzleOutput);\n\n   dstAddrInput.bankSwizzle = dstSwizzleOutput.bankSwizzle;\n   dstAddrInput.pipeSwizzle = dstSwizzleOutput.pipeSwizzle;\n\n   // Setup width\n   auto srcWidth = std::max<uint32_t>(1u, surfaceSrc->width >> srcLevel);\n   auto srcHeight = std::max<uint32_t>(1u, surfaceSrc->height >> srcLevel);\n   auto hwFormatSrc = static_cast<latte::SQ_DATA_FORMAT>(surfaceSrc->format & 0x3F);\n\n   if (hwFormatSrc >= latte::SQ_DATA_FORMAT::FMT_BC1 && hwFormatSrc <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      srcWidth = (srcWidth + 3) / 4;\n      srcHeight = (srcHeight + 3) / 4;\n   }\n\n   auto dstWidth = std::max<uint32_t>(1u, surfaceDst->width >> dstLevel);\n   auto dstHeight = std::max<uint32_t>(1u, surfaceDst->height >> dstLevel);\n   auto hwFormatDst = static_cast<latte::SQ_DATA_FORMAT>(surfaceDst->format & 0x3F);\n\n   if (hwFormatDst >= latte::SQ_DATA_FORMAT::FMT_BC1 && hwFormatDst <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      dstWidth = (dstWidth + 3) / 4;\n      dstHeight = (dstHeight + 3) / 4;\n   }\n\n   uint8_t *srcBasePtr = nullptr;\n   uint8_t *dstBasePtr = nullptr;\n\n   if (srcLevel == 0) {\n      srcBasePtr = surfaceSrc->image.get();\n   } else if (srcLevel == 1) {\n      srcBasePtr = surfaceSrc->mipmaps.get();\n   } else {\n      srcBasePtr = surfaceSrc->mipmaps.get() + surfaceSrc->mipLevelOffset[srcLevel - 1];\n   }\n\n   if (dstLevel == 0) {\n      dstBasePtr = dstImage ? dstImage : surfaceDst->image.get();\n   } else if (dstLevel == 1) {\n      dstBasePtr = dstMipmap ? dstMipmap : surfaceDst->mipmaps.get();\n   } else {\n      dstBasePtr = dstMipmap ? dstMipmap : surfaceDst->mipmaps.get();\n      dstBasePtr += surfaceDst->mipLevelOffset[dstLevel - 1];\n   }\n\n   // LinearSpecial is a special mode available through GX2 which forces that the\n   //  tiling mode be linear (possibly unaligned).  Because this also signals the\n   //  surface info calculation to pick its own tiling, we need to handle this\n   //  specially here...  Note that while ADDR_TM_LINEAR_GENERAL is a valid AMD\n   //  specified mode, it is not actually normally supported by the R600 hardware\n   //  which is the reason for LinearSpecial (which forces SW handling in GX2).\n\n   if (surfaceSrc->tileMode == GX2TileMode::LinearSpecial) {\n      srcAddrInput.tileMode = AddrTileMode::ADDR_TM_LINEAR_GENERAL;\n   }\n\n   if (surfaceDst->tileMode == GX2TileMode::LinearSpecial) {\n      dstAddrInput.tileMode = AddrTileMode::ADDR_TM_LINEAR_GENERAL;\n   }\n\n   return gpu::copySurfacePixels(\n      dstBasePtr, dstWidth, dstHeight, dstAddrInput,\n      srcBasePtr, srcWidth, srcHeight, srcAddrInput);\n}\n\nuint32_t\ngetSurfaceSliceSwizzle(GX2TileMode tileMode,\n                       uint32_t baseSwizzle,\n                       uint32_t slice)\n{\n   ADDR_COMPUTE_SLICESWIZZLE_INPUT input;\n   ADDR_COMPUTE_SLICESWIZZLE_OUTPUT output;\n   auto tileSwizzle = uint32_t { 0 };\n\n   std::memset(&input, 0, sizeof(ADDR_COMPUTE_SLICESWIZZLE_INPUT));\n   std::memset(&output, 0, sizeof(ADDR_COMPUTE_SLICESWIZZLE_OUTPUT));\n\n   input.size = sizeof(ADDR_COMPUTE_SLICESWIZZLE_INPUT);\n   output.size = sizeof(ADDR_COMPUTE_SLICESWIZZLE_OUTPUT);\n\n   if (tileMode >= GX2TileMode::Tiled2DThin1 && tileMode != GX2TileMode::LinearSpecial) {\n      input.tileMode = static_cast<AddrTileMode>(tileMode);\n      input.baseSwizzle = baseSwizzle;\n      input.slice = slice;\n      input.baseAddr = 0;\n\n      auto handle = gpu::getAddrLibHandle();\n      AddrComputeSliceSwizzle(handle, &input, &output);\n      tileSwizzle = output.tileSwizzle;\n   }\n\n   return tileSwizzle;\n}\n\nuint32_t\ncalcSliceSize(GX2Surface *surface,\n              ADDR_COMPUTE_SURFACE_INFO_OUTPUT *info)\n{\n   return info->pitch * info->height * (1 << surface->aa) * (info->bpp / 8);\n}\n\n} // namespace cafe::gx2::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_addrlib.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <addrlib/addrinterface.h>\n#include <cstdint>\n#include <libgpu/latte/latte_enum_sq.h>\n#include <vector>\n\nnamespace cafe::gx2\n{\n\nstruct GX2Surface;\n\nnamespace internal\n{\n\nbool\ngetSurfaceInfo(GX2Surface *surface,\n               uint32_t level,\n               ADDR_COMPUTE_SURFACE_INFO_OUTPUT *output);\n\nbool\ncopySurface(GX2Surface *surfaceSrc,\n            uint32_t srcLevel,\n            uint32_t srcSlice,\n            GX2Surface *surfaceDst,\n            uint32_t dstLevel,\n            uint32_t dstSlice,\n            uint8_t *dstImage = nullptr,\n            uint8_t *dstMipmap = nullptr);\n\nuint32_t\ngetSurfaceSliceSwizzle(GX2TileMode tileMode,\n                       uint32_t baseSwizzle,\n                       uint32_t slice);\n\nuint32_t\ncalcSliceSize(GX2Surface *surface,\n              ADDR_COMPUTE_SURFACE_INFO_OUTPUT *info);\n\n} // namespace internal\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_aperture.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_aperture.h\"\n#include \"gx2_format.h\"\n#include \"gx2_surface.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n#include \"cafe/libraries/tcl/tcl_aperture.h\"\n\n#include <libgpu/gpu7_tiling.h>\n#include <libgpu/gpu7_tiling_cpu.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace cafe::coreinit;\nusing namespace cafe::tcl;\n\nstruct ApertureInfo\n{\n   gpu7::tiling::RetileInfo retileInfo;\n   be2_val<virt_addr> tiledAddress;\n   be2_val<uint32_t> tiledSize;\n   be2_val<virt_addr> untiledAddress;\n   be2_val<uint32_t> untiledSize;\n   be2_val<uint32_t> slice;\n   be2_val<uint32_t> numSlices;\n};\n\nstruct StaticApertureData\n{\n   be2_array<ApertureInfo, 32> apertureInfo;\n};\n\nstatic virt_ptr<StaticApertureData>\nsApertureData = nullptr;\n\nvoid\nGX2AllocateTilingApertureEx(virt_ptr<GX2Surface> surface,\n                            uint32_t level,\n                            uint32_t depth,\n                            GX2EndianSwapMode endian,\n                            virt_ptr<GX2ApertureHandle> outHandle,\n                            virt_ptr<virt_addr> outAddress)\n{\n   auto surfaceDescription = gpu7::tiling::SurfaceDescription{ };\n   surfaceDescription.tileMode = static_cast<gpu7::tiling::TileMode>(surface->tileMode);\n   surfaceDescription.format = static_cast<gpu7::tiling::DataFormat>(surface->format & 0x3f);\n   surfaceDescription.bpp = GX2GetSurfaceFormatBitsPerElement(surface->format) / 8;\n   surfaceDescription.numSlices = surface->depth;\n   surfaceDescription.numSamples = 1 << surface->aa;\n   surfaceDescription.numFrags = 1 << surface->aa;\n   surfaceDescription.numLevels = surface->mipLevels;\n   surfaceDescription.pipeSwizzle = (surface->swizzle >> 8) & 1;\n   surfaceDescription.bankSwizzle = (surface->swizzle >> 9) & 3;\n   surfaceDescription.width = surface->width;\n   surfaceDescription.height = surface->height;\n   surfaceDescription.use = static_cast<gpu7::tiling::SurfaceUse>(surface->use);\n   surfaceDescription.dim = static_cast<gpu7::tiling::SurfaceDim>(surface->dim);\n\n   if (GX2SurfaceIsCompressed(surface->format)) {\n      surfaceDescription.width = (surfaceDescription.width + 3) / 4;\n      surfaceDescription.height = (surfaceDescription.height + 3) / 4;\n   }\n\n   auto surfaceInfo = gpu7::tiling::computeSurfaceInfo(surfaceDescription, level);\n\n   auto addr = virt_addr { 0 };\n   if (level == 0) {\n      addr = virt_cast<virt_addr>(surface->image);\n   } else if (level == 1) {\n      addr = virt_cast<virt_addr>(surface->mipmaps);\n   } else if (level > 1) {\n      addr = virt_cast<virt_addr>(surface->mipmaps) + surface->mipLevelOffset[level - 1];\n   }\n\n   // TODO: (addr + surfaceInfo.sliceSize * depth) is not actually correct for\n   //       all tile modes.\n\n   if (TCLAllocTilingAperture(OSEffectiveToPhysical(addr + surfaceInfo.sliceSize * depth),\n                              surfaceInfo.pitch,\n                              surfaceInfo.height,\n                              surfaceInfo.bpp,\n                              surfaceInfo.tileMode,\n                              endian,\n                              outHandle,\n                              outAddress) == TCLStatus::OK) {\n      auto &info = sApertureData->apertureInfo[*outHandle];\n      info.retileInfo = gpu7::tiling::computeRetileInfo(surfaceInfo);\n      info.tiledAddress = addr;\n      info.tiledSize = surfaceInfo.sliceSize;\n      info.untiledAddress = *outAddress;\n      info.untiledSize = surfaceInfo.sliceSize;\n      info.slice = depth;\n      info.numSlices = 1u;\n\n      gpu7::tiling::cpu::untile(info.retileInfo,\n                                virt_cast<uint8_t *>(info.untiledAddress).get(),\n                                virt_cast<uint8_t *>(info.tiledAddress).get(),\n                                info.slice,\n                                info.numSlices);\n   }\n}\n\nvoid\nGX2FreeTilingAperture(GX2ApertureHandle handle)\n{\n   auto &info = sApertureData->apertureInfo[handle];\n   if (info.tiledAddress && info.untiledAddress) {\n      gpu7::tiling::cpu::tile(info.retileInfo,\n                              virt_cast<uint8_t *>(info.untiledAddress).get(),\n                              virt_cast<uint8_t *>(info.tiledAddress).get(),\n                              info.slice,\n                              info.numSlices);\n   }\n\n   TCLFreeTilingAperture(handle);\n   info.tiledAddress = virt_addr { 0u };\n   info.tiledSize = 0u;\n   info.untiledAddress = virt_addr { 0u };\n   info.untiledSize = 0u;\n}\n\nnamespace internal\n{\n\nbool\ntranslateAperture(virt_addr &address,\n                  uint32_t &size)\n{\n   for (auto i = 0u; i < sApertureData->apertureInfo.size(); ++i) {\n      auto &info = sApertureData->apertureInfo[i];\n      if (address >= info.untiledAddress &&\n          address < info.untiledAddress + info.untiledSize) {\n         address = info.tiledAddress;\n         size = info.tiledSize;\n         return true;\n      }\n   }\n\n   return false;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerApertureSymbols()\n{\n   RegisterFunctionExport(GX2AllocateTilingApertureEx);\n   RegisterFunctionExport(GX2FreeTilingAperture);\n\n   RegisterDataInternal(sApertureData);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_aperture.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_aperture Tiling Aperture\n * \\ingroup gx2\n * @{\n */\n\nstruct GX2Surface;\n\nusing GX2ApertureHandle = uint32_t;\n\nvoid\nGX2AllocateTilingApertureEx(virt_ptr<GX2Surface> surface,\n                            uint32_t level,\n                            uint32_t depth,\n                            GX2EndianSwapMode endian,\n                            virt_ptr<GX2ApertureHandle> outHandle,\n                            virt_ptr<virt_addr> outAddress);\n\nvoid\nGX2FreeTilingAperture(GX2ApertureHandle handle);\n\nnamespace internal\n{\n\nbool\ntranslateAperture(virt_addr &address,\n                  uint32_t &size);\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_cbpool.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_debugcapture.h\"\n#include \"gx2_displaylist.h\"\n#include \"gx2_event.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_internal_pm4cap.h\"\n#include \"gx2_state.h\"\n#include \"gx2_query.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n#include \"cafe/libraries/coreinit/coreinit_time.h\"\n#include \"cafe/libraries/tcl/tcl_ring.h\"\n\n#include <common/decaf_assert.h>\n#include <libcpu/be2_struct.h>\n#include <libgpu/latte/latte_pm4.h>\n#include <libgpu/latte/latte_pm4_writer.h>\n\nnamespace cafe::gx2\n{\n\nconstexpr auto MinimumCommandBufferNumWords = uint32_t { 0x100 };\nconstexpr auto MaximumCommandBufferNumWords = uint32_t { 0x20000 };\n\nusing namespace cafe::coreinit;\nusing namespace cafe::tcl;\n\nstruct StaticCbPoolData\n{\n   be2_struct<internal::ActiveCommandBuffer> mainCoreCommandBuffer;\n   be2_array<internal::ActiveCommandBuffer, 3> activeCommandBuffer;\n   be2_val<BOOL> profilingWasEnabledBeforeUserDisplayList;\n   be2_val<virt_addr> gpuLastReadCommandBuffer;\n   be2_val<GX2Timestamp> lastSubmittedTimestamp;\n};\n\nstatic virt_ptr<StaticCbPoolData>\nsCbPoolData = nullptr;\n\nGX2Timestamp\nGX2GetRetiredTimeStamp()\n{\n   auto timestamp = StackObject<TCLTimestamp> { };\n   TCLReadTimestamp(TCLTimestampID::CPRetired, timestamp);\n   return *timestamp;\n}\n\nGX2Timestamp\nGX2GetLastSubmittedTimeStamp()\n{\n   return sCbPoolData->lastSubmittedTimestamp;\n}\n\n\n/**\n * Submit a user timestamp to the GPU.\n */\nvoid\nGX2SubmitUserTimeStamp(virt_ptr<GX2Timestamp> dst,\n                       GX2Timestamp timestamp,\n                       GX2PipeEvent type,\n                       BOOL triggerInterrupt)\n{\n   using namespace latte;\n   using namespace latte::pm4;\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(dst));\n   auto dataLo = static_cast<uint32_t>(timestamp & 0xFFFFFFFFu);\n   auto dataHi = static_cast<uint32_t>(timestamp >> 32);\n\n   switch (type) {\n   case GX2PipeEvent::Top:\n   {\n      internal::writePM4(MemWrite {\n         MW_ADDR_LO::get(0)\n            .ADDR_LO(addr >> 2)\n            .ENDIAN_SWAP(CB_ENDIAN::SWAP_8IN32),\n         MW_ADDR_HI::get(0)\n            .CNTR_SEL(MW_WRITE_DATA),\n         dataLo, dataHi\n      });\n\n      if (triggerInterrupt) {\n         internal::writeType0(Register::CP_INT_STATUS,\n                              CP_INT_STATUS::get(0)\n                              .IB1_INT_STAT(true)\n                              .value);\n      }\n      break;\n   }\n   case GX2PipeEvent::Bottom:\n   case GX2PipeEvent::BottomAfterFlush:\n   {\n      internal::writePM4(EventWriteEOP {\n         VGT_EVENT_INITIATOR::get(0)\n            .EVENT_TYPE(type == GX2PipeEvent::BottomAfterFlush ?\n                           VGT_EVENT_TYPE::CACHE_FLUSH_AND_INV_TS_EVENT :\n                           VGT_EVENT_TYPE::BOTTOM_OF_PIPE_TS)\n            .EVENT_INDEX(VGT_EVENT_INDEX::TS),\n         EW_ADDR_LO::get(0)\n            .ADDR_LO(addr >> 2)\n            .ENDIAN_SWAP(CB_ENDIAN::SWAP_8IN32),\n         EWP_ADDR_HI::get(0)\n            .DATA_SEL(EWP_DATA_64)\n            .INT_SEL(triggerInterrupt ?\n                        EWP_INT_SEL::EWP_INT_WRITE_CONFIRM :\n                        EWP_INT_SEL::EWP_INT_NONE),\n            dataLo, dataHi\n      });\n      break;\n   }\n   default:\n      decaf_abort(fmt::format(\"Unexpected GX2SubmitUserTimestamp type {}\",\n                              type));\n   }\n}\n\n\n/**\n * Wait for retired timestamp.\n */\nBOOL\nGX2WaitTimeStamp(GX2Timestamp timestamp)\n{\n   auto timeoutTicks = coreinit::internal::msToTicks(GX2GetGPUTimeout());\n   if (TCLWaitTimestamp(TCLTimestampID::CPRetired, timestamp, timeoutTicks) == TCLStatus::OK) {\n      return TRUE;\n   }\n\n   // TODO: Set GPU hang state\n   return FALSE;\n}\n\n\nnamespace internal\n{\n\nvoid\nflushCommandBuffer(uint32_t requiredNumWords,\n                   BOOL a2);\n\nvoid\nallocateCommandBuffer(uint32_t requiredNumWords);\n\nvoid\npadCommandBuffer(virt_ptr<ActiveCommandBuffer> cb);\n\n\n/**\n * Get the active command buffer for the current core.\n */\nvirt_ptr<ActiveCommandBuffer>\ngetActiveCommandBuffer()\n{\n   return virt_addrof(sCbPoolData->activeCommandBuffer[cpu::this_core::id()]);\n}\n\n\n/**\n * Get an active command buffer with space to write numWords.\n */\nvirt_ptr<ActiveCommandBuffer>\ngetWriteCommandBuffer(uint32_t numWords)\n{\n   auto cb = getActiveCommandBuffer();\n   if (cb->bufferPosWords + numWords > cb->bufferSizeWords) {\n      flushCommandBuffer(numWords, TRUE);\n   }\n\n   cb->writeGatherPtr = cb->buffer + cb->bufferPosWords;\n   cb->bufferPosWords += numWords;\n   cb->cmdSizeTarget = numWords;\n   cb->cmdSize = 0u;\n   return cb;\n}\n\n\n/**\n * Initialise command buffer pool.\n */\nvoid\ninitialiseCommandBufferPool(virt_ptr<void> base,\n                            uint32_t size)\n{\n   auto &mainCoreCb = sCbPoolData->mainCoreCommandBuffer;\n   mainCoreCb.isUserBuffer = FALSE;\n   mainCoreCb.cbPoolBase = virt_cast<uint32_t *>(base);\n   mainCoreCb.cbPoolNumWords = size / 4;\n   mainCoreCb.buffer = virt_cast<uint32_t *>(base);\n   mainCoreCb.bufferPosWords = 0u;\n\n   sCbPoolData->activeCommandBuffer[getMainCoreId()] = mainCoreCb;\n   sCbPoolData->gpuLastReadCommandBuffer =\n      virt_cast<virt_addr>(mainCoreCb.cbPoolBase + mainCoreCb.cbPoolNumWords);\n\n   // Allocate initial command buffer\n   allocateCommandBuffer(MinimumCommandBufferNumWords);\n}\n\n\n/**\n * Initialise a command buffer.\n */\nvoid\ninitialiseCommandBuffer(virt_ptr<ActiveCommandBuffer> cb,\n                        virt_ptr<uint32_t> buffer,\n                        uint32_t bufferSizeWords,\n                        BOOL isUserBuffer)\n{\n   // Normally this would be set to the write gather address, but we are faking\n   // write gathering...!\n   cb->writeGatherPtr = buffer;\n\n   cb->isUserBuffer = isUserBuffer;\n   cb->buffer = buffer;\n   cb->bufferPosWords = 0u;\n   cb->bufferSizeWords = bufferSizeWords;\n   cb->cmdSize = 0u;\n   cb->cmdSizeTarget = 0u;\n}\n\n\n/**\n * Allocate a command buffer with space for requiredNumWords.\n */\nvoid\nallocateCommandBuffer(uint32_t requiredNumWords)\n{\n   auto coreId = cpu::this_core::id();\n   auto cb = virt_addrof(sCbPoolData->activeCommandBuffer[cpu::this_core::id()]);\n   decaf_check(coreId == getMainCoreId());\n   decaf_check(requiredNumWords < MaximumCommandBufferNumWords);\n   requiredNumWords = std::max(requiredNumWords, MinimumCommandBufferNumWords);\n\n   // Ring buffer between write pointer and read pointer\n   auto writePosition = virt_cast<virt_addr>(cb->buffer + cb->bufferPosWords);\n   auto readPosition = virt_addr { sCbPoolData->gpuLastReadCommandBuffer };\n   auto ringBufferStart = virt_cast<virt_addr>(cb->cbPoolBase);\n   auto ringBufferEnd = virt_cast<virt_addr>(cb->cbPoolBase + cb->cbPoolNumWords);\n   auto freeBytes = ptrdiff_t { 0 };\n   auto requiredBytes = requiredNumWords * 4;\n\n#ifdef GX2_ENABLE_CBPOOL_TIMEOUT\n   auto allocateStartTime = OSGetSystemTime();\n   auto gpuTimeoutTicks = coreinit::internal::msToTicks(GX2GetGPUTimeout());\n#endif\n\n   while (freeBytes < requiredBytes) {\n      auto retiredTimestamp = GX2GetRetiredTimeStamp();\n\n      if (writePosition < readPosition) {\n         freeBytes = readPosition - writePosition;\n      } else {\n         // When writePosition == readPosition, we check the retired and\n         // submitted timestamp to decide if the ringbuffer is empty or full.\n         if (writePosition == readPosition &&\n             GX2GetLastSubmittedTimeStamp() < retiredTimestamp) {\n            // Ringbuffer is full\n            freeBytes = 0;\n         } else if (ringBufferEnd - writePosition >= requiredBytes) {\n            freeBytes = ringBufferEnd - writePosition;\n         } else {\n            // Not enough space at end of the ringbuffer, try from the start instead\n            freeBytes = readPosition - ringBufferStart;\n            writePosition = ringBufferStart;\n         }\n      }\n\n      if (freeBytes < requiredBytes) {\n#ifdef GX2_ENABLE_CBPOOL_TIMEOUT\n         // Check if we have hit timeout waiting for gpu\n         if (OSGetSystemTime() - allocateStartTime >= gpuTimeoutTicks) {\n            // TODO: Handle GPU timeout\n            break;\n         }\n#endif\n\n         // Wait for next retired buffer to try get some more free pool space\n         GX2WaitTimeStamp(retiredTimestamp + 1);\n      }\n   }\n\n   decaf_check(freeBytes >= requiredBytes);\n   auto allocNumWords = std::min<uint32_t>(MaximumCommandBufferNumWords,\n                                           static_cast<uint32_t>(freeBytes / 4));\n   initialiseCommandBuffer(cb,\n                           virt_cast<uint32_t *>(writePosition),\n                           allocNumWords,\n                           FALSE);\n}\n\n\n/**\n * Begin a user command buffer (aka a display list).\n */\nvoid\nbeginUserCommandBuffer(virt_ptr<uint32_t> displayList,\n                       uint32_t bytes,\n                       BOOL profilingEnabled)\n{\n   auto coreId = cpu::this_core::id();\n   auto cb = virt_addrof(sCbPoolData->activeCommandBuffer[cpu::this_core::id()]);\n   decaf_check(!cb->isUserBuffer);\n\n   if (coreId == getMainCoreId()) {\n      padCommandBuffer(cb);\n      sCbPoolData->mainCoreCommandBuffer = *cb;\n      sCbPoolData->profilingWasEnabledBeforeUserDisplayList = getProfilingEnabled();\n   }\n\n   setProfilingEnabled(profilingEnabled);\n   initialiseCommandBuffer(cb, displayList, bytes / 4, TRUE);\n}\n\n\n/**\n * End a user command buffer.\n */\nuint32_t\nendUserCommandBuffer(virt_ptr<uint32_t> displayList)\n{\n   auto coreId = cpu::this_core::id();\n   auto cb = virt_addrof(sCbPoolData->activeCommandBuffer[cpu::this_core::id()]);\n   decaf_check(cb->isUserBuffer);\n\n   // Pad the command buffer so it is ready for submission\n   padCommandBuffer(cb);\n   auto bufferSize = cb->bufferPosWords * 4;\n\n   if (coreId == getMainCoreId()) {\n      // Restore the main command buffer\n      *cb = sCbPoolData->mainCoreCommandBuffer;\n      setProfilingEnabled(sCbPoolData->profilingWasEnabledBeforeUserDisplayList);\n   } else {\n      // Clear the user display list\n      cb->isUserBuffer = FALSE;\n      cb->bufferPosWords = 0u;\n      cb->bufferSizeWords = 0u;\n      cb->buffer = nullptr;\n   }\n\n   return bufferSize;\n}\n\n\n/**\n * Pad a command buffer with NOPs to align to 8 words.\n */\nvoid\npadCommandBuffer(virt_ptr<ActiveCommandBuffer> cb)\n{\n   auto alignedBufferPos = align_up(cb->bufferPosWords, 8);\n   if (alignedBufferPos != cb->bufferPosWords) {\n      auto padNumWords = alignedBufferPos - cb->bufferPosWords;\n      cb = getWriteCommandBuffer(padNumWords);\n\n      for (auto i = 0u; i < padNumWords; ++i) {\n         *cb->writeGatherPtr = 0x80000000u;\n      }\n   }\n}\n\n\n/**\n * Queue a command buffer in the gpu ring buffer.\n */\nvoid\nqueueCommandBuffer(virt_ptr<uint32_t> cbBase,\n                   uint32_t cbSize,\n                   virt_ptr<virt_addr> gpuLastReadPointer,\n                   BOOL writeConfirmTimestamp)\n{\n   auto submitCommand = StackArray<uint32_t, 9> { };\n   auto submitCommandNumWords = uint32_t { 0u };\n\n   decaf_check(cbBase);\n   decaf_check(cbSize);\n\n   auto indirectBufferCall =\n      latte::pm4::IndirectBufferCallPriv {\n         OSEffectiveToPhysical(virt_cast<virt_addr>(cbBase)),\n         cbSize\n   };\n   writePM4(submitCommand, submitCommandNumWords, indirectBufferCall);\n\n   if (gpuLastReadPointer) {\n      auto memWrite =\n         latte::pm4::MemWrite {\n            latte::pm4::MW_ADDR_LO::get(0)\n               .ADDR_LO(OSEffectiveToPhysical(virt_cast<virt_addr>(gpuLastReadPointer)) >> 2)\n               .ENDIAN_SWAP(latte::CB_ENDIAN::SWAP_8IN32),\n            latte::pm4::MW_ADDR_HI::get(0)\n               .CNTR_SEL(latte::pm4::MW_WRITE_DATA)\n               .DATA32(true),\n            static_cast<uint32_t>(virt_cast<virt_addr>(cbBase + cbSize)),\n            0u\n      };\n      writePM4(submitCommand, submitCommandNumWords, memWrite);\n   }\n\n   if (getProfileMode() & GX2ProfileMode::SkipExecuteCommandBuffers) {\n      // Change IndirectBufferCallPriv to a NOP\n      submitCommand[0] =\n         latte::pm4::HeaderType3::get(0)\n         .type(latte::pm4::PacketType::Type3)\n         .opcode(latte::pm4::IT_OPCODE::NOP)\n         .size(2)\n         .value;\n   }\n\n   // TODO: Call pm4 capture flush command buffer\n\n   auto submitFlags = StackObject<TCLSubmitFlags> { };\n   *submitFlags = TCLSubmitFlags::UpdateTimestamp;\n   if (!writeConfirmTimestamp) {\n      *submitFlags |= TCLSubmitFlags::NoWriteConfirmTimestamp;\n   }\n\n   captureCommandBuffer(submitCommand,\n                        submitCommandNumWords);\n\n   if (!debugCaptureEnabled()) {\n      TCLSubmitToRing(submitCommand,\n                      submitCommandNumWords,\n                      submitFlags,\n                      virt_addrof(sCbPoolData->lastSubmittedTimestamp));\n   } else {\n      debugCaptureSubmit(submitCommand,\n                         submitCommandNumWords,\n                         submitFlags,\n                         virt_addrof(sCbPoolData->lastSubmittedTimestamp));\n   }\n}\n\n\n/**\n * Flush the currently active command buffer.\n */\nvoid\nflushCommandBuffer(uint32_t requiredNumWords,\n                   BOOL writeConfirmTimestamp)\n{\n   auto coreId = cpu::this_core::id();\n   auto cb = virt_addrof(sCbPoolData->activeCommandBuffer[cpu::this_core::id()]);\n\n   if (cb->isUserBuffer) {\n      auto usedSize = GX2EndDisplayList(cb->buffer);\n      auto newSize = 4 * requiredNumWords;\n      auto newDisplayList = displayListOverrun(cb->buffer, usedSize, newSize);\n\n      GX2BeginDisplayListEx(newDisplayList.first,\n                            newDisplayList.second,\n                            getProfilingEnabled());\n   } else {\n      decaf_check(coreId == getMainCoreId());\n\n      if (cb->bufferPosWords != 0) {\n         padCommandBuffer(cb);\n         queueCommandBuffer(cb->buffer,\n                            cb->bufferPosWords,\n                            virt_addrof(sCbPoolData->gpuLastReadCommandBuffer),\n                            writeConfirmTimestamp);\n      }\n\n      allocateCommandBuffer(requiredNumWords);\n      decaf_check(requiredNumWords <= cb->bufferSizeWords - cb->bufferPosWords);\n   }\n}\n\n\n/**\n * Inform debug capture about the pointers that we send over PM4 for commands\n * such as EVENT_WRITE_EOP.\n */\nvoid\ndebugCaptureCbPoolPointers()\n{\n   debugCaptureAlloc(virt_addrof(sCbPoolData->gpuLastReadCommandBuffer),\n                     sizeof(sCbPoolData->gpuLastReadCommandBuffer), 8);\n\n   debugCaptureAlloc(virt_addrof(sCbPoolData->lastSubmittedTimestamp),\n                     sizeof(sCbPoolData->lastSubmittedTimestamp), 8);\n\n   debugCaptureInvalidate(virt_addrof(sCbPoolData->gpuLastReadCommandBuffer),\n                          sizeof(sCbPoolData->gpuLastReadCommandBuffer));\n\n   debugCaptureInvalidate(virt_addrof(sCbPoolData->lastSubmittedTimestamp),\n                          sizeof(sCbPoolData->lastSubmittedTimestamp));\n}\n\nvoid\ndebugCaptureCbPoolPointersFree()\n{\n   debugCaptureFree(virt_addrof(sCbPoolData->gpuLastReadCommandBuffer));\n   debugCaptureFree(virt_addrof(sCbPoolData->lastSubmittedTimestamp));\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerCbPoolSymbols()\n{\n   RegisterFunctionExport(GX2GetLastSubmittedTimeStamp);\n   RegisterFunctionExport(GX2GetRetiredTimeStamp);\n   RegisterFunctionExport(GX2SubmitUserTimeStamp);\n   RegisterFunctionExport(GX2WaitTimeStamp);\n\n   RegisterDataInternal(sCbPoolData);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_cbpool.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include \"gx2_internal_writegatherptr.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libgpu/latte/latte_pm4_sizer.h>\n#include <libgpu/latte/latte_pm4_writer.h>\n\nnamespace cafe::gx2\n{\n\nusing GX2Timestamp = uint64_t;\n\nGX2Timestamp\nGX2GetRetiredTimeStamp();\n\nGX2Timestamp\nGX2GetLastSubmittedTimeStamp();\n\nvoid\nGX2SubmitUserTimeStamp(virt_ptr<GX2Timestamp> dst,\n                       GX2Timestamp timestamp,\n                       GX2PipeEvent type,\n                       BOOL triggerInterrupt);\n\nBOOL\nGX2WaitTimeStamp(GX2Timestamp time);\n\nnamespace internal\n{\n\nstruct ActiveCommandBuffer\n{\n   //! Write gather address for writing to buffer.\n   be2_write_gather_ptr<uint32_t> writeGatherPtr;\n\n   //! Buffer for commands.\n   be2_virt_ptr<uint32_t> buffer;\n\n   //! Write position of buffer in words.\n   be2_val<uint32_t> bufferPosWords;\n\n   //! Size of buffer in words.\n   be2_val<uint32_t> bufferSizeWords;\n\n   //! Size of the current writing command.\n   be2_val<uint32_t> cmdSize;\n\n   //! Maximum size of the current writing command.\n   be2_val<uint32_t> cmdSizeTarget;\n\n   be2_val<BOOL> isUserBuffer;\n   be2_virt_ptr<uint32_t> cbPoolBase;\n   be2_val<uint32_t> cbPoolNumWords;\n   be2_val<uint32_t> cbPoolNumCommandBuffers;\n\n   UNKNOWN(0x4);\n};\n\nvoid\ninitialiseCommandBufferPool(virt_ptr<void> base,\n                            uint32_t size);\n\nvirt_ptr<ActiveCommandBuffer>\ngetActiveCommandBuffer();\n\nvirt_ptr<ActiveCommandBuffer>\ngetWriteCommandBuffer(uint32_t numWords);\n\nvoid\nbeginUserCommandBuffer(virt_ptr<uint32_t> displayList,\n                       uint32_t bytes,\n                       BOOL profilingEnabled);\n\nuint32_t\nendUserCommandBuffer(virt_ptr<uint32_t> displayList);\n\nvoid\nflushCommandBuffer(uint32_t requiredNumWords,\n                   BOOL writeConfirmTimestamp);\n\nvoid\nqueueCommandBuffer(virt_ptr<uint32_t> cbBase,\n                   uint32_t cbSize,\n                   virt_ptr<virt_addr> gpuLastReadPointer,\n                   BOOL writeConfirmTimestamp);\n\nvoid\ndebugCaptureCbPoolPointers();\n\nvoid\ndebugCaptureCbPoolPointersFree();\n\n/**\n * Write the pm4 serialiser to the given buffer\n */\ntemplate<typename Type>\ninline void\nwritePM4(virt_ptr<uint32_t> buffer,\n         uint32_t &bufferPosWords,\n         const Type &command)\n{\n   // Remove const for the .serialise function\n   auto &cmd = const_cast<Type &>(command);\n\n   // Calculate the total size this object will be\n   latte::pm4::PacketSizer sizer;\n   cmd.serialise(sizer);\n   auto totalSize = sizer.getSize() + 1;\n\n   // Serialize the packet to the given buffer\n   auto writer = latte::pm4::PacketWriter {\n      buffer.getRawPointer(),\n      bufferPosWords,\n      Type::Opcode,\n      totalSize\n   };\n   cmd.serialise(writer);\n}\n\n\n/**\n * Write a PM4 command to the active command buffer for the current core.\n */\ntemplate<typename Type>\ninline void\nwritePM4(const Type &command)\n{\n   // Remove const for the .serialise function.\n   auto &cmd = const_cast<Type &>(command);\n\n   // Calculate the total size this command will be.\n   latte::pm4::PacketSizer sizer;\n   cmd.serialise(sizer);\n\n   // Allocate a command buffer.\n   auto totalSize = sizer.getSize() + 1;\n   auto cmdSize = uint32_t { 0 };\n   auto cb = getWriteCommandBuffer(totalSize);\n\n   // Serialize the packet to the command buffer.\n   auto writer = latte::pm4::PacketWriter {\n      cb->writeGatherPtr.get().getRawPointer(),\n      cmdSize,\n      Type::Opcode,\n      totalSize\n   };\n   cmd.serialise(writer);\n   cb->cmdSize = cmdSize;\n\n   // Verify the size was as expected.\n   decaf_check(cb->cmdSize == cb->cmdSizeTarget);\n}\n\n\n/**\n * Write a PM4 command to the provided command buffer.\n */\ntemplate<typename Type>\nvoid\nwritePM4(uint32_t *buffer,\n         uint32_t &bufferPosWords,\n         const Type &command)\n{\n   // Remove const for the .serialise function\n   auto &cmd = const_cast<Type &>(command);\n\n   // Calculate the total size this object will be\n   latte::pm4::PacketSizer sizer;\n   cmd.serialise(sizer);\n   auto totalSize = sizer.getSize() + 1;\n\n   // Serialize the packet to the given buffer\n   auto writer = latte::pm4::PacketWriter {\n      buffer,\n      bufferPosWords,\n      Type::Opcode,\n      totalSize\n   };\n   cmd.serialise(writer);\n}\n\n\ninline void\nwriteType0(latte::Register baseIndex,\n           gsl::span<uint32_t> values)\n{\n   auto numValues = static_cast<uint32_t>(values.size());\n   auto cb = getWriteCommandBuffer(numValues + 1);\n   auto header = latte::pm4::HeaderType0::get(0)\n      .type(latte::pm4::PacketType::Type0)\n      .baseIndex(baseIndex / 4)\n      .count(numValues - 1);\n\n   // Write data to command buffer\n   *cb->writeGatherPtr = header.value;\n   for (auto value : values) {\n      *cb->writeGatherPtr = value;\n   }\n\n   // Verify the size was as expected.\n   cb->cmdSize = numValues + 1;\n   decaf_check(cb->cmdSize == cb->cmdSizeTarget);\n}\n\ninline void\nwriteType0(latte::Register id,\n           uint32_t value)\n{\n   writeType0(id, { &value, 1 });\n}\n\n} // namespace internal\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_clear.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_clear.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_debugcapture.h\"\n#include \"gx2_surface.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <common/bit_cast.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace cafe::coreinit;\n\nvoid\nGX2ClearColor(virt_ptr<GX2ColorBuffer> colorBuffer,\n              float red,\n              float green,\n              float blue,\n              float alpha)\n{\n   internal::debugCaptureTagGroup(GX2DebugTag::ClearColor,\n                                  \"{}, {:.2f}, {:.2f}, {:.2f}, {:.2f}\",\n                                  colorBuffer, red, green, blue, alpha);\n\n   auto address = OSEffectiveToPhysical(virt_cast<virt_addr>(colorBuffer->surface.image));\n   auto cb_color_frag = latte::CB_COLORN_FRAG::get(0);\n   auto cb_color_base = latte::CB_COLORN_BASE::get(0)\n      .BASE_256B(address >> 8);\n\n   if (colorBuffer->surface.aa != 0) {\n      auto aaAddress = OSEffectiveToPhysical(virt_cast<virt_addr>(colorBuffer->aaBuffer));\n      cb_color_frag = cb_color_frag\n         .BASE_256B(aaAddress >> 8);\n   }\n\n   GX2InitColorBufferRegs(colorBuffer);\n\n   internal::writePM4(latte::pm4::DecafClearColor {\n      red, green, blue, alpha,\n      cb_color_base,\n      cb_color_frag,\n      colorBuffer->regs.cb_color_size,\n      colorBuffer->regs.cb_color_info,\n      colorBuffer->regs.cb_color_view,\n      colorBuffer->regs.cb_color_mask\n   });\n\n   internal::debugCaptureTagGroup(GX2DebugTag::ClearColor,\n                                  \"{}, {:.2f}, {:.2f}, {:.2f}, {:.2f}\",\n                                  colorBuffer, red, green, blue, alpha);\n}\n\nvoid\nDecafClearDepthStencil(virt_ptr<GX2DepthBuffer> depthBuffer,\n                       GX2ClearFlags clearFlags)\n{\n   auto addrImage = OSEffectiveToPhysical(virt_cast<virt_addr>(depthBuffer->surface.image));\n   auto addrHiZ = OSEffectiveToPhysical(virt_cast<virt_addr>(depthBuffer->hiZPtr));\n\n   auto db_depth_base = latte::DB_DEPTH_BASE::get(0)\n      .BASE_256B(addrImage >> 8);\n\n   auto db_depth_htile_data_base = latte::DB_DEPTH_HTILE_DATA_BASE::get(0)\n      .BASE_256B(addrHiZ >> 8);\n\n   GX2InitDepthBufferRegs(depthBuffer);\n\n   internal::writePM4(latte::pm4::DecafClearDepthStencil {\n      clearFlags,\n      db_depth_base,\n      db_depth_htile_data_base,\n      depthBuffer->regs.db_depth_info,\n      depthBuffer->regs.db_depth_size,\n      depthBuffer->regs.db_depth_view,\n   });\n}\n\nvoid\nGX2ClearDepthStencilEx(virt_ptr<GX2DepthBuffer> depthBuffer,\n                       float depth, uint8_t stencil,\n                       GX2ClearFlags clearFlags)\n{\n   internal::debugCaptureTagGroup(GX2DebugTag::ClearDepthStencil,\n                                  \"{}, {:.2f}, {}, {}\",\n                                  depthBuffer, depth, stencil, clearFlags);\n\n   uint32_t values[] = {\n      stencil,\n      bit_cast<uint32_t>(depth)\n   };\n\n   internal::writePM4(latte::pm4::SetContextRegs {\n      latte::Register::DB_STENCIL_CLEAR,\n      gsl::make_span(values)\n   });\n   DecafClearDepthStencil(depthBuffer, clearFlags);\n\n   internal::debugCaptureTagGroup(GX2DebugTag::ClearDepthStencil,\n                                  \"{}, {:.2f}, {}, {}\",\n                                  depthBuffer, depth, stencil, clearFlags);\n}\n\nvoid\nGX2ClearBuffersEx(virt_ptr<GX2ColorBuffer> colorBuffer,\n                  virt_ptr<GX2DepthBuffer> depthBuffer,\n                  float red, float green, float blue, float alpha,\n                  float depth,\n                  uint8_t stencil,\n                  GX2ClearFlags clearFlags)\n{\n   internal::debugCaptureTagGroup(\n      GX2DebugTag::ClearBuffers,\n      \"{}, {}, {:.2f}, {:.2f}, {:.2f}, {:.2f}, {:.2f}, {}, {}\",\n      colorBuffer, depthBuffer,\n      red, green, blue, alpha,\n      depth, stencil, clearFlags);\n\n   GX2ClearColor(colorBuffer, red, green, blue, alpha);\n   GX2ClearDepthStencilEx(depthBuffer, depth, stencil, clearFlags);\n\n   internal::debugCaptureTagGroup(\n      GX2DebugTag::ClearBuffers,\n      \"{}, {}, {:.2f}, {:.2f}, {:.2f}, {:.2f}, {:.2f}, {}, {}\",\n      colorBuffer, depthBuffer,\n      red, green, blue, alpha,\n      depth, stencil, clearFlags);\n}\n\nvoid\nGX2SetClearDepth(virt_ptr<GX2DepthBuffer> depthBuffer,\n                 float depth)\n{\n   depthBuffer->depthClear = depth;\n   internal::writePM4(latte::pm4::SetContextReg {\n      latte::Register::DB_DEPTH_CLEAR,\n      bit_cast<uint32_t>(depth)\n   });\n}\n\nvoid\nGX2SetClearStencil(virt_ptr<GX2DepthBuffer> depthBuffer,\n                   uint8_t stencil)\n{\n   depthBuffer->stencilClear = stencil;\n   internal::writePM4(latte::pm4::SetContextReg {\n      latte::Register::DB_STENCIL_CLEAR,\n      stencil\n   });\n}\n\n\nvoid\nGX2SetClearDepthStencil(virt_ptr<GX2DepthBuffer> depthBuffer,\n                        float depth,\n                        uint8_t stencil)\n{\n   depthBuffer->depthClear = depth;\n   depthBuffer->stencilClear = stencil;\n\n   uint32_t values[] = {\n      stencil,\n      bit_cast<uint32_t>(depth)\n   };\n\n   internal::writePM4(latte::pm4::SetContextRegs {\n      latte::Register::DB_STENCIL_CLEAR,\n      gsl::make_span(values)\n   });\n}\n\nvoid\nLibrary::registerClearSymbols()\n{\n   RegisterFunctionExport(GX2ClearColor);\n   RegisterFunctionExport(GX2ClearDepthStencilEx);\n   RegisterFunctionExport(GX2ClearBuffersEx);\n   RegisterFunctionExport(GX2SetClearDepth);\n   RegisterFunctionExport(GX2SetClearStencil);\n   RegisterFunctionExport(GX2SetClearDepthStencil);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_clear.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2 Clear Functions\n * \\ingroup gx2\n * @{\n */\n\nstruct GX2ColorBuffer;\nstruct GX2DepthBuffer;\n\nvoid\nGX2ClearColor(virt_ptr<GX2ColorBuffer> colorBuffer,\n              float red,\n              float green,\n              float blue,\n              float alpha);\n\nvoid\nGX2ClearDepthStencilEx(virt_ptr<GX2DepthBuffer> depthBuffer,\n                       float depth,\n                       uint8_t stencil,\n                       GX2ClearFlags clearMode);\n\nvoid\nGX2ClearBuffersEx(virt_ptr<GX2ColorBuffer> colorBuffer,\n                  virt_ptr<GX2DepthBuffer> depthBuffer,\n                  float red,\n                  float green,\n                  float blue,\n                  float alpha,\n                  float depth,\n                  uint8_t stencil,\n                  GX2ClearFlags clearMode);\n\nvoid\nGX2SetClearDepth(virt_ptr<GX2DepthBuffer> depthBuffer,\n                 float depth);\n\nvoid\nGX2SetClearStencil(virt_ptr<GX2DepthBuffer> depthBuffer,\n                   uint8_t stencil);\n\nvoid\nGX2SetClearDepthStencil(virt_ptr<GX2DepthBuffer> depthBuffer,\n                        float depth,\n                        uint8_t stencil);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_contextstate.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_contextstate.h\"\n#include \"gx2_debugcapture.h\"\n#include \"gx2_draw.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_memory.h\"\n#include \"gx2_registers.h\"\n#include \"gx2_shaders.h\"\n#include \"gx2_state.h\"\n#include \"gx2_tessellation.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <libgpu/latte/latte_pm4_commands.h>\n#include <utility>\n\nnamespace cafe::gx2\n{\n\nusing namespace cafe::coreinit;\nusing namespace latte::pm4;\n\nstatic std::pair<uint32_t, uint32_t>\nConfigRegisterRange[] =\n{\n   { 0x300, 6 },\n   { 0x900, 0x48 },\n   { 0x980, 0x48 },\n   { 0xA00, 0x48 },\n   { 0x310, 0xC },\n   { 0x542, 1 },\n   { 0x235, 1 },\n   { 0x232, 2 },\n   { 0x23A, 1 },\n   { 0x256, 1 },\n   { 0x60C, 1 },\n   { 0x5C5, 1 },\n   { 0x2C8, 1 },\n   { 0x363, 1 },\n   { 0x404, 2 },\n};\n\nstatic std::pair<uint32_t, uint32_t>\nContextRegisterRange[] =\n{\n   { 0, 2 },\n   { 3, 3 },\n   { 0xA, 4 },\n   { 0x10, 0x38 },\n   { 0x50, 0x34 },\n   { 0x8E, 4 },\n   { 0x94, 0x40 },\n   { 0x100, 9 },\n   { 0x10C, 3 },\n   { 0x10F, 0x60 },\n   { 0x185, 0xA },\n   { 0x191, 0x27 },\n   { 0x1E0, 9 },\n   { 0x200, 1 },\n   { 0x202, 7 },\n   { 0xE0, 0x20 },\n   { 0x210, 0x29 },\n   { 0x250, 0x34 },\n   { 0x290, 1 },\n   { 0x292, 2 },\n   { 0x2A1, 1 },\n   { 0x2A5, 1 },\n   { 0x2A8, 2 },\n   { 0x2AC, 3 },\n   { 0x2CA, 1 },\n   { 0x2CC, 1 },\n   { 0x2CE, 1 },\n   { 0x300, 9 },\n   { 0x30C, 1 },\n   { 0x312, 1 },\n   { 0x316, 2 },\n   { 0x343, 2 },\n   { 0x349, 3 },\n   { 0x34C, 2 },\n   { 0x351, 1 },\n   { 0x37E, 6 },\n   { 0x2B4, 3 },\n   { 0x2B8, 3 },\n   { 0x2BC, 3 },\n   { 0x2C0, 3 },\n   { 0x2C8, 1 },\n   { 0x29B, 1 },\n   { 0x8C, 1 },\n   { 0xD5, 1 },\n   { 0x284, 0xC },\n};\n\nstatic std::pair<uint32_t, uint32_t>\nAluConstRange[] =\n{\n   { 0, 0x800 },\n};\n\nstatic std::pair<uint32_t, uint32_t>\nLoopConstRange[] =\n{\n   { 0, 0x60 },\n};\n\nstatic std::pair<uint32_t, uint32_t>\nResourceRange[] =\n{\n   { 0, 0x70 },\n   { 0x380, 0x70 },\n   { 0x460, 0x70 },\n   { 0x7E0, 0x70 },\n   { 0x8B9, 7 },\n   { 0x8C0, 0x70 },\n   { 0x930, 0x70 },\n   { 0xCB0, 0x70 },\n   { 0xD89, 7 },\n};\n\nstatic std::pair<uint32_t, uint32_t>\nSamplerRange[] =\n{\n   { 0, 0x36 },\n   { 0x36, 0x36 },\n   { 0x6C, 0x36 },\n};\n\nstatic std::pair<uint32_t, uint32_t>\nEmptyRange[] =\n{\n   { 0, 0 },\n};\n\nstatic auto\nEmptyRangeSpan = gsl::make_span(EmptyRange);\n\nstatic void\nloadState(virt_ptr<GX2ContextState> state,\n          bool skipLoad)\n{\n   internal::enableStateShadowing();\n\n   internal::writePM4(LoadConfigReg {\n      OSEffectiveToPhysical(virt_cast<virt_addr>(virt_addrof(state->shadowState.config))),\n      skipLoad ? EmptyRangeSpan : gsl::make_span(ConfigRegisterRange)\n   });\n\n   internal::writePM4(LoadContextReg {\n      OSEffectiveToPhysical(virt_cast<virt_addr>(virt_addrof(state->shadowState.context))),\n      skipLoad ? EmptyRangeSpan : gsl::make_span(ContextRegisterRange)\n   });\n\n   internal::writePM4(LoadAluConst {\n      OSEffectiveToPhysical(virt_cast<virt_addr>(virt_addrof(state->shadowState.alu))),\n      skipLoad ? EmptyRangeSpan : gsl::make_span(AluConstRange)\n   });\n\n   internal::writePM4(LoadLoopConst {\n      OSEffectiveToPhysical(virt_cast<virt_addr>(virt_addrof(state->shadowState.loop))),\n      skipLoad ? EmptyRangeSpan : gsl::make_span(LoopConstRange)\n   });\n\n   internal::writePM4(latte::pm4::LoadResource {\n      OSEffectiveToPhysical(virt_cast<virt_addr>(virt_addrof(state->shadowState.resource))),\n      skipLoad ? EmptyRangeSpan : gsl::make_span(ResourceRange)\n   });\n\n   internal::writePM4(LoadSampler {\n      OSEffectiveToPhysical(virt_cast<virt_addr>(virt_addrof(state->shadowState.sampler))),\n      skipLoad ? EmptyRangeSpan : gsl::make_span(SamplerRange)\n   });\n\n   if (!skipLoad) {\n      GX2Invalidate(GX2InvalidateMode::Shader, nullptr, 0xFFFFFFFF);\n   }\n}\n\nvoid\nGX2SetupContextStateEx(virt_ptr<GX2ContextState> state,\n                       GX2ContextStateFlags flags)\n{\n   // Create our internal shadow display list\n   std::memset(state.get(), 0, sizeof(GX2ContextState));\n   state->profilingEnabled = (flags & GX2ContextStateFlags::ProfilingEnabled) ? TRUE : FALSE;\n   internal::setProfilingEnabled(state->profilingEnabled);\n\n   // Clear load state\n   loadState(state, true);\n\n   // Initialise default state\n   internal::initialiseRegisters();\n   GX2SetDefaultState();\n\n   // Setup shadow display list\n   if (!(flags & GX2ContextStateFlags::NoShadowDisplayList)) {\n      GX2BeginDisplayListEx(virt_addrof(state->shadowDisplayList),\n                            GX2ContextState::MaxDisplayListSize * 4,\n                            state->profilingEnabled);\n      loadState(state, false);\n      state->shadowDisplayListSize = GX2EndDisplayList(virt_addrof(state->shadowDisplayList));\n   }\n}\n\nvoid\nGX2GetContextStateDisplayList(virt_ptr<GX2ContextState> state,\n                              virt_ptr<virt_ptr<void>> outDisplayList,\n                              virt_ptr<uint32_t> outSize)\n{\n   if (outDisplayList) {\n      *outDisplayList = virt_addrof(state->shadowDisplayList);\n   }\n\n   if (outSize) {\n      *outSize = state->shadowDisplayListSize;\n   }\n}\n\nvoid\nGX2SetContextState(virt_ptr<GX2ContextState> state)\n{\n   if (state) {\n      if (!state->shadowDisplayListSize) {\n         loadState(state, false);\n      } else {\n         GX2CallDisplayList(virt_addrof(state->shadowDisplayList),\n                            state->shadowDisplayListSize);\n      }\n   } else {\n      internal::disableStateShadowing();\n   }\n}\n\nvoid\nGX2SetDefaultState()\n{\n   internal::debugCaptureTagGroup(GX2DebugTag::SetDefaultState);\n\n   GX2SetShaderModeEx(GX2ShaderMode::UniformRegister, // mode\n                      48,     // numVsGpr\n                      64,     // numVsStackEntries\n                      0,      // numGsGpr\n                      0,      // numGsStackEntries\n                      200,    // numPsGpr\n                      192);   // numPsStackEntries\n\n   GX2SetDepthStencilControl(TRUE,                          // depthTest\n                             TRUE,                          // depthWrite\n                             GX2CompareFunction::Less,      // depthCompare\n                             FALSE,                         // stencilTest\n                             FALSE,                         // backfaceStencil\n                             GX2CompareFunction::Always,    // frontStencilFunc\n                             GX2StencilFunction::Replace,   // frontStencilZPass\n                             GX2StencilFunction::Replace,   // frontStencilZFail\n                             GX2StencilFunction::Replace,   // frontStencilFail\n                             GX2CompareFunction::Always,    // backStencilFunc\n                             GX2StencilFunction::Replace,   // backStencilZPass\n                             GX2StencilFunction::Replace,   // backStencilZFail\n                             GX2StencilFunction::Replace);  // backStencilFail\n\n   GX2SetPolygonControl(GX2FrontFace::CounterClockwise,  // frontFace\n                        FALSE,                           // cullFront\n                        FALSE,                           // cullBack\n                        FALSE,                           // polyMode\n                        GX2PolygonMode::Triangle,        // polyModeFront\n                        GX2PolygonMode::Triangle,        // polyModeBack\n                        FALSE,                           // polyOffsetFrontEnable\n                        FALSE,                           // polyOffsetBackEnable\n                        FALSE);                          // polyOffsetParaEnable\n\n   GX2SetStencilMask(0xff,    // frontMask\n                     0xff,    // frontWriteMask\n                     1,       // frontRef\n                     0xff,    // backMask\n                     0xff,    // backWriteMask\n                     1);      // backRef\n\n   GX2SetPolygonOffset(0.0f,  // frontOffset\n                       0.0f,  // frontScale\n                       0.0f,  // backOffset\n                       0.0f,  // backScale\n                       0.0f); // clamp\n\n   GX2SetPointSize(1.0f,      // width\n                   1.0f);     // height\n\n   GX2SetPointLimits(1.0f,    // min\n                     1.0f);   // max\n\n   GX2SetLineWidth(1.0f);\n\n   GX2SetPrimitiveRestartIndex(0xffffffffu);\n\n   GX2SetAlphaTest(FALSE,                    // alphaTest\n                   GX2CompareFunction::Less, // func\n                   0.0f);                    // ref\n\n   GX2SetAlphaToMask(FALSE,                              // enable\n                     GX2AlphaToMaskMode::NonDithered);   // mode\n\n   GX2SetTargetChannelMasks(GX2ChannelMask::RGBA,\n                            GX2ChannelMask::RGBA,\n                            GX2ChannelMask::RGBA,\n                            GX2ChannelMask::RGBA,\n                            GX2ChannelMask::RGBA,\n                            GX2ChannelMask::RGBA,\n                            GX2ChannelMask::RGBA,\n                            GX2ChannelMask::RGBA);\n\n   GX2SetColorControl(GX2LogicOp::Copy,   // rop3\n                      0,                  // targetBlendEnable\n                      FALSE,              // multiWriteEnable\n                      TRUE);              // colorWriteEnable\n\n   for (auto i = 0u; i < 8u; ++i) {\n      GX2SetBlendControl(static_cast<GX2RenderTarget>(i),  // target\n                         GX2BlendMode::SrcAlpha,      // colorSrcBlend\n                         GX2BlendMode::InvSrcAlpha,   // colorDstBlend\n                         GX2BlendCombineMode::Add,    // colorCombine\n                         TRUE,                        // useAlphaBlend\n                         GX2BlendMode::SrcAlpha,      // alphaSrcBlend\n                         GX2BlendMode::InvSrcAlpha,   // alphaDstBlend\n                         GX2BlendCombineMode::Add);   // alphaCombine\n   }\n\n   GX2SetBlendConstantColor(0.0f, 0.0f, 0.0f, 0.0f); // RGBA\n\n   GX2SetStreamOutEnable(0);\n\n   GX2SetRasterizerClipControl(TRUE,   // rasteriser\n                               TRUE);  // zclipEnable\n\n   // TODO: Figure out what GX2PrimitiveMode 0x84 is\n   GX2SetTessellation(GX2TessellationMode::Discrete,\n                      static_cast<GX2PrimitiveMode>(0x84),\n                      GX2IndexType::U32);\n\n   GX2SetMaxTessellationLevel(1.0f);\n   GX2SetMinTessellationLevel(1.0f);\n\n   internal::writePM4(SetContextReg { latte::Register::DB_RENDER_CONTROL, 0 });\n\n   internal::debugCaptureTagGroup(GX2DebugTag::SetDefaultState);\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseRegisters()\n{\n   std::array<uint32_t, 24> zeroes;\n   zeroes.fill(0);\n\n   uint32_t values28030_28034[] = {\n      latte::PA_SC_SCREEN_SCISSOR_TL::get(0).value,\n      latte::PA_SC_SCREEN_SCISSOR_BR::get(0)\n         .BR_X(8192)\n         .BR_Y(8192).value\n   };\n\n   internal::writePM4(SetContextRegs {\n      latte::Register::PA_SC_SCREEN_SCISSOR_TL,\n      gsl::make_span(values28030_28034)\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::PA_SC_LINE_CNTL,\n      latte::PA_SC_LINE_CNTL::get(0)\n         .value\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::PA_SU_VTX_CNTL,\n      latte::PA_SU_VTX_CNTL::get(0)\n         .PIX_CENTER(latte::PA_SU_VTX_CNTL_PIX_CENTER::OGL)\n         .ROUND_MODE(latte::PA_SU_VTX_CNTL_ROUND_MODE::TRUNCATE)\n         .QUANT_MODE(latte::PA_SU_VTX_CNTL_QUANT_MODE::QUANT_1_256TH)\n         .value\n   });\n\n   // PA_CL_POINT_X_RAD, PA_CL_POINT_Y_RAD, PA_CL_POINT_POINT_SIZE, PA_CL_POINT_POINT_CULL_RAD\n   internal::writePM4(SetContextRegs {\n      latte::Register::PA_CL_POINT_X_RAD,\n      gsl::make_span(zeroes.data(), 4)\n   });\n\n   // PA_CL_UCP_0_X ... PA_CL_UCP_5_W\n   internal::writePM4(SetContextRegs {\n      latte::Register::PA_CL_UCP_0_X,\n      gsl::make_span(zeroes.data(), 24)\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::PA_CL_VTE_CNTL,\n      latte::PA_CL_VTE_CNTL::get(0)\n         .VPORT_X_SCALE_ENA(true)\n         .VPORT_X_OFFSET_ENA(true)\n         .VPORT_Y_SCALE_ENA(true)\n         .VPORT_Y_OFFSET_ENA(true)\n         .VPORT_Z_SCALE_ENA(true)\n         .VPORT_Z_OFFSET_ENA(true)\n         .VTX_W0_FMT(true)\n         .value\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::PA_CL_NANINF_CNTL,\n      latte::PA_CL_NANINF_CNTL::get(0)\n         .value\n   });\n\n   uint32_t values28200_28208[] = {\n      0,\n      latte::PA_SC_WINDOW_SCISSOR_TL::get(0)\n         .WINDOW_OFFSET_DISABLE(true)\n         .value,\n      latte::PA_SC_WINDOW_SCISSOR_BR::get(0)\n         .BR_X(8192)\n         .BR_Y(8192)\n         .value,\n   };\n\n   internal::writePM4(SetContextRegs {\n      latte::Register::PA_SC_WINDOW_OFFSET,\n      gsl::make_span(values28200_28208)\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::PA_SC_LINE_STIPPLE,\n      latte::PA_SC_LINE_STIPPLE::get(0)\n      .value\n   });\n\n   uint32_t values28A0C_28A10[] = {\n      latte::PA_SC_MPASS_PS_CNTL::get(0)\n         .value,\n      latte::PA_SC_MODE_CNTL::get(0)\n         .MSAA_ENABLE(true)\n         .FORCE_EOV_CNTDWN_ENABLE(true)\n         .FORCE_EOV_REZ_ENABLE(true)\n         .value\n   };\n\n   internal::writePM4(SetContextRegs {\n      latte::Register::PA_SC_LINE_STIPPLE,\n      gsl::make_span(values28A0C_28A10)\n   });\n\n   uint32_t values28250_28254[] = {\n      latte::PA_SC_VPORT_SCISSOR_0_TL::get(0)\n         .WINDOW_OFFSET_DISABLE(true)\n         .value,\n      latte::PA_SC_VPORT_SCISSOR_0_BR::get(0)\n         .BR_X(8192)\n         .BR_Y(8192)\n         .value,\n   };\n\n   internal::writePM4(SetContextRegs {\n      latte::Register::PA_SC_VPORT_SCISSOR_0_TL,\n      gsl::make_span(values28250_28254)\n   });\n\n   // TODO: Register 0x8B24 unknown\n   internal::writePM4(SetConfigReg {\n      static_cast<latte::Register>(0x8B24),\n      0xFF3FFF\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::PA_SC_CLIPRECT_RULE,\n      latte::PA_SC_CLIPRECT_RULE::get(0)\n         .CLIP_RULE(0xFFFF)\n         .value\n   });\n\n   internal::writePM4(SetConfigReg {\n      latte::Register::VGT_GS_VERTEX_REUSE,\n      latte::VGT_GS_VERTEX_REUSE::get(0)\n         .VERT_REUSE(16)\n         .value\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::VGT_OUTPUT_PATH_CNTL,\n      latte::VGT_OUTPUT_PATH_CNTL::get(0)\n         .PATH_SELECT(latte::VGT_OUTPUT_PATH_SELECT::TESS_EN)\n         .value\n   });\n\n   // TODO: This is an unknown value 16 * 0xb14(r31) * 0xb18(r31)\n   internal::writePM4(SetConfigReg {\n      latte::Register::VGT_ES_PER_GS,\n      latte::VGT_ES_PER_GS::get(0)\n         .ES_PER_GS(16 * 1 * 1)\n         .value\n   });\n\n   internal::writePM4(SetConfigReg {\n      latte::Register::VGT_GS_PER_ES,\n      latte::VGT_GS_PER_ES::get(0)\n         .GS_PER_ES(256)\n         .value\n   });\n\n   internal::writePM4(SetConfigReg {\n      latte::Register::VGT_GS_PER_VS,\n      latte::VGT_GS_PER_VS::get(0)\n         .GS_PER_VS(4)\n         .value\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::VGT_INDX_OFFSET,\n      latte::VGT_INDX_OFFSET::get(0)\n         .INDX_OFFSET(0)\n         .value\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::VGT_REUSE_OFF,\n      latte::VGT_REUSE_OFF::get(0)\n         .REUSE_OFF(false)\n         .value\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::VGT_MULTI_PRIM_IB_RESET_EN,\n      latte::VGT_MULTI_PRIM_IB_RESET_EN::get(0)\n         .RESET_EN(true)\n         .value\n   });\n\n   uint32_t values28C58_28C5C[] = {\n      latte::VGT_VERTEX_REUSE_BLOCK_CNTL::get(0)\n         .VTX_REUSE_DEPTH(14)\n         .value,\n      latte::VGT_OUT_DEALLOC_CNTL::get(0)\n         .DEALLOC_DIST(16)\n         .value,\n   };\n\n   internal::writePM4(SetContextRegs {\n      latte::Register::VGT_VERTEX_REUSE_BLOCK_CNTL,\n      gsl::make_span(values28C58_28C5C)\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::VGT_HOS_REUSE_DEPTH,\n      latte::VGT_HOS_REUSE_DEPTH::get(0)\n         .REUSE_DEPTH(16)\n         .value\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::VGT_STRMOUT_DRAW_OPAQUE_OFFSET,\n      latte::VGT_STRMOUT_DRAW_OPAQUE_OFFSET::get(0)\n         .OFFSET(0)\n         .value\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::VGT_VTX_CNT_EN,\n      latte::VGT_VTX_CNT_EN::get(0)\n         .VTX_CNT_EN(false)\n         .value\n   });\n\n   uint32_t values28400_28404[] = {\n      latte::VGT_MAX_VTX_INDX::get(0)\n         .MAX_INDX(0xffffffffu)\n         .value,\n      latte::VGT_MIN_VTX_INDX::get(0)\n         .MIN_INDX(0x00000000u)\n         .value\n   };\n\n   internal::writePM4(SetContextRegs {\n      latte::Register::VGT_MAX_VTX_INDX,\n      gsl::make_span(values28400_28404)\n   });\n\n   internal::writePM4(SetConfigReg {\n      latte::Register::TA_CNTL_AUX,\n      latte::TA_CNTL_AUX::get(0)\n         .UNK0(true)\n         .SYNC_GRADIENT(true)\n         .SYNC_WALKER(true)\n         .SYNC_ALIGNER(true)\n         .value\n   });\n\n   // TODO: Register 0x9714 unknown\n   internal::writePM4(SetConfigReg {\n      static_cast<latte::Register>(0x9714),\n      1\n   });\n\n   // TODO: Register 0x8D8C unknown\n   internal::writePM4(SetConfigReg {\n      static_cast<latte::Register>(0x8D8C),\n      0x4000\n   });\n\n   // SQ_ESTMP_RING_BASE ... SQ_REDUC_RING_SIZE\n   internal::writePM4(SetConfigRegs {\n      latte::Register::SQ_ESTMP_RING_BASE,\n      gsl::make_span(zeroes.data(), 12)\n   });\n\n   // SQ_ESTMP_RING_ITEMSIZE ... SQ_REDUC_RING_ITEMSIZE\n   internal::writePM4(SetContextRegs {\n      latte::Register::SQ_ESTMP_RING_ITEMSIZE,\n      gsl::make_span(zeroes.data(), 6)\n   });\n\n   internal::writePM4(SetControlConstant {\n      latte::Register::SQ_VTX_START_INST_LOC,\n      latte::SQ_VTX_START_INST_LOC::get(0)\n         .OFFSET(0)\n         .value\n   });\n\n   // SPI_FOG_CNTL ... SPI_FOG_FUNC_BIAS\n   internal::writePM4(SetContextRegs {\n      latte::Register::SPI_FOG_CNTL,\n      gsl::make_span(zeroes.data(), 3)\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::SPI_INTERP_CONTROL_0,\n      latte::SPI_INTERP_CONTROL_0::get(0)\n         .FLAT_SHADE_ENA(true)\n         .PNT_SPRITE_ENA(false)\n         .PNT_SPRITE_OVRD_X(latte::SPI_PNT_SPRITE_SEL::SEL_S)\n         .PNT_SPRITE_OVRD_Y(latte::SPI_PNT_SPRITE_SEL::SEL_T)\n         .PNT_SPRITE_OVRD_Z(latte::SPI_PNT_SPRITE_SEL::SEL_0)\n         .PNT_SPRITE_OVRD_W(latte::SPI_PNT_SPRITE_SEL::SEL_1)\n         .PNT_SPRITE_TOP_1(true)\n         .value\n   });\n\n   internal::writePM4(SetConfigReg {\n      latte::Register::SPI_CONFIG_CNTL_1,\n      latte::SPI_CONFIG_CNTL_1::get(0)\n         .value\n   });\n\n   // TODO: Register 0x286C8 unknown\n   internal::writePM4(SetAllContextsReg {\n      static_cast<latte::Register>(0x286C8),\n      1\n   });\n\n   // TODO: Register 0x28354 unknown\n   auto unkValue = 0u; // 0x143C(r31)\n\n   if (unkValue > 0x5270) {\n      internal::writePM4(SetContextReg {\n         static_cast<latte::Register>(0x28354),\n         0xFF\n      });\n   } else {\n      internal::writePM4(SetContextReg {\n         static_cast<latte::Register>(0x28354),\n         0x1FF\n      });\n   }\n\n   uint32_t values28D28_28D2C[] = {\n      latte::DB_SRESULTS_COMPARE_STATE0::get(0)\n         .value,\n      latte::DB_SRESULTS_COMPARE_STATE1::get(0)\n         .value\n   };\n\n   internal::writePM4(SetContextRegs {\n      latte::Register::DB_SRESULTS_COMPARE_STATE0,\n      gsl::make_span(values28D28_28D2C)\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::DB_RENDER_OVERRIDE,\n      latte::DB_RENDER_OVERRIDE::get(0)\n         .value\n   });\n\n   // TODO: Register 0x9830 unknown\n   internal::writePM4(SetConfigReg {\n      static_cast<latte::Register>(0x9830),\n      0\n   });\n\n   // TODO: Register 0x983C unknown\n   internal::writePM4(SetConfigReg {\n      static_cast<latte::Register>(0x983C),\n      0x1000000\n   });\n\n   uint32_t values28C30_28C3C[] = {\n      latte::CB_CLRCMP_CONTROL::get(0)\n         .CLRCMP_FCN_SEL(latte::CB_CLRCMP_SEL::SRC)\n         .value,\n      latte::CB_CLRCMP_SRC::get(0)\n         .CLRCMP_SRC(0)\n         .value,\n      latte::CB_CLRCMP_DST::get(0)\n         .CLRCMP_DST(0)\n         .value,\n      latte::CB_CLRCMP_MSK::get(0)\n         .CLRCMP_MSK(0xFFFFFFFF)\n         .value\n   };\n\n   internal::writePM4(SetContextRegs {\n      latte::Register::CB_CLRCMP_CONTROL,\n      gsl::make_span(values28C30_28C3C)\n   });\n\n   // TODO: Register 0x9A1C unknown\n   internal::writePM4(SetConfigReg {\n      static_cast<latte::Register>(0x9A1C),\n      0\n   });\n\n   internal::writePM4(SetContextReg {\n      latte::Register::PA_SC_AA_MASK,\n      latte::PA_SC_AA_MASK::get(0)\n         .AA_MASK_ULC(0xFF)\n         .AA_MASK_URC(0xFF)\n         .AA_MASK_LLC(0xFF)\n         .AA_MASK_LRC(0xFF)\n         .value\n   });\n\n   // TODO: Register 0x28230 unknown\n   internal::writePM4(SetContextReg {\n      static_cast<latte::Register>(0x28230),\n      0xAAAAAAAA\n   });\n\n   // TODO: GX2SetAAMode(0);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerContextStateSymbols()\n{\n   RegisterFunctionExport(GX2SetupContextStateEx);\n   RegisterFunctionExport(GX2GetContextStateDisplayList);\n   RegisterFunctionExport(GX2SetContextState);\n   RegisterFunctionExport(GX2SetDefaultState);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_contextstate.h",
    "content": "#pragma once\n#include \"gx2_displaylist.h\"\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_contextstate Context State\n * \\ingroup gx2\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct GX2ShadowRegisters\n{\n   be2_array<uint32_t, 0xB00> config;\n   be2_array<uint32_t, 0x400> context;\n   be2_array<uint32_t, 0x800> alu;\n   be2_array<uint32_t, 0x60> loop;\n   PADDING((0x80 - 0x60) * 4);\n   be2_array<uint32_t, 0xD9E> resource;\n   PADDING((0xDC0 - 0xD9E) * 4);\n   be2_array<uint32_t, 0xA2> sampler;\n   PADDING((0xC0 - 0xA2) * 4);\n};\nCHECK_OFFSET(GX2ShadowRegisters, 0x0000, config);\nCHECK_OFFSET(GX2ShadowRegisters, 0x2C00, context);\nCHECK_OFFSET(GX2ShadowRegisters, 0x3C00, alu);\nCHECK_OFFSET(GX2ShadowRegisters, 0x5C00, loop);\nCHECK_OFFSET(GX2ShadowRegisters, 0x5E00, resource);\nCHECK_OFFSET(GX2ShadowRegisters, 0x9500, sampler);\nCHECK_SIZE(GX2ShadowRegisters, 0x9800);\n\n// Internal display list is used to create LOAD_ dlist for the shadow state\nstruct GX2ContextState\n{\n   static const auto MaxDisplayListSize = 192u;\n   be2_struct<GX2ShadowRegisters> shadowState;\n   be2_val<BOOL> profilingEnabled;\n   be2_val<uint32_t> shadowDisplayListSize;\n   // stw 0x9808, value 0 in GX2SetupContextStateEx\n   UNKNOWN(0x9e00 - 0x9808);\n   be2_array<uint32_t, MaxDisplayListSize> shadowDisplayList;\n};\nCHECK_OFFSET(GX2ContextState, 0x0000, shadowState);\nCHECK_OFFSET(GX2ContextState, 0x9800, profilingEnabled);\nCHECK_OFFSET(GX2ContextState, 0x9804, shadowDisplayListSize);\nCHECK_OFFSET(GX2ContextState, 0x9E00, shadowDisplayList);\nCHECK_SIZE(GX2ContextState, 0xA100);\n\n#pragma pack(pop)\n\nvoid\nGX2SetupContextStateEx(virt_ptr<GX2ContextState> state,\n                       GX2ContextStateFlags flags);\n\nvoid\nGX2GetContextStateDisplayList(virt_ptr<GX2ContextState> state,\n                              virt_ptr<virt_ptr<void>> outDisplayList,\n                              virt_ptr<uint32_t> outSize);\n\nvoid\nGX2SetContextState(virt_ptr<GX2ContextState> state);\n\nvoid\nGX2SetDefaultState();\n\nnamespace internal\n{\n\nvoid\ninitialiseRegisters();\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_counter.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_counter.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include <cstring>\n\nnamespace cafe::gx2\n{\n\nBOOL\nGX2InitCounterInfo(virt_ptr<GX2CounterInfo> info,\n                   uint32_t unk0,\n                   uint32_t unk1)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nGX2ResetCounterInfo(virt_ptr<GX2CounterInfo> info)\n{\n   decaf_warn_stub();\n   std::memset(info.get(), 0, sizeof(GX2CounterInfo));\n}\n\nuint64_t\nGX2GetCounterResult(virt_ptr<GX2CounterInfo> info,\n                    uint32_t unk0)\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nvoid\nLibrary::registerCounterSymbols()\n{\n   RegisterFunctionExport(GX2InitCounterInfo);\n   RegisterFunctionExport(GX2ResetCounterInfo);\n   RegisterFunctionExport(GX2GetCounterResult);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_counter.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_counter Counter\n * \\ingroup gx2\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct GX2CounterInfo\n{\n   UNKNOWN(0x4A0);\n};\nCHECK_SIZE(GX2CounterInfo, 0x4A0);\n\n#pragma pack(pop)\n\nBOOL\nGX2InitCounterInfo(virt_ptr<GX2CounterInfo> info,\n                   uint32_t unk0,\n                   uint32_t unk1);\n\nvoid\nGX2ResetCounterInfo(virt_ptr<GX2CounterInfo> info);\n\nuint64_t\nGX2GetCounterResult(virt_ptr<GX2CounterInfo> info,\n                    uint32_t unk0);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_debug.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_debug.h\"\n#include \"gx2_enum_string.h\"\n#include \"gx2_fetchshader.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_internal_gfd.h\"\n#include \"gx2_shaders.h\"\n#include \"gx2_texture.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_snprintf.h\"\n#include \"decaf_config.h\"\n#include \"decaf_configstorage.h\"\n\n#include <atomic>\n#include <fstream>\n#include <common/align.h>\n#include <common/log.h>\n#include <common/platform_dir.h>\n#include <fmt/format.h>\n#include <iterator>\n#include <libcpu/cpu_formatters.h>\n#include <libcpu/mem.h>\n#include <libgpu/latte/latte_disassembler.h>\n#include <libgfd/gfd.h>\n#include <thread>\n\nnamespace cafe::gx2::internal\n{\n\nstatic std::atomic<bool> sDumpTextures;\nstatic std::atomic<bool> sDumpShaders;\n\nstatic void\ncreateDumpDirectory()\n{\n   platform::createDirectory(\"dump\");\n}\n\nstatic void\ndebugDumpData(const std::string &filename,\n              virt_ptr<const void> data,\n              size_t size)\n{\n   auto file = std::ofstream { filename, std::ofstream::out | std::ofstream::binary };\n   file.write(static_cast<const char *>(data.get()), size);\n   file.close();\n}\n\nvoid\ninitialiseDebug()\n{\n   static std::once_flag sRegisteredConfigChangeListener;\n   std::call_once(sRegisteredConfigChangeListener,\n      []() {\n         decaf::registerConfigChangeListener(\n            [](const decaf::Settings &settings) {\n               sDumpShaders = settings.gx2.dump_shaders;\n               sDumpTextures = settings.gx2.dump_textures;\n            });\n      });\n   sDumpShaders = decaf::config()->gx2.dump_shaders;\n   sDumpTextures = decaf::config()->gx2.dump_textures;\n}\n\nvoid\ndebugDumpTexture(virt_ptr<const GX2Texture> texture)\n{\n   if (!sDumpTextures.load(std::memory_order_relaxed)) {\n      return;\n   }\n\n   createDumpDirectory();\n\n   // Write text dump of GX2Texture structure to texture_X.txt\n   auto filename = fmt::format(\"texture_{}\", texture);\n\n   if (platform::fileExists(\"dump/\" + filename + \".txt\")) {\n      return;\n   }\n\n   auto file = std::ofstream { \"dump/\" + filename + \".txt\", std::ofstream::out };\n   auto out = fmt::memory_buffer {};\n\n   fmt::format_to(std::back_inserter(out), \"surface.dim = {}\\n\", gx2::to_string(texture->surface.dim));\n   fmt::format_to(std::back_inserter(out), \"surface.width = {}\\n\", texture->surface.width);\n   fmt::format_to(std::back_inserter(out), \"surface.height = {}\\n\", texture->surface.height);\n   fmt::format_to(std::back_inserter(out), \"surface.depth = {}\\n\", texture->surface.depth);\n   fmt::format_to(std::back_inserter(out), \"surface.mipLevels = {}\\n\", texture->surface.mipLevels);\n   fmt::format_to(std::back_inserter(out), \"surface.format = {}\\n\", gx2::to_string(texture->surface.format));\n   fmt::format_to(std::back_inserter(out), \"surface.aa = {}\\n\", gx2::to_string(texture->surface.aa));\n   fmt::format_to(std::back_inserter(out), \"surface.use = {}\\n\", gx2::to_string(texture->surface.use));\n   fmt::format_to(std::back_inserter(out), \"surface.resourceFlags = {}\\n\", texture->surface.resourceFlags);\n   fmt::format_to(std::back_inserter(out), \"surface.imageSize = {}\\n\", texture->surface.imageSize);\n   fmt::format_to(std::back_inserter(out), \"surface.image = {}\\n\", texture->surface.image);\n   fmt::format_to(std::back_inserter(out), \"surface.mipmapSize = {}\\n\", texture->surface.mipmapSize);\n   fmt::format_to(std::back_inserter(out), \"surface.mipmaps = {}\\n\", texture->surface.mipmaps);\n   fmt::format_to(std::back_inserter(out), \"surface.tileMode = {}\\n\", gx2::to_string(texture->surface.tileMode));\n   fmt::format_to(std::back_inserter(out), \"surface.swizzle = {}\\n\", texture->surface.swizzle);\n   fmt::format_to(std::back_inserter(out), \"surface.alignment = {}\\n\", texture->surface.alignment);\n   fmt::format_to(std::back_inserter(out), \"surface.pitch = {}\\n\", texture->surface.pitch);\n   fmt::format_to(std::back_inserter(out), \"viewFirstMip = {}\\n\", texture->viewFirstMip);\n   fmt::format_to(std::back_inserter(out), \"viewNumMips = {}\\n\", texture->viewNumMips);\n   fmt::format_to(std::back_inserter(out), \"viewFirstSlice = {}\\n\", texture->viewFirstSlice);\n   fmt::format_to(std::back_inserter(out), \"viewNumSlices = {}\\n\", texture->viewNumSlices);\n\n   file << std::string_view { out.data(), out.size() };\n\n   if (!texture->surface.image || !texture->surface.imageSize) {\n      return;\n   }\n\n   // Write GTX file\n   gfd::GFDFile gtx;\n   gfd::GFDTexture gfdTexture;\n   gx2ToGFDTexture(texture.get(), gfdTexture);\n   gtx.textures.push_back(gfdTexture);\n   gfd::writeFile(gtx, \"dump/\" + filename + \".gtx\");\n}\n\nstatic void\naddShader(gfd::GFDFile &file,\n          virt_ptr<const GX2VertexShader> shader)\n{\n   gfd::GFDVertexShader gfdShader;\n   gx2ToGFDVertexShader(shader.get(), gfdShader);\n   file.vertexShaders.emplace_back(std::move(gfdShader));\n}\n\nstatic void\naddShader(gfd::GFDFile &file,\n          virt_ptr<const GX2PixelShader> shader)\n{\n   gfd::GFDPixelShader gfdShader;\n   gx2ToGFDPixelShader(shader.get(), gfdShader);\n   file.pixelShaders.emplace_back(std::move(gfdShader));\n}\n\nstatic void\naddShader(gfd::GFDFile &file,\n          virt_ptr<const GX2FetchShader> shader)\n{\n   // TODO: Add FetchShader support to .gsh\n}\n\ntemplate<typename ShaderType>\nstatic void\ndebugDumpShader(const std::string_view &filename,\n                const std::string_view &info,\n                virt_ptr<ShaderType> shader,\n                bool isSubroutine = false)\n{\n   std::string output;\n\n   // Write binary of shader data to shader_pixel_X.bin\n   createDumpDirectory();\n\n   auto outputBin = fmt::format(\"dump/{}.bin\", filename);\n   if (platform::fileExists(outputBin)) {\n      return;\n   }\n\n   if (shader->data) {\n      gLog->debug(\"Dumping shader {}\", filename);\n      debugDumpData(outputBin, shader->data, shader->size);\n   }\n\n   // Write GSH file\n   if (shader->data) {\n      gfd::GFDFile gsh;\n      addShader(gsh, shader);\n      gfd::writeFile(gsh, fmt::format(\"dump/{}.gsh\", filename));\n   }\n\n   // Write text of shader to shader_pixel_X.txt\n   auto file = std::ofstream { fmt::format(\"dump/{}.txt\", filename), std::ofstream::out };\n\n   // Disassemble\n   if (shader->data) {\n      output = latte::disassemble(gsl::make_span(shader->data.get(), shader->size), isSubroutine);\n   }\n\n   file\n      << info << std::endl\n      << \"Disassembly:\" << std::endl\n      << output << std::endl;\n}\n\nstatic void\nformatUniformBlocks(fmt::memory_buffer &out,\n                    uint32_t count,\n                    virt_ptr<GX2UniformBlock> blocks)\n{\n   fmt::format_to(std::back_inserter(out), \"  uniformBlockCount: {}\\n\", count);\n\n   for (auto i = 0u; i < count; ++i) {\n      fmt::format_to(std::back_inserter(out), \"    Block {}\\n\", i);\n      fmt::format_to(std::back_inserter(out), \"      name: {}\\n\", blocks[i].name);\n      fmt::format_to(std::back_inserter(out), \"      offset: {}\\n\", blocks[i].offset);\n      fmt::format_to(std::back_inserter(out), \"      size: {}\\n\", blocks[i].size);\n   }\n}\n\nstatic void\nformatAttribVars(fmt::memory_buffer &out,\n                 uint32_t count,\n                 virt_ptr<GX2AttribVar> vars)\n{\n   fmt::format_to(std::back_inserter(out), \"  attribVarCount: {}\\n\", count);\n\n   for (auto i = 0u; i < count; ++i) {\n      fmt::format_to(std::back_inserter(out), \"    Var {}\\n\", i);\n      fmt::format_to(std::back_inserter(out), \"      name: {}\\n\", vars[i].name);\n      fmt::format_to(std::back_inserter(out), \"      type: {}\\n\", gx2::to_string(vars[i].type));\n      fmt::format_to(std::back_inserter(out), \"      count: {}\\n\", vars[i].count);\n      fmt::format_to(std::back_inserter(out), \"      location: {}\\n\", vars[i].location);\n   }\n}\n\nstatic void\nformatInitialValues(fmt::memory_buffer &out,\n                    uint32_t count,\n                    virt_ptr<GX2UniformInitialValue> vars)\n{\n   fmt::format_to(std::back_inserter(out), \"  intialValueCount: {}\\n\", count);\n\n   for (auto i = 0u; i < count; ++i) {\n      fmt::format_to(std::back_inserter(out), \"    Var {}\\n\", i);\n      fmt::format_to(std::back_inserter(out), \"      offset: {}\\n\", vars[i].offset);\n      fmt::format_to(std::back_inserter(out), \"      value: ({}, {}, {}, {})\\n\",\n                     vars[i].value[0], vars[i].value[1],\n                     vars[i].value[2], vars[i].value[3]);\n   }\n}\n\nstatic void\nformatLoopVars(fmt::memory_buffer &out,\n               uint32_t count,\n               virt_ptr<GX2LoopVar> vars)\n{\n   fmt::format_to(std::back_inserter(out), \"  loopVarCount: {}\\n\", count);\n\n   for (auto i = 0u; i < count; ++i) {\n      fmt::format_to(std::back_inserter(out), \"    Var {}\\n\", i);\n      fmt::format_to(std::back_inserter(out), \"      value: {}\\n\", vars[i].value);\n      fmt::format_to(std::back_inserter(out), \"      offset: {}\\n\", vars[i].offset);\n   }\n}\n\nstatic void\nformatUniformVars(fmt::memory_buffer &out,\n                  uint32_t count,\n                  virt_ptr<GX2UniformVar> vars)\n{\n   fmt::format_to(std::back_inserter(out), \"  uniformVarCount: {}\\n\", count);\n\n   for (auto i = 0u; i < count; ++i) {\n      fmt::format_to(std::back_inserter(out), \"    Var {}\\n\", i);\n      fmt::format_to(std::back_inserter(out), \"      name: {}\\n\", vars[i].name);\n      fmt::format_to(std::back_inserter(out), \"      type: {}\\n\", gx2::to_string(vars[i].type));\n      fmt::format_to(std::back_inserter(out), \"      count: {}\\n\", vars[i].count);\n      fmt::format_to(std::back_inserter(out), \"      offset: {}\\n\", vars[i].offset);\n      fmt::format_to(std::back_inserter(out), \"      block: {}\\n\", vars[i].block);\n   }\n}\n\nstatic void\nformatSamplerVars(fmt::memory_buffer &out,\n                  uint32_t count,\n                  virt_ptr<GX2SamplerVar> vars)\n{\n   fmt::format_to(std::back_inserter(out), \"  samplerVarCount: {}\\n\", count);\n\n   for (auto i = 0u; i < count; ++i) {\n      fmt::format_to(std::back_inserter(out), \"    Var {}\\n\", i);\n      fmt::format_to(std::back_inserter(out), \"      name: {}\\n\", vars[i].name);\n      fmt::format_to(std::back_inserter(out), \"      type: {}\\n\", gx2::to_string(vars[i].type));\n      fmt::format_to(std::back_inserter(out), \"      location: {}\\n\", vars[i].location);\n   }\n}\n\nvoid\ndebugDumpShader(virt_ptr<const GX2FetchShader> shader)\n{\n   if (!sDumpShaders.load(std::memory_order_relaxed)) {\n      return;\n   }\n\n   fmt::memory_buffer out;\n   fmt::format_to(std::back_inserter(out), \"GX2FetchShader:\\n\");\n   fmt::format_to(std::back_inserter(out), \"  address: {}\\n\", shader->data);\n   fmt::format_to(std::back_inserter(out), \"  size: {}\\n\", shader->size);\n\n   debugDumpShader(fmt::format(\"shader_fetch_{}\", shader),\n                   std::string_view { out.data(), out.size() },\n                   shader,\n                   true);\n}\n\nvoid\ndebugDumpShader(virt_ptr<const GX2PixelShader> shader)\n{\n   if (!sDumpShaders.load(std::memory_order_relaxed)) {\n      return;\n   }\n\n   fmt::memory_buffer out;\n   fmt::format_to(std::back_inserter(out), \"GX2PixelShader:\\n\");\n   fmt::format_to(std::back_inserter(out), \"  address: {}\\n\", shader->data);\n   fmt::format_to(std::back_inserter(out), \"  size: {}\\n\", shader->size);\n   fmt::format_to(std::back_inserter(out), \"  mode: {}\\n\", gx2::to_string(shader->mode));\n\n   formatUniformBlocks(out, shader->uniformBlockCount, shader->uniformBlocks);\n   formatUniformVars(out, shader->uniformVarCount, shader->uniformVars);\n   formatInitialValues(out, shader->initialValueCount, shader->initialValues);\n   formatLoopVars(out, shader->loopVarCount, shader->loopVars);\n   formatSamplerVars(out, shader->samplerVarCount, shader->samplerVars);\n\n   debugDumpShader(fmt::format(\"shader_pixel_{}\", shader),\n                   std::string_view { out.data(), out.size() },\n                   shader);\n}\n\nvoid\ndebugDumpShader(virt_ptr<const GX2VertexShader> shader)\n{\n   if (!sDumpShaders.load(std::memory_order_relaxed)) {\n      return;\n   }\n\n   fmt::memory_buffer out;\n   fmt::format_to(std::back_inserter(out), \"GX2VertexShader:\\n\");\n   fmt::format_to(std::back_inserter(out), \"  address: {}\\n\", shader->data);\n   fmt::format_to(std::back_inserter(out), \"  size: {}\\n\", shader->size);\n   fmt::format_to(std::back_inserter(out), \"  mode: {}\\n\", gx2::to_string(shader->mode));\n\n   formatUniformBlocks(out, shader->uniformBlockCount, shader->uniformBlocks);\n   formatUniformVars(out, shader->uniformVarCount, shader->uniformVars);\n   formatInitialValues(out, shader->initialValueCount, shader->initialValues);\n   formatLoopVars(out, shader->loopVarCount, shader->loopVars);\n   formatSamplerVars(out, shader->samplerVarCount, shader->samplerVars);\n   formatAttribVars(out, shader->attribVarCount, shader->attribVars);\n\n   debugDumpShader(fmt::format(\"shader_vertex_{}\", shader),\n                   std::string_view { out.data(), out.size() },\n                   shader);\n}\n\n} // namespace cafe::gx2::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_debug.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\nstruct GX2Texture;\nstruct GX2FetchShader;\nstruct GX2PixelShader;\nstruct GX2VertexShader;\n\nnamespace internal\n{\n\nvoid\ninitialiseDebug();\n\nvoid\ndebugDumpTexture(virt_ptr<const GX2Texture> texture);\n\nvoid\ndebugDumpShader(virt_ptr<const GX2FetchShader> shader);\n\nvoid\ndebugDumpShader(virt_ptr<const GX2PixelShader> shader);\n\nvoid\ndebugDumpShader(virt_ptr<const GX2VertexShader> shader);\n\n} // namespace internal\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_debug_dds.cpp",
    "content": "#include \"gx2_debug_dds.h\"\n#include \"gx2_format.h\"\n#include \"gx2_surface.h\"\n\n#include <fstream>\n#include <vector>\n\nnamespace cafe::gx2\n{\n\nconstexpr inline uint32_t\nmake_fourcc(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3)\n{\n   return c0 | (c1 << 8) | (c2 << 16) | (c3 << 24);\n}\n\nstatic const uint32_t\nDDS_MAGIC = make_fourcc('D', 'D', 'S', ' ');\n\nenum DXGI_FORMAT : uint32_t\n{\n   DXGI_FORMAT_UNKNOWN = 0,\n   DXGI_FORMAT_R32G32B32A32_TYPELESS = 1,\n   DXGI_FORMAT_R32G32B32A32_FLOAT = 2,\n   DXGI_FORMAT_R32G32B32A32_UINT = 3,\n   DXGI_FORMAT_R32G32B32A32_SINT = 4,\n   DXGI_FORMAT_R32G32B32_TYPELESS = 5,\n   DXGI_FORMAT_R32G32B32_FLOAT = 6,\n   DXGI_FORMAT_R32G32B32_UINT = 7,\n   DXGI_FORMAT_R32G32B32_SINT = 8,\n   DXGI_FORMAT_R16G16B16A16_TYPELESS = 9,\n   DXGI_FORMAT_R16G16B16A16_FLOAT = 10,\n   DXGI_FORMAT_R16G16B16A16_UNORM = 11,\n   DXGI_FORMAT_R16G16B16A16_UINT = 12,\n   DXGI_FORMAT_R16G16B16A16_SNORM = 13,\n   DXGI_FORMAT_R16G16B16A16_SINT = 14,\n   DXGI_FORMAT_R32G32_TYPELESS = 15,\n   DXGI_FORMAT_R32G32_FLOAT = 16,\n   DXGI_FORMAT_R32G32_UINT = 17,\n   DXGI_FORMAT_R32G32_SINT = 18,\n   DXGI_FORMAT_R32G8X24_TYPELESS = 19,\n   DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20,\n   DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21,\n   DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22,\n   DXGI_FORMAT_R10G10B10A2_TYPELESS = 23,\n   DXGI_FORMAT_R10G10B10A2_UNORM = 24,\n   DXGI_FORMAT_R10G10B10A2_UINT = 25,\n   DXGI_FORMAT_R11G11B10_FLOAT = 26,\n   DXGI_FORMAT_R8G8B8A8_TYPELESS = 27,\n   DXGI_FORMAT_R8G8B8A8_UNORM = 28,\n   DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29,\n   DXGI_FORMAT_R8G8B8A8_UINT = 30,\n   DXGI_FORMAT_R8G8B8A8_SNORM = 31,\n   DXGI_FORMAT_R8G8B8A8_SINT = 32,\n   DXGI_FORMAT_R16G16_TYPELESS = 33,\n   DXGI_FORMAT_R16G16_FLOAT = 34,\n   DXGI_FORMAT_R16G16_UNORM = 35,\n   DXGI_FORMAT_R16G16_UINT = 36,\n   DXGI_FORMAT_R16G16_SNORM = 37,\n   DXGI_FORMAT_R16G16_SINT = 38,\n   DXGI_FORMAT_R32_TYPELESS = 39,\n   DXGI_FORMAT_D32_FLOAT = 40,\n   DXGI_FORMAT_R32_FLOAT = 41,\n   DXGI_FORMAT_R32_UINT = 42,\n   DXGI_FORMAT_R32_SINT = 43,\n   DXGI_FORMAT_R24G8_TYPELESS = 44,\n   DXGI_FORMAT_D24_UNORM_S8_UINT = 45,\n   DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46,\n   DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47,\n   DXGI_FORMAT_R8G8_TYPELESS = 48,\n   DXGI_FORMAT_R8G8_UNORM = 49,\n   DXGI_FORMAT_R8G8_UINT = 50,\n   DXGI_FORMAT_R8G8_SNORM = 51,\n   DXGI_FORMAT_R8G8_SINT = 52,\n   DXGI_FORMAT_R16_TYPELESS = 53,\n   DXGI_FORMAT_R16_FLOAT = 54,\n   DXGI_FORMAT_D16_UNORM = 55,\n   DXGI_FORMAT_R16_UNORM = 56,\n   DXGI_FORMAT_R16_UINT = 57,\n   DXGI_FORMAT_R16_SNORM = 58,\n   DXGI_FORMAT_R16_SINT = 59,\n   DXGI_FORMAT_R8_TYPELESS = 60,\n   DXGI_FORMAT_R8_UNORM = 61,\n   DXGI_FORMAT_R8_UINT = 62,\n   DXGI_FORMAT_R8_SNORM = 63,\n   DXGI_FORMAT_R8_SINT = 64,\n   DXGI_FORMAT_A8_UNORM = 65,\n   DXGI_FORMAT_R1_UNORM = 66,\n   DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67,\n   DXGI_FORMAT_R8G8_B8G8_UNORM = 68,\n   DXGI_FORMAT_G8R8_G8B8_UNORM = 69,\n   DXGI_FORMAT_BC1_TYPELESS = 70,\n   DXGI_FORMAT_BC1_UNORM = 71,\n   DXGI_FORMAT_BC1_UNORM_SRGB = 72,\n   DXGI_FORMAT_BC2_TYPELESS = 73,\n   DXGI_FORMAT_BC2_UNORM = 74,\n   DXGI_FORMAT_BC2_UNORM_SRGB = 75,\n   DXGI_FORMAT_BC3_TYPELESS = 76,\n   DXGI_FORMAT_BC3_UNORM = 77,\n   DXGI_FORMAT_BC3_UNORM_SRGB = 78,\n   DXGI_FORMAT_BC4_TYPELESS = 79,\n   DXGI_FORMAT_BC4_UNORM = 80,\n   DXGI_FORMAT_BC4_SNORM = 81,\n   DXGI_FORMAT_BC5_TYPELESS = 82,\n   DXGI_FORMAT_BC5_UNORM = 83,\n   DXGI_FORMAT_BC5_SNORM = 84,\n   DXGI_FORMAT_B5G6R5_UNORM = 85,\n   DXGI_FORMAT_B5G5R5A1_UNORM = 86,\n   DXGI_FORMAT_B8G8R8A8_UNORM = 87,\n   DXGI_FORMAT_B8G8R8X8_UNORM = 88,\n   DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89,\n   DXGI_FORMAT_B8G8R8A8_TYPELESS = 90,\n   DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91,\n   DXGI_FORMAT_B8G8R8X8_TYPELESS = 92,\n   DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93,\n   DXGI_FORMAT_BC6H_TYPELESS = 94,\n   DXGI_FORMAT_BC6H_UF16 = 95,\n   DXGI_FORMAT_BC6H_SF16 = 96,\n   DXGI_FORMAT_BC7_TYPELESS = 97,\n   DXGI_FORMAT_BC7_UNORM = 98,\n   DXGI_FORMAT_BC7_UNORM_SRGB = 99,\n   DXGI_FORMAT_AYUV = 100,\n   DXGI_FORMAT_Y410 = 101,\n   DXGI_FORMAT_Y416 = 102,\n   DXGI_FORMAT_NV12 = 103,\n   DXGI_FORMAT_P010 = 104,\n   DXGI_FORMAT_P016 = 105,\n   DXGI_FORMAT_420_OPAQUE = 106,\n   DXGI_FORMAT_YUY2 = 107,\n   DXGI_FORMAT_Y210 = 108,\n   DXGI_FORMAT_Y216 = 109,\n   DXGI_FORMAT_NV11 = 110,\n   DXGI_FORMAT_AI44 = 111,\n   DXGI_FORMAT_IA44 = 112,\n   DXGI_FORMAT_P8 = 113,\n   DXGI_FORMAT_A8P8 = 114,\n   DXGI_FORMAT_B4G4R4A4_UNORM = 115,\n   DXGI_FORMAT_P208 = 130,\n   DXGI_FORMAT_V208 = 131,\n   DXGI_FORMAT_V408 = 132,\n   DXGI_FORMAT_FORCE_UINT = 0xffffffff\n};\n\nenum D3D10_RESOURCE_DIMENSION\n{\n   D3D10_RESOURCE_DIMENSION_UNKNOWN = 0,\n   D3D10_RESOURCE_DIMENSION_BUFFER = 1,\n   D3D10_RESOURCE_DIMENSION_TEXTURE1D = 2,\n   D3D10_RESOURCE_DIMENSION_TEXTURE2D = 3,\n   D3D10_RESOURCE_DIMENSION_TEXTURE3D = 4\n};\n\nenum D3D10_RESOURCE_MISC_FLAG\n{\n   D3D10_RESOURCE_MISC_GENERATE_MIPS = 0x1L,\n   D3D10_RESOURCE_MISC_SHARED = 0x2L,\n   D3D10_RESOURCE_MISC_TEXTURECUBE = 0x4L,\n   D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX = 0x10L,\n   D3D10_RESOURCE_MISC_GDI_COMPATIBLE = 0x20L\n};\n\n#pragma pack(push, 1)\n\nstruct DdsPixelFormat\n{\n   uint32_t\tdwSize;\n   uint32_t\tdwFlags;\n   uint32_t\tdwFourCC;\n   uint32_t\tdwRGBBitCount;\n   uint32_t\tdwRBitMask;\n   uint32_t\tdwGBitMask;\n   uint32_t\tdwBBitMask;\n   uint32_t\tdwABitMask;\n};\n\nstruct DdsHeader\n{\n   uint32_t\tdwSize;\n   uint32_t\tdwFlags;\n   uint32_t\tdwHeight;\n   uint32_t\tdwWidth;\n   uint32_t\tdwPitchOrLinearSize;\n   uint32_t\tdwDepth;\n   uint32_t\tdwMipMapCount;\n   uint32_t\tdwReserved1[11];\n   DdsPixelFormat ddspf;\n   uint32_t\tdwCaps;\n   uint32_t\tdwCaps2;\n   uint32_t\tdwCaps3;\n   uint32_t\tdwCaps4;\n   uint32_t\tdwReserved2;\n};\n\nstruct DdsHeaderDX10\n{\n   DXGI_FORMAT dxgiFormat;\n   D3D10_RESOURCE_DIMENSION resourceDimension;\n   uint32_t miscFlag;\n   uint32_t arraySize;\n   uint32_t reserved;\n};\n\n#pragma pack(pop)\n\n#define DDSD_CAPS                               0x00000001L\n#define DDSD_HEIGHT                             0x00000002L\n#define DDSD_WIDTH                              0x00000004L\n#define DDSD_PITCH                              0x00000008L\n#define DDSD_BACKBUFFERCOUNT                    0x00000020L\n#define DDSD_ZBUFFERBITDEPTH                    0x00000040L\n#define DDSD_ALPHABITDEPTH                      0x00000080L\n#define DDSD_LPSURFACE                          0x00000800L\n#define DDSD_PIXELFORMAT                        0x00001000L\n#define DDSD_CKDESTOVERLAY                      0x00002000L\n#define DDSD_CKDESTBLT                          0x00004000L\n#define DDSD_CKSRCOVERLAY                       0x00008000L\n#define DDSD_CKSRCBLT                           0x00010000L\n#define DDSD_MIPMAPCOUNT                        0x00020000L\n#define DDSD_REFRESHRATE                        0x00040000L\n#define DDSD_LINEARSIZE                         0x00080000L\n#define DDSD_TEXTURESTAGE                       0x00100000L\n#define DDSD_FVF                                0x00200000L\n#define DDSD_SRCVBHANDLE                        0x00400000L\n#define DDSD_DEPTH                              0x00800000L\n#define DDSD_ALL                                0x007ff9eeL\n\n#define DDSCAPS_RESERVED1                       0x00000001L\n#define DDSCAPS_ALPHA                           0x00000002L\n#define DDSCAPS_BACKBUFFER                      0x00000004L\n#define DDSCAPS_COMPLEX                         0x00000008L\n#define DDSCAPS_FLIP                            0x00000010L\n#define DDSCAPS_FRONTBUFFER                     0x00000020L\n#define DDSCAPS_OFFSCREENPLAIN                  0x00000040L\n#define DDSCAPS_OVERLAY                         0x00000080L\n#define DDSCAPS_PALETTE                         0x00000100L\n#define DDSCAPS_PRIMARYSURFACE                  0x00000200L\n#define DDSCAPS_RESERVED3                       0x00000400L\n#define DDSCAPS_SYSTEMMEMORY                    0x00000800L\n#define DDSCAPS_TEXTURE                         0x00001000L\n#define DDSCAPS_3DDEVICE                        0x00002000L\n#define DDSCAPS_VIDEOMEMORY                     0x00004000L\n#define DDSCAPS_VISIBLE                         0x00008000L\n#define DDSCAPS_WRITEONLY                       0x00010000L\n#define DDSCAPS_ZBUFFER                         0x00020000L\n#define DDSCAPS_OWNDC                           0x00040000L\n#define DDSCAPS_LIVEVIDEO                       0x00080000L\n#define DDSCAPS_HWCODEC                         0x00100000L\n#define DDSCAPS_MODEX                           0x00200000L\n#define DDSCAPS_MIPMAP                          0x00400000L\n#define DDSCAPS_RESERVED2                       0x00800000L\n#define DDSCAPS_ALLOCONLOAD                     0x04000000L\n#define DDSCAPS_VIDEOPORT                       0x08000000L\n#define DDSCAPS_LOCALVIDMEM                     0x10000000L\n#define DDSCAPS_NONLOCALVIDMEM                  0x20000000L\n#define DDSCAPS_STANDARDVGAMODE                 0x40000000L\n#define DDSCAPS_OPTIMIZED                       0x80000000L\n\n#define DDSCAPS2_HARDWAREDEINTERLACE            0x00000002L\n#define DDSCAPS2_HINTDYNAMIC                    0x00000004L\n#define DDSCAPS2_HINTSTATIC                     0x00000008L\n#define DDSCAPS2_TEXTUREMANAGE                  0x00000010L\n#define DDSCAPS2_RESERVED1                      0x00000020L\n#define DDSCAPS2_RESERVED2                      0x00000040L\n#define DDSCAPS2_HINTANTIALIASING               0x00000100L\n#define DDSCAPS2_CUBEMAP                        0x00000200L\n#define DDSCAPS2_CUBEMAP_POSITIVEX              0x00000400L\n#define DDSCAPS2_CUBEMAP_NEGATIVEX              0x00000800L\n#define DDSCAPS2_CUBEMAP_POSITIVEY              0x00001000L\n#define DDSCAPS2_CUBEMAP_NEGATIVEY              0x00002000L\n#define DDSCAPS2_CUBEMAP_POSITIVEZ              0x00004000L\n#define DDSCAPS2_CUBEMAP_NEGATIVEZ              0x00008000L\n#define DDSCAPS2_CUBEMAP_ALLFACES ( DDSCAPS2_CUBEMAP_POSITIVEX |\\\n                                    DDSCAPS2_CUBEMAP_NEGATIVEX |\\\n                                    DDSCAPS2_CUBEMAP_POSITIVEY |\\\n                                    DDSCAPS2_CUBEMAP_NEGATIVEY |\\\n                                    DDSCAPS2_CUBEMAP_POSITIVEZ |\\\n                                    DDSCAPS2_CUBEMAP_NEGATIVEZ )\n#define DDSCAPS2_MIPMAPSUBLEVEL                 0x00010000L\n#define DDSCAPS2_D3DTEXTUREMANAGE               0x00020000L\n#define DDSCAPS2_DONOTPERSIST                   0x00040000L\n#define DDSCAPS2_STEREOSURFACELEFT              0x00080000L\n#define DDSCAPS2_VOLUME                         0x00200000L\n\n#define DDPF_ALPHAPIXELS                        0x00000001L\n#define DDPF_ALPHA                              0x00000002L\n#define DDPF_FOURCC                             0x00000004L\n#define DDPF_PALETTEINDEXED4                    0x00000008L\n#define DDPF_PALETTEINDEXEDTO8                  0x00000010L\n#define DDPF_PALETTEINDEXED8                    0x00000020L\n#define DDPF_RGB                                0x00000040L\n#define DDPF_COMPRESSED                         0x00000080L\n#define DDPF_RGBTOYUV                           0x00000100L\n#define DDPF_YUV                                0x00000200L\n#define DDPF_ZBUFFER                            0x00000400L\n#define DDPF_PALETTEINDEXED1                    0x00000800L\n#define DDPF_PALETTEINDEXED2                    0x00001000L\n#define DDPF_ZPIXELS                            0x00002000L\n#define DDPF_STENCILBUFFER                      0x00004000L\n#define DDPF_ALPHAPREMULT                       0x00008000L\n#define DDPF_LUMINANCE                          0x00020000L\n#define DDPF_BUMPLUMINANCE                      0x00040000L\n#define DDPF_BUMPDUDV                           0x00080000L\n\nenum D3DFORMAT\n{\n   D3DFMT_UNKNOWN           =  0,\n\n   D3DFMT_R8G8B8            = 20,\n   D3DFMT_A8R8G8B8          = 21,\n   D3DFMT_X8R8G8B8          = 22,\n   D3DFMT_R5G6B5            = 23,\n   D3DFMT_X1R5G5B5          = 24,\n   D3DFMT_A1R5G5B5          = 25,\n   D3DFMT_A4R4G4B4          = 26,\n   D3DFMT_R3G3B2            = 27,\n   D3DFMT_A8                = 28,\n   D3DFMT_A8R3G3B2          = 29,\n   D3DFMT_X4R4G4B4          = 30,\n   D3DFMT_A2B10G10R10       = 31,\n   D3DFMT_A8B8G8R8          = 32,\n   D3DFMT_X8B8G8R8          = 33,\n   D3DFMT_G16R16            = 34,\n   D3DFMT_A2R10G10B10       = 35,\n   D3DFMT_A16B16G16R16      = 36,\n\n   D3DFMT_A8P8              = 40,\n   D3DFMT_P8                = 41,\n\n   D3DFMT_L8                = 50,\n   D3DFMT_A8L8              = 51,\n   D3DFMT_A4L4              = 52,\n\n   D3DFMT_V8U8              = 60,\n   D3DFMT_L6V5U5            = 61,\n   D3DFMT_X8L8V8U8          = 62,\n   D3DFMT_Q8W8V8U8          = 63,\n   D3DFMT_V16U16            = 64,\n   D3DFMT_A2W10V10U10       = 67,\n\n   D3DFMT_UYVY              = make_fourcc('U', 'Y', 'V', 'Y'),\n   D3DFMT_R8G8_B8G8         = make_fourcc('R', 'G', 'B', 'G'),\n   D3DFMT_YUY2              = make_fourcc('Y', 'U', 'Y', '2'),\n   D3DFMT_G8R8_G8B8         = make_fourcc('G', 'R', 'G', 'B'),\n   D3DFMT_DXT1              = make_fourcc('D', 'X', 'T', '1'),\n   D3DFMT_DXT2              = make_fourcc('D', 'X', 'T', '2'),\n   D3DFMT_DXT3              = make_fourcc('D', 'X', 'T', '3'),\n   D3DFMT_DXT4              = make_fourcc('D', 'X', 'T', '4'),\n   D3DFMT_DXT5              = make_fourcc('D', 'X', 'T', '5'),\n\n   D3DFMT_ATI1              = make_fourcc('A', 'T', 'I', '1'),\n   D3DFMT_AT1N              = make_fourcc('A', 'T', '1', 'N'),\n   D3DFMT_ATI2              = make_fourcc('A', 'T', 'I', '2'),\n   D3DFMT_AT2N              = make_fourcc('A', 'T', '2', 'N'),\n\n   D3DFMT_ETC               = make_fourcc('E', 'T', 'C', ' '),\n   D3DFMT_ETC1              = make_fourcc('E', 'T', 'C', '1'),\n   D3DFMT_ATC               = make_fourcc('A', 'T', 'C', ' '),\n   D3DFMT_ATCA              = make_fourcc('A', 'T', 'C', 'A'),\n   D3DFMT_ATCI              = make_fourcc('A', 'T', 'C', 'I'),\n\n   D3DFMT_POWERVR_2BPP      = make_fourcc('P', 'T', 'C', '2'),\n   D3DFMT_POWERVR_4BPP      = make_fourcc('P', 'T', 'C', '4'),\n\n   D3DFMT_D16_LOCKABLE      = 70,\n   D3DFMT_D32               = 71,\n   D3DFMT_D15S1             = 73,\n   D3DFMT_D24S8             = 75,\n   D3DFMT_D24X8             = 77,\n   D3DFMT_D24X4S4           = 79,\n   D3DFMT_D16               = 80,\n\n   D3DFMT_D32F_LOCKABLE     = 82,\n   D3DFMT_D24FS8            = 83,\n\n   D3DFMT_L16               = 81,\n\n   D3DFMT_VERTEXDATA        = 100,\n   D3DFMT_INDEX16           = 101,\n   D3DFMT_INDEX32           = 102,\n\n   D3DFMT_Q16W16V16U16      = 110,\n\n   D3DFMT_MULTI2_ARGB8      = make_fourcc('M','E','T','1'),\n\n   D3DFMT_R16F              = 111,\n   D3DFMT_G16R16F           = 112,\n   D3DFMT_A16B16G16R16F     = 113,\n\n   D3DFMT_R32F              = 114,\n   D3DFMT_G32R32F           = 115,\n   D3DFMT_A32B32G32R32F     = 116,\n\n   D3DFMT_CxV8U8            = 117,\n\n   D3DFMT_DX10              = make_fourcc('D', 'X', '1', '0'),\n\n   D3DFMT_FORCE_DWORD       = 0x7fffffff\n};\n\nstatic DdsHeader\nencodeHeader(const GX2Surface* surface)\n{\n   DdsHeader dds;\n   memset(&dds, 0, sizeof(DdsHeader));\n   dds.dwSize = sizeof(DdsHeader);\n\n   dds.dwWidth = surface->width;\n   dds.dwHeight = surface->height;\n   dds.dwMipMapCount = surface->mipLevels;\n   dds.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT;\n\n   if ((surface->format >= GX2SurfaceFormat::UNORM_BC1 && surface->format <= GX2SurfaceFormat::UNORM_BC5)\n    || (surface->format >= GX2SurfaceFormat::SRGB_BC1 && surface->format <= GX2SurfaceFormat::SRGB_BC3)\n    || (surface->format >= GX2SurfaceFormat::SNORM_BC4 && surface->format <= GX2SurfaceFormat::SNORM_BC5)) {\n      dds.dwFlags |= DDSD_LINEARSIZE;\n      dds.dwPitchOrLinearSize = surface->imageSize + surface->mipmapSize;\n   } else {\n      dds.dwFlags |= DDSD_PITCH;\n   }\n\n   dds.ddspf.dwSize = sizeof(DdsPixelFormat);\n   dds.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_COMPLEX | DDSCAPS_MIPMAP;\n\n   if (surface->dim == GX2SurfaceDim::TextureCube) {\n      dds.dwCaps2 |= DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALLFACES;\n   } else if (surface->dim == GX2SurfaceDim::Texture3D) {\n      dds.dwFlags |= DDSD_DEPTH;\n      dds.dwDepth = surface->depth;\n      dds.dwCaps2 |= DDSCAPS2_VOLUME;\n   } else if (surface->dim == GX2SurfaceDim::Texture2DArray) {\n      dds.dwFlags |= DDSD_DEPTH;\n      dds.dwDepth = surface->depth;\n   }\n\n   return dds;\n}\n\nstatic DdsHeader\nencodeHeader10(const GX2Surface* surface)\n{\n   DdsHeader dds;\n   memset(&dds, 0, sizeof(DdsHeader));\n   dds.dwSize = sizeof(DdsHeader);\n\n   dds.dwWidth = surface->width;\n   dds.dwHeight = surface->height;\n   dds.dwMipMapCount = surface->mipLevels;\n   dds.dwFlags = DDSD_WIDTH | DDSD_HEIGHT;\n\n   dds.ddspf.dwSize = sizeof(DdsPixelFormat);\n   dds.dwCaps = DDSCAPS_TEXTURE;\n\n   if (surface->dim == GX2SurfaceDim::TextureCube) {\n      dds.dwCaps2 |= DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALLFACES;\n   } else if (surface->dim == GX2SurfaceDim::Texture3D) {\n      dds.dwFlags |= DDSD_DEPTH;\n      dds.dwDepth = surface->depth;\n      dds.dwCaps2 |= DDSCAPS2_VOLUME;\n   } else if (surface->dim == GX2SurfaceDim::Texture2DArray) {\n      dds.dwFlags |= DDSD_DEPTH;\n      dds.dwDepth = surface->depth;\n   }\n\n   if (surface->mipLevels > 1) {\n      dds.dwFlags |= DDSD_MIPMAPCOUNT;\n      dds.dwCaps |= DDSCAPS_MIPMAP;\n   }\n\n   return dds;\n}\n\nstatic void\nwriteData(std::ofstream &file,\n          DdsHeader *header,\n          const void *image,\n          size_t imageSize,\n          const void *mipmaps,\n          size_t mipmapSize)\n{\n   file.write(reinterpret_cast<char *>(header), sizeof(DdsHeader));\n\n   if (imageSize > 0) {\n      file.write(reinterpret_cast<const char *>(image), imageSize);\n   }\n\n   if (mipmapSize > 0) {\n      file.write(reinterpret_cast<const char *>(mipmaps), mipmapSize);\n   }\n}\n\nstatic bool\nencodeFourCC(std::ofstream &file,\n             const GX2Surface* surface,\n             const void *imagePtr,\n             const void *mipPtr,\n             uint32_t fourCC,\n             uint32_t flags = 0)\n{\n   DdsHeader header = encodeHeader(surface);\n   header.ddspf.dwFourCC = fourCC;\n   header.ddspf.dwFlags |= DDPF_FOURCC;\n   writeData(file, &header, imagePtr, surface->imageSize, mipPtr, surface->mipmapSize);\n   return true;\n}\n\nstatic bool\nencodeFourCCWithPitch(std::ofstream &file,\n                      const GX2Surface *surface,\n                      const void *imagePtr,\n                      const void *mipPtr,\n                      uint32_t fourCC,\n                      uint32_t flags = 0)\n{\n   DdsHeader header = encodeHeader(surface);\n   header.ddspf.dwFourCC = fourCC;\n   header.ddspf.dwFlags |= DDPF_FOURCC | flags;\n   header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format);\n   writeData(file, &header, imagePtr, surface->imageSize, mipPtr, surface->mipmapSize);\n   return true;\n}\n\nstatic bool\nencodeMasked(std::ofstream &file,\n             const GX2Surface *surface,\n             const void *imagePtr,\n             const void *mipPtr,\n             uint32_t maskR,\n             uint32_t maskG,\n             uint32_t maskB,\n             uint32_t maskA,\n             uint32_t flags = 0)\n{\n   DdsHeader header = encodeHeader(surface);\n   header.ddspf.dwFlags |= DDPF_RGB | flags;\n   header.ddspf.dwRBitMask = maskR;\n   header.ddspf.dwGBitMask = maskG;\n   header.ddspf.dwBBitMask = maskB;\n   header.ddspf.dwABitMask = maskA;\n   header.ddspf.dwRGBBitCount = GX2GetSurfaceFormatBitsPerElement(surface->format);\n   header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format);\n   writeData(file, &header, imagePtr, surface->imageSize, mipPtr, surface->mipmapSize);\n   return true;\n}\n\nstatic bool\nencodeLuminance(std::ofstream &file,\n                const GX2Surface *surface,\n                const void *imagePtr,\n                const void *mipPtr,\n                uint32_t maskL,\n                uint32_t maskA,\n                uint32_t flags = 0)\n{\n   DdsHeader header = encodeHeader(surface);\n   header.ddspf.dwFlags |= DDPF_LUMINANCE | flags;\n   header.ddspf.dwRGBBitCount = GX2GetSurfaceFormatBitsPerElement(surface->format);\n   header.ddspf.dwRBitMask = maskL;\n   header.ddspf.dwABitMask = maskA;\n   header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format);\n   writeData(file, &header, imagePtr, surface->imageSize, mipPtr, surface->mipmapSize);\n   return true;\n}\n\nstatic void\nswizzle565(const void *srcPtr,\n           void *dstPtr,\n           size_t size)\n{\n   auto src = reinterpret_cast<const uint16_t *>(srcPtr);\n   auto dst = reinterpret_cast<uint16_t *>(dstPtr);\n\n   for (auto i = 0u; i < (size / 2); ++i) {\n      auto pixel = src[i];\n      pixel = ((pixel & 0xF800) >> 11) | (pixel & 0x07E0) | ((pixel & 0x1F) << 11);\n      dst[i] = pixel;\n   }\n}\n\nstatic void\nswizzle1555(const void *srcPtr,\n            void *dstPtr,\n            size_t size)\n{\n   auto src = reinterpret_cast<const uint16_t *>(srcPtr);\n   auto dst = reinterpret_cast<uint16_t *>(dstPtr);\n\n   for (auto i = 0u; i < (size / 2); ++i) {\n      auto pixel = src[i];\n      pixel = (pixel & 0x8000) | ((pixel & 0x7c00) >> 10) | ((pixel & 0x001f) << 10) | (pixel & 0x03e0);\n      dst[i] = pixel;\n   }\n}\n\nstatic void\nswizzle4444(const void *srcPtr,\n            void *dstPtr,\n            size_t size)\n{\n   auto src = reinterpret_cast<const uint16_t *>(srcPtr);\n   auto dst = reinterpret_cast<uint16_t *>(dstPtr);\n\n   for (auto i = 0u; i < (size / 2); ++i) {\n      auto pixel = src[i];\n      pixel = (pixel & 0xf000) | ((pixel & 0x0f00) >> 8) | (pixel & 0x00f0) | ((pixel & 0x000f) << 8);\n      dst[i] = pixel;\n   }\n}\n\nstatic bool\nencode565(std::ofstream &file,\n          const GX2Surface *surface,\n          const void *imagePtr,\n          const void *mipPtr,\n          uint32_t flags = 0)\n{\n   DdsHeader header = encodeHeader(surface);\n   header.ddspf.dwFlags |= DDPF_RGB | flags;\n   header.ddspf.dwRGBBitCount = GX2GetSurfaceFormatBitsPerElement(surface->format);\n   header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format);\n\n   std::vector<uint8_t> image, mipmap;\n   image.resize(surface->imageSize);\n   mipmap.resize(surface->mipmapSize);\n\n   swizzle565(imagePtr, image.data(), image.size());\n   swizzle565(mipPtr, mipmap.data(), mipmap.size());\n\n   writeData(file, &header, image.data(), image.size(), mipmap.data(), mipmap.size());\n   return true;\n}\n\nstatic bool\nencode1555(std::ofstream &file,\n           const GX2Surface *surface,\n           const void *imagePtr,\n           const void *mipPtr,\n           uint32_t flags = 0)\n{\n   DdsHeader header = encodeHeader(surface);\n   header.ddspf.dwFlags |= DDPF_RGB | DDPF_ALPHAPIXELS | flags;\n   header.ddspf.dwRGBBitCount = GX2GetSurfaceFormatBitsPerElement(surface->format);\n   header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format);\n\n   header.ddspf.dwRBitMask = 0x7c00;\n   header.ddspf.dwGBitMask = 0x3e0;\n   header.ddspf.dwBBitMask = 0x1f;\n   header.ddspf.dwABitMask = 0x8000;\n\n   std::vector<uint8_t> image, mipmap;\n   image.resize(surface->imageSize);\n   mipmap.resize(surface->mipmapSize);\n\n   swizzle1555(imagePtr, image.data(), image.size());\n   swizzle1555(mipPtr, mipmap.data(), mipmap.size());\n\n   writeData(file, &header, image.data(), image.size(), mipmap.data(), mipmap.size());\n   return true;\n}\n\nstatic bool\nencode4444(std::ofstream &file,\n           const GX2Surface *surface,\n           const void *imagePtr,\n           const void *mipPtr,\n           uint32_t flags = 0)\n{\n   DdsHeader header = encodeHeader(surface);\n   header.ddspf.dwFlags |= DDPF_RGB | DDPF_ALPHAPIXELS | flags;\n   header.ddspf.dwRGBBitCount = GX2GetSurfaceFormatBitsPerElement(surface->format);\n   header.dwPitchOrLinearSize = surface->pitch * internal::getSurfaceFormatBytesPerElement(surface->format);\n\n   header.ddspf.dwRBitMask = 0xf00;\n   header.ddspf.dwGBitMask = 0xf0;\n   header.ddspf.dwBBitMask = 0xf;\n   header.ddspf.dwABitMask = 0xf000;\n\n   std::vector<uint8_t> image, mipmap;\n   image.resize(surface->imageSize);\n   mipmap.resize(surface->mipmapSize);\n\n   swizzle4444(imagePtr, image.data(), image.size());\n   swizzle4444(mipPtr, mipmap.data(), mipmap.size());\n\n   writeData(file, &header, image.data(), image.size(), mipmap.data(), mipmap.size());\n   return true;\n}\n\nstatic bool\nencodeDX10(std::ofstream &file,\n           const GX2Surface *surface,\n           const void *imagePtr,\n           const void *mipPtr,\n           DXGI_FORMAT dxgiFormat)\n{\n   DdsHeader header = encodeHeader10(surface);\n   header.ddspf.dwFlags = DDPF_FOURCC;\n   header.ddspf.dwFourCC = make_fourcc('D', 'X', '1', '0');\n\n   switch (surface->format) {\n   case GX2SurfaceFormat::UNORM_BC1:\n   case GX2SurfaceFormat::SRGB_BC1:\n   case GX2SurfaceFormat::UNORM_BC4:\n   case GX2SurfaceFormat::SNORM_BC4:\n      header.dwPitchOrLinearSize = header.dwWidth * 2;\n      break;\n   case GX2SurfaceFormat::UNORM_BC2:\n   case GX2SurfaceFormat::UNORM_BC3:\n   case GX2SurfaceFormat::UNORM_BC5:\n   case GX2SurfaceFormat::SRGB_BC2:\n   case GX2SurfaceFormat::SRGB_BC3:\n   case GX2SurfaceFormat::SNORM_BC5:\n      header.dwPitchOrLinearSize = header.dwWidth * 4;\n      break;\n   case GX2SurfaceFormat::FLOAT_R11_G11_B10:\n      header.dwPitchOrLinearSize = header.dwWidth * 4;\n      break;\n   }\n\n   file.write(reinterpret_cast<char *>(&header), sizeof(DdsHeader));\n\n   // Setup dx10 header\n   DdsHeaderDX10 headerDX10;\n   memset(&headerDX10, 0, sizeof(DdsHeaderDX10));\n\n   headerDX10.dxgiFormat = dxgiFormat;\n\n   if (surface->dim == GX2SurfaceDim::Texture2D) {\n      headerDX10.resourceDimension = D3D10_RESOURCE_DIMENSION_TEXTURE2D;\n      headerDX10.miscFlag = 0;\n      headerDX10.arraySize = 1;\n   } else if (surface->dim == GX2SurfaceDim::TextureCube) {\n      headerDX10.resourceDimension = D3D10_RESOURCE_DIMENSION_TEXTURE2D;\n      headerDX10.miscFlag = D3D10_RESOURCE_MISC_TEXTURECUBE;\n      headerDX10.arraySize = 6;\n   } else {\n      headerDX10.resourceDimension = D3D10_RESOURCE_DIMENSION_TEXTURE3D;\n      headerDX10.miscFlag = 0;\n      headerDX10.arraySize = surface->depth;\n   }\n\n   file.write(reinterpret_cast<char *>(&headerDX10), sizeof(DdsHeaderDX10));\n\n   if (surface->imageSize > 0) {\n      file.write(reinterpret_cast<const char *>(imagePtr), surface->imageSize);\n   }\n\n   if (surface->mipmapSize > 0) {\n      file.write(reinterpret_cast<const char *>(mipPtr), surface->mipmapSize);\n   }\n\n   return true;\n}\n\nstatic void\nencodeCubemap(const GX2Surface *surface,\n              const void *imagePtr,\n              const void *mipPtr,\n              void *cubeData)\n{\n   // DDS cubemap has slices interleaved with mipmaps... fuck you.\n   int minMipSize = 1;\n   if (surface->format == GX2SurfaceFormat::UNORM_BC1 ||\n       surface->format == GX2SurfaceFormat::SRGB_BC1) {\n      minMipSize = 8;\n   } else if (surface->format >= GX2SurfaceFormat::UNORM_BC2 &&\n              surface->format <= GX2SurfaceFormat::SNORM_BC5) {\n      minMipSize = 16;\n   } else {\n      minMipSize = 1;\n   }\n\n   auto dst = reinterpret_cast<uint8_t *>(cubeData);\n   auto srcImage = reinterpret_cast<const uint8_t *>(imagePtr);\n   auto srcMip = reinterpret_cast<const uint8_t *>(mipPtr);\n\n   for (auto i = 0; i < 6; ++i) {\n      int sliceSize = surface->imageSize / 6;\n      std::memcpy(dst, srcImage + (sliceSize * i), sliceSize);\n      dst += sliceSize;\n\n      int mipOffset = 0;\n      for (auto j = 0u; j < surface->mipLevels - 1; ++j) {\n         int mipSize = sliceSize >> ((j + 1) * 2);\n         mipSize = std::max(mipSize, minMipSize);\n\n         memcpy(dst, srcMip + mipOffset + (mipSize * i), mipSize);\n         dst += mipSize;\n         mipOffset += mipSize * 6;\n      }\n   }\n}\n\nnamespace debug\n{\n\nbool\nsaveDDS(const std::string &filename,\n        const GX2Surface *surface,\n        const void *imagePtr,\n        const void *mipPtr)\n{\n   std::ofstream fh { filename, std::ofstream::binary };\n\n   if (!fh.is_open()) {\n      return false;\n   }\n\n   fh.write(reinterpret_cast<const char *>(&DDS_MAGIC), sizeof(DDS_MAGIC));\n\n   std::vector<uint8_t> cubeAdjustedImage;\n   std::vector<uint8_t> cubeAdjustedMip;\n   if (surface->dim == GX2SurfaceDim::TextureCube) {\n      std::vector<uint8_t> cubeData;\n      cubeData.resize(surface->imageSize + surface->mipmapSize);\n\n      encodeCubemap(surface, imagePtr, mipPtr, cubeData.data());\n\n      cubeAdjustedImage.resize(surface->imageSize);\n      std::memcpy(cubeAdjustedImage.data(), cubeData.data(), surface->imageSize);\n      imagePtr = cubeAdjustedImage.data();\n\n      cubeAdjustedMip.resize(surface->mipmapSize);\n      std::memcpy(cubeAdjustedMip.data(), cubeData.data() + surface->imageSize, surface->mipmapSize);\n      mipPtr = cubeAdjustedMip.data();\n   }\n\n   bool result = false;\n\n   switch (surface->format) {\n   case GX2SurfaceFormat::UNORM_R16_G16_B16_A16:\n      result = encodeFourCCWithPitch(fh, surface, imagePtr, mipPtr, D3DFMT_A16B16G16R16, DDPF_ALPHAPIXELS);\n      break;\n   case GX2SurfaceFormat::FLOAT_R16_G16_B16_A16:\n      result = encodeFourCCWithPitch(fh, surface, imagePtr, mipPtr, D3DFMT_A16B16G16R16F, DDPF_ALPHAPIXELS);\n      break;\n   case GX2SurfaceFormat::FLOAT_R32_G32_B32_A32:\n      result = encodeFourCCWithPitch(fh, surface, imagePtr, mipPtr, D3DFMT_A32B32G32R32F, DDPF_ALPHAPIXELS);\n      break;\n   case GX2SurfaceFormat::UNORM_R10_G10_B10_A2:\n      result = encodeMasked(fh, surface, imagePtr, mipPtr, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000, DDPF_ALPHAPIXELS);\n      break;\n   case GX2SurfaceFormat::UNORM_R8_G8_B8_A8:\n   case GX2SurfaceFormat::SRGB_R8_G8_B8_A8:\n      result = encodeMasked(fh, surface, imagePtr, mipPtr, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000, DDPF_ALPHAPIXELS);\n      break;\n   case GX2SurfaceFormat::UNORM_R5_G6_B5:\n      result = encode565(fh, surface, imagePtr, mipPtr);\n      break;\n   case GX2SurfaceFormat::UNORM_R5_G5_B5_A1:\n      result = encode1555(fh, surface, imagePtr, mipPtr);\n      break;\n   case GX2SurfaceFormat::UNORM_R4_G4_B4_A4:\n      result = encode4444(fh, surface, imagePtr, mipPtr);\n      break;\n   case GX2SurfaceFormat::UNORM_R8:\n      result = encodeLuminance(fh, surface, imagePtr, mipPtr, 0xff, 0);\n      break;\n   case GX2SurfaceFormat::UNORM_R8_G8:\n      result = encodeLuminance(fh, surface, imagePtr, mipPtr, 0xff, 0xff00, DDPF_ALPHAPIXELS);\n      break;\n   case GX2SurfaceFormat::UNORM_BC1:\n      result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('D', 'X', 'T', '1'));\n      break;\n   case GX2SurfaceFormat::UNORM_BC2:\n      result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('D', 'X', 'T', '3'));\n      break;\n   case GX2SurfaceFormat::UNORM_BC3:\n      result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('D', 'X', 'T', '5'));\n      break;\n   case GX2SurfaceFormat::UNORM_BC4:\n      result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('A', 'T', 'I', '1'));\n      break;\n   case GX2SurfaceFormat::SNORM_BC4:\n      result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('A', 'T', 'I', '1'));\n      break;\n   case GX2SurfaceFormat::UNORM_BC5:\n      result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('A', 'T', 'I', '2'));\n      break;\n   case GX2SurfaceFormat::SNORM_BC5:\n      result = encodeFourCC(fh, surface, imagePtr, mipPtr, make_fourcc('A', 'T', 'I', '2'));\n      break;\n   case GX2SurfaceFormat::SRGB_BC1:\n      result = encodeDX10(fh, surface, imagePtr, mipPtr, DXGI_FORMAT_BC1_UNORM_SRGB);\n      break;\n   case GX2SurfaceFormat::SRGB_BC2:\n      result = encodeDX10(fh, surface, imagePtr, mipPtr, DXGI_FORMAT_BC2_UNORM_SRGB);\n      break;\n   case GX2SurfaceFormat::SRGB_BC3:\n      result = encodeDX10(fh, surface, imagePtr, mipPtr, DXGI_FORMAT_BC3_UNORM_SRGB);\n      break;\n   case GX2SurfaceFormat::FLOAT_R11_G11_B10:\n      result = encodeDX10(fh, surface, imagePtr, mipPtr, DXGI_FORMAT_R11G11B10_FLOAT);\n      break;\n   }\n\n   return result;\n}\n\n} // namespace debug\n\n} // namepsace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_debug_dds.h",
    "content": "#pragma once\n#include <string>\n\nnamespace cafe::gx2\n{\n\nstruct GX2Surface;\n\nnamespace debug\n{\n\nbool\nsaveDDS(const std::string &filename,\n        const GX2Surface *surface,\n        const void *imagePtr,\n        const void *mipPtr);\n\n} // namespace debug\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_debugcapture.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_debugcapture.h\"\n#include \"gx2_display.h\"\n#include \"gx2_event.h\"\n#include \"gx2_state.h\"\n#include \"gx2r_resource.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_ppc_interface_varargs.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_snprintf.h\"\n\n#include <common/strutils.h>\n\nnamespace cafe::gx2\n{\n\nstruct StaticDebugCaptureData\n{\n   be2_val<BOOL> enabled;\n   be2_val<uint32_t> previousSwapInterval;\n   be2_struct<GX2DebugCaptureInterface> captureInterface;\n\n   be2_val<uint32_t> numCaptureFramesRemaining;\n   be2_array<char, 256> pendingCaptureFilename;\n};\n\nstatic virt_ptr<StaticDebugCaptureData>\nsDebugCaptureData = nullptr;\n\n\n/**\n * Initialise the debug capture interface.\n */\nBOOL\nGX2DebugSetCaptureInterface(virt_ptr<GX2DebugCaptureInterface> captureInterface)\n{\n   // Normally this is only allowed when:\n   // OSGetSecurityLevel () != 1 &&\n   // OSGetConsoleType() != 0x13000048 &&\n   // OSGetConsoleType() != 0x13000040\n\n   if (!captureInterface ||\n       captureInterface->version != GX2DebugCaptureInterfaceVersion::Version1 ||\n       !captureInterface->onShutdown ||\n       !captureInterface->setAllocator ||\n       !captureInterface->onCaptureStart ||\n       !captureInterface->onCaptureEnd ||\n       !captureInterface->isCaptureEnabled ||\n       !captureInterface->onAlloc ||\n       !captureInterface->onInvalidate ||\n       !captureInterface->setScanbuffer ||\n       !captureInterface->onSwapScanBuffers ||\n       !captureInterface->submitToRing) {\n      return FALSE;\n   }\n\n   sDebugCaptureData->captureInterface = *captureInterface;\n   sDebugCaptureData->enabled = TRUE;\n   cafe::invoke(cpu::this_core::state(),\n                sDebugCaptureData->captureInterface.setAllocator,\n                internal::getDefaultGx2rAlloc(),\n                internal::getDefaultGx2rFree());\n   return TRUE;\n}\n\n\n/**\n * Start a debug capture.\n */\nvoid\nGX2DebugCaptureStart(virt_ptr<const char> filename,\n                     BOOL noCallDrawDone)\n{\n   if (!sDebugCaptureData->enabled) {\n      return;\n   }\n\n   if (!noCallDrawDone) {\n      GX2DrawDone();\n\n      sDebugCaptureData->previousSwapInterval = GX2GetSwapInterval();\n      GX2SetSwapInterval(0);\n   }\n\n   cafe::invoke(cpu::this_core::state(),\n                sDebugCaptureData->captureInterface.onCaptureStart,\n                filename);\n\n   internal::debugCaptureCbPoolPointers();\n\n   cafe::invoke(cpu::this_core::state(),\n                sDebugCaptureData->captureInterface.setScanbuffer,\n                internal::getTvScanBuffer(),\n                internal::getDrcScanBuffer());\n}\n\n\n/**\n * End a debug capture.\n */\nvoid\nGX2DebugCaptureEnd(BOOL noCallFlush)\n{\n   if (!sDebugCaptureData->enabled) {\n      return;\n   }\n\n   if (!noCallFlush) {\n      GX2Flush();\n   }\n\n   internal::debugCaptureCbPoolPointersFree();\n\n   cafe::invoke(cpu::this_core::state(),\n                sDebugCaptureData->captureInterface.onCaptureEnd);\n\n   GX2SetSwapInterval(sDebugCaptureData->previousSwapInterval);\n}\n\n\n/**\n * Capture the next frame.\n */\nvoid\nGX2DebugCaptureFrame(virt_ptr<const char> filename)\n{\n   if (!sDebugCaptureData->enabled) {\n      return;\n   }\n\n   GX2DebugCaptureFrames(filename, 1);\n}\n\n\n/**\n * Capture the next n frames.\n */\nvoid\nGX2DebugCaptureFrames(virt_ptr<const char> filename,\n                      uint32_t numFrames)\n{\n   if (!sDebugCaptureData->enabled) {\n      return;\n   }\n\n   string_copy(virt_addrof(sDebugCaptureData->pendingCaptureFilename).get(),\n               filename.get(),\n               sDebugCaptureData->pendingCaptureFilename.size() - 1);\n   sDebugCaptureData->numCaptureFramesRemaining = numFrames;\n}\n\n\n/**\n * Insert a debug tag into the pm4 stream.\n *\n * Only written when debug capture is enabled.\n */\nvoid\nGX2DebugTagUserString(GX2DebugTag tag,\n                      virt_ptr<const char> fmt,\n                      var_args va)\n{\n   if (internal::debugCaptureEnabled()) {\n      auto list = make_va_list(va);\n      GX2DebugTagUserStringVA(tag, fmt, list);\n      free_va_list(list);\n   }\n}\n\n\n/**\n * Insert a debug tag into the pm4 stream.\n *\n * Only written when debug capture is enabled.\n */\nvoid\nGX2DebugTagUserStringVA(GX2DebugTag tag,\n                        virt_ptr<const char> fmt,\n                        virt_ptr<va_list> vaList)\n{\n   if (internal::debugCaptureEnabled()) {\n      auto buffer = StackArray<char, 0x404> { };\n      std::memset(buffer.get(), 0, 0x404);\n\n      if (fmt) {\n         coreinit::internal::formatStringV(buffer, 0x3FF, fmt, vaList);\n      }\n\n      // Convert string to words!\n      auto length = static_cast<uint32_t>(strlen(buffer.get()));\n      auto numWords = align_up(length + 1, 4) / 4;\n\n      // Write NOP packet\n      internal::writePM4(latte::pm4::NopBE {\n         GX2DebugTag::User | tag,\n         gsl::make_span(virt_cast<uint32_t *>(buffer).get(), numWords)\n      });\n   }\n}\n\n\n/**\n * Notify gx2 of a graphics memory allocation.\n */\nvoid\nGX2NotifyMemAlloc(virt_ptr<void> ptr,\n                  uint32_t size,\n                  uint32_t align)\n{\n   if (internal::debugCaptureEnabled()) {\n      internal::debugCaptureAlloc(ptr, size, align);\n   }\n}\n\n\n/**\n * Notify gx2 of a graphics memory free.\n */\nvoid\nGX2NotifyMemFree(virt_ptr<void> ptr)\n{\n   if (internal::debugCaptureEnabled()) {\n      internal::debugCaptureFree(ptr);\n   }\n}\n\n\nnamespace internal\n{\n\nBOOL\ndebugCaptureEnabled()\n{\n   if (!sDebugCaptureData->enabled) {\n      return FALSE;\n   }\n\n   return cafe::invoke(cpu::this_core::state(),\n                       sDebugCaptureData->captureInterface.isCaptureEnabled);\n}\n\nvoid\ndebugCaptureAlloc(virt_ptr<void> ptr,\n                  uint32_t size,\n                  uint32_t align)\n{\n   cafe::invoke(cpu::this_core::state(),\n                sDebugCaptureData->captureInterface.onAlloc,\n                ptr, size, align);\n}\n\nvoid\ndebugCaptureFree(virt_ptr<void> ptr)\n{\n   cafe::invoke(cpu::this_core::state(),\n                sDebugCaptureData->captureInterface.onFree,\n                ptr);\n}\n\nvoid\ndebugCaptureInvalidate(virt_ptr<void> ptr,\n                       uint32_t size)\n{\n   cafe::invoke(cpu::this_core::state(),\n                sDebugCaptureData->captureInterface.onInvalidate,\n                ptr, size);\n}\n\nvoid\ndebugCaptureShutdown()\n{\n   cafe::invoke(cpu::this_core::state(),\n                sDebugCaptureData->captureInterface.onShutdown);\n   sDebugCaptureData->enabled = FALSE;\n}\n\ntcl::TCLStatus\ndebugCaptureSubmit(virt_ptr<uint32_t> buffer,\n                   uint32_t numWords,\n                   virt_ptr<tcl::TCLSubmitFlags> submitFlags,\n                   virt_ptr<tcl::TCLTimestamp> lastSubmittedTimestamp)\n{\n   return cafe::invoke(cpu::this_core::state(),\n                       sDebugCaptureData->captureInterface.submitToRing,\n                       buffer, numWords, submitFlags, lastSubmittedTimestamp);\n}\n\nvoid\ndebugCaptureSwap(virt_ptr<GX2Surface> tvScanBuffer,\n                 virt_ptr<GX2Surface> drcScanBuffer)\n{\n   if (!sDebugCaptureData->enabled) {\n      return;\n   }\n\n   auto enabled = debugCaptureEnabled();\n   if (!enabled) {\n      // Check if we need to start a capture\n      if (sDebugCaptureData->numCaptureFramesRemaining) {\n         GX2DebugCaptureStart(virt_addrof(sDebugCaptureData->pendingCaptureFilename),\n                              FALSE);\n      }\n\n      return;\n   }\n\n   // Capture frame\n   GX2DrawDone();\n   cafe::invoke(cpu::this_core::state(),\n                sDebugCaptureData->captureInterface.onSwapScanBuffers,\n                tvScanBuffer, drcScanBuffer);\n\n   if (!sDebugCaptureData->numCaptureFramesRemaining) {\n      return;\n   }\n\n   // Check if we need to end a capture\n   if (sDebugCaptureData->numCaptureFramesRemaining == 1) {\n      GX2DebugCaptureEnd(FALSE);\n   }\n\n   sDebugCaptureData->numCaptureFramesRemaining--;\n}\n\nvoid\ndebugCaptureTagGroup(GX2DebugTag tagId,\n                     std::string_view str)\n{\n   if (!sDebugCaptureData->enabled) {\n      return;\n   }\n\n   auto id = tagId | GX2DebugTag::Group;\n\n   if (str.empty()) {\n      internal::writePM4(latte::pm4::Nop { id, { } });\n   } else {\n      std::vector<uint32_t> buffer;\n      buffer.resize(align_up(str.size() + 1, 4) / 4, 0u);\n      std::memcpy(buffer.data(), str.data(), str.size());\n      internal::writePM4(latte::pm4::NopBE {\n         id,\n         { reinterpret_cast<be2_val<uint32_t> *>(buffer.data()), buffer.size() }\n      });\n   }\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerDebugCaptureSymbols()\n{\n   RegisterFunctionExportName(\"_GX2DebugSetCaptureInterface\",\n                              GX2DebugSetCaptureInterface);\n   RegisterFunctionExport(GX2DebugCaptureStart);\n   RegisterFunctionExport(GX2DebugCaptureEnd);\n   RegisterFunctionExport(GX2DebugCaptureFrame);\n   RegisterFunctionExport(GX2DebugCaptureFrames);\n   RegisterFunctionExport(GX2DebugTagUserString);\n   RegisterFunctionExport(GX2DebugTagUserStringVA);\n   RegisterFunctionExport(GX2NotifyMemAlloc);\n   RegisterFunctionExport(GX2NotifyMemFree);\n\n   RegisterDataInternal(sDebugCaptureData);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_debugcapture.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include \"gx2r_resource.h\"\n\n#include \"cafe/cafe_ppc_interface_varargs.h\"\n#include \"cafe/libraries/tcl/tcl_ring.h\"\n\n#include <fmt/core.h>\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::gx2\n{\n\nstruct GX2Surface;\n\nusing GX2DebugCaptureInterfaceOnShutdownFn =\n   virt_func_ptr<void ()>;\n\nusing GX2DebugCaptureInterfaceSetAllocatorFn =\n   virt_func_ptr<void (GX2RAllocFuncPtr allocFn,\n                       GX2RFreeFuncPtr freeFn)>;\n\nusing GX2DebugCaptureInterfaceOnCaptureStartFn =\n   virt_func_ptr<void (virt_ptr<const char> filename)>;\n\nusing GX2DebugCaptureInterfaceOnCaptureEndFn =\n   virt_func_ptr<void ()>;\n\nusing GX2DebugCaptureInterfaceIsCaptureEnabledFn =\n   virt_func_ptr<BOOL ()>;\n\nusing GX2DebugCaptureInterfaceOnAllocFn =\n   virt_func_ptr<void (virt_ptr<void> ptr, uint32_t size, uint32_t align)>;\n\nusing GX2DebugCaptureInterfaceOnFreeFn =\n   virt_func_ptr<void (virt_ptr<void> ptr)>;\n\nusing GX2DebugCaptureInterfaceOnInvalidateFn =\n   virt_func_ptr<void (virt_ptr<void> ptr, uint32_t size)>;\n\n// Note: Real API from gx2.rpl does not actually pass drc scanbuffer, but it\n// would be useful for our pm4 capture to have it.\nusing GX2DebugCaptureInterfaceSetScanbufferFn =\n   virt_func_ptr<void (virt_ptr<GX2Surface> tvScanbuffer,\n                       virt_ptr<GX2Surface> drcScanbuffer)>;\n\nusing GX2DebugCaptureInterfaceOnSwapFn =\n   virt_func_ptr<void (virt_ptr<GX2Surface> tvScanbuffer,\n                       virt_ptr<GX2Surface> drcScanbuffer)>;\n\nusing GX2DebugCaptureInterfaceSubmitFn =\n   virt_func_ptr<tcl::TCLStatus (virt_ptr<uint32_t> buffer,\n                                 uint32_t numWords,\n                                 virt_ptr<tcl::TCLSubmitFlags> submitFlags,\n                                 virt_ptr<tcl::TCLTimestamp> lastSubmittedTimestamp)>;\n\nstruct GX2DebugCaptureInterface\n{\n   //! Must be set to GX2DebugCaptureInterfaceVersion::Version1\n   be2_val<GX2DebugCaptureInterfaceVersion> version;\n\n   //! Called from GX2Shutdown.\n   be2_val<GX2DebugCaptureInterfaceOnShutdownFn> onShutdown;\n\n   //! Called from GX2DebugSetCaptureInterface with the default gx2 allocators.\n   be2_val<GX2DebugCaptureInterfaceSetAllocatorFn> setAllocator;\n\n   //! Called from GX2DebugCaptureStart, the filename is first argument passed\n   //! in to GX2DebugCaptureStart.\n   be2_val<GX2DebugCaptureInterfaceOnCaptureStartFn> onCaptureStart;\n\n   //! Called from GX2DebugCaptureEnd.\n   be2_val<GX2DebugCaptureInterfaceOnCaptureEndFn> onCaptureEnd;\n\n   //! Check if capture is enabled.\n   be2_val<GX2DebugCaptureInterfaceIsCaptureEnabledFn> isCaptureEnabled;\n\n   //! Called when graphics memory is allocated.\n   be2_val<GX2DebugCaptureInterfaceOnAllocFn> onAlloc;\n\n   //! Called when graphics memory is freed.\n   be2_val<GX2DebugCaptureInterfaceOnFreeFn> onFree;\n\n   //! Called when graphics memory is invalidated.\n   be2_val<GX2DebugCaptureInterfaceOnInvalidateFn> onInvalidate;\n\n   //! Called from GX2DebugCaptureStart with the TV scan buffer.\n   be2_val<GX2DebugCaptureInterfaceSetScanbufferFn> setScanbuffer;\n\n   //! Called from GX2SwapScanBuffers with the TV scan buffer.\n   be2_val<GX2DebugCaptureInterfaceOnSwapFn> onSwapScanBuffers;\n\n   //! Called when a command buffer is ready to be submitted to ring buffer.\n   //! Note that it seems we must call TCLSubmitToRing from this callback\n   //! because gx2 will not do it when capturing.\n   be2_val<GX2DebugCaptureInterfaceSubmitFn> submitToRing;\n};\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x00, version);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x04, onShutdown);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x08, setAllocator);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x0C, onCaptureStart);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x10, onCaptureEnd);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x14, isCaptureEnabled);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x18, onAlloc);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x1C, onFree);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x20, onInvalidate);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x24, setScanbuffer);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x28, onSwapScanBuffers);\nCHECK_OFFSET(GX2DebugCaptureInterface, 0x2C, submitToRing);\nCHECK_SIZE(GX2DebugCaptureInterface, 0x30);\n\nBOOL\nGX2DebugSetCaptureInterface(virt_ptr<GX2DebugCaptureInterface> captureInterface);\n\nvoid\nGX2DebugCaptureStart(virt_ptr<const char> filename,\n                     BOOL noCallDrawDone);\n\nvoid\nGX2DebugCaptureEnd(BOOL noCallDrawDone);\n\nvoid\nGX2DebugCaptureFrame(virt_ptr<const char> filename);\n\nvoid\nGX2DebugCaptureFrames(virt_ptr<const char> filename,\n                      uint32_t numFrames);\n\nvoid\nGX2DebugTagUserString(GX2DebugTag tag,\n                      virt_ptr<const char> fmt,\n                      var_args va);\n\nvoid\nGX2DebugTagUserStringVA(GX2DebugTag tag,\n                        virt_ptr<const char> fmt,\n                        virt_ptr<va_list> vaList);\n\nvoid\nGX2NotifyMemAlloc(virt_ptr<void> ptr,\n                  uint32_t size,\n                  uint32_t align);\n\nvoid\nGX2NotifyMemFree(virt_ptr<void> ptr);\n\nnamespace internal\n{\n\nBOOL\ndebugCaptureEnabled();\n\nvoid\ndebugCaptureAlloc(virt_ptr<void> ptr,\n                  uint32_t size,\n                  uint32_t align);\n\nvoid\ndebugCaptureFree(virt_ptr<void> ptr);\n\nvoid\ndebugCaptureInvalidate(virt_ptr<void> ptr,\n                       uint32_t size);\n\nvoid\ndebugCaptureShutdown();\n\ntcl::TCLStatus\ndebugCaptureSubmit(virt_ptr<uint32_t> buffer,\n                   uint32_t numWords,\n                   virt_ptr<tcl::TCLSubmitFlags> submitFlags,\n                   virt_ptr<tcl::TCLTimestamp> lastSubmittedTimestamp);\n\nvoid\ndebugCaptureSwap(virt_ptr<GX2Surface> tvScanbuffer,\n                 virt_ptr<GX2Surface> drcScanbuffer);\n\nvoid\ndebugCaptureTagGroup(GX2DebugTag tagId,\n                     std::string_view str = {});\n\ntemplate<typename... Args>\ninline void\ndebugCaptureTagGroup(GX2DebugTag tagId,\n                     const char *fmt,\n                     Args &&... args)\n{\n   if (debugCaptureEnabled()) {\n      debugCaptureTagGroup(tagId,\n                           std::string_view { fmt::format(fmt, args...) });\n   }\n}\n\n} // namespace internal\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_display.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_debug.h\"\n#include \"gx2_debugcapture.h\"\n#include \"gx2_internal_pm4cap.h\"\n#include \"gx2_display.h\"\n#include \"gx2_enum_string.h\"\n#include \"gx2_event.h\"\n#include \"gx2_format.h\"\n#include \"gx2_surface.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n#include <libcpu/cpu_formatters.h>\n#include <libgpu/gpu_ringbuffer.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace cafe::coreinit;\n\nstruct StaticDisplayData\n{\n   be2_struct<GX2ColorBuffer> tvScanBuffer;\n   be2_struct<GX2ColorBuffer> drcScanBuffer;\n   be2_val<GX2TVScanMode> tvScanMode = GX2TVScanMode::P1080;\n   be2_val<GX2TVRenderMode> tvRenderMode = GX2TVRenderMode::Disabled;\n   be2_val<GX2BufferingMode> tvBufferingMode;\n   be2_val<GX2DrcRenderMode> drcRenderMode = GX2DrcRenderMode::Disabled;\n   be2_val<GX2BufferingMode> drcBufferingMode;\n   be2_val<GX2DRCConnectCallbackFunction> drcConnectCallback;\n   be2_val<uint32_t> swapInterval = 1u;\n};\n\nstatic virt_ptr<StaticDisplayData>\nsDisplayData = nullptr;\n\nstatic std::pair<unsigned, unsigned>\ngetTVSize(GX2TVRenderMode mode)\n{\n   switch (mode) {\n   case GX2TVRenderMode::Standard480p:\n      return { 640, 480 };\n   case GX2TVRenderMode::Wide480p:\n      return { 854, 480 };\n   case GX2TVRenderMode::Wide720p:\n      return { 1280, 720 };\n   case GX2TVRenderMode::Unk720p:\n      return { 1280, 720 };\n   case GX2TVRenderMode::Wide1080p:\n      return { 1920, 1080 };\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2TVRenderMode {}\", to_string(mode)));\n   }\n}\n\nstatic unsigned\ngetBpp(GX2SurfaceFormat format)\n{\n   auto bpp = internal::getSurfaceFormatBytesPerElement(format);\n   decaf_assert(bpp > 0, fmt::format(\"Unexpected GX2SurfaceFormat {}\", to_string(format)));\n   return bpp;\n}\n\nstatic unsigned\ngetNumBuffers(GX2BufferingMode mode)\n{\n   switch (mode) {\n   case GX2BufferingMode::Single:\n      return 1;\n   case GX2BufferingMode::Double:\n      return 2;\n   case GX2BufferingMode::Triple:\n      return 3;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2BufferingMode {}\", to_string(mode)));\n   }\n}\n\nstatic void\ninitialiseScanBuffer(virt_ptr<GX2ColorBuffer> buffer,\n                     uint32_t width,\n                     uint32_t height,\n                     GX2SurfaceFormat format)\n{\n   std::memset(buffer.get(), 0, sizeof(GX2ColorBuffer));\n   buffer->surface.use = GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::Texture;\n   buffer->surface.width = width;\n   buffer->surface.height = height;\n   buffer->surface.mipLevels = 1u;\n   buffer->surface.dim = GX2SurfaceDim::Texture2D;\n   buffer->surface.swizzle = 0u;\n   buffer->surface.depth = 1u;\n   buffer->surface.tileMode = GX2TileMode::Default;\n   buffer->surface.format = format;\n   buffer->surface.mipmaps = nullptr;\n   buffer->surface.aa = GX2AAMode::Mode1X;\n   buffer->viewFirstSlice = 0u;\n   buffer->viewNumSlices = 1u;\n   buffer->viewMip = 0u;\n   GX2CalcSurfaceSizeAndAlignment(virt_addrof(buffer->surface));\n   GX2InitColorBufferRegs(buffer);\n   buffer->surface.use |= GX2SurfaceUse::ScanBuffer;\n}\n\nvoid\nGX2CalcDRCSize(GX2DrcRenderMode drcRenderMode,\n               GX2SurfaceFormat surfaceFormat,\n               GX2BufferingMode bufferingMode,\n               virt_ptr<uint32_t> outSize,\n               virt_ptr<uint32_t> outUnk)\n{\n   auto bytesPerPixel = internal::getSurfaceFormatBytesPerElement(surfaceFormat);\n   auto numBuffers = getNumBuffers(bufferingMode);\n\n   *outSize = 864 * 480 * bytesPerPixel * numBuffers;\n   *outUnk = 0u;\n}\n\nvoid\nGX2CalcTVSize(GX2TVRenderMode tvRenderMode,\n              GX2SurfaceFormat surfaceFormat,\n              GX2BufferingMode bufferingMode,\n              virt_ptr<uint32_t> outSize,\n              virt_ptr<uint32_t> outUnk)\n{\n   unsigned width, height;\n   std::tie(width, height) = getTVSize(tvRenderMode);\n\n   auto bytesPerPixel = getBpp(surfaceFormat);\n   auto numBuffers = getNumBuffers(bufferingMode);\n\n   *outSize = width * height * bytesPerPixel * numBuffers;\n   *outUnk = 0u;\n}\n\nvoid\nGX2CopyColorBufferToScanBuffer(virt_ptr<GX2ColorBuffer> buffer,\n                               GX2ScanTarget scanTarget)\n{\n   internal::debugCaptureTagGroup(GX2DebugTag::CopyColorBufferToScanBuffer,\n                                  \"{}, {}\", buffer, scanTarget);\n\n   auto addrImage = OSEffectiveToPhysical(virt_cast<virt_addr>(buffer->surface.image));\n   auto cb_color_frag = latte::CB_COLORN_FRAG::get(0);\n   auto cb_color_base = latte::CB_COLORN_BASE::get(0)\n      .BASE_256B(addrImage >> 8);\n\n   if (buffer->surface.aa != 0) {\n      auto addrAA = OSEffectiveToPhysical(virt_cast<virt_addr>(buffer->aaBuffer));\n      cb_color_frag = cb_color_frag.BASE_256B(addrAA >> 8);\n   }\n\n   // TODO: We should check this function, this was added\n   // as a temporary solution to new crashes.\n   if (buffer->viewNumSlices == 0) {\n      buffer->viewNumSlices = 1u;\n   }\n\n   GX2InitColorBufferRegs(buffer);\n\n   internal::writePM4(latte::pm4::DecafCopyColorToScan {\n      latte::pm4::ScanTarget(scanTarget),\n      cb_color_base,\n      cb_color_frag,\n      buffer->surface.width,\n      buffer->surface.height,\n      buffer->regs.cb_color_size,\n      buffer->regs.cb_color_info,\n      buffer->regs.cb_color_view,\n      buffer->regs.cb_color_mask\n   });\n\n   internal::debugCaptureTagGroup(GX2DebugTag::CopyColorBufferToScanBuffer,\n                                  \"{}, {}\", buffer, scanTarget);\n}\n\nBOOL\nGX2GetLastFrame(GX2ScanTarget scanTarget,\n                virt_ptr<GX2Texture> texture)\n{\n   return FALSE;\n}\n\nBOOL\nGX2GetLastFrameB(GX2ScanTarget scanTarget,\n                 virt_ptr<GX2Texture> texture)\n{\n   return FALSE;\n}\n\nBOOL\nGX2GetLastFrameGamma(GX2ScanTarget scanTarget,\n                     virt_ptr<float> outGamma)\n{\n   return FALSE;\n}\n\nBOOL\nGX2GetLastFrameGammaA(GX2ScanTarget scanTarget,\n                      virt_ptr<float> outGamma)\n{\n   return GX2GetLastFrameGamma(scanTarget, outGamma);\n}\n\nBOOL\nGX2GetLastFrameGammaB(GX2ScanTarget scanTarget,\n                      virt_ptr<float> outGamma)\n{\n   return FALSE;\n}\n\nGX2TVScanMode\nGX2GetSystemTVScanMode()\n{\n   return sDisplayData->tvScanMode;\n}\n\nGX2DrcRenderMode\nGX2GetSystemDRCMode()\n{\n   return sDisplayData->drcRenderMode;\n}\n\nGX2AspectRatio\nGX2GetSystemTVAspectRatio()\n{\n   switch (sDisplayData->tvScanMode) {\n   case GX2TVScanMode::None:\n   case GX2TVScanMode::I480:\n   case GX2TVScanMode::P480:\n      return GX2AspectRatio::Normal;\n   case GX2TVScanMode::P720:\n   case GX2TVScanMode::I1080:\n   case GX2TVScanMode::P1080:\n      return GX2AspectRatio::Widescreen;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2TVScanMode {}\",\n                              to_string(sDisplayData->tvScanMode)));\n   }\n}\n\nuint32_t\nGX2GetSwapInterval()\n{\n   return sDisplayData->swapInterval;\n}\n\nBOOL\nGX2IsVideoOutReady()\n{\n   return TRUE;\n}\n\nvoid\nGX2SetDRCBuffer(virt_ptr<void> buffer,\n                uint32_t size,\n                GX2DrcRenderMode drcRenderMode,\n                GX2SurfaceFormat surfaceFormat,\n                GX2BufferingMode bufferingMode)\n{\n   constexpr auto width = 854u, height = 480u;\n\n   initialiseScanBuffer(virt_addrof(sDisplayData->drcScanBuffer),\n                        width, height,\n                        surfaceFormat);\n   sDisplayData->drcScanBuffer.surface.image = virt_cast<uint8_t *>(buffer);\n   sDisplayData->drcRenderMode = drcRenderMode;\n   sDisplayData->drcBufferingMode = bufferingMode;\n\n   // Using a command buffer is to communicate this data to the GPU is our\n   // decaf specific hack, therefore we must write it directly instead of\n   // infecting the GX2 command buffer with our fake command.\n   std::array<uint32_t, 6> commandBuffer;\n   auto commandBufferPos = 0u;\n   internal::writePM4(commandBuffer.data(), commandBufferPos,\n      latte::pm4::DecafSetBuffer {\n         latte::pm4::ScanTarget::DRC,\n         OSEffectiveToPhysical(virt_cast<virt_addr>(buffer)),\n         bufferingMode, // bufferingMode is conveniently equal to the number of buffers\n         static_cast<uint32_t>(width),\n         static_cast<uint32_t>(height)\n      });\n   gpu::ringbuffer::write({ commandBuffer.data(), commandBufferPos });\n}\n\nGX2DRCConnectCallbackFunction\nGX2SetDRCConnectCallback(uint32_t id,\n                         GX2DRCConnectCallbackFunction callback)\n{\n   auto old = sDisplayData->drcConnectCallback;\n   sDisplayData->drcConnectCallback = callback;\n\n   if (callback) {\n      cafe::invoke(cpu::this_core::state(),\n                   callback,\n                   id,\n                   TRUE);\n   }\n\n   return old;\n}\n\nvoid\nGX2SetDRCEnable(BOOL enable)\n{\n}\n\nvoid\nGX2SetDRCScale(uint32_t x,\n               uint32_t y)\n{\n}\n\nvoid\nGX2SetSwapInterval(uint32_t interval)\n{\n   if (interval == sDisplayData->swapInterval) {\n      return;\n   }\n\n   sDisplayData->swapInterval = interval;\n}\n\nvoid\nGX2SetTVBuffer(virt_ptr<void> buffer,\n               uint32_t size,\n               GX2TVRenderMode tvRenderMode,\n               GX2SurfaceFormat surfaceFormat,\n               GX2BufferingMode bufferingMode)\n{\n   unsigned width, height;\n   std::tie(width, height) = getTVSize(tvRenderMode);\n\n   initialiseScanBuffer(virt_addrof(sDisplayData->tvScanBuffer),\n                        width, height, surfaceFormat);\n   sDisplayData->tvScanBuffer.surface.image = virt_cast<uint8_t *>(buffer);\n   sDisplayData->tvRenderMode = tvRenderMode;\n   sDisplayData->tvBufferingMode = bufferingMode;\n\n   /*\n   auto pitch = width;\n   AVMSetTVScale(width, height);\n   AVMSetTVBufferAttr(bufferingMode, tvRenderMode, pitch);\n   */\n\n   // Using a command buffer is to communicate this data to the GPU is our\n   // decaf specific hack, therefore we must write it directly instead of\n   // infecting the GX2 command buffer with our fake command.\n   std::array<uint32_t, 6> commandBuffer;\n   auto commandBufferPos = 0u;\n   internal::writePM4(commandBuffer.data(), commandBufferPos,\n      latte::pm4::DecafSetBuffer {\n         latte::pm4::ScanTarget::TV,\n         OSEffectiveToPhysical(virt_cast<virt_addr>(buffer)),\n         bufferingMode, // bufferingMode is conveniently equal to the number of buffers\n         static_cast<uint32_t>(width),\n         static_cast<uint32_t>(height)\n      });\n   gpu::ringbuffer::write({ commandBuffer.data(), commandBufferPos });\n}\n\nvoid\nGX2SetTVEnable(BOOL enable)\n{\n}\n\nvoid\nGX2SetTVScale(uint32_t x,\n              uint32_t y)\n{\n}\n\nvoid\nGX2SwapScanBuffers()\n{\n   internal::debugCaptureTagGroup(GX2DebugTag::SwapScanBuffers);\n\n   internal::onSwap();\n   internal::writePM4(latte::pm4::DecafSwapBuffers { });\n\n   internal::debugCaptureTagGroup(GX2DebugTag::SwapScanBuffers);\n   internal::captureSwap();\n   internal::debugCaptureSwap(virt_addrof(sDisplayData->tvScanBuffer.surface),\n                              virt_addrof(sDisplayData->drcScanBuffer.surface));\n}\n\nnamespace internal\n{\n\nvirt_ptr<GX2Surface>\ngetTvScanBuffer()\n{\n   return virt_addrof(sDisplayData->tvScanBuffer.surface);\n}\n\nvirt_ptr<GX2Surface>\ngetDrcScanBuffer()\n{\n   return virt_addrof(sDisplayData->drcScanBuffer.surface);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerDisplaySymbols()\n{\n   RegisterFunctionExport(GX2CalcDRCSize);\n   RegisterFunctionExport(GX2CalcTVSize);\n   RegisterFunctionExport(GX2CopyColorBufferToScanBuffer);\n   RegisterFunctionExport(GX2GetLastFrame);\n   RegisterFunctionExportName(\"_GX2GetLastFrameB\", GX2GetLastFrameB);\n   RegisterFunctionExport(GX2GetLastFrameGamma);\n   RegisterFunctionExport(GX2GetLastFrameGammaA);\n   RegisterFunctionExport(GX2GetLastFrameGammaB);\n   RegisterFunctionExport(GX2GetSystemTVScanMode);\n   RegisterFunctionExport(GX2GetSystemDRCMode);\n   RegisterFunctionExport(GX2GetSystemTVAspectRatio);\n   RegisterFunctionExport(GX2GetSwapInterval);\n   RegisterFunctionExport(GX2IsVideoOutReady);\n   RegisterFunctionExport(GX2SetDRCBuffer);\n   RegisterFunctionExport(GX2SetDRCConnectCallback);\n   RegisterFunctionExport(GX2SetDRCEnable);\n   RegisterFunctionExport(GX2SetDRCScale);\n   RegisterFunctionExport(GX2SetSwapInterval);\n   RegisterFunctionExport(GX2SetTVBuffer);\n   RegisterFunctionExport(GX2SetTVEnable);\n   RegisterFunctionExport(GX2SetTVScale);\n   RegisterFunctionExport(GX2SwapScanBuffers);\n\n   RegisterDataInternal(sDisplayData);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_display.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include \"gx2_surface.h\"\n#include \"gx2_texture.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\nusing GX2DRCConnectCallbackFunction = virt_func_ptr<void(uint32_t, BOOL)>;\n\nvoid\nGX2CalcTVSize(GX2TVRenderMode tvRenderMode,\n              GX2SurfaceFormat surfaceFormat,\n              GX2BufferingMode bufferingMode,\n              virt_ptr<uint32_t> outSize,\n              virt_ptr<uint32_t> outUnk);\n\nvoid\nGX2CalcDRCSize(GX2DrcRenderMode drcRenderMode,\n               GX2SurfaceFormat surfaceFormat,\n               GX2BufferingMode bufferingMode,\n               virt_ptr<uint32_t> outSize,\n               virt_ptr<uint32_t> outUnk);\n\nvoid\nGX2CopyColorBufferToScanBuffer(virt_ptr<GX2ColorBuffer> buffer,\n                               GX2ScanTarget scanTarget);\n\nBOOL\nGX2GetLastFrame(GX2ScanTarget scanTarget,\n                virt_ptr<GX2Texture> texture);\n\nBOOL\nGX2GetLastFrameB(GX2ScanTarget scanTarget,\n                virt_ptr<GX2Texture> texture);\n\nBOOL\nGX2GetLastFrameGamma(GX2ScanTarget scanTarget,\n                     virt_ptr<float> outGamma);\n\nBOOL\nGX2GetLastFrameGammaA(GX2ScanTarget scanTarget,\n                      virt_ptr<float> outGamma);\n\nBOOL\nGX2GetLastFrameGammaB(GX2ScanTarget scanTarget,\n                      virt_ptr<float> outGamma);\n\nGX2TVScanMode\nGX2GetSystemTVScanMode();\n\nGX2DrcRenderMode\nGX2GetSystemDRCMode();\n\nGX2AspectRatio\nGX2GetSystemTVAspectRatio();\n\nuint32_t\nGX2GetSwapInterval();\n\nBOOL\nGX2IsVideoOutReady();\n\nvoid\nGX2SetDRCBuffer(virt_ptr<void> buffer,\n                uint32_t size,\n                GX2DrcRenderMode drcRenderMode,\n                GX2SurfaceFormat surfaceFormat,\n                GX2BufferingMode bufferingMode);\n\nGX2DRCConnectCallbackFunction\nGX2SetDRCConnectCallback(uint32_t id,\n                         GX2DRCConnectCallbackFunction callback);\n\nvoid\nGX2SetDRCEnable(BOOL enable);\n\nvoid\nGX2SetDRCScale(uint32_t x,\n               uint32_t y);\n\nvoid\nGX2SetSwapInterval(uint32_t interval);\n\nvoid\nGX2SetTVBuffer(virt_ptr<void> buffer,\n               uint32_t size,\n               GX2TVRenderMode tvRenderMode,\n               GX2SurfaceFormat surfaceFormat,\n               GX2BufferingMode bufferingMode);\n\nvoid\nGX2SetTVEnable(BOOL enable);\n\nvoid\nGX2SetTVScale(uint32_t x,\n              uint32_t y);\n\nvoid\nGX2SwapScanBuffers();\n\nnamespace internal\n{\n\nvirt_ptr<GX2Surface>\ngetTvScanBuffer();\n\nvirt_ptr<GX2Surface>\ngetDrcScanBuffer();\n\n} // namespace internal\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_displaylist.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_debugcapture.h\"\n#include \"gx2_displaylist.h\"\n#include \"gx2_enum_string.h\"\n#include \"gx2_fetchshader.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_shaders.h\"\n#include \"gx2_state.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <array>\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace cafe::coreinit;\n\nvoid\nGX2BeginDisplayListEx(virt_ptr<void> displayList,\n                      uint32_t bytes,\n                      BOOL profilingEnabled)\n{\n   internal::beginUserCommandBuffer(virt_cast<uint32_t *>(displayList),\n                                    bytes,\n                                    profilingEnabled);\n}\n\nuint32_t\nGX2EndDisplayList(virt_ptr<void> displayList)\n{\n   auto size = internal::endUserCommandBuffer(virt_cast<uint32_t *>(displayList));\n\n   if (internal::debugCaptureEnabled()) {\n      internal::debugCaptureInvalidate(displayList, size);\n   }\n\n   return size;\n}\n\nBOOL\nGX2GetDisplayListWriteStatus()\n{\n   return internal::getActiveCommandBuffer()->isUserBuffer;\n}\n\nBOOL\nGX2GetCurrentDisplayList(virt_ptr<virt_ptr<void>> outDisplayList,\n                         virt_ptr<uint32_t> outSize)\n{\n   auto cb = internal::getActiveCommandBuffer();\n\n   if (!cb->isUserBuffer) {\n      return FALSE;\n   }\n\n   if (outDisplayList) {\n      *outDisplayList = cb->buffer;\n   }\n\n   if (outSize) {\n      *outSize = 4 * cb->bufferSizeWords;\n   }\n\n   return TRUE;\n}\n\nvoid\nGX2DirectCallDisplayList(virt_ptr<void> displayList,\n                         uint32_t bytes)\n{\n   auto cb = internal::getActiveCommandBuffer();\n   if (!cb->isUserBuffer) {\n      internal::flushCommandBuffer(256, FALSE);\n   }\n\n   internal::queueCommandBuffer(virt_cast<uint32_t *>(displayList),\n                                bytes / sizeof(uint32_t),\n                                nullptr,\n                                TRUE);\n}\n\nvoid\nGX2CallDisplayList(virt_ptr<void> displayList,\n                   uint32_t bytes)\n{\n   internal::writePM4(latte::pm4::IndirectBufferCallPriv {\n      OSEffectiveToPhysical(virt_cast<virt_addr>(displayList)),\n      bytes / 4\n   });\n}\n\nvoid\nGX2CopyDisplayList(virt_ptr<void> displayList,\n                   uint32_t bytes)\n{\n   auto numWords = bytes / 4;\n   auto cb = internal::getWriteCommandBuffer(numWords);\n   cb->writeGatherPtr.write(virt_cast<uint32_t *>(displayList), numWords);\n   cb->cmdSize = numWords;\n   decaf_check(cb->cmdSize == cb->cmdSizeTarget);\n}\n\nvoid\nGX2PatchDisplayList(virt_ptr<void> displayList,\n                    GX2PatchShaderType type,\n                    uint32_t byteOffset,\n                    virt_ptr<void> shader)\n{\n   auto addr = virt_addr { 0u };\n\n   switch (type) {\n   case GX2PatchShaderType::FetchShader:\n   {\n      auto fetchShader = virt_cast<GX2FetchShader *>(shader);\n      addr = virt_cast<virt_addr>(fetchShader->data);\n      break;\n   }\n   case GX2PatchShaderType::VertexShader:\n   {\n      auto vertexShader = virt_cast<GX2VertexShader *>(shader);\n\n      if (vertexShader->data) {\n         addr = virt_cast<virt_addr>(vertexShader->data);\n      } else {\n         addr = virt_cast<virt_addr>(vertexShader->gx2rData.buffer);\n      }\n\n      break;\n   }\n   case GX2PatchShaderType::GeometryVertexShader:\n   {\n      auto geometryShader = virt_cast<GX2GeometryShader *>(shader);\n\n      if (geometryShader->vertexShaderData) {\n         addr = virt_cast<virt_addr>(geometryShader->vertexShaderData);\n      } else {\n         addr = virt_cast<virt_addr>(geometryShader->gx2rVertexShaderData.buffer);\n      }\n\n      break;\n   }\n   case GX2PatchShaderType::GeometryShader:\n   {\n      auto geometryShader = virt_cast<GX2GeometryShader *>(shader);\n\n      if (geometryShader->data) {\n         addr = virt_cast<virt_addr>(geometryShader->data);\n      } else {\n         addr = virt_cast<virt_addr>(geometryShader->gx2rData.buffer);\n      }\n\n      break;\n   }\n   case GX2PatchShaderType::PixelShader:\n   {\n      auto pixelShader = virt_cast<GX2PixelShader *>(shader);\n\n      if (pixelShader->data) {\n         addr = virt_cast<virt_addr>(pixelShader->data);\n      } else {\n         addr = virt_cast<virt_addr>(pixelShader->gx2rData.buffer);\n      }\n\n      break;\n   }\n   default:\n      decaf_abort(fmt::format(\"Unsupported GX2PatchShaderType {}\", to_string(type)));\n   }\n\n   // Apply the actual patch\n   auto words = virt_cast<uint32_t *>(displayList);\n   auto idx = byteOffset / 4;\n   words[idx + 2] = OSEffectiveToPhysical(addr) >> 8;\n}\n\nvoid\nLibrary::registerDisplayListSymbols()\n{\n   RegisterFunctionExport(GX2BeginDisplayListEx);\n   RegisterFunctionExport(GX2EndDisplayList);\n   RegisterFunctionExport(GX2DirectCallDisplayList);\n   RegisterFunctionExport(GX2CallDisplayList);\n   RegisterFunctionExport(GX2GetDisplayListWriteStatus);\n   RegisterFunctionExport(GX2GetCurrentDisplayList);\n   RegisterFunctionExport(GX2CopyDisplayList);\n   RegisterFunctionExport(GX2PatchDisplayList);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_displaylist.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_displaylist Display List\n * \\ingroup gx2\n * @{\n */\n\nconstexpr auto GX2DisplayListAlign = 0x20u;\n\nvoid\nGX2BeginDisplayListEx(virt_ptr<void> displayList,\n                      uint32_t bytes,\n                      BOOL profilingEnabled);\n\nuint32_t\nGX2EndDisplayList(virt_ptr<void> displayList);\n\nvoid\nGX2DirectCallDisplayList(virt_ptr<void> displayList,\n                         uint32_t bytes);\n\nvoid\nGX2CallDisplayList(virt_ptr<void> displayList,\n                   uint32_t bytes);\n\nBOOL\nGX2GetDisplayListWriteStatus();\n\nBOOL\nGX2GetCurrentDisplayList(virt_ptr<virt_ptr<void>> outDisplayList,\n                         virt_ptr<uint32_t> outSize);\n\nvoid\nGX2CopyDisplayList(virt_ptr<void> displayList,\n                   uint32_t bytes);\n\nvoid\nGX2PatchDisplayList(virt_ptr<void> displayList,\n                    GX2PatchShaderType type,\n                    uint32_t byteOffset,\n                    virt_ptr<void> shader);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_draw.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_draw.h\"\n#include \"gx2_enum_string.h\"\n#include \"gx2_shaders.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <common/decaf_assert.h>\n#include <cstring>\n#include <fmt/core.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace cafe::coreinit;\n\nvoid\nGX2SetAttribBuffer(uint32_t index,\n                   uint32_t size,\n                   uint32_t stride,\n                   virt_ptr<void> buffer)\n{\n   latte::pm4::SetVtxResource res;\n   std::memset(&res, 0, sizeof(latte::pm4::SetVtxResource));\n\n   res.id = (latte::SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 + index) * 7;\n   res.baseAddress = OSEffectiveToPhysical(virt_cast<virt_addr>(buffer));\n\n   res.word1 = res.word1\n      .SIZE(size - 1);\n\n   res.word2 = res.word2\n      .STRIDE(stride);\n\n   res.word6 = res.word6\n      .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER);\n\n   internal::writePM4(res);\n}\n\nvoid\nGX2DrawEx(GX2PrimitiveMode mode,\n          uint32_t count,\n          uint32_t offset,\n          uint32_t numInstances)\n{\n   auto vgt_draw_initiator = latte::VGT_DRAW_INITIATOR::get(0);\n\n   internal::writePM4(latte::pm4::SetControlConstant {\n      latte::Register::SQ_VTX_BASE_VTX_LOC,\n      offset\n   });\n\n   internal::writePM4(latte::pm4::SetConfigReg {\n      latte::Register::VGT_PRIMITIVE_TYPE,\n      mode\n   });\n\n   internal::writePM4(latte::pm4::NumInstances {\n      numInstances\n   });\n\n   internal::writePM4(latte::pm4::DrawIndexAuto {\n      count,\n      vgt_draw_initiator\n   });\n}\n\nvoid\nGX2DrawEx2(GX2PrimitiveMode mode,\n           uint32_t count,\n           uint32_t offset,\n           uint32_t numInstances,\n           uint32_t baseInstance)\n{\n   internal::writePM4(latte::pm4::SetControlConstant {\n      latte::Register::SQ_VTX_START_INST_LOC,\n      latte::SQ_VTX_START_INST_LOC::get(0)\n         .OFFSET(baseInstance)\n         .value\n   });\n\n   GX2DrawEx(mode, count, offset, numInstances);\n\n   internal::writePM4(latte::pm4::SetControlConstant {\n      latte::Register::SQ_VTX_START_INST_LOC,\n      latte::SQ_VTX_START_INST_LOC::get(0)\n         .OFFSET(0)\n         .value\n   });\n}\n\nvoid\nGX2DrawIndexedEx(GX2PrimitiveMode mode,\n                 uint32_t count,\n                 GX2IndexType indexType,\n                 virt_ptr<void> indices,\n                 uint32_t offset,\n                 uint32_t numInstances)\n{\n   auto index_type = latte::VGT_INDEX_TYPE::INDEX_16;\n   auto swap_mode = latte::VGT_DMA_SWAP::NONE;\n\n   switch (indexType) {\n   case GX2IndexType::U16:\n      index_type = latte::VGT_INDEX_TYPE::INDEX_16;\n      swap_mode = latte::VGT_DMA_SWAP::SWAP_16_BIT;\n      break;\n   case GX2IndexType::U16_LE:\n      index_type = latte::VGT_INDEX_TYPE::INDEX_16;\n      swap_mode = latte::VGT_DMA_SWAP::NONE;\n      break;\n   case GX2IndexType::U32:\n      index_type = latte::VGT_INDEX_TYPE::INDEX_32;\n      swap_mode = latte::VGT_DMA_SWAP::SWAP_32_BIT;\n      break;\n   case GX2IndexType::U32_LE:\n      index_type = latte::VGT_INDEX_TYPE::INDEX_32;\n      swap_mode = latte::VGT_DMA_SWAP::NONE;\n      break;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2IndexType {}\", to_string(indexType)));\n   }\n\n   auto vgt_dma_index_type = latte::VGT_DMA_INDEX_TYPE::get(0)\n      .INDEX_TYPE(index_type)\n      .SWAP_MODE(swap_mode);\n\n   auto vgt_draw_initiator = latte::VGT_DRAW_INITIATOR::get(0)\n      .SOURCE_SELECT(latte::VGT_DI_SRC_SEL::DMA);\n\n   if (mode & 0x80) {\n      vgt_draw_initiator = vgt_draw_initiator\n         .MAJOR_MODE(latte::VGT_DI_MAJOR_MODE::MODE1);\n   }\n\n   internal::writePM4(latte::pm4::SetControlConstant {\n      latte::Register::SQ_VTX_BASE_VTX_LOC,\n      offset\n   });\n\n   internal::writePM4(latte::pm4::SetConfigReg {\n      latte::Register::VGT_PRIMITIVE_TYPE, mode\n   });\n\n   internal::writePM4(latte::pm4::IndexType {\n      vgt_dma_index_type\n   });\n\n   internal::writePM4(latte::pm4::NumInstances {\n      numInstances\n   });\n\n   internal::writePM4(latte::pm4::DrawIndex2 {\n      static_cast<uint32_t>(-1),\n      OSEffectiveToPhysical(virt_cast<virt_addr>(indices)),\n      count,\n      vgt_draw_initiator\n   });\n}\n\nvoid\nGX2DrawIndexedEx2(GX2PrimitiveMode mode,\n                  uint32_t count,\n                  GX2IndexType indexType,\n                  virt_ptr<void> indices,\n                  uint32_t offset,\n                  uint32_t numInstances,\n                  uint32_t baseInstance)\n{\n   internal::writePM4(latte::pm4::SetControlConstant {\n      latte::Register::SQ_VTX_START_INST_LOC,\n      latte::SQ_VTX_START_INST_LOC::get(0)\n         .OFFSET(baseInstance)\n         .value\n   });\n\n   GX2DrawIndexedEx(mode, count, indexType, indices, offset, numInstances);\n\n   internal::writePM4(latte::pm4::SetControlConstant {\n      latte::Register::SQ_VTX_START_INST_LOC,\n      latte::SQ_VTX_START_INST_LOC::get(0)\n         .OFFSET(0)\n         .value\n   });\n}\n\nvoid\nGX2DrawIndexedImmediateEx(GX2PrimitiveMode mode,\n                          uint32_t count,\n                          GX2IndexType indexType,\n                          virt_ptr<void> indices,\n                          uint32_t offset,\n                          uint32_t numInstances)\n{\n   auto index_type = latte::VGT_INDEX_TYPE::INDEX_16;\n   auto swap_mode = latte::VGT_DMA_SWAP::NONE;\n\n   switch (indexType) {\n   case GX2IndexType::U16:\n      index_type = latte::VGT_INDEX_TYPE::INDEX_16;\n      swap_mode = latte::VGT_DMA_SWAP::SWAP_16_BIT;\n      break;\n   case GX2IndexType::U16_LE:\n      index_type = latte::VGT_INDEX_TYPE::INDEX_16;\n      swap_mode = latte::VGT_DMA_SWAP::NONE;\n      break;\n   case GX2IndexType::U32:\n      index_type = latte::VGT_INDEX_TYPE::INDEX_32;\n      swap_mode = latte::VGT_DMA_SWAP::SWAP_32_BIT;\n      break;\n   case GX2IndexType::U32_LE:\n      index_type = latte::VGT_INDEX_TYPE::INDEX_32;\n      swap_mode = latte::VGT_DMA_SWAP::NONE;\n      break;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2IndexType {}\", to_string(indexType)));\n   }\n\n   auto vgt_dma_index_type = latte::VGT_DMA_INDEX_TYPE::get(0)\n      .INDEX_TYPE(index_type)\n      .SWAP_MODE(swap_mode);\n\n   auto vgt_draw_initiator = latte::VGT_DRAW_INITIATOR::get(0)\n      .SOURCE_SELECT(latte::VGT_DI_SRC_SEL::IMMEDIATE);\n\n   internal::writePM4(latte::pm4::SetControlConstant {\n      latte::Register::SQ_VTX_BASE_VTX_LOC,\n      offset\n   });\n\n   internal::writePM4(latte::pm4::SetConfigReg {\n      latte::Register::VGT_PRIMITIVE_TYPE,\n      mode\n   });\n\n   internal::writePM4(latte::pm4::IndexType {\n      vgt_dma_index_type\n   });\n\n   internal::writePM4(latte::pm4::NumInstances {\n      numInstances\n   });\n\n   auto numWords = 0u;\n\n   if (index_type == latte::VGT_INDEX_TYPE::INDEX_16) {\n      numWords = (count + 1) / 2;\n   } else if (index_type == latte::VGT_INDEX_TYPE::INDEX_32) {\n      numWords = count;\n   } else {\n      decaf_abort(fmt::format(\"Invalid index_type {}\", index_type));\n   }\n\n   if (indexType == GX2IndexType::U16) {\n      internal::writePM4(latte::pm4::DrawIndexImmdBE16 {\n         count,\n         vgt_draw_initiator,\n         gsl::make_span(virt_cast<uint32_t *>(indices).get(), numWords)\n      });\n   } else {\n      internal::writePM4(latte::pm4::DrawIndexImmdBE {\n         count,\n         vgt_draw_initiator,\n         gsl::make_span(virt_cast<uint32_t *>(indices).get(), numWords),\n      });\n   }\n}\n\nvoid\nGX2DrawStreamOut(GX2PrimitiveMode mode,\n                 virt_ptr<GX2OutputStream> buffer)\n{\n   internal::writePM4(latte::pm4::SetControlConstant {\n      latte::Register::SQ_VTX_BASE_VTX_LOC,\n      0u\n   });\n\n   internal::writePM4(latte::pm4::SetConfigReg {\n      latte::Register::VGT_PRIMITIVE_TYPE,\n      mode & GX2PrimitiveModeFlags::ModeMask\n   });\n\n   internal::writePM4(latte::pm4::NumInstances {\n      0\n   });\n\n   auto stride = 0u;\n   if (buffer->buffer) {\n      stride = buffer->stride;\n   } else {\n      stride = buffer->gx2rData.elemSize;\n   }\n\n   internal::writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_STRMOUT_DRAW_OPAQUE_VERTEX_STRIDE,\n      stride >> 2\n   });\n\n   internal::writePM4(latte::pm4::CopyDw {\n      latte::pm4::COPY_DW_SELECT::get(0)\n         .SRC(latte::pm4::COPY_DW_SEL_MEMORY)\n         .DST(latte::pm4::COPY_DW_SEL_REGISTER),\n      OSEffectiveToPhysical(virt_cast<virt_addr>(buffer->context)),\n      0u,\n      latte::Register::VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE,\n      0u\n   });\n\n   auto vgt_draw_initiator = latte::VGT_DRAW_INITIATOR::get(0)\n      .SOURCE_SELECT(latte::VGT_DI_SRC_SEL::AUTO_INDEX)\n      .USE_OPAQUE(true);\n\n   if (mode & GX2PrimitiveModeFlags::Tessellate) {\n      internal::writePM4(latte::pm4::IndexType {\n         latte::VGT_DMA_INDEX_TYPE::get(0)\n            .INDEX_TYPE(latte::VGT_INDEX_TYPE::INDEX_32)\n            .SWAP_MODE(latte::VGT_DMA_SWAP::SWAP_32_BIT)\n      });\n\n      vgt_draw_initiator = vgt_draw_initiator\n         .MAJOR_MODE(latte::VGT_DI_MAJOR_MODE::MODE1);\n   }\n\n   // TODO: This type3 packet should have the predicate bool set to true\n   internal::writePM4(latte::pm4::DrawIndexAuto {\n      0u,\n      vgt_draw_initiator\n   });\n}\n\nvoid\nGX2SetPrimitiveRestartIndex(uint32_t index)\n{\n   internal::writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_MULTI_PRIM_IB_RESET_INDX,\n      index\n   });\n}\n\nvoid\nLibrary::registerDrawSymbols()\n{\n   RegisterFunctionExport(GX2SetAttribBuffer);\n   RegisterFunctionExport(GX2DrawEx);\n   RegisterFunctionExport(GX2DrawEx2);\n   RegisterFunctionExport(GX2DrawIndexedEx);\n   RegisterFunctionExport(GX2DrawIndexedEx2);\n   RegisterFunctionExport(GX2DrawIndexedImmediateEx);\n   RegisterFunctionExport(GX2DrawStreamOut);\n   RegisterFunctionExport(GX2SetPrimitiveRestartIndex);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_draw.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\nstruct GX2OutputStream;\n\n/**\n * \\defgroup gx2_draw Draw\n * \\ingroup gx2\n * @{\n */\n\nvoid\nGX2SetAttribBuffer(uint32_t index,\n                   uint32_t size,\n                   uint32_t stride,\n                   virt_ptr<void> buffer);\n\nvoid\nGX2DrawEx(GX2PrimitiveMode mode,\n          uint32_t count,\n          uint32_t offset,\n          uint32_t numInstances);\n\nvoid\nGX2DrawEx2(GX2PrimitiveMode mode,\n           uint32_t count,\n           uint32_t offset,\n           uint32_t numInstances,\n           uint32_t baseInstance);\n\nvoid\nGX2DrawIndexedEx(GX2PrimitiveMode mode,\n                 uint32_t count,\n                 GX2IndexType indexType,\n                 virt_ptr<void> indices,\n                 uint32_t offset,\n                 uint32_t numInstances);\n\nvoid\nGX2DrawIndexedEx2(GX2PrimitiveMode mode,\n                  uint32_t count,\n                  GX2IndexType indexType,\n                  virt_ptr<void> indices,\n                  uint32_t offset,\n                  uint32_t numInstances,\n                  uint32_t baseInstance);\n\nvoid\nGX2DrawIndexedImmediateEx(GX2PrimitiveMode mode,\n                          uint32_t count,\n                          GX2IndexType indexType,\n                          virt_ptr<void> indices,\n                          uint32_t offset,\n                          uint32_t numInstances);\n\nvoid\nGX2DrawStreamOut(GX2PrimitiveMode mode,\n                 virt_ptr<GX2OutputStream> buffer);\n\nvoid\nGX2SetPrimitiveRestartIndex(uint32_t index);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_enum.h",
    "content": "#ifndef CAFE_GX2_ENUM_H\n#define CAFE_GX2_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(gx2)\n\nENUM_BEG(GX2AAMode, uint32_t)\n   ENUM_VALUE(Mode1X,                  0)\n   ENUM_VALUE(Mode2X,                  1)\n   ENUM_VALUE(Mode4X,                  2)\n   ENUM_VALUE(Mode8X,                  3)\nENUM_END(GX2AAMode)\n\nENUM_BEG(GX2AspectRatio, uint32_t)\n   ENUM_VALUE(Normal,                  0)\n   ENUM_VALUE(Widescreen,              1)\nENUM_END(GX2AspectRatio)\n\nENUM_BEG(GX2AttribFormatType, uint32_t)\n   ENUM_VALUE(TYPE_8,                  0x00)\n   ENUM_VALUE(TYPE_4_4,                0x01)\n   ENUM_VALUE(TYPE_16,                 0x02)\n   ENUM_VALUE(TYPE_16_FLOAT,           0x03)\n   ENUM_VALUE(TYPE_8_8,                0x04)\n   ENUM_VALUE(TYPE_32,                 0x05)\n   ENUM_VALUE(TYPE_32_FLOAT,           0x06)\n   ENUM_VALUE(TYPE_16_16,              0x07)\n   ENUM_VALUE(TYPE_16_16_FLOAT,        0x08)\n   ENUM_VALUE(TYPE_10_11_11_FLOAT,     0x09)\n   ENUM_VALUE(TYPE_8_8_8_8,            0x0A)\n   ENUM_VALUE(TYPE_10_10_10_2,         0x0B)\n   ENUM_VALUE(TYPE_32_32,              0x0C)\n   ENUM_VALUE(TYPE_32_32_FLOAT,        0x0D)\n   ENUM_VALUE(TYPE_16_16_16_16,        0x0E)\n   ENUM_VALUE(TYPE_16_16_16_16_FLOAT,  0x0F)\n   ENUM_VALUE(TYPE_32_32_32,           0x10)\n   ENUM_VALUE(TYPE_32_32_32_FLOAT,     0x11)\n   ENUM_VALUE(TYPE_32_32_32_32,        0x12)\n   ENUM_VALUE(TYPE_32_32_32_32_FLOAT,  0x13)\nENUM_END(GX2AttribFormatType)\n\nENUM_BEG(GX2AttribFormatFlags, uint32_t)\n   ENUM_VALUE(INTEGER,                 0x100)\n   ENUM_VALUE(SIGNED,                  0x200)\n   ENUM_VALUE(DEGAMMA,                 0x400)\n   ENUM_VALUE(SCALED,                  0x800)\nENUM_END(GX2AttribFormatFlags)\n\nENUM_BEG(GX2AttribFormat, uint32_t)\n   ENUM_VALUE(UNORM_8,                 0x0)\n   ENUM_VALUE(UNORM_8_8,               0x04)\n   ENUM_VALUE(UNORM_8_8_8_8,           0x0a)\n\n   ENUM_VALUE(UINT_8,                  0x100)\n   ENUM_VALUE(UINT_8_8,                0x104)\n   ENUM_VALUE(UINT_8_8_8_8,            0x10a)\n\n   ENUM_VALUE(SNORM_8,                 0x200)\n   ENUM_VALUE(SNORM_8_8,               0x204)\n   ENUM_VALUE(SNORM_8_8_8_8,           0x20a)\n\n   ENUM_VALUE(SINT_8,                  0x300)\n   ENUM_VALUE(SINT_8_8,                0x304)\n   ENUM_VALUE(SINT_8_8_8_8,            0x30a)\n\n   ENUM_VALUE(FLOAT_32,                0x806)\n   ENUM_VALUE(FLOAT_32_32,             0x80d)\n   ENUM_VALUE(FLOAT_32_32_32,          0x811)\n   ENUM_VALUE(FLOAT_32_32_32_32,       0x813)\nENUM_END(GX2AttribFormat)\n\nENUM_BEG(GX2AttribIndexType, uint32_t)\n   ENUM_VALUE(PerVertex,               0)\n   ENUM_VALUE(PerInstance,             1)\nENUM_END(GX2AttribIndexType)\n\nENUM_BEG(GX2AlphaToMaskMode, uint32_t)\n   ENUM_VALUE(NonDithered,             0)\n   ENUM_VALUE(Dither0,                 1)\n   ENUM_VALUE(Dither90,                2)\n   ENUM_VALUE(Dither180,               3)\n   ENUM_VALUE(Dither270,               4)\nENUM_END(GX2AlphaToMaskMode)\n\nENUM_BEG(GX2BlendMode, uint32_t)\n   ENUM_VALUE(Zero,                    0)\n   ENUM_VALUE(One,                     1)\n   ENUM_VALUE(SrcColor,                2)\n   ENUM_VALUE(InvSrcColor,             3)\n   ENUM_VALUE(SrcAlpha,                4)\n   ENUM_VALUE(InvSrcAlpha,             5)\n   ENUM_VALUE(DestAlpha,               6)\n   ENUM_VALUE(InvDestAlpha,            7)\n   ENUM_VALUE(DestColor,               8)\n   ENUM_VALUE(InvDestColor,            9)\n   ENUM_VALUE(SrcAlphaSat,             10)\n   ENUM_VALUE(BothSrcAlpha,            11)\n   ENUM_VALUE(BothInvSrcAlpha,         12)\n   ENUM_VALUE(BlendFactor,             13)\n   ENUM_VALUE(InvBlendFactor,          14)\n   ENUM_VALUE(Src1Color,               15)\n   ENUM_VALUE(InvSrc1Color,            16)\n   ENUM_VALUE(Src1Alpha,               17)\n   ENUM_VALUE(InvSrc1Alpha,            18)\nENUM_END(GX2BlendMode)\n\nENUM_BEG(GX2BlendCombineMode, uint32_t)\n   ENUM_VALUE(Add,                     0)\n   ENUM_VALUE(Subtract,                1)\n   ENUM_VALUE(Min,                     2)\n   ENUM_VALUE(Max,                     3)\n   ENUM_VALUE(RevSubtract,             4)\nENUM_END(GX2BlendCombineMode)\n\nENUM_BEG(GX2BufferingMode, uint32_t)\n   ENUM_VALUE(Single,                  1)\n   ENUM_VALUE(Double,                  2)\n   ENUM_VALUE(Triple,                  3)\nENUM_END(GX2BufferingMode)\n\nENUM_BEG(GX2ChannelMask, uint8_t)\n   ENUM_VALUE(R,                       1)\n   ENUM_VALUE(G,                       2)\n   ENUM_VALUE(RG,                      3)\n   ENUM_VALUE(B,                       4)\n   ENUM_VALUE(RB,                      5)\n   ENUM_VALUE(GB,                      6)\n   ENUM_VALUE(RGB,                     7)\n   ENUM_VALUE(A,                       8)\n   ENUM_VALUE(RA,                      9)\n   ENUM_VALUE(GA,                      10)\n   ENUM_VALUE(RGA,                     11)\n   ENUM_VALUE(BA,                      12)\n   ENUM_VALUE(RBA,                     13)\n   ENUM_VALUE(GBA,                     14)\n   ENUM_VALUE(RGBA,                    15)\nENUM_END(GX2ChannelMask)\n\nENUM_BEG(GX2CompareFunction, uint32_t)\n   ENUM_VALUE(Never,                   0)\n   ENUM_VALUE(Less,                    1)\n   ENUM_VALUE(Equal,                   2)\n   ENUM_VALUE(LessOrEqual,             3)\n   ENUM_VALUE(Greater,                 4)\n   ENUM_VALUE(NotEqual,                5)\n   ENUM_VALUE(GreaterOrEqual,          6)\n   ENUM_VALUE(Always,                  7)\nENUM_END(GX2CompareFunction)\n\nENUM_BEG(GX2Component, uint32_t)\n   ENUM_VALUE(Mem0,                    0)\n   ENUM_VALUE(Mem1,                    1)\n   ENUM_VALUE(Mem2,                    2)\n   ENUM_VALUE(Mem3,                    3)\n   ENUM_VALUE(Zero,                    4)\n   ENUM_VALUE(One,                     5)\nENUM_END(GX2Component)\n\nFLAGS_BEG(GX2ContextStateFlags, uint32_t)\n   FLAGS_VALUE(ProfilingEnabled,       1 << 0)\n   FLAGS_VALUE(NoShadowDisplayList,    1 << 1)\nFLAGS_END(GX2ContextStateFlags)\n\nENUM_BEG(GX2ClearFlags, uint32_t)\n   ENUM_VALUE(Depth,                   1)\n   ENUM_VALUE(Stencil,                 2)\nENUM_END(GX2ClearFlags)\n\nENUM_BEG(GX2DebugCaptureInterfaceVersion, uint32_t)\n   ENUM_VALUE(Version1,                1)\nENUM_END(GX2DebugCaptureInterfaceVersion)\n\nENUM_BEG(GX2DebugTag, uint32_t)\n   ENUM_VALUE(SetDefaultState,               1)\n   ENUM_VALUE(ClearColor,                    2)\n   ENUM_VALUE(ClearDepthStencil,             3)\n   ENUM_VALUE(ClearBuffers,                  4)\n   ENUM_VALUE(ResolveAAColorBuffer,          7)\n   ENUM_VALUE(ExpandAAColorBuffer,           8)\n   ENUM_VALUE(ExpandDepthBuffer,             9)\n   ENUM_VALUE(ConvertDepthBufferToTexture,   10)\n   ENUM_VALUE(CopyColorBufferToScanBuffer,   11)\n   ENUM_VALUE(SwapScanBuffers,               12)\n   ENUM_VALUE(PerfPassStart,                 13)\n   ENUM_VALUE(PerfPassEnd,                   14)\n   ENUM_VALUE(PerfTagStart,                  15)\n   ENUM_VALUE(PerfTagEnd,                    16)\n   ENUM_VALUE(User,                          0xFEAD0000u)\n   ENUM_VALUE(Group,                         0xFEAE0000u)\nENUM_END(GX2DebugTag)\n\nENUM_BEG(GX2DrcRenderMode, uint32_t)\n   ENUM_VALUE(Disabled,                0)\n   ENUM_VALUE(Single,                  1)\nENUM_END(GX2DrcRenderMode)\n\nENUM_BEG(GX2EndianSwapMode, uint32_t)\n   ENUM_VALUE(None,                    0)\n   ENUM_VALUE(Swap8In16,               1)\n   ENUM_VALUE(Swap8In32,               2)\n   ENUM_VALUE(Default,                 3)\nENUM_END(GX2EndianSwapMode)\n\nENUM_BEG(GX2EventType, uint32_t)\n   ENUM_VALUE(StartOfPipeInterrupt,    0)\n   ENUM_VALUE(EndOfPipeInterrupt,      1)\n   ENUM_VALUE(Vsync,                   2)\n   ENUM_VALUE(Flip,                    3)\n   ENUM_VALUE(DisplayListOverrun,      4)\n   ENUM_VALUE(Max,                     5)\n   ENUM_VALUE(StopAppIoThread,         0xFFFFFFFFu)\nENUM_END(GX2EventType)\n\nENUM_BEG(GX2FetchShaderType, uint32_t)\n   ENUM_VALUE(NoTessellation,          0)\n   ENUM_VALUE(LineTessellation,        1)\n   ENUM_VALUE(TriangleTessellation,    2)\n   ENUM_VALUE(QuadTessellation,        3)\nENUM_END(GX2FetchShaderType)\n\nENUM_BEG(GX2FrontFace, uint32_t)\n   ENUM_VALUE(CounterClockwise,        0)\n   ENUM_VALUE(Clockwise,               1)\nENUM_END(GX2FrontFace)\n\nENUM_BEG(GX2InitAttrib, uint32_t)\n   ENUM_VALUE(End,                     0)\n   ENUM_VALUE(CommandBufferPoolBase,   1)\n   ENUM_VALUE(CommandBufferPoolSize,   2)\n   ENUM_VALUE(ArgC,                    7)\n   ENUM_VALUE(ArgV,                    8)\n   ENUM_VALUE(ProfileMode,             9)\n   ENUM_VALUE(TossStage,               10)\n   ENUM_VALUE(AppIoThreadStackSize,    11)\nENUM_END(GX2InitAttrib)\n\nENUM_BEG(GX2IndexType, uint32_t)\n   ENUM_VALUE(U16_LE,                  0x0)\n   ENUM_VALUE(U32_LE,                  0x1)\n   ENUM_VALUE(U16,                     0x4)\n   ENUM_VALUE(U32,                     0x9)\nENUM_END(GX2IndexType)\n\nFLAGS_BEG(GX2InvalidateMode, uint32_t)\n   FLAGS_VALUE(AttributeBuffer,        1 << 0)\n   FLAGS_VALUE(Texture,                1 << 1)\n   FLAGS_VALUE(UniformBlock,           1 << 2)\n   FLAGS_VALUE(Shader,                 1 << 3)\n   FLAGS_VALUE(ColorBuffer,            1 << 4)\n   FLAGS_VALUE(DepthBuffer,            1 << 5)\n   FLAGS_VALUE(CPU,                    1 << 6)\n   FLAGS_VALUE(StreamOutBuffer,        1 << 7)\n   FLAGS_VALUE(ExportBuffer,           1 << 8)\nFLAGS_END(GX2InvalidateMode)\n\nENUM_BEG(GX2LogicOp, uint8_t)\n   ENUM_VALUE(Clear,                   0x00)\n   ENUM_VALUE(Nor,                     0x11)\n   ENUM_VALUE(InvertedAnd,             0x22)\n   ENUM_VALUE(InvertedCopy,            0x33)\n   ENUM_VALUE(ReverseAnd,              0x44)\n   ENUM_VALUE(Invert,                  0x55)\n   ENUM_VALUE(Xor,                     0x66)\n   ENUM_VALUE(NotAnd,                  0x77)\n   ENUM_VALUE(And,                     0x88)\n   ENUM_VALUE(Equiv,                   0x99)\n   ENUM_VALUE(NoOp,                    0xAA)\n   ENUM_VALUE(InvertedOr,              0xBB)\n   ENUM_VALUE(Copy,                    0xCC)\n   ENUM_VALUE(ReverseOr,               0xDD)\n   ENUM_VALUE(Or,                      0xEE)\n   ENUM_VALUE(Set,                     0xFF)\nENUM_END(GX2LogicOp)\n\nENUM_BEG(GX2MiscParam, uint32_t)\n   ENUM_VALUE(HangState,                  0)\n   ENUM_VALUE(HangResponse,               1)\n   ENUM_VALUE(HangResetSwapTimeout,       2)\n   ENUM_VALUE(HangResetSwapsOutstanding,  3)\nENUM_END(GX2MiscParam)\n\nENUM_BEG(GX2PatchShaderType, uint32_t)\n   ENUM_VALUE(FetchShader,             0x1)\n   ENUM_VALUE(VertexShader,            0x2)\n   ENUM_VALUE(GeometryVertexShader,    0x3)\n   ENUM_VALUE(GeometryShader,          0x4)\n   ENUM_VALUE(PixelShader,             0x5)\nENUM_END(GX2PatchShaderType)\n\nENUM_BEG(GX2PerfType, uint32_t)\n   ENUM_VALUE(GpuMetric,               0x1)\n   ENUM_VALUE(GpuStat,                 0x2)\n   ENUM_VALUE(MemStat,                 0x3)\nENUM_END(GX2PerfType)\n\nFLAGS_BEG(GX2ProfileMode, uint32_t)\n   FLAGS_VALUE(None,                      0)\n   FLAGS_VALUE(SkipExecuteCommandBuffers, 1 << 0)\nFLAGS_END(GX2ProfileMode)\n\nENUM_BEG(GX2PrimitiveMode, uint32_t)\n   ENUM_VALUE(Triangles,               0x4)\n   ENUM_VALUE(TriangleStrip,           0x6)\n   ENUM_VALUE(Quads,                   0x13)\n   ENUM_VALUE(QuadStrip,               0x14)\nENUM_END(GX2PrimitiveMode)\n\nFLAGS_BEG(GX2PrimitiveModeFlags, uint32_t)\n   FLAGS_VALUE(ModeMask,               0x1F)\n   FLAGS_VALUE(Tessellate,             0x80)\nFLAGS_END(GX2PrimitiveModeFlags)\n\nENUM_BEG(GX2PolygonMode, uint32_t)\n   ENUM_VALUE(Point,                   0)\n   ENUM_VALUE(Line,                    1)\n   ENUM_VALUE(Triangle,                2)\nENUM_END(GX2PolygonMode)\n\nENUM_BEG(GX2QueryType, uint32_t)\n   ENUM_VALUE(OcclusionQuery,          0)\n   ENUM_VALUE(StreamOutStats,          1)\n   ENUM_VALUE(OcclusionQueryGpuMem,    2)\n   ENUM_VALUE(StreamOutStatsGpuMem,    3)\nENUM_END(GX2QueryType)\n\nENUM_BEG(GX2RenderTarget, uint32_t)\n   ENUM_VALUE(Target0,                 0)\n   ENUM_VALUE(Target1,                 1)\n   ENUM_VALUE(Target2,                 2)\n   ENUM_VALUE(Target3,                 3)\n   ENUM_VALUE(Target4,                 4)\n   ENUM_VALUE(Target5,                 5)\n   ENUM_VALUE(Target6,                 6)\n   ENUM_VALUE(Target7,                 7)\nENUM_END(GX2RenderTarget)\n\nFLAGS_BEG(GX2RResourceFlags, uint32_t)\n   FLAGS_VALUE(BindTexture,            1 << 0)\n   FLAGS_VALUE(BindColorBuffer,        1 << 1)\n   FLAGS_VALUE(BindDepthBuffer,        1 << 2)\n   FLAGS_VALUE(BindScanBuffer,         1 << 3)\n   FLAGS_VALUE(BindVertexBuffer,       1 << 4)\n   FLAGS_VALUE(BindIndexBuffer,        1 << 5)\n   FLAGS_VALUE(BindUniformBlock,       1 << 6)\n   FLAGS_VALUE(BindShaderProgram,      1 << 7)\n   FLAGS_VALUE(BindStreamOutput,       1 << 8)\n   FLAGS_VALUE(BindDisplayList,        1 << 9)\n   FLAGS_VALUE(BindGSRing,             1 << 10)\n   FLAGS_VALUE(UsageCpuRead,           1 << 11)\n   FLAGS_VALUE(UsageCpuWrite,          1 << 12)\n   FLAGS_VALUE(UsageCpuReadWrite,      UsageCpuRead | UsageCpuWrite)\n   FLAGS_VALUE(UsageGpuRead,           1 << 13)\n   FLAGS_VALUE(UsageGpuWrite,          1 << 14)\n   FLAGS_VALUE(UsageGpuReadWrite,      UsageGpuRead | UsageGpuWrite)\n   FLAGS_VALUE(UsageDmaRead,           1 << 15)\n   FLAGS_VALUE(UsageDmaWrite,          1 << 16)\n   FLAGS_VALUE(UsageForceMEM1,         1 << 17)\n   FLAGS_VALUE(UsageForceMEM2,         1 << 18)\n   FLAGS_VALUE(DisableCpuInvalidate,   1 << 20)\n   FLAGS_VALUE(DisableGpuInvalidate,   1 << 21)\n   FLAGS_VALUE(LockedReadOnly,         1 << 22)\n   FLAGS_VALUE(DestroyNoFree,          1 << 23)\n   FLAGS_VALUE(Gx2rAllocated,          1 << 29)\n   FLAGS_VALUE(Locked,                 1 << 30)\nFLAGS_END(GX2RResourceFlags)\n\nENUM_BEG(GX2RoundingMode, uint32_t)\n   ENUM_VALUE(RoundToEven,             0)\n   ENUM_VALUE(Truncate,                1)\nENUM_END(GX2RoundingMode)\n\nENUM_BEG(GXSpecialState, uint32_t)\n   ENUM_VALUE(Clear,                      0)\n   ENUM_VALUE(ClearHiZ,                   1)\n   ENUM_VALUE(Copy,                       2)\n   ENUM_VALUE(ExpandColor,                3)\n   ENUM_VALUE(ExpandDepth,                4)\n   ENUM_VALUE(ConvertDepth,               5)\n   ENUM_VALUE(ConvertMsaaDepth,           6)\n   ENUM_VALUE(ResolveColor,               7)\n   ENUM_VALUE(ClearColorAsDepth,          8)\nENUM_END(GXSpecialState)\n\nENUM_BEG(GX2ScanTarget, uint32_t)\n   ENUM_VALUE(TV,                      1)\n   ENUM_VALUE(DRC,                     4)\nENUM_END(GX2ScanTarget)\n\nENUM_BEG(GX2StreamOutContextMode, uint32_t)\n   ENUM_VALUE(Append,                  0)\n   ENUM_VALUE(FromStart,               1)\n   ENUM_VALUE(FromOffset,              2)\nENUM_END(GX2StreamOutContextMode)\n\nENUM_BEG(GX2PipeEvent, uint32_t)\n   ENUM_VALUE(Top,                     0)\n   ENUM_VALUE(Bottom,                  1)\n   ENUM_VALUE(BottomAfterFlush,        2)\nENUM_END(GX2PipeEvent)\n\nENUM_BEG(GX2SurfaceDim, uint32_t)\n   ENUM_VALUE(Texture1D,               0)\n   ENUM_VALUE(Texture2D,               1)\n   ENUM_VALUE(Texture3D,               2)\n   ENUM_VALUE(TextureCube,             3)\n   ENUM_VALUE(Texture1DArray,          4)\n   ENUM_VALUE(Texture2DArray,          5)\n   ENUM_VALUE(Texture2DMSAA,           6)\n   ENUM_VALUE(Texture2DMSAAArray,      7)\nENUM_END(GX2SurfaceDim)\n\nENUM_BEG(GX2SurfaceFormat, uint32_t)\n   ENUM_VALUE(INVALID,                    0x00)\n   ENUM_VALUE(UNORM_R4_G4,                0x02)\n   ENUM_VALUE(UNORM_R4_G4_B4_A4,          0x0b)\n   ENUM_VALUE(UNORM_R8,                   0x01)\n   ENUM_VALUE(UNORM_R8_G8,                0x07)\n   ENUM_VALUE(UNORM_R8_G8_B8_A8,          0x01a)\n   ENUM_VALUE(UNORM_R16,                  0x05)\n   ENUM_VALUE(UNORM_R16_G16,              0x0f)\n   ENUM_VALUE(UNORM_R16_G16_B16_A16,      0x01f)\n   ENUM_VALUE(UNORM_R5_G6_B5,             0x08)\n   ENUM_VALUE(UNORM_R5_G5_B5_A1,          0x0a)\n   ENUM_VALUE(UNORM_A1_B5_G5_R5,          0x0c)\n   ENUM_VALUE(UNORM_R24_X8,               0x011)\n   ENUM_VALUE(UNORM_A2_B10_G10_R10,       0x01b)\n   ENUM_VALUE(UNORM_R10_G10_B10_A2,       0x019)\n   ENUM_VALUE(UNORM_BC1,                  0x031)\n   ENUM_VALUE(UNORM_BC2,                  0x032)\n   ENUM_VALUE(UNORM_BC3,                  0x033)\n   ENUM_VALUE(UNORM_BC4,                  0x034)\n   ENUM_VALUE(UNORM_BC5,                  0x035)\n   ENUM_VALUE(UNORM_NV12,                 0x081)\n\n   ENUM_VALUE(UINT_R8,                    0x101)\n   ENUM_VALUE(UINT_R8_G8,                 0x107)\n   ENUM_VALUE(UINT_R8_G8_B8_A8,           0x11a)\n   ENUM_VALUE(UINT_R16,                   0x105)\n   ENUM_VALUE(UINT_R16_G16,               0x10f)\n   ENUM_VALUE(UINT_R16_G16_B16_A16,       0x11f)\n   ENUM_VALUE(UINT_R32,                   0x10d)\n   ENUM_VALUE(UINT_R32_G32,               0x11d)\n   ENUM_VALUE(UINT_R32_G32_B32_A32,       0x122)\n   ENUM_VALUE(UINT_A2_B10_G10_R10,        0x11b)\n   ENUM_VALUE(UINT_R10_G10_B10_A2,        0x119)\n   ENUM_VALUE(UINT_X24_G8,                0x111)\n   ENUM_VALUE(UINT_G8_X24,                0x11c)\n\n   ENUM_VALUE(SNORM_R8,                   0x201)\n   ENUM_VALUE(SNORM_R8_G8,                0x207)\n   ENUM_VALUE(SNORM_R8_G8_B8_A8,          0x21a)\n   ENUM_VALUE(SNORM_R16,                  0x205)\n   ENUM_VALUE(SNORM_R16_G16,              0x20f)\n   ENUM_VALUE(SNORM_R16_G16_B16_A16,      0x21f)\n   ENUM_VALUE(SNORM_R10_G10_B10_A2,       0x219)\n   ENUM_VALUE(SNORM_BC4,                  0x234)\n   ENUM_VALUE(SNORM_BC5,                  0x235)\n\n   ENUM_VALUE(SINT_R8,                    0x301)\n   ENUM_VALUE(SINT_R8_G8,                 0x307)\n   ENUM_VALUE(SINT_R8_G8_B8_A8,           0x31a)\n   ENUM_VALUE(SINT_R16,                   0x305)\n   ENUM_VALUE(SINT_R16_G16,               0x30f)\n   ENUM_VALUE(SINT_R16_G16_B16_A16,       0x31f)\n   ENUM_VALUE(SINT_R32,                   0x30d)\n   ENUM_VALUE(SINT_R32_G32,               0x31d)\n   ENUM_VALUE(SINT_R32_G32_B32_A32,       0x322)\n   ENUM_VALUE(SINT_R10_G10_B10_A2,        0x319)\n\n   ENUM_VALUE(SRGB_R8_G8_B8_A8,           0x41a)\n   ENUM_VALUE(SRGB_BC1,                   0x431)\n   ENUM_VALUE(SRGB_BC2,                   0x432)\n   ENUM_VALUE(SRGB_BC3,                   0x433)\n\n   ENUM_VALUE(FLOAT_R32,                  0x80e)\n   ENUM_VALUE(FLOAT_R32_G32,              0x81e)\n   ENUM_VALUE(FLOAT_R32_G32_B32_A32,      0x823)\n   ENUM_VALUE(FLOAT_R16,                  0x806)\n   ENUM_VALUE(FLOAT_R16_G16,              0x810)\n   ENUM_VALUE(FLOAT_R16_G16_B16_A16,      0x820)\n   ENUM_VALUE(FLOAT_R11_G11_B10,          0x816)\n   ENUM_VALUE(FLOAT_D24_S8,               0x811)\n   ENUM_VALUE(FLOAT_X8_X24,               0x81c)\nENUM_END(GX2SurfaceFormat)\n\nENUM_BEG(GX2SurfaceFormatType, uint32_t)\n   ENUM_VALUE(UNORM,             0x0)\n   ENUM_VALUE(UINT,              0x1)\n   ENUM_VALUE(SNORM,             0x2)\n   ENUM_VALUE(SINT,              0x3)\n   ENUM_VALUE(SRGB,              0x4)\n   ENUM_VALUE(FLOAT,             0x8)\nENUM_END(GX2SurfaceFormatType)\n\nFLAGS_BEG(GX2SurfaceUse, uint32_t)\n   FLAGS_VALUE(None,             0)\n   FLAGS_VALUE(Texture,          1 << 0)\n   FLAGS_VALUE(ColorBuffer,      1 << 1)\n   FLAGS_VALUE(DepthBuffer,      1 << 2)\n   FLAGS_VALUE(ScanBuffer,       1 << 3)\nFLAGS_END(GX2SurfaceUse)\n\nENUM_BEG(GX2StencilFunction, uint32_t)\n   ENUM_VALUE(Keep,              0)\n   ENUM_VALUE(Zero,              1)\n   ENUM_VALUE(Replace,           2)\n   ENUM_VALUE(IncrClamp,         3)\n   ENUM_VALUE(DecrClamp,         4)\n   ENUM_VALUE(Invert,            5)\n   ENUM_VALUE(IncrWrap,          6)\n   ENUM_VALUE(DecrWrap,          7)\nENUM_END(GX2StencilFunction)\n\nENUM_BEG(GX2TessellationMode, uint32_t)\n   ENUM_VALUE(Discrete,          0)\n   ENUM_VALUE(Continuous,        1)\n   ENUM_VALUE(Adaptive,          2)\nENUM_END(GX2TessellationMode)\n\nENUM_BEG(GX2TexBorderType, uint32_t)\n   ENUM_VALUE(TransparentBlack,  0)\n   ENUM_VALUE(Black,             1)\n   ENUM_VALUE(White,             2)\n   ENUM_VALUE(Variable,          3)\nENUM_END(GX2TexBorderType)\n\nENUM_BEG(GX2TexClampMode, uint32_t)\n   ENUM_VALUE(Wrap,              0)\n   ENUM_VALUE(Mirror,            1)\n   ENUM_VALUE(Clamp,             2)\n   ENUM_VALUE(MirrorOnce,        3)\n   ENUM_VALUE(ClampBorder,       6)\nENUM_END(GX2TexClampMode)\n\nENUM_BEG(GX2TexMipFilterMode, uint32_t)\n   ENUM_VALUE(None,              0)\n   ENUM_VALUE(Point,             1)\n   ENUM_VALUE(Linear,            2)\nENUM_END(GX2TexMipFilterMode)\n\nENUM_BEG(GX2TexMipPerfMode, uint32_t)\n   ENUM_VALUE(Disable,           0)\nENUM_END(GX2TexMipPerfMode)\n\nENUM_BEG(GX2TexXYFilterMode, uint32_t)\n   ENUM_VALUE(Point,             0)\n   ENUM_VALUE(Linear,            1)\nENUM_END(GX2TexXYFilterMode)\n\nENUM_BEG(GX2TexAnisoRatio, uint32_t)\n   ENUM_VALUE(None,              0)\nENUM_END(GX2TexAnisoRatio)\n\nENUM_BEG(GX2TexZFilterMode, uint32_t)\n   ENUM_VALUE(None,              0)\n   ENUM_VALUE(Point,             1)\n   ENUM_VALUE(Linear,            2)\nENUM_END(GX2TexZFilterMode)\n\nENUM_BEG(GX2TexZPerfMode, uint32_t)\n   ENUM_VALUE(Disabled,          0)\nENUM_END(GX2TexZPerfMode)\n\nENUM_BEG(GX2TileMode, uint32_t)\n   ENUM_VALUE(Default,           0)\n   ENUM_VALUE(LinearAligned,     1)\n   ENUM_VALUE(Tiled1DThin1,      2)\n   ENUM_VALUE(Tiled1DThick,      3)\n   ENUM_VALUE(Tiled2DThin1,      4)\n   ENUM_VALUE(Tiled2DThin2,      5)\n   ENUM_VALUE(Tiled2DThin4,      6)\n   ENUM_VALUE(Tiled2DThick,      7)\n   ENUM_VALUE(Tiled2BThin1,      8)\n   ENUM_VALUE(Tiled2BThin2,      9)\n   ENUM_VALUE(Tiled2BThin4,      10)\n   ENUM_VALUE(Tiled2BThick,      11)\n   ENUM_VALUE(Tiled3DThin1,      12)\n   ENUM_VALUE(Tiled3DThick,      13)\n   ENUM_VALUE(Tiled3BThin1,      14)\n   ENUM_VALUE(Tiled3BThick,      15)\n   ENUM_VALUE(LinearSpecial,     16)\n   ENUM_VALUE(DefaultBadAlign,   0x20)\nENUM_END(GX2TileMode)\n\nENUM_BEG(GX2TossStage, uint32_t)\n   ENUM_VALUE(None,              0)\n   ENUM_VALUE(Unk1,              1)\n   ENUM_VALUE(Unk2,              2)\n   ENUM_VALUE(Unk7,              7)\n   ENUM_VALUE(Unk8,              8)\nENUM_END(GX2TossStage)\n\nENUM_BEG(GX2TVRenderMode, uint32_t)\n   ENUM_VALUE(Disabled,          0)\n   ENUM_VALUE(Standard480p,      1)\n   ENUM_VALUE(Wide480p,          2)\n   ENUM_VALUE(Wide720p,          3)\n   ENUM_VALUE(Unk720p,           4)\n   ENUM_VALUE(Wide1080p,         5)\nENUM_END(GX2TVRenderMode)\n\nENUM_BEG(GX2TVScanMode, uint32_t)\n   ENUM_VALUE(None,              0)\n   ENUM_VALUE(I576,              1)\n   ENUM_VALUE(I480,              2)\n   ENUM_VALUE(P480,              3)\n   ENUM_VALUE(P720,              4)\n   ENUM_VALUE(I1080,             6)\n   ENUM_VALUE(P1080,             7)\nENUM_END(GX2TVScanMode)\n\nENUM_BEG(GX2SamplerVarType, uint32_t)\n   ENUM_VALUE(Sampler1D,         0)\n   ENUM_VALUE(Sampler2D,         1)\n   ENUM_VALUE(Sampler3D,         3)\n   ENUM_VALUE(SamplerCube,       4)\nENUM_END(GX2SamplerVarType)\n\nENUM_BEG(GX2ShaderMode, uint32_t)\n   ENUM_VALUE(UniformRegister,   0)\n   ENUM_VALUE(UniformBlock,      1)\n   ENUM_VALUE(GeometryShader,    2)\n   ENUM_VALUE(ComputeShader,     3)\nENUM_END(GX2ShaderMode)\n\nENUM_BEG(GX2ShaderVarType, uint32_t)\n   ENUM_VALUE(Void,              0)\n   ENUM_VALUE(Bool,              1)\n   ENUM_VALUE(Int,               2)\n   ENUM_VALUE(Uint,              3)\n   ENUM_VALUE(Float,             4)\n   ENUM_VALUE(Double,            5)\n   ENUM_VALUE(Double2,           6)\n   ENUM_VALUE(Double3,           7)\n   ENUM_VALUE(Double4,           8)\n   ENUM_VALUE(Float2,            9)\n   ENUM_VALUE(Float3,            10)\n   ENUM_VALUE(Float4,            11)\n   ENUM_VALUE(Bool2,             12)\n   ENUM_VALUE(Bool3,             13)\n   ENUM_VALUE(Bool4,             14)\n   ENUM_VALUE(Int2,              15)\n   ENUM_VALUE(Int3,              16)\n   ENUM_VALUE(Int4,              17)\n   ENUM_VALUE(Uint2,             18)\n   ENUM_VALUE(Uint3,             19)\n   ENUM_VALUE(Uint4,             20)\n   ENUM_VALUE(Float2x2,          21)\n   ENUM_VALUE(Float2x3,          22)\n   ENUM_VALUE(Float2x4,          23)\n   ENUM_VALUE(Float3x2,          24)\n   ENUM_VALUE(Float3x3,          25)\n   ENUM_VALUE(Float3x4,          26)\n   ENUM_VALUE(Float4x2,          27)\n   ENUM_VALUE(Float4x3,          28)\n   ENUM_VALUE(Float4x4,          29)\n   ENUM_VALUE(Double2x2,         30)\n   ENUM_VALUE(Double2x3,         31)\n   ENUM_VALUE(Double2x4,         32)\n   ENUM_VALUE(Double3x2,         33)\n   ENUM_VALUE(Double3x3,         34)\n   ENUM_VALUE(Double3x4,         35)\n   ENUM_VALUE(Double4x2,         36)\n   ENUM_VALUE(Double4x3,         37)\n   ENUM_VALUE(Double4x4,         38)\nENUM_END(GX2ShaderVarType)\n\nENUM_NAMESPACE_EXIT(gx2)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_GX2_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_enum_string.cpp",
    "content": "#include \"gx2_enum_string.h\"\n\n#undef CAFE_GX2_ENUM_H\n#include <common/enum_string_define.inl>\n#include \"gx2_enum.h\"\n#include <common/enum_end.inl>\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_enum_string.h",
    "content": "#pragma once\n#include <cstdint>\n#include <string>\n#include \"gx2_enum.h\"\n\n#undef CAFE_GX2_ENUM_H\n#include <common/enum_string_declare.inl>\n#include \"gx2_enum.h\"\n#include <common/enum_end.inl>\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_event.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_event.h\"\n#include \"gx2_state.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_alarm.h\"\n#include \"cafe/libraries/coreinit/coreinit_memheap.h\"\n#include \"cafe/libraries/coreinit/coreinit_messagequeue.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_scheduler.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n#include \"cafe/libraries/coreinit/coreinit_thread.h\"\n#include \"cafe/libraries/coreinit/coreinit_time.h\"\n#include \"cafe/libraries/tcl/tcl_interrupthandler.h\"\n\n#include <atomic>\n#include <chrono>\n#include <common/log.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace cafe::coreinit;\nusing namespace cafe::tcl;\n\nstruct StaticEventData\n{\n   struct EventCallbackData\n   {\n      GX2EventCallbackFunction func;\n      virt_ptr<void> data;\n   };\n\n   be2_struct<OSThread> appIoThread;\n   be2_array<OSMessage, 32> appIoMessageBuffer;\n   be2_struct<OSMessageQueue> appIoMessageQueue;\n   be2_struct<OSThreadQueue> vsyncThreadQueue;\n   be2_struct<OSThreadQueue> flipThreadQueue;\n   be2_struct<OSAlarm> vsyncAlarm;\n   be2_array<EventCallbackData, GX2EventType::Max> eventCallbacks;\n\n   std::atomic<OSTime> lastVsync;\n   std::atomic<OSTime> lastFlip;\n   std::atomic<uint32_t> swapCount;\n   std::atomic<uint32_t> flipCount;\n   std::atomic<uint32_t> framesReady;\n};\n\nstatic virt_ptr<StaticEventData> sEventData = nullptr;\n\nstatic OSThreadEntryPointFn sAppIoThreadEntry = nullptr;\nstatic AlarmCallbackFn sVsyncAlarmHandler = nullptr;\nstatic TCLInterruptHandlerFn sTclEventCallback = nullptr;\n\n/**\n * Sleep the current thread until the last submitted command buffer\n * has been processed by the driver.\n */\nBOOL\nGX2DrawDone()\n{\n   GX2Flush();\n   return GX2WaitTimeStamp(GX2GetLastSubmittedTimeStamp());\n}\n\n\n/**\n * Set the callback and data for a type of event.\n */\nvoid\nGX2SetEventCallback(GX2EventType type,\n                    GX2EventCallbackFunction func,\n                    virt_ptr<void> userData)\n{\n   if (type == GX2EventType::EndOfPipeInterrupt) {\n      if (func && !sEventData->eventCallbacks[type].func) {\n         TCLIHEnableInterrupt(TCLInterruptType::CP_EOP_EVENT, TRUE);\n         TCLIHRegister(TCLInterruptType::CP_EOP_EVENT, sTclEventCallback,\n                       virt_cast<void *>(static_cast<virt_addr>(GX2EventType::EndOfPipeInterrupt)));\n      } else if (!func && sEventData->eventCallbacks[type].func) {\n         TCLIHEnableInterrupt(TCLInterruptType::CP_EOP_EVENT, FALSE);\n         TCLIHUnregister(TCLInterruptType::CP_EOP_EVENT, sTclEventCallback,\n                         virt_cast<void *>(static_cast<virt_addr>(GX2EventType::EndOfPipeInterrupt)));\n      }\n   }\n\n   if (type < GX2EventType::Max) {\n      sEventData->eventCallbacks[type].func = func;\n      sEventData->eventCallbacks[type].data = userData;\n   }\n}\n\n\n/**\n * Return the callback and data for a type of event.\n */\nvoid\nGX2GetEventCallback(GX2EventType type,\n                    virt_ptr<GX2EventCallbackFunction> outFunc,\n                    virt_ptr<virt_ptr<void>> outUserData)\n{\n   if (type < GX2EventType::Max) {\n      *outFunc = sEventData->eventCallbacks[type].func;\n      *outUserData = sEventData->eventCallbacks[type].data;\n   } else {\n      *outFunc = nullptr;\n      *outUserData = nullptr;\n   }\n}\n\n\n/**\n * Return the current swap status.\n *\n * \\param outSwapCount\n * Outputs the number of swaps requested.\n *\n * \\param outFlipCount\n * Outputs the number of swaps performed.\n *\n * \\param outLastFlip\n * Outputs the time of the last flip.\n *\n * \\param outLastVsync\n * Outputs the time of the last vsync.\n */\nvoid\nGX2GetSwapStatus(virt_ptr<uint32_t> outSwapCount,\n                 virt_ptr<uint32_t> outFlipCount,\n                 virt_ptr<OSTime> outLastFlip,\n                 virt_ptr<OSTime> outLastVsync)\n{\n   if (outSwapCount) {\n      *outSwapCount = sEventData->swapCount.load(std::memory_order_acquire);\n   }\n\n   if (outFlipCount) {\n      *outFlipCount = sEventData->flipCount.load(std::memory_order_acquire);\n   }\n\n   if (outLastFlip) {\n      *outLastFlip = sEventData->lastFlip.load(std::memory_order_acquire);\n   }\n\n   if (outLastVsync) {\n      *outLastVsync = sEventData->lastVsync.load(std::memory_order_acquire);\n   }\n}\n\n\n/**\n * Sleep the current thread until the vsync alarm has triggered.\n */\nvoid\nGX2WaitForVsync()\n{\n   OSSleepThread(virt_addrof(sEventData->vsyncThreadQueue));\n}\n\n\n/**\n * Sleep the current thread until a flip has been performed.\n */\nvoid\nGX2WaitForFlip()\n{\n   auto curFlipCount = sEventData->flipCount.load(std::memory_order_acquire);\n   auto curSwapCount = sEventData->swapCount.load(std::memory_order_acquire);\n   if (curFlipCount == curSwapCount) {\n      // The user has no more pending flips, return immediately.\n      return;\n   }\n\n   OSSleepThread(virt_addrof(sEventData->flipThreadQueue));\n}\n\n\nnamespace internal\n{\n\n/**\n * TCL interrupt handler which forwards a message to the AppIo thread to\n * perform the user callback.\n */\nstatic void\ntclEventCallback(virt_ptr<TCLInterruptEntry> interruptEntry,\n                 virt_ptr<void> userData)\n{\n   auto eventType = static_cast<GX2EventType>(static_cast<uint32_t>(virt_cast<virt_addr>(userData)));\n   if (sEventData->eventCallbacks[eventType].func) {\n      auto message = StackObject<OSMessage> { };\n      message->message = nullptr;\n      message->args[0] = eventType;\n      message->args[1] = 0u;\n      message->args[2] = 0u;\n      OSSendMessage(virt_addrof(sEventData->appIoMessageQueue), message,\n                    OSMessageFlags::Blocking);\n   }\n}\n\n\n/**\n * Initialise GX2 events.\n */\nvoid\ninitEvents(virt_ptr<void> appIoThreadStackBuffer,\n           uint32_t appIoThreadStackSize)\n{\n   OSInitThreadQueue(virt_addrof(sEventData->flipThreadQueue));\n   OSInitThreadQueue(virt_addrof(sEventData->vsyncThreadQueue));\n\n   // Setup 60hz alarm to perform vsync\n   auto ticks = static_cast<OSTime>(OSGetSystemInfo()->busSpeed / 4) / 60;\n   OSCreateAlarm(virt_addrof(sEventData->vsyncAlarm));\n   OSSetPeriodicAlarm(virt_addrof(sEventData->vsyncAlarm), OSGetTime(), ticks,\n                      sVsyncAlarmHandler);\n\n   // Create AppIo thread\n   OSInitMessageQueue(virt_addrof(sEventData->appIoMessageQueue),\n                      virt_addrof(sEventData->appIoMessageBuffer),\n                      sEventData->appIoMessageBuffer.size());\n   OSCreateThreadType(virt_addrof(sEventData->appIoThread),\n                      sAppIoThreadEntry, 0, nullptr,\n                      virt_cast<uint32_t *>(virt_cast<virt_addr>(appIoThreadStackBuffer) + appIoThreadStackSize),\n                      appIoThreadStackSize,\n                      16, OSThreadAttributes::Detached,\n                      OSThreadType::AppIo);\n   OSResumeThread(virt_addrof(sEventData->appIoThread));\n\n   // Register TCL interrupt handlers.\n   TCLIHRegister(TCLInterruptType::CP_IB1, sTclEventCallback,\n                 virt_cast<void *>(static_cast<virt_addr>(GX2EventType::StartOfPipeInterrupt)));\n   if (sEventData->eventCallbacks[GX2EventType::EndOfPipeInterrupt].func) {\n      TCLIHRegister(TCLInterruptType::CP_EOP_EVENT, sTclEventCallback,\n                    virt_cast<void *>(static_cast<virt_addr>(GX2EventType::EndOfPipeInterrupt)));\n   }\n}\n\n\n/**\n * AppIo thread for GX2, receives messages and calls the appropriate callback.\n */\nstatic uint32_t\nappIoThread(uint32_t argc,\n            virt_ptr<void> argv)\n{\n   auto message = StackObject<OSMessage> { };\n   while (true) {\n      OSReceiveMessage(virt_addrof(sEventData->appIoMessageQueue), message,\n                       OSMessageFlags::Blocking);\n      auto eventType = static_cast<GX2EventType>(message->args[0]);\n      if (eventType == GX2EventType::StopAppIoThread) {\n         break;\n      }\n\n      auto &callback = sEventData->eventCallbacks[eventType];\n      if (callback.func) {\n         cafe::invoke(cpu::this_core::state(),\n                      callback.func, eventType, callback.data);\n      }\n   }\n\n   return 0;\n}\n\n\n/**\n * Send a message to stop appIoThread.\n */\nvoid\nstopAppIoThread()\n{\n   // Unregister TCL interrupt handlers.\n   TCLIHUnregister(TCLInterruptType::CP_IB1, sTclEventCallback,\n                   virt_cast<void *>(static_cast<virt_addr>(GX2EventType::StartOfPipeInterrupt)));\n   if (sEventData->eventCallbacks[GX2EventType::EndOfPipeInterrupt].func) {\n      TCLIHUnregister(TCLInterruptType::CP_EOP_EVENT, sTclEventCallback,\n                      virt_cast<void *>(static_cast<virt_addr>(GX2EventType::EndOfPipeInterrupt)));\n   }\n\n   // Send stop message\n   auto message = StackObject<OSMessage> { };\n   message->message = nullptr;\n   message->args[0] = GX2EventType::StopAppIoThread;\n   message->args[1] = 0u;\n   message->args[2] = 0u;\n   OSSendMessage(virt_addrof(sEventData->appIoMessageQueue), message,\n                 OSMessageFlags::Blocking);\n}\n\n\n/**\n * VSync alarm handler.\n *\n * Wakes up any threads waiting for vsync.\n * If a vsync callback has been set, trigger it.\n */\nstatic void\nvsyncAlarmHandler(virt_ptr<OSAlarm> alarm,\n                  virt_ptr<OSContext> context)\n{\n   auto vsyncTime = OSGetSystemTime();\n\n   auto curFramesReady = sEventData->framesReady.load(std::memory_order_acquire);\n   auto curFlipCount = sEventData->flipCount.load(std::memory_order_acquire);\n   if (curFramesReady > curFlipCount) {\n      sEventData->flipCount.fetch_add(1, std::memory_order_release);\n      sEventData->lastFlip.store(vsyncTime, std::memory_order_release);\n      OSWakeupThread(virt_addrof(sEventData->flipThreadQueue));\n\n      auto callback = sEventData->eventCallbacks[GX2EventType::Flip];\n      if (callback.func) {\n         cafe::invoke(cpu::this_core::state(),\n                      callback.func,\n                      GX2EventType::Flip,\n                      callback.data);\n      }\n   }\n\n   sEventData->lastVsync.store(vsyncTime, std::memory_order_release);\n   OSWakeupThread(virt_addrof(sEventData->vsyncThreadQueue));\n\n   auto callback = sEventData->eventCallbacks[GX2EventType::Vsync];\n   if (callback.func) {\n      cafe::invoke(cpu::this_core::state(),\n                   callback.func,\n                   GX2EventType::Vsync,\n                   callback.data);\n   }\n}\n\n\n/**\n * Called when a GX2 command requires more space in a display list.\n *\n * Will call the display list overrun callback to allocate a new command buffer.\n */\nstd::pair<virt_ptr<void>, uint32_t>\ndisplayListOverrun(virt_ptr<void> list,\n                   uint32_t size,\n                   uint32_t neededSize)\n{\n   auto callback = sEventData->eventCallbacks[GX2EventType::DisplayListOverrun];\n\n   if (callback.func && callback.data) {\n      auto data = virt_cast<GX2DisplayListOverrunData *>(callback.data);\n      data->oldList = list;\n      data->oldSize = size;\n      data->newList = nullptr;\n      data->newSize = neededSize;\n\n      // Call the user's function, it should set newList and newSize\n      cafe::invoke(cpu::this_core::state(),\n                   callback.func,\n                   GX2EventType::DisplayListOverrun,\n                   callback.data);\n      return { data->newList, data->newSize };\n   }\n\n   gLog->error(\"Encountered DisplayListOverrun without a valid callback!\");\n   return { nullptr, 0 };\n}\n\n\n/**\n * Called when a swap is requested with GX2SwapBuffers.\n */\nvoid\nonSwap()\n{\n   sEventData->swapCount.fetch_add(1, std::memory_order_release);\n}\n\n\n/**\n * Called when a swap is performed by the driver.\n */\nvoid\nonFlip()\n{\n   sEventData->framesReady.fetch_add(1, std::memory_order_release);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerEventSymbols()\n{\n   RegisterFunctionExport(GX2DrawDone);\n   RegisterFunctionExport(GX2WaitForVsync);\n   RegisterFunctionExport(GX2WaitForFlip);\n   RegisterFunctionExport(GX2SetEventCallback);\n   RegisterFunctionExport(GX2GetEventCallback);\n   RegisterFunctionExport(GX2GetSwapStatus);\n\n   RegisterDataInternal(sEventData);\n   RegisterFunctionInternal(internal::vsyncAlarmHandler, sVsyncAlarmHandler);\n   RegisterFunctionInternal(internal::appIoThread, sAppIoThreadEntry);\n   RegisterFunctionInternal(internal::tclEventCallback, sTclEventCallback);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_event.h",
    "content": "#pragma once\n#include \"cafe/libraries/coreinit/coreinit_alarm.h\"\n#include \"cafe/libraries/coreinit/coreinit_time.h\"\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n#include <utility>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_event Event\n * \\ingroup gx2\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct GX2DisplayListOverrunData\n{\n   be2_virt_ptr<void> oldList;\n   be2_val<uint32_t> oldSize;\n   be2_virt_ptr<void> newList;\n   be2_val<uint32_t> newSize;\n   UNKNOWN(8);\n};\nCHECK_OFFSET(GX2DisplayListOverrunData, 0x00, oldList);\nCHECK_OFFSET(GX2DisplayListOverrunData, 0x04, oldSize);\nCHECK_OFFSET(GX2DisplayListOverrunData, 0x08, newList);\nCHECK_OFFSET(GX2DisplayListOverrunData, 0x0C, newSize);\nCHECK_SIZE(GX2DisplayListOverrunData, 0x18);\n\n#pragma pack(pop)\n\nusing GX2EventCallbackFunction = virt_func_ptr<void(GX2EventType, virt_ptr<void>)>;\n\nBOOL\nGX2DrawDone();\n\nvoid\nGX2WaitForVsync();\n\nvoid\nGX2WaitForFlip();\n\nvoid\nGX2SetEventCallback(GX2EventType type,\n                    GX2EventCallbackFunction func,\n                    virt_ptr<void> userData);\n\nvoid\nGX2GetEventCallback(GX2EventType type,\n                    virt_ptr<GX2EventCallbackFunction> outFunc,\n                    virt_ptr<virt_ptr<void>> outUserData);\n\nvoid\nGX2GetSwapStatus(virt_ptr<uint32_t> outSwapCount,\n                 virt_ptr<uint32_t> outFlipCount,\n                 virt_ptr<coreinit::OSTime> outLastFlip,\n                 virt_ptr<coreinit::OSTime> outLastVsync);\n\nnamespace internal\n{\n\nvoid\ninitEvents(virt_ptr<void> appIoThreadStackBuffer,\n           uint32_t appIoThreadStackSize);\n\nvoid\nstopAppIoThread();\n\nstd::pair<virt_ptr<void>, uint32_t>\ndisplayListOverrun(virt_ptr<void> list,\n                   uint32_t size,\n                   uint32_t neededSize);\n\nvoid\nonSwap();\n\nvoid\nonFlip();\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_fence.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_fence.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <common/decaf_assert.h>\n#include <libcpu/be2_struct.h>\n#include <libgpu/latte/latte_pm4.h>\n#include <libgpu/latte/latte_pm4_writer.h>\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2SetGPUFence(virt_ptr<uint32_t> memory,\n               uint32_t mask,\n               GX2CompareFunction op,\n               uint32_t value)\n{\n   using namespace latte;\n   using namespace latte::pm4;\n\n   if (op == GX2CompareFunction::Never) {\n      return;\n   }\n\n   WRM_FUNCTION function;\n   switch (op) {\n   case GX2CompareFunction::Never:\n      return;\n   case GX2CompareFunction::Less:\n      function = WRM_FUNCTION::FUNCTION_LESS_THAN;\n      break;\n   case GX2CompareFunction::Equal:\n      function = WRM_FUNCTION::FUNCTION_EQUAL;\n      break;\n   case GX2CompareFunction::LessOrEqual:\n      function = WRM_FUNCTION::FUNCTION_LESS_THAN_EQUAL;\n      break;\n   case GX2CompareFunction::Greater:\n      function = WRM_FUNCTION::FUNCTION_GREATER_THAN;\n      break;\n   case GX2CompareFunction::NotEqual:\n      function = WRM_FUNCTION::FUNCTION_NOT_EQUAL;\n      break;\n   case GX2CompareFunction::GreaterOrEqual:\n      function = WRM_FUNCTION::FUNCTION_GREATER_THAN_EQUAL;\n      break;\n   case GX2CompareFunction::Always:\n      function = WRM_FUNCTION::FUNCTION_ALWAYS;\n      break;\n   default:\n      function = WRM_FUNCTION::FUNCTION_ALWAYS;\n   }\n\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(memory));\n   internal::writePM4(WaitMem {\n      MEM_SPACE_FUNCTION::get(0)\n         .MEM_SPACE(WRM_MEM_SPACE::MEM_SPACE_MEMORY)\n         .FUNCTION(function)\n         .ENGINE(WRM_ENGINE::ENGINE_ME),\n      WRM_ADDR_LO::get(0)\n         .ADDR_LO(addr >> 2)\n         .ENDIAN_SWAP(CB_ENDIAN::SWAP_8IN32),\n      WRM_ADDR_HI::get(0),\n      value, mask, 10,\n   });\n}\n\nvoid\nLibrary::registerFenceSymbols()\n{\n   RegisterFunctionExport(GX2SetGPUFence);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_fence.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_fence Fence\n * \\ingroup gx2\n * @{\n */\n\nvoid\nGX2SetGPUFence(virt_ptr<uint32_t> memory,\n               uint32_t mask,\n               GX2CompareFunction op,\n               uint32_t value);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_fetchshader.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_fetchshader.h\"\n#include \"gx2_shaders.h\"\n#include \"gx2_enum.h\"\n#include \"gx2_enum_string.h\"\n#include \"gx2_format.h\"\n#include \"gx2_memory.h\"\n\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n#include <libgpu/latte/latte_instructions.h>\n\nnamespace cafe::gx2\n{\n\nstruct IndexMapEntry\n{\n   uint32_t gpr;\n   latte::SQ_CHAN chan;\n};\n\nstatic const auto\nFetchesPerControlFlow = 16u;\n\nstatic const IndexMapEntry IndexMapNoTess[] = {\n   { 0, latte::SQ_CHAN::X },\n   { 0, latte::SQ_CHAN::X },\n   { 0, latte::SQ_CHAN::X },\n   { 0, latte::SQ_CHAN::X },\n};\n\nstatic const IndexMapEntry IndexMapLineTess[] = {\n   { 0, latte::SQ_CHAN::Y },\n   { 0, latte::SQ_CHAN::Z },\n   { 0, latte::SQ_CHAN::Y },\n   { 0, latte::SQ_CHAN::Y },\n};\n\nstatic const IndexMapEntry IndexMapLineTessAdaptive[] = {\n   { 6, latte::SQ_CHAN::X },\n   { 6, latte::SQ_CHAN::Y },\n   { 6, latte::SQ_CHAN::Z },\n   { 6, latte::SQ_CHAN::X },\n};\n\nstatic const IndexMapEntry IndexMapTriTess[] = {\n   { 1, latte::SQ_CHAN::X },\n   { 1, latte::SQ_CHAN::Y },\n   { 1, latte::SQ_CHAN::Z },\n   { 1, latte::SQ_CHAN::X },\n};\n\nstatic const IndexMapEntry IndexMapTriTessAdaptive[] = {\n   { 6, latte::SQ_CHAN::X },\n   { 6, latte::SQ_CHAN::Y },\n   { 6, latte::SQ_CHAN::Z },\n   { 6, latte::SQ_CHAN::X },\n};\n\nstatic const IndexMapEntry IndexMapQuadTess[] = {\n   { 0, latte::SQ_CHAN::Z },\n   { 1, latte::SQ_CHAN::X },\n   { 1, latte::SQ_CHAN::Y },\n   { 1, latte::SQ_CHAN::Z },\n};\n\nstatic const IndexMapEntry IndexMapQuadTessAdaptive[] = {\n   { 6, latte::SQ_CHAN::X },\n   { 6, latte::SQ_CHAN::Y },\n   { 6, latte::SQ_CHAN::Z },\n   { 6, latte::SQ_CHAN::W },\n};\n\nstatic uint32_t\nfetchInstsPerAttrib(GX2FetchShaderType type)\n{\n   switch (type) {\n   case GX2FetchShaderType::NoTessellation:\n      return 1;\n   case GX2FetchShaderType::LineTessellation:\n      return 2;\n   case GX2FetchShaderType::TriangleTessellation:\n      return 3;\n   case GX2FetchShaderType::QuadTessellation:\n      return 4;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2FetchShaderType {}\", to_string(type)));\n   }\n}\n\nstatic uint32_t\ncalcNumFetchInsts(uint32_t attribs,\n                  GX2FetchShaderType type)\n{\n   switch (type) {\n   case GX2FetchShaderType::NoTessellation:\n      return attribs;\n   case GX2FetchShaderType::LineTessellation:\n   case GX2FetchShaderType::TriangleTessellation:\n   case GX2FetchShaderType::QuadTessellation:\n      return fetchInstsPerAttrib(type) * (attribs - 2);\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2FetchShaderType {}\", to_string(type)));\n   }\n}\n\nstatic uint32_t\ncalcNumTessALUInsts(GX2FetchShaderType type,\n                    GX2TessellationMode mode)\n{\n   if (mode == GX2TessellationMode::Adaptive) {\n      switch (type) {\n      case GX2FetchShaderType::NoTessellation:\n         break;\n      case GX2FetchShaderType::LineTessellation:\n         return 11;\n      case GX2FetchShaderType::TriangleTessellation:\n         return 57;\n      case GX2FetchShaderType::QuadTessellation:\n         return 43;\n      }\n   }\n\n   return 4;\n}\n\nstatic uint32_t\ncalcNumAluInsts(GX2FetchShaderType type,\n                GX2TessellationMode mode)\n{\n   if (type == GX2FetchShaderType::NoTessellation) {\n      return 0;\n   } else {\n      return calcNumTessALUInsts(type, mode) + 4;\n   }\n}\n\nstatic uint32_t\ncalcNumFetchCFInsts(uint32_t fetches)\n{\n   return (fetches + (FetchesPerControlFlow - 1)) / FetchesPerControlFlow;\n}\n\nstatic uint32_t\ncalcNumCFInsts(uint32_t fetches,\n               GX2FetchShaderType type)\n{\n   auto count = calcNumFetchCFInsts(fetches);\n\n   if (type != GX2FetchShaderType::NoTessellation) {\n      count += 2;\n   }\n\n   return count + 1;\n}\n\nstatic const IndexMapEntry *\ngetIndexGprMap(GX2FetchShaderType type,\n               GX2TessellationMode mode)\n{\n   switch (type) {\n   case GX2FetchShaderType::LineTessellation:\n      if (mode == GX2TessellationMode::Adaptive) {\n         return IndexMapLineTessAdaptive;\n      } else {\n         return IndexMapLineTess;\n      }\n   case GX2FetchShaderType::TriangleTessellation:\n      if (mode == GX2TessellationMode::Adaptive) {\n         return IndexMapTriTessAdaptive;\n      } else {\n         return IndexMapTriTess;\n      }\n   case GX2FetchShaderType::QuadTessellation:\n      if (mode == GX2TessellationMode::Adaptive) {\n         return IndexMapQuadTessAdaptive;\n      } else {\n         return IndexMapQuadTess;\n      }\n   case GX2FetchShaderType::NoTessellation:\n   default:\n      return IndexMapNoTess;\n   }\n}\n\nuint32_t\nGX2CalcFetchShaderSizeEx(uint32_t attribs,\n                         GX2FetchShaderType type,\n                         GX2TessellationMode mode)\n{\n   auto fetch = calcNumFetchInsts(attribs, type);\n   auto aluBytes = sizeof(latte::AluInst) * calcNumAluInsts(type, mode);\n   auto cfBytes = sizeof(latte::ControlFlowInst) * calcNumCFInsts(fetch, type);\n\n   return static_cast<uint32_t>(sizeof(latte::VertexFetchInst) * fetch + align_up(cfBytes + aluBytes, 16));\n}\n\nvoid\nGX2InitFetchShaderEx(virt_ptr<GX2FetchShader> fetchShader,\n                     virt_ptr<uint8_t> buffer,\n                     uint32_t attribCount,\n                     virt_ptr<GX2AttribStream> attribs,\n                     GX2FetchShaderType type,\n                     GX2TessellationMode tessMode)\n{\n   if (type != GX2FetchShaderType::NoTessellation) {\n      decaf_abort(fmt::format(\"Invalid GX2FetchShaderType {}\", to_string(type)));\n   }\n\n   if (tessMode != GX2TessellationMode::Discrete) {\n      decaf_abort(fmt::format(\"Invalid GX2TessellationMode {}\", to_string(tessMode)));\n   }\n\n   auto someTessVar1 = 128u;\n   auto someTessVar2 = 128u;\n   auto numGPRs = 0u;\n   auto barrier = false;\n\n   // Calculate instruction pointers\n   auto fetchCount = calcNumFetchInsts(attribCount, type);\n   auto aluCount = 0; // calcNumAluInsts(type, tessMode);\n   auto cfCount = calcNumCFInsts(fetchCount, type);\n\n   auto cfSize = cfCount * sizeof(latte::ControlFlowInst);\n   auto aluSize = aluCount * sizeof(latte::AluInst);\n\n   auto cfOffset = 0u;\n   auto aluOffset = cfSize;\n   auto fetchOffset = align_up(cfSize + aluSize, 0x10u);\n\n   auto cfPtr = virt_cast<latte::ControlFlowInst *>(buffer + cfOffset);\n   auto aluPtr = virt_cast<latte::AluInst *>(buffer + aluOffset);\n   auto fetchPtr = virt_cast<latte::VertexFetchInst *>(buffer + fetchOffset);\n\n   // Setup fetch shader\n   fetchShader->type = type;\n   fetchShader->attribCount = attribCount;\n   fetchShader->data = buffer;\n   fetchShader->size = GX2CalcFetchShaderSizeEx(attribCount, type, tessMode);\n\n   // Generate fetch instructions\n   auto indexMap = getIndexGprMap(type, tessMode);\n\n   for (auto i = 0u; i < attribCount; ++i) {\n      latte::VertexFetchInst vfetch;\n      auto &attrib = attribs[i];\n      std::memset(&vfetch, 0, sizeof(vfetch));\n\n      if (attrib.buffer == 16) {\n         // TODO: Figure out what these vars are for\n         if (attrib.offset) {\n            if (attrib.offset == 1) {\n               someTessVar1 = attrib.location;\n            }\n         } else {\n            someTessVar2 = attrib.location;\n         }\n      } else {\n         // Semantic vertex fetch\n         vfetch.word0 = vfetch.word0\n            .VTX_INST(latte::SQ_VTX_INST_SEMANTIC)\n            .BUFFER_ID(latte::SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 + attrib.buffer - latte::SQ_RES_OFFSET::VS_TEX_RESOURCE_0);\n\n         vfetch.word2 = vfetch.word2\n            .OFFSET(attribs[i].offset);\n\n         if (attrib.type) {\n            auto selX = latte::SQ_SEL::SEL_X;\n            auto fetchType = latte::SQ_VTX_FETCH_TYPE::VERTEX_DATA;\n\n            if (attrib.type == GX2AttribIndexType::PerInstance) {\n               fetchType = latte::SQ_VTX_FETCH_TYPE::INSTANCE_DATA;\n\n               if (attrib.aluDivisor == 1) {\n                  selX = latte::SQ_SEL::SEL_W;\n               } else if (attrib.aluDivisor == fetchShader->divisors[0]) {\n                  selX = latte::SQ_SEL::SEL_Y;\n               } else if (attrib.aluDivisor == fetchShader->divisors[1]) {\n                  selX = latte::SQ_SEL::SEL_Z;\n               } else {\n                  fetchShader->divisors[fetchShader->numDivisors] = attrib.aluDivisor;\n\n                  if (fetchShader->numDivisors == 0) {\n                     selX = latte::SQ_SEL::SEL_Y;\n                  } else if (fetchShader->numDivisors == 1) {\n                     selX = latte::SQ_SEL::SEL_Z;\n                  }\n\n                  fetchShader->numDivisors++;\n               }\n            }\n\n            vfetch.word0 = vfetch.word0\n               .FETCH_TYPE(fetchType)\n               .SRC_SEL_X(selX);\n         } else {\n            vfetch.word0 = vfetch.word0\n               .SRC_GPR(indexMap[0].gpr)\n               .SRC_SEL_X(static_cast<latte::SQ_SEL>(indexMap[0].chan));\n         }\n\n         // Setup dest\n         vfetch.gpr = vfetch.gpr\n            .DST_GPR(attrib.location);\n\n         vfetch.word1 = vfetch.word1\n            .DST_SEL_W(static_cast<latte::SQ_SEL>(attrib.mask & 0x7))\n            .DST_SEL_Z(static_cast<latte::SQ_SEL>((attrib.mask >> 8) & 0x7))\n            .DST_SEL_Y(static_cast<latte::SQ_SEL>((attrib.mask >> 16) & 0x7))\n            .DST_SEL_X(static_cast<latte::SQ_SEL>((attrib.mask >> 24) & 0x7));\n\n         // Setup mega fetch\n         vfetch.word2 = vfetch.word2\n            .MEGA_FETCH(1);\n\n         vfetch.word0 = vfetch.word0\n            .MEGA_FETCH_COUNT(internal::getAttribFormatBytes(attrib.format) - 1);\n\n         // Setup format\n         auto dataFormat = internal::getAttribFormatDataFormat(attrib.format);\n         auto numFormat = latte::SQ_NUM_FORMAT::NORM;\n         auto formatComp = latte::SQ_FORMAT_COMP::UNSIGNED;\n\n         if (attribs[i].format & GX2AttribFormatFlags::SCALED) {\n            numFormat = latte::SQ_NUM_FORMAT::SCALED;\n         } else if (attribs[i].format & GX2AttribFormatFlags::INTEGER) {\n            numFormat = latte::SQ_NUM_FORMAT::INT;\n         }\n\n         if (attribs[i].format & GX2AttribFormatFlags::SIGNED) {\n            formatComp = latte::SQ_FORMAT_COMP::SIGNED;\n         }\n\n         vfetch.word1 = vfetch.word1\n            .DATA_FORMAT(dataFormat)\n            .NUM_FORMAT_ALL(numFormat)\n            .FORMAT_COMP_ALL(formatComp);\n\n         auto swapMode = internal::getSwapModeEndian(static_cast<GX2EndianSwapMode>(attribs[i].endianSwap & 3));\n\n         if (attribs[i].endianSwap == GX2EndianSwapMode::Default) {\n            swapMode = internal::getAttribFormatEndian(attribs[i].format);\n         }\n\n         vfetch.word2 = vfetch.word2\n            .ENDIAN_SWAP(swapMode);\n\n         // Append to program\n         *(fetchPtr++) = vfetch;\n\n         // Add extra tesselation vertex fetches\n         if (type != GX2FetchShaderType::NoTessellation && attrib.type != GX2AttribIndexType::PerInstance) {\n            auto perAttrib = fetchInstsPerAttrib(type);\n\n            for (auto j = 1u; j < perAttrib; ++j) {\n               latte::VertexFetchInst vfetch2 = vfetch;\n\n               // Update src/dst\n               vfetch2.word0 = vfetch2.word0\n                  .SRC_GPR(indexMap[j].gpr)\n                  .SRC_SEL_X(static_cast<latte::SQ_SEL>(indexMap[j].chan));\n\n               vfetch2.gpr = vfetch2.gpr\n                  .DST_GPR(j + attrib.location);\n\n               // Append to program\n               *(fetchPtr++) = vfetch;\n            }\n         }\n      }\n   }\n\n   // Generate tessellation ALU ops\n   if (type != GX2FetchShaderType::NoTessellation) {\n      numGPRs = 2;\n\n      if (tessMode == GX2TessellationMode::Adaptive) {\n         switch (type) {\n         case GX2FetchShaderType::LineTessellation:\n            numGPRs = 3;\n            break;\n         case GX2FetchShaderType::TriangleTessellation:\n            numGPRs = 7;\n            break;\n         case GX2FetchShaderType::QuadTessellation:\n            numGPRs = 7;\n            break;\n         }\n      }\n\n      // TODO: GX2FSGenTessAluOps\n      barrier = true;\n   }\n\n   // Generate a VTX CF per 16 VFETCH\n   if (fetchCount) {\n      for (auto i = 0u; i < cfCount - 1; ++i) {\n         auto fetches = FetchesPerControlFlow;\n\n         if (fetchCount < (i + 1) * FetchesPerControlFlow) {\n            // Don't overrun our fetches!\n            fetches = fetchCount % FetchesPerControlFlow;\n         }\n\n         latte::ControlFlowInst inst;\n         std::memset(&inst, 0, sizeof(inst));\n         inst.word0 = inst.word0\n            .ADDR(static_cast<uint32_t>((fetchOffset + sizeof(latte::VertexFetchInst) * i * FetchesPerControlFlow) / 8));\n         inst.word1 = inst.word1\n            .COUNT((fetches - 1) & 0x7)\n            .COUNT_3(((fetches - 1) >> 3) & 0x1)\n            .CF_INST(latte::SQ_CF_INST_VTX_TC)\n            .BARRIER(barrier ? 1 : 0);\n         *(cfPtr++) = inst;\n      }\n   }\n\n   // Generate tessellation \"post\" ALU ops\n   if (numGPRs) {\n      // TODO: GX2FSGenPostAluOps\n   }\n\n   // Generate an EOP\n   latte::ControlFlowInst eop;\n   std::memset(&eop, 0, sizeof(eop));\n   eop.word1 = eop.word1\n      .BARRIER(1)\n      .CF_INST(latte::SQ_CF_INST_RETURN);\n   *(cfPtr++) = eop;\n\n   // Set sq_pgm_resources_fs\n   auto sq_pgm_resources_fs = fetchShader->regs.sq_pgm_resources_fs.value();\n   sq_pgm_resources_fs = sq_pgm_resources_fs\n      .NUM_GPRS(numGPRs);\n   fetchShader->regs.sq_pgm_resources_fs = sq_pgm_resources_fs;\n\n   GX2Invalidate(GX2InvalidateMode::CPU, fetchShader->data, fetchShader->size);\n}\n\nvoid\nLibrary::registerFetchShadersSymbols()\n{\n   RegisterFunctionExport(GX2CalcFetchShaderSizeEx);\n   RegisterFunctionExport(GX2InitFetchShaderEx);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_fetchshader.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libgpu/latte/latte_registers.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_shaders Shaders\n * \\ingroup gx2\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct GX2AttribStream;\n\nstruct GX2FetchShader\n{\n   be2_val<GX2FetchShaderType> type;\n\n   struct\n   {\n      be2_val<latte::SQ_PGM_RESOURCES_FS> sq_pgm_resources_fs;\n   } regs;\n\n   be2_val<uint32_t> size;\n   be2_virt_ptr<uint8_t> data;\n   be2_val<uint32_t> attribCount;\n   be2_val<uint32_t> numDivisors;\n   be2_val<uint32_t> divisors[2];\n};\nCHECK_OFFSET(GX2FetchShader, 0x0, type);\nCHECK_OFFSET(GX2FetchShader, 0x4, regs.sq_pgm_resources_fs);\nCHECK_OFFSET(GX2FetchShader, 0x8, size);\nCHECK_OFFSET(GX2FetchShader, 0xC, data);\nCHECK_OFFSET(GX2FetchShader, 0x10, attribCount);\nCHECK_OFFSET(GX2FetchShader, 0x14, numDivisors);\nCHECK_OFFSET(GX2FetchShader, 0x18, divisors);\nCHECK_SIZE(GX2FetchShader, 0x20);\n\n#pragma pack(pop)\n\nuint32_t\nGX2CalcFetchShaderSizeEx(uint32_t attribs,\n                         GX2FetchShaderType fetchShaderType,\n                         GX2TessellationMode tesellationMode);\n\nvoid\nGX2InitFetchShaderEx(virt_ptr<GX2FetchShader> fetchShader,\n                     virt_ptr<uint8_t> buffer,\n                     uint32_t attribCount,\n                     virt_ptr<GX2AttribStream> attribs,\n                     GX2FetchShaderType type,\n                     GX2TessellationMode tessMode);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_format.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_enum_string.h\"\n#include \"gx2_format.h\"\n\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n#include <libgpu/latte/latte_enum_sq.h>\n\nnamespace cafe::gx2\n{\n\nstruct GX2SurfaceFormatData\n{\n   uint8_t bpp;\n   GX2SurfaceUse use;\n   uint8_t endian;\n   uint8_t sourceFormat; // CB_SOURCE_FORMAT\n};\n\nstatic GX2SurfaceFormatData\nsSurfaceFormatData[] =\n{\n   { 0, GX2SurfaceUse::None, 0, 1 },\n   { 8, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 },\n   { 8, GX2SurfaceUse::Texture, 0, 1 },\n   { 0, GX2SurfaceUse::None, 0, 1 },\n   { 0, GX2SurfaceUse::None, 0, 1 },\n   { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::DepthBuffer, 0, 0 },\n   { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 },\n   { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 },\n   { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::ScanBuffer, 0, 1 },\n   { 16, GX2SurfaceUse::Texture, 0, 1 },\n   { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 },\n   { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 },\n   { 16, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 },\n   { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 },\n   { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::DepthBuffer, 0, 0 },\n   { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 },\n   { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 },\n   { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::DepthBuffer, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 1 },\n   { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 },\n   { 0, GX2SurfaceUse::None, 0, 1 },\n   { 0, GX2SurfaceUse::None, 0, 1 },\n   { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::ScanBuffer, 0, 1 },\n   { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::ScanBuffer, 0, 1 },\n   { 32, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer | GX2SurfaceUse::ScanBuffer, 0, 1 },\n   { 64, GX2SurfaceUse::Texture | GX2SurfaceUse::DepthBuffer, 0, 0 },\n   { 64, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 },\n   { 64, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 },\n   { 64, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 },\n   { 64, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 1 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 128, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 },\n   { 128, GX2SurfaceUse::Texture | GX2SurfaceUse::ColorBuffer, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 1 },\n   { 0, GX2SurfaceUse::None, 0, 1 },\n   { 0, GX2SurfaceUse::None, 0, 1 },\n   { 16, GX2SurfaceUse::Texture, 0, 0 },\n   { 16, GX2SurfaceUse::Texture, 0, 0 },\n   { 32, GX2SurfaceUse::Texture, 0, 0 },\n   { 32, GX2SurfaceUse::Texture, 0, 0 },\n   { 32, GX2SurfaceUse::Texture, 0, 0 },\n   { 0, GX2SurfaceUse::Texture, 0, 1 },\n   { 0, GX2SurfaceUse::Texture, 0, 0 },\n   { 0, GX2SurfaceUse::Texture, 0, 0 },\n   { 96, GX2SurfaceUse::Texture, 0, 0 },\n   { 96, GX2SurfaceUse::Texture, 0, 0 },\n   { 64, GX2SurfaceUse::Texture, 0, 1 },\n   { 128, GX2SurfaceUse::Texture, 0, 1 },\n   { 128, GX2SurfaceUse::Texture, 0, 1 },\n   { 64, GX2SurfaceUse::Texture, 0, 1 },\n   { 128, GX2SurfaceUse::Texture, 0, 1 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n   { 0, GX2SurfaceUse::None, 0, 0 },\n};\n\nBOOL\nGX2CheckSurfaceUseVsFormat(GX2SurfaceUse use,\n                           GX2SurfaceFormat format)\n{\n   return internal::getSurfaceUse(format) & use ? TRUE : FALSE;\n}\n\nuint32_t\nGX2GetAttribFormatBits(GX2AttribFormat format)\n{\n   switch (format & 0x1F) {\n   case GX2AttribFormatType::TYPE_8:\n   case GX2AttribFormatType::TYPE_4_4:\n      return 8;\n   case GX2AttribFormatType::TYPE_16:\n   case GX2AttribFormatType::TYPE_16_FLOAT:\n   case GX2AttribFormatType::TYPE_8_8:\n      return 16;\n   case GX2AttribFormatType::TYPE_32:\n   case GX2AttribFormatType::TYPE_32_FLOAT:\n   case GX2AttribFormatType::TYPE_16_16:\n   case GX2AttribFormatType::TYPE_16_16_FLOAT:\n   case GX2AttribFormatType::TYPE_10_11_11_FLOAT:\n   case GX2AttribFormatType::TYPE_8_8_8_8:\n   case GX2AttribFormatType::TYPE_10_10_10_2:\n      return 32;\n   case GX2AttribFormatType::TYPE_32_32:\n   case GX2AttribFormatType::TYPE_32_32_FLOAT:\n   case GX2AttribFormatType::TYPE_16_16_16_16:\n   case GX2AttribFormatType::TYPE_16_16_16_16_FLOAT:\n      return 64;\n   case GX2AttribFormatType::TYPE_32_32_32:\n   case GX2AttribFormatType::TYPE_32_32_32_FLOAT:\n      return 96;\n   case GX2AttribFormatType::TYPE_32_32_32_32:\n   case GX2AttribFormatType::TYPE_32_32_32_32_FLOAT:\n      return 128;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2AttribFormat {}\", to_string(format)));\n   }\n}\n\nuint32_t\nGX2GetSurfaceFormatBits(GX2SurfaceFormat format)\n{\n   auto latteFormat = format & 0x3F;\n   auto bpp = sSurfaceFormatData[latteFormat].bpp;\n\n   if (latteFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && latteFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      bpp >>= 4;\n   }\n\n   return bpp;\n}\n\nuint32_t\nGX2GetSurfaceFormatBitsPerElement(GX2SurfaceFormat format)\n{\n   return sSurfaceFormatData[format & 0x3F].bpp;\n}\n\nBOOL\nGX2SurfaceIsCompressed(GX2SurfaceFormat format)\n{\n   auto latteFormat = format & 0x3F;\n\n   if (latteFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && latteFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      return TRUE;\n   }\n\n   return FALSE;\n}\n\nnamespace internal\n{\n\nuint32_t\ngetAttribFormatBytes(GX2AttribFormat format)\n{\n   return GX2GetAttribFormatBits(format) / 8;\n}\n\nlatte::SQ_DATA_FORMAT\ngetAttribFormatDataFormat(GX2AttribFormat format)\n{\n   switch (format & 0x1F) {\n   case GX2AttribFormatType::TYPE_8:\n      return latte::SQ_DATA_FORMAT::FMT_8;\n   case GX2AttribFormatType::TYPE_4_4:\n      return latte::SQ_DATA_FORMAT::FMT_4_4;\n   case GX2AttribFormatType::TYPE_16:\n      return latte::SQ_DATA_FORMAT::FMT_16;\n   case GX2AttribFormatType::TYPE_16_FLOAT:\n      return latte::SQ_DATA_FORMAT::FMT_16_FLOAT;\n   case GX2AttribFormatType::TYPE_8_8:\n      return latte::SQ_DATA_FORMAT::FMT_8_8;\n   case GX2AttribFormatType::TYPE_32:\n      return latte::SQ_DATA_FORMAT::FMT_32;\n   case GX2AttribFormatType::TYPE_32_FLOAT:\n      return latte::SQ_DATA_FORMAT::FMT_32_FLOAT;\n   case GX2AttribFormatType::TYPE_16_16:\n      return latte::SQ_DATA_FORMAT::FMT_16_16;\n   case GX2AttribFormatType::TYPE_16_16_FLOAT:\n      return latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT;\n   case GX2AttribFormatType::TYPE_10_11_11_FLOAT:\n      return latte::SQ_DATA_FORMAT::FMT_10_11_11_FLOAT;\n   case GX2AttribFormatType::TYPE_8_8_8_8:\n      return latte::SQ_DATA_FORMAT::FMT_8_8_8_8;\n   case GX2AttribFormatType::TYPE_10_10_10_2:\n      return latte::SQ_DATA_FORMAT::FMT_10_10_10_2;\n   case GX2AttribFormatType::TYPE_32_32:\n      return latte::SQ_DATA_FORMAT::FMT_32_32;\n   case GX2AttribFormatType::TYPE_32_32_FLOAT:\n      return latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT;\n   case GX2AttribFormatType::TYPE_16_16_16_16:\n      return latte::SQ_DATA_FORMAT::FMT_16_16_16_16;\n   case GX2AttribFormatType::TYPE_16_16_16_16_FLOAT:\n      return latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT;\n   case GX2AttribFormatType::TYPE_32_32_32:\n      return latte::SQ_DATA_FORMAT::FMT_32_32_32;\n   case GX2AttribFormatType::TYPE_32_32_32_FLOAT:\n      return latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT;\n   case GX2AttribFormatType::TYPE_32_32_32_32:\n      return latte::SQ_DATA_FORMAT::FMT_32_32_32_32;\n   case GX2AttribFormatType::TYPE_32_32_32_32_FLOAT:\n      return latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2AttribFormat {}\", to_string(format)));\n   }\n}\n\nGX2EndianSwapMode\ngetAttribFormatSwapMode(GX2AttribFormat format)\n{\n   switch (format & 0x1F) {\n   case GX2AttribFormatType::TYPE_8:\n   case GX2AttribFormatType::TYPE_4_4:\n   case GX2AttribFormatType::TYPE_8_8:\n   case GX2AttribFormatType::TYPE_8_8_8_8:\n      return GX2EndianSwapMode::None;\n   case GX2AttribFormatType::TYPE_16:\n   case GX2AttribFormatType::TYPE_16_FLOAT:\n   case GX2AttribFormatType::TYPE_16_16:\n   case GX2AttribFormatType::TYPE_16_16_FLOAT:\n   case GX2AttribFormatType::TYPE_16_16_16_16:\n   case GX2AttribFormatType::TYPE_16_16_16_16_FLOAT:\n      return GX2EndianSwapMode::Swap8In16;\n   case GX2AttribFormatType::TYPE_32:\n   case GX2AttribFormatType::TYPE_32_FLOAT:\n   case GX2AttribFormatType::TYPE_10_11_11_FLOAT:\n   case GX2AttribFormatType::TYPE_10_10_10_2:\n   case GX2AttribFormatType::TYPE_32_32:\n   case GX2AttribFormatType::TYPE_32_32_FLOAT:\n   case GX2AttribFormatType::TYPE_32_32_32:\n   case GX2AttribFormatType::TYPE_32_32_32_FLOAT:\n   case GX2AttribFormatType::TYPE_32_32_32_32:\n   case GX2AttribFormatType::TYPE_32_32_32_32_FLOAT:\n      return GX2EndianSwapMode::Swap8In32;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2AttribFormat {}\", to_string(format)));\n   }\n}\n\nlatte::SQ_ENDIAN\ngetAttribFormatEndian(GX2AttribFormat format)\n{\n   return getSwapModeEndian(getAttribFormatSwapMode(format));\n}\n\nuint32_t\ngetSurfaceFormatBytesPerElement(GX2SurfaceFormat format)\n{\n   return sSurfaceFormatData[format & 0x3F].bpp / 8;\n}\n\nGX2SurfaceUse\ngetSurfaceUse(GX2SurfaceFormat format)\n{\n   auto idx = format & 0x3F;\n   if (idx == 17 || idx == 28) {\n      return GX2SurfaceUse::DepthBuffer | GX2SurfaceUse::Texture;\n   }\n\n   return static_cast<GX2SurfaceUse>(sSurfaceFormatData[format & 0x3F].use);\n}\n\nGX2EndianSwapMode\ngetSurfaceFormatSwapMode(GX2SurfaceFormat format)\n{\n   return static_cast<GX2EndianSwapMode>(sSurfaceFormatData[format & 0x3F].endian);\n}\n\nlatte::SQ_ENDIAN\ngetSurfaceFormatEndian(GX2SurfaceFormat format)\n{\n   return getSwapModeEndian(getSurfaceFormatSwapMode(format));\n}\n\nGX2SurfaceFormatType\ngetSurfaceFormatType(GX2SurfaceFormat format)\n{\n   return static_cast<GX2SurfaceFormatType>(format >> 8);\n}\n\nlatte::CB_ENDIAN\ngetSurfaceFormatColorEndian(GX2SurfaceFormat format)\n{\n   return static_cast<latte::CB_ENDIAN>(sSurfaceFormatData[format & 0x3F].endian);\n}\n\nlatte::CB_FORMAT\ngetSurfaceFormatColorFormat(GX2SurfaceFormat format)\n{\n   switch (format & 0x3F) {\n   case latte::SQ_DATA_FORMAT::FMT_8:\n      return latte::CB_FORMAT::COLOR_8;\n   case latte::SQ_DATA_FORMAT::FMT_4_4:\n      return latte::CB_FORMAT::COLOR_4_4;\n   case latte::SQ_DATA_FORMAT::FMT_3_3_2:\n      return latte::CB_FORMAT::COLOR_3_3_2;\n   case latte::SQ_DATA_FORMAT::FMT_16:\n      return latte::CB_FORMAT::COLOR_16;\n   case latte::SQ_DATA_FORMAT::FMT_16_FLOAT:\n      return latte::CB_FORMAT::COLOR_16_FLOAT;\n   case latte::SQ_DATA_FORMAT::FMT_8_8:\n      return latte::CB_FORMAT::COLOR_8_8;\n   case latte::SQ_DATA_FORMAT::FMT_5_6_5:\n      return latte::CB_FORMAT::COLOR_5_6_5;\n   case latte::SQ_DATA_FORMAT::FMT_6_5_5:\n      return latte::CB_FORMAT::COLOR_6_5_5;\n   case latte::SQ_DATA_FORMAT::FMT_1_5_5_5:\n      return latte::CB_FORMAT::COLOR_1_5_5_5;\n   case latte::SQ_DATA_FORMAT::FMT_4_4_4_4:\n      return latte::CB_FORMAT::COLOR_4_4_4_4;\n   case latte::SQ_DATA_FORMAT::FMT_5_5_5_1:\n      return latte::CB_FORMAT::COLOR_5_5_5_1;\n   case latte::SQ_DATA_FORMAT::FMT_32:\n      return latte::CB_FORMAT::COLOR_32;\n   case latte::SQ_DATA_FORMAT::FMT_32_FLOAT:\n      return latte::CB_FORMAT::COLOR_32_FLOAT;\n   case latte::SQ_DATA_FORMAT::FMT_16_16:\n      return latte::CB_FORMAT::COLOR_16_16;\n   case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT:\n      return latte::CB_FORMAT::COLOR_16_16_FLOAT;\n   case latte::SQ_DATA_FORMAT::FMT_8_24:\n      return latte::CB_FORMAT::COLOR_8_24;\n   case latte::SQ_DATA_FORMAT::FMT_8_24_FLOAT:\n      return latte::CB_FORMAT::COLOR_8_24_FLOAT;\n   case latte::SQ_DATA_FORMAT::FMT_24_8:\n      return latte::CB_FORMAT::COLOR_24_8;\n   case latte::SQ_DATA_FORMAT::FMT_24_8_FLOAT:\n      return latte::CB_FORMAT::COLOR_24_8_FLOAT;\n   case latte::SQ_DATA_FORMAT::FMT_10_11_11:\n      return latte::CB_FORMAT::COLOR_10_11_11;\n   case latte::SQ_DATA_FORMAT::FMT_10_11_11_FLOAT:\n      return latte::CB_FORMAT::COLOR_10_11_11_FLOAT;\n   case latte::SQ_DATA_FORMAT::FMT_11_11_10:\n      return latte::CB_FORMAT::COLOR_11_11_10;\n   case latte::SQ_DATA_FORMAT::FMT_11_11_10_FLOAT:\n      return latte::CB_FORMAT::COLOR_11_11_10_FLOAT;\n   case latte::SQ_DATA_FORMAT::FMT_2_10_10_10:\n      return latte::CB_FORMAT::COLOR_2_10_10_10;\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8_8:\n      return latte::CB_FORMAT::COLOR_8_8_8_8;\n   case latte::SQ_DATA_FORMAT::FMT_10_10_10_2:\n      return latte::CB_FORMAT::COLOR_10_10_10_2;\n   case latte::SQ_DATA_FORMAT::FMT_X24_8_32_FLOAT:\n      return latte::CB_FORMAT::COLOR_X24_8_32_FLOAT;\n   case latte::SQ_DATA_FORMAT::FMT_32_32:\n      return latte::CB_FORMAT::COLOR_32_32;\n   case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT:\n      return latte::CB_FORMAT::COLOR_32_32_FLOAT;\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16:\n      return latte::CB_FORMAT::COLOR_16_16_16_16;\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT:\n      return latte::CB_FORMAT::COLOR_16_16_16_16_FLOAT;\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32:\n      return latte::CB_FORMAT::COLOR_32_32_32_32;\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT:\n      return latte::CB_FORMAT::COLOR_32_32_32_32_FLOAT;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2SurfaceFormat {}\", to_string(format)));\n   }\n}\n\nlatte::CB_NUMBER_TYPE\ngetSurfaceFormatColorNumberType(GX2SurfaceFormat format)\n{\n   switch (internal::getSurfaceFormatType(format)) {\n   case GX2SurfaceFormatType::UNORM:\n      return latte::CB_NUMBER_TYPE::UNORM;\n   case GX2SurfaceFormatType::UINT:\n      return latte::CB_NUMBER_TYPE::UINT;\n   case GX2SurfaceFormatType::SNORM:\n      return latte::CB_NUMBER_TYPE::SNORM;\n   case GX2SurfaceFormatType::SINT:\n      return latte::CB_NUMBER_TYPE::SINT;\n   case GX2SurfaceFormatType::SRGB:\n      return latte::CB_NUMBER_TYPE::SRGB;\n   case GX2SurfaceFormatType::FLOAT:\n      return latte::CB_NUMBER_TYPE::FLOAT;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2SurfaceFormat {}\", to_string(format)));\n   }\n}\n\nlatte::CB_SOURCE_FORMAT\ngetSurfaceFormatColorSourceFormat(GX2SurfaceFormat format)\n{\n   return static_cast<latte::CB_SOURCE_FORMAT>(sSurfaceFormatData[format & 0x3F].sourceFormat);\n}\n\nlatte::SQ_ENDIAN\ngetSwapModeEndian(GX2EndianSwapMode mode)\n{\n   switch (mode) {\n   case GX2EndianSwapMode::None:\n      return latte::SQ_ENDIAN::NONE;\n   case GX2EndianSwapMode::Swap8In16:\n      return latte::SQ_ENDIAN::SWAP_8IN16;\n   case GX2EndianSwapMode::Swap8In32:\n      return latte::SQ_ENDIAN::SWAP_8IN32;\n   case GX2EndianSwapMode::Default:\n      return latte::SQ_ENDIAN::AUTO;\n   default:\n      decaf_abort(fmt::format(\"Invalid GX2EndianSwapMode {}\", to_string(mode)));\n   }\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerFormatSymbols()\n{\n   RegisterFunctionExport(GX2CheckSurfaceUseVsFormat);\n   RegisterFunctionExport(GX2GetAttribFormatBits);\n   RegisterFunctionExport(GX2GetSurfaceFormatBits);\n   RegisterFunctionExport(GX2GetSurfaceFormatBitsPerElement);\n   RegisterFunctionExport(GX2SurfaceIsCompressed);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_format.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <common/cbool.h>\n#include <cstdint>\n#include <libgpu/latte/latte_enum_cb.h>\n#include <libgpu/latte/latte_enum_db.h>\n#include <libgpu/latte/latte_enum_sq.h>\n#include <utility>\n\nnamespace cafe::gx2\n{\n\nBOOL\nGX2CheckSurfaceUseVsFormat(GX2SurfaceUse use,\n                           GX2SurfaceFormat format);\n\nuint32_t\nGX2GetAttribFormatBits(GX2AttribFormat format);\n\nuint32_t\nGX2GetSurfaceFormatBits(GX2SurfaceFormat format);\n\nuint32_t\nGX2GetSurfaceFormatBitsPerElement(GX2SurfaceFormat format);\n\nBOOL\nGX2SurfaceIsCompressed(GX2SurfaceFormat format);\n\nnamespace internal\n{\n\nuint32_t\ngetAttribFormatBytes(GX2AttribFormat format);\n\nlatte::SQ_DATA_FORMAT\ngetAttribFormatDataFormat(GX2AttribFormat type);\n\nGX2EndianSwapMode\ngetAttribFormatSwapMode(GX2AttribFormat format);\n\nlatte::SQ_ENDIAN\ngetAttribFormatEndian(GX2AttribFormat format);\n\nuint32_t\ngetSurfaceFormatBytesPerElement(GX2SurfaceFormat format);\n\nGX2SurfaceUse\ngetSurfaceUse(GX2SurfaceFormat format);\n\nlatte::CB_ENDIAN\ngetSurfaceFormatColorEndian(GX2SurfaceFormat format);\n\nlatte::CB_FORMAT\ngetSurfaceFormatColorFormat(GX2SurfaceFormat format);\n\nlatte::CB_NUMBER_TYPE\ngetSurfaceFormatColorNumberType(GX2SurfaceFormat format);\n\nlatte::CB_SOURCE_FORMAT\ngetSurfaceFormatColorSourceFormat(GX2SurfaceFormat format);\n\nlatte::SQ_ENDIAN\ngetSurfaceFormatEndian(GX2SurfaceFormat format);\n\nGX2EndianSwapMode\ngetSurfaceFormatSwapMode(GX2SurfaceFormat format);\n\nGX2SurfaceFormatType\ngetSurfaceFormatType(GX2SurfaceFormat format);\n\nlatte::SQ_ENDIAN\ngetSwapModeEndian(GX2EndianSwapMode mode);\n\n} // namespace internal\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_internal_flush.cpp",
    "content": "#include \"decaf_graphics.h\"\n#include \"gx2_internal_flush.h\"\n#include \"gx2_internal_pm4cap.h\"\n\nnamespace cafe::gx2::internal\n{\n\nvoid\nnotifyCpuFlush(phys_addr address,\n               uint32_t size)\n{\n   captureCpuFlush(address, size);\n   decaf::getGraphicsDriver()->notifyCpuFlush(address, size);\n}\n\nvoid\nnotifyGpuFlush(phys_addr address,\n               uint32_t size)\n{\n   captureGpuFlush(address, size);\n   decaf::getGraphicsDriver()->notifyGpuFlush(address, size);\n}\n\n} // namespace cafe::gx2::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_internal_flush.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2::internal\n{\n\nvoid\nnotifyCpuFlush(phys_addr address,\n               uint32_t size);\n\nvoid\nnotifyGpuFlush(phys_addr address,\n               uint32_t size);\n\n} // namespace cafe::gx2::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_internal_gfd.cpp",
    "content": "#include \"gx2_internal_gfd.h\"\n\nnamespace cafe::gx2::internal\n{\n\nvoid\ngfdToGX2Surface(const gfd::GFDSurface &src,\n                GX2Surface *dst)\n{\n   dst->dim = src.dim;\n   dst->width = src.width;\n   dst->height = src.height;\n   dst->depth = src.depth;\n   dst->mipLevels = src.mipLevels;\n   dst->format = src.format;\n   dst->aa = src.aa;\n   dst->use = src.use;\n   dst->imageSize = static_cast<uint32_t>(src.image.size());\n   dst->mipmapSize = static_cast<uint32_t>(src.mipmap.size());\n   dst->tileMode = src.tileMode;\n   dst->swizzle = src.swizzle;\n   dst->alignment = src.alignment;\n   dst->pitch = src.pitch;\n\n   for (auto i = 0u; i < src.mipLevelOffset.size(); ++i) {\n      dst->mipLevelOffset[i] = src.mipLevelOffset[i];\n   }\n}\n\nvoid\ngx2ToGFDSurface(const GX2Surface *src,\n                gfd::GFDSurface &dst)\n{\n   dst.dim = src->dim;\n   dst.width = src->width;\n   dst.height = src->height;\n   dst.depth = src->depth;\n   dst.mipLevels = src->mipLevels;\n   dst.format = src->format;\n   dst.aa = src->aa;\n   dst.use = src->use;\n   dst.image.resize(src->imageSize);\n   std::memcpy(dst.image.data(), src->image.get(), src->imageSize);\n   dst.mipmap.resize(src->mipmapSize);\n   std::memcpy(dst.mipmap.data(), src->mipmaps.get(), src->mipmapSize);\n   dst.tileMode = src->tileMode;\n   dst.swizzle = src->swizzle;\n   dst.alignment = src->alignment;\n   dst.pitch = src->pitch;\n\n   for (auto i = 0u; i < src->mipLevelOffset.size(); ++i) {\n      dst.mipLevelOffset[i] = src->mipLevelOffset[i];\n   }\n}\n\nvoid\ngfdToGX2Texture(const gfd::GFDTexture &src,\n                GX2Texture *dst)\n{\n   gfdToGX2Surface(src.surface,\n                   virt_addrof(dst->surface).get());\n\n   dst->viewFirstMip = src.viewFirstMip;\n   dst->viewNumMips = src.viewNumMips;\n   dst->viewFirstSlice = src.viewFirstSlice;\n   dst->viewNumSlices = src.viewNumSlices;\n   dst->compMap = src.compMap;\n\n   dst->regs.word0 = src.regs.word0;\n   dst->regs.word1 = src.regs.word1;\n   dst->regs.word4 = src.regs.word4;\n   dst->regs.word5 = src.regs.word5;\n   dst->regs.word6 = src.regs.word6;\n}\n\nvoid\ngx2ToGFDTexture(const GX2Texture *src,\n                gfd::GFDTexture &dst)\n{\n   gx2ToGFDSurface(&src->surface, dst.surface);\n\n   dst.viewFirstMip = src->viewFirstMip;\n   dst.viewNumMips = src->viewNumMips;\n   dst.viewFirstSlice = src->viewFirstSlice;\n   dst.viewNumSlices = src->viewNumSlices;\n   dst.compMap = src->compMap;\n\n   dst.regs.word0 = src->regs.word0;\n   dst.regs.word1 = src->regs.word1;\n   dst.regs.word4 = src->regs.word4;\n   dst.regs.word5 = src->regs.word5;\n   dst.regs.word6 = src->regs.word6;\n}\n\nvoid\ngx2ToGFDVertexShader(const GX2VertexShader *src,\n                     gfd::GFDVertexShader &dst)\n{\n   dst.regs.sq_pgm_resources_vs = src->regs.sq_pgm_resources_vs;\n   dst.regs.vgt_primitiveid_en = src->regs.vgt_primitiveid_en;\n   dst.regs.spi_vs_out_config = src->regs.spi_vs_out_config;\n   dst.regs.num_spi_vs_out_id = src->regs.num_spi_vs_out_id;\n\n   for (auto i = 0u; i < src->regs.spi_vs_out_id.size(); ++i) {\n      dst.regs.spi_vs_out_id[i] = src->regs.spi_vs_out_id[i];\n   }\n\n   dst.regs.pa_cl_vs_out_cntl = src->regs.pa_cl_vs_out_cntl;\n   dst.regs.sq_vtx_semantic_clear = src->regs.sq_vtx_semantic_clear;\n   dst.regs.num_sq_vtx_semantic = src->regs.num_sq_vtx_semantic;\n\n   for (auto i = 0u; i < src->regs.sq_vtx_semantic.size(); ++i) {\n      dst.regs.sq_vtx_semantic[i] = src->regs.sq_vtx_semantic[i];\n   }\n\n   dst.regs.vgt_strmout_buffer_en = src->regs.vgt_strmout_buffer_en;\n   dst.regs.vgt_vertex_reuse_block_cntl = src->regs.vgt_vertex_reuse_block_cntl;\n   dst.regs.vgt_hos_reuse_depth = src->regs.vgt_hos_reuse_depth;\n   dst.mode = src->mode;\n\n   dst.uniformBlocks.resize(src->uniformBlockCount);\n   for (auto i = 0u; i < src->uniformBlockCount; ++i) {\n      dst.uniformBlocks[i].name = src->uniformBlocks[i].name.get();\n      dst.uniformBlocks[i].offset = src->uniformBlocks[i].offset;\n      dst.uniformBlocks[i].size = src->uniformBlocks[i].size;\n   }\n\n   dst.uniformVars.resize(src->uniformVarCount);\n   for (auto i = 0u; i < src->uniformBlockCount; ++i) {\n      dst.uniformVars[i].name = src->uniformVars[i].name.get();\n      dst.uniformVars[i].type = src->uniformVars[i].type;\n      dst.uniformVars[i].count = src->uniformVars[i].count;\n      dst.uniformVars[i].offset = src->uniformVars[i].offset;\n      dst.uniformVars[i].block = src->uniformVars[i].block;\n   }\n\n   dst.initialValues.resize(src->initialValueCount);\n   for (auto i = 0u; i < src->initialValueCount; ++i) {\n      for (auto j = 0u; j < dst.initialValues[i].value.size(); ++j) {\n         dst.initialValues[i].value[j] = src->initialValues[i].value[j];\n      }\n\n      dst.initialValues[i].offset = src->initialValues[i].offset;\n   }\n\n   dst.loopVars.resize(src->loopVarCount);\n   for (auto i = 0u; i < src->loopVarCount; ++i) {\n      dst.loopVars[i].offset = src->loopVars[i].offset;\n      dst.loopVars[i].value = src->loopVars[i].value;\n   }\n\n   dst.samplerVars.resize(src->samplerVarCount);\n   for (auto i = 0u; i < src->samplerVarCount; ++i) {\n      dst.samplerVars[i].name = src->samplerVars[i].name.get();\n      dst.samplerVars[i].type = src->samplerVars[i].type;\n      dst.samplerVars[i].location = src->samplerVars[i].location;\n   }\n\n   dst.attribVars.resize(src->attribVarCount);\n   for (auto i = 0u; i < src->attribVarCount; ++i) {\n      dst.attribVars[i].name = src->attribVars[i].name.get();\n      dst.attribVars[i].type = src->attribVars[i].type;\n      dst.attribVars[i].count = src->attribVars[i].count;\n      dst.attribVars[i].location = src->attribVars[i].location;\n   }\n\n   dst.ringItemSize = src->ringItemsize;\n   dst.hasStreamOut = src->hasStreamOut;\n\n   for (auto i = 0u; i < dst.streamOutStride.size(); ++i) {\n      dst.streamOutStride[i] = src->streamOutStride[i];\n   }\n\n   dst.gx2rData.elemCount = src->gx2rData.elemCount;\n   dst.gx2rData.elemSize = src->gx2rData.elemSize;\n   dst.gx2rData.flags = src->gx2rData.flags;\n\n   if (src->gx2rData.buffer) {\n      auto size = src->gx2rData.elemCount * src->gx2rData.elemSize;\n      dst.gx2rData.buffer.resize(size);\n      std::memcpy(dst.gx2rData.buffer.data(),\n                  src->gx2rData.buffer.get(),\n                  size);\n   }\n}\n\nvoid\ngx2ToGFDPixelShader(const GX2PixelShader *src,\n                    gfd::GFDPixelShader &dst)\n{\n   dst.regs.sq_pgm_resources_ps = src->regs.sq_pgm_resources_ps;\n   dst.regs.sq_pgm_exports_ps = src->regs.sq_pgm_exports_ps;\n   dst.regs.spi_ps_in_control_0 = src->regs.spi_ps_in_control_0;\n   dst.regs.spi_ps_in_control_1 = src->regs.spi_ps_in_control_1;\n   dst.regs.num_spi_ps_input_cntl = src->regs.num_spi_ps_input_cntl;\n\n   for (auto i = 0u; i < src->regs.spi_ps_input_cntls.size(); ++i) {\n      dst.regs.spi_ps_input_cntls[i] = src->regs.spi_ps_input_cntls[i];\n   }\n\n   dst.regs.cb_shader_mask = src->regs.cb_shader_mask;\n   dst.regs.cb_shader_control = src->regs.cb_shader_control;\n   dst.regs.db_shader_control = src->regs.db_shader_control;\n   dst.regs.spi_input_z = src->regs.spi_input_z;\n\n   dst.data.resize(src->size);\n   std::memcpy(dst.data.data(), src->data.get(), src->size);\n   dst.mode = src->mode;\n\n   dst.uniformBlocks.resize(src->uniformBlockCount);\n   for (auto i = 0u; i < src->uniformBlockCount; ++i) {\n      dst.uniformBlocks[i].name = src->uniformBlocks[i].name.get();\n      dst.uniformBlocks[i].offset = src->uniformBlocks[i].offset;\n      dst.uniformBlocks[i].size = src->uniformBlocks[i].size;\n   }\n\n   dst.uniformVars.resize(src->uniformVarCount);\n   for (auto i = 0u; i < src->uniformBlockCount; ++i) {\n      dst.uniformVars[i].name = src->uniformVars[i].name.get();\n      dst.uniformVars[i].type = src->uniformVars[i].type;\n      dst.uniformVars[i].count = src->uniformVars[i].count;\n      dst.uniformVars[i].offset = src->uniformVars[i].offset;\n      dst.uniformVars[i].block = src->uniformVars[i].block;\n   }\n\n   dst.initialValues.resize(src->initialValueCount);\n   for (auto i = 0u; i < src->initialValueCount; ++i) {\n      for (auto j = 0u; j < dst.initialValues[i].value.size(); ++j) {\n         dst.initialValues[i].value[j] = src->initialValues[i].value[j];\n      }\n\n      dst.initialValues[i].offset = src->initialValues[i].offset;\n   }\n\n   dst.loopVars.resize(src->loopVarCount);\n   for (auto i = 0u; i < src->loopVarCount; ++i) {\n      dst.loopVars[i].offset = src->loopVars[i].offset;\n      dst.loopVars[i].value = src->loopVars[i].value;\n   }\n\n   dst.samplerVars.resize(src->samplerVarCount);\n   for (auto i = 0u; i < src->samplerVarCount; ++i) {\n      dst.samplerVars[i].name = src->samplerVars[i].name.get();\n      dst.samplerVars[i].type = src->samplerVars[i].type;\n      dst.samplerVars[i].location = src->samplerVars[i].location;\n   }\n\n   dst.gx2rData.elemCount = src->gx2rData.elemCount;\n   dst.gx2rData.elemSize = src->gx2rData.elemSize;\n   dst.gx2rData.flags = src->gx2rData.flags;\n\n   if (src->gx2rData.buffer) {\n      auto size = src->gx2rData.elemCount * src->gx2rData.elemSize;\n      dst.gx2rData.buffer.resize(size);\n      std::memcpy(dst.gx2rData.buffer.data(),\n                  src->gx2rData.buffer.get(),\n                  size);\n   }\n}\n\nvoid\ngx2ToGFDGeometryShader(const GX2GeometryShader *src,\n                       gfd::GFDGeometryShader &dst)\n{\n   dst.regs.sq_pgm_resources_gs = src->regs.sq_pgm_resources_gs;\n   dst.regs.vgt_gs_out_prim_type = src->regs.vgt_gs_out_prim_type;\n   dst.regs.vgt_gs_mode = src->regs.vgt_gs_mode;\n   dst.regs.pa_cl_vs_out_cntl = src->regs.pa_cl_vs_out_cntl;\n   dst.regs.sq_pgm_resources_vs = src->regs.sq_pgm_resources_vs;\n   dst.regs.sq_gs_vert_itemsize = src->regs.sq_gs_vert_itemsize;\n   dst.regs.spi_vs_out_config = src->regs.spi_vs_out_config;\n   dst.regs.num_spi_vs_out_id = src->regs.num_spi_vs_out_id;\n\n   for (auto i = 0u; i < src->regs.spi_vs_out_id.size(); ++i) {\n      dst.regs.spi_vs_out_id[i] = src->regs.spi_vs_out_id[i];\n   }\n\n   dst.regs.vgt_strmout_buffer_en = src->regs.vgt_strmout_buffer_en;\n\n   dst.data.resize(src->size);\n   std::memcpy(dst.data.data(), src->data.get(), src->size);\n\n   dst.vertexShaderData.resize(src->vertexShaderSize);\n   std::memcpy(dst.vertexShaderData.data(),\n               src->vertexShaderData.get(),\n               src->vertexShaderSize);\n\n   dst.mode = src->mode;\n\n   dst.uniformBlocks.resize(src->uniformBlockCount);\n   for (auto i = 0u; i < src->uniformBlockCount; ++i) {\n      dst.uniformBlocks[i].name = src->uniformBlocks[i].name.get();\n      dst.uniformBlocks[i].offset = src->uniformBlocks[i].offset;\n      dst.uniformBlocks[i].size = src->uniformBlocks[i].size;\n   }\n\n   dst.uniformVars.resize(src->uniformVarCount);\n   for (auto i = 0u; i < src->uniformBlockCount; ++i) {\n      dst.uniformVars[i].name = src->uniformVars[i].name.get();\n      dst.uniformVars[i].type = src->uniformVars[i].type;\n      dst.uniformVars[i].count = src->uniformVars[i].count;\n      dst.uniformVars[i].offset = src->uniformVars[i].offset;\n      dst.uniformVars[i].block = src->uniformVars[i].block;\n   }\n\n   dst.initialValues.resize(src->initialValueCount);\n   for (auto i = 0u; i < src->initialValueCount; ++i) {\n      for (auto j = 0u; j < dst.initialValues[i].value.size(); ++j) {\n         dst.initialValues[i].value[j] = src->initialValues[i].value[j];\n      }\n\n      dst.initialValues[i].offset = src->initialValues[i].offset;\n   }\n\n   dst.loopVars.resize(src->loopVarCount);\n   for (auto i = 0u; i < src->loopVarCount; ++i) {\n      dst.loopVars[i].offset = src->loopVars[i].offset;\n      dst.loopVars[i].value = src->loopVars[i].value;\n   }\n\n   dst.samplerVars.resize(src->samplerVarCount);\n   for (auto i = 0u; i < src->samplerVarCount; ++i) {\n      dst.samplerVars[i].name = src->samplerVars[i].name.get();\n      dst.samplerVars[i].type = src->samplerVars[i].type;\n      dst.samplerVars[i].location = src->samplerVars[i].location;\n   }\n\n   dst.ringItemSize = src->ringItemSize;\n   dst.hasStreamOut = src->hasStreamOut;\n\n   for (auto i = 0u; i < dst.streamOutStride.size(); ++i) {\n      dst.streamOutStride[i] = src->streamOutStride[i];\n   }\n\n   dst.gx2rData.elemCount = src->gx2rData.elemCount;\n   dst.gx2rData.elemSize = src->gx2rData.elemSize;\n   dst.gx2rData.flags = src->gx2rData.flags;\n\n   if (src->gx2rData.buffer) {\n      auto size = src->gx2rData.elemCount * src->gx2rData.elemSize;\n      dst.gx2rData.buffer.resize(size);\n      std::memcpy(dst.gx2rData.buffer.data(),\n                  src->gx2rData.buffer.get(),\n                  size);\n   }\n\n   dst.gx2rVertexShaderData.elemCount = src->gx2rVertexShaderData.elemCount;\n   dst.gx2rVertexShaderData.elemSize = src->gx2rVertexShaderData.elemSize;\n   dst.gx2rVertexShaderData.flags = src->gx2rVertexShaderData.flags;\n\n   if (src->gx2rVertexShaderData.buffer) {\n      auto size = src->gx2rVertexShaderData.elemCount * src->gx2rVertexShaderData.elemSize;\n      dst.gx2rVertexShaderData.buffer.resize(size);\n      std::memcpy(dst.gx2rVertexShaderData.buffer.data(),\n                  src->gx2rVertexShaderData.buffer.get(),\n                  size);\n   }\n}\n\n} // namespace cafe::gx2::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_internal_gfd.h",
    "content": "#pragma once\n#include \"gx2_shaders.h\"\n#include \"gx2_surface.h\"\n#include \"gx2_texture.h\"\n\n#include <libgfd/gfd.h>\n\nnamespace cafe::gx2::internal\n{\n\nvoid\ngfdToGX2Surface(const gfd::GFDSurface &src,\n                GX2Surface *dst);\nvoid\ngx2ToGFDSurface(const GX2Surface *src,\n                gfd::GFDSurface &dst);\n\nvoid\ngfdToGX2Texture(const gfd::GFDTexture &src,\n                GX2Texture *dst);\n\nvoid\ngx2ToGFDTexture(const GX2Texture *src,\n                gfd::GFDTexture &dst);\n\nvoid\ngx2ToGFDVertexShader(const GX2VertexShader *src,\n                     gfd::GFDVertexShader &dst);\n\nvoid\ngx2ToGFDPixelShader(const GX2PixelShader *src,\n                    gfd::GFDPixelShader &dst);\n\nvoid\ngx2ToGFDGeometryShader(const GX2GeometryShader *src,\n                       gfd::GFDGeometryShader &dst);\n\n} // namespace cafe::gx2::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_internal_pm4cap.cpp",
    "content": "#include \"decaf_pm4replay.h\"\n#include \"gx2_display.h\"\n#include \"gx2_event.h\"\n#include \"gx2_internal_pm4cap.h\"\n#include \"gx2_cbpool.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <array>\n#include <addrlib/addrinterface.h>\n#include <common/byte_swap.h>\n#include <common/log.h>\n#include <common/platform_dir.h>\n#include <common/murmur3.h>\n#include <fmt/core.h>\n#include <fstream>\n#include <gsl/gsl-lite.hpp>\n#include <libcpu/cpu_formatters.h>\n#include <libgpu/gpu7_tiling.h>\n#include <libgpu/latte/latte_constants.h>\n#include <libgpu/latte/latte_formats.h>\n#include <libgpu/latte/latte_pm4.h>\n#include <libgpu/latte/latte_pm4_commands.h>\n#include <libgpu/latte/latte_pm4_reader.h>\n#include <mutex>\n#include <vector>\n\nusing decaf::pm4::CaptureMagic;\nusing decaf::pm4::CapturePacket;\nusing decaf::pm4::CaptureMemoryLoad;\nusing decaf::pm4::CaptureSetBuffer;\nusing namespace latte;\nusing namespace latte::pm4;\nusing namespace cafe::coreinit;\n\nstatic const auto\nHashAllMemory = true;\n\nstatic const auto\nHashShadowState = true;\n\nnamespace cafe::gx2::internal\n{\n\nclass Recorder\n{\n   struct RecordedMemory\n   {\n      phys_addr start;\n      phys_addr end;\n      uint64_t hash[2];\n   };\n\npublic:\n   Recorder()\n   {\n      mRegisters.fill(0);\n   }\n\n   bool\n   requestStart(const std::string &path)\n   {\n      decaf_check(mState == CaptureState::Disabled);\n      std::unique_lock<std::mutex> lock { mMutex };\n      mOut.open(path, std::fstream::binary);\n\n      if (!mOut.is_open()) {\n         return false;\n      }\n\n      // Write magic header\n      mOut.write(CaptureMagic.data(), CaptureMagic.size());\n\n      // Set intial state\n      mRecordedMemory.clear();\n      mState = CaptureState::WaitStartNextFrame;\n\n      return true;\n   }\n\n   void\n   requestStop()\n   {\n      decaf_check(mState == CaptureState::Enabled);\n      std::unique_lock<std::mutex> lock { mMutex };\n      mState = CaptureState::WaitEndNextFrame;\n   }\n\n   void\n   setCaptureNumFrames(size_t count)\n   {\n      mCaptureNumFrames = count;\n   }\n\n   CaptureState\n   state() const\n   {\n      return mState;\n   }\n\n   void\n   swap()\n   {\n      std::unique_lock<std::mutex> lock { mMutex };\n\n      if (mState == CaptureState::WaitStartNextFrame) {\n         start();\n         mCapturedFrames = 0;\n      } else if (mState == CaptureState::WaitEndNextFrame) {\n         stop();\n         mCaptureNumFrames = 0;\n      }\n\n      ++mCapturedFrames;\n\n      if (mCapturedFrames == mCaptureNumFrames) {\n         mState = CaptureState::WaitEndNextFrame;\n      }\n   }\n\n   void\n   commandBuffer(virt_ptr<uint32_t> buffer,\n                 uint32_t numWords)\n   {\n      decaf_check(mState == CaptureState::Enabled ||\n                  mState == CaptureState::WaitEndNextFrame);\n      std::unique_lock<std::mutex> lock { mMutex };\n      scanCommandBuffer(\n         phys_cast<uint32_t *>(OSEffectiveToPhysical(virt_cast<virt_addr>(buffer))),\n         numWords);\n\n      CapturePacket packet;\n      packet.type = CapturePacket::CommandBuffer;\n      packet.size = numWords * 4;\n      writePacket(packet);\n      writeData(buffer.get(), packet.size);\n   }\n\n   void\n   cpuFlush(phys_addr address,\n            uint32_t size)\n   {\n      decaf_check(mState == CaptureState::Enabled ||\n                  mState == CaptureState::WaitEndNextFrame);\n      std::unique_lock<std::mutex> lock { mMutex };\n      trackMemory(CaptureMemoryLoad::CpuFlush, address, size);\n   }\n\n   void\n   gpuFlush(phys_addr address,\n            uint32_t size)\n   {\n      decaf_check(mState == CaptureState::Enabled ||\n                  mState == CaptureState::WaitEndNextFrame);\n      // Not sure if we need to do something here...\n   }\n\nprivate:\n   void\n   start()\n   {\n      decaf_check(mState == CaptureState::Disabled || mState == CaptureState::WaitStartNextFrame);\n      mState = CaptureState::Enabled;\n      writeRegisterSnapshot();\n      writeDisplayInfo();\n   }\n\n   void\n   stop()\n   {\n      decaf_check(mState == CaptureState::Enabled || mState == CaptureState::WaitEndNextFrame);\n      mOut.close();\n      mState = CaptureState::Disabled;\n   }\n\n   void\n   writeRegisterSnapshot()\n   {\n      CapturePacket packet;\n      packet.type = CapturePacket::RegisterSnapshot;\n      packet.size = static_cast<uint32_t>(mRegisters.size() * sizeof(uint32_t));\n      writePacket(packet);\n      writeData(mRegisters.data(), packet.size);\n   }\n\n   void\n   writeDisplayInfo()\n   {\n      auto tvScanBuffer = getTvScanBuffer();\n      if (tvScanBuffer->image) {\n         CapturePacket packet;\n         packet.type = CapturePacket::SetBuffer;\n         packet.size = sizeof(CaptureSetBuffer);\n         writePacket(packet);\n\n         CaptureSetBuffer setBuffer;\n         setBuffer.type = CaptureSetBuffer::TvBuffer;\n         setBuffer.address = OSEffectiveToPhysical(virt_cast<virt_addr>(tvScanBuffer->image));\n         setBuffer.size = tvScanBuffer->imageSize;\n         setBuffer.renderMode = GX2TVRenderMode::Wide1080p;\n         setBuffer.surfaceFormat = tvScanBuffer->format;\n         setBuffer.bufferingMode = GX2BufferingMode::Double;\n         setBuffer.width = tvScanBuffer->width;\n         setBuffer.height = tvScanBuffer->height;\n         writeData(&setBuffer, packet.size);\n      }\n\n      auto drcScanBuffer = getDrcScanBuffer();\n      if (drcScanBuffer->image) {\n         CapturePacket packet;\n         packet.type = CapturePacket::SetBuffer;\n         packet.size = sizeof(CaptureSetBuffer);\n         writePacket(packet);\n\n         CaptureSetBuffer setBuffer;\n         setBuffer.type = CaptureSetBuffer::DrcBuffer;\n         setBuffer.address = OSEffectiveToPhysical(virt_cast<virt_addr>(drcScanBuffer->image));\n         setBuffer.size = drcScanBuffer->imageSize;\n         setBuffer.renderMode = GX2DrcRenderMode::Single;\n         setBuffer.surfaceFormat = drcScanBuffer->format;\n         setBuffer.bufferingMode = GX2BufferingMode::Double;\n         setBuffer.width = drcScanBuffer->width;\n         setBuffer.height = drcScanBuffer->height;\n         writeData(&setBuffer, packet.size);\n      }\n   }\n\n   void\n   writePacket(CapturePacket &packet)\n   {\n      packet.timestamp = mPacketTimestamp++;\n      mOut.write(reinterpret_cast<const char *>(&packet), sizeof(CapturePacket));\n   }\n\n   void\n   writeData(void *data, uint32_t size)\n   {\n      mOut.write(reinterpret_cast<const char *>(data), size);\n   }\n\n   void\n   writeMemoryLoad(CaptureMemoryLoad::MemoryType type,\n                   phys_addr address,\n                   uint32_t size)\n   {\n      CaptureMemoryLoad load;\n      load.type = type;\n      load.address = address;\n\n      CapturePacket packet;\n      packet.type = CapturePacket::MemoryLoad;\n      packet.size = size + sizeof(CaptureMemoryLoad);\n      writePacket(packet);\n      writeData(&load, sizeof(CaptureMemoryLoad));\n      writeData(phys_cast<void *>(address).get(), size);\n   }\n\n   void\n   scanCommandBuffer(phys_ptr<uint32_t> words, uint32_t numWords)\n   {\n      std::vector<uint32_t> swapped;\n      swapped.resize(numWords);\n\n      for (auto i = 0u; i < swapped.size(); ++i) {\n         swapped[i] = words[i];\n      }\n\n      auto buffer = swapped.data();\n      auto bufferSize = swapped.size();\n\n      for (auto pos = size_t { 0u }; pos < bufferSize; ) {\n         auto header = Header::get(buffer[pos]);\n         auto size = size_t { 0u };\n\n         switch (header.type()) {\n         case PacketType::Type0:\n         {\n            auto header0 = HeaderType0::get(header.value);\n            size = header0.count() + 1;\n\n            decaf_check(pos + size < bufferSize);\n            scanType0(header0, gsl::make_span(&buffer[pos + 1], size));\n            break;\n         }\n         case PacketType::Type3:\n         {\n            auto header3 = HeaderType3::get(header.value);\n            size = header3.size() + 1;\n\n            decaf_check(pos + size < bufferSize);\n            scanType3(header3, gsl::make_span(&buffer[pos + 1], size));\n            break;\n         }\n         case PacketType::Type2:\n         {\n            // This is a filler packet, like a \"nop\", ignore it\n            break;\n         }\n         case PacketType::Type1:\n         default:\n            gLog->error(\"Invalid packet header type {}, header = 0x{:08X}\",\n                        header.type(), header.value);\n            size = bufferSize;\n            break;\n         }\n\n         pos += size + 1;\n      }\n   }\n\n   void\n   scanType0(HeaderType0 header,\n             const gsl::span<uint32_t> &data)\n   {\n   }\n\n   void\n   trackSurface(phys_addr baseAddress,\n                uint32_t pitch,\n                uint32_t height,\n                uint32_t depth,\n                uint32_t aa,\n                uint32_t level,\n                bool isDepthBuffer,\n                bool isScanBuffer,\n                SQ_TEX_DIM dim,\n                SQ_DATA_FORMAT format,\n                SQ_TILE_MODE tileMode)\n   {\n      if (!baseAddress || !pitch || !height) {\n         return;\n      }\n\n      // Adjust address for swizzling\n      if (tileMode >= SQ_TILE_MODE::TILED_2D_THIN1) {\n         baseAddress &= ~(0x800u - 1);\n      } else {\n         baseAddress &= ~(0x100u - 1);\n      }\n\n      auto desc = gpu7::tiling::SurfaceDescription{ };\n      desc.tileMode = static_cast<gpu7::tiling::TileMode>(tileMode);\n      desc.format = static_cast<gpu7::tiling::DataFormat>(format);\n      desc.bpp = getDataFormatBitsPerElement(format);\n      desc.numSamples = 1; // TODO: 1 << aa\n      desc.width = pitch;\n      desc.height = height;\n      desc.numSlices = depth;\n      desc.numLevels = 0;\n      desc.bankSwizzle = 0;\n      desc.pipeSwizzle = 0;\n      if (isDepthBuffer) {\n         desc.use |= gpu7::tiling::SurfaceUse::DepthBuffer;\n      }\n      if (isScanBuffer) {\n         desc.use |= gpu7::tiling::SurfaceUse::ScanBuffer;\n      }\n      desc.dim = static_cast<gpu7::tiling::SurfaceDim>(dim);\n\n      auto info = gpu7::tiling::computeSurfaceInfo(desc, 0);\n      // TODO: Use align? info.baseAlign;\n\n      // Track that badboy\n      trackMemory(CaptureMemoryLoad::Surface, baseAddress, static_cast<uint32_t>(info.surfSize));\n   }\n\n   void\n   trackColorBuffer(CB_COLORN_BASE cb_color_base,\n                    CB_COLORN_SIZE cb_color_size,\n                    CB_COLORN_INFO cb_color_info)\n   {\n      auto pitch_tile_max = cb_color_size.PITCH_TILE_MAX();\n      auto slice_tile_max = cb_color_size.SLICE_TILE_MAX();\n      auto pitch = static_cast<uint32_t>((pitch_tile_max + 1) * MicroTileWidth);\n      auto height = static_cast<uint32_t>(\n         ((slice_tile_max + 1) * (MicroTileWidth * MicroTileHeight)) / pitch);\n      auto addr = (cb_color_base.BASE_256B() << 8);\n\n      if (!addr || !pitch || !height) {\n         return;\n      }\n\n      // Disabled for now, because it's a pointless upload\n      // auto format = static_cast<SQ_DATA_FORMAT>(cb_color_info.FORMAT());\n      // auto tileMode = getArrayModeTileMode(cb_color_info.ARRAY_MODE());\n      // trackSurface(addr, pitch, height, 1, SQ_TEX_DIM::DIM_2D, format, tileMode);\n   }\n\n   void\n   trackDepthBuffer(DB_DEPTH_BASE db_depth_base,\n                    DB_DEPTH_SIZE db_depth_size,\n                    DB_DEPTH_INFO db_depth_info)\n   {\n      auto pitch_tile_max = db_depth_size.PITCH_TILE_MAX();\n      auto slice_tile_max = db_depth_size.SLICE_TILE_MAX();\n      auto pitch = static_cast<uint32_t>((pitch_tile_max + 1) * MicroTileWidth);\n      auto height = static_cast<uint32_t>(\n         ((slice_tile_max + 1) * (MicroTileWidth * MicroTileHeight)) / pitch);\n      auto addr = (db_depth_base.BASE_256B() << 8);\n\n      if (!addr || !pitch || !height) {\n         return;\n      }\n\n      // Disabled for now, because it's a pointless upload\n      // auto format = static_cast<SQ_DATA_FORMAT>(db_depth_info.FORMAT());\n      // auto tileMode = getArrayModeTileMode(db_depth_info.ARRAY_MODE());\n      //trackSurface(addr, pitch, height, 1, SQ_TEX_DIM::DIM_2D, format, tileMode);\n   }\n\n   void\n      trackActiveShaders()\n   {\n      auto pgm_start_fs = getRegister<SQ_PGM_START_FS>(Register::SQ_PGM_START_FS);\n      auto pgm_size_fs = getRegister<SQ_PGM_SIZE_FS>(Register::SQ_PGM_SIZE_FS);\n      trackMemory(CaptureMemoryLoad::FetchShader,\n                  phys_addr { pgm_start_fs.PGM_START() << 8 },\n                  pgm_size_fs.PGM_SIZE() << 3);\n\n      auto pgm_start_vs = getRegister<SQ_PGM_START_VS>(Register::SQ_PGM_START_VS);\n      auto pgm_size_vs = getRegister<SQ_PGM_SIZE_VS>(Register::SQ_PGM_SIZE_VS);\n      trackMemory(CaptureMemoryLoad::VertexShader,\n                  phys_addr { pgm_start_vs.PGM_START() << 8 },\n                  pgm_size_vs.PGM_SIZE() << 3);\n\n      auto pgm_start_ps = getRegister<SQ_PGM_START_PS>(Register::SQ_PGM_START_PS);\n      auto pgm_size_ps = getRegister<SQ_PGM_SIZE_PS>(Register::SQ_PGM_SIZE_PS);\n      trackMemory(CaptureMemoryLoad::PixelShader,\n                  phys_addr { pgm_start_ps.PGM_START() << 8 },\n                  pgm_size_ps.PGM_SIZE() << 3);\n\n      auto pgm_start_gs = getRegister<SQ_PGM_START_GS>(Register::SQ_PGM_START_GS);\n      auto pgm_size_gs = getRegister<SQ_PGM_SIZE_GS>(Register::SQ_PGM_SIZE_GS);\n      trackMemory(CaptureMemoryLoad::GeometryShader,\n                  phys_addr { pgm_start_gs.PGM_START() << 8 },\n                  pgm_size_gs.PGM_SIZE() << 3);\n   }\n\n   void\n   trackActiveAttribBuffers()\n   {\n      for (auto i = 0u; i < MaxAttribBuffers; ++i) {\n         auto resourceOffset = (SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 + i) * 7;\n         auto sq_vtx_constant_word0 = getRegister<SQ_VTX_CONSTANT_WORD0_N>(Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset);\n         auto sq_vtx_constant_word1 = getRegister<SQ_VTX_CONSTANT_WORD1_N>(Register::SQ_RESOURCE_WORD1_0 + 4 * resourceOffset);\n\n         trackMemory(CaptureMemoryLoad::AttributeBuffer,\n                     phys_addr { sq_vtx_constant_word0.BASE_ADDRESS() },\n                     sq_vtx_constant_word1.SIZE() + 1);\n      }\n   }\n\n   void\n   trackActiveUniforms()\n   {\n      for (auto i = 0u; i < MaxUniformBlocks; ++i) {\n         auto sq_alu_const_cache_vs = getRegister<uint32_t>(Register::SQ_ALU_CONST_CACHE_VS_0 + 4 * i);\n         auto sq_alu_const_buffer_size_vs = getRegister<uint32_t>(Register::SQ_ALU_CONST_BUFFER_SIZE_VS_0 + 4 * i);\n\n         trackMemory(CaptureMemoryLoad::UniformBuffer,\n                     phys_addr { sq_alu_const_cache_vs << 8 },\n                     sq_alu_const_buffer_size_vs << 8);\n      }\n   }\n\n   void\n   trackActiveTextures()\n   {\n      for (auto i = 0u; i < MaxTextures; ++i) {\n         auto resourceOffset = (SQ_RES_OFFSET::PS_TEX_RESOURCE_0 + i) * 7;\n         auto sq_tex_resource_word0 = getRegister<SQ_TEX_RESOURCE_WORD0_N>(Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset);\n         auto sq_tex_resource_word1 = getRegister<SQ_TEX_RESOURCE_WORD1_N>(Register::SQ_RESOURCE_WORD1_0 + 4 * resourceOffset);\n         auto sq_tex_resource_word2 = getRegister<SQ_TEX_RESOURCE_WORD2_N>(Register::SQ_RESOURCE_WORD2_0 + 4 * resourceOffset);\n         auto sq_tex_resource_word3 = getRegister<SQ_TEX_RESOURCE_WORD3_N>(Register::SQ_RESOURCE_WORD3_0 + 4 * resourceOffset);\n         auto sq_tex_resource_word4 = getRegister<SQ_TEX_RESOURCE_WORD4_N>(Register::SQ_RESOURCE_WORD4_0 + 4 * resourceOffset);\n         auto sq_tex_resource_word5 = getRegister<SQ_TEX_RESOURCE_WORD5_N>(Register::SQ_RESOURCE_WORD5_0 + 4 * resourceOffset);\n         auto sq_tex_resource_word6 = getRegister<SQ_TEX_RESOURCE_WORD6_N>(Register::SQ_RESOURCE_WORD6_0 + 4 * resourceOffset);\n\n         trackSurface(phys_addr { sq_tex_resource_word2.BASE_ADDRESS() << 8 },\n                      (sq_tex_resource_word0.PITCH() + 1) * 8,\n                      sq_tex_resource_word1.TEX_HEIGHT() + 1,\n                      sq_tex_resource_word1.TEX_DEPTH() + 1,\n                      sq_tex_resource_word5.LAST_LEVEL(),\n                      0,\n                      sq_tex_resource_word0.TILE_TYPE() == 1,\n                      false,\n                      sq_tex_resource_word0.DIM(),\n                      sq_tex_resource_word1.DATA_FORMAT(),\n                      sq_tex_resource_word0.TILE_MODE());\n      }\n   }\n\n   void\n   trackActiveColorBuffer()\n   {\n      for (auto i = 0u; i < MaxRenderTargets; ++i) {\n         auto cb_color_base = getRegister<CB_COLORN_BASE>(Register::CB_COLOR0_BASE + i * 4);\n         auto cb_color_size = getRegister<CB_COLORN_SIZE>(Register::CB_COLOR0_SIZE + i * 4);\n         auto cb_color_info = getRegister<CB_COLORN_INFO>(Register::CB_COLOR0_INFO + i * 4);\n\n         trackColorBuffer(cb_color_base, cb_color_size, cb_color_info);\n      }\n   }\n\n   void\n   trackActiveDepthBuffer()\n   {\n      auto db_depth_base = getRegister<DB_DEPTH_BASE>(Register::DB_DEPTH_BASE);\n      auto db_depth_size = getRegister<DB_DEPTH_SIZE>(Register::DB_DEPTH_SIZE);\n      auto db_depth_info = getRegister<DB_DEPTH_INFO>(Register::DB_DEPTH_INFO);\n\n      trackDepthBuffer(db_depth_base, db_depth_size, db_depth_info);\n   }\n\n   void\n   trackReadyDraw()\n   {\n      trackActiveShaders();\n      trackActiveAttribBuffers();\n      trackActiveUniforms();\n      trackActiveTextures();\n      trackActiveColorBuffer();\n      trackActiveDepthBuffer();\n   }\n\n   void\n   scanSetRegister(Register reg,\n                   uint32_t value)\n   {\n      mRegisters[reg / 4] = value;\n   }\n\n   void\n   scanLoadRegisters(Register base,\n                     uint32_t *src,\n                     const gsl::span<std::pair<uint32_t, uint32_t>> &registers)\n   {\n      for (auto &range : registers) {\n         auto start = range.first;\n         auto count = range.second;\n\n         for (auto j = start; j < start + count; ++j) {\n            scanSetRegister(static_cast<Register>(base + j * 4), src[j]);\n         }\n      }\n   }\n\n   template<typename Type>\n   Type\n   getRegister(uint32_t id)\n   {\n      return bit_cast<Type>(mRegisters[id / 4]);\n   }\n\n   void\n   scanType3(HeaderType3 header,\n             const gsl::span<uint32_t> &rawData)\n   {\n      PacketReader reader { rawData };\n\n      switch (header.opcode()) {\n      case IT_OPCODE::DECAF_COPY_COLOR_TO_SCAN:\n      {\n         auto data = read<DecafCopyColorToScan>(reader);\n         trackColorBuffer(data.cb_color_base,\n                          data.cb_color_size,\n                          data.cb_color_info);\n         break;\n      }\n      case IT_OPCODE::DECAF_SWAP_BUFFERS:\n         // Nothing to track!\n         break;\n      case IT_OPCODE::DECAF_CLEAR_COLOR:\n      {\n         auto data = read<DecafClearColor>(reader);\n         trackColorBuffer(data.cb_color_base,\n                          data.cb_color_size,\n                          data.cb_color_info);\n         break;\n      }\n      case IT_OPCODE::DECAF_CLEAR_DEPTH_STENCIL:\n      {\n         auto data = read<DecafClearDepthStencil>(reader);\n         trackDepthBuffer(data.db_depth_base,\n                          data.db_depth_size,\n                          data.db_depth_info);\n         break;\n      }\n      case IT_OPCODE::DECAF_SET_BUFFER:\n         // Nothing to track!\n         break;\n      case IT_OPCODE::DECAF_OSSCREEN_FLIP:\n         decaf_abort(\"pm4 capture not enabled for OSScreen api\");\n         break;\n      case IT_OPCODE::DECAF_COPY_SURFACE:\n      {\n         auto data = read<DecafCopySurface>(reader);\n         trackSurface(phys_addr { data.srcImage }, data.srcPitch,\n                      data.srcHeight, data.srcDepth, 0, data.srcLevel, false,\n                      false, data.srcDim, data.srcFormat, data.srcTileMode);\n         break;\n      }\n      case IT_OPCODE::DRAW_INDEX_AUTO:\n         trackReadyDraw();\n         break;\n      case IT_OPCODE::DRAW_INDEX_2:\n      {\n         auto data = read<DrawIndex2>(reader);\n         auto vgt_dma_index_type =\n            getRegister<VGT_DMA_INDEX_TYPE>(\n               Register::VGT_DMA_INDEX_TYPE);\n         auto indexByteSize = 4u;\n\n         if (vgt_dma_index_type.INDEX_TYPE() == VGT_INDEX_TYPE::INDEX_16) {\n            indexByteSize = 2u;\n         }\n\n         trackMemory(CaptureMemoryLoad::IndexBuffer,\n                     phys_addr { data.addr },\n                     data.count * indexByteSize);\n         trackReadyDraw();\n         break;\n      }\n      case IT_OPCODE::DRAW_INDEX_IMMD:\n         trackReadyDraw();\n         break;\n      case IT_OPCODE::INDEX_TYPE:\n      {\n         auto data = read<IndexType>(reader);\n         mRegisters[Register::VGT_DMA_INDEX_TYPE / 4] = data.type.value;\n         break;\n      }\n      case IT_OPCODE::NUM_INSTANCES:\n      {\n         auto data = read<NumInstances>(reader);\n         mRegisters[Register::VGT_DMA_NUM_INSTANCES / 4] = data.count;\n         break;\n      }\n      case IT_OPCODE::SET_ALU_CONST:\n      {\n         auto data = read<SetAluConsts>(reader);\n\n         for (auto i = 0u; i < data.values.size(); ++i) {\n            scanSetRegister(static_cast<Register>(data.id + i * 4),\n                            data.values[i]);\n         }\n\n         break;\n      }\n      case IT_OPCODE::SET_CONFIG_REG:\n      {\n         auto data = read<SetConfigRegs>(reader);\n\n         for (auto i = 0u; i < data.values.size(); ++i) {\n            scanSetRegister(static_cast<Register>(data.id + i * 4),\n                            data.values[i]);\n         }\n\n         break;\n      }\n      case IT_OPCODE::SET_CONTEXT_REG:\n      {\n         auto data = read<SetContextRegs>(reader);\n\n         for (auto i = 0u; i < data.values.size(); ++i) {\n            scanSetRegister(static_cast<Register>(data.id + i * 4),\n                            data.values[i]);\n         }\n\n         break;\n      }\n      case IT_OPCODE::SET_CTL_CONST:\n      {\n         auto data = read<SetControlConstants>(reader);\n\n         for (auto i = 0u; i < data.values.size(); ++i) {\n            scanSetRegister(static_cast<Register>(data.id + i * 4),\n                            data.values[i]);\n         }\n\n         break;\n      }\n      case IT_OPCODE::SET_LOOP_CONST:\n      {\n         auto data = read<SetLoopConsts>(reader);\n\n         for (auto i = 0u; i < data.values.size(); ++i) {\n            scanSetRegister(static_cast<Register>(data.id + i * 4),\n                            data.values[i]);\n         }\n\n         break;\n      }\n      case IT_OPCODE::SET_SAMPLER:\n      {\n         auto data = read<SetSamplers>(reader);\n\n         for (auto i = 0u; i < data.values.size(); ++i) {\n            scanSetRegister(static_cast<Register>(data.id + i * 4),\n                            data.values[i]);\n         }\n\n         break;\n      }\n      case IT_OPCODE::SET_RESOURCE:\n      {\n         auto data = read<SetResources>(reader);\n         auto id = Register::ResourceRegisterBase + (4 * data.id);\n\n         for (auto i = 0u; i < data.values.size(); ++i) {\n            scanSetRegister(static_cast<Register>(id + i * 4),\n                            data.values[i]);\n         }\n\n         break;\n      }\n      case IT_OPCODE::LOAD_CONFIG_REG:\n      {\n         auto data = read<LoadControlConst>(reader);\n         scanLoadRegisters(Register::ConfigRegisterBase,\n                           phys_cast<uint32_t *>(data.addr).getRawPointer(),\n                           data.values);\n         trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0xB00 * 4);\n         break;\n      }\n      case IT_OPCODE::LOAD_CONTEXT_REG:\n      {\n         auto data = read<LoadControlConst>(reader);\n         scanLoadRegisters(Register::ContextRegisterBase,\n                           phys_cast<uint32_t *>(data.addr).getRawPointer(),\n                           data.values);\n         trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0x400 * 4);\n         break;\n      }\n      case IT_OPCODE::LOAD_ALU_CONST:\n      {\n         auto data = read<LoadControlConst>(reader);\n         scanLoadRegisters(Register::AluConstRegisterBase,\n                           phys_cast<uint32_t *>(data.addr).getRawPointer(),\n                           data.values);\n         trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0x800 * 4);\n         break;\n      }\n      case IT_OPCODE::LOAD_BOOL_CONST:\n      {\n         decaf_abort(\"Unsupported LOAD_BOOL_CONST\");\n         auto data = read<LoadControlConst>(reader);\n         scanLoadRegisters(Register::BoolConstRegisterBase,\n                           phys_cast<uint32_t *>(data.addr).getRawPointer(),\n                           data.values);\n         break;\n      }\n      case IT_OPCODE::LOAD_LOOP_CONST:\n      {\n         auto data = read<LoadControlConst>(reader);\n         scanLoadRegisters(Register::LoopConstRegisterBase,\n                           phys_cast<uint32_t *>(data.addr).getRawPointer(),\n                           data.values);\n         trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0x60 * 4);\n         break;\n      }\n      case IT_OPCODE::LOAD_RESOURCE:\n      {\n         auto data = read<LoadControlConst>(reader);\n         scanLoadRegisters(Register::ResourceRegisterBase,\n                           phys_cast<uint32_t *>(data.addr).getRawPointer(),\n                           data.values);\n         trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0xD9E * 4);\n         break;\n      }\n      case IT_OPCODE::LOAD_SAMPLER:\n      {\n         auto data = read<LoadControlConst>(reader);\n         scanLoadRegisters(Register::SamplerRegisterBase,\n                           phys_cast<uint32_t *>(data.addr).getRawPointer(),\n                           data.values);\n         trackMemory(CaptureMemoryLoad::ShadowState, data.addr, 0xA2 * 4);\n         break;\n      }\n      case IT_OPCODE::LOAD_CTL_CONST:\n      {\n         decaf_abort(\"Unsupported LOAD_CTL_CONST\");\n         auto data = read<LoadControlConst>(reader);\n         scanLoadRegisters(Register::ControlRegisterBase,\n                           phys_cast<uint32_t *>(data.addr).getRawPointer(),\n                           data.values);\n         break;\n      }\n      case IT_OPCODE::INDIRECT_BUFFER:\n      {\n         auto data = read<IndirectBufferCall>(reader);\n         trackMemory(CaptureMemoryLoad::CommandBuffer, data.addr, data.size * 4u);\n         scanCommandBuffer(phys_cast<uint32_t *>(data.addr), data.size);\n         break;\n      }\n      case IT_OPCODE::INDIRECT_BUFFER_PRIV:\n      {\n         auto data = read<IndirectBufferCallPriv>(reader);\n         trackMemory(CaptureMemoryLoad::CommandBuffer, data.addr, data.size * 4u);\n         scanCommandBuffer(phys_cast<uint32_t *>(data.addr), data.size);\n         break;\n      }\n      case IT_OPCODE::MEM_WRITE:\n         break;\n      case IT_OPCODE::EVENT_WRITE:\n         break;\n      case IT_OPCODE::EVENT_WRITE_EOP:\n         break;\n      case IT_OPCODE::PFP_SYNC_ME:\n         break;\n      case IT_OPCODE::STRMOUT_BASE_UPDATE:\n         break;\n      case IT_OPCODE::STRMOUT_BUFFER_UPDATE:\n         break;\n      case IT_OPCODE::NOP:\n         break;\n      case IT_OPCODE::SURFACE_SYNC:\n      {\n         auto data = read<SurfaceSync>(reader);\n         trackMemory(CaptureMemoryLoad::SurfaceSync,\n                     phys_addr { data.addr << 8 },\n                     data.size << 8);\n         break;\n      }\n      case IT_OPCODE::CONTEXT_CTL:\n         break;\n      default:\n         gLog->debug(\"Unhandled pm4 packet type 3 opcode {}\", header.opcode());\n      }\n   }\n\n   // Returns true if the memory was written into pm4 stream\n   bool\n   trackMemory(CaptureMemoryLoad::MemoryType type,\n               phys_addr addr,\n               uint32_t size)\n   {\n      auto trackStart = addr;\n      auto trackEnd = trackStart + size;\n      auto addNewEntry = true;\n      uint64_t hash[2] = { 0, 0 };\n      auto useHash = HashAllMemory || (HashShadowState && type == CaptureMemoryLoad::ShadowState);\n\n      if (!addr || size == 0) {\n         return false;\n      }\n\n      if (useHash) {\n         MurmurHash3_x64_128(phys_cast<void *>(addr).get(), size, 0, hash);\n      }\n\n      for (auto &mem : mRecordedMemory) {\n         if (trackStart < mem.start || trackStart > mem.end) {\n            // Not in this block!\n            continue;\n         }\n\n         if (trackEnd <= mem.end) {\n            // Current memory is completely contained within an already tracked block\n            if (useHash) {\n               // If hash is enabled, then we do not write if hash matches\n               if (hash[0] == mem.hash[0] && hash[1] == mem.hash[1]) {\n                  return false;\n               }\n            } else if (type != CaptureMemoryLoad::CpuFlush) {\n               // If hash is disabled, and this is NOT a flush, then do not write memory\n               return false;\n            }\n         }\n\n         mem.end = trackEnd;\n         mem.hash[0] = hash[0];\n         mem.hash[1] = hash[1];\n         addNewEntry = false;\n         break;\n      }\n\n      if (addNewEntry) {\n         mRecordedMemory.emplace_back(RecordedMemory { trackStart, trackEnd, hash[0], hash[1] });\n      }\n\n      writeMemoryLoad(type, addr, size);\n      return true;\n   }\n\nprivate:\n   CaptureState mState = CaptureState::Disabled;\n   std::mutex mMutex;\n   std::ofstream mOut;\n   std::vector<RecordedMemory> mRecordedMemory;\n   std::array<uint32_t, 0x10000> mRegisters;\n   size_t mCapturedFrames = 0;\n   size_t mCaptureNumFrames = 0;\n   uint64_t mPacketTimestamp = 0ull;\n};\n\nstatic Recorder\ngRecorder;\n\nbool\ncaptureStartAtNextSwap()\n{\n   decaf_check(gRecorder.state() == CaptureState::Disabled);\n   auto filename = std::string {};\n\n   // Find an unused filename!\n   for (auto i = 0u; i < 256u; ++i) {\n      filename = fmt::format(\"trace{}.pm4\", i);\n\n      if (platform::fileExists(filename)) {\n         continue;\n      }\n\n      break;\n   }\n\n   if (filename.empty()) {\n      return false;\n   }\n\n   gLog->info(\"Starting pm4 trace as {}\", filename);\n   return gRecorder.requestStart(filename);\n}\n\nvoid\ncaptureStopAtNextSwap()\n{\n   gRecorder.requestStop();\n}\n\nbool\ncaptureNextFrame()\n{\n   gRecorder.setCaptureNumFrames(1);\n   return captureStartAtNextSwap();\n}\n\nCaptureState\ncaptureState()\n{\n   return gRecorder.state();\n}\n\nvoid\ncaptureSwap()\n{\n   if (captureState() != CaptureState::Disabled) {\n      gx2::GX2DrawDone();\n      gRecorder.swap();\n   }\n}\n\nvoid\ncaptureCommandBuffer(virt_ptr<uint32_t> buffer,\n                     uint32_t numWords)\n{\n   if (captureState() == CaptureState::Enabled ||\n       captureState() == CaptureState::WaitEndNextFrame) {\n      gRecorder.commandBuffer(buffer, numWords);\n   }\n}\n\nvoid\ncaptureCpuFlush(phys_addr address,\n                uint32_t size)\n{\n   if (captureState() == CaptureState::Enabled ||\n       captureState() == CaptureState::WaitEndNextFrame) {\n      gRecorder.cpuFlush(address, size);\n   }\n}\n\nvoid\ncaptureGpuFlush(phys_addr address,\n                uint32_t size)\n{\n   if (captureState() == CaptureState::Enabled ||\n       captureState() == CaptureState::WaitEndNextFrame) {\n      gRecorder.gpuFlush(address, size);\n   }\n}\n\n} // namespace cafe::gx2::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_internal_pm4cap.h",
    "content": "#pragma once\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2::internal\n{\n\nenum class CaptureState\n{\n   Disabled,\n   WaitStartNextFrame,\n   Enabled,\n   WaitEndNextFrame,\n};\n\nbool\ncaptureStartAtNextSwap();\n\nvoid\ncaptureStopAtNextSwap();\n\nbool\ncaptureNextFrame();\n\nCaptureState\ncaptureState();\n\nvoid\ncaptureSwap();\n\nvoid\ncaptureCommandBuffer(virt_ptr<uint32_t> buffer,\n                     uint32_t numWords);\n\nvoid\ncaptureCpuFlush(phys_addr buffer,\n                uint32_t size);\n\nvoid\ncaptureGpuFlush(phys_addr buffer,\n                uint32_t size);\n\n} // namespace cafe::gx2::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_internal_writegatherptr.h",
    "content": "#pragma once\n#include <cstring>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2::internal\n{\n\n/**\n * A very simple class to emulate a write gather pointer.\n */\ntemplate<typename Type>\nclass be2_write_gather_ptr\n{\npublic:\n   be2_val<Type> &\n   operator *()\n   {\n      return *(mPointer++);\n   }\n\n   void\n   write(Type value)\n   {\n      *(mPointer++) = value;\n   }\n\n   void\n   write(virt_ptr<Type> buffer,\n         uint32_t count)\n   {\n      std::memcpy(mPointer.get(), buffer.get(), sizeof(Type) * count);\n      mPointer += count;\n   }\n\n   virt_ptr<Type>\n   get()\n   {\n      return mPointer;\n   }\n\n   be2_write_gather_ptr &\n   operator =(virt_ptr<Type> ptr)\n   {\n      mPointer = ptr;\n      return *this;\n   }\n\nprivate:\n   be2_virt_ptr<Type> mPointer;\n};\n\n} // namespace cafe::gx2::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_memory.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_aperture.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_debugcapture.h\"\n#include \"gx2_memory.h\"\n#include \"gx2_state.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_cache.h\"\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <common/align.h>\n#include <common/log.h>\n#include <fmt/core.h>\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2Invalidate(GX2InvalidateMode mode,\n              virt_ptr<void> buffer,\n              uint32_t size)\n{\n   if (!mode) {\n      return;\n   }\n\n   auto addr = virt_cast<virt_addr>(buffer);\n\n   if (size != -1) {\n      size = align_up(size, 0x100);\n   }\n\n   if (addr >= virt_addr { 0xE8000000 } &&\n       addr < virt_addr { 0xEA000000 }) {\n      internal::translateAperture(addr, size);\n   }\n\n   if (mode & GX2InvalidateMode::CPU) {\n      DCFlushRange(addr, size);\n\n      if (internal::debugCaptureEnabled()) {\n         internal::debugCaptureInvalidate(buffer, size);\n      }\n   }\n\n   if (mode != GX2InvalidateMode::CPU) {\n      auto cp_coher_cntl = latte::CP_COHER_CNTL::get(0)\n         .ENGINE_ME(true);\n\n      if (!addr && size == -1 && (mode & 0xF)) {\n         cp_coher_cntl = cp_coher_cntl\n            .FULL_CACHE_ENA(true);\n      }\n\n      if ((mode & GX2InvalidateMode::Texture) || (mode & GX2InvalidateMode::AttributeBuffer)) {\n         cp_coher_cntl = cp_coher_cntl\n            .TC_ACTION_ENA(true);\n      }\n\n      if (mode & GX2InvalidateMode::UniformBlock) {\n         cp_coher_cntl = cp_coher_cntl\n            .TC_ACTION_ENA(true)\n            .SH_ACTION_ENA(true);\n      }\n\n      if (mode & GX2InvalidateMode::Shader) {\n         cp_coher_cntl = cp_coher_cntl\n            .SH_ACTION_ENA(true);\n      }\n\n      if (mode & GX2InvalidateMode::ColorBuffer) {\n         cp_coher_cntl = cp_coher_cntl\n            .CB0_DEST_BASE_ENA(true)\n            .CB1_DEST_BASE_ENA(true)\n            .CB2_DEST_BASE_ENA(true)\n            .CB3_DEST_BASE_ENA(true)\n            .CB4_DEST_BASE_ENA(true)\n            .CB5_DEST_BASE_ENA(true)\n            .CB6_DEST_BASE_ENA(true)\n            .CB7_DEST_BASE_ENA(true)\n            .CB_ACTION_ENA(true);\n      }\n\n      if (mode & GX2InvalidateMode::DepthBuffer) {\n         cp_coher_cntl = cp_coher_cntl\n            .DB_DEST_BASE_ENA(true)\n            .DB_ACTION_ENA(true);\n      }\n\n      if (mode & GX2InvalidateMode::StreamOutBuffer) {\n         cp_coher_cntl = cp_coher_cntl\n            .SO0_DEST_BASE_ENA(true)\n            .SO1_DEST_BASE_ENA(true)\n            .SO2_DEST_BASE_ENA(true)\n            .SO3_DEST_BASE_ENA(true)\n            .SX_ACTION_ENA(true);\n      }\n\n      if (mode & GX2InvalidateMode::ExportBuffer) {\n         cp_coher_cntl = cp_coher_cntl\n            .DEST_BASE_0_ENA(true)\n            .TC_ACTION_ENA(true)\n            .CB_ACTION_ENA(true)\n            .DB_ACTION_ENA(true)\n            .SX_ACTION_ENA(true);\n      }\n\n      internal::writePM4(latte::pm4::SurfaceSync {\n         cp_coher_cntl,\n         size >> 8,\n         OSEffectiveToPhysical(addr) >> 8,\n         4\n      });\n   }\n}\n\nvoid\nLibrary::registerMemorySymbols()\n{\n   RegisterFunctionExport(GX2Invalidate);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_memory.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2Invalidate(GX2InvalidateMode mode,\n              virt_ptr<void> buffer,\n              uint32_t size);\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_query.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_query.h\"\n#include \"gx2_memory.h\"\n#include \"cafe/libraries/coreinit/coreinit_cache.h\"\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <libcpu/mem.h>\n\nusing namespace cafe::coreinit;\nusing namespace latte::pm4;\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2SampleTopGPUCycle(virt_ptr<int64_t> writeSamplePtr)\n{\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(writeSamplePtr));\n   *writeSamplePtr = -1;\n\n   auto addrLo = MW_ADDR_LO::get(0)\n      .ADDR_LO(addr >> 2)\n      .ENDIAN_SWAP(latte::CB_ENDIAN::SWAP_8IN64);\n\n   auto addrHi = MW_ADDR_HI::get(0)\n      .CNTR_SEL(MW_WRITE_CLOCK);\n\n   internal::writePM4(MemWrite { addrLo, addrHi, 0, 0 });\n}\n\nvoid\nGX2SampleBottomGPUCycle(virt_ptr<int64_t> writeSamplePtr)\n{\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(writeSamplePtr));\n   *writeSamplePtr = -1;\n\n   auto eventInitiator = latte::VGT_EVENT_INITIATOR::get(0)\n      .EVENT_TYPE(latte::VGT_EVENT_TYPE::BOTTOM_OF_PIPE_TS)\n      .EVENT_INDEX(latte::VGT_EVENT_INDEX::TS);\n\n   auto addrLo = EW_ADDR_LO::get(0)\n      .ADDR_LO(addr >> 2)\n      .ENDIAN_SWAP(latte::CB_ENDIAN::SWAP_8IN64);\n\n   auto addrHi = EWP_ADDR_HI::get(0)\n      .DATA_SEL(EWP_DATA_CLOCK);\n\n   internal::writePM4(EventWriteEOP { eventInitiator, addrLo, addrHi, 0, 0 });\n}\n\nuint64_t\nGX2GPUTimeToCPUTime(uint64_t time)\n{\n   return time;\n}\n\nstatic void\nbeginOcclusionQuery(virt_ptr<GX2QueryData> data,\n                    bool gpuMemoryWrite)\n{\n   decaf_check(virt_cast<virt_addr>(data) % 4 == 0);\n\n   // There is some magic number read from their global gx2 state + 0xB0C, no\n   // fucking clue what it is, so let's just set it to 0?\n   auto magicNumber = 0u;\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(data));\n\n   if (gpuMemoryWrite) {\n      DCInvalidateRange(virt_cast<virt_addr>(data), sizeof(GX2QueryData));\n\n      // Zero GX2QueryData from GPU\n      for (auto i = 0u; i < 8; ++i) {\n         auto addrLo = MW_ADDR_LO::get(0)\n            .ADDR_LO((addr + 8 * i) >> 2);\n\n         auto addrHi = MW_ADDR_HI::get(0)\n            .WR_CONFIRM(true);\n\n         auto dataHi = 0u;\n\n         if (i < magicNumber) {\n            dataHi = 0x80000000;\n         }\n\n         internal::writePM4(MemWrite { addrLo, addrHi, 0, dataHi });\n      }\n\n      internal::writePM4(PfpSyncMe {});\n   } else {\n      std::memset(data.get(), 0, sizeof(GX2QueryData));\n\n      auto dataWords = virt_cast<uint32_t *>(data);\n      dataWords[magicNumber + 0] = 0x4F435055u; // \"OCPU\"\n      dataWords[magicNumber + 1] = 0u;\n      GX2Invalidate(GX2InvalidateMode::StreamOutBuffer,\n                    dataWords,\n                    sizeof(GX2QueryData));\n   }\n\n   // DB_RENDER_CONTROL\n   auto render_control = latte::DB_RENDER_CONTROL::get(0)\n      .PERFECT_ZPASS_COUNTS(true);\n\n   internal::writePM4(SetContextReg { latte::Register::DB_RENDER_CONTROL, render_control.value });\n\n   // EVENT_WRITE\n   auto eventInitiator = latte::VGT_EVENT_INITIATOR::get(0)\n      .EVENT_TYPE(latte::VGT_EVENT_TYPE::ZPASS_DONE)\n      .EVENT_INDEX(latte::VGT_EVENT_INDEX::ZPASS_DONE);\n\n   auto addrLo = EW_ADDR_LO::get(0)\n      .ADDR_LO(addr >> 2);\n\n   auto addrHi = EW_ADDR_HI::get(0);\n\n   internal::writePM4(EventWrite { eventInitiator, addrLo, addrHi });\n}\n\nstatic void\nendOcclusionQuery(virt_ptr<GX2QueryData> data,\n                  bool gpuMemoryWrite)\n{\n   // EVENT_WRITE\n   auto eventInitiator = latte::VGT_EVENT_INITIATOR::get(0)\n      .EVENT_TYPE(latte::VGT_EVENT_TYPE::ZPASS_DONE)\n      .EVENT_INDEX(latte::VGT_EVENT_INDEX::ZPASS_DONE);\n\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(data));\n   auto addrLo = EW_ADDR_LO::get(0)\n      .ADDR_LO((addr + 8) >> 2);\n\n   auto addrHi = EW_ADDR_HI::get(0);\n\n   internal::writePM4(EventWrite { eventInitiator, addrLo, addrHi });\n\n   // DB_RENDER_CONTROL\n   auto render_control = latte::DB_RENDER_CONTROL::get(0)\n      .PERFECT_ZPASS_COUNTS(false);\n\n   internal::writePM4(SetContextReg { latte::Register::DB_RENDER_CONTROL, render_control.value });\n}\n\nstatic void\nbeginStreamOutStatsQuery(virt_ptr<GX2QueryData> data,\n                         bool gpuMemoryWrite)\n{\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(data));\n   decaf_check(addr % 4 == 0);\n\n   // There is some magic number read from their global gx2 state + 0xB0C, no\n   // fucking clue what it is, so let's just set it to 0?\n   auto magicNumber = 0u;\n   auto endianSwap = latte::CB_ENDIAN::NONE;\n\n   if (gpuMemoryWrite) {\n      DCInvalidateRange(virt_cast<virt_addr>(data), sizeof(GX2QueryData));\n\n      // Zero data first\n      for (auto i = 0u; i < 4; ++i) {\n         auto addrLo = MW_ADDR_LO::get(0)\n            .ADDR_LO((addr + 8 * i) >> 2);\n\n         auto addrHi = MW_ADDR_HI::get(0)\n            .WR_CONFIRM(true);\n\n         internal::writePM4(MemWrite { addrLo, addrHi, 0, 0 });\n      }\n\n      internal::writePM4(PfpSyncMe {});\n\n      endianSwap = latte::CB_ENDIAN::SWAP_8IN32;\n   } else {\n      std::memset(data.get(), 0, sizeof(GX2QueryData));\n\n      auto dataWords = virt_cast<uint32_t *>(data);\n      dataWords[magicNumber + 0] = 0x53435055u; // \"SCPU\"\n      dataWords[magicNumber + 1] = 0u;\n      GX2Invalidate(GX2InvalidateMode::StreamOutBuffer, dataWords, sizeof(GX2QueryData));\n\n      endianSwap = latte::CB_ENDIAN::SWAP_8IN64;\n   }\n\n   // EVENT_WRITE\n   auto eventInitiator = latte::VGT_EVENT_INITIATOR::get(0)\n      .EVENT_TYPE(latte::VGT_EVENT_TYPE::SAMPLE_STREAMOUTSTATS)\n      .EVENT_INDEX(latte::VGT_EVENT_INDEX::SAMPLE_STREAMOUTSTAT);\n\n   auto addrLo = EW_ADDR_LO::get(0)\n      .ADDR_LO(addr >> 2)\n      .ENDIAN_SWAP(endianSwap);\n\n   auto addrHi = EW_ADDR_HI::get(0);\n\n   internal::writePM4(EventWrite { eventInitiator, addrLo, addrHi });\n}\n\nstatic void\nendStreamOutStatsQuery(virt_ptr<GX2QueryData> data,\n                       bool gpuMemoryWrite)\n{\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(data));\n   auto endianSwap = latte::CB_ENDIAN::NONE;\n\n   if (gpuMemoryWrite) {\n      endianSwap = latte::CB_ENDIAN::SWAP_8IN32;\n   } else {\n      endianSwap = latte::CB_ENDIAN::SWAP_8IN64;\n   }\n\n   // EVENT_WRITE\n   auto eventInitiator = latte::VGT_EVENT_INITIATOR::get(0)\n      .EVENT_TYPE(latte::VGT_EVENT_TYPE::SAMPLE_STREAMOUTSTATS)\n      .EVENT_INDEX(latte::VGT_EVENT_INDEX::SAMPLE_STREAMOUTSTAT);\n\n   auto addrLo = EW_ADDR_LO::get(0)\n      .ADDR_LO(addr >> 2)\n      .ENDIAN_SWAP(endianSwap);\n\n   auto addrHi = EW_ADDR_HI::get(0);\n\n   internal::writePM4(EventWrite { eventInitiator, addrLo, addrHi });\n}\n\nvoid\nGX2QueryBegin(GX2QueryType type,\n              virt_ptr<GX2QueryData> data)\n{\n   switch (type) {\n   case GX2QueryType::OcclusionQuery:\n      beginOcclusionQuery(data, false);\n      break;\n   case GX2QueryType::OcclusionQueryGpuMem:\n      beginOcclusionQuery(data, true);\n      break;\n   case GX2QueryType::StreamOutStats:\n      beginStreamOutStatsQuery(data, false);\n      break;\n   case GX2QueryType::StreamOutStatsGpuMem:\n      beginStreamOutStatsQuery(data, true);\n      break;\n   }\n}\n\nvoid\nGX2QueryEnd(GX2QueryType type,\n            virt_ptr<GX2QueryData> data)\n{\n   switch (type) {\n   case GX2QueryType::OcclusionQuery:\n      endOcclusionQuery(data, false);\n      break;\n   case GX2QueryType::OcclusionQueryGpuMem:\n      endOcclusionQuery(data, true);\n      break;\n   case GX2QueryType::StreamOutStats:\n      endStreamOutStatsQuery(data, false);\n      break;\n   case GX2QueryType::StreamOutStatsGpuMem:\n      endStreamOutStatsQuery(data, true);\n      break;\n   }\n}\n\nvoid\nGX2QueryGetOcclusionResult(virt_ptr<GX2QueryData> data,\n                           virt_ptr<uint64_t> outResult)\n{\n   *outResult = data->_gpudata[1] - data->_gpudata[0];\n}\n\nvoid\nGX2QueryBeginConditionalRender(GX2QueryType type,\n                               virt_ptr<GX2QueryData> data,\n                               BOOL hint,\n                               BOOL predicate)\n{\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(data));\n   auto op = SP_PRED_OP_PRIMCOUNT;\n\n   if (type == 2) {\n      op = SP_PRED_OP_ZPASS;\n   }\n\n   auto set_pred = SET_PRED::get(0)\n      .PRED_OP(op)\n      .HINT(!!hint)\n      .PREDICATE(!!predicate);\n\n   internal::writePM4(SetPredication { static_cast<uint32_t>(addr), set_pred });\n}\n\nvoid\nGX2QueryEndConditionalRender()\n{\n   internal::writePM4(SetPredication { 0, SET_PRED::get(0) });\n}\n\nbool\nGX2PerfMetricEnable(virt_ptr<GX2PerfData> data,\n                    GX2PerfType type,\n                    uint32_t \tid)\n{\n   return true;\n}\n\nvoid\nLibrary::registerQuerySymbols()\n{\n   RegisterFunctionExport(GX2QueryBegin);\n   RegisterFunctionExport(GX2QueryEnd);\n   RegisterFunctionExport(GX2QueryGetOcclusionResult);\n   RegisterFunctionExport(GX2QueryBeginConditionalRender);\n   RegisterFunctionExport(GX2QueryEndConditionalRender);\n   RegisterFunctionExport(GX2SampleTopGPUCycle);\n   RegisterFunctionExport(GX2SampleBottomGPUCycle);\n   RegisterFunctionExport(GX2GPUTimeToCPUTime);\n   RegisterFunctionExport(GX2PerfMetricEnable);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_query.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\nstruct GX2PerfData\n{\n   UNKNOWN(0x8a0);\n};\nCHECK_SIZE(GX2PerfData, 0x8a0);\n\nstruct GX2QueryData\n{\n   // Note that these are intentionally host-endian as they\n   // are GPU managed which is litte-endian, not big-endian (PPC).\n   uint64_t _gpudata[8];\n};\nCHECK_SIZE(GX2QueryData, 0x40);\n\nvoid\nGX2SampleTopGPUCycle(virt_ptr<int64_t> writeSamplePtr);\n\nvoid\nGX2SampleBottomGPUCycle(virt_ptr<int64_t> writeSamplePtr);\n\nuint64_t\nGX2GPUTimeToCPUTime(uint64_t time);\n\nvoid\nGX2QueryBegin(GX2QueryType type,\n              virt_ptr<GX2QueryData> data);\n\nvoid\nGX2QueryEnd(GX2QueryType type,\n            virt_ptr<GX2QueryData> data);\n\nvoid\nGX2QueryGetOcclusionResult(virt_ptr<GX2QueryData> data,\n                           virt_ptr<uint64_t> outResult);\n\nvoid\nGX2QueryBeginConditionalRender(GX2QueryType type,\n                               virt_ptr<GX2QueryData> data,\n                               BOOL hint,\n                               BOOL predicate);\nvoid\nGX2QueryEndConditionalRender();\n\nbool\nGX2PerfMetricEnable(virt_ptr<GX2PerfData> data,\n                    GX2PerfType type,\n                    uint32_t \tid);\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_registers.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_registers.h\"\n#include \"cafe/cafe_stackobject.h\"\n\nusing namespace latte::pm4;\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2SetAAMask(uint8_t upperLeft,\n             uint8_t upperRight,\n             uint8_t lowerLeft,\n             uint8_t lowerRight)\n{\n   auto reg = StackObject<GX2AAMaskReg> { };\n   GX2InitAAMaskReg(reg,\n                    upperLeft,\n                    upperRight,\n                    lowerLeft,\n                    lowerRight);\n   GX2SetAAMaskReg(reg);\n}\n\nvoid\nGX2InitAAMaskReg(virt_ptr<GX2AAMaskReg> reg,\n                 uint8_t upperLeft,\n                 uint8_t upperRight,\n                 uint8_t lowerLeft,\n                 uint8_t lowerRight)\n{\n   auto pa_sc_aa_mask = latte::PA_SC_AA_MASK::get(0);\n\n   pa_sc_aa_mask = pa_sc_aa_mask\n      .AA_MASK_ULC(upperLeft)\n      .AA_MASK_URC(upperRight)\n      .AA_MASK_LLC(lowerLeft)\n      .AA_MASK_LRC(lowerRight);\n\n   reg->pa_sc_aa_mask = pa_sc_aa_mask;\n}\n\nvoid\nGX2GetAAMaskReg(virt_ptr<GX2AAMaskReg> reg,\n                virt_ptr<uint8_t> upperLeft,\n                virt_ptr<uint8_t> upperRight,\n                virt_ptr<uint8_t> lowerLeft,\n                virt_ptr<uint8_t> lowerRight)\n{\n   auto pa_sc_aa_mask = reg->pa_sc_aa_mask.value();\n   *upperLeft = pa_sc_aa_mask.AA_MASK_ULC();\n   *upperRight = pa_sc_aa_mask.AA_MASK_URC();\n   *lowerLeft = pa_sc_aa_mask.AA_MASK_LLC();\n   *lowerRight = pa_sc_aa_mask.AA_MASK_LRC();\n}\n\nvoid\nGX2SetAAMaskReg(virt_ptr<GX2AAMaskReg> reg)\n{\n   auto pa_sc_aa_mask = reg->pa_sc_aa_mask.value();\n   internal::writePM4(SetContextReg { latte::Register::PA_SC_AA_MASK, pa_sc_aa_mask.value });\n}\n\nvoid\nGX2SetAlphaTest(BOOL alphaTest,\n                GX2CompareFunction func,\n                float ref)\n{\n   auto reg = StackObject<GX2AlphaTestReg> { };\n   GX2InitAlphaTestReg(reg, alphaTest, func, ref);\n   GX2SetAlphaTestReg(reg);\n}\n\nvoid\nGX2InitAlphaTestReg(virt_ptr<GX2AlphaTestReg> reg,\n                    BOOL alphaTest,\n                    GX2CompareFunction func,\n                    float ref)\n{\n   auto sx_alpha_ref = latte::SX_ALPHA_REF::get(0);\n   auto sx_alpha_test_control = latte::SX_ALPHA_TEST_CONTROL::get(0);\n\n   sx_alpha_test_control = sx_alpha_test_control\n      .ALPHA_TEST_ENABLE(!!alphaTest)\n      .ALPHA_FUNC(static_cast<latte::REF_FUNC>(func));\n\n   sx_alpha_ref = sx_alpha_ref\n      .ALPHA_REF(ref);\n\n   reg->sx_alpha_ref = sx_alpha_ref;\n   reg->sx_alpha_test_control = sx_alpha_test_control;\n}\n\nvoid\nGX2GetAlphaTestReg(virt_ptr<const GX2AlphaTestReg> reg,\n                   virt_ptr<BOOL> alphaTest,\n                   virt_ptr<GX2CompareFunction> func,\n                   virt_ptr<float> ref)\n{\n   auto sx_alpha_ref = reg->sx_alpha_ref.value();\n   auto sx_alpha_test_control = reg->sx_alpha_test_control.value();\n\n   *alphaTest = sx_alpha_test_control.ALPHA_TEST_ENABLE();\n   *func = static_cast<GX2CompareFunction>(sx_alpha_test_control.ALPHA_FUNC());\n   *ref = sx_alpha_ref.ALPHA_REF();\n}\n\nvoid\nGX2SetAlphaTestReg(virt_ptr<GX2AlphaTestReg> reg)\n{\n   auto sx_alpha_test_control = reg->sx_alpha_test_control.value();\n   internal::writePM4(SetContextReg { latte::Register::SX_ALPHA_TEST_CONTROL, sx_alpha_test_control.value });\n\n   auto sx_alpha_ref = reg->sx_alpha_ref.value();\n   internal::writePM4(SetContextReg { latte::Register::SX_ALPHA_REF, sx_alpha_ref.value });\n}\n\nvoid\nGX2SetAlphaToMask(BOOL alphaToMask,\n                  GX2AlphaToMaskMode mode)\n{\n   auto reg = StackObject<GX2AlphaToMaskReg> { };\n   GX2InitAlphaToMaskReg(reg, alphaToMask, mode);\n   GX2SetAlphaToMaskReg(reg);\n}\n\nvoid\nGX2InitAlphaToMaskReg(virt_ptr<GX2AlphaToMaskReg> reg,\n                      BOOL alphaToMask,\n                      GX2AlphaToMaskMode mode)\n{\n   auto db_alpha_to_mask = latte::DB_ALPHA_TO_MASK::get(0);\n\n   db_alpha_to_mask = db_alpha_to_mask\n      .ALPHA_TO_MASK_ENABLE(!!alphaToMask);\n\n   switch (mode) {\n   case GX2AlphaToMaskMode::NonDithered:\n      // 0xAA = 10 10 10 10\n      db_alpha_to_mask = db_alpha_to_mask\n         .ALPHA_TO_MASK_OFFSET0(2)\n         .ALPHA_TO_MASK_OFFSET1(2)\n         .ALPHA_TO_MASK_OFFSET2(2)\n         .ALPHA_TO_MASK_OFFSET3(2);\n      break;\n   case GX2AlphaToMaskMode::Dither0:\n      // 0x78 = 01 11 10 00\n      db_alpha_to_mask = db_alpha_to_mask\n         .ALPHA_TO_MASK_OFFSET0(0)\n         .ALPHA_TO_MASK_OFFSET1(2)\n         .ALPHA_TO_MASK_OFFSET2(3)\n         .ALPHA_TO_MASK_OFFSET3(1);\n      break;\n   case GX2AlphaToMaskMode::Dither90:\n      // 0xC6 = 11 00 01 10\n      db_alpha_to_mask = db_alpha_to_mask\n         .ALPHA_TO_MASK_OFFSET0(2)\n         .ALPHA_TO_MASK_OFFSET1(1)\n         .ALPHA_TO_MASK_OFFSET2(0)\n         .ALPHA_TO_MASK_OFFSET3(3);\n      break;\n   case GX2AlphaToMaskMode::Dither180:\n      // 0x2D = 00 10 11 01\n      db_alpha_to_mask = db_alpha_to_mask\n         .ALPHA_TO_MASK_OFFSET0(1)\n         .ALPHA_TO_MASK_OFFSET1(3)\n         .ALPHA_TO_MASK_OFFSET2(2)\n         .ALPHA_TO_MASK_OFFSET3(0);\n      break;\n   case GX2AlphaToMaskMode::Dither270:\n      // 0x93 = 10 01 00 11\n      db_alpha_to_mask = db_alpha_to_mask\n         .ALPHA_TO_MASK_OFFSET0(3)\n         .ALPHA_TO_MASK_OFFSET1(0)\n         .ALPHA_TO_MASK_OFFSET2(1)\n         .ALPHA_TO_MASK_OFFSET3(2);\n      break;\n   }\n\n   reg->db_alpha_to_mask = db_alpha_to_mask;\n}\n\nvoid\nGX2GetAlphaToMaskReg(virt_ptr<const GX2AlphaToMaskReg> reg,\n                     virt_ptr<BOOL> alphaToMask,\n                     virt_ptr<GX2AlphaToMaskMode> mode)\n{\n   auto db_alpha_to_mask = reg->db_alpha_to_mask.value();\n   auto value = (db_alpha_to_mask.value >> 8) & 0xff;\n   *alphaToMask = db_alpha_to_mask.ALPHA_TO_MASK_ENABLE();\n\n   switch (value) {\n   case 0x78:\n      *mode = GX2AlphaToMaskMode::Dither0;\n      break;\n   case 0xC6:\n      *mode = GX2AlphaToMaskMode::Dither90;\n      break;\n   case 0x2D:\n      *mode = GX2AlphaToMaskMode::Dither180;\n      break;\n   case 0x93:\n      *mode = GX2AlphaToMaskMode::Dither270;\n      break;\n   default:\n      *mode = GX2AlphaToMaskMode::NonDithered;\n      break;\n   }\n}\n\nvoid\nGX2SetAlphaToMaskReg(virt_ptr<GX2AlphaToMaskReg> reg)\n{\n   auto db_alpha_to_mask = reg->db_alpha_to_mask.value();\n   internal::writePM4(SetContextReg { latte::Register::DB_ALPHA_TO_MASK, db_alpha_to_mask.value });\n}\n\nvoid\nGX2SetBlendConstantColor(float red,\n                         float green,\n                         float blue,\n                         float alpha)\n{\n   auto reg = StackObject<GX2BlendConstantColorReg> { };\n   GX2InitBlendConstantColorReg(reg, red, green, blue, alpha);\n   GX2SetBlendConstantColorReg(reg);\n}\n\nvoid\nGX2InitBlendConstantColorReg(virt_ptr<GX2BlendConstantColorReg> reg,\n                             float red,\n                             float green,\n                             float blue,\n                             float alpha)\n{\n   reg->red = red;\n   reg->green = green;\n   reg->blue = blue;\n   reg->alpha = alpha;\n}\n\nvoid\nGX2GetBlendConstantColorReg(virt_ptr<GX2BlendConstantColorReg> reg,\n                            virt_ptr<float> red,\n                            virt_ptr<float> green,\n                            virt_ptr<float> blue,\n                            virt_ptr<float> alpha)\n{\n   *red = reg->red;\n   *green = reg->green;\n   *blue = reg->blue;\n   *alpha = reg->alpha;\n}\n\nvoid\nGX2SetBlendConstantColorReg(virt_ptr<GX2BlendConstantColorReg> reg)\n{\n   float colors[] = {\n      reg->red,\n      reg->green,\n      reg->blue,\n      reg->alpha\n   };\n\n   auto values = reinterpret_cast<uint32_t *>(colors);\n   internal::writePM4(SetContextRegs { latte::Register::CB_BLEND_RED, gsl::make_span(values, 4) });\n}\n\nvoid\nGX2SetBlendControl(GX2RenderTarget target,\n                   GX2BlendMode colorSrcBlend,\n                   GX2BlendMode colorDstBlend,\n                   GX2BlendCombineMode colorCombine,\n                   BOOL useAlphaBlend,\n                   GX2BlendMode alphaSrcBlend,\n                   GX2BlendMode alphaDstBlend,\n                   GX2BlendCombineMode alphaCombine)\n{\n   auto reg = StackObject<GX2BlendControlReg> { };\n   GX2InitBlendControlReg(reg,\n                          target,\n                          colorSrcBlend,\n                          colorDstBlend,\n                          colorCombine,\n                          useAlphaBlend,\n                          alphaSrcBlend,\n                          alphaDstBlend,\n                          alphaCombine);\n   GX2SetBlendControlReg(reg);\n}\n\nvoid\nGX2InitBlendControlReg(virt_ptr<GX2BlendControlReg> reg,\n                       GX2RenderTarget target,\n                       GX2BlendMode colorSrcBlend,\n                       GX2BlendMode colorDstBlend,\n                       GX2BlendCombineMode colorCombine,\n                       BOOL useAlphaBlend,\n                       GX2BlendMode alphaSrcBlend,\n                       GX2BlendMode alphaDstBlend,\n                       GX2BlendCombineMode alphaCombine)\n{\n   auto cb_blend_control = latte::CB_BLENDN_CONTROL::get(0);\n   reg->target = target;\n\n   cb_blend_control = cb_blend_control\n      .COLOR_SRCBLEND(static_cast<latte::CB_BLEND_FUNC>(colorSrcBlend))\n      .COLOR_DESTBLEND(static_cast<latte::CB_BLEND_FUNC>(colorDstBlend))\n      .COLOR_COMB_FCN(static_cast<latte::CB_COMB_FUNC>(colorCombine))\n      .SEPARATE_ALPHA_BLEND(useAlphaBlend)\n      .ALPHA_SRCBLEND(static_cast<latte::CB_BLEND_FUNC>(alphaSrcBlend))\n      .ALPHA_DESTBLEND(static_cast<latte::CB_BLEND_FUNC>(alphaDstBlend))\n      .ALPHA_COMB_FCN(static_cast<latte::CB_COMB_FUNC>(alphaCombine));\n\n   reg->cb_blend_control = cb_blend_control;\n}\n\nvoid\nGX2GetBlendControlReg(virt_ptr<GX2BlendControlReg> reg,\n                      virt_ptr<GX2RenderTarget> target,\n                      virt_ptr<GX2BlendMode> colorSrcBlend,\n                      virt_ptr<GX2BlendMode> colorDstBlend,\n                      virt_ptr<GX2BlendCombineMode> colorCombine,\n                      virt_ptr<BOOL> useAlphaBlend,\n                      virt_ptr<GX2BlendMode> alphaSrcBlend,\n                      virt_ptr<GX2BlendMode> alphaDstBlend,\n                      virt_ptr<GX2BlendCombineMode> alphaCombine)\n{\n   auto cb_blend_control = reg->cb_blend_control.value();\n   *target = reg->target;\n   *colorSrcBlend = static_cast<GX2BlendMode>(cb_blend_control.COLOR_SRCBLEND());\n   *colorDstBlend = static_cast<GX2BlendMode>(cb_blend_control.COLOR_DESTBLEND());\n   *colorCombine = static_cast<GX2BlendCombineMode>(cb_blend_control.COLOR_COMB_FCN());\n   *useAlphaBlend = cb_blend_control.SEPARATE_ALPHA_BLEND() ? TRUE : FALSE;\n   *alphaSrcBlend = static_cast<GX2BlendMode>(cb_blend_control.ALPHA_SRCBLEND());\n   *alphaDstBlend = static_cast<GX2BlendMode>(cb_blend_control.ALPHA_DESTBLEND());\n   *alphaCombine = static_cast<GX2BlendCombineMode>(cb_blend_control.ALPHA_COMB_FCN());\n}\n\nvoid\nGX2SetBlendControlReg(virt_ptr<GX2BlendControlReg> reg)\n{\n   auto cb_blend_control = reg->cb_blend_control.value();\n   auto id = static_cast<latte::Register>(latte::Register::CB_BLEND0_CONTROL + reg->target * 4);\n   internal::writePM4(SetContextReg { id, cb_blend_control.value });\n}\n\nvoid\nGX2SetColorControl(GX2LogicOp rop3,\n                   uint8_t targetBlendEnable,\n                   BOOL multiWriteEnable,\n                   BOOL colorWriteEnable)\n{\n   auto reg = StackObject<GX2ColorControlReg> { };\n   GX2InitColorControlReg(reg,\n                          rop3,\n                          targetBlendEnable,\n                          multiWriteEnable,\n                          colorWriteEnable);\n   GX2SetColorControlReg(reg);\n}\n\nvoid\nGX2InitColorControlReg(virt_ptr<GX2ColorControlReg> reg,\n                       GX2LogicOp rop3,\n                       uint8_t targetBlendEnable,\n                       BOOL multiWriteEnable,\n                       BOOL colorWriteEnable)\n{\n   auto cb_color_control = latte::CB_COLOR_CONTROL::get(0);\n\n   auto specialOp = latte::CB_SPECIAL_OP::DISABLE;\n   if (colorWriteEnable) {\n      specialOp = latte::CB_SPECIAL_OP::NORMAL;\n   }\n\n   cb_color_control = cb_color_control\n      .ROP3(rop3)\n      .TARGET_BLEND_ENABLE(targetBlendEnable)\n      .MULTIWRITE_ENABLE(multiWriteEnable)\n      .SPECIAL_OP(specialOp);\n\n   reg->cb_color_control = cb_color_control;\n}\n\nvoid\nGX2GetColorControlReg(virt_ptr<GX2ColorControlReg> reg,\n                      virt_ptr<GX2LogicOp> rop3,\n                      virt_ptr<uint8_t> targetBlendEnable,\n                      virt_ptr<BOOL> multiWriteEnable,\n                      virt_ptr<BOOL> colorWriteEnable)\n{\n   auto cb_color_control = reg->cb_color_control.value();\n\n   *rop3 = static_cast<GX2LogicOp>(cb_color_control.ROP3());\n   *targetBlendEnable = cb_color_control.TARGET_BLEND_ENABLE();\n   *multiWriteEnable = cb_color_control.MULTIWRITE_ENABLE() ? TRUE : FALSE;\n\n   if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::DISABLE) {\n      *colorWriteEnable = FALSE;\n   } else {\n      *colorWriteEnable = TRUE;\n   }\n}\n\nvoid\nGX2SetColorControlReg(virt_ptr<GX2ColorControlReg> reg)\n{\n   auto cb_color_control = reg->cb_color_control.value();\n   internal::writePM4(SetContextReg { latte::Register::CB_COLOR_CONTROL, cb_color_control.value });\n}\n\nvoid\nGX2SetDepthOnlyControl(BOOL depthTest,\n                       BOOL depthWrite,\n                       GX2CompareFunction depthCompare)\n{\n   GX2SetDepthStencilControl(depthTest,\n                             depthWrite,\n                             depthCompare,\n                             FALSE,\n                             FALSE,\n                             GX2CompareFunction::Never,\n                             GX2StencilFunction::Keep,\n                             GX2StencilFunction::Keep,\n                             GX2StencilFunction::Keep,\n                             GX2CompareFunction::Never,\n                             GX2StencilFunction::Keep,\n                             GX2StencilFunction::Keep,\n                             GX2StencilFunction::Keep);\n}\n\nvoid\nGX2SetDepthStencilControl(BOOL depthTest,\n                          BOOL depthWrite,\n                          GX2CompareFunction depthCompare,\n                          BOOL stencilTest,\n                          BOOL backfaceStencil,\n                          GX2CompareFunction frontStencilFunc,\n                          GX2StencilFunction frontStencilZPass,\n                          GX2StencilFunction frontStencilZFail,\n                          GX2StencilFunction frontStencilFail,\n                          GX2CompareFunction backStencilFunc,\n                          GX2StencilFunction backStencilZPass,\n                          GX2StencilFunction backStencilZFail,\n                          GX2StencilFunction backStencilFail)\n{\n   auto reg = StackObject<GX2DepthStencilControlReg> { };\n   GX2InitDepthStencilControlReg(reg,\n                                 depthTest,\n                                 depthWrite,\n                                 depthCompare,\n                                 stencilTest,\n                                 backfaceStencil,\n                                 frontStencilFunc,\n                                 frontStencilZPass,\n                                 frontStencilZFail,\n                                 frontStencilFail,\n                                 backStencilFunc,\n                                 backStencilZPass,\n                                 backStencilZFail,\n                                 backStencilFail);\n   GX2SetDepthStencilControlReg(reg);\n}\n\nvoid\nGX2InitDepthStencilControlReg(virt_ptr<GX2DepthStencilControlReg> reg,\n                              BOOL depthTest,\n                              BOOL depthWrite,\n                              GX2CompareFunction depthCompare,\n                              BOOL stencilTest,\n                              BOOL backfaceStencil,\n                              GX2CompareFunction frontStencilFunc,\n                              GX2StencilFunction frontStencilZPass,\n                              GX2StencilFunction frontStencilZFail,\n                              GX2StencilFunction frontStencilFail,\n                              GX2CompareFunction backStencilFunc,\n                              GX2StencilFunction backStencilZPass,\n                              GX2StencilFunction backStencilZFail,\n                              GX2StencilFunction backStencilFail)\n{\n   auto db_depth_control = latte::DB_DEPTH_CONTROL::get(0);\n\n   db_depth_control = db_depth_control\n      .Z_ENABLE(!!depthTest)\n      .Z_WRITE_ENABLE(!!depthWrite)\n      .ZFUNC(static_cast<latte::REF_FUNC>(depthCompare))\n      .STENCIL_ENABLE(!!stencilTest)\n      .BACKFACE_ENABLE(!!backfaceStencil)\n      .STENCILFUNC(static_cast<latte::REF_FUNC>(frontStencilFunc))\n      .STENCILZPASS(static_cast<latte::DB_STENCIL_FUNC>(frontStencilZPass))\n      .STENCILZFAIL(static_cast<latte::DB_STENCIL_FUNC>(frontStencilZFail))\n      .STENCILFAIL(static_cast<latte::DB_STENCIL_FUNC>(frontStencilFail))\n      .STENCILFUNC_BF(static_cast<latte::REF_FUNC>(backStencilFunc))\n      .STENCILZPASS_BF(static_cast<latte::DB_STENCIL_FUNC>(backStencilZPass))\n      .STENCILZFAIL_BF(static_cast<latte::DB_STENCIL_FUNC>(backStencilZFail))\n      .STENCILFAIL_BF(static_cast<latte::DB_STENCIL_FUNC>(backStencilFail));\n\n   reg->db_depth_control = db_depth_control;\n}\n\nvoid\nGX2GetDepthStencilControlReg(virt_ptr<GX2DepthStencilControlReg> reg,\n                             virt_ptr<BOOL> depthTest,\n                             virt_ptr<BOOL> depthWrite,\n                             virt_ptr<GX2CompareFunction> depthCompare,\n                             virt_ptr<BOOL> stencilTest,\n                             virt_ptr<BOOL> backfaceStencil,\n                             virt_ptr<GX2CompareFunction> frontStencilFunc,\n                             virt_ptr<GX2StencilFunction> frontStencilZPass,\n                             virt_ptr<GX2StencilFunction> frontStencilZFail,\n                             virt_ptr<GX2StencilFunction> frontStencilFail,\n                             virt_ptr<GX2CompareFunction> backStencilFunc,\n                             virt_ptr<GX2StencilFunction> backStencilZPass,\n                             virt_ptr<GX2StencilFunction> backStencilZFail,\n                             virt_ptr<GX2StencilFunction> backStencilFail)\n{\n   auto db_depth_control = reg->db_depth_control.value();\n   *depthTest = db_depth_control.Z_ENABLE();\n   *depthWrite = db_depth_control.Z_WRITE_ENABLE();\n   *depthCompare = static_cast<GX2CompareFunction>(db_depth_control.ZFUNC());\n   *stencilTest = db_depth_control.STENCIL_ENABLE();\n   *backfaceStencil = db_depth_control.BACKFACE_ENABLE();\n   *frontStencilFunc = static_cast<GX2CompareFunction>(db_depth_control.STENCILFUNC());\n   *frontStencilZPass = static_cast<GX2StencilFunction>(db_depth_control.STENCILZPASS());\n   *frontStencilZFail = static_cast<GX2StencilFunction>(db_depth_control.STENCILZFAIL());\n   *frontStencilFail = static_cast<GX2StencilFunction>(db_depth_control.STENCILFAIL());\n   *backStencilFunc = static_cast<GX2CompareFunction>(db_depth_control.STENCILFUNC_BF());\n   *backStencilZPass = static_cast<GX2StencilFunction>(db_depth_control.STENCILZPASS_BF());\n   *backStencilZFail = static_cast<GX2StencilFunction>(db_depth_control.STENCILZFAIL_BF());\n   *backStencilFail = static_cast<GX2StencilFunction>(db_depth_control.STENCILFAIL_BF());\n}\n\nvoid\nGX2SetDepthStencilControlReg(virt_ptr<GX2DepthStencilControlReg> reg)\n{\n   auto db_depth_control = reg->db_depth_control.value();\n   internal::writePM4(SetContextReg { latte::Register::DB_DEPTH_CONTROL, db_depth_control.value });\n}\n\nvoid GX2SetStencilMask(uint8_t frontMask,\n                       uint8_t frontWriteMask,\n                       uint8_t frontRef,\n                       uint8_t backMask,\n                       uint8_t backWriteMask,\n                       uint8_t backRef)\n{\n   auto reg = StackObject<GX2StencilMaskReg> { };\n   GX2InitStencilMaskReg(reg, frontMask, frontWriteMask, frontRef, backMask, backWriteMask, backRef);\n   GX2SetStencilMaskReg(reg);\n}\n\nvoid GX2InitStencilMaskReg(virt_ptr<GX2StencilMaskReg> reg,\n                           uint8_t frontMask,\n                           uint8_t frontWriteMask,\n                           uint8_t frontRef,\n                           uint8_t backMask,\n                           uint8_t backWriteMask,\n                           uint8_t backRef)\n{\n   auto db_stencilrefmask = latte::DB_STENCILREFMASK::get(0);\n   auto db_stencilrefmask_bf = latte::DB_STENCILREFMASK_BF::get(0);\n\n   db_stencilrefmask = db_stencilrefmask\n      .STENCILREF(frontRef)\n      .STENCILMASK(frontMask)\n      .STENCILWRITEMASK(frontWriteMask);\n\n   db_stencilrefmask_bf = db_stencilrefmask_bf\n      .STENCILREF_BF(backRef)\n      .STENCILMASK_BF(backMask)\n      .STENCILWRITEMASK_BF(backWriteMask);\n\n   reg->db_stencilrefmask = db_stencilrefmask;\n   reg->db_stencilrefmask_bf = db_stencilrefmask_bf;\n}\n\nvoid\nGX2GetStencilMaskReg(virt_ptr<GX2StencilMaskReg> reg,\n                     virt_ptr<uint8_t> frontMask,\n                     virt_ptr<uint8_t> frontWriteMask,\n                     virt_ptr<uint8_t> frontRef,\n                     virt_ptr<uint8_t> backMask,\n                     virt_ptr<uint8_t> backWriteMask,\n                     virt_ptr<uint8_t> backRef)\n{\n   auto db_stencilrefmask = reg->db_stencilrefmask.value();\n   auto db_stencilrefmask_bf = reg->db_stencilrefmask_bf.value();\n\n   *frontRef = db_stencilrefmask.STENCILREF();\n   *frontMask = db_stencilrefmask.STENCILMASK();\n   *frontWriteMask = db_stencilrefmask.STENCILWRITEMASK();\n\n   *backRef = db_stencilrefmask_bf.STENCILREF_BF();\n   *backMask = db_stencilrefmask_bf.STENCILMASK_BF();\n   *backWriteMask = db_stencilrefmask_bf.STENCILWRITEMASK_BF();\n}\n\nvoid GX2SetStencilMaskReg(virt_ptr<GX2StencilMaskReg> reg)\n{\n   auto db_stencilrefmask = reg->db_stencilrefmask.value();\n   auto db_stencilrefmask_bf = reg->db_stencilrefmask_bf.value();\n   internal::writePM4(SetContextReg { latte::Register::DB_STENCILREFMASK, db_stencilrefmask.value });\n   internal::writePM4(SetContextReg { latte::Register::DB_STENCILREFMASK_BF, db_stencilrefmask_bf.value });\n}\n\nvoid\nGX2SetLineWidth(float width)\n{\n   auto reg = StackObject<GX2LineWidthReg> { };\n   GX2InitLineWidthReg(reg, width);\n   GX2SetLineWidthReg(reg);\n}\n\nvoid\nGX2InitLineWidthReg(virt_ptr<GX2LineWidthReg> reg,\n                    float width)\n{\n   auto pa_su_line_cntl = latte::PA_SU_LINE_CNTL::get(0);\n\n   pa_su_line_cntl = pa_su_line_cntl\n      .WIDTH(gsl::narrow_cast<uint32_t>(width * 8.0f));\n\n   reg->pa_su_line_cntl = pa_su_line_cntl;\n}\n\nvoid\nGX2GetLineWidthReg(virt_ptr<GX2LineWidthReg> reg,\n                   virt_ptr<float> width)\n{\n   auto pa_su_line_cntl = reg->pa_su_line_cntl.value();\n   *width = static_cast<float>(pa_su_line_cntl.WIDTH()) / 8.0f;\n}\n\nvoid\nGX2SetLineWidthReg(virt_ptr<GX2LineWidthReg> reg)\n{\n   auto pa_su_line_cntl = reg->pa_su_line_cntl.value();\n   internal::writePM4(SetContextReg { latte::Register::PA_SU_LINE_CNTL, pa_su_line_cntl.value });\n}\n\nvoid\nGX2SetPointSize(float width,\n                float height)\n{\n   auto reg = StackObject<GX2PointSizeReg> { };\n   GX2InitPointSizeReg(reg, width, height);\n   GX2SetPointSizeReg(reg);\n}\n\nvoid\nGX2InitPointSizeReg(virt_ptr<GX2PointSizeReg> reg,\n                    float width,\n                    float height)\n{\n   auto pa_su_point_size = latte::PA_SU_POINT_SIZE::get(0);\n\n   pa_su_point_size = pa_su_point_size\n      .WIDTH(gsl::narrow_cast<uint32_t>(width * 8.0f))\n      .HEIGHT(gsl::narrow_cast<uint32_t>(height * 8.0f));\n\n   reg->pa_su_point_size = pa_su_point_size;\n}\n\nvoid\nGX2GetPointSizeReg(virt_ptr<GX2PointSizeReg> reg,\n                   virt_ptr<float> width,\n                   virt_ptr<float> height)\n{\n   auto pa_su_point_size = reg->pa_su_point_size.value();\n   *width = static_cast<float>(pa_su_point_size.WIDTH()) / 8.0f;\n   *height = static_cast<float>(pa_su_point_size.HEIGHT()) / 8.0f;\n}\n\nvoid\nGX2SetPointSizeReg(virt_ptr<GX2PointSizeReg> reg)\n{\n   auto pa_su_point_size = reg->pa_su_point_size.value();\n   internal::writePM4(SetContextReg { latte::Register::PA_SU_POINT_SIZE, pa_su_point_size.value });\n}\n\nvoid\nGX2SetPointLimits(float min,\n                  float max)\n{\n   auto reg = StackObject<GX2PointLimitsReg> { };\n   GX2InitPointLimitsReg(reg, min, max);\n   GX2SetPointLimitsReg(reg);\n}\n\nvoid\nGX2InitPointLimitsReg(virt_ptr<GX2PointLimitsReg> reg,\n                      float min,\n                      float max)\n{\n   auto pa_su_point_minmax = latte::PA_SU_POINT_MINMAX::get(0);\n\n   pa_su_point_minmax = pa_su_point_minmax\n      .MIN_SIZE(gsl::narrow_cast<uint32_t>(min * 8.0f))\n      .MAX_SIZE(gsl::narrow_cast<uint32_t>(max * 8.0f));\n\n   reg->pa_su_point_minmax = pa_su_point_minmax;\n}\n\nvoid\nGX2GetPointLimitsReg(virt_ptr<GX2PointLimitsReg> reg,\n                     virt_ptr<float> min,\n                     virt_ptr<float> max)\n{\n   auto pa_su_point_minmax = reg->pa_su_point_minmax.value();\n   *min = static_cast<float>(pa_su_point_minmax.MIN_SIZE()) / 8.0f;\n   *max = static_cast<float>(pa_su_point_minmax.MAX_SIZE()) / 8.0f;\n}\n\nvoid\nGX2SetPointLimitsReg(virt_ptr<GX2PointLimitsReg> reg)\n{\n   auto pa_su_point_minmax = reg->pa_su_point_minmax.value();\n   internal::writePM4(SetContextReg { latte::Register::PA_SU_POINT_MINMAX, pa_su_point_minmax.value });\n}\n\nvoid\nGX2SetCullOnlyControl(GX2FrontFace frontFace,\n                      BOOL cullFront,\n                      BOOL cullBack)\n{\n   GX2SetPolygonControl(frontFace,\n                        cullFront,\n                        cullBack,\n                        FALSE,\n                        GX2PolygonMode::Point,\n                        GX2PolygonMode::Point,\n                        FALSE,\n                        FALSE,\n                        FALSE);\n}\n\nvoid\nGX2SetPolygonControl(GX2FrontFace frontFace,\n                     BOOL cullFront,\n                     BOOL cullBack,\n                     BOOL polyMode,\n                     GX2PolygonMode polyModeFront,\n                     GX2PolygonMode polyModeBack,\n                     BOOL polyOffsetFrontEnable,\n                     BOOL polyOffsetBackEnable,\n                     BOOL polyOffsetParaEnable)\n{\n   auto reg = StackObject<GX2PolygonControlReg> { };\n   GX2InitPolygonControlReg(reg,\n                            frontFace,\n                            cullFront,\n                            cullBack,\n                            polyMode,\n                            polyModeFront,\n                            polyModeBack,\n                            polyOffsetFrontEnable,\n                            polyOffsetBackEnable,\n                            polyOffsetParaEnable);\n   GX2SetPolygonControlReg(reg);\n}\n\nvoid\nGX2InitPolygonControlReg(virt_ptr<GX2PolygonControlReg> reg,\n                         GX2FrontFace frontFace,\n                         BOOL cullFront,\n                         BOOL cullBack,\n                         uint32_t polyMode,\n                         GX2PolygonMode polyModeFront,\n                         GX2PolygonMode polyModeBack,\n                         BOOL polyOffsetFrontEnable,\n                         BOOL polyOffsetBackEnable,\n                         BOOL polyOffsetParaEnable)\n{\n   auto pa_su_sc_mode_cntl = latte::PA_SU_SC_MODE_CNTL::get(0);\n\n   pa_su_sc_mode_cntl = pa_su_sc_mode_cntl\n      .FACE(static_cast<latte::PA_FACE>(!!frontFace))\n      .CULL_FRONT(!!cullFront)\n      .CULL_BACK(!!cullBack)\n      .POLY_MODE(polyMode)\n      .POLYMODE_FRONT_PTYPE(static_cast<latte::PA_PTYPE>(polyModeFront))\n      .POLYMODE_BACK_PTYPE(static_cast<latte::PA_PTYPE>(polyModeBack))\n      .POLY_OFFSET_FRONT_ENABLE(!!polyOffsetFrontEnable)\n      .POLY_OFFSET_BACK_ENABLE(!!polyOffsetBackEnable)\n      .POLY_OFFSET_PARA_ENABLE(!!polyOffsetParaEnable);\n\n   reg->pa_su_sc_mode_cntl = pa_su_sc_mode_cntl;\n}\n\nvoid\nGX2GetPolygonControlReg(virt_ptr<GX2PolygonControlReg> reg,\n                        virt_ptr<GX2FrontFace> frontFace,\n                        virt_ptr<BOOL> cullFront,\n                        virt_ptr<BOOL> cullBack,\n                        virt_ptr<uint32_t> polyMode,\n                        virt_ptr<GX2PolygonMode> polyModeFront,\n                        virt_ptr<GX2PolygonMode> polyModeBack,\n                        virt_ptr<BOOL> polyOffsetFrontEnable,\n                        virt_ptr<BOOL> polyOffsetBackEnable,\n                        virt_ptr<BOOL> polyOffsetParaEnable)\n{\n   auto pa_su_sc_mode_cntl = reg->pa_su_sc_mode_cntl.value();\n   *frontFace = static_cast<GX2FrontFace>(pa_su_sc_mode_cntl.FACE());\n   *cullFront = pa_su_sc_mode_cntl.CULL_FRONT();\n   *cullBack = pa_su_sc_mode_cntl.CULL_BACK();\n   *polyMode = pa_su_sc_mode_cntl.POLY_MODE();\n   *polyModeFront = static_cast<GX2PolygonMode>(pa_su_sc_mode_cntl.POLYMODE_FRONT_PTYPE());\n   *polyModeBack = static_cast<GX2PolygonMode>(pa_su_sc_mode_cntl.POLYMODE_BACK_PTYPE());\n   *polyOffsetFrontEnable = pa_su_sc_mode_cntl.POLY_OFFSET_FRONT_ENABLE();\n   *polyOffsetBackEnable = pa_su_sc_mode_cntl.POLY_OFFSET_BACK_ENABLE();\n   *polyOffsetParaEnable = pa_su_sc_mode_cntl.POLY_OFFSET_PARA_ENABLE();\n}\n\nvoid\nGX2SetPolygonControlReg(virt_ptr<GX2PolygonControlReg> reg)\n{\n   auto pa_su_sc_mode_cntl = reg->pa_su_sc_mode_cntl.value();\n   internal::writePM4(SetContextReg { latte::Register::PA_SU_SC_MODE_CNTL, pa_su_sc_mode_cntl.value });\n}\n\nvoid\nGX2SetPolygonOffset(float frontOffset,\n                    float frontScale,\n                    float backOffset,\n                    float backScale,\n                    float clamp)\n{\n   auto reg = StackObject<GX2PolygonOffsetReg> { };\n   GX2InitPolygonOffsetReg(reg,\n                           frontOffset,\n                           frontScale,\n                           backOffset,\n                           backScale,\n                           clamp);\n   GX2SetPolygonOffsetReg(reg);\n}\n\nvoid\nGX2InitPolygonOffsetReg(virt_ptr<GX2PolygonOffsetReg> reg,\n                        float frontOffset,\n                        float frontScale,\n                        float backOffset,\n                        float backScale,\n                        float clamp)\n{\n   auto pa_su_poly_offset_front_offset = latte::PA_SU_POLY_OFFSET_FRONT_OFFSET::get(0);\n   auto pa_su_poly_offset_front_scale = latte::PA_SU_POLY_OFFSET_FRONT_SCALE::get(0);\n   auto pa_su_poly_offset_back_offset = latte::PA_SU_POLY_OFFSET_BACK_OFFSET::get(0);\n   auto pa_su_poly_offset_back_scale = latte::PA_SU_POLY_OFFSET_BACK_SCALE::get(0);\n   auto pa_su_poly_offset_clamp = latte::PA_SU_POLY_OFFSET_CLAMP::get(0);\n\n   pa_su_poly_offset_front_offset = pa_su_poly_offset_front_offset\n      .OFFSET(frontOffset);\n   pa_su_poly_offset_front_scale = pa_su_poly_offset_front_scale\n      .SCALE(frontScale * 16.0f);\n   pa_su_poly_offset_back_offset = pa_su_poly_offset_back_offset\n      .OFFSET(backOffset);\n   pa_su_poly_offset_back_scale = pa_su_poly_offset_back_scale\n      .SCALE(backScale * 16.0f);\n   pa_su_poly_offset_clamp = pa_su_poly_offset_clamp\n      .CLAMP(clamp);\n\n   reg->pa_su_poly_offset_front_offset = pa_su_poly_offset_front_offset;\n   reg->pa_su_poly_offset_front_scale = pa_su_poly_offset_front_scale;\n   reg->pa_su_poly_offset_back_offset = pa_su_poly_offset_back_offset;\n   reg->pa_su_poly_offset_back_scale = pa_su_poly_offset_back_scale;\n   reg->pa_su_poly_offset_clamp = pa_su_poly_offset_clamp;\n}\n\nvoid\nGX2GetPolygonOffsetReg(virt_ptr<GX2PolygonOffsetReg> reg,\n                       virt_ptr<float> frontOffset,\n                       virt_ptr<float> frontScale,\n                       virt_ptr<float> backOffset,\n                       virt_ptr<float> backScale,\n                       virt_ptr<float> clamp)\n{\n   auto pa_su_poly_offset_front_offset = reg->pa_su_poly_offset_front_offset.value();\n   auto pa_su_poly_offset_front_scale = reg->pa_su_poly_offset_front_scale.value();\n   auto pa_su_poly_offset_back_offset = reg->pa_su_poly_offset_back_offset.value();\n   auto pa_su_poly_offset_back_scale = reg->pa_su_poly_offset_back_scale.value();\n   auto pa_su_poly_offset_clamp = reg->pa_su_poly_offset_clamp.value();\n\n   *frontOffset = pa_su_poly_offset_front_offset.OFFSET();\n   *frontScale = pa_su_poly_offset_front_scale.SCALE() / 16.0f;\n   *backOffset = pa_su_poly_offset_back_offset.OFFSET();\n   *backScale = pa_su_poly_offset_back_scale.SCALE() / 16.0f;\n   *clamp = pa_su_poly_offset_clamp.CLAMP();\n}\n\nvoid\nGX2SetPolygonOffsetReg(virt_ptr<GX2PolygonOffsetReg> reg)\n{\n   auto pa_su_poly_offset_front_offset = reg->pa_su_poly_offset_front_offset.value();\n   auto pa_su_poly_offset_front_scale = reg->pa_su_poly_offset_front_scale.value();\n   auto pa_su_poly_offset_back_offset = reg->pa_su_poly_offset_back_offset.value();\n   auto pa_su_poly_offset_back_scale = reg->pa_su_poly_offset_back_scale.value();\n\n   uint32_t values[] = {\n      pa_su_poly_offset_front_scale.value,\n      pa_su_poly_offset_front_offset.value,\n      pa_su_poly_offset_back_scale.value,\n      pa_su_poly_offset_back_offset.value,\n   };\n   internal::writePM4(SetContextRegs { latte::Register::PA_SU_POLY_OFFSET_FRONT_SCALE, gsl::make_span(values) });\n\n   auto pa_su_poly_offset_clamp = reg->pa_su_poly_offset_clamp.value();\n   internal::writePM4(SetContextReg { latte::Register::PA_SU_POLY_OFFSET_CLAMP, pa_su_poly_offset_clamp.value });\n}\n\nvoid\nGX2SetScissor(uint32_t x,\n              uint32_t y,\n              uint32_t width,\n              uint32_t height)\n{\n   auto reg = StackObject<GX2ScissorReg> { };\n   GX2InitScissorReg(reg, x, y, width, height);\n   GX2SetScissorReg(reg);\n}\n\nvoid\nGX2InitScissorReg(virt_ptr<GX2ScissorReg> reg,\n                  uint32_t x,\n                  uint32_t y,\n                  uint32_t width,\n                  uint32_t height)\n{\n   auto pa_sc_generic_scissor_tl = latte::PA_SC_GENERIC_SCISSOR_TL::get(0);\n   auto pa_sc_generic_scissor_br = latte::PA_SC_GENERIC_SCISSOR_BR::get(0);\n\n   pa_sc_generic_scissor_tl = pa_sc_generic_scissor_tl\n      .TL_X(x)\n      .TL_Y(y);\n\n   pa_sc_generic_scissor_br = pa_sc_generic_scissor_br\n      .BR_X(x + width)\n      .BR_Y(y + height);\n\n   reg->pa_sc_generic_scissor_tl = pa_sc_generic_scissor_tl;\n   reg->pa_sc_generic_scissor_br = pa_sc_generic_scissor_br;\n}\n\nvoid\nGX2GetScissorReg(virt_ptr<GX2ScissorReg> reg,\n                 virt_ptr<uint32_t> x,\n                 virt_ptr<uint32_t> y,\n                 virt_ptr<uint32_t> width,\n                 virt_ptr<uint32_t> height)\n{\n   auto pa_sc_generic_scissor_tl = reg->pa_sc_generic_scissor_tl.value();\n   auto pa_sc_generic_scissor_br = reg->pa_sc_generic_scissor_br.value();\n\n   *x = pa_sc_generic_scissor_tl.TL_X();\n   *y = pa_sc_generic_scissor_tl.TL_Y();\n   *width = pa_sc_generic_scissor_br.BR_X() - pa_sc_generic_scissor_tl.TL_X();\n   *height = pa_sc_generic_scissor_br.BR_Y() - pa_sc_generic_scissor_tl.TL_Y();\n}\n\nvoid\nGX2SetScissorReg(virt_ptr<GX2ScissorReg> reg)\n{\n   auto pa_sc_generic_scissor_tl = reg->pa_sc_generic_scissor_tl.value();\n   auto pa_sc_generic_scissor_br = reg->pa_sc_generic_scissor_br.value();\n\n   uint32_t values[] = {\n      pa_sc_generic_scissor_tl.value,\n      pa_sc_generic_scissor_br.value,\n   };\n\n   internal::writePM4(SetContextRegs { latte::Register::PA_SC_GENERIC_SCISSOR_TL, gsl::make_span(values) });\n}\n\nvoid\nGX2SetTargetChannelMasks(GX2ChannelMask mask0,\n                         GX2ChannelMask mask1,\n                         GX2ChannelMask mask2,\n                         GX2ChannelMask mask3,\n                         GX2ChannelMask mask4,\n                         GX2ChannelMask mask5,\n                         GX2ChannelMask mask6,\n                         GX2ChannelMask mask7)\n{\n   auto reg = StackObject<GX2TargetChannelMaskReg> { };\n   GX2InitTargetChannelMasksReg(reg,\n                                mask0,\n                                mask1,\n                                mask2,\n                                mask3,\n                                mask4,\n                                mask5,\n                                mask6,\n                                mask7);\n\n   GX2SetTargetChannelMasksReg(reg);\n}\n\nvoid\nGX2InitTargetChannelMasksReg(virt_ptr<GX2TargetChannelMaskReg> reg,\n                             GX2ChannelMask mask0,\n                             GX2ChannelMask mask1,\n                             GX2ChannelMask mask2,\n                             GX2ChannelMask mask3,\n                             GX2ChannelMask mask4,\n                             GX2ChannelMask mask5,\n                             GX2ChannelMask mask6,\n                             GX2ChannelMask mask7)\n{\n   auto cb_target_mask = latte::CB_TARGET_MASK::get(0);\n\n   cb_target_mask = cb_target_mask\n      .TARGET0_ENABLE(mask0)\n      .TARGET1_ENABLE(mask1)\n      .TARGET2_ENABLE(mask2)\n      .TARGET3_ENABLE(mask3)\n      .TARGET4_ENABLE(mask4)\n      .TARGET5_ENABLE(mask5)\n      .TARGET6_ENABLE(mask6)\n      .TARGET7_ENABLE(mask7);\n\n   reg->cb_target_mask = cb_target_mask;\n}\n\nvoid\nGX2GetTargetChannelMasksReg(virt_ptr<GX2TargetChannelMaskReg> reg,\n                            virt_ptr<GX2ChannelMask> mask0,\n                            virt_ptr<GX2ChannelMask> mask1,\n                            virt_ptr<GX2ChannelMask> mask2,\n                            virt_ptr<GX2ChannelMask> mask3,\n                            virt_ptr<GX2ChannelMask> mask4,\n                            virt_ptr<GX2ChannelMask> mask5,\n                            virt_ptr<GX2ChannelMask> mask6,\n                            virt_ptr<GX2ChannelMask> mask7)\n{\n   auto cb_target_mask = reg->cb_target_mask.value();\n   *mask0 = static_cast<GX2ChannelMask>(cb_target_mask.TARGET0_ENABLE());\n   *mask1 = static_cast<GX2ChannelMask>(cb_target_mask.TARGET1_ENABLE());\n   *mask2 = static_cast<GX2ChannelMask>(cb_target_mask.TARGET2_ENABLE());\n   *mask3 = static_cast<GX2ChannelMask>(cb_target_mask.TARGET3_ENABLE());\n   *mask4 = static_cast<GX2ChannelMask>(cb_target_mask.TARGET4_ENABLE());\n   *mask5 = static_cast<GX2ChannelMask>(cb_target_mask.TARGET5_ENABLE());\n   *mask6 = static_cast<GX2ChannelMask>(cb_target_mask.TARGET6_ENABLE());\n   *mask7 = static_cast<GX2ChannelMask>(cb_target_mask.TARGET7_ENABLE());\n}\n\nvoid\nGX2SetTargetChannelMasksReg(virt_ptr<GX2TargetChannelMaskReg> reg)\n{\n   auto cb_target_mask = reg->cb_target_mask.value();\n   internal::writePM4(SetContextReg { latte::Register::CB_TARGET_MASK, cb_target_mask.value });\n}\n\nvoid\nGX2SetViewport(float x,\n               float y,\n               float width,\n               float height,\n               float nearZ,\n               float farZ)\n{\n   auto reg = StackObject<GX2ViewportReg> { };\n   GX2InitViewportReg(reg, x, y, width, height, nearZ, farZ);\n   GX2SetViewportReg(reg);\n}\n\nvoid\nGX2InitViewportReg(virt_ptr<GX2ViewportReg> reg,\n                   float x,\n                   float y,\n                   float width,\n                   float height,\n                   float nearZ,\n                   float farZ)\n{\n   auto pa_cl_vport_xscale = latte::PA_CL_VPORT_XSCALE_N::get(0);\n   auto pa_cl_vport_xoffset = latte::PA_CL_VPORT_XOFFSET_N::get(0);\n   auto pa_cl_vport_yscale = latte::PA_CL_VPORT_YSCALE_N::get(0);\n   auto pa_cl_vport_yoffset = latte::PA_CL_VPORT_YOFFSET_N::get(0);\n   auto pa_cl_vport_zscale = latte::PA_CL_VPORT_ZSCALE_N::get(0);\n   auto pa_cl_vport_zoffset = latte::PA_CL_VPORT_ZOFFSET_N::get(0);\n\n   auto pa_cl_gb_vert_clip_adj = latte::PA_CL_GB_VERT_CLIP_ADJ::get(0);\n   auto pa_cl_gb_vert_disc_adj = latte::PA_CL_GB_VERT_DISC_ADJ::get(0);\n   auto pa_cl_gb_horz_clip_adj = latte::PA_CL_GB_HORZ_CLIP_ADJ::get(0);\n   auto pa_cl_gb_horz_disc_adj = latte::PA_CL_GB_HORZ_DISC_ADJ::get(0);\n\n   auto pa_sc_vport_zmin = reg->pa_sc_vport_zmin.value();\n   auto pa_sc_vport_zmax = reg->pa_sc_vport_zmax.value();\n\n   pa_cl_vport_xscale = pa_cl_vport_xscale\n      .VPORT_XSCALE(width * 0.5f);\n   pa_cl_vport_xoffset = pa_cl_vport_xoffset\n      .VPORT_XOFFSET(x + (width * 0.5f));\n   pa_cl_vport_yscale = pa_cl_vport_yscale\n      .VPORT_YSCALE(height * -0.5f);\n   pa_cl_vport_yoffset = pa_cl_vport_yoffset\n      .VPORT_YOFFSET(y + (height * 0.5f));\n   pa_cl_vport_zscale = pa_cl_vport_zscale\n      .VPORT_ZSCALE((farZ - nearZ) * 0.5f);\n   pa_cl_vport_zoffset = pa_cl_vport_zoffset\n      .VPORT_ZOFFSET((farZ + nearZ) * 0.5f);\n\n   if (width == 0.0f || height == 0.0f) {\n      pa_cl_gb_vert_clip_adj = pa_cl_gb_vert_clip_adj\n         .DATA_REGISTER(1.0f);\n      pa_cl_gb_horz_clip_adj = pa_cl_gb_horz_clip_adj\n         .DATA_REGISTER(1.0f);\n   } else {\n      if (height < 0.0f) {\n         y += height;\n         height = -height;\n      }\n\n      auto horz_clip_adj = x + 8192.0f;\n      if (horz_clip_adj <= 0.0f) {\n         horz_clip_adj = 8192.0f - (width + x);\n      }\n      pa_cl_gb_horz_clip_adj = pa_cl_gb_horz_clip_adj\n         .DATA_REGISTER((horz_clip_adj + horz_clip_adj + width) / width);\n\n      auto vert_clip_adj = y + 8192.0f;\n      if (vert_clip_adj <= 0.0f) {\n         vert_clip_adj = 8192.0f - (height + y);\n      }\n      pa_cl_gb_vert_clip_adj = pa_cl_gb_vert_clip_adj\n         .DATA_REGISTER((vert_clip_adj + vert_clip_adj + height) / height);\n   }\n\n   pa_cl_gb_horz_disc_adj = pa_cl_gb_horz_disc_adj\n      .DATA_REGISTER(1.0f);\n   pa_cl_gb_vert_disc_adj = pa_cl_gb_vert_disc_adj\n      .DATA_REGISTER(1.0f);\n\n   pa_sc_vport_zmin = pa_sc_vport_zmin\n      .VPORT_ZMIN(std::min(nearZ, farZ));\n   pa_sc_vport_zmax = pa_sc_vport_zmax\n      .VPORT_ZMAX(std::max(nearZ, farZ));\n\n   reg->pa_cl_vport_xscale = pa_cl_vport_xscale;\n   reg->pa_cl_vport_xoffset = pa_cl_vport_xoffset;\n   reg->pa_cl_vport_yscale = pa_cl_vport_yscale;\n   reg->pa_cl_vport_yoffset = pa_cl_vport_yoffset;\n   reg->pa_cl_vport_zscale = pa_cl_vport_zscale;\n   reg->pa_cl_vport_zoffset = pa_cl_vport_zoffset;\n\n   reg->pa_cl_gb_vert_clip_adj = pa_cl_gb_vert_clip_adj;\n   reg->pa_cl_gb_vert_disc_adj = pa_cl_gb_vert_disc_adj;\n   reg->pa_cl_gb_horz_clip_adj = pa_cl_gb_horz_clip_adj;\n   reg->pa_cl_gb_horz_disc_adj = pa_cl_gb_horz_disc_adj;\n\n   reg->pa_sc_vport_zmin = pa_sc_vport_zmin;\n   reg->pa_sc_vport_zmax = pa_sc_vport_zmax;\n}\n\nvoid\nGX2GetViewportReg(virt_ptr<GX2ViewportReg> reg,\n                  virt_ptr<float> x,\n                  virt_ptr<float> y,\n                  virt_ptr<float> width,\n                  virt_ptr<float> height,\n                  virt_ptr<float> nearZ,\n                  virt_ptr<float> farZ)\n{\n   auto pa_cl_vport_xscale = reg->pa_cl_vport_xscale.value();\n   auto pa_cl_vport_xoffset = reg->pa_cl_vport_xoffset.value();\n   auto pa_cl_vport_yscale = reg->pa_cl_vport_yscale.value();\n   auto pa_cl_vport_yoffset = reg->pa_cl_vport_yoffset.value();\n   auto pa_cl_vport_zscale = reg->pa_cl_vport_zscale.value();\n   auto pa_cl_vport_zoffset = reg->pa_cl_vport_zoffset.value();\n\n   *x = pa_cl_vport_xoffset.VPORT_XOFFSET() - pa_cl_vport_xscale.VPORT_XSCALE();\n   *y = pa_cl_vport_yoffset.VPORT_YOFFSET() + pa_cl_vport_yscale.VPORT_YSCALE();\n\n   *width = 2.0f * pa_cl_vport_xscale.VPORT_XSCALE();\n   *height = -2.0f * pa_cl_vport_yscale.VPORT_YSCALE();\n\n   *farZ = pa_cl_vport_zoffset.VPORT_ZOFFSET() + pa_cl_vport_zscale.VPORT_ZSCALE();\n   *nearZ = pa_cl_vport_zoffset.VPORT_ZOFFSET() - pa_cl_vport_zscale.VPORT_ZSCALE();\n}\n\nvoid\nGX2SetViewportReg(virt_ptr<GX2ViewportReg> reg)\n{\n   auto pa_cl_vport_xscale = reg->pa_cl_vport_xscale.value();\n   auto pa_cl_vport_xoffset = reg->pa_cl_vport_xoffset.value();\n   auto pa_cl_vport_yscale = reg->pa_cl_vport_yscale.value();\n   auto pa_cl_vport_yoffset = reg->pa_cl_vport_yoffset.value();\n   auto pa_cl_vport_zscale = reg->pa_cl_vport_zscale.value();\n   auto pa_cl_vport_zoffset = reg->pa_cl_vport_zoffset.value();\n   uint32_t values1[] = {\n      pa_cl_vport_xscale.value,\n      pa_cl_vport_xoffset.value,\n      pa_cl_vport_yscale.value,\n      pa_cl_vport_yoffset.value,\n      pa_cl_vport_zscale.value,\n      pa_cl_vport_zoffset.value,\n   };\n   internal::writePM4(SetContextRegs { latte::Register::PA_CL_VPORT_XSCALE_0, gsl::make_span(values1) });\n\n   auto pa_cl_gb_vert_clip_adj = reg->pa_cl_gb_vert_clip_adj.value();\n   auto pa_cl_gb_vert_disc_adj = reg->pa_cl_gb_vert_disc_adj.value();\n   auto pa_cl_gb_horz_clip_adj = reg->pa_cl_gb_horz_clip_adj.value();\n   auto pa_cl_gb_horz_disc_adj = reg->pa_cl_gb_horz_disc_adj.value();\n   uint32_t values2[] = {\n      pa_cl_gb_vert_clip_adj.value,\n      pa_cl_gb_vert_disc_adj.value,\n      pa_cl_gb_horz_clip_adj.value,pa_cl_gb_horz_disc_adj.value,\n   };\n   internal::writePM4(SetContextRegs { latte::Register::PA_CL_GB_VERT_CLIP_ADJ, gsl::make_span(values2) });\n\n   auto pa_sc_vport_zmin = reg->pa_sc_vport_zmin.value();\n   auto pa_sc_vport_zmax = reg->pa_sc_vport_zmax.value();\n   uint32_t values3[] = {\n      pa_sc_vport_zmin.value,\n      pa_sc_vport_zmax.value,\n   };\n   internal::writePM4(SetContextRegs { latte::Register::PA_SC_VPORT_ZMIN_0, gsl::make_span(values3) });\n}\n\nvoid\nGX2SetRasterizerClipControl(BOOL rasteriser,\n                            BOOL zclipEnable)\n{\n   GX2SetRasterizerClipControlEx(rasteriser, zclipEnable, FALSE);\n}\n\nvoid\nGX2SetRasterizerClipControlEx(BOOL rasteriser,\n                              BOOL zclipEnable,\n                              BOOL halfZ)\n{\n   auto pa_cl_clip_cntl = latte::PA_CL_CLIP_CNTL::get(0);\n\n   pa_cl_clip_cntl = pa_cl_clip_cntl\n      .RASTERISER_DISABLE(!rasteriser)\n      .ZCLIP_NEAR_DISABLE(!zclipEnable)\n      .ZCLIP_FAR_DISABLE(!zclipEnable)\n      .DX_CLIP_SPACE_DEF(!!halfZ);\n\n   internal::writePM4(SetContextReg { latte::Register::PA_CL_CLIP_CNTL, pa_cl_clip_cntl.value });\n}\n\nvoid\nGX2SetRasterizerClipControlHalfZ(BOOL rasteriser,\n                                 BOOL zclipEnable,\n                                 BOOL halfZ)\n{\n   GX2SetRasterizerClipControlEx(rasteriser, zclipEnable, halfZ);\n}\n\nvoid\nLibrary::registerRegistersSymbols()\n{\n   RegisterFunctionExport(GX2SetAAMask);\n   RegisterFunctionExport(GX2InitAAMaskReg);\n   RegisterFunctionExport(GX2GetAAMaskReg);\n   RegisterFunctionExport(GX2SetAAMaskReg);\n   RegisterFunctionExport(GX2SetAlphaTest);\n   RegisterFunctionExport(GX2InitAlphaTestReg);\n   RegisterFunctionExport(GX2GetAlphaTestReg);\n   RegisterFunctionExport(GX2SetAlphaTestReg);\n   RegisterFunctionExport(GX2SetAlphaToMask);\n   RegisterFunctionExport(GX2InitAlphaToMaskReg);\n   RegisterFunctionExport(GX2GetAlphaToMaskReg);\n   RegisterFunctionExport(GX2SetAlphaToMaskReg);\n   RegisterFunctionExport(GX2SetBlendConstantColor);\n   RegisterFunctionExport(GX2InitBlendConstantColorReg);\n   RegisterFunctionExport(GX2GetBlendConstantColorReg);\n   RegisterFunctionExport(GX2SetBlendConstantColorReg);\n   RegisterFunctionExport(GX2SetBlendControl);\n   RegisterFunctionExport(GX2InitBlendControlReg);\n   RegisterFunctionExport(GX2GetBlendControlReg);\n   RegisterFunctionExport(GX2SetBlendControlReg);\n   RegisterFunctionExport(GX2SetColorControl);\n   RegisterFunctionExport(GX2InitColorControlReg);\n   RegisterFunctionExport(GX2GetColorControlReg);\n   RegisterFunctionExport(GX2SetColorControlReg);\n   RegisterFunctionExport(GX2SetDepthOnlyControl);\n   RegisterFunctionExport(GX2SetDepthStencilControl);\n   RegisterFunctionExport(GX2InitDepthStencilControlReg);\n   RegisterFunctionExport(GX2GetDepthStencilControlReg);\n   RegisterFunctionExport(GX2SetDepthStencilControlReg);\n   RegisterFunctionExport(GX2SetStencilMask);\n   RegisterFunctionExport(GX2InitStencilMaskReg);\n   RegisterFunctionExport(GX2GetStencilMaskReg);\n   RegisterFunctionExport(GX2SetStencilMaskReg);\n   RegisterFunctionExport(GX2SetLineWidth);\n   RegisterFunctionExport(GX2InitLineWidthReg);\n   RegisterFunctionExport(GX2GetLineWidthReg);\n   RegisterFunctionExport(GX2SetLineWidthReg);\n   RegisterFunctionExport(GX2SetPointSize);\n   RegisterFunctionExport(GX2InitPointSizeReg);\n   RegisterFunctionExport(GX2GetPointSizeReg);\n   RegisterFunctionExport(GX2SetPointSizeReg);\n   RegisterFunctionExport(GX2SetPointLimits);\n   RegisterFunctionExport(GX2InitPointLimitsReg);\n   RegisterFunctionExport(GX2GetPointLimitsReg);\n   RegisterFunctionExport(GX2SetPointLimitsReg);\n   RegisterFunctionExport(GX2SetCullOnlyControl);\n   RegisterFunctionExport(GX2SetPolygonControl);\n   RegisterFunctionExport(GX2InitPolygonControlReg);\n   RegisterFunctionExport(GX2GetPolygonControlReg);\n   RegisterFunctionExport(GX2SetPolygonControlReg);\n   RegisterFunctionExport(GX2SetPolygonOffset);\n   RegisterFunctionExport(GX2InitPolygonOffsetReg);\n   RegisterFunctionExport(GX2GetPolygonOffsetReg);\n   RegisterFunctionExport(GX2SetPolygonOffsetReg);\n   RegisterFunctionExport(GX2SetScissor);\n   RegisterFunctionExport(GX2InitScissorReg);\n   RegisterFunctionExport(GX2GetScissorReg);\n   RegisterFunctionExport(GX2SetScissorReg);\n   RegisterFunctionExport(GX2SetTargetChannelMasks);\n   RegisterFunctionExport(GX2InitTargetChannelMasksReg);\n   RegisterFunctionExport(GX2GetTargetChannelMasksReg);\n   RegisterFunctionExport(GX2SetTargetChannelMasksReg);\n   RegisterFunctionExport(GX2SetViewport);\n   RegisterFunctionExport(GX2InitViewportReg);\n   RegisterFunctionExport(GX2GetViewportReg);\n   RegisterFunctionExport(GX2SetViewportReg);\n   RegisterFunctionExport(GX2SetRasterizerClipControl);\n   RegisterFunctionExport(GX2SetRasterizerClipControlEx);\n   RegisterFunctionExport(GX2SetRasterizerClipControlHalfZ);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_registers.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libgpu/latte/latte_registers.h>\n\nnamespace cafe::gx2\n{\n\n#pragma pack(push, 1)\n\nstruct GX2AAMaskReg\n{\n   be2_val<latte::PA_SC_AA_MASK> pa_sc_aa_mask;\n};\nCHECK_SIZE(GX2AAMaskReg, 4);\nCHECK_OFFSET(GX2AAMaskReg, 0, pa_sc_aa_mask);\n\nstruct GX2AlphaTestReg\n{\n   be2_val<latte::SX_ALPHA_TEST_CONTROL> sx_alpha_test_control;\n   be2_val<latte::SX_ALPHA_REF> sx_alpha_ref;\n};\nCHECK_SIZE(GX2AlphaTestReg, 8);\nCHECK_OFFSET(GX2AlphaTestReg, 0, sx_alpha_test_control);\nCHECK_OFFSET(GX2AlphaTestReg, 4, sx_alpha_ref);\n\nstruct GX2AlphaToMaskReg\n{\n   be2_val<latte::DB_ALPHA_TO_MASK> db_alpha_to_mask;\n};\nCHECK_SIZE(GX2AlphaToMaskReg, 4);\nCHECK_OFFSET(GX2AlphaToMaskReg, 0, db_alpha_to_mask);\n\nstruct GX2BlendControlReg\n{\n   be2_val<GX2RenderTarget> target;\n   be2_val<latte::CB_BLENDN_CONTROL> cb_blend_control;\n};\nCHECK_SIZE(GX2BlendControlReg, 8);\nCHECK_OFFSET(GX2BlendControlReg, 0, target);\nCHECK_OFFSET(GX2BlendControlReg, 4, cb_blend_control);\n\nstruct GX2BlendConstantColorReg\n{\n   be2_val<float> red;\n   be2_val<float> green;\n   be2_val<float> blue;\n   be2_val<float> alpha;\n};\nCHECK_SIZE(GX2BlendConstantColorReg, 0x10);\nCHECK_OFFSET(GX2BlendConstantColorReg, 0x00, red);\nCHECK_OFFSET(GX2BlendConstantColorReg, 0x04, green);\nCHECK_OFFSET(GX2BlendConstantColorReg, 0x08, blue);\nCHECK_OFFSET(GX2BlendConstantColorReg, 0x0c, alpha);\n\nstruct GX2ColorControlReg\n{\n   be2_val<latte::CB_COLOR_CONTROL> cb_color_control;\n};\nCHECK_SIZE(GX2ColorControlReg, 0x04);\nCHECK_OFFSET(GX2ColorControlReg, 0x00, cb_color_control);\n\nstruct GX2DepthStencilControlReg\n{\n   be2_val<latte::DB_DEPTH_CONTROL> db_depth_control;\n};\nCHECK_SIZE(GX2DepthStencilControlReg, 4);\nCHECK_OFFSET(GX2DepthStencilControlReg, 0, db_depth_control);\n\nstruct GX2StencilMaskReg\n{\n   be2_val<latte::DB_STENCILREFMASK> db_stencilrefmask;\n   be2_val<latte::DB_STENCILREFMASK_BF> db_stencilrefmask_bf;\n};\nCHECK_SIZE(GX2StencilMaskReg, 8);\nCHECK_OFFSET(GX2StencilMaskReg, 0, db_stencilrefmask);\nCHECK_OFFSET(GX2StencilMaskReg, 4, db_stencilrefmask_bf);\n\nstruct GX2LineWidthReg\n{\n   be2_val<latte::PA_SU_LINE_CNTL> pa_su_line_cntl;\n};\nCHECK_SIZE(GX2LineWidthReg, 4);\nCHECK_OFFSET(GX2LineWidthReg, 0, pa_su_line_cntl);\n\nstruct GX2PointSizeReg\n{\n   be2_val<latte::PA_SU_POINT_SIZE> pa_su_point_size;\n};\nCHECK_SIZE(GX2PointSizeReg, 4);\nCHECK_OFFSET(GX2PointSizeReg, 0, pa_su_point_size);\n\nstruct GX2PointLimitsReg\n{\n   be2_val<latte::PA_SU_POINT_MINMAX> pa_su_point_minmax;\n};\nCHECK_SIZE(GX2PointLimitsReg, 4);\nCHECK_OFFSET(GX2PointLimitsReg, 0, pa_su_point_minmax);\n\nstruct GX2PolygonControlReg\n{\n   be2_val<latte::PA_SU_SC_MODE_CNTL> pa_su_sc_mode_cntl;\n};\nCHECK_SIZE(GX2PolygonControlReg, 4);\nCHECK_OFFSET(GX2PolygonControlReg, 0, pa_su_sc_mode_cntl);\n\nstruct GX2PolygonOffsetReg\n{\n   be2_val<latte::PA_SU_POLY_OFFSET_FRONT_SCALE> pa_su_poly_offset_front_scale;\n   be2_val<latte::PA_SU_POLY_OFFSET_FRONT_OFFSET> pa_su_poly_offset_front_offset;\n   be2_val<latte::PA_SU_POLY_OFFSET_BACK_SCALE> pa_su_poly_offset_back_scale;\n   be2_val<latte::PA_SU_POLY_OFFSET_BACK_OFFSET> pa_su_poly_offset_back_offset;\n\n   be2_val<latte::PA_SU_POLY_OFFSET_CLAMP> pa_su_poly_offset_clamp;\n};\nCHECK_SIZE(GX2PolygonOffsetReg, 0x14);\nCHECK_OFFSET(GX2PolygonOffsetReg, 0x00, pa_su_poly_offset_front_scale);\nCHECK_OFFSET(GX2PolygonOffsetReg, 0x04, pa_su_poly_offset_front_offset);\nCHECK_OFFSET(GX2PolygonOffsetReg, 0x08, pa_su_poly_offset_back_scale);\nCHECK_OFFSET(GX2PolygonOffsetReg, 0x0C, pa_su_poly_offset_back_offset);\nCHECK_OFFSET(GX2PolygonOffsetReg, 0x10, pa_su_poly_offset_clamp);\n\nstruct GX2ScissorReg\n{\n   be2_val<latte::PA_SC_GENERIC_SCISSOR_TL> pa_sc_generic_scissor_tl;\n   be2_val<latte::PA_SC_GENERIC_SCISSOR_BR> pa_sc_generic_scissor_br;\n};\nCHECK_SIZE(GX2ScissorReg, 0x08);\nCHECK_OFFSET(GX2ScissorReg, 0x00, pa_sc_generic_scissor_tl);\nCHECK_OFFSET(GX2ScissorReg, 0x04, pa_sc_generic_scissor_br);\n\nstruct GX2TargetChannelMaskReg\n{\n   be2_val<latte::CB_TARGET_MASK> cb_target_mask;\n};\nCHECK_SIZE(GX2TargetChannelMaskReg, 0x04);\nCHECK_OFFSET(GX2TargetChannelMaskReg, 0x00, cb_target_mask);\n\nstruct GX2ViewportReg\n{\n   be2_val<latte::PA_CL_VPORT_XSCALE_N> pa_cl_vport_xscale;\n   be2_val<latte::PA_CL_VPORT_XOFFSET_N> pa_cl_vport_xoffset;\n   be2_val<latte::PA_CL_VPORT_YSCALE_N> pa_cl_vport_yscale;\n   be2_val<latte::PA_CL_VPORT_YOFFSET_N> pa_cl_vport_yoffset;\n   be2_val<latte::PA_CL_VPORT_ZSCALE_N> pa_cl_vport_zscale;\n   be2_val<latte::PA_CL_VPORT_ZOFFSET_N> pa_cl_vport_zoffset;\n\n   be2_val<latte::PA_CL_GB_VERT_CLIP_ADJ> pa_cl_gb_vert_clip_adj;\n   be2_val<latte::PA_CL_GB_VERT_DISC_ADJ> pa_cl_gb_vert_disc_adj;\n   be2_val<latte::PA_CL_GB_HORZ_CLIP_ADJ> pa_cl_gb_horz_clip_adj;\n   be2_val<latte::PA_CL_GB_HORZ_DISC_ADJ> pa_cl_gb_horz_disc_adj;\n\n   be2_val<latte::PA_SC_VPORT_ZMIN_N> pa_sc_vport_zmin;\n   be2_val<latte::PA_SC_VPORT_ZMAX_N> pa_sc_vport_zmax;\n};\nCHECK_SIZE(GX2ViewportReg, 0x30);\nCHECK_OFFSET(GX2ViewportReg, 0x00, pa_cl_vport_xscale);\nCHECK_OFFSET(GX2ViewportReg, 0x04, pa_cl_vport_xoffset);\nCHECK_OFFSET(GX2ViewportReg, 0x08, pa_cl_vport_yscale);\nCHECK_OFFSET(GX2ViewportReg, 0x0C, pa_cl_vport_yoffset);\nCHECK_OFFSET(GX2ViewportReg, 0x10, pa_cl_vport_zscale);\nCHECK_OFFSET(GX2ViewportReg, 0x14, pa_cl_vport_zoffset);\nCHECK_OFFSET(GX2ViewportReg, 0x18, pa_cl_gb_vert_clip_adj);\nCHECK_OFFSET(GX2ViewportReg, 0x1C, pa_cl_gb_vert_disc_adj);\nCHECK_OFFSET(GX2ViewportReg, 0x20, pa_cl_gb_horz_clip_adj);\nCHECK_OFFSET(GX2ViewportReg, 0x24, pa_cl_gb_horz_disc_adj);\nCHECK_OFFSET(GX2ViewportReg, 0x28, pa_sc_vport_zmin);\nCHECK_OFFSET(GX2ViewportReg, 0x2C, pa_sc_vport_zmax);\n\n#pragma pack(pop)\n\nvoid\nGX2SetAAMask(uint8_t upperLeft,\n             uint8_t upperRight,\n             uint8_t lowerLeft,\n             uint8_t lowerRight);\n\nvoid\nGX2InitAAMaskReg(virt_ptr<GX2AAMaskReg> reg,\n                 uint8_t upperLeft,\n                 uint8_t upperRight,\n                 uint8_t lowerLeft,\n                 uint8_t lowerRight);\n\nvoid\nGX2GetAAMaskReg(virt_ptr<GX2AAMaskReg> reg,\n                virt_ptr<uint8_t> upperLeft,\n                virt_ptr<uint8_t> upperRight,\n                virt_ptr<uint8_t> lowerLeft,\n                virt_ptr<uint8_t> lowerRight);\n\nvoid\nGX2SetAAMaskReg(virt_ptr<GX2AAMaskReg> reg);\n\nvoid\nGX2SetAlphaTest(BOOL alphaTest,\n                GX2CompareFunction func,\n                float ref);\n\nvoid\nGX2InitAlphaTestReg(virt_ptr<GX2AlphaTestReg> reg,\n                    BOOL alphaTest,\n                    GX2CompareFunction func,\n                    float ref);\n\nvoid\nGX2GetAlphaTestReg(virt_ptr<const GX2AlphaTestReg> reg,\n                   virt_ptr<BOOL> alphaTest,\n                   virt_ptr<GX2CompareFunction> func,\n                   virt_ptr<float> ref);\n\nvoid\nGX2SetAlphaTestReg(virt_ptr<GX2AlphaTestReg> reg);\n\nvoid\nGX2SetAlphaToMask(BOOL alphaToMask,\n                  GX2AlphaToMaskMode mode);\n\nvoid\nGX2InitAlphaToMaskReg(virt_ptr<GX2AlphaToMaskReg> reg,\n                      BOOL alphaToMask,\n                      GX2AlphaToMaskMode mode);\n\nvoid\nGX2GetAlphaToMaskReg(virt_ptr<const GX2AlphaToMaskReg> reg,\n                     virt_ptr<BOOL> alphaToMask,\n                     virt_ptr<GX2AlphaToMaskMode> mode);\n\nvoid\nGX2SetAlphaToMaskReg(virt_ptr<GX2AlphaToMaskReg> reg);\n\nvoid\nGX2SetBlendConstantColor(float red,\n                         float green,\n                         float blue,\n                         float alpha);\n\nvoid\nGX2InitBlendConstantColorReg(virt_ptr<GX2BlendConstantColorReg> reg,\n                             float red,\n                             float green,\n                             float blue,\n                             float alpha);\n\nvoid\nGX2GetBlendConstantColorReg(virt_ptr<GX2BlendConstantColorReg> reg,\n                            virt_ptr<float> red,\n                            virt_ptr<float> green,\n                            virt_ptr<float> blue,\n                            virt_ptr<float> alpha);\n\nvoid\nGX2SetBlendConstantColorReg(virt_ptr<GX2BlendConstantColorReg> reg);\n\nvoid\nGX2SetBlendControl(GX2RenderTarget target,\n                   GX2BlendMode colorSrcBlend,\n                   GX2BlendMode colorDstBlend,\n                   GX2BlendCombineMode colorCombine,\n                   BOOL useAlphaBlend,\n                   GX2BlendMode alphaSrcBlend,\n                   GX2BlendMode alphaDstBlend,\n                   GX2BlendCombineMode alphaCombine);\n\nvoid\nGX2InitBlendControlReg(virt_ptr<GX2BlendControlReg> reg,\n                       GX2RenderTarget target,\n                       GX2BlendMode colorSrcBlend,\n                       GX2BlendMode colorDstBlend,\n                       GX2BlendCombineMode colorCombine,\n                       BOOL useAlphaBlend,\n                       GX2BlendMode alphaSrcBlend,\n                       GX2BlendMode alphaDstBlend,\n                       GX2BlendCombineMode alphaCombine);\n\nvoid\nGX2GetBlendControlReg(virt_ptr<GX2BlendControlReg> reg,\n                      virt_ptr<GX2RenderTarget> target,\n                      virt_ptr<GX2BlendMode> colorSrcBlend,\n                      virt_ptr<GX2BlendMode> colorDstBlend,\n                      virt_ptr<GX2BlendCombineMode> colorCombine,\n                      virt_ptr<BOOL> useAlphaBlend,\n                      virt_ptr<GX2BlendMode> alphaSrcBlend,\n                      virt_ptr<GX2BlendMode> alphaDstBlend,\n                      virt_ptr<GX2BlendCombineMode> alphaCombine);\n\nvoid\nGX2SetBlendControlReg(virt_ptr<GX2BlendControlReg> reg);\n\nvoid\nGX2SetColorControl(GX2LogicOp rop3,\n                   uint8_t targetBlendEnable,\n                   BOOL multiWriteEnable,\n                   BOOL colorWriteEnable);\n\nvoid\nGX2InitColorControlReg(virt_ptr<GX2ColorControlReg> reg,\n                       GX2LogicOp rop3,\n                       uint8_t targetBlendEnable,\n                       BOOL multiWriteEnable,\n                       BOOL colorWriteEnable);\n\nvoid\nGX2GetColorControlReg(virt_ptr<GX2ColorControlReg> reg,\n                      virt_ptr<GX2LogicOp> rop3,\n                      virt_ptr<uint8_t> targetBlendEnable,\n                      virt_ptr<BOOL> multiWriteEnable,\n                      virt_ptr<BOOL> colorWriteEnable);\n\nvoid\nGX2SetColorControlReg(virt_ptr<GX2ColorControlReg> reg);\n\nvoid\nGX2SetDepthOnlyControl(BOOL depthTest,\n                       BOOL depthWrite,\n                       GX2CompareFunction depthCompare);\n\nvoid\nGX2SetDepthStencilControl(BOOL depthTest,\n                          BOOL depthWrite,\n                          GX2CompareFunction depthCompare,\n                          BOOL stencilTest,\n                          BOOL backfaceStencil,\n                          GX2CompareFunction frontStencilFunc,\n                          GX2StencilFunction frontStencilZPass,\n                          GX2StencilFunction frontStencilZFail,\n                          GX2StencilFunction frontStencilFail,\n                          GX2CompareFunction backStencilFunc,\n                          GX2StencilFunction backStencilZPass,\n                          GX2StencilFunction backStencilZFail,\n                          GX2StencilFunction backStencilFail);\n\nvoid\nGX2InitDepthStencilControlReg(virt_ptr<GX2DepthStencilControlReg> reg,\n                              BOOL depthTest,\n                              BOOL depthWrite,\n                              GX2CompareFunction depthCompare,\n                              BOOL stencilTest,\n                              BOOL backfaceStencil,\n                              GX2CompareFunction frontStencilFunc,\n                              GX2StencilFunction frontStencilZPass,\n                              GX2StencilFunction frontStencilZFail,\n                              GX2StencilFunction frontStencilFail,\n                              GX2CompareFunction backStencilFunc,\n                              GX2StencilFunction backStencilZPass,\n                              GX2StencilFunction backStencilZFail,\n                              GX2StencilFunction backStencilFail);\n\nvoid\nGX2GetDepthStencilControlReg(virt_ptr<GX2DepthStencilControlReg> reg,\n                             virt_ptr<BOOL> depthTest,\n                             virt_ptr<BOOL> depthWrite,\n                             virt_ptr<GX2CompareFunction> depthCompare,\n                             virt_ptr<BOOL> stencilTest,\n                             virt_ptr<BOOL> backfaceStencil,\n                             virt_ptr<GX2CompareFunction> frontStencilFunc,\n                             virt_ptr<GX2StencilFunction> frontStencilZPass,\n                             virt_ptr<GX2StencilFunction> frontStencilZFail,\n                             virt_ptr<GX2StencilFunction> frontStencilFail,\n                             virt_ptr<GX2CompareFunction> backStencilFunc,\n                             virt_ptr<GX2StencilFunction> backStencilZPass,\n                             virt_ptr<GX2StencilFunction> backStencilZFail,\n                             virt_ptr<GX2StencilFunction> backStencilFail);\n\nvoid\nGX2SetDepthStencilControlReg(virt_ptr<GX2DepthStencilControlReg> reg);\n\nvoid\nGX2SetStencilMask(uint8_t frontMask, uint8_t frontWriteMask, uint8_t frontRef,\n                  uint8_t backMask, uint8_t backWriteMask, uint8_t backRef);\n\nvoid\nGX2InitStencilMaskReg(virt_ptr<GX2StencilMaskReg> reg,\n                      uint8_t frontMask, uint8_t frontWriteMask, uint8_t frontRef,\n                      uint8_t backMask, uint8_t backWriteMask, uint8_t backRef);\n\nvoid\nGX2GetStencilMaskReg(virt_ptr<GX2StencilMaskReg> reg,\n                     virt_ptr<uint8_t> frontMask,\n                     virt_ptr<uint8_t> frontWriteMask,\n                     virt_ptr<uint8_t> frontRef,\n                     virt_ptr<uint8_t> backMask,\n                     virt_ptr<uint8_t> backWriteMask,\n                     virt_ptr<uint8_t> backRef);\n\nvoid\nGX2SetStencilMaskReg(virt_ptr<GX2StencilMaskReg> reg);\n\nvoid\nGX2SetLineWidth(float width);\n\nvoid\nGX2InitLineWidthReg(virt_ptr<GX2LineWidthReg> reg,\n                    float width);\n\nvoid\nGX2GetLineWidthReg(virt_ptr<GX2LineWidthReg> reg,\n                   virt_ptr<float> width);\n\nvoid\nGX2SetLineWidthReg(virt_ptr<GX2LineWidthReg> reg);\n\nvoid\nGX2SetPointSize(float width,\n                float height);\n\nvoid\nGX2InitPointSizeReg(virt_ptr<GX2PointSizeReg> reg,\n                    float width,\n                    float height);\n\nvoid\nGX2GetPointSizeReg(virt_ptr<GX2PointSizeReg> reg,\n                   virt_ptr<float> width,\n                   virt_ptr<float> height);\n\nvoid\nGX2SetPointSizeReg(virt_ptr<GX2PointSizeReg> reg);\n\nvoid\nGX2SetPointLimits(float min,\n                  float max);\n\nvoid\nGX2InitPointLimitsReg(virt_ptr<GX2PointLimitsReg> reg,\n                      float min,\n                      float max);\n\nvoid\nGX2GetPointLimitsReg(virt_ptr<GX2PointLimitsReg> reg,\n                     virt_ptr<float> min,\n                     virt_ptr<float> max);\n\nvoid\nGX2SetPointLimitsReg(virt_ptr<GX2PointLimitsReg> reg);\n\nvoid\nGX2SetCullOnlyControl(GX2FrontFace frontFace,\n                      BOOL cullFront,\n                      BOOL cullBack);\n\nvoid\nGX2SetPolygonControl(GX2FrontFace frontFace,\n                     BOOL cullFront,\n                     BOOL cullBack,\n                     BOOL polyMode,\n                     GX2PolygonMode polyModeFront,\n                     GX2PolygonMode polyModeBack,\n                     BOOL polyOffsetFrontEnable,\n                     BOOL polyOffsetBackEnable,\n                     BOOL polyOffsetParaEnable);\n\nvoid\nGX2InitPolygonControlReg(virt_ptr<GX2PolygonControlReg> reg,\n                         GX2FrontFace frontFace,\n                         BOOL cullFront,\n                         BOOL cullBack,\n                         uint32_t polyMode,\n                         GX2PolygonMode polyModeFront,\n                         GX2PolygonMode polyModeBack,\n                         BOOL polyOffsetFrontEnable,\n                         BOOL polyOffsetBackEnable,\n                         BOOL polyOffsetParaEnable);\n\nvoid\nGX2GetPolygonControlReg(virt_ptr<GX2PolygonControlReg> reg,\n                        virt_ptr<GX2FrontFace> frontFace,\n                        virt_ptr<BOOL> cullFront,\n                        virt_ptr<BOOL> cullBack,\n                        virt_ptr<uint32_t> polyMode,\n                        virt_ptr<GX2PolygonMode> polyModeFront,\n                        virt_ptr<GX2PolygonMode> polyModeBack,\n                        virt_ptr<BOOL> polyOffsetFrontEnable,\n                        virt_ptr<BOOL> polyOffsetBackEnable,\n                        virt_ptr<BOOL> polyOffsetParaEnable);\n\nvoid\nGX2SetPolygonControlReg(virt_ptr<GX2PolygonControlReg> reg);\n\nvoid\nGX2SetPolygonOffset(float frontOffset,\n                    float frontScale,\n                    float backOffset,\n                    float backScale,\n                    float clamp);\n\nvoid\nGX2InitPolygonOffsetReg(virt_ptr<GX2PolygonOffsetReg> reg,\n                        float frontOffset,\n                        float frontScale,\n                        float backOffset,\n                        float backScale,\n                        float clamp);\n\nvoid\nGX2GetPolygonOffsetReg(virt_ptr<GX2PolygonOffsetReg> reg,\n                       virt_ptr<float> frontOffset,\n                       virt_ptr<float> frontScale,\n                       virt_ptr<float> backOffset,\n                       virt_ptr<float> backScale,\n                       virt_ptr<float> clamp);\n\nvoid\nGX2SetPolygonOffsetReg(virt_ptr<GX2PolygonOffsetReg> reg);\n\nvoid\nGX2SetScissor(uint32_t x,\n              uint32_t y,\n              uint32_t width,\n              uint32_t height);\n\nvoid\nGX2InitScissorReg(virt_ptr<GX2ScissorReg> reg,\n                  uint32_t x,\n                  uint32_t y,\n                  uint32_t width,\n                  uint32_t height);\n\nvoid\nGX2GetScissorReg(virt_ptr<GX2ScissorReg> reg,\n                 virt_ptr<uint32_t> x,\n                 virt_ptr<uint32_t> y,\n                 virt_ptr<uint32_t> width,\n                 virt_ptr<uint32_t> height);\n\nvoid\nGX2SetScissorReg(virt_ptr<GX2ScissorReg> reg);\n\nvoid\nGX2SetTargetChannelMasks(GX2ChannelMask mask0,\n                         GX2ChannelMask mask1,\n                         GX2ChannelMask mask2,\n                         GX2ChannelMask mask3,\n                         GX2ChannelMask mask4,\n                         GX2ChannelMask mask5,\n                         GX2ChannelMask mask6,\n                         GX2ChannelMask mask7);\n\nvoid\nGX2InitTargetChannelMasksReg(virt_ptr<GX2TargetChannelMaskReg> reg,\n                             GX2ChannelMask mask0,\n                             GX2ChannelMask mask1,\n                             GX2ChannelMask mask2,\n                             GX2ChannelMask mask3,\n                             GX2ChannelMask mask4,\n                             GX2ChannelMask mask5,\n                             GX2ChannelMask mask6,\n                             GX2ChannelMask mask7);\n\nvoid\nGX2GetTargetChannelMasksReg(virt_ptr<GX2TargetChannelMaskReg> reg,\n                            virt_ptr<GX2ChannelMask> mask0,\n                            virt_ptr<GX2ChannelMask> mask1,\n                            virt_ptr<GX2ChannelMask> mask2,\n                            virt_ptr<GX2ChannelMask> mask3,\n                            virt_ptr<GX2ChannelMask> mask4,\n                            virt_ptr<GX2ChannelMask> mask5,\n                            virt_ptr<GX2ChannelMask> mask6,\n                            virt_ptr<GX2ChannelMask> mask7);\n\nvoid\nGX2SetTargetChannelMasksReg(virt_ptr<GX2TargetChannelMaskReg> reg);\n\nvoid\nGX2SetViewport(float x,\n               float y,\n               float width,\n               float height,\n               float nearZ,\n               float farZ);\n\nvoid\nGX2InitViewportReg(virt_ptr<GX2ViewportReg> reg,\n                   float x,\n                   float y,\n                   float width,\n                   float height,\n                   float nearZ,\n                   float farZ);\n\nvoid\nGX2GetViewportReg(virt_ptr<GX2ViewportReg> reg,\n                  virt_ptr<float> x,\n                  virt_ptr<float> y,\n                  virt_ptr<float> width,\n                  virt_ptr<float> height,\n                  virt_ptr<float> nearZ,\n                  virt_ptr<float> farZ);\n\nvoid\nGX2SetViewportReg(virt_ptr<GX2ViewportReg> reg);\n\nvoid\nGX2SetRasterizerClipControl(BOOL rasteriser,\n                            BOOL zclipEnable);\n\nvoid\nGX2SetRasterizerClipControlEx(BOOL rasteriser,\n                              BOOL zclipEnable,\n                              BOOL halfZ);\n\nvoid\nGX2SetRasterizerClipControlHalfZ(BOOL rasteriser,\n                                 BOOL zclipEnable,\n                                 BOOL halfZ);\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_sampler.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_sampler.h\"\n\n#include <algorithm>\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2InitSampler(virt_ptr<GX2Sampler> sampler,\n               GX2TexClampMode clampMode,\n               GX2TexXYFilterMode minMagFilterMode)\n{\n   sampler->regs.word0 = latte::SQ_TEX_SAMPLER_WORD0_N::get(0)\n      .CLAMP_X(static_cast<latte::SQ_TEX_CLAMP>(clampMode))\n      .CLAMP_Y(static_cast<latte::SQ_TEX_CLAMP>(clampMode))\n      .CLAMP_Z(static_cast<latte::SQ_TEX_CLAMP>(clampMode))\n      .XY_MAG_FILTER(static_cast<latte::SQ_TEX_XY_FILTER>(minMagFilterMode))\n      .XY_MIN_FILTER(static_cast<latte::SQ_TEX_XY_FILTER>(minMagFilterMode));\n\n   sampler->regs.word1 = latte::SQ_TEX_SAMPLER_WORD1_N::get(0)\n      .MAX_LOD(fixed_from_data<ufixed_4_6_t>(1023));\n\n   sampler->regs.word2 = latte::SQ_TEX_SAMPLER_WORD2_N::get(0)\n      .TYPE(true);\n}\n\nvoid\nGX2InitSamplerBorderType(virt_ptr<GX2Sampler> sampler,\n                         GX2TexBorderType borderType)\n{\n   auto word0 = sampler->regs.word0.value();\n\n   word0 = word0\n      .BORDER_COLOR_TYPE(static_cast<latte::SQ_TEX_BORDER_COLOR>(borderType));\n\n   sampler->regs.word0 = word0;\n}\n\nvoid\nGX2InitSamplerClamping(virt_ptr<GX2Sampler> sampler,\n                       GX2TexClampMode clampX,\n                       GX2TexClampMode clampY,\n                       GX2TexClampMode clampZ)\n{\n   auto word0 = sampler->regs.word0.value();\n\n   word0 = word0\n      .CLAMP_X(static_cast<latte::SQ_TEX_CLAMP>(clampX))\n      .CLAMP_Y(static_cast<latte::SQ_TEX_CLAMP>(clampY))\n      .CLAMP_Z(static_cast<latte::SQ_TEX_CLAMP>(clampZ));\n\n   sampler->regs.word0 = word0;\n}\n\nvoid\nGX2InitSamplerDepthCompare(virt_ptr<GX2Sampler> sampler,\n                           GX2CompareFunction depthCompare)\n{\n   auto word0 = sampler->regs.word0.value();\n\n   word0 = word0\n      .DEPTH_COMPARE_FUNCTION(static_cast<latte::REF_FUNC>(depthCompare));\n\n   sampler->regs.word0 = word0;\n}\n\nvoid\nGX2InitSamplerFilterAdjust(virt_ptr<GX2Sampler> sampler,\n                           BOOL highPrecision,\n                           GX2TexMipPerfMode perfMip,\n                           GX2TexZPerfMode perfZ)\n{\n   auto word2 = sampler->regs.word2.value();\n\n   word2 = word2\n      .HIGH_PRECISION_FILTER(!!highPrecision)\n      .PERF_MIP(perfMip)\n      .PERF_Z(perfZ);\n\n   sampler->regs.word2 = word2;\n}\n\nvoid\nGX2InitSamplerLOD(virt_ptr<GX2Sampler> sampler,\n                  float lodMin,\n                  float lodMax,\n                  float lodBias)\n{\n   auto word1 = sampler->regs.word1.value();\n\n   lodMin = std::min(std::max(lodMin, 0.0f), 16.0f);\n   lodMax = std::min(std::max(lodMax, 0.0f), 16.0f);\n   lodBias = std::min(std::max(lodBias, -32.0f), 32.0f);\n\n   word1 = word1\n      .MIN_LOD(ufixed_4_6_t { lodMin })\n      .MAX_LOD(ufixed_4_6_t { lodMax })\n      .LOD_BIAS(sfixed_1_5_6_t { lodBias });\n\n   sampler->regs.word1 = word1;\n}\n\nvoid\nGX2InitSamplerLODAdjust(virt_ptr<GX2Sampler> sampler,\n                        float anisoBias,\n                        BOOL lodUsesMinorAxis)\n{\n   auto word0 = sampler->regs.word0.value();\n   auto word2 = sampler->regs.word2.value();\n\n   anisoBias = std::min(std::max(anisoBias, 0.0f), 2.0f);\n\n   word2 = word2\n      .ANISO_BIAS(ufixed_1_5_t { anisoBias });\n\n   word0 = word0\n      .LOD_USES_MINOR_AXIS(!!lodUsesMinorAxis);\n\n   sampler->regs.word0 = word0;\n   sampler->regs.word2 = word2;\n}\n\nvoid\nGX2InitSamplerRoundingMode(virt_ptr<GX2Sampler> sampler,\n                           GX2RoundingMode roundingMode)\n{\n   auto word2 = sampler->regs.word2.value();\n\n   word2 = word2\n      .TRUNCATE_COORD(static_cast<latte::SQ_TEX_ROUNDING_MODE>(roundingMode));\n\n   sampler->regs.word2 = word2;\n}\n\nvoid\nGX2InitSamplerXYFilter(virt_ptr<GX2Sampler> sampler,\n                       GX2TexXYFilterMode filterMag,\n                       GX2TexXYFilterMode filterMin,\n                       GX2TexAnisoRatio maxAniso)\n{\n   auto word0 = sampler->regs.word0.value();\n\n   word0 = word0\n      .XY_MAG_FILTER(static_cast<latte::SQ_TEX_XY_FILTER>(filterMag))\n      .XY_MIN_FILTER(static_cast<latte::SQ_TEX_XY_FILTER>(filterMin))\n      .MAX_ANISO_RATIO(static_cast<latte::SQ_TEX_ANISO>(maxAniso));\n\n   sampler->regs.word0 = word0;\n}\n\nvoid\nGX2InitSamplerZMFilter(virt_ptr<GX2Sampler> sampler,\n                       GX2TexZFilterMode filterZ,\n                       GX2TexMipFilterMode filterMip)\n{\n   auto word0 = sampler->regs.word0.value();\n\n   word0 = word0\n      .Z_FILTER(static_cast<latte::SQ_TEX_Z_FILTER>(filterZ))\n      .MIP_FILTER(static_cast<latte::SQ_TEX_Z_FILTER>(filterMip));\n\n   sampler->regs.word0 = word0;\n}\n\nvoid\nGX2SetPixelSamplerBorderColor(uint32_t unit,\n                              float red,\n                              float green,\n                              float blue,\n                              float alpha)\n{\n   uint32_t values[] = {\n      bit_cast<uint32_t>(red),\n      bit_cast<uint32_t>(green),\n      bit_cast<uint32_t>(blue),\n      bit_cast<uint32_t>(alpha),\n   };\n\n   auto id = latte::Register::TD_PS_SAMPLER_BORDER0_RED + 4 * (unit * 4);\n   internal::writePM4(latte::pm4::SetConfigRegs {\n      static_cast<latte::Register>(id),\n      gsl::make_span(values)\n   });\n}\n\n\nvoid\nGX2SetVertexSamplerBorderColor(uint32_t unit,\n                                 float red,\n                                 float green,\n                                 float blue,\n                                 float alpha)\n{\n   uint32_t values[] = {\n      bit_cast<uint32_t>(red),\n      bit_cast<uint32_t>(green),\n      bit_cast<uint32_t>(blue),\n      bit_cast<uint32_t>(alpha),\n   };\n\n   auto id = latte::Register::TD_VS_SAMPLER_BORDER0_RED + 4 * (unit * 4);\n   internal::writePM4(latte::pm4::SetConfigRegs {\n      static_cast<latte::Register>(id),\n      gsl::make_span(values)\n   });\n}\n\nvoid\nGX2SetGeometrySamplerBorderColor(uint32_t unit,\n                                 float red,\n                                 float green,\n                                 float blue,\n                                 float alpha)\n{\n   uint32_t values[] = {\n      bit_cast<uint32_t>(red),\n      bit_cast<uint32_t>(green),\n      bit_cast<uint32_t>(blue),\n      bit_cast<uint32_t>(alpha),\n   };\n\n   auto id = latte::Register::TD_GS_SAMPLER_BORDER0_RED + 4 * (unit * 4);\n   internal::writePM4(latte::pm4::SetConfigRegs {\n      static_cast<latte::Register>(id),\n      gsl::make_span(values)\n   });\n}\n\nvoid\nLibrary::registerSamplerSymbols()\n{\n   RegisterFunctionExport(GX2InitSampler);\n   RegisterFunctionExport(GX2InitSamplerBorderType);\n   RegisterFunctionExport(GX2InitSamplerClamping);\n   RegisterFunctionExport(GX2InitSamplerDepthCompare);\n   RegisterFunctionExport(GX2InitSamplerFilterAdjust);\n   RegisterFunctionExport(GX2InitSamplerLOD);\n   RegisterFunctionExport(GX2InitSamplerLODAdjust);\n   RegisterFunctionExport(GX2InitSamplerRoundingMode);\n   RegisterFunctionExport(GX2InitSamplerXYFilter);\n   RegisterFunctionExport(GX2InitSamplerZMFilter);\n   RegisterFunctionExport(GX2SetPixelSamplerBorderColor);\n   RegisterFunctionExport(GX2SetVertexSamplerBorderColor);\n   RegisterFunctionExport(GX2SetGeometrySamplerBorderColor);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_sampler.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libgpu/latte/latte_registers.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_sampler Sampler\n * \\ingroup gx2\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct GX2Sampler\n{\n   struct {\n      be2_val<latte::SQ_TEX_SAMPLER_WORD0_N> word0;\n      be2_val<latte::SQ_TEX_SAMPLER_WORD1_N> word1;\n      be2_val<latte::SQ_TEX_SAMPLER_WORD2_N> word2;\n   } regs;\n};\nCHECK_OFFSET(GX2Sampler, 0x00, regs.word0);\nCHECK_OFFSET(GX2Sampler, 0x04, regs.word1);\nCHECK_OFFSET(GX2Sampler, 0x08, regs.word2);\nCHECK_SIZE(GX2Sampler, 0x0C);\n\n#pragma pack(pop)\n\nvoid\nGX2InitSampler(virt_ptr<GX2Sampler> sampler,\n               GX2TexClampMode clampMode,\n               GX2TexXYFilterMode minMagFilterMode);\n\nvoid\nGX2InitSamplerBorderType(virt_ptr<GX2Sampler> sampler,\n                         GX2TexBorderType borderType);\n\nvoid\nGX2InitSamplerClamping(virt_ptr<GX2Sampler> sampler,\n                       GX2TexClampMode clampX,\n                       GX2TexClampMode clampY,\n                       GX2TexClampMode clampZ);\n\nvoid\nGX2InitSamplerDepthCompare(virt_ptr<GX2Sampler> sampler,\n                           GX2CompareFunction depthCompare);\n\nvoid\nGX2InitSamplerFilterAdjust(virt_ptr<GX2Sampler> sampler,\n                           BOOL highPrecision,\n                           GX2TexMipPerfMode perfMip,\n                           GX2TexZPerfMode perfZ);\n\nvoid\nGX2InitSamplerLOD(virt_ptr<GX2Sampler> sampler,\n                  float lodMin,\n                  float lodMax,\n                  float lodBias);\n\nvoid\nGX2InitSamplerLODAdjust(virt_ptr<GX2Sampler> sampler,\n                        float unk1,\n                        BOOL unk2);\n\nvoid\nGX2InitSamplerRoundingMode(virt_ptr<GX2Sampler> sampler,\n                           GX2RoundingMode roundingMode);\n\nvoid\nGX2InitSamplerXYFilter(virt_ptr<GX2Sampler> sampler,\n                       GX2TexXYFilterMode filterMag,\n                       GX2TexXYFilterMode filterMin,\n                       GX2TexAnisoRatio maxAniso);\n\nvoid\nGX2InitSamplerZMFilter(virt_ptr<GX2Sampler> sampler,\n                       GX2TexZFilterMode filterZ,\n                       GX2TexMipFilterMode filterMip);\n\nvoid\nGX2SetPixelSamplerBorderColor(uint32_t unit,\n                              float red,\n                              float green,\n                              float blue,\n                              float alpha);\nvoid\nGX2SetVertexSamplerBorderColor(uint32_t unit,\n                               float red,\n                               float green,\n                               float blue,\n                               float alpha);\nvoid\nGX2SetGeometrySamplerBorderColor(uint32_t unit,\n                                 float red,\n                                 float green,\n                                 float blue,\n                                 float alpha);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_shaders.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_debug.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_fetchshader.h\"\n#include \"gx2_shaders.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <common/decaf_assert.h>\n\nusing namespace latte::pm4;\nusing latte::Register;\n\nnamespace cafe::gx2\n{\n\nusing namespace cafe::coreinit;\n\nuint32_t\nGX2CalcGeometryShaderInputRingBufferSize(uint32_t ringItemSize)\n{\n   return ringItemSize * 16384;\n}\n\nuint32_t\nGX2CalcGeometryShaderOutputRingBufferSize(uint32_t ringItemSize)\n{\n   return ringItemSize * 16384;\n}\n\nvoid\nGX2SetFetchShader(virt_ptr<GX2FetchShader> shader)\n{\n   auto sq_pgm_resources_fs = shader->regs.sq_pgm_resources_fs.value();\n\n   uint32_t shaderRegData[] = {\n      OSEffectiveToPhysical(virt_cast<virt_addr>(shader->data)) >> 8,\n      shader->size >> 3,\n      0x100000,\n      0x100000,\n      sq_pgm_resources_fs.value,\n   };\n   internal::writePM4(SetContextRegs { Register::SQ_PGM_START_FS, gsl::make_span(shaderRegData) });\n\n   uint32_t vgt_instance_step_rates[] = {\n      shader->divisors[0],\n      shader->divisors[1],\n   };\n   internal::writePM4(SetContextRegs { Register::VGT_INSTANCE_STEP_RATE_0, gsl::make_span(vgt_instance_step_rates) });\n\n   internal::debugDumpShader(shader);\n}\n\nvoid\nGX2SetVertexShader(virt_ptr<GX2VertexShader> shader)\n{\n   auto ringItemsize = shader->ringItemsize.value();\n   auto pa_cl_vs_out_cntl = shader->regs.pa_cl_vs_out_cntl.value();\n\n   auto spi_vs_out_config = shader->regs.spi_vs_out_config.value();\n   auto num_spi_vs_out_id = shader->regs.num_spi_vs_out_id.value();\n   auto spi_vs_out_id = shader->regs.spi_vs_out_id.value();\n\n   auto sq_pgm_resources_vs = shader->regs.sq_pgm_resources_vs.value();\n   auto sq_vtx_semantic_clear = shader->regs.sq_vtx_semantic_clear.value();\n   auto num_sq_vtx_semantic = shader->regs.num_sq_vtx_semantic.value();\n   auto sq_vtx_semantic = shader->regs.sq_vtx_semantic.value();\n\n   auto vgt_hos_reuse_depth = shader->regs.vgt_hos_reuse_depth.value();\n   auto vgt_primitiveid_en = shader->regs.vgt_primitiveid_en.value();\n   auto vgt_strmout_buffer_en = shader->regs.vgt_strmout_buffer_en.value();\n   auto vgt_vertex_reuse_block_cntl = shader->regs.vgt_vertex_reuse_block_cntl.value();\n\n   auto shaderProgAddr = OSEffectiveToPhysical(virt_cast<virt_addr>(shader->data));\n   auto shaderProgSize = shader->size;\n   if (!shaderProgAddr) {\n      shaderProgAddr = OSEffectiveToPhysical(virt_cast<virt_addr>(shader->gx2rData.buffer));\n      shaderProgSize = shader->gx2rData.elemCount * shader->gx2rData.elemSize;\n   }\n\n   decaf_check(shaderProgAddr);\n   decaf_check(shaderProgSize);\n\n   uint32_t shaderRegData[] = {\n      shaderProgAddr >> 8,\n      shaderProgSize >> 3,\n      0x100000,\n      0x100000,\n      sq_pgm_resources_vs.value,\n   };\n\n   if (shader->mode != GX2ShaderMode::GeometryShader) {\n      internal::writePM4(SetContextRegs { Register::SQ_PGM_START_VS, gsl::make_span(shaderRegData) });\n      internal::writePM4(SetContextReg { Register::VGT_PRIMITIVEID_EN, vgt_primitiveid_en.value });\n      internal::writePM4(SetContextReg { Register::SPI_VS_OUT_CONFIG, spi_vs_out_config.value });\n      internal::writePM4(SetContextReg { Register::PA_CL_VS_OUT_CNTL, pa_cl_vs_out_cntl.value });\n\n      if (num_spi_vs_out_id > 0) {\n         internal::writePM4(SetContextRegs {\n            Register::SPI_VS_OUT_ID_0,\n            gsl::make_span(&spi_vs_out_id[0].value, num_spi_vs_out_id)\n         });\n      }\n\n      internal::writePM4(SetContextReg { Register::SQ_PGM_CF_OFFSET_VS, 0 });\n\n      if (shader->hasStreamOut) {\n         internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_0, shader->streamOutStride[0] >> 2 });\n         internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_1, shader->streamOutStride[1] >> 2 });\n         internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_2, shader->streamOutStride[2] >> 2 });\n         internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_3, shader->streamOutStride[3] >> 2 });\n      }\n\n      internal::writePM4(SetContextReg { Register::VGT_STRMOUT_BUFFER_EN, vgt_strmout_buffer_en.value });\n   } else {\n      internal::writePM4(SetContextRegs { Register::SQ_PGM_START_ES, gsl::make_span(shaderRegData) });\n      internal::writePM4(SetContextReg { Register::SQ_ESGS_RING_ITEMSIZE, ringItemsize });\n   }\n\n   internal::writePM4(SetContextReg { Register::SQ_VTX_SEMANTIC_CLEAR, sq_vtx_semantic_clear.value });\n\n   if (num_sq_vtx_semantic > 0) {\n      internal::writePM4(SetContextRegs {\n         Register::SQ_VTX_SEMANTIC_0,\n         gsl::make_span(&sq_vtx_semantic[0].value, num_sq_vtx_semantic)\n      });\n   }\n\n   internal::writePM4(SetContextReg { Register::VGT_VERTEX_REUSE_BLOCK_CNTL, vgt_vertex_reuse_block_cntl.value });\n   internal::writePM4(SetContextReg { Register::VGT_HOS_REUSE_DEPTH, vgt_hos_reuse_depth.value });\n\n   for (auto i = 0u; i < shader->loopVarCount; ++i) {\n      auto id = static_cast<Register>(Register::SQ_LOOP_CONST_VS_0 + shader->loopVars[i].offset);\n      internal::writePM4(SetLoopConst { id, shader->loopVars[i].value });\n   }\n\n   internal::debugDumpShader(shader);\n}\n\nvoid\nGX2SetPixelShader(virt_ptr<GX2PixelShader> shader)\n{\n   auto cb_shader_control = shader->regs.cb_shader_control.value();\n   auto cb_shader_mask = shader->regs.cb_shader_mask.value();\n   auto db_shader_control = shader->regs.db_shader_control.value();\n\n   auto spi_input_z = shader->regs.spi_input_z.value();\n   auto spi_ps_in_control_0 = shader->regs.spi_ps_in_control_0.value();\n   auto spi_ps_in_control_1 = shader->regs.spi_ps_in_control_1.value();\n   auto spi_ps_input_cntls = shader->regs.spi_ps_input_cntls.value();\n   auto num_spi_ps_input_cntl = shader->regs.num_spi_ps_input_cntl.value();\n\n   auto sq_pgm_resources_ps = shader->regs.sq_pgm_resources_ps.value();\n   auto sq_pgm_exports_ps = shader->regs.sq_pgm_exports_ps.value();\n\n   auto shaderProgAddr = OSEffectiveToPhysical(virt_cast<virt_addr>(shader->data));\n   auto shaderProgSize = shader->size;\n   if (!shaderProgAddr) {\n      shaderProgAddr = OSEffectiveToPhysical(virt_cast<virt_addr>(shader->gx2rData.buffer));\n      shaderProgSize = shader->gx2rData.elemCount * shader->gx2rData.elemSize;\n   }\n\n   decaf_check(shaderProgAddr);\n   decaf_check(shaderProgSize);\n\n   uint32_t shaderRegData[] = {\n      shaderProgAddr >> 8,\n      shaderProgSize >> 3,\n      0x100000,\n      0x100000,\n      sq_pgm_resources_ps.value,\n      sq_pgm_exports_ps.value,\n   };\n   internal::writePM4(SetContextRegs { Register::SQ_PGM_START_PS, gsl::make_span(shaderRegData) });\n\n   uint32_t spi_ps_in_control[] = {\n      spi_ps_in_control_0.value,\n      spi_ps_in_control_1.value,\n   };\n   internal::writePM4(SetContextRegs { Register::SPI_PS_IN_CONTROL_0, gsl::make_span(spi_ps_in_control) });\n\n   if (num_spi_ps_input_cntl > 0) {\n      internal::writePM4(SetContextRegs {\n         Register::SPI_PS_INPUT_CNTL_0,\n         gsl::make_span(&spi_ps_input_cntls[0].value, num_spi_ps_input_cntl),\n      });\n   }\n\n   db_shader_control = db_shader_control\n      .DUAL_EXPORT_ENABLE(1);\n\n   internal::writePM4(SetContextReg { Register::CB_SHADER_MASK, cb_shader_mask.value });\n   internal::writePM4(SetContextReg { Register::CB_SHADER_CONTROL, cb_shader_control.value });\n   internal::writePM4(SetContextReg { Register::DB_SHADER_CONTROL, db_shader_control.value });\n   internal::writePM4(SetContextReg { Register::SPI_INPUT_Z, spi_input_z.value });\n\n   for (auto i = 0u; i < shader->loopVarCount; ++i) {\n      auto id = static_cast<Register>(Register::SQ_LOOP_CONST_PS_0 + shader->loopVars[i].offset);\n      internal::writePM4(SetLoopConst { id, shader->loopVars[i].value });\n   }\n\n   internal::debugDumpShader(shader);\n}\n\nvoid\nGX2SetGeometryShader(virt_ptr<GX2GeometryShader> shader)\n{\n   // Setup geometry shader data\n   auto sq_pgm_resources_gs = shader->regs.sq_pgm_resources_gs.value();\n   auto sq_gs_vert_itemsize = shader->regs.sq_gs_vert_itemsize.value();\n   auto vgt_gs_out_prim_type = shader->regs.vgt_gs_out_prim_type.value();\n   auto vgt_gs_mode = shader->regs.vgt_gs_mode.value();\n   auto vgt_strmout_buffer_en = shader->regs.vgt_strmout_buffer_en.value();\n\n   auto shaderProgAddr = OSEffectiveToPhysical(virt_cast<virt_addr>(shader->data));\n   auto shaderProgSize = shader->size;\n   if (!shaderProgAddr) {\n      shaderProgAddr = OSEffectiveToPhysical(virt_cast<virt_addr>(shader->gx2rData.buffer));\n      shaderProgSize = shader->gx2rData.elemCount * shader->gx2rData.elemSize;\n   }\n\n   decaf_check(shaderProgAddr);\n   decaf_check(shaderProgSize);\n\n   uint32_t shaderRegData[] = {\n      shaderProgAddr >> 8,\n      shaderProgSize >> 3,\n      0,\n      0,\n      sq_pgm_resources_gs.value,\n   };\n   internal::writePM4(SetContextRegs { Register::SQ_PGM_START_GS, gsl::make_span(shaderRegData) });\n   internal::writePM4(SetContextReg { Register::VGT_GS_OUT_PRIM_TYPE, vgt_gs_out_prim_type.value });\n   internal::writePM4(SetContextReg { Register::VGT_GS_MODE, vgt_gs_mode.value });\n   internal::writePM4(SetContextReg { Register::SQ_GS_VERT_ITEMSIZE, sq_gs_vert_itemsize.value });\n\n   if (shader->hasStreamOut) {\n      internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_0, shader->streamOutStride[0] >> 2 });\n      internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_1, shader->streamOutStride[1] >> 2 });\n      internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_2, shader->streamOutStride[2] >> 2 });\n      internal::writePM4(SetContextReg { Register::VGT_STRMOUT_VTX_STRIDE_3, shader->streamOutStride[3] >> 2 });\n   }\n\n   internal::writePM4(SetContextReg { Register::VGT_STRMOUT_BUFFER_EN, vgt_strmout_buffer_en.value });\n\n   // Setup vertex shader data\n   auto sq_pgm_resources_vs = shader->regs.sq_pgm_resources_vs.value();\n   auto num_spi_vs_out_id = shader->regs.num_spi_vs_out_id.value();\n   auto spi_vs_out_id = shader->regs.spi_vs_out_id.value();\n   auto spi_vs_out_config = shader->regs.spi_vs_out_config.value();\n   auto pa_cl_vs_out_cntl = shader->regs.pa_cl_vs_out_cntl.value();\n\n   auto vertexShaderProgAddr = OSEffectiveToPhysical(virt_cast<virt_addr>(shader->vertexShaderData));\n   auto vertexShaderProgSize = shader->vertexShaderSize;\n   if (!vertexShaderProgAddr) {\n      vertexShaderProgAddr = OSEffectiveToPhysical(virt_cast<virt_addr>(shader->gx2rVertexShaderData.buffer));\n      vertexShaderProgSize = shader->gx2rVertexShaderData.elemCount * shader->gx2rVertexShaderData.elemSize;\n   }\n\n   decaf_check(vertexShaderProgAddr);\n   decaf_check(vertexShaderProgSize);\n\n   uint32_t vertexShaderRegData[] = {\n      vertexShaderProgAddr >> 8,\n      vertexShaderProgSize >> 3,\n      0,\n      0,\n      sq_pgm_resources_vs.value,\n   };\n   internal::writePM4(SetContextRegs { Register::SQ_PGM_START_VS, gsl::make_span(vertexShaderRegData) });\n   internal::writePM4(SetContextReg { Register::PA_CL_VS_OUT_CNTL, pa_cl_vs_out_cntl.value });\n\n   if (num_spi_vs_out_id > 0) {\n      internal::writePM4(SetContextRegs {\n         Register::SPI_VS_OUT_ID_0,\n         gsl::make_span(&spi_vs_out_id[0].value, num_spi_vs_out_id)\n      });\n   }\n\n   internal::writePM4(SetContextReg { Register::SPI_VS_OUT_CONFIG, spi_vs_out_config.value });\n   internal::writePM4(SetContextReg { Register::SQ_GSVS_RING_ITEMSIZE, shader->ringItemSize });\n\n   for (auto i = 0u; i < shader->loopVarCount; ++i) {\n      auto id = static_cast<Register>(Register::SQ_LOOP_CONST_GS_0 + shader->loopVars[i].offset);\n      internal::writePM4(SetLoopConst { id, shader->loopVars[i].value });\n   }\n}\n\nvoid\n_GX2SetSampler(virt_ptr<GX2Sampler> sampler,\n               uint32_t id)\n{\n   internal::writePM4(SetSamplerAttrib {\n      id * 3,\n      sampler->regs.word0,\n      sampler->regs.word1,\n      sampler->regs.word2\n   });\n}\n\nvoid\nGX2SetPixelSampler(virt_ptr<GX2Sampler> sampler,\n                   uint32_t id)\n{\n   decaf_check(id < 0x12);\n   _GX2SetSampler(sampler, 0 + id);\n}\n\nvoid\nGX2SetVertexSampler(virt_ptr<GX2Sampler> sampler,\n                    uint32_t id)\n{\n   decaf_check(id < 0x12);\n   _GX2SetSampler(sampler, 18 + id);\n}\n\nvoid\nGX2SetGeometrySampler(virt_ptr<GX2Sampler> sampler,\n                      uint32_t id)\n{\n   decaf_check(id < 0x12);\n   _GX2SetSampler(sampler, 36 + id);\n}\n\nvoid\nGX2SetVertexUniformReg(uint32_t offset,\n                       uint32_t count,\n                       virt_ptr<uint32_t> data)\n{\n   auto loop = offset >> 16;\n   if (loop) {\n      auto id = static_cast<Register>(Register::SQ_LOOP_CONST_VS_0 + 4 * loop);\n      internal::writePM4(SetLoopConst { id, data[0] });\n      offset &= 0x7fff;\n   }\n\n   auto id = static_cast<Register>(Register::SQ_ALU_CONSTANT0_256 + 4 * offset);\n   internal::writePM4(SetAluConstsBE { id, { data.get(), count } });\n}\n\nvoid\nGX2SetPixelUniformReg(uint32_t offset,\n                      uint32_t count,\n                      virt_ptr<uint32_t> data)\n{\n   auto loop = offset >> 16;\n   if (loop) {\n      auto id = static_cast<Register>(Register::SQ_LOOP_CONST_PS_0 + 4 * loop);\n      internal::writePM4(SetLoopConst { id, data[0] });\n      offset &= 0x7fff;\n   }\n\n   auto id = static_cast<Register>(Register::SQ_ALU_CONSTANT0_0 + 4 * offset);\n   internal::writePM4(SetAluConstsBE { id, { data.get(), count } });\n}\n\nvoid\nGX2SetVertexUniformBlock(uint32_t location,\n                         uint32_t size,\n                         virt_ptr<const void> data)\n{\n   decaf_check(!(virt_cast<virt_addr>(data) & 0xFF));\n\n   SetVtxResource res;\n   std::memset(&res, 0, sizeof(SetVtxResource));\n   res.id = (latte::SQ_RES_OFFSET::VS_BUF_RESOURCE_0 + location) * 7;\n   res.baseAddress = OSEffectiveToPhysical(virt_cast<virt_addr>(data));\n\n   res.word1 = res.word1\n      .SIZE(size - 1);\n\n   res.word2 = res.word2\n      .STRIDE(16)\n      .DATA_FORMAT(latte::SQ_DATA_FORMAT::FMT_32_32_32_32)\n      .FORMAT_COMP_ALL(latte::SQ_FORMAT_COMP::SIGNED);\n\n   res.word3 = res.word3\n      .MEM_REQUEST_SIZE(1);\n\n   res.word6 = res.word6\n      .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER);\n\n   internal::writePM4(res);\n\n   auto addrId = static_cast<Register>(Register::SQ_ALU_CONST_CACHE_VS_0 + location * 4);\n   auto addr256 = OSEffectiveToPhysical(virt_cast<virt_addr>(data)) >> 8;\n   internal::writePM4(SetContextReg { addrId, addr256 });\n\n   auto sizeId = static_cast<Register>(Register::SQ_ALU_CONST_BUFFER_SIZE_VS_0 + location * 4);\n   auto size256 = ((size + 255) >> 8) & 0x1FF;\n   internal::writePM4(SetContextReg { sizeId, size256 });\n}\n\nvoid\nGX2SetPixelUniformBlock(uint32_t location,\n                        uint32_t size,\n                        virt_ptr<const void> data)\n{\n   decaf_check(!(virt_cast<virt_addr>(data) & 0xFF));\n\n   SetVtxResource res;\n   std::memset(&res, 0, sizeof(SetVtxResource));\n   res.id = (latte::SQ_RES_OFFSET::PS_BUF_RESOURCE_0 + location) * 7;\n   res.baseAddress = OSEffectiveToPhysical(virt_cast<virt_addr>(data));\n\n   res.word1 = res.word1\n      .SIZE(size - 1);\n\n   res.word2 = res.word2\n      .STRIDE(16)\n      .DATA_FORMAT(latte::SQ_DATA_FORMAT::FMT_32_32_32_32)\n      .FORMAT_COMP_ALL(latte::SQ_FORMAT_COMP::SIGNED);\n\n   res.word3 = res.word3\n      .MEM_REQUEST_SIZE(1);\n\n   res.word6 = res.word6\n      .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER);\n\n   internal::writePM4(res);\n\n   auto addrId = static_cast<Register>(Register::SQ_ALU_CONST_CACHE_PS_0 + location * 4);\n   auto addr256 = OSEffectiveToPhysical(virt_cast<virt_addr>(data)) >> 8;\n   internal::writePM4(SetContextReg { addrId, addr256 });\n\n   auto sizeId = static_cast<Register>(Register::SQ_ALU_CONST_BUFFER_SIZE_PS_0 + location * 4);\n   auto size256 = ((size + 255) >> 8) & 0x1FF;\n   internal::writePM4(SetContextReg { sizeId, size256 });\n}\n\nvoid\nGX2SetGeometryUniformBlock(uint32_t location,\n                           uint32_t size,\n                           virt_ptr<const void> data)\n{\n   decaf_check(!(virt_cast<virt_addr>(data) & 0xFF));\n\n   SetVtxResource res;\n   std::memset(&res, 0, sizeof(SetVtxResource));\n   res.id = (latte::SQ_RES_OFFSET::GS_BUF_RESOURCE_0 + location) * 7;\n   res.baseAddress = OSEffectiveToPhysical(virt_cast<virt_addr>(data));\n\n   res.word1 = res.word1\n      .SIZE(size - 1);\n\n   res.word2 = res.word2\n      .STRIDE(16)\n      .DATA_FORMAT(latte::SQ_DATA_FORMAT::FMT_32_32_32_32)\n      .FORMAT_COMP_ALL(latte::SQ_FORMAT_COMP::SIGNED);\n\n   res.word3 = res.word3\n      .MEM_REQUEST_SIZE(1);\n\n   res.word6 = res.word6\n      .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER);\n\n   internal::writePM4(res);\n\n   auto addrId = static_cast<Register>(Register::SQ_ALU_CONST_CACHE_GS_0 + location * 4);\n   auto addr256 = OSEffectiveToPhysical(virt_cast<virt_addr>(data)) >> 8;\n   internal::writePM4(SetContextReg { addrId, addr256 });\n\n   auto sizeId = static_cast<Register>(Register::SQ_ALU_CONST_BUFFER_SIZE_GS_0 + location * 4);\n   auto size256 = ((size + 255) >> 8) & 0x1FF;\n   internal::writePM4(SetContextReg { sizeId, size256 });\n}\n\nvoid\nGX2SetShaderModeEx(GX2ShaderMode mode,\n                   uint32_t numVsGpr, uint32_t numVsStackEntries,\n                   uint32_t numGsGpr, uint32_t numGsStackEntries,\n                   uint32_t numPsGpr, uint32_t numPsStackEntries)\n{\n   auto sq_config = latte::SQ_CONFIG::get(0);\n   auto sq_gpr_resource_mgmt_1 = latte::SQ_GPR_RESOURCE_MGMT_1::get(0);\n   auto sq_gpr_resource_mgmt_2 = latte::SQ_GPR_RESOURCE_MGMT_2::get(0);\n   auto sq_thread_resource_mgmt = latte::SQ_THREAD_RESOURCE_MGMT::get(0);\n   auto sq_stack_resource_mgmt_1 = latte::SQ_STACK_RESOURCE_MGMT_1::get(0);\n   auto sq_stack_resource_mgmt_2 = latte::SQ_STACK_RESOURCE_MGMT_2::get(0);\n\n   if (mode != GX2ShaderMode::GeometryShader) {\n      auto vgt_gs_mode = latte::VGT_GS_MODE::get(0);\n\n      if (mode == GX2ShaderMode::ComputeShader) {\n         vgt_gs_mode = vgt_gs_mode\n            .MODE(latte::VGT_GS_ENABLE_MODE::SCENARIO_G)\n            .COMPUTE_MODE(1)\n            .FAST_COMPUTE_MODE(1)\n            .PARTIAL_THD_AT_EOI(1);\n      } else {\n         vgt_gs_mode = vgt_gs_mode\n            .MODE(latte::VGT_GS_ENABLE_MODE::OFF);\n      }\n\n      internal::writePM4(SetContextReg { Register::VGT_GS_MODE, vgt_gs_mode.value });\n   }\n\n   if (mode == GX2ShaderMode::ComputeShader) {\n      sq_config = sq_config\n         .ALU_INST_PREFER_VECTOR(1)\n         .PS_PRIO(0)\n         .VS_PRIO(1)\n         .GS_PRIO(2)\n         .ES_PRIO(3);\n   } else {\n      sq_config = sq_config\n         .ALU_INST_PREFER_VECTOR(1)\n         .PS_PRIO(3)\n         .VS_PRIO(2)\n         .GS_PRIO(1)\n         .ES_PRIO(0);\n   }\n\n   if (mode == GX2ShaderMode::UniformRegister) {\n      sq_config = sq_config\n         .DX9_CONSTS(1);\n   }\n\n   if (mode == GX2ShaderMode::GeometryShader) {\n      sq_gpr_resource_mgmt_1 = sq_gpr_resource_mgmt_1\n         .NUM_PS_GPRS(numPsGpr)\n         .NUM_VS_GPRS(64)\n         .NUM_CLAUSE_TEMP_GPRS(4);\n\n      sq_gpr_resource_mgmt_2 = sq_gpr_resource_mgmt_2\n         .NUM_ES_GPRS(numVsGpr)\n         .NUM_GS_GPRS(numGsGpr);\n\n      sq_stack_resource_mgmt_1 = sq_stack_resource_mgmt_1\n         .NUM_PS_STACK_ENTRIES(numPsStackEntries);\n\n      sq_stack_resource_mgmt_2\n         .NUM_ES_STACK_ENTRIES(numVsStackEntries);\n\n      sq_stack_resource_mgmt_2\n         .NUM_GS_STACK_ENTRIES(numGsStackEntries);\n\n      sq_thread_resource_mgmt = sq_thread_resource_mgmt\n         .NUM_PS_THREADS(124)\n         .NUM_VS_THREADS(32)\n         .NUM_GS_THREADS(8)\n         .NUM_ES_THREADS(28);\n   } else if (mode == GX2ShaderMode::ComputeShader) {\n      sq_gpr_resource_mgmt_1 = sq_gpr_resource_mgmt_1\n         .NUM_CLAUSE_TEMP_GPRS(4);\n\n      sq_gpr_resource_mgmt_2 = sq_gpr_resource_mgmt_2\n         .NUM_ES_GPRS(248);\n\n      sq_stack_resource_mgmt_2 = sq_stack_resource_mgmt_2\n         .NUM_ES_STACK_ENTRIES(256);\n\n      sq_thread_resource_mgmt = sq_thread_resource_mgmt\n         .NUM_PS_THREADS(1)\n         .NUM_VS_THREADS(1)\n         .NUM_GS_THREADS(1)\n         .NUM_ES_THREADS(189);\n   } else {\n      sq_gpr_resource_mgmt_1 = sq_gpr_resource_mgmt_1\n         .NUM_PS_GPRS(numPsGpr)\n         .NUM_VS_GPRS(numVsGpr);\n\n      sq_stack_resource_mgmt_1 = sq_stack_resource_mgmt_1\n         .NUM_PS_STACK_ENTRIES(numPsStackEntries)\n         .NUM_VS_STACK_ENTRIES(numVsStackEntries);\n\n      sq_thread_resource_mgmt = sq_thread_resource_mgmt\n         .NUM_PS_THREADS(136)\n         .NUM_VS_THREADS(48)\n         .NUM_GS_THREADS(4)\n         .NUM_ES_THREADS(4);\n   }\n\n   uint32_t regData[] = {\n      sq_config.value,\n      sq_gpr_resource_mgmt_1.value,\n      sq_gpr_resource_mgmt_2.value,\n      sq_thread_resource_mgmt.value,\n      sq_stack_resource_mgmt_1.value,\n      sq_stack_resource_mgmt_2.value,\n   };\n   internal::writePM4(SetConfigRegs { Register::SQ_CONFIG, gsl::make_span(regData) });\n\n   if (mode == GX2ShaderMode::ComputeShader) {\n      uint32_t ringBaseData[] = { 0, 0xFFFFFF, 0, 0xFFFFFF };\n      internal::writePM4(SetConfigRegs { Register::SQ_ESGS_RING_BASE, gsl::make_span(ringBaseData) });\n\n      uint32_t ringItemSizes[] = { 0, 1 };\n      internal::writePM4(SetContextRegs { Register::SQ_ESGS_RING_ITEMSIZE, gsl::make_span(ringItemSizes) });\n\n      internal::writePM4(SetContextReg { Register::VGT_STRMOUT_EN, 0 });\n   }\n}\n\nvoid\nGX2SetStreamOutBuffer(uint32_t index,\n                      virt_ptr<GX2OutputStream> stream)\n{\n   decaf_check(index <= 3);\n   decaf_check(virt_cast<virt_addr>(stream->buffer) % 256 == 0);\n   decaf_check(stream->stride % 4 == 0);\n   decaf_check(stream->size % 4 == 0);\n\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(stream->buffer));\n   auto size = stream->size;\n\n   if (!addr) {\n      addr = OSEffectiveToPhysical(virt_cast<virt_addr>(stream->gx2rData.buffer));\n      size = stream->gx2rData.elemCount * stream->gx2rData.elemSize;\n   }\n\n   internal::writePM4(SetContextReg { static_cast<Register>(Register::VGT_STRMOUT_BUFFER_SIZE_0 + 16 * index), size >> 2 });\n   internal::writePM4(SetContextReg { static_cast<Register>(Register::VGT_STRMOUT_BUFFER_BASE_0 + 16 * index), addr >> 8 });\n   internal::writePM4(StreamOutBaseUpdate { index, addr >> 8 });\n}\n\nvoid\nGX2SetStreamOutEnable(BOOL enable)\n{\n   auto vgt_strmout_en = latte::VGT_STRMOUT_EN::get(0);\n\n   vgt_strmout_en = vgt_strmout_en\n      .STREAMOUT(!!enable);\n\n   internal::writePM4(SetContextReg { Register::VGT_STRMOUT_EN, vgt_strmout_en.value });\n}\n\nvoid\nGX2SetStreamOutContext(uint32_t index,\n                       virt_ptr<GX2OutputStream> stream,\n                       GX2StreamOutContextMode mode)\n{\n   decaf_check(index <= 3);\n   auto srcLo = phys_addr { 0 };\n\n   // In the case of an explicit offset, the stream pointer is actually\n   // a uint32_t specifying the offset into the buffer to be using.\n   uint32_t explicitOffset = virt_cast<virt_addr>(stream).getAddress();\n\n   auto control = SBU_CONTROL::get(0)\n      .STORE_BUFFER_FILLED_SIZE(false)\n      .SELECT_BUFFER(static_cast<uint8_t>(index));\n\n   switch (mode) {\n   case GX2StreamOutContextMode::Append:\n      control = control\n         .OFFSET_SOURCE(STRMOUT_OFFSET_FROM_MEM);\n      srcLo = OSEffectiveToPhysical(virt_cast<virt_addr>(stream->context));\n      break;\n   case GX2StreamOutContextMode::FromStart:\n      control = control\n         .OFFSET_SOURCE(STRMOUT_OFFSET_FROM_PACKET);\n      srcLo = phys_addr { 0 };\n      break;\n   case GX2StreamOutContextMode::FromOffset:\n      control = control\n         .OFFSET_SOURCE(STRMOUT_OFFSET_FROM_PACKET);\n      srcLo = phys_addr { explicitOffset };\n      break;\n   }\n\n   internal::writePM4(StreamOutBufferUpdate { control, phys_addr { 0 }, 0, srcLo, 0 });\n}\n\nvoid\nGX2SaveStreamOutContext(uint32_t index,\n                        virt_ptr<GX2OutputStream> stream)\n{\n   decaf_check(index <= 3);\n   auto dstLo = OSEffectiveToPhysical(virt_cast<virt_addr>(stream->context));\n\n   auto control = SBU_CONTROL::get(0)\n      .STORE_BUFFER_FILLED_SIZE(true)\n      .OFFSET_SOURCE(STRMOUT_OFFSET_NONE)\n      .SELECT_BUFFER(static_cast<uint8_t>(index));\n\n   internal::writePM4(StreamOutBufferUpdate { control, dstLo, 0, phys_addr { 0 }, 0 });\n}\n\nvoid\nGX2SetGeometryShaderInputRingBuffer(virt_ptr<void> buffer,\n                                    uint32_t size)\n{\n   internal::writePM4(SetConfigReg {\n      Register::SQ_ESGS_RING_BASE,\n      OSEffectiveToPhysical(virt_cast<virt_addr>(buffer)) >> 8\n   });\n   internal::writePM4(SetConfigReg { Register::SQ_ESGS_RING_SIZE, size >> 8 });\n\n   SetVtxResource res;\n   std::memset(&res, 0, sizeof(SetVtxResource));\n   res.id = latte::SQ_RES_OFFSET::GS_GSIN_RESOURCE * 7;\n   res.baseAddress = OSEffectiveToPhysical(virt_cast<virt_addr>(buffer));\n\n   res.word1 = res.word1\n      .SIZE(size - 1);\n\n   res.word2 = res.word2\n      .STRIDE(4)\n      .CLAMP_X(latte::SQ_VTX_CLAMP::TO_NAN)\n      .DATA_FORMAT(latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT);\n\n   res.word3 = res.word3\n      .UNCACHED(1);\n\n   res.word6 = res.word6\n      .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER);\n\n   internal::writePM4(res);\n}\n\nvoid\nGX2SetGeometryShaderOutputRingBuffer(virt_ptr<void> buffer,\n                                     uint32_t size)\n{\n   internal::writePM4(SetConfigReg {\n      Register::SQ_GSVS_RING_BASE,\n      OSEffectiveToPhysical(virt_cast<virt_addr>(buffer)) >> 8\n   });\n   internal::writePM4(SetConfigReg { Register::SQ_GSVS_RING_SIZE, size >> 8 });\n\n   SetVtxResource res;\n   std::memset(&res, 0, sizeof(SetVtxResource));\n   res.id = latte::SQ_RES_OFFSET::VS_GSOUT_RESOURCE * 7;\n   res.baseAddress = OSEffectiveToPhysical(virt_cast<virt_addr>(buffer));\n\n   res.word1 = res.word1\n      .SIZE(size - 1);\n\n   res.word2 = res.word2\n      .STRIDE(4)\n      .CLAMP_X(latte::SQ_VTX_CLAMP::TO_NAN)\n      .DATA_FORMAT(latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT);\n\n   res.word3 = res.word3\n      .UNCACHED(1);\n\n   res.word6 = res.word6\n      .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_BUFFER);\n\n   internal::writePM4(res);\n}\n\nuint32_t\nGX2GetPixelShaderGPRs(virt_ptr<GX2PixelShader> shader)\n{\n   return shader->regs.sq_pgm_resources_ps.value().NUM_GPRS();\n}\n\nuint32_t\nGX2GetPixelShaderStackEntries(virt_ptr<GX2PixelShader> shader)\n{\n   return shader->regs.sq_pgm_resources_ps.value().STACK_SIZE();\n}\n\nuint32_t\nGX2GetVertexShaderGPRs(virt_ptr<GX2VertexShader> shader)\n{\n   return shader->regs.sq_pgm_resources_vs.value().NUM_GPRS();\n}\n\nuint32_t\nGX2GetVertexShaderStackEntries(virt_ptr<GX2VertexShader> shader)\n{\n   return shader->regs.sq_pgm_resources_vs.value().STACK_SIZE();\n}\n\nuint32_t\nGX2GetGeometryShaderGPRs(virt_ptr<GX2GeometryShader> shader)\n{\n   return shader->regs.sq_pgm_resources_gs.value().NUM_GPRS();\n}\n\nuint32_t\nGX2GetGeometryShaderStackEntries(virt_ptr<GX2GeometryShader> shader)\n{\n   return shader->regs.sq_pgm_resources_gs.value().STACK_SIZE();\n}\n\nvoid\nLibrary::registerShadersSymbols()\n{\n   RegisterFunctionExport(GX2CalcGeometryShaderInputRingBufferSize);\n   RegisterFunctionExport(GX2CalcGeometryShaderOutputRingBufferSize);\n   RegisterFunctionExport(GX2SetFetchShader);\n   RegisterFunctionExport(GX2SetVertexShader);\n   RegisterFunctionExport(GX2SetPixelShader);\n   RegisterFunctionExport(GX2SetGeometryShader);\n   RegisterFunctionExport(GX2SetVertexSampler);\n   RegisterFunctionExport(GX2SetPixelSampler);\n   RegisterFunctionExport(GX2SetGeometrySampler);\n   RegisterFunctionExport(GX2SetVertexUniformReg);\n   RegisterFunctionExport(GX2SetPixelUniformReg);\n   RegisterFunctionExport(GX2SetVertexUniformBlock);\n   RegisterFunctionExport(GX2SetPixelUniformBlock);\n   RegisterFunctionExport(GX2SetGeometryUniformBlock);\n   RegisterFunctionExport(GX2SetShaderModeEx);\n   RegisterFunctionExport(GX2SetStreamOutBuffer);\n   RegisterFunctionExport(GX2SetStreamOutEnable);\n   RegisterFunctionExport(GX2SetStreamOutContext);\n   RegisterFunctionExport(GX2SaveStreamOutContext);\n   RegisterFunctionExport(GX2SetGeometryShaderInputRingBuffer);\n   RegisterFunctionExport(GX2SetGeometryShaderOutputRingBuffer);\n   RegisterFunctionExport(GX2GetPixelShaderGPRs);\n   RegisterFunctionExport(GX2GetPixelShaderStackEntries);\n   RegisterFunctionExport(GX2GetVertexShaderGPRs);\n   RegisterFunctionExport(GX2GetVertexShaderStackEntries);\n   RegisterFunctionExport(GX2GetGeometryShaderGPRs);\n   RegisterFunctionExport(GX2GetGeometryShaderStackEntries);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_shaders.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include \"gx2_sampler.h\"\n#include \"gx2r_buffer.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libgpu/latte/latte_registers.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_shaders Shaders\n * \\ingroup gx2\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct GX2FetchShader;\n\nstruct GX2UniformVar\n{\n   be2_virt_ptr<const char> name;\n   be2_val<GX2ShaderVarType> type;\n   be2_val<uint32_t> count;\n   be2_val<uint32_t> offset;\n   be2_val<int32_t> block;\n};\nCHECK_OFFSET(GX2UniformVar, 0x00, name);\nCHECK_OFFSET(GX2UniformVar, 0x04, type);\nCHECK_OFFSET(GX2UniformVar, 0x08, count);\nCHECK_OFFSET(GX2UniformVar, 0x0C, offset);\nCHECK_OFFSET(GX2UniformVar, 0x10, block);\nCHECK_SIZE(GX2UniformVar, 0x14);\n\nstruct GX2UniformInitialValue\n{\n   be2_array<float, 4> value;\n   be2_val<uint32_t> offset;\n};\nCHECK_OFFSET(GX2UniformInitialValue, 0x00, value);\nCHECK_OFFSET(GX2UniformInitialValue, 0x10, offset);\nCHECK_SIZE(GX2UniformInitialValue, 0x14);\n\nstruct GX2UniformBlock\n{\n   be2_virt_ptr<const char> name;\n   be2_val<uint32_t> offset;\n   be2_val<uint32_t> size;\n};\nCHECK_OFFSET(GX2UniformBlock, 0x00, name);\nCHECK_OFFSET(GX2UniformBlock, 0x04, offset);\nCHECK_OFFSET(GX2UniformBlock, 0x08, size);\nCHECK_SIZE(GX2UniformBlock, 0x0C);\n\nstruct GX2AttribVar\n{\n   be2_virt_ptr<const char> name;\n   be2_val<GX2ShaderVarType> type;\n   be2_val<uint32_t> count;\n   be2_val<uint32_t> location;\n};\nCHECK_OFFSET(GX2AttribVar, 0x00, name);\nCHECK_OFFSET(GX2AttribVar, 0x04, type);\nCHECK_OFFSET(GX2AttribVar, 0x08, count);\nCHECK_OFFSET(GX2AttribVar, 0x0C, location);\nCHECK_SIZE(GX2AttribVar, 0x10);\n\nstruct GX2SamplerVar\n{\n   be2_virt_ptr<const char> name;\n   be2_val<GX2SamplerVarType> type;\n   be2_val<uint32_t> location;\n};\nCHECK_OFFSET(GX2SamplerVar, 0x00, name);\nCHECK_OFFSET(GX2SamplerVar, 0x04, type);\nCHECK_OFFSET(GX2SamplerVar, 0x08, location);\nCHECK_SIZE(GX2SamplerVar, 0x0C);\n\nstruct GX2LoopVar\n{\n   be2_val<uint32_t> offset;\n   be2_val<uint32_t> value;\n};\nCHECK_OFFSET(GX2LoopVar, 0x00, offset);\nCHECK_OFFSET(GX2LoopVar, 0x04, value);\nCHECK_SIZE(GX2LoopVar, 0x08);\n\nstruct GX2VertexShader\n{\n   struct\n   {\n      be2_val<latte::SQ_PGM_RESOURCES_VS> sq_pgm_resources_vs;\n      be2_val<latte::VGT_PRIMITIVEID_EN> vgt_primitiveid_en;\n      be2_val<latte::SPI_VS_OUT_CONFIG> spi_vs_out_config;\n      be2_val<uint32_t> num_spi_vs_out_id;\n      be2_array<latte::SPI_VS_OUT_ID_N, 10> spi_vs_out_id;\n      be2_val<latte::PA_CL_VS_OUT_CNTL> pa_cl_vs_out_cntl;\n      be2_val<latte::SQ_VTX_SEMANTIC_CLEAR> sq_vtx_semantic_clear;\n      be2_val<uint32_t> num_sq_vtx_semantic;\n      be2_array<latte::SQ_VTX_SEMANTIC_N, 32> sq_vtx_semantic;\n      be2_val<latte::VGT_STRMOUT_BUFFER_EN> vgt_strmout_buffer_en;\n      be2_val<latte::VGT_VERTEX_REUSE_BLOCK_CNTL> vgt_vertex_reuse_block_cntl;\n      be2_val<latte::VGT_HOS_REUSE_DEPTH> vgt_hos_reuse_depth;\n   } regs;\n\n   be2_val<uint32_t> size;\n   be2_virt_ptr<uint8_t> data;\n   be2_val<GX2ShaderMode> mode;\n\n   be2_val<uint32_t> uniformBlockCount;\n   be2_virt_ptr<GX2UniformBlock> uniformBlocks;\n\n   be2_val<uint32_t> uniformVarCount;\n   be2_virt_ptr<GX2UniformVar> uniformVars;\n\n   be2_val<uint32_t> initialValueCount;\n   be2_virt_ptr<GX2UniformInitialValue> initialValues;\n\n   be2_val<uint32_t> loopVarCount;\n   be2_virt_ptr<GX2LoopVar> loopVars;\n\n   be2_val<uint32_t> samplerVarCount;\n   be2_virt_ptr<GX2SamplerVar> samplerVars;\n\n   be2_val<uint32_t> attribVarCount;\n   be2_virt_ptr<GX2AttribVar> attribVars;\n\n   be2_val<uint32_t> ringItemsize;\n\n   be2_val<BOOL> hasStreamOut;\n   be2_array<uint32_t, 4> streamOutStride;\n\n   GX2RBuffer gx2rData;\n};\nCHECK_OFFSET(GX2VertexShader, 0x00, regs.sq_pgm_resources_vs);\nCHECK_OFFSET(GX2VertexShader, 0x04, regs.vgt_primitiveid_en);\nCHECK_OFFSET(GX2VertexShader, 0x08, regs.spi_vs_out_config);\nCHECK_OFFSET(GX2VertexShader, 0x0C, regs.num_spi_vs_out_id);\nCHECK_OFFSET(GX2VertexShader, 0x10, regs.spi_vs_out_id);\nCHECK_OFFSET(GX2VertexShader, 0x38, regs.pa_cl_vs_out_cntl);\nCHECK_OFFSET(GX2VertexShader, 0x3C, regs.sq_vtx_semantic_clear);\nCHECK_OFFSET(GX2VertexShader, 0x40, regs.num_sq_vtx_semantic);\nCHECK_OFFSET(GX2VertexShader, 0x44, regs.sq_vtx_semantic);\nCHECK_OFFSET(GX2VertexShader, 0xC4, regs.vgt_strmout_buffer_en);\nCHECK_OFFSET(GX2VertexShader, 0xC8, regs.vgt_vertex_reuse_block_cntl);\nCHECK_OFFSET(GX2VertexShader, 0xCC, regs.vgt_hos_reuse_depth);\nCHECK_OFFSET(GX2VertexShader, 0xD0, size);\nCHECK_OFFSET(GX2VertexShader, 0xD4, data);\nCHECK_OFFSET(GX2VertexShader, 0xD8, mode);\nCHECK_OFFSET(GX2VertexShader, 0xDC, uniformBlockCount);\nCHECK_OFFSET(GX2VertexShader, 0xE0, uniformBlocks);\nCHECK_OFFSET(GX2VertexShader, 0xE4, uniformVarCount);\nCHECK_OFFSET(GX2VertexShader, 0xE8, uniformVars);\nCHECK_OFFSET(GX2VertexShader, 0xEC, initialValueCount);\nCHECK_OFFSET(GX2VertexShader, 0xF0, initialValues);\nCHECK_OFFSET(GX2VertexShader, 0xF4, loopVarCount);\nCHECK_OFFSET(GX2VertexShader, 0xF8, loopVars);\nCHECK_OFFSET(GX2VertexShader, 0xFC, samplerVarCount);\nCHECK_OFFSET(GX2VertexShader, 0x100, samplerVars);\nCHECK_OFFSET(GX2VertexShader, 0x104, attribVarCount);\nCHECK_OFFSET(GX2VertexShader, 0x108, attribVars);\nCHECK_OFFSET(GX2VertexShader, 0x10C, ringItemsize);\nCHECK_OFFSET(GX2VertexShader, 0x110, hasStreamOut);\nCHECK_OFFSET(GX2VertexShader, 0x114, streamOutStride);\nCHECK_OFFSET(GX2VertexShader, 0x124, gx2rData);\nCHECK_SIZE(GX2VertexShader, 0x134);\n\nstruct GX2PixelShader\n{\n   struct\n   {\n      be2_val<latte::SQ_PGM_RESOURCES_PS> sq_pgm_resources_ps;\n      be2_val<latte::SQ_PGM_EXPORTS_PS> sq_pgm_exports_ps;\n      be2_val<latte::SPI_PS_IN_CONTROL_0> spi_ps_in_control_0;\n      be2_val<latte::SPI_PS_IN_CONTROL_1> spi_ps_in_control_1;\n      be2_val<uint32_t> num_spi_ps_input_cntl;\n      be2_array<latte::SPI_PS_INPUT_CNTL_N, 32> spi_ps_input_cntls;\n      be2_val<latte::CB_SHADER_MASK> cb_shader_mask;\n      be2_val<latte::CB_SHADER_CONTROL> cb_shader_control;\n      be2_val<latte::DB_SHADER_CONTROL> db_shader_control;\n      be2_val<latte::SPI_INPUT_Z> spi_input_z;\n   } regs;\n\n   be2_val<uint32_t> size;\n   be2_virt_ptr<uint8_t> data;\n   be2_val<GX2ShaderMode> mode;\n\n   be2_val<uint32_t> uniformBlockCount;\n   be2_virt_ptr<GX2UniformBlock> uniformBlocks;\n\n   be2_val<uint32_t> uniformVarCount;\n   be2_virt_ptr<GX2UniformVar> uniformVars;\n\n   be2_val<uint32_t> initialValueCount;\n   be2_virt_ptr<GX2UniformInitialValue> initialValues;\n\n   be2_val<uint32_t> loopVarCount;\n   be2_virt_ptr<GX2LoopVar> loopVars;\n\n   be2_val<uint32_t> samplerVarCount;\n   be2_virt_ptr<GX2SamplerVar> samplerVars;\n\n   GX2RBuffer gx2rData;\n};\nCHECK_OFFSET(GX2PixelShader, 0x00, regs.sq_pgm_resources_ps);\nCHECK_OFFSET(GX2PixelShader, 0x04, regs.sq_pgm_exports_ps);\nCHECK_OFFSET(GX2PixelShader, 0x08, regs.spi_ps_in_control_0);\nCHECK_OFFSET(GX2PixelShader, 0x0C, regs.spi_ps_in_control_1);\nCHECK_OFFSET(GX2PixelShader, 0x10, regs.num_spi_ps_input_cntl);\nCHECK_OFFSET(GX2PixelShader, 0x14, regs.spi_ps_input_cntls);\nCHECK_OFFSET(GX2PixelShader, 0x94, regs.cb_shader_mask);\nCHECK_OFFSET(GX2PixelShader, 0x98, regs.cb_shader_control);\nCHECK_OFFSET(GX2PixelShader, 0x9C, regs.db_shader_control);\nCHECK_OFFSET(GX2PixelShader, 0xA0, regs.spi_input_z);\nCHECK_OFFSET(GX2PixelShader, 0xA4, size);\nCHECK_OFFSET(GX2PixelShader, 0xA8, data);\nCHECK_OFFSET(GX2PixelShader, 0xAC, mode);\nCHECK_OFFSET(GX2PixelShader, 0xB0, uniformBlockCount);\nCHECK_OFFSET(GX2PixelShader, 0xB4, uniformBlocks);\nCHECK_OFFSET(GX2PixelShader, 0xB8, uniformVarCount);\nCHECK_OFFSET(GX2PixelShader, 0xBC, uniformVars);\nCHECK_OFFSET(GX2PixelShader, 0xC0, initialValueCount);\nCHECK_OFFSET(GX2PixelShader, 0xC4, initialValues);\nCHECK_OFFSET(GX2PixelShader, 0xC8, loopVarCount);\nCHECK_OFFSET(GX2PixelShader, 0xCC, loopVars);\nCHECK_OFFSET(GX2PixelShader, 0xD0, samplerVarCount);\nCHECK_OFFSET(GX2PixelShader, 0xD4, samplerVars);\nCHECK_OFFSET(GX2PixelShader, 0xD8, gx2rData);\nCHECK_SIZE(GX2PixelShader, 0xe8);\n\nstruct GX2GeometryShader\n{\n   struct\n   {\n      be2_val<latte::SQ_PGM_RESOURCES_GS> sq_pgm_resources_gs;\n      be2_val<latte::VGT_GS_OUT_PRIM_TYPE> vgt_gs_out_prim_type;\n      be2_val<latte::VGT_GS_MODE> vgt_gs_mode;\n      be2_val<latte::PA_CL_VS_OUT_CNTL> pa_cl_vs_out_cntl;\n      be2_val<latte::SQ_PGM_RESOURCES_VS> sq_pgm_resources_vs;\n      be2_val<latte::SQ_GS_VERT_ITEMSIZE> sq_gs_vert_itemsize;\n      be2_val<latte::SPI_VS_OUT_CONFIG> spi_vs_out_config;\n      be2_val<uint32_t> num_spi_vs_out_id;\n      be2_array<latte::SPI_VS_OUT_ID_N, 10> spi_vs_out_id;\n      be2_val<latte::VGT_STRMOUT_BUFFER_EN> vgt_strmout_buffer_en;\n   } regs;\n\n   be2_val<uint32_t> size;\n   be2_virt_ptr<uint8_t> data;\n   be2_val<uint32_t> vertexShaderSize;\n   be2_virt_ptr<uint8_t> vertexShaderData;\n   be2_val<GX2ShaderMode> mode;\n\n   be2_val<uint32_t> uniformBlockCount;\n   be2_virt_ptr<GX2UniformBlock> uniformBlocks;\n\n   be2_val<uint32_t> uniformVarCount;\n   be2_virt_ptr<GX2UniformVar> uniformVars;\n\n   be2_val<uint32_t> initialValueCount;\n   be2_virt_ptr<GX2UniformInitialValue> initialValues;\n\n   be2_val<uint32_t> loopVarCount;\n   be2_virt_ptr<GX2LoopVar> loopVars;\n\n   be2_val<uint32_t> samplerVarCount;\n   be2_virt_ptr<GX2SamplerVar> samplerVars;\n\n   be2_val<uint32_t> ringItemSize;\n   be2_val<BOOL> hasStreamOut;\n   be2_array<uint32_t, 4> streamOutStride;\n\n   GX2RBuffer gx2rData;\n   GX2RBuffer gx2rVertexShaderData;\n};\nCHECK_OFFSET(GX2GeometryShader, 0x00, regs.sq_pgm_resources_gs);\nCHECK_OFFSET(GX2GeometryShader, 0x04, regs.vgt_gs_out_prim_type);\nCHECK_OFFSET(GX2GeometryShader, 0x08, regs.vgt_gs_mode);\nCHECK_OFFSET(GX2GeometryShader, 0x0C, regs.pa_cl_vs_out_cntl);\nCHECK_OFFSET(GX2GeometryShader, 0x10, regs.sq_pgm_resources_vs);\nCHECK_OFFSET(GX2GeometryShader, 0x14, regs.sq_gs_vert_itemsize);\nCHECK_OFFSET(GX2GeometryShader, 0x18, regs.spi_vs_out_config);\nCHECK_OFFSET(GX2GeometryShader, 0x1C, regs.num_spi_vs_out_id);\nCHECK_OFFSET(GX2GeometryShader, 0x20, regs.spi_vs_out_id);\nCHECK_OFFSET(GX2GeometryShader, 0x48, regs.vgt_strmout_buffer_en);\nCHECK_OFFSET(GX2GeometryShader, 0x4C, size);\nCHECK_OFFSET(GX2GeometryShader, 0x50, data);\nCHECK_OFFSET(GX2GeometryShader, 0x54, vertexShaderSize);\nCHECK_OFFSET(GX2GeometryShader, 0x58, vertexShaderData);\nCHECK_OFFSET(GX2GeometryShader, 0x5C, mode);\nCHECK_OFFSET(GX2GeometryShader, 0x60, uniformBlockCount);\nCHECK_OFFSET(GX2GeometryShader, 0x64, uniformBlocks);\nCHECK_OFFSET(GX2GeometryShader, 0x68, uniformVarCount);\nCHECK_OFFSET(GX2GeometryShader, 0x6C, uniformVars);\nCHECK_OFFSET(GX2GeometryShader, 0x70, initialValueCount);\nCHECK_OFFSET(GX2GeometryShader, 0x74, initialValues);\nCHECK_OFFSET(GX2GeometryShader, 0x78, loopVarCount);\nCHECK_OFFSET(GX2GeometryShader, 0x7C, loopVars);\nCHECK_OFFSET(GX2GeometryShader, 0x80, samplerVarCount);\nCHECK_OFFSET(GX2GeometryShader, 0x84, samplerVars);\nCHECK_OFFSET(GX2GeometryShader, 0x88, ringItemSize);\nCHECK_OFFSET(GX2GeometryShader, 0x8C, hasStreamOut);\nCHECK_OFFSET(GX2GeometryShader, 0x90, streamOutStride);\nCHECK_OFFSET(GX2GeometryShader, 0xA0, gx2rData);\nCHECK_OFFSET(GX2GeometryShader, 0xB0, gx2rVertexShaderData);\nCHECK_SIZE(GX2GeometryShader, 0xC0);\n\nstruct GX2AttribStream\n{\n   be2_val<uint32_t> location;\n   be2_val<uint32_t> buffer;\n   be2_val<uint32_t> offset;\n   be2_val<GX2AttribFormat> format;\n   be2_val<GX2AttribIndexType> type;\n   be2_val<uint32_t> aluDivisor;\n   be2_val<uint32_t> mask;\n   be2_val<GX2EndianSwapMode> endianSwap;\n};\nCHECK_OFFSET(GX2AttribStream, 0x0, location);\nCHECK_OFFSET(GX2AttribStream, 0x4, buffer);\nCHECK_OFFSET(GX2AttribStream, 0x8, offset);\nCHECK_OFFSET(GX2AttribStream, 0xC, format);\nCHECK_OFFSET(GX2AttribStream, 0x10, type);\nCHECK_OFFSET(GX2AttribStream, 0x14, aluDivisor);\nCHECK_OFFSET(GX2AttribStream, 0x18, mask);\nCHECK_OFFSET(GX2AttribStream, 0x1C, endianSwap);\nCHECK_SIZE(GX2AttribStream, 0x20);\n\nstruct GX2StreamContext\n{\n   be2_val<uint32_t> currentOffset;\n   // Total size unknown (but <= 256)\n};\nCHECK_OFFSET(GX2StreamContext, 0x00, currentOffset);\n\nstruct GX2OutputStream\n{\n   be2_val<uint32_t> size;\n   be2_virt_ptr<uint8_t> buffer;\n   be2_val<uint32_t> stride;\n   GX2RBuffer gx2rData;\n   be2_virt_ptr<GX2StreamContext> context;\n};\nCHECK_OFFSET(GX2OutputStream, 0x00, size);\nCHECK_OFFSET(GX2OutputStream, 0x04, buffer);\nCHECK_OFFSET(GX2OutputStream, 0x08, stride);\nCHECK_OFFSET(GX2OutputStream, 0x0C, gx2rData);\nCHECK_OFFSET(GX2OutputStream, 0x1C, context);\nCHECK_SIZE(GX2OutputStream, 0x20);\n\n#pragma pack(pop)\n\nuint32_t\nGX2CalcGeometryShaderInputRingBufferSize(uint32_t ringItemSize);\n\nuint32_t\nGX2CalcGeometryShaderOutputRingBufferSize(uint32_t ringItemSize);\n\nvoid\nGX2SetFetchShader(virt_ptr<GX2FetchShader> shader);\n\nvoid\nGX2SetVertexShader(virt_ptr<GX2VertexShader> shader);\n\nvoid\nGX2SetPixelShader(virt_ptr<GX2PixelShader> shader);\n\nvoid\nGX2SetGeometryShader(virt_ptr<GX2GeometryShader> shader);\n\nvoid\nGX2SetVertexSampler(virt_ptr<GX2Sampler> sampler,\n                    uint32_t id);\n\nvoid\nGX2SetPixelSampler(virt_ptr<GX2Sampler> sampler,\n                   uint32_t id);\n\nvoid\nGX2SetGeometrySampler(virt_ptr<GX2Sampler> sampler,\n                      uint32_t id);\n\nvoid\nGX2SetVertexUniformReg(uint32_t offset,\n                       uint32_t count,\n                       virt_ptr<uint32_t> data);\n\nvoid\nGX2SetPixelUniformReg(uint32_t offset,\n                      uint32_t count,\n                      virt_ptr<uint32_t> data);\n\nvoid\nGX2SetVertexUniformBlock(uint32_t location,\n                         uint32_t size,\n                         virt_ptr<const void> data);\n\nvoid\nGX2SetPixelUniformBlock(uint32_t location,\n                        uint32_t size,\n                        virt_ptr<const void> data);\n\nvoid\nGX2SetGeometryUniformBlock(uint32_t location,\n                           uint32_t size,\n                           virt_ptr<const void> data);\n\nvoid\nGX2SetShaderModeEx(GX2ShaderMode mode,\n                   uint32_t numVsGpr, uint32_t numVsStackEntries,\n                   uint32_t numGsGpr, uint32_t numGsStackEntries,\n                   uint32_t numPsGpr, uint32_t numPsStackEntries);\n\nvoid\nGX2SetStreamOutBuffer(uint32_t index,\n                      virt_ptr<GX2OutputStream> stream);\n\nvoid\nGX2SetStreamOutEnable(BOOL enable);\n\nvoid\nGX2SetStreamOutContext(uint32_t index,\n                       virt_ptr<GX2OutputStream> stream,\n                       GX2StreamOutContextMode mode);\n\nvoid\nGX2SaveStreamOutContext(uint32_t index,\n                        virt_ptr<GX2OutputStream> stream);\n\nvoid\nGX2SetGeometryShaderInputRingBuffer(virt_ptr<void> buffer,\n                                    uint32_t size);\n\nvoid\nGX2SetGeometryShaderOutputRingBuffer(virt_ptr<void> buffer,\n                                     uint32_t size);\n\nuint32_t\nGX2GetPixelShaderGPRs(virt_ptr<GX2PixelShader> shader);\n\nuint32_t\nGX2GetPixelShaderStackEntries(virt_ptr<GX2PixelShader> shader);\n\nuint32_t\nGX2GetVertexShaderGPRs(virt_ptr<GX2VertexShader> shader);\n\nuint32_t\nGX2GetVertexShaderStackEntries(virt_ptr<GX2VertexShader> shader);\n\nuint32_t\nGX2GetGeometryShaderGPRs(virt_ptr<GX2GeometryShader> shader);\n\nuint32_t\nGX2GetGeometryShaderStackEntries(virt_ptr<GX2GeometryShader> shader);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_state.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_contextstate.h\"\n#include \"gx2_debugcapture.h\"\n#include \"gx2_displaylist.h\"\n#include \"gx2_event.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_internal_pm4cap.h\"\n#include \"gx2_registers.h\"\n#include \"gx2_state.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_core.h\"\n#include \"cafe/libraries/coreinit/coreinit_memdefaultheap.h\"\n#include \"cafe/libraries/tcl/tcl_driver.h\"\n#include \"decaf_config.h\"\n\n#include <common/log.h>\n#include <common/platform_dir.h>\n#include <libcpu/cpu_formatters.h>\n#include <libgpu/gpu.h>\n#include <libgpu/latte/latte_pm4_commands.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace coreinit;\n\nstruct StaticStateData\n{\n   be2_val<bool> initialized;\n   be2_val<uint32_t> mainCoreId;\n   be2_array<BOOL, 3> profilingEnabled;\n   be2_val<GX2ProfileMode> profileMode;\n   be2_val<GX2TossStage> tossStage;\n   be2_val<uint32_t> timeoutMS;\n   be2_val<uint32_t> hangState;\n   be2_val<uint32_t> hangResponse;\n   be2_val<uint32_t> hangResetSwapTimeout;\n   be2_val<uint32_t> hangResetSwapsOutstanding;\n};\n\nstatic virt_ptr<StaticStateData>\nsStateData = nullptr;\n\nvoid\nGX2Init(virt_ptr<GX2InitAttrib> attributes)\n{\n   auto cbPoolBase = virt_ptr<uint32_t> { nullptr };\n   auto argv = virt_ptr<char> { nullptr };\n   auto cbPoolSize = 0x400000u;\n   auto argc = 0u;\n   auto profileMode = GX2ProfileMode::None;\n   auto tossStage = GX2TossStage::None;\n   auto appIoThreadStackSize = 4096u;\n\n   // Set main gx2 core\n   sStateData->initialized = true;\n   sStateData->mainCoreId = OSGetCoreId();\n\n   // Set default GPU timeout to 10 seconds\n   sStateData->timeoutMS = 10u * 1000;\n\n   // Setup hang params\n   GX2SetMiscParam(GX2MiscParam::HangResponse, 1);\n   GX2SetMiscParam(GX2MiscParam::HangResetSwapTimeout, 1000);\n   GX2SetMiscParam(GX2MiscParam::HangResetSwapsOutstanding, 3);\n\n   // Parse attributes\n   while (attributes && *attributes != GX2InitAttrib::End) {\n      auto id = *(attributes++);\n      auto value = static_cast<uint32_t>(*(attributes++));\n\n      switch (id) {\n      case GX2InitAttrib::CommandBufferPoolBase:\n         cbPoolBase = virt_cast<uint32_t *>(virt_addr { value });\n         break;\n      case GX2InitAttrib::CommandBufferPoolSize:\n         cbPoolSize = value;\n         break;\n      case GX2InitAttrib::ArgC:\n         argc = value;\n         break;\n      case GX2InitAttrib::ArgV:\n         argv = virt_cast<char *>(virt_addr { value });\n         break;\n      case GX2InitAttrib::ProfileMode:\n         profileMode = static_cast<GX2ProfileMode>(value);\n         break;\n      case GX2InitAttrib::TossStage:\n         tossStage = static_cast<GX2TossStage>(value);\n         break;\n      case GX2InitAttrib::AppIoThreadStackSize:\n         appIoThreadStackSize = value;\n         break;\n      default:\n         gLog->warn(\"Unknown GX2InitAttrib {} = {}\", id, value);\n      }\n   }\n\n   // Ensure minimum size\n   if (cbPoolSize < 0x2000) {\n      cbPoolSize = 0x2000;\n   }\n\n   // Allocate command buffer pool\n   if (!cbPoolBase) {\n      cbPoolBase = virt_cast<uint32_t *>(MEMAllocFromDefaultHeapEx(cbPoolSize, 0x100));\n   }\n\n   // Allocate AppIo stack from end of cbPool\n   auto appIoStackBuffer = align_up(virt_cast<virt_addr>(cbPoolBase) + cbPoolSize - appIoThreadStackSize, 64);\n   appIoThreadStackSize = static_cast<uint32_t>(virt_cast<virt_addr>(cbPoolBase) + cbPoolSize - appIoStackBuffer);\n   cbPoolSize = static_cast<uint32_t>(appIoStackBuffer - virt_cast<virt_addr>(cbPoolBase));\n\n   // Init event handler stuff (vsync, flips, etc)\n   internal::initEvents(virt_cast<void *>(appIoStackBuffer),\n                        appIoThreadStackSize);\n\n   // Initialise GPU callbacks\n   gpu::setFlipCallback(&internal::onFlip);\n\n   // Initialise command buffer pools\n   internal::initialiseCommandBufferPool(cbPoolBase, cbPoolSize);\n\n   // Initialise profiling settings\n   internal::initialiseProfiling(profileMode, tossStage);\n\n   // Setup default gx2 state\n   internal::disableStateShadowing();\n   internal::initialiseRegisters();\n   GX2SetDefaultState();\n   GX2Flush();\n}\n\nvoid\nGX2Shutdown()\n{\n   if (internal::debugCaptureEnabled()) {\n      internal::debugCaptureShutdown();\n   }\n}\n\nint32_t\nGX2GetMainCoreId()\n{\n   if (sStateData->initialized) {\n      return sStateData->mainCoreId;\n   } else {\n      return -1;\n   }\n}\n\nvoid\nGX2Flush()\n{\n   if (GX2GetDisplayListWriteStatus()) {\n      gLog->error(\"GX2Flush called from within a display list\");\n   }\n\n   internal::flushCommandBuffer(0x100, TRUE);\n}\n\nuint32_t\nGX2GetGPUTimeout()\n{\n   return sStateData->timeoutMS;\n}\n\nvoid\nGX2SetGPUTimeout(uint32_t timeout)\n{\n   sStateData->timeoutMS = timeout;\n}\n\nuint32_t\nGX2GetMiscParam(GX2MiscParam param)\n{\n   switch (param) {\n   case GX2MiscParam::HangState:\n      return sStateData->hangState;\n   case GX2MiscParam::HangResponse:\n      return sStateData->hangResponse;\n      break;\n   case GX2MiscParam::HangResetSwapTimeout:\n      return sStateData->hangResetSwapTimeout;\n      break;\n   case GX2MiscParam::HangResetSwapsOutstanding:\n      return sStateData->hangResetSwapsOutstanding;\n      break;\n   default:\n      return static_cast<uint32_t>(-1);\n   }\n}\n\nBOOL\nGX2SetMiscParam(GX2MiscParam param,\n                uint32_t value)\n{\n   switch (param) {\n   case GX2MiscParam::HangState:\n      sStateData->hangState = value;\n      break;\n   case GX2MiscParam::HangResponse:\n      if (value > 2) {\n         return FALSE;\n      }\n\n      tcl::TCLSetHangWait(value == 1 ? TRUE : FALSE);\n      sStateData->hangResponse = value;\n      break;\n   case GX2MiscParam::HangResetSwapTimeout:\n      sStateData->hangResetSwapTimeout = value;\n      break;\n   case GX2MiscParam::HangResetSwapsOutstanding:\n      sStateData->hangResetSwapsOutstanding = value;\n      break;\n   default:\n      return FALSE;\n   }\n\n   return TRUE;\n}\n\nvoid\nGX2SetSpecialState(GXSpecialState state,\n                   BOOL enabled)\n{\n   if (state == GXSpecialState::Clear) {\n      if (enabled) {\n         internal::writePM4(latte::pm4::SetContextReg {\n            latte::Register::PA_CL_VTE_CNTL,\n            latte::PA_CL_VTE_CNTL::get(0)\n               .VTX_XY_FMT(true)\n               .VTX_Z_FMT(true)\n               .value\n         });\n         internal::writePM4(latte::pm4::SetContextReg {\n            latte::Register::PA_CL_CLIP_CNTL,\n            latte::PA_CL_CLIP_CNTL::get(0)\n               .CLIP_DISABLE(true)\n               .DX_CLIP_SPACE_DEF(true)\n               .value\n         });\n      } else {\n         internal::writePM4(latte::pm4::SetContextReg {\n            latte::Register::PA_CL_VTE_CNTL,\n            latte::PA_CL_VTE_CNTL::get(0)\n               .VPORT_X_SCALE_ENA(true)\n               .VPORT_X_OFFSET_ENA(true)\n               .VPORT_Y_SCALE_ENA(true)\n               .VPORT_Y_OFFSET_ENA(true)\n               .VPORT_Z_SCALE_ENA(true)\n               .VPORT_Z_OFFSET_ENA(true)\n               .VTX_W0_FMT(true)\n               .value\n         });\n         GX2SetRasterizerClipControl(true, true);\n      }\n   } else if (state == GXSpecialState::Copy) {\n      // There are no registers set for this special state.\n   } else {\n      gLog->warn(\"Unexpected GX2SetSpecialState state {}\", state);\n   }\n}\n\nnamespace internal\n{\n\nvoid\nenableStateShadowing()\n{\n   auto LOAD_CONTROL = latte::CONTEXT_CONTROL_ENABLE::get(0)\n      .ENABLE_CONFIG_REG(true)\n      .ENABLE_CONTEXT_REG(true)\n      .ENABLE_ALU_CONST(true)\n      .ENABLE_BOOL_CONST(true)\n      .ENABLE_LOOP_CONST(true)\n      .ENABLE_RESOURCE(true)\n      .ENABLE_SAMPLER(true)\n      .ENABLE_CTL_CONST(true)\n      .ENABLE_ORDINAL(true);\n\n   auto SHADOW_ENABLE = latte::CONTEXT_CONTROL_ENABLE::get(0)\n      .ENABLE_CONFIG_REG(true)\n      .ENABLE_CONTEXT_REG(true)\n      .ENABLE_ALU_CONST(true)\n      .ENABLE_BOOL_CONST(true)\n      .ENABLE_LOOP_CONST(true)\n      .ENABLE_RESOURCE(true)\n      .ENABLE_SAMPLER(true)\n      .ENABLE_CTL_CONST(true)\n      .ENABLE_ORDINAL(true);\n\n   internal::writePM4(latte::pm4::ContextControl {\n      LOAD_CONTROL,\n      SHADOW_ENABLE\n   });\n}\n\nvoid\ndisableStateShadowing()\n{\n   auto LOAD_CONTROL = latte::CONTEXT_CONTROL_ENABLE::get(0)\n      .ENABLE_ORDINAL(true);\n\n   auto SHADOW_ENABLE = latte::CONTEXT_CONTROL_ENABLE::get(0)\n      .ENABLE_ORDINAL(true);\n\n   internal::writePM4(latte::pm4::ContextControl {\n      LOAD_CONTROL,\n      SHADOW_ENABLE\n   });\n}\n\nbool\nisInitialised()\n{\n   return sStateData->mainCoreId != 0xFF;\n}\n\nuint32_t\ngetMainCoreId()\n{\n   return sStateData->mainCoreId;\n}\n\nvoid\nsetMainCore()\n{\n   sStateData->mainCoreId = OSGetCoreId();\n}\n\nvoid\ninitialiseProfiling(GX2ProfileMode profileMode,\n                    GX2TossStage tossStage)\n{\n   sStateData->profileMode = profileMode;\n   sStateData->tossStage = tossStage;\n\n   // TODO: Update these GX2ProfileMode values with enum named values\n   switch (tossStage) {\n   case GX2TossStage::Unk1:\n      sStateData->profileMode |= 0x60;\n      break;\n   case GX2TossStage::Unk2:\n      sStateData->profileMode |= 0x40;\n      break;\n   case GX2TossStage::Unk7:\n      sStateData->profileMode |= 0x90;\n      break;\n   case GX2TossStage::Unk8:\n      sStateData->profileMode |= 0x10;\n      break;\n   }\n\n   sStateData->profilingEnabled[0] = true;\n   sStateData->profilingEnabled[1] = true;\n   sStateData->profilingEnabled[2] = true;\n}\n\nGX2ProfileMode\ngetProfileMode()\n{\n   return sStateData->profileMode;\n}\n\nGX2TossStage\ngetTossStage()\n{\n   return sStateData->tossStage;\n}\n\nBOOL\ngetProfilingEnabled()\n{\n   return sStateData->profilingEnabled[cpu::this_core::id()];\n}\n\nvoid\nsetProfilingEnabled(BOOL enabled)\n{\n   sStateData->profilingEnabled[cpu::this_core::id()] = enabled;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerStateSymbols()\n{\n   RegisterFunctionExport(GX2Init);\n   RegisterFunctionExport(GX2Shutdown);\n   RegisterFunctionExport(GX2GetMainCoreId);\n   RegisterFunctionExport(GX2Flush);\n   RegisterFunctionExport(GX2GetGPUTimeout);\n   RegisterFunctionExport(GX2SetGPUTimeout);\n   RegisterFunctionExport(GX2GetMiscParam);\n   RegisterFunctionExport(GX2SetMiscParam);\n   RegisterFunctionExport(GX2SetSpecialState);\n\n   RegisterDataInternal(sStateData);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_state.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2Init(virt_ptr<GX2InitAttrib> attributes);\n\nvoid\nGX2Shutdown();\n\nint32_t\nGX2GetMainCoreId();\n\nvoid\nGX2Flush();\n\nuint32_t\nGX2GetGPUTimeout();\n\nvoid\nGX2SetGPUTimeout(uint32_t timeout);\n\nuint32_t\nGX2GetMiscParam(GX2MiscParam param);\n\nBOOL\nGX2SetMiscParam(GX2MiscParam param,\n                uint32_t value);\n\nvoid\nGX2SetSpecialState(GXSpecialState state,\n                   BOOL enabled);\n\nnamespace internal\n{\n\nvoid\nenableStateShadowing();\n\nvoid\ndisableStateShadowing();\n\nbool\nisInitialised();\n\nuint32_t\ngetMainCoreId();\n\nvoid\nsetMainCore();\n\nvoid\ninitialiseProfiling(GX2ProfileMode profileMode,\n                    GX2TossStage tossStage);\n\nGX2ProfileMode\ngetProfileMode();\n\nGX2TossStage\ngetTossStage();\n\nBOOL\ngetProfilingEnabled();\n\nvoid\nsetProfilingEnabled(BOOL enabled);\n\n} // namespace internal\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_surface.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_addrlib.h\"\n#include \"gx2_enum_string.h\"\n#include \"gx2_format.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_surface.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <common/align.h>\n#include <common/log.h>\n#include <common/pow.h>\n#include <gsl/gsl-lite.hpp>\n#include <libgpu/gpu_tiling.h>\n#include <libgpu/latte/latte_enum_sq.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace cafe::coreinit;\n\nstatic uint32_t\ncalcNumLevelsForSize(uint32_t size)\n{\n   return 32 - clz(size);\n}\n\nstatic uint32_t\ncalcNumLevels(virt_ptr<GX2Surface> surface)\n{\n   if (surface->mipLevels <= 1) {\n      return 1;\n   }\n\n   auto levels = std::max(\n      calcNumLevelsForSize(surface->width),\n      calcNumLevelsForSize(surface->height));\n\n   if (surface->dim == GX2SurfaceDim::Texture3D) {\n      levels = std::max(levels, calcNumLevelsForSize(surface->depth));\n   }\n\n   return levels;\n}\n\nstatic void\ncomputeAuxInfo(virt_ptr<GX2ColorBuffer> colorBuffer,\n               virt_ptr<uint32_t> outSize,\n               virt_ptr<uint32_t> outAlignment,\n               bool updateRegisters)\n{\n   // TODO: Implement once we have AddrComputeCmaskInfo, AddrComputeFmaskInfo\n   if (outSize) {\n      *outSize = 2048u;\n   }\n   if (outAlignment) {\n      *outAlignment = 2048u;\n   }\n}\n\nvoid\nGX2CalcSurfaceSizeAndAlignment(virt_ptr<GX2Surface> surface)\n{\n   ADDR_COMPUTE_SURFACE_INFO_OUTPUT output;\n   auto isDepthBuffer = !!(surface->use & GX2SurfaceUse::DepthBuffer);\n   auto isColorBuffer = !!(surface->use & GX2SurfaceUse::ColorBuffer);\n   auto tileModeChanged = false;\n   auto isBuggedTilemode = (surface->tileMode == GX2TileMode::DefaultBadAlign);\n\n   if (surface->tileMode == GX2TileMode::Default ||\n       surface->tileMode == GX2TileMode::DefaultBadAlign) {\n      if (surface->dim || surface->aa || isDepthBuffer) {\n         if (surface->dim != GX2SurfaceDim::Texture3D || isColorBuffer) {\n            surface->tileMode = GX2TileMode::Tiled2DThin1;\n         } else {\n            surface->tileMode = GX2TileMode::Tiled2DThick;\n         }\n\n         tileModeChanged = true;\n      } else {\n         surface->tileMode = GX2TileMode::LinearAligned;\n      }\n   }\n\n   surface->mipLevels = std::max<uint32_t>(surface->mipLevels, 1u);\n   surface->mipLevels = std::min<uint32_t>(surface->mipLevels, calcNumLevels(surface));\n\n   surface->mipLevelOffset[0] = 0u;\n   surface->swizzle &= 0xFF00FFFF;\n\n   if (surface->tileMode >= GX2TileMode::Tiled2DThin1 &&\n       surface->tileMode != GX2TileMode::LinearSpecial) {\n      surface->swizzle |= 0xD0000;\n   }\n\n   auto buggedAlignShiftFix = 0u;\n   if (isBuggedTilemode && GX2SurfaceIsCompressed(surface->format)) {\n      buggedAlignShiftFix = 2u;\n   }\n\n   auto lastTileMode = static_cast<GX2TileMode>(surface->tileMode);\n   auto prevSize = 0u;\n   auto offset0 = 0u;\n\n   for (auto level = 0u; level < surface->mipLevels; ++level) {\n      internal::getSurfaceInfo(surface.get(), level, &output);\n\n      if (level) {\n         auto pad = 0u;\n\n         if (lastTileMode >= GX2TileMode::Tiled2DThin1 &&\n             lastTileMode != GX2TileMode::LinearSpecial) {\n            if (output.tileMode < ADDR_TM_2D_TILED_THIN1) {\n               surface->swizzle = (level << 16) | (surface->swizzle & 0xFF00FFFF);\n               lastTileMode = static_cast<GX2TileMode>(output.tileMode);\n\n               if (level > 1) {\n                  pad = surface->swizzle & 0xFFFF;\n               }\n            }\n         }\n\n         pad += (output.baseAlign - (prevSize % output.baseAlign)) % output.baseAlign;\n\n         if (level == 1) {\n            offset0 = pad + prevSize;\n         } else {\n            surface->mipLevelOffset[level - 1] =\n               pad + prevSize + surface->mipLevelOffset[level - 2];\n         }\n      } else {\n         if (tileModeChanged) {\n            if (surface->tileMode != output.tileMode) {\n               surface->tileMode = static_cast<GX2TileMode>(output.tileMode);\n\n               internal::getSurfaceInfo(surface.get(), level, &output);\n               if (surface->tileMode < GX2TileMode::Tiled2DThin1 ||\n                   surface->tileMode == GX2TileMode::LinearSpecial) {\n                  surface->swizzle &= 0xFF00FFFF;\n               }\n               lastTileMode = surface->tileMode;\n            }\n\n            if (surface->width < (output.pitchAlign << buggedAlignShiftFix) &&\n                surface->height < (output.heightAlign << buggedAlignShiftFix)) {\n               if (surface->tileMode == GX2TileMode::Tiled2DThick) {\n                  surface->tileMode = GX2TileMode::Tiled1DThick;\n               } else {\n                  surface->tileMode = GX2TileMode::Tiled1DThin1;\n               }\n\n               internal::getSurfaceInfo(surface.get(), level, &output);\n               surface->swizzle &= 0xFF00FFFF;\n               lastTileMode = surface->tileMode;\n            }\n         }\n\n         surface->imageSize = static_cast<uint32_t>(output.surfSize);\n         surface->alignment = output.baseAlign;\n         surface->pitch = output.pitch;\n      }\n\n      prevSize = static_cast<uint32_t>(output.surfSize);\n   }\n\n   if (surface->mipLevels <= 1) {\n      surface->mipmapSize = 0u;\n   } else {\n      surface->mipmapSize =\n         prevSize + surface->mipLevelOffset[surface->mipLevels - 2];\n   }\n\n   surface->mipLevelOffset[0] = offset0;\n\n   if (surface->format == GX2SurfaceFormat::UNORM_NV12) {\n      auto pad = (surface->alignment - surface->imageSize % surface->alignment) % surface->alignment;\n      surface->mipLevelOffset[0] = pad + surface->imageSize;\n      surface->imageSize = surface->mipLevelOffset[0] + (surface->imageSize >> 1);\n   }\n}\n\nvoid\nGX2CalcDepthBufferHiZInfo(virt_ptr<GX2DepthBuffer> depthBuffer,\n                          virt_ptr<uint32_t> outSize,\n                          virt_ptr<uint32_t> outAlignment)\n{\n   ADDR_COMPUTE_HTILE_INFO_INPUT input;\n   ADDR_COMPUTE_HTILE_INFO_OUTPUT output;\n\n   std::memset(&input, 0, sizeof(ADDR_COMPUTE_HTILE_INFO_INPUT));\n   std::memset(&output, 0, sizeof(ADDR_COMPUTE_HTILE_INFO_OUTPUT));\n\n   input.size = sizeof(ADDR_COMPUTE_HTILE_INFO_INPUT);\n   output.size = sizeof(ADDR_COMPUTE_HTILE_INFO_OUTPUT);\n\n   input.pitch = depthBuffer->surface.pitch;\n   input.height = depthBuffer->surface.height;\n   input.numSlices = depthBuffer->surface.depth;\n   input.blockWidth = ADDR_HTILE_BLOCKSIZE_8;\n   input.blockHeight = ADDR_HTILE_BLOCKSIZE_8;\n   AddrComputeHtileInfo(gpu::getAddrLibHandle(), &input, &output);\n\n   depthBuffer->hiZSize = gsl::narrow_cast<uint32_t>(output.htileBytes);\n\n   if (outSize) {\n      *outSize = depthBuffer->hiZSize;\n   }\n\n   if (outAlignment) {\n      *outAlignment = output.baseAlign;\n   }\n}\n\nvoid\nGX2CalcColorBufferAuxInfo(virt_ptr<GX2ColorBuffer> colorBuffer,\n                          virt_ptr<uint32_t> outSize,\n                          virt_ptr<uint32_t> outAlignment)\n{\n   decaf_warn_stub();\n   computeAuxInfo(colorBuffer, outSize, outAlignment, false);\n   colorBuffer->aaSize = *outSize;\n}\n\nvoid\nGX2SetColorBuffer(virt_ptr<GX2ColorBuffer> colorBuffer,\n                  GX2RenderTarget target)\n{\n   using latte::Register;\n   auto reg = [](unsigned id) { return static_cast<Register>(id); };\n   auto cb_color_info = colorBuffer->regs.cb_color_info.value();\n   auto cb_color_mask = colorBuffer->regs.cb_color_mask.value();\n   auto cb_color_size = colorBuffer->regs.cb_color_size.value();\n   auto cb_color_view = colorBuffer->regs.cb_color_view.value();\n\n   auto addr = static_cast<uint32_t>(\n      OSEffectiveToPhysical(virt_cast<virt_addr>(colorBuffer->surface.image)));\n   auto addrTile = 0u;\n   auto addrFrag = 0u;\n\n   if (colorBuffer->viewMip) {\n      addr = static_cast<uint32_t>(\n         OSEffectiveToPhysical(virt_cast<virt_addr>(colorBuffer->surface.mipmaps)));\n\n      if (colorBuffer->viewMip > 1) {\n         addr += colorBuffer->surface.mipLevelOffset[colorBuffer->viewMip - 1];\n      }\n   }\n\n   if (colorBuffer->surface.tileMode >= GX2TileMode::Tiled2DThin1 &&\n       colorBuffer->surface.tileMode != GX2TileMode::LinearSpecial) {\n      if (colorBuffer->viewMip < ((colorBuffer->surface.swizzle >> 16) & 0xFF)) {\n         addr ^= colorBuffer->surface.swizzle & 0xFFFF;\n      }\n   }\n\n   if (colorBuffer->surface.aa) {\n      addrFrag = static_cast<uint32_t>(\n         OSEffectiveToPhysical(virt_cast<virt_addr>(colorBuffer->aaBuffer)));\n      addrTile = addrFrag + colorBuffer->regs.cmask_offset;\n   }\n\n   internal::writePM4(latte::pm4::SetContextReg {\n      reg(Register::CB_COLOR0_BASE + target * 4),\n      addr >> 8\n   });\n\n   internal::writePM4(latte::pm4::SetContextReg {\n      reg(Register::CB_COLOR0_SIZE + target * 4),\n      cb_color_size.value\n   });\n\n   internal::writePM4(latte::pm4::SetContextReg {\n      reg(Register::CB_COLOR0_INFO + target * 4),\n      cb_color_info.value\n   });\n\n   internal::writePM4(latte::pm4::SetContextReg {\n      reg(Register::CB_COLOR0_TILE + target * 4),\n      addrTile >> 8\n   });\n\n   internal::writePM4(latte::pm4::SetContextReg {\n      reg(Register::CB_COLOR0_FRAG + target * 4),\n      addrFrag >> 8\n   });\n\n   internal::writePM4(latte::pm4::SetContextReg {\n      reg(Register::CB_COLOR0_VIEW + target * 4),\n      cb_color_view.value\n   });\n\n   internal::writePM4(latte::pm4::SetContextReg {\n      reg(Register::CB_COLOR0_MASK + target * 4),\n      cb_color_mask.value\n   });\n}\n\nvoid\nGX2SetDepthBuffer(virt_ptr<GX2DepthBuffer> depthBuffer)\n{\n   auto db_depth_info = depthBuffer->regs.db_depth_info.value();\n   auto db_depth_size = depthBuffer->regs.db_depth_size.value();\n   auto db_depth_view = depthBuffer->regs.db_depth_view.value();\n   auto db_htile_surface = depthBuffer->regs.db_htile_surface.value();\n   auto db_prefetch_limit = depthBuffer->regs.db_prefetch_limit.value();\n   auto db_preload_control = depthBuffer->regs.db_preload_control.value();\n   auto pa_poly_offset_cntl = depthBuffer->regs.pa_poly_offset_cntl.value();\n\n   uint32_t values1[] = {\n      db_depth_size.value,\n      db_depth_view.value,\n   };\n   internal::writePM4(latte::pm4::SetContextRegs { latte::Register::DB_DEPTH_SIZE, gsl::make_span(values1) });\n\n   auto addr = OSEffectiveToPhysical(virt_cast<virt_addr>(depthBuffer->surface.image));\n   auto addrHiZ = OSEffectiveToPhysical(virt_cast<virt_addr>(depthBuffer->hiZPtr));\n\n   if (depthBuffer->viewMip) {\n      addr = OSEffectiveToPhysical(virt_cast<virt_addr>(depthBuffer->surface.mipmaps));\n\n      if (depthBuffer->viewMip > 1) {\n         addr += depthBuffer->surface.mipLevelOffset[depthBuffer->viewMip - 1];\n      }\n   }\n\n   if (depthBuffer->surface.tileMode >= GX2TileMode::Tiled2DThin1 && depthBuffer->surface.tileMode != GX2TileMode::LinearSpecial) {\n      if (depthBuffer->viewMip < ((depthBuffer->surface.swizzle >> 16) & 0xFF)) {\n         addr ^= depthBuffer->surface.swizzle & 0xFFFF;\n      }\n   }\n\n   uint32_t values2[] = {\n      addr >> 8,\n      db_depth_info.value,\n      addrHiZ >> 8,\n   };\n   internal::writePM4(latte::pm4::SetContextRegs { latte::Register::DB_DEPTH_BASE, gsl::make_span(values2) });\n\n   internal::writePM4(latte::pm4::SetContextReg { latte::Register::DB_HTILE_SURFACE, db_htile_surface.value });\n   internal::writePM4(latte::pm4::SetContextReg { latte::Register::DB_PREFETCH_LIMIT, db_prefetch_limit.value });\n   internal::writePM4(latte::pm4::SetContextReg { latte::Register::DB_PRELOAD_CONTROL, db_preload_control.value });\n   internal::writePM4(latte::pm4::SetContextReg { latte::Register::PA_SU_POLY_OFFSET_DB_FMT_CNTL, pa_poly_offset_cntl.value });\n\n   uint32_t values3[] = {\n      depthBuffer->stencilClear,\n      bit_cast<uint32_t, float>(depthBuffer->depthClear),\n   };\n   internal::writePM4(latte::pm4::SetContextRegs { latte::Register::DB_STENCIL_CLEAR, gsl::make_span(values3) });\n}\n\nvoid\nGX2InitColorBufferRegs(virt_ptr<GX2ColorBuffer> colorBuffer)\n{\n   auto surfaceFormat = colorBuffer->surface.format;\n   auto surfaceFormatType = internal::getSurfaceFormatType(surfaceFormat);\n\n   auto output = ADDR_COMPUTE_SURFACE_INFO_OUTPUT { };\n   internal::getSurfaceInfo(virt_addrof(colorBuffer->surface).get(),\n                            colorBuffer->viewMip,\n                            &output);\n\n   // cb_color_size\n   auto pitchTileMax = (output.pitch / latte::MicroTileWidth) - 1;\n   auto sliceTileMax = ((output.pitch * output.height) / (latte::MicroTileWidth * latte::MicroTileHeight)) - 1;\n\n   colorBuffer->regs.cb_color_size = latte::CB_COLORN_SIZE::get(0)\n      .PITCH_TILE_MAX(pitchTileMax)\n      .SLICE_TILE_MAX(sliceTileMax);\n\n   // cb_color_info\n   auto cbFormat = internal::getSurfaceFormatColorFormat(colorBuffer->surface.format);\n   auto cbCompSwap = latte::CB_COMP_SWAP::STD;\n   if (cbFormat == latte::CB_FORMAT::COLOR_5_5_5_1 ||\n       cbFormat == latte::CB_FORMAT::COLOR_10_10_10_2) {\n      cbCompSwap = latte::CB_COMP_SWAP::STD_REV;\n   }\n\n   auto cbNumberType = internal::getSurfaceFormatColorNumberType(surfaceFormat);\n   auto cbTileMode = latte::CB_TILE_MODE::DISABLE;\n   if (colorBuffer->surface.aa) {\n      cbTileMode = latte::CB_TILE_MODE::FRAG_ENABLE;\n   }\n\n   auto blendBypass = false;\n   if (surfaceFormatType == GX2SurfaceFormatType::UINT ||\n       surfaceFormat == GX2SurfaceFormat::UNORM_R24_X8 ||\n       surfaceFormat == GX2SurfaceFormat::FLOAT_D24_S8 ||\n       surfaceFormat == GX2SurfaceFormat::FLOAT_X8_X24) {\n      blendBypass = true;\n   }\n\n   auto cbRoundMode = latte::CB_ROUND_MODE::BY_HALF;\n   if (surfaceFormatType == GX2SurfaceFormatType::FLOAT) {\n      cbRoundMode = latte::CB_ROUND_MODE::TRUNCATE;\n   }\n\n   auto cbSourceFormat = internal::getSurfaceFormatColorSourceFormat(surfaceFormat);\n   if (surfaceFormatType == GX2SurfaceFormatType::UINT) {\n      cbSourceFormat = latte::CB_SOURCE_FORMAT::EXPORT_NORM;\n   }\n\n   colorBuffer->regs.cb_color_info = latte::CB_COLORN_INFO::get(0)\n      .ENDIAN(internal::getSurfaceFormatColorEndian(surfaceFormat))\n      .FORMAT(cbFormat)\n      .NUMBER_TYPE(cbNumberType)\n      .ARRAY_MODE(static_cast<latte::BUFFER_ARRAY_MODE>(output.tileMode))\n      .COMP_SWAP(cbCompSwap)\n      .TILE_MODE(cbTileMode)\n      .BLEND_BYPASS(blendBypass)\n      .ROUND_MODE(cbRoundMode)\n      .SOURCE_FORMAT(cbSourceFormat);\n\n   colorBuffer->regs.cb_color_mask = latte::CB_COLORN_MASK::get(0);\n\n   if (colorBuffer->surface.tileMode == GX2TileMode::LinearSpecial) {\n      colorBuffer->regs.cb_color_view = latte::CB_COLORN_VIEW::get(0);\n   } else {\n      colorBuffer->regs.cb_color_view = latte::CB_COLORN_VIEW::get(0)\n         .SLICE_START(colorBuffer->viewFirstSlice)\n         .SLICE_MAX(colorBuffer->viewFirstSlice + colorBuffer->viewNumSlices - 1);\n   }\n\n   if (colorBuffer->surface.aa) {\n      computeAuxInfo(colorBuffer, nullptr, nullptr, true);\n   }\n}\n\nvoid\nGX2InitDepthBufferRegs(virt_ptr<GX2DepthBuffer> depthBuffer)\n{\n   ADDR_COMPUTE_SURFACE_INFO_OUTPUT surfaceInfo;\n   internal::getSurfaceInfo(virt_addrof(depthBuffer->surface).get(),\n                            depthBuffer->viewMip,\n                            &surfaceInfo);\n\n   // db_depth_size\n   auto pitchTileMax = (surfaceInfo.pitch / latte::MicroTileWidth) - 1;\n   auto sliceTileMax = ((surfaceInfo.pitch * surfaceInfo.height) / (latte::MicroTileWidth * latte::MicroTileHeight)) - 1;\n\n   depthBuffer->regs.db_depth_size = latte::DB_DEPTH_SIZE::get(0)\n      .PITCH_TILE_MAX(pitchTileMax)\n      .SLICE_TILE_MAX(sliceTileMax);\n\n   // db_depth_view\n   depthBuffer->regs.db_depth_view = latte::DB_DEPTH_VIEW::get(0)\n      .SLICE_START(depthBuffer->viewFirstSlice)\n      .SLICE_MAX(depthBuffer->viewFirstSlice + depthBuffer->viewNumSlices - 1);\n\n   depthBuffer->regs.db_htile_surface = latte::DB_HTILE_SURFACE::get(0)\n      .HTILE_WIDTH(true)\n      .HTILE_HEIGHT(true)\n      .FULL_CACHE(true);\n\n   depthBuffer->regs.db_prefetch_limit = latte::DB_PREFETCH_LIMIT::get(0)\n      .DEPTH_HEIGHT_TILE_MAX(((depthBuffer->surface.height / 8) - 1) & 0x3FF);\n\n   depthBuffer->regs.db_preload_control = latte::DB_PRELOAD_CONTROL::get(0)\n      .MAX_X(static_cast<uint8_t>(depthBuffer->surface.width / 32))\n      .MAX_Y(static_cast<uint8_t>(depthBuffer->surface.height / 32));\n\n   auto db_depth_info = latte::DB_DEPTH_INFO::get(0)\n      .READ_SIZE(latte::BUFFER_READ_SIZE::READ_512_BITS)\n      .ARRAY_MODE(static_cast<latte::BUFFER_ARRAY_MODE>(surfaceInfo.tileMode))\n      .TILE_SURFACE_ENABLE(depthBuffer->hiZPtr != nullptr);\n\n   auto pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0);\n\n   switch (depthBuffer->surface.format) {\n   case GX2SurfaceFormat::UNORM_R16:\n      db_depth_info = db_depth_info\n         .FORMAT(latte::DB_FORMAT::DEPTH_16);\n      pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0)\n         .POLY_OFFSET_NEG_NUM_DB_BITS(240);\n      break;\n   case GX2SurfaceFormat::UNORM_R24_X8:\n      db_depth_info = db_depth_info\n         .FORMAT(latte::DB_FORMAT::DEPTH_8_24);\n      pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0)\n         .POLY_OFFSET_NEG_NUM_DB_BITS(232);\n      break;\n   case GX2SurfaceFormat::FLOAT_R32:\n      db_depth_info = db_depth_info\n         .FORMAT(latte::DB_FORMAT::DEPTH_32_FLOAT);\n      pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0)\n         .POLY_OFFSET_NEG_NUM_DB_BITS(233)\n         .POLY_OFFSET_DB_IS_FLOAT_FMT(true);\n      break;\n   case GX2SurfaceFormat::FLOAT_D24_S8:\n      db_depth_info = db_depth_info\n         .FORMAT(latte::DB_FORMAT::DEPTH_8_24_FLOAT);\n      pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0)\n         .POLY_OFFSET_NEG_NUM_DB_BITS(236)\n         .POLY_OFFSET_DB_IS_FLOAT_FMT(true);\n      break;\n   case GX2SurfaceFormat::FLOAT_X8_X24:\n      db_depth_info = db_depth_info\n         .FORMAT(latte::DB_FORMAT::DEPTH_X24_8_32_FLOAT);\n      pa_poly_offset_cntl = latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL::get(0)\n         .POLY_OFFSET_NEG_NUM_DB_BITS(233)\n         .POLY_OFFSET_DB_IS_FLOAT_FMT(true);\n      break;\n   default:\n      db_depth_info = db_depth_info\n         .FORMAT(latte::DB_FORMAT::DEPTH_INVALID);\n   }\n\n   depthBuffer->regs.db_depth_info = db_depth_info;\n   depthBuffer->regs.pa_poly_offset_cntl = pa_poly_offset_cntl;\n}\n\nvoid\nGX2InitDepthBufferHiZEnable(virt_ptr<GX2DepthBuffer> depthBuffer,\n                            BOOL enable)\n{\n   auto db_depth_info = depthBuffer->regs.db_depth_info.value();\n\n   db_depth_info = db_depth_info\n      .TILE_SURFACE_ENABLE(!!enable);\n\n   depthBuffer->regs.db_depth_info = db_depth_info;\n}\n\nuint32_t\nGX2GetSurfaceSwizzle(virt_ptr<GX2Surface> surface)\n{\n   return (surface->swizzle >> 8) & 0xff;\n}\n\nuint32_t\nGX2GetSurfaceSwizzleOffset(virt_ptr<GX2Surface> surface,\n                           uint32_t level)\n{\n   if (surface->tileMode < GX2TileMode::Tiled2DThin1 || surface->tileMode == GX2TileMode::LinearSpecial) {\n      return 0;\n   }\n\n   if (level < ((surface->swizzle >> 16) & 0xFF)) {\n      return 0;\n   }\n\n   return surface->swizzle & 0xFFFF;\n}\n\nvoid\nGX2SetSurfaceSwizzle(virt_ptr<GX2Surface> surface,\n                     uint32_t swizzle)\n{\n   surface->swizzle &= 0xFFFF00FF;\n   surface->swizzle |= swizzle << 8;\n}\n\nuint32_t\nGX2GetSurfaceMipPitch(virt_ptr<GX2Surface> surface,\n                      uint32_t level)\n{\n   ADDR_COMPUTE_SURFACE_INFO_OUTPUT info;\n   internal::getSurfaceInfo(surface.get(), level, &info);\n   return info.pitch;\n}\n\nuint32_t\nGX2GetSurfaceMipSliceSize(virt_ptr<GX2Surface> surface,\n                          uint32_t level)\n{\n   ADDR_COMPUTE_SURFACE_INFO_OUTPUT info;\n   internal::getSurfaceInfo(surface.get(), level, &info);\n   return internal::calcSliceSize(surface.get(), &info);\n}\n\nvoid\nGX2CopySurface(virt_ptr<GX2Surface> src,\n               uint32_t srcLevel,\n               uint32_t srcSlice,\n               virt_ptr<GX2Surface> dst,\n               uint32_t dstLevel,\n               uint32_t dstSlice)\n{\n   if (src->format == GX2SurfaceFormat::INVALID || src->width == 0 || src->height == 0) {\n      return;\n   }\n\n   if (dst->format == GX2SurfaceFormat::INVALID) {\n      return;\n   }\n\n   if (src->tileMode == GX2TileMode::LinearSpecial ||\n       dst->tileMode == GX2TileMode::LinearSpecial)\n   {\n      // LinearSpecial surfaces cause the copy to occur on the CPU.  This code\n      //  assumes that if the texture was previously written by the GPU, that it\n      //  has since been invalidated into CPU memory.\n      gx2::internal::copySurface(src.get(), srcLevel, srcSlice,\n                                 dst.get(), dstLevel, dstSlice);\n      return;\n   }\n\n   auto dstTileType = latte::SQ_TILE_TYPE::DEFAULT;\n   if (dst->use & GX2SurfaceUse::DepthBuffer) {\n      dstTileType = latte::SQ_TILE_TYPE::DEPTH;\n   }\n\n   auto dstDim = static_cast<latte::SQ_TEX_DIM>(dst->dim.value());\n   auto dstFormat = static_cast<latte::SQ_DATA_FORMAT>(dst->format & 0x3f);\n   auto dstTileMode = static_cast<latte::SQ_TILE_MODE>(dst->tileMode.value());\n   auto dstFormatComp = latte::SQ_FORMAT_COMP::UNSIGNED;\n   auto dstNumFormat = latte::SQ_NUM_FORMAT::NORM;\n   auto dstForceDegamma = false;\n   auto dstPitch = dst->pitch;\n   auto dstDepth = dst->depth;\n   auto dstSamples = 0u;\n\n   if (dst->format & GX2AttribFormatFlags::SIGNED) {\n      dstFormatComp = latte::SQ_FORMAT_COMP::SIGNED;\n   }\n\n   if (dst->format & GX2AttribFormatFlags::SCALED) {\n      dstNumFormat = latte::SQ_NUM_FORMAT::SCALED;\n   } else if (dst->format & GX2AttribFormatFlags::INTEGER) {\n      dstNumFormat = latte::SQ_NUM_FORMAT::INT;\n   }\n\n   if (dst->format & GX2AttribFormatFlags::DEGAMMA) {\n      dstForceDegamma = true;\n   }\n\n   if (dstFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && dstFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      dstPitch *= 4;\n   }\n\n   if (dstDim == latte::SQ_TEX_DIM::DIM_CUBEMAP) {\n      dstDepth /= 6;\n   }\n\n   if (dst->aa == GX2AAMode::Mode2X) {\n      dstSamples = 2;\n   } else if (dst->aa == GX2AAMode::Mode4X) {\n      dstSamples = 4;\n   } else if (dst->aa == GX2AAMode::Mode8X) {\n      dstSamples = 8;\n   }\n\n   auto srcTileType = latte::SQ_TILE_TYPE::DEFAULT;\n   if (src->use & GX2SurfaceUse::DepthBuffer) {\n      srcTileType = latte::SQ_TILE_TYPE::DEPTH;\n   }\n\n   auto srcDim = static_cast<latte::SQ_TEX_DIM>(src->dim.value());\n   auto srcFormat = static_cast<latte::SQ_DATA_FORMAT>(src->format & 0x3f);\n   auto srcTileMode = static_cast<latte::SQ_TILE_MODE>(src->tileMode.value());\n   auto srcFormatComp = latte::SQ_FORMAT_COMP::UNSIGNED;\n   auto srcNumFormat = latte::SQ_NUM_FORMAT::NORM;\n   auto srcForceDegamma = false;\n   auto srcPitch = src->pitch;\n   auto srcDepth = src->depth;\n   auto srcSamples = 0u;\n\n   if (src->format & GX2AttribFormatFlags::SIGNED) {\n      srcFormatComp = latte::SQ_FORMAT_COMP::SIGNED;\n   }\n\n   if (src->format & GX2AttribFormatFlags::SCALED) {\n      srcNumFormat = latte::SQ_NUM_FORMAT::SCALED;\n   } else if (src->format & GX2AttribFormatFlags::INTEGER) {\n      srcNumFormat = latte::SQ_NUM_FORMAT::INT;\n   }\n\n   if (src->format & GX2AttribFormatFlags::DEGAMMA) {\n      srcForceDegamma = true;\n   }\n\n   if (srcFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && srcFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      srcPitch *= 4;\n   }\n\n   if (srcDim == latte::SQ_TEX_DIM::DIM_CUBEMAP) {\n      srcDepth /= 6;\n   }\n\n   if (src->aa == GX2AAMode::Mode2X) {\n      srcSamples = 2;\n   } else if (src->aa == GX2AAMode::Mode4X) {\n      srcSamples = 4;\n   } else if (src->aa == GX2AAMode::Mode8X) {\n      srcSamples = 8;\n   }\n\n   internal::writePM4(latte::pm4::DecafCopySurface {\n      OSEffectiveToPhysical(virt_cast<virt_addr>(dst->image)),\n      OSEffectiveToPhysical(virt_cast<virt_addr>(dst->mipmaps)),\n      dstLevel,\n      dstSlice,\n      dstPitch,\n      dst->width,\n      dst->height,\n      dstDepth,\n      dstSamples,\n      dstDim,\n      dstFormat,\n      dstNumFormat,\n      dstFormatComp,\n      dstForceDegamma ? 1u : 0u,\n      dstTileType,\n      dstTileMode,\n      OSEffectiveToPhysical(virt_cast<virt_addr>(src->image)),\n      OSEffectiveToPhysical(virt_cast<virt_addr>(src->mipmaps)),\n      srcLevel,\n      srcSlice,\n      srcPitch,\n      src->width,\n      src->height,\n      srcDepth,\n      srcSamples,\n      srcDim,\n      srcFormat,\n      srcNumFormat,\n      srcFormatComp,\n      srcForceDegamma ? 1u : 0u,\n      srcTileType,\n      srcTileMode\n   });\n}\n\nvoid\nGX2ExpandColorBuffer(virt_ptr<GX2ColorBuffer> buffer)\n{\n   // TODO: GX2ExpandColorBuffer\n   decaf_warn_stub();\n}\n\nvoid\nGX2ExpandDepthBuffer(virt_ptr<GX2DepthBuffer> buffer)\n{\n   // TODO: GX2ExpandDepthBuffer\n   decaf_warn_stub();\n}\n\nvoid\nGX2ResolveAAColorBuffer(virt_ptr<GX2ColorBuffer> src,\n                        virt_ptr<GX2Surface> dst,\n                        uint32_t dstLevel,\n                        uint32_t dstSlice)\n{\n   if (src->surface.format == GX2SurfaceFormat::INVALID || src->surface.width == 0 || src->surface.height == 0) {\n      return;\n   }\n\n   if (dst->format == GX2SurfaceFormat::INVALID) {\n      return;\n   }\n\n   auto dstTileType = latte::SQ_TILE_TYPE::DEFAULT;\n   auto dstDim = static_cast<latte::SQ_TEX_DIM>(dst->dim.value());\n   auto dstFormat = static_cast<latte::SQ_DATA_FORMAT>(dst->format & 0x3f);\n   auto dstTileMode = static_cast<latte::SQ_TILE_MODE>(dst->tileMode.value());\n   auto dstFormatComp = latte::SQ_FORMAT_COMP::UNSIGNED;\n   auto dstNumFormat = latte::SQ_NUM_FORMAT::NORM;\n   auto dstForceDegamma = false;\n   auto dstPitch = dst->pitch;\n   auto dstDepth = dst->depth;\n   auto dstSamples = 0u;\n\n   if (dst->format & GX2AttribFormatFlags::SIGNED) {\n      dstFormatComp = latte::SQ_FORMAT_COMP::SIGNED;\n   }\n\n   if (dst->format & GX2AttribFormatFlags::SCALED) {\n      dstNumFormat = latte::SQ_NUM_FORMAT::SCALED;\n   } else if (dst->format & GX2AttribFormatFlags::INTEGER) {\n      dstNumFormat = latte::SQ_NUM_FORMAT::INT;\n   }\n\n   if (dst->format & GX2AttribFormatFlags::DEGAMMA) {\n      dstForceDegamma = true;\n   }\n\n   if (dstFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && dstFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      dstPitch *= 4;\n   }\n\n   if (dstDim == latte::SQ_TEX_DIM::DIM_CUBEMAP) {\n      dstDepth /= 6;\n   }\n\n   if (dst->aa == GX2AAMode::Mode2X) {\n      dstSamples = 2;\n   } else if (dst->aa == GX2AAMode::Mode4X) {\n      dstSamples = 4;\n   } else if (dst->aa == GX2AAMode::Mode8X) {\n      dstSamples = 8;\n   }\n\n   auto srcTileType = latte::SQ_TILE_TYPE::DEFAULT;\n   auto srcDim = static_cast<latte::SQ_TEX_DIM>(src->surface.dim.value());\n   auto srcFormat = static_cast<latte::SQ_DATA_FORMAT>(src->surface.format & 0x3f);\n   auto srcTileMode = static_cast<latte::SQ_TILE_MODE>(src->surface.tileMode.value());\n   auto srcFormatComp = latte::SQ_FORMAT_COMP::UNSIGNED;\n   auto srcNumFormat = latte::SQ_NUM_FORMAT::NORM;\n   auto srcForceDegamma = false;\n   auto srcPitch = src->surface.pitch;\n   auto srcDepth = src->surface.depth;\n   auto srcSamples = 0u;\n\n   if (src->surface.format & GX2AttribFormatFlags::SIGNED) {\n      srcFormatComp = latte::SQ_FORMAT_COMP::SIGNED;\n   }\n\n   if (src->surface.format & GX2AttribFormatFlags::SCALED) {\n      srcNumFormat = latte::SQ_NUM_FORMAT::SCALED;\n   } else if (src->surface.format & GX2AttribFormatFlags::INTEGER) {\n      srcNumFormat = latte::SQ_NUM_FORMAT::INT;\n   }\n\n   if (src->surface.format & GX2AttribFormatFlags::DEGAMMA) {\n      srcForceDegamma = true;\n   }\n\n   if (srcFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && srcFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      srcPitch *= 4;\n   }\n\n   if (srcDim == latte::SQ_TEX_DIM::DIM_CUBEMAP) {\n      srcDepth /= 6;\n   }\n\n   if (src->surface.aa == GX2AAMode::Mode2X) {\n      srcSamples = 2;\n   } else if (src->surface.aa == GX2AAMode::Mode4X) {\n      srcSamples = 4;\n   } else if (src->surface.aa == GX2AAMode::Mode8X) {\n      srcSamples = 8;\n   }\n\n   internal::writePM4(latte::pm4::DecafExpandColorBuffer {\n      OSEffectiveToPhysical(virt_cast<virt_addr>(dst->image)),\n      OSEffectiveToPhysical(virt_cast<virt_addr>(dst->mipmaps)),\n      dstLevel,\n      dstSlice,\n      dstPitch,\n      dst->width,\n      dst->height,\n      dstDepth,\n      dstSamples,\n      dstDim,\n      dstFormat,\n      dstNumFormat,\n      dstFormatComp,\n      dstForceDegamma ? 1u : 0u,\n      dstTileType,\n      dstTileMode,\n      OSEffectiveToPhysical(virt_cast<virt_addr>(src->surface.image)),\n      OSEffectiveToPhysical(virt_cast<virt_addr>(src->aaBuffer)),\n      OSEffectiveToPhysical(virt_cast<virt_addr>(src->surface.mipmaps)),\n      src->viewMip,\n      src->viewFirstSlice,\n      srcPitch,\n      src->surface.width,\n      src->surface.height,\n      srcDepth,\n      srcSamples,\n      srcDim,\n      srcFormat,\n      srcNumFormat,\n      srcFormatComp,\n      srcForceDegamma ? 1u : 0u,\n      srcTileType,\n      srcTileMode,\n      src->viewNumSlices\n   });\n}\n\nvoid\nLibrary::registerSurfaceSymbols()\n{\n   RegisterFunctionExport(GX2InitDepthBufferHiZEnable);\n   RegisterFunctionExport(GX2InitDepthBufferRegs);\n   RegisterFunctionExport(GX2InitColorBufferRegs);\n   RegisterFunctionExport(GX2SetDepthBuffer);\n   RegisterFunctionExport(GX2SetColorBuffer);\n   RegisterFunctionExport(GX2CalcColorBufferAuxInfo);\n   RegisterFunctionExport(GX2CalcDepthBufferHiZInfo);\n   RegisterFunctionExport(GX2CalcSurfaceSizeAndAlignment);\n   RegisterFunctionExport(GX2GetSurfaceSwizzle);\n   RegisterFunctionExport(GX2GetSurfaceSwizzleOffset);\n   RegisterFunctionExport(GX2SetSurfaceSwizzle);\n   RegisterFunctionExport(GX2GetSurfaceMipPitch);\n   RegisterFunctionExport(GX2GetSurfaceMipSliceSize);\n   RegisterFunctionExport(GX2CopySurface);\n   RegisterFunctionExport(GX2ExpandColorBuffer);\n   RegisterFunctionExport(GX2ExpandDepthBuffer);\n   RegisterFunctionExport(GX2ResolveAAColorBuffer);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_surface.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libgpu/latte/latte_registers.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_surface Surface\n * \\ingroup gx2\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct GX2Surface\n{\n   be2_val<GX2SurfaceDim> dim;\n   be2_val<uint32_t> width;\n   be2_val<uint32_t> height;\n   be2_val<uint32_t> depth;\n   be2_val<uint32_t> mipLevels;\n   be2_val<GX2SurfaceFormat> format;\n   be2_val<GX2AAMode> aa;\n   union\n   {\n      be2_val<GX2SurfaceUse> use;\n      be2_val<GX2RResourceFlags> resourceFlags;\n   };\n   be2_val<uint32_t> imageSize;\n   be2_virt_ptr<uint8_t> image;\n   be2_val<uint32_t> mipmapSize;\n   be2_virt_ptr<uint8_t> mipmaps;\n   be2_val<GX2TileMode> tileMode;\n   be2_val<uint32_t> swizzle;\n   be2_val<uint32_t> alignment;\n   be2_val<uint32_t> pitch;\n   be2_array<uint32_t, 13> mipLevelOffset;\n};\nCHECK_OFFSET(GX2Surface, 0x00, dim);\nCHECK_OFFSET(GX2Surface, 0x04, width);\nCHECK_OFFSET(GX2Surface, 0x08, height);\nCHECK_OFFSET(GX2Surface, 0x0C, depth);\nCHECK_OFFSET(GX2Surface, 0x10, mipLevels);\nCHECK_OFFSET(GX2Surface, 0x14, format);\nCHECK_OFFSET(GX2Surface, 0x18, aa);\nCHECK_OFFSET(GX2Surface, 0x1C, use);\nCHECK_OFFSET(GX2Surface, 0x1C, resourceFlags);\nCHECK_OFFSET(GX2Surface, 0x20, imageSize);\nCHECK_OFFSET(GX2Surface, 0x24, image);\nCHECK_OFFSET(GX2Surface, 0x28, mipmapSize);\nCHECK_OFFSET(GX2Surface, 0x2C, mipmaps);\nCHECK_OFFSET(GX2Surface, 0x30, tileMode);\nCHECK_OFFSET(GX2Surface, 0x34, swizzle);\nCHECK_OFFSET(GX2Surface, 0x38, alignment);\nCHECK_OFFSET(GX2Surface, 0x3C, pitch);\nCHECK_OFFSET(GX2Surface, 0x40, mipLevelOffset);\nCHECK_SIZE(GX2Surface, 0x74);\n\nstruct GX2DepthBuffer\n{\n   be2_struct<GX2Surface> surface;\n   be2_val<uint32_t> viewMip;\n   be2_val<uint32_t> viewFirstSlice;\n   be2_val<uint32_t> viewNumSlices;\n   be2_virt_ptr<void> hiZPtr;\n   be2_val<uint32_t> hiZSize;\n   be2_val<float> depthClear;\n   be2_val<uint32_t> stencilClear;\n\n   struct\n   {\n      be2_val<latte::DB_DEPTH_SIZE> db_depth_size;\n      be2_val<latte::DB_DEPTH_VIEW> db_depth_view;\n      be2_val<latte::DB_DEPTH_INFO> db_depth_info;\n      be2_val<latte::DB_HTILE_SURFACE> db_htile_surface;\n      be2_val<latte::DB_PREFETCH_LIMIT> db_prefetch_limit;\n      be2_val<latte::DB_PRELOAD_CONTROL> db_preload_control;\n      be2_val<latte::PA_SU_POLY_OFFSET_DB_FMT_CNTL> pa_poly_offset_cntl;\n   } regs;\n};\nCHECK_OFFSET(GX2DepthBuffer, 0x00, surface);\nCHECK_OFFSET(GX2DepthBuffer, 0x74, viewMip);\nCHECK_OFFSET(GX2DepthBuffer, 0x78, viewFirstSlice);\nCHECK_OFFSET(GX2DepthBuffer, 0x7C, viewNumSlices);\nCHECK_OFFSET(GX2DepthBuffer, 0x80, hiZPtr);\nCHECK_OFFSET(GX2DepthBuffer, 0x84, hiZSize);\nCHECK_OFFSET(GX2DepthBuffer, 0x88, depthClear);\nCHECK_OFFSET(GX2DepthBuffer, 0x8C, stencilClear);\nCHECK_OFFSET(GX2DepthBuffer, 0x90, regs.db_depth_size);\nCHECK_OFFSET(GX2DepthBuffer, 0x94, regs.db_depth_view);\nCHECK_OFFSET(GX2DepthBuffer, 0x98, regs.db_depth_info);\nCHECK_OFFSET(GX2DepthBuffer, 0x9C, regs.db_htile_surface);\nCHECK_OFFSET(GX2DepthBuffer, 0xA0, regs.db_prefetch_limit);\nCHECK_OFFSET(GX2DepthBuffer, 0xA4, regs.db_preload_control);\nCHECK_OFFSET(GX2DepthBuffer, 0xA8, regs.pa_poly_offset_cntl);\nCHECK_SIZE(GX2DepthBuffer, 0xAC);\n\nstruct GX2ColorBuffer\n{\n   be2_struct<GX2Surface> surface;\n   be2_val<uint32_t> viewMip;\n   be2_val<uint32_t> viewFirstSlice;\n   be2_val<uint32_t> viewNumSlices;\n   be2_virt_ptr<void> aaBuffer;\n   be2_val<uint32_t> aaSize;\n\n   struct\n   {\n      be2_val<latte::CB_COLORN_SIZE> cb_color_size;\n      be2_val<latte::CB_COLORN_INFO> cb_color_info;\n      be2_val<latte::CB_COLORN_VIEW> cb_color_view;\n      be2_val<latte::CB_COLORN_MASK> cb_color_mask;\n      be2_val<uint32_t> cmask_offset;\n   } regs;\n};\nCHECK_OFFSET(GX2ColorBuffer, 0x00, surface);\nCHECK_OFFSET(GX2ColorBuffer, 0x74, viewMip);\nCHECK_OFFSET(GX2ColorBuffer, 0x78, viewFirstSlice);\nCHECK_OFFSET(GX2ColorBuffer, 0x7C, viewNumSlices);\nCHECK_OFFSET(GX2ColorBuffer, 0x80, aaBuffer);\nCHECK_OFFSET(GX2ColorBuffer, 0x84, aaSize);\nCHECK_OFFSET(GX2ColorBuffer, 0x88, regs.cb_color_size);\nCHECK_OFFSET(GX2ColorBuffer, 0x8C, regs.cb_color_info);\nCHECK_OFFSET(GX2ColorBuffer, 0x90, regs.cb_color_view);\nCHECK_OFFSET(GX2ColorBuffer, 0x94, regs.cb_color_mask);\nCHECK_OFFSET(GX2ColorBuffer, 0x98, regs.cmask_offset);\nCHECK_SIZE(GX2ColorBuffer, 0x9C);\n\n#pragma pack(pop)\n\nvoid\nGX2CalcSurfaceSizeAndAlignment(virt_ptr<GX2Surface> surface);\n\nvoid\nGX2CalcDepthBufferHiZInfo(virt_ptr<GX2DepthBuffer> depthBuffer,\n                          virt_ptr<uint32_t> outSize,\n                          virt_ptr<uint32_t> outAlignment);\n\nvoid\nGX2CalcColorBufferAuxInfo(virt_ptr<GX2ColorBuffer> colorBuffer,\n                          virt_ptr<uint32_t> outSize,\n                          virt_ptr<uint32_t> outAlignment);\n\nvoid\nGX2SetColorBuffer(virt_ptr<GX2ColorBuffer> colorBuffer,\n                  GX2RenderTarget target);\n\nvoid\nGX2SetDepthBuffer(virt_ptr<GX2DepthBuffer> depthBuffer);\n\nvoid\nGX2InitColorBufferRegs(virt_ptr<GX2ColorBuffer> colorBuffer);\n\nvoid\nGX2InitDepthBufferRegs(virt_ptr<GX2DepthBuffer> depthBuffer);\n\nvoid\nGX2InitDepthBufferHiZEnable(virt_ptr<GX2DepthBuffer> depthBuffer,\n                            BOOL enable);\n\nuint32_t\nGX2GetSurfaceSwizzle(virt_ptr<GX2Surface> surface);\n\nuint32_t\nGX2GetSurfaceSwizzleOffset(virt_ptr<GX2Surface> surface,\n                           uint32_t level);\n\nvoid\nGX2SetSurfaceSwizzle(virt_ptr<GX2Surface> surface,\n                     uint32_t swizzle);\n\nuint32_t\nGX2GetSurfaceMipPitch(virt_ptr<GX2Surface> surface,\n                      uint32_t level);\n\nuint32_t\nGX2GetSurfaceMipSliceSize(virt_ptr<GX2Surface> surface,\n                          uint32_t level);\n\nvoid\nGX2CopySurface(virt_ptr<GX2Surface> src,\n               uint32_t srcLevel,\n               uint32_t srcSlice,\n               virt_ptr<GX2Surface> dst,\n               uint32_t dstLevel,\n               uint32_t dstSlice);\n\nvoid\nGX2ExpandDepthBuffer(virt_ptr<GX2DepthBuffer> buffer);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_temp.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_temp.h\"\n\nnamespace cafe::gx2\n{\n\nuint32_t\nGX2TempGetGPUVersion()\n{\n   return 2;\n}\n\nvoid\nLibrary::registerTempSymbols()\n{\n   RegisterFunctionExport(GX2TempGetGPUVersion);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_temp.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace cafe::gx2\n{\n\nuint32_t\nGX2TempGetGPUVersion();\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_tessellation.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_tessellation.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2SetTessellation(GX2TessellationMode tessellationMode,\n                   GX2PrimitiveMode primitiveMode,\n                   GX2IndexType indexType)\n{\n   decaf_warn_stub();\n   // TODO: Set registers 0xA285, 0xA289, 0xA28A, 0xA28B, 0xA28C, 0xA28E, 0xA28D, 0xA28F\n}\n\nvoid\nGX2SetMinTessellationLevel(float min)\n{\n   auto vgt_hos_min_tess_level = latte::VGT_HOS_MIN_TESS_LEVEL::get(0);\n\n   vgt_hos_min_tess_level = vgt_hos_min_tess_level\n      .MIN_TESS(min);\n\n   internal::writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_HOS_MIN_TESS_LEVEL,\n      vgt_hos_min_tess_level.value\n   });\n}\n\nvoid\nGX2SetMaxTessellationLevel(float max)\n{\n   auto vgt_hos_max_tess_level = latte::VGT_HOS_MAX_TESS_LEVEL::get(0);\n\n   vgt_hos_max_tess_level = vgt_hos_max_tess_level\n      .MAX_TESS(max);\n\n   internal::writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_HOS_MAX_TESS_LEVEL,\n      vgt_hos_max_tess_level.value\n   });\n}\n\nvoid\nLibrary::registerTessellationSymbols()\n{\n   RegisterFunctionExport(GX2SetTessellation);\n   RegisterFunctionExport(GX2SetMinTessellationLevel);\n   RegisterFunctionExport(GX2SetMaxTessellationLevel);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_tessellation.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2SetTessellation(GX2TessellationMode tessellationMode,\n                   GX2PrimitiveMode primitiveMode,\n                   GX2IndexType indexType);\n\nvoid\nGX2SetMinTessellationLevel(float min);\n\nvoid\nGX2SetMaxTessellationLevel(float max);\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_texture.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_debug.h\"\n#include \"gx2_format.h\"\n#include \"gx2_cbpool.h\"\n#include \"gx2_texture.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\n#include <common/decaf_assert.h>\n#include <libgpu/latte/latte_registers.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace cafe::coreinit;\n\nvoid\nGX2InitTextureRegs(virt_ptr<GX2Texture> texture)\n{\n   auto word0 = latte::SQ_TEX_RESOURCE_WORD0_N::get(0);\n   auto word1 = latte::SQ_TEX_RESOURCE_WORD1_N::get(0);\n   auto word4 = latte::SQ_TEX_RESOURCE_WORD4_N::get(0);\n   auto word5 = texture->regs.word5.value();\n   auto word6 = texture->regs.word6.value();\n\n   // Minimum values\n   if (!texture->viewNumMips) {\n      texture->viewNumMips = 1u;\n   }\n\n   if (!texture->viewNumSlices) {\n      texture->viewNumSlices = 1u;\n   }\n\n   if (!texture->surface.width) {\n      texture->surface.width = 1u;\n   }\n\n   if (!texture->surface.height) {\n      texture->surface.height = 1u;\n   }\n\n   if (!texture->surface.depth) {\n      texture->surface.depth = 1u;\n   }\n\n   if (!texture->surface.mipLevels) {\n      texture->surface.mipLevels = 1u;\n   }\n\n   // Word 0\n   auto tileType = 0;\n   auto format = static_cast<latte::SQ_DATA_FORMAT>(texture->surface.format & 0x3F);\n   auto pitch = texture->surface.pitch;\n\n   if (texture->surface.use & GX2SurfaceUse::DepthBuffer) {\n      tileType = 1;\n   }\n\n   if (format >= latte::SQ_DATA_FORMAT::FMT_BC1 && format <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      pitch *= 4;\n   }\n\n   pitch = std::max<uint32_t>(pitch, 8u);\n\n   word0 = word0\n      .DIM(static_cast<latte::SQ_TEX_DIM>(texture->surface.dim & 0x7))\n      .TILE_MODE(static_cast<latte::SQ_TILE_MODE>(texture->surface.tileMode.value()))\n      .TILE_TYPE(static_cast<latte::SQ_TILE_TYPE>(tileType))\n      .PITCH((pitch / 8) - 1)\n      .TEX_WIDTH(texture->surface.width - 1);\n\n   // Word 1\n   auto depth = 0u;\n\n   if (texture->surface.dim == GX2SurfaceDim::TextureCube) {\n      depth = (texture->surface.depth / 6) - 1;\n   } else if (texture->surface.dim == GX2SurfaceDim::Texture3D ||\n              texture->surface.dim == GX2SurfaceDim::Texture2DMSAAArray ||\n              texture->surface.dim == GX2SurfaceDim::Texture2DArray ||\n              texture->surface.dim == GX2SurfaceDim::Texture1DArray) {\n      depth = texture->surface.depth - 1;\n   }\n\n   word1 = word1\n      .TEX_HEIGHT(texture->surface.height - 1)\n      .TEX_DEPTH(depth)\n      .DATA_FORMAT(format);\n\n   // Word 4\n   auto formatComp = latte::SQ_FORMAT_COMP::UNSIGNED;\n   auto numFormat = latte::SQ_NUM_FORMAT::NORM;\n   auto forceDegamma = false;\n\n   if (texture->surface.format & GX2AttribFormatFlags::SIGNED) {\n      formatComp = latte::SQ_FORMAT_COMP::SIGNED;\n   }\n\n   if (texture->surface.format & GX2AttribFormatFlags::SCALED) {\n      numFormat = latte::SQ_NUM_FORMAT::SCALED;\n   } else if (texture->surface.format & GX2AttribFormatFlags::INTEGER) {\n      numFormat = latte::SQ_NUM_FORMAT::INT;\n   }\n\n   if (texture->surface.format & GX2AttribFormatFlags::DEGAMMA) {\n      forceDegamma = true;\n   }\n\n   auto endian = internal::getSurfaceFormatEndian(texture->surface.format);\n   auto dstSelX = static_cast<latte::SQ_SEL>((texture->compMap >> 24) & 0x7);\n   auto dstSelY = static_cast<latte::SQ_SEL>((texture->compMap >> 16) & 0x7);\n   auto dstSelZ = static_cast<latte::SQ_SEL>((texture->compMap >> 8) & 0x7);\n   auto dstSelW = static_cast<latte::SQ_SEL>(texture->compMap & 0x7);\n\n   word4 = word4\n      .FORMAT_COMP_X(formatComp)\n      .FORMAT_COMP_Y(formatComp)\n      .FORMAT_COMP_Z(formatComp)\n      .FORMAT_COMP_W(formatComp)\n      .NUM_FORMAT_ALL(numFormat)\n      .FORCE_DEGAMMA(forceDegamma)\n      .ENDIAN_SWAP(endian)\n      .REQUEST_SIZE(2)\n      .DST_SEL_X(dstSelX)\n      .DST_SEL_Y(dstSelY)\n      .DST_SEL_Z(dstSelZ)\n      .DST_SEL_W(dstSelW)\n      .BASE_LEVEL(texture->viewFirstMip);\n\n   // Word 5\n   auto yuvConv = 0u;\n\n   if (texture->surface.dim == GX2SurfaceDim::TextureCube && word1.TEX_DEPTH()) {\n      yuvConv = 1;\n   }\n\n   word5 = word5\n      .LAST_LEVEL(texture->viewFirstMip + texture->viewNumMips - 1)\n      .BASE_ARRAY(texture->viewFirstSlice)\n      .LAST_ARRAY(texture->viewFirstSlice + texture->viewNumSlices - 1)\n      .YUV_CONV(yuvConv);\n\n   // For MSAA textures, we overwrite the LAST_LEVEL field\n   if (texture->surface.aa) {\n      decaf_check(texture->surface.dim == GX2SurfaceDim::Texture2DMSAA || texture->surface.dim == GX2SurfaceDim::Texture2DMSAAArray);\n\n      if (texture->surface.aa == GX2AAMode::Mode2X) {\n         word5 = word5\n            .LAST_LEVEL(1);\n      } else if (texture->surface.aa == GX2AAMode::Mode4X) {\n         word5 = word5\n            .LAST_LEVEL(2);\n      } else if (texture->surface.aa == GX2AAMode::Mode8X) {\n         word5 = word5\n            .LAST_LEVEL(3);\n      }\n   }\n\n   // Word 6\n   word6 = word6\n      .MAX_ANISO_RATIO(4)\n      .PERF_MODULATION(7)\n      .TYPE(latte::SQ_TEX_VTX_TYPE::VALID_TEXTURE);\n\n   // Update big endian register in texture\n   texture->regs.word0 = word0;\n   texture->regs.word1 = word1;\n   texture->regs.word4 = word4;\n   texture->regs.word5 = word5;\n   texture->regs.word6 = word6;\n}\n\nstatic void\nsetTexture(virt_ptr<GX2Texture> texture,\n           latte::SQ_RES_OFFSET offset,\n           uint32_t unit)\n{\n   auto imageAddress = static_cast<uint32_t>(\n      OSEffectiveToPhysical(virt_cast<virt_addr>(texture->surface.image)));\n   auto mipAddress = static_cast<uint32_t>(\n      OSEffectiveToPhysical(virt_cast<virt_addr>(texture->surface.mipmaps)));\n\n   if (mipAddress & 0xff) {\n      decaf_check_warn_once(!\"Game used unaligned mip address\");\n   }\n\n   if (imageAddress & 0xff) {\n      decaf_check_warn_once(!\"Game used unaligned image address\");\n   }\n\n   if (texture->surface.tileMode >= GX2TileMode::Tiled2DThin1 &&\n       texture->surface.tileMode != GX2TileMode::LinearSpecial) {\n      if ((texture->surface.swizzle >> 16) & 0xFF) {\n         imageAddress ^= (texture->surface.swizzle & 0xFFFF);\n         mipAddress ^= (texture->surface.swizzle & 0xFFFF);\n      }\n   }\n\n   auto word2 = latte::SQ_TEX_RESOURCE_WORD2_N::get(imageAddress >> 8);\n   auto word3 = latte::SQ_TEX_RESOURCE_WORD3_N::get(mipAddress >> 8);\n\n   internal::writePM4(latte::pm4::SetTexResource {\n      (offset + unit) * 7,\n      texture->regs.word0,\n      texture->regs.word1,\n      word2,\n      word3,\n      texture->regs.word4,\n      texture->regs.word5,\n      texture->regs.word6,\n   });\n\n   internal::debugDumpTexture(texture);\n}\n\nvoid\nGX2SetPixelTexture(virt_ptr<GX2Texture> texture,\n                   uint32_t unit)\n{\n   setTexture(texture, latte::SQ_RES_OFFSET::PS_TEX_RESOURCE_0, unit);\n}\n\nvoid\nGX2SetVertexTexture(virt_ptr<GX2Texture> texture,\n                    uint32_t unit)\n{\n   setTexture(texture, latte::SQ_RES_OFFSET::VS_TEX_RESOURCE_0, unit);\n}\n\nvoid\nGX2SetGeometryTexture(virt_ptr<GX2Texture> texture,\n                      uint32_t unit)\n{\n   setTexture(texture, latte::SQ_RES_OFFSET::GS_TEX_RESOURCE_0, unit);\n}\n\nvoid\nLibrary::registerTextureSymbols()\n{\n   RegisterFunctionExport(GX2InitTextureRegs);\n   RegisterFunctionExport(GX2SetPixelTexture);\n   RegisterFunctionExport(GX2SetVertexTexture);\n   RegisterFunctionExport(GX2SetGeometryTexture);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2_texture.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include \"gx2_surface.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libgpu/latte/latte_registers.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2_texture Texture\n * \\ingroup gx2\n * @{\n */\n\nstruct GX2Sampler;\n\n#pragma pack(push, 1)\n\nstruct GX2Texture\n{\n   be2_struct<GX2Surface> surface;\n   be2_val<uint32_t> viewFirstMip;\n   be2_val<uint32_t> viewNumMips;\n   be2_val<uint32_t> viewFirstSlice;\n   be2_val<uint32_t> viewNumSlices;\n   be2_val<uint32_t> compMap;\n\n   struct\n   {\n      be2_val<latte::SQ_TEX_RESOURCE_WORD0_N> word0;\n      be2_val<latte::SQ_TEX_RESOURCE_WORD1_N> word1;\n      be2_val<latte::SQ_TEX_RESOURCE_WORD4_N> word4;\n      be2_val<latte::SQ_TEX_RESOURCE_WORD5_N> word5;\n      be2_val<latte::SQ_TEX_RESOURCE_WORD6_N> word6;\n   } regs;\n};\nCHECK_OFFSET(GX2Texture, 0x00, surface);\nCHECK_OFFSET(GX2Texture, 0x74, viewFirstMip);\nCHECK_OFFSET(GX2Texture, 0x78, viewNumMips);\nCHECK_OFFSET(GX2Texture, 0x7C, viewFirstSlice);\nCHECK_OFFSET(GX2Texture, 0x80, viewNumSlices);\nCHECK_OFFSET(GX2Texture, 0x84, compMap);\nCHECK_OFFSET(GX2Texture, 0x88, regs.word0);\nCHECK_OFFSET(GX2Texture, 0x8C, regs.word1);\nCHECK_OFFSET(GX2Texture, 0x90, regs.word4);\nCHECK_OFFSET(GX2Texture, 0x94, regs.word5);\nCHECK_OFFSET(GX2Texture, 0x98, regs.word6);\nCHECK_SIZE(GX2Texture, 0x9c);\n\n#pragma pack(pop)\n\nvoid\nGX2InitTextureRegs(virt_ptr<GX2Texture> texture);\n\nvoid\nGX2SetPixelTexture(virt_ptr<GX2Texture> texture,\n                   uint32_t unit);\n\nvoid\nGX2SetVertexTexture(virt_ptr<GX2Texture> texture,\n                    uint32_t unit);\n\nvoid\nGX2SetGeometryTexture(virt_ptr<GX2Texture> texture,\n                      uint32_t unit);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_buffer.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_debugcapture.h\"\n#include \"gx2_memory.h\"\n#include \"gx2_shaders.h\"\n#include \"gx2r_buffer.h\"\n#include \"gx2r_memory.h\"\n#include \"gx2r_resource.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_cache.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\n#include <common/align.h>\n#include <common/log.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace coreinit;\n\nuint32_t\nGX2RGetBufferAlignment(GX2RResourceFlags flags)\n{\n   return 256;\n}\n\nuint32_t\nGX2RGetBufferAllocationSize(virt_ptr<GX2RBuffer> buffer)\n{\n   return align_up(buffer->elemCount * buffer->elemSize, 64);\n}\n\nvoid\nGX2RSetBufferName(virt_ptr<GX2RBuffer> buffer,\n                  virt_ptr<const char> name)\n{\n   decaf_warn_stub();\n}\n\nBOOL\nGX2RBufferExists(virt_ptr<GX2RBuffer> buffer)\n{\n   return (buffer && buffer->buffer) ? TRUE : FALSE;\n}\n\nBOOL\nGX2RCreateBuffer(virt_ptr<GX2RBuffer> buffer)\n{\n   decaf_check(!buffer->buffer);\n\n   auto align = GX2RGetBufferAlignment(buffer->flags);\n   auto size = GX2RGetBufferAllocationSize(buffer);\n\n   buffer->flags &= ~GX2RResourceFlags::Locked;\n   buffer->flags |= GX2RResourceFlags::Gx2rAllocated;\n\n   buffer->buffer = gx2::internal::gx2rAlloc(buffer->flags, size, align);\n\n   if (!buffer->buffer) {\n      return FALSE;\n   }\n\n   GX2NotifyMemAlloc(buffer->buffer, size, align);\n\n   // Check if we need to invalidate the buffer\n   if ((buffer->flags & GX2RResourceFlags::UsageGpuWrite) ||\n       (buffer->flags & GX2RResourceFlags::UsageDmaWrite)) {\n      DCInvalidateRange(virt_cast<virt_addr>(buffer->buffer), size);\n   }\n\n   return TRUE;\n}\n\nBOOL\nGX2RCreateBufferUserMemory(virt_ptr<GX2RBuffer> buffer,\n                           virt_ptr<void> memory,\n                           uint32_t size)\n{\n   decaf_check(buffer);\n   decaf_check(memory);\n   buffer->buffer = memory;\n   buffer->flags &= ~GX2RResourceFlags::Locked;\n   buffer->flags &= ~GX2RResourceFlags::Gx2rAllocated;\n\n   // Check if we need to invalidate the buffer\n   if ((buffer->flags & GX2RResourceFlags::UsageGpuWrite) ||\n       (buffer->flags & GX2RResourceFlags::UsageDmaWrite)) {\n      DCInvalidateRange(virt_cast<virt_addr>(buffer->buffer),\n                        buffer->elemCount * buffer->elemSize);\n   }\n\n   GX2NotifyMemAlloc(buffer->buffer, size,\n                     GX2RGetBufferAlignment(buffer->flags));\n   return TRUE;\n}\n\nvoid\nGX2RDestroyBufferEx(virt_ptr<GX2RBuffer> buffer,\n                    GX2RResourceFlags flags)\n{\n   if (!buffer || !buffer->buffer) {\n      return;\n   }\n\n   flags = internal::getOptionFlags(flags);\n\n   if (!GX2RIsUserMemory(buffer->flags) && !(flags & GX2RResourceFlags::DestroyNoFree)) {\n      gx2::internal::gx2rFree(flags, buffer->buffer);\n      buffer->buffer = nullptr;\n   }\n\n   GX2NotifyMemFree(buffer->buffer);\n}\n\nvoid\nGX2RInvalidateBuffer(virt_ptr<GX2RBuffer> buffer,\n                     GX2RResourceFlags flags)\n{\n   flags = internal::getOptionFlags(flags);\n   flags = static_cast<GX2RResourceFlags>(flags | (buffer->flags & ~0xF80000));\n\n   GX2RInvalidateMemory(flags,\n                        buffer->buffer,\n                        buffer->elemSize * buffer->elemCount);\n}\n\nvirt_ptr<void>\nGX2RLockBufferEx(virt_ptr<GX2RBuffer> buffer,\n                 GX2RResourceFlags flags)\n{\n   flags = buffer->flags | internal::getOptionFlags(flags);\n\n   // Update buffer flags\n   buffer->flags |= GX2RResourceFlags::Locked;\n   buffer->flags |= flags & GX2RResourceFlags::LockedReadOnly;\n\n   // Check if we need to invalidate the buffer\n   if ((flags & GX2RResourceFlags::UsageGpuWrite) ||\n       (flags & GX2RResourceFlags::UsageDmaWrite)) {\n      if (!(flags & GX2RResourceFlags::DisableCpuInvalidate) &&\n          !(flags & GX2RResourceFlags::LockedReadOnly)) {\n         DCInvalidateRange(virt_cast<virt_addr>(buffer->buffer),\n                           buffer->elemCount * buffer->elemSize);\n      }\n   }\n\n   // Return buffer pointer\n   return buffer->buffer;\n}\n\nvoid\nGX2RUnlockBufferEx(virt_ptr<GX2RBuffer> buffer,\n                   GX2RResourceFlags flags)\n{\n   flags = internal::getOptionFlags(flags);\n\n   // Invalidate the GPU buffer only if it was not read only locked\n   if (!(buffer->flags & GX2RResourceFlags::LockedReadOnly)) {\n      GX2RInvalidateBuffer(buffer, flags);\n   }\n\n   // Update buffer flags\n   buffer->flags &= ~GX2RResourceFlags::Locked;\n   buffer->flags &= ~GX2RResourceFlags::LockedReadOnly;\n}\n\nvoid\nGX2RSetStreamOutBuffer(uint32_t index,\n                       virt_ptr<GX2OutputStream> stream)\n{\n   GX2SetStreamOutBuffer(index, stream);\n}\n\nvoid\nLibrary::registerGx2rBufferSymbols()\n{\n   RegisterFunctionExport(GX2RGetBufferAlignment);\n   RegisterFunctionExport(GX2RGetBufferAllocationSize);\n   RegisterFunctionExport(GX2RSetBufferName);\n   RegisterFunctionExport(GX2RBufferExists);\n   RegisterFunctionExport(GX2RCreateBuffer);\n   RegisterFunctionExport(GX2RCreateBufferUserMemory);\n   RegisterFunctionExport(GX2RDestroyBufferEx);\n   RegisterFunctionExport(GX2RInvalidateBuffer);\n   RegisterFunctionExport(GX2RLockBufferEx);\n   RegisterFunctionExport(GX2RUnlockBufferEx);\n   RegisterFunctionExport(GX2RSetStreamOutBuffer);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_buffer.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2r_buffer GX2R Buffer\n * \\ingroup gx2\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct GX2OutputStream;\n\nstruct GX2RBuffer\n{\n   be2_val<GX2RResourceFlags> flags;\n   be2_val<uint32_t> elemSize;\n   be2_val<uint32_t> elemCount;\n   be2_virt_ptr<void> buffer;\n};\nCHECK_SIZE(GX2RBuffer, 0x10);\nCHECK_OFFSET(GX2RBuffer, 0x00, flags);\nCHECK_OFFSET(GX2RBuffer, 0x04, elemSize);\nCHECK_OFFSET(GX2RBuffer, 0x08, elemCount);\nCHECK_OFFSET(GX2RBuffer, 0x0C, buffer);\n\n#pragma pack(pop)\n\nuint32_t\nGX2RGetBufferAlignment(GX2RResourceFlags flags);\n\nuint32_t\nGX2RGetBufferAllocationSize(virt_ptr<GX2RBuffer> buffer);\n\nvoid\nGX2RSetBufferName(virt_ptr<GX2RBuffer> buffer,\n                  virt_ptr<const char> name);\n\nBOOL\nGX2RBufferExists(virt_ptr<GX2RBuffer> buffer);\n\nBOOL\nGX2RCreateBuffer(virt_ptr<GX2RBuffer> buffer);\n\nBOOL\nGX2RCreateBufferUserMemory(virt_ptr<GX2RBuffer> buffer,\n                           virt_ptr<void> memory,\n                           uint32_t size);\n\nvoid\nGX2RDestroyBufferEx(virt_ptr<GX2RBuffer> buffer,\n                    GX2RResourceFlags flags);\n\nvoid\nGX2RInvalidateBuffer(virt_ptr<GX2RBuffer> buffer,\n                     GX2RResourceFlags flags);\n\nvirt_ptr<void>\nGX2RLockBufferEx(virt_ptr<GX2RBuffer> buffer,\n                 GX2RResourceFlags flags);\n\nvoid\nGX2RUnlockBufferEx(virt_ptr<GX2RBuffer> buffer,\n                   GX2RResourceFlags flags);\n\nvoid\nGX2RSetStreamOutBuffer(uint32_t index,\n                       virt_ptr<GX2OutputStream> stream);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_displaylist.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_displaylist.h\"\n#include \"gx2r_buffer.h\"\n#include \"gx2r_displaylist.h\"\n#include <common/decaf_assert.h>\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2RBeginDisplayListEx(virt_ptr<GX2RBuffer> displayList,\n                       uint32_t unused,\n                       GX2RResourceFlags flags)\n{\n   if (!displayList || !displayList->buffer) {\n      return;\n   }\n\n   GX2BeginDisplayListEx(displayList->buffer,\n                         displayList->elemCount * displayList->elemSize,\n                         TRUE);\n}\n\nuint32_t\nGX2REndDisplayList(virt_ptr<GX2RBuffer> displayList)\n{\n   auto size = GX2EndDisplayList(displayList->buffer);\n   decaf_check(size < (displayList->elemCount * displayList->elemSize));\n   return size;\n}\n\nvoid\nGX2RCallDisplayList(virt_ptr<GX2RBuffer> displayList,\n                    uint32_t size)\n{\n   if (!displayList || !displayList->buffer) {\n      return;\n   }\n\n   GX2CallDisplayList(displayList->buffer, size);\n}\n\nvoid\nGX2RDirectCallDisplayList(virt_ptr<GX2RBuffer> displayList,\n                          uint32_t size)\n{\n   if (!displayList || !displayList->buffer) {\n      return;\n   }\n\n   GX2DirectCallDisplayList(displayList->buffer, size);\n}\n\nvoid\nLibrary::registerGx2rDisplayListSymbols()\n{\n   RegisterFunctionExport(GX2RBeginDisplayListEx);\n   RegisterFunctionExport(GX2REndDisplayList);\n   RegisterFunctionExport(GX2RCallDisplayList);\n   RegisterFunctionExport(GX2RDirectCallDisplayList);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_displaylist.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include \"gx2r_buffer.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2r_aperture GX2R Display List\n * \\ingroup gx2\n * @{\n */\n\nvoid\nGX2RBeginDisplayListEx(virt_ptr<GX2RBuffer> displayList,\n                       uint32_t unused,\n                       GX2RResourceFlags flags);\n\nuint32_t\nGX2REndDisplayList(virt_ptr<GX2RBuffer> displayList);\n\nvoid\nGX2RCallDisplayList(virt_ptr<GX2RBuffer> displayList,\n                    uint32_t size);\n\nvoid\nGX2RDirectCallDisplayList(virt_ptr<GX2RBuffer> displayList,\n                          uint32_t size);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_draw.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_draw.h\"\n#include \"gx2r_buffer.h\"\n#include \"gx2r_draw.h\"\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2RSetAttributeBuffer(virt_ptr<GX2RBuffer> buffer,\n                       uint32_t index,\n                       uint32_t stride,\n                       uint32_t offset)\n{\n   GX2SetAttribBuffer(index,\n                      buffer->elemCount * buffer->elemSize,\n                      stride,\n                      virt_cast<uint8_t*>(buffer->buffer) + offset);\n}\n\nvoid\nGX2RDrawIndexed(GX2PrimitiveMode mode,\n                virt_ptr<GX2RBuffer> buffer,\n                GX2IndexType indexType,\n                uint32_t count,\n                uint32_t indexOffset,\n                uint32_t vertexOffset,\n                uint32_t numInstances)\n{\n   GX2DrawIndexedEx(mode,\n                    count,\n                    indexType,\n                    virt_cast<uint8_t *>(buffer->buffer) + indexOffset * buffer->elemSize,\n                    vertexOffset,\n                    numInstances);\n}\n\nvoid\nLibrary::registerGx2rDrawSymbols()\n{\n   RegisterFunctionExport(GX2RSetAttributeBuffer);\n   RegisterFunctionExport(GX2RDrawIndexed);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_draw.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2r_draw GX2R Draw\n * \\ingroup gx2\n * @{\n */\n\nstruct GX2RBuffer;\n\nvoid\nGX2RSetAttributeBuffer(virt_ptr<GX2RBuffer> buffer,\n                       uint32_t index,\n                       uint32_t stride,\n                       uint32_t offset);\n\nvoid\nGX2RDrawIndexed(GX2PrimitiveMode mode,\n                virt_ptr<GX2RBuffer> buffer,\n                GX2IndexType indexType,\n                uint32_t count,\n                uint32_t indexOffset,\n                uint32_t vertexOffset,\n                uint32_t numInstances);\n\n/** @{ */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_memory.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_memory.h\"\n#include \"gx2r_memory.h\"\n\nnamespace cafe::gx2\n{\n\nstatic GX2InvalidateMode\ngetInvalidateMode(GX2RResourceFlags flags)\n{\n   auto mode = 0u;\n\n   if (flags & GX2RResourceFlags::BindTexture) {\n      mode |= GX2InvalidateMode::Texture;\n   }\n\n   if (flags & GX2RResourceFlags::BindColorBuffer) {\n      mode |= GX2InvalidateMode::ColorBuffer;\n   }\n\n   if (flags & GX2RResourceFlags::BindDepthBuffer) {\n      mode |= GX2InvalidateMode::DepthBuffer;\n   }\n\n   if (flags & GX2RResourceFlags::BindScanBuffer) {\n      mode |= GX2InvalidateMode::ColorBuffer;\n      mode |= GX2InvalidateMode::DepthBuffer;\n   }\n\n   if (flags & GX2RResourceFlags::BindVertexBuffer) {\n      mode |= GX2InvalidateMode::AttributeBuffer;\n   }\n\n   if (flags & GX2RResourceFlags::BindIndexBuffer) {\n      mode |= GX2InvalidateMode::AttributeBuffer;\n   }\n\n   if (flags & GX2RResourceFlags::BindUniformBlock) {\n      mode |= GX2InvalidateMode::UniformBlock;\n   }\n\n   if (flags & GX2RResourceFlags::BindShaderProgram) {\n      mode |= GX2InvalidateMode::Shader;\n   }\n\n   if (flags & GX2RResourceFlags::BindStreamOutput) {\n      mode |= GX2InvalidateMode::StreamOutBuffer;\n   }\n\n   if (flags & GX2RResourceFlags::UsageCpuReadWrite) {\n      mode |= GX2InvalidateMode::CPU;\n   }\n\n   if (flags & GX2RResourceFlags::DisableCpuInvalidate) {\n      // Clear only the CPU bit\n      mode &= ~GX2InvalidateMode::CPU;\n   }\n\n   if (flags & GX2RResourceFlags::DisableGpuInvalidate) {\n      // Clear every bit except CPU\n      mode &= GX2InvalidateMode::CPU;\n   }\n\n   return static_cast<GX2InvalidateMode>(mode);\n}\n\nvoid\nGX2RInvalidateMemory(GX2RResourceFlags flags,\n                     virt_ptr<void> buffer,\n                     uint32_t size)\n{\n   GX2Invalidate(getInvalidateMode(flags), buffer, size);\n}\n\nvoid\nLibrary::registerGx2rMemorySymbols()\n{\n   RegisterFunctionExport(GX2RInvalidateMemory);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_memory.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2r_memory GX2R Memory\n * \\ingroup gx2\n * @{\n */\n\nvoid\nGX2RInvalidateMemory(GX2RResourceFlags flags,\n                     virt_ptr<void> buffer,\n                     uint32_t size);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_resource.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2r_resource.h\"\n#include \"cafe/libraries/coreinit/coreinit_memdefaultheap.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libcpu/state.h>\n\nnamespace cafe::gx2\n{\n\nusing namespace coreinit;\n\nstruct StaticGx2rResourceData\n{\n   be2_val<GX2RAllocFuncPtr> alloc;\n   be2_val<GX2RFreeFuncPtr> free;\n};\n\nstatic virt_ptr<StaticGx2rResourceData> sGx2rResourceData = nullptr;\nstatic GX2RAllocFuncPtr GX2RDefaultAlloc = nullptr;\nstatic GX2RFreeFuncPtr GX2RDefaultFree = nullptr;\n\nvoid\nGX2RSetAllocator(GX2RAllocFuncPtr allocFn,\n                 GX2RFreeFuncPtr freeFn)\n{\n   sGx2rResourceData->alloc = allocFn;\n   sGx2rResourceData->free = freeFn;\n}\n\nBOOL\nGX2RIsUserMemory(GX2RResourceFlags flags)\n{\n   return (flags & GX2RResourceFlags::Gx2rAllocated) ? FALSE : TRUE;\n}\n\nstatic virt_ptr<void>\ndefaultAlloc(GX2RResourceFlags flags,\n             uint32_t size,\n             uint32_t align)\n{\n   return MEMAllocFromDefaultHeapEx(size, align);\n}\n\nstatic void\ndefaultFree(GX2RResourceFlags flags,\n            virt_ptr<void> buffer)\n{\n   MEMFreeToDefaultHeap(buffer);\n}\n\nnamespace internal\n{\n\nGX2RResourceFlags\ngetOptionFlags(GX2RResourceFlags flags)\n{\n   // Allow flags in bits 19 to 23\n   return static_cast<GX2RResourceFlags>(flags & 0xF80000);\n}\n\nvirt_ptr<void>\ngx2rAlloc(GX2RResourceFlags flags,\n          uint32_t size,\n          uint32_t align)\n{\n   return cafe::invoke(cpu::this_core::state(),\n                       sGx2rResourceData->alloc,\n                       flags,\n                       size,\n                       align);\n}\n\nvoid\ngx2rFree(GX2RResourceFlags flags,\n         virt_ptr<void> buffer)\n{\n   cafe::invoke(cpu::this_core::state(),\n                sGx2rResourceData->free,\n                flags,\n                buffer);\n}\n\nGX2RAllocFuncPtr\ngetDefaultGx2rAlloc()\n{\n   return GX2RDefaultAlloc;\n}\n\nGX2RFreeFuncPtr\ngetDefaultGx2rFree()\n{\n   return GX2RDefaultFree;\n}\n\nvoid\ninitialiseGx2rAllocator()\n{\n   GX2RSetAllocator(GX2RDefaultAlloc, GX2RDefaultFree);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerGx2rResourceSymbols()\n{\n   RegisterFunctionExport(GX2RSetAllocator);\n   RegisterFunctionExport(GX2RIsUserMemory);\n\n   RegisterDataInternal(sGx2rResourceData);\n   RegisterFunctionInternal(defaultAlloc, GX2RDefaultAlloc);\n   RegisterFunctionInternal(defaultFree, GX2RDefaultFree);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_resource.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2r_resource GX2R Resource\n * \\ingroup gx2\n * @{\n */\n\nusing GX2RAllocFuncPtr = virt_func_ptr<\n   virt_ptr<void>(GX2RResourceFlags, uint32_t, uint32_t)\n>;\n\nusing GX2RFreeFuncPtr = virt_func_ptr<\n   void(GX2RResourceFlags, virt_ptr<void>)\n>;\n\nvoid\nGX2RSetAllocator(GX2RAllocFuncPtr allocFn,\n                 GX2RFreeFuncPtr freeFn);\n\nBOOL\nGX2RIsUserMemory(GX2RResourceFlags flags);\n\nnamespace internal\n{\n\nvoid\ninitialiseGx2rAllocator();\n\nGX2RResourceFlags\ngetOptionFlags(GX2RResourceFlags flags);\n\nvirt_ptr<void>\ngx2rAlloc(GX2RResourceFlags flags,\n          uint32_t size,\n          uint32_t align);\n\nvoid\ngx2rFree(GX2RResourceFlags flags,\n         virt_ptr<void> buffer);\n\nGX2RAllocFuncPtr\ngetDefaultGx2rAlloc();\n\nGX2RFreeFuncPtr\ngetDefaultGx2rFree();\n\n} // namespace internal\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_shaders.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2r_shaders.h\"\n#include \"gx2_shaders.h\"\n\nnamespace cafe::gx2\n{\n\nvoid\nGX2RSetVertexUniformBlock(virt_ptr<GX2RBuffer> buffer,\n                          uint32_t location,\n                          uint32_t offset)\n{\n   GX2SetVertexUniformBlock(location,\n                            (buffer->elemSize * buffer->elemCount) - offset,\n                            virt_cast<uint8_t *>(buffer->buffer) + offset);\n}\n\nvoid\nGX2RSetPixelUniformBlock(virt_ptr<GX2RBuffer> buffer,\n                         uint32_t location,\n                         uint32_t offset)\n{\n   GX2SetPixelUniformBlock(location,\n                           (buffer->elemSize * buffer->elemCount) - offset,\n                           virt_cast<uint8_t *>(buffer->buffer) + offset);\n}\n\nvoid\nGX2RSetGeometryUniformBlock(virt_ptr<GX2RBuffer> buffer,\n                            uint32_t location,\n                            uint32_t offset)\n{\n   GX2SetGeometryUniformBlock(location,\n                              (buffer->elemSize * buffer->elemCount) - offset,\n                              virt_cast<uint8_t *>(buffer->buffer) + offset);\n}\n\nvoid\nLibrary::registerGx2rShadersSymbols()\n{\n   RegisterFunctionExport(GX2RSetVertexUniformBlock);\n   RegisterFunctionExport(GX2RSetPixelUniformBlock);\n   RegisterFunctionExport(GX2RSetGeometryUniformBlock);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_shaders.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2r_shaders GX2R Shaders\n * \\ingroup gx2\n * @{\n */\n\nstruct GX2RBuffer;\n\nvoid\nGX2RSetVertexUniformBlock(virt_ptr<GX2RBuffer> buffer,\n                          uint32_t location,\n                          uint32_t offset);\n\nvoid\nGX2RSetPixelUniformBlock(virt_ptr<GX2RBuffer> buffer,\n                         uint32_t location,\n                         uint32_t offset);\n\nvoid\nGX2RSetGeometryUniformBlock(virt_ptr<GX2RBuffer> buffer,\n                            uint32_t location,\n                            uint32_t offset);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_surface.cpp",
    "content": "#include \"gx2.h\"\n#include \"gx2_surface.h\"\n#include \"gx2r_memory.h\"\n#include \"gx2r_resource.h\"\n#include \"gx2r_surface.h\"\n#include \"cafe/libraries/coreinit/coreinit_cache.h\"\n\nnamespace cafe::gx2\n{\n\nusing namespace coreinit;\n\nnamespace internal\n{\n\nstatic void\ngetSurfaceData(virt_ptr<GX2Surface> surface,\n               int32_t level,\n               virt_ptr<void> *addr,\n               uint32_t *size)\n{\n   if (level == 0) {\n      *addr = surface->image;\n      *size = surface->imageSize;\n   } else if (level == -1) {\n      *addr = surface->mipmaps;\n      *size = surface->mipmapSize;\n   } else {\n      decaf_check(level > 0);\n      auto curLevelOffset = 0u;\n      auto nextLevelOffset = 0u;\n\n      if (level > 1) {\n         curLevelOffset = surface->mipLevelOffset[level - 1];\n      }\n\n      if (static_cast<uint32_t>(level + 1) >= surface->mipLevels) {\n         nextLevelOffset = surface->mipmapSize;\n      } else {\n         nextLevelOffset = surface->mipLevelOffset[level];\n      }\n\n      *addr = surface->mipmaps + curLevelOffset;\n      *size = nextLevelOffset - curLevelOffset;\n   }\n}\n\n} // namespace internal\n\nBOOL\nGX2RCreateSurface(virt_ptr<GX2Surface> surface,\n                  GX2RResourceFlags flags)\n{\n   surface->resourceFlags = flags;\n   surface->resourceFlags &= ~GX2RResourceFlags::Locked;\n   surface->resourceFlags |= GX2RResourceFlags::Gx2rAllocated;\n   GX2CalcSurfaceSizeAndAlignment(surface);\n\n   auto buffer = internal::gx2rAlloc(surface->resourceFlags,\n                                     surface->imageSize + surface->mipmapSize,\n                                     surface->alignment);\n\n   surface->image = virt_cast<uint8_t *>(buffer);\n\n   if (!surface->image) {\n      return FALSE;\n   }\n\n   surface->mipmaps = nullptr;\n\n   if (surface->mipmapSize) {\n      surface->mipmaps = surface->image + surface->imageSize;\n   }\n\n   if ((surface->resourceFlags & GX2RResourceFlags::UsageGpuWrite) ||\n      (surface->resourceFlags & GX2RResourceFlags::UsageDmaWrite)) {\n      DCInvalidateRange(virt_cast<virt_addr>(surface->image),\n                        surface->imageSize);\n   }\n\n   return TRUE;\n}\n\nBOOL\nGX2RCreateSurfaceUserMemory(virt_ptr<GX2Surface> surface,\n                            virt_ptr<uint8_t> image,\n                            virt_ptr<uint8_t> mipmap,\n                            GX2RResourceFlags flags)\n{\n   GX2CalcSurfaceSizeAndAlignment(surface);\n   surface->resourceFlags = flags;\n   surface->resourceFlags &= ~GX2RResourceFlags::Locked;\n   surface->resourceFlags &= ~GX2RResourceFlags::Gx2rAllocated;\n   surface->image = image;\n   surface->mipmaps = mipmap;\n\n   if ((surface->resourceFlags & GX2RResourceFlags::UsageGpuWrite) ||\n      (surface->resourceFlags & GX2RResourceFlags::UsageDmaWrite)) {\n      DCInvalidateRange(virt_cast<virt_addr>(surface->image),\n                        surface->imageSize);\n\n      if (surface->mipmaps) {\n         DCInvalidateRange(virt_cast<virt_addr>(surface->mipmaps),\n                           surface->mipmapSize);\n      }\n   }\n\n   return true;\n}\n\nvoid\nGX2RDestroySurfaceEx(virt_ptr<GX2Surface> surface,\n                     GX2RResourceFlags flags)\n{\n   if (!surface || !surface->image) {\n      return;\n   }\n\n   flags = surface->resourceFlags | internal::getOptionFlags(flags);\n\n   if (!GX2RIsUserMemory(surface->resourceFlags)) {\n      gx2::internal::gx2rFree(flags, surface->image);\n   }\n\n   surface->image = nullptr;\n}\n\nvirt_ptr<void>\nGX2RLockSurfaceEx(virt_ptr<GX2Surface> surface,\n                  int32_t level,\n                  GX2RResourceFlags flags)\n{\n   decaf_check(surface);\n   decaf_check(surface->resourceFlags & ~GX2RResourceFlags::Locked);\n   flags = surface->resourceFlags | internal::getOptionFlags(flags);\n\n   // Set Locked flag\n   surface->resourceFlags |= GX2RResourceFlags::Locked;\n   surface->resourceFlags |= flags & GX2RResourceFlags::LockedReadOnly;\n\n   // Check if we need to invalidate the surface.\n   if ((flags & GX2RResourceFlags::UsageGpuWrite) ||\n      (flags & GX2RResourceFlags::UsageDmaWrite)) {\n      if (!(flags & GX2RResourceFlags::DisableCpuInvalidate)) {\n         auto ptr = virt_ptr<void> { nullptr };\n         auto size = uint32_t { 0 };\n         internal::getSurfaceData(surface, level, &ptr, &size);\n         DCInvalidateRange(virt_cast<virt_addr>(ptr), size);\n      }\n   }\n\n   return surface->image;\n}\n\nvoid\nGX2RUnlockSurfaceEx(virt_ptr<GX2Surface> surface,\n                    int32_t level,\n                    GX2RResourceFlags flags)\n{\n   decaf_check(surface);\n   decaf_check(surface->resourceFlags & GX2RResourceFlags::Locked);\n\n   // Invalidate surface\n   GX2RInvalidateSurface(surface, level, flags);\n\n   // Clear locked flags.\n   surface->resourceFlags &= ~GX2RResourceFlags::LockedReadOnly;\n   surface->resourceFlags &= ~GX2RResourceFlags::Locked;\n}\n\nBOOL\nGX2RIsGX2RSurface(GX2RResourceFlags flags)\n{\n   return (flags & (GX2RResourceFlags::UsageCpuReadWrite | GX2RResourceFlags::UsageGpuReadWrite)) ? TRUE : FALSE;\n}\n\nvoid\nGX2RInvalidateSurface(virt_ptr<GX2Surface> surface,\n                      int32_t level,\n                      GX2RResourceFlags flags)\n{\n   flags = internal::getOptionFlags(flags);\n\n   // Get surface ptr & size\n   auto ptr = virt_ptr<void> { nullptr };\n   auto size = uint32_t { 0 };\n   internal::getSurfaceData(surface, level, &ptr, &size);\n\n   // Invalidate!\n   GX2RInvalidateMemory(surface->resourceFlags | flags, ptr, size);\n}\n\nvoid\nLibrary::registerGx2rSurfaceSymbols()\n{\n   RegisterFunctionExport(GX2RCreateSurface);\n   RegisterFunctionExport(GX2RCreateSurfaceUserMemory);\n   RegisterFunctionExport(GX2RDestroySurfaceEx);\n   RegisterFunctionExport(GX2RLockSurfaceEx);\n   RegisterFunctionExport(GX2RUnlockSurfaceEx);\n   RegisterFunctionExport(GX2RIsGX2RSurface);\n   RegisterFunctionExport(GX2RInvalidateSurface);\n}\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/gx2/gx2r_surface.h",
    "content": "#pragma once\n#include \"gx2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::gx2\n{\n\n/**\n * \\defgroup gx2r_surface GX2R Surface\n * \\ingroup gx2\n * @{\n */\n\nstruct GX2Surface;\n\nBOOL\nGX2RCreateSurface(virt_ptr<GX2Surface> surface,\n                  GX2RResourceFlags flags);\n\nBOOL\nGX2RCreateSurfaceUserMemory(virt_ptr<GX2Surface> surface,\n                            virt_ptr<uint8_t> image,\n                            virt_ptr<uint8_t> mipmap,\n                            GX2RResourceFlags flags);\n\nvoid\nGX2RDestroySurfaceEx(virt_ptr<GX2Surface> surface,\n                     GX2RResourceFlags flags);\n\nvirt_ptr<void>\nGX2RLockSurfaceEx(virt_ptr<GX2Surface> surface,\n                  int32_t level,\n                  GX2RResourceFlags flags);\n\nvoid\nGX2RUnlockSurfaceEx(virt_ptr<GX2Surface> surface,\n                    int32_t level,\n                    GX2RResourceFlags flags);\n\nBOOL\nGX2RIsGX2RSurface(GX2RResourceFlags flags);\n\nvoid\nGX2RInvalidateSurface(virt_ptr<GX2Surface> surface,\n                      int32_t level,\n                      GX2RResourceFlags flags);\n\n/** @} */\n\n} // namespace cafe::gx2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264.cpp",
    "content": "#include \"h264.h\"\n\nnamespace cafe::h264\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerDecodeSymbols();\n   registerStreamSymbols();\n}\n\n} // namespace cafe::h264\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::h264\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::h264, \"h264.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerDecodeSymbols();\n   void registerStreamSymbols();\n};\n\n} // namespace cafe::h264\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264_bitstream.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace cafe::h264\n{\n\nclass BitStream\n{\npublic:\n   BitStream(const uint8_t *buffer,\n             size_t size) :\n      mBuffer(buffer),\n      mSize(size),\n      mBytePosition(0),\n      mBitPosition(0)\n   {\n   }\n\n   uint8_t peekU1()\n   {\n      if (eof()) {\n         return 0;\n      }\n\n      return static_cast<uint8_t>((mBuffer[mBytePosition] >> (7 - mBitPosition)) & 1);\n   }\n\n   uint8_t readU1()\n   {\n      if (eof()) {\n         return 0;\n      }\n\n      auto value = static_cast<uint8_t>((mBuffer[mBytePosition] >> (7 - mBitPosition)) & 1);\n\n      mBitPosition++;\n      if (mBitPosition == 8) {\n         mBitPosition = 0;\n         mBytePosition++;\n      }\n\n      return value;\n   }\n\n   uint32_t readU(size_t n)\n   {\n      auto value = uint32_t { 0 };\n      for (auto i = 0u; i < n; ++i) {\n         value |= readU1() << (n - i - 1);\n      }\n\n      return value;\n   }\n\n   uint8_t readU2()\n   {\n      return static_cast<uint8_t>(readU(2));\n   }\n\n   uint8_t readU3()\n   {\n      return static_cast<uint8_t>(readU(3));\n   }\n\n   uint8_t readU4()\n   {\n      return static_cast<uint8_t>(readU(4));\n   }\n\n   uint8_t readU5()\n   {\n      return static_cast<uint8_t>(readU(5));\n   }\n\n   uint8_t readU8()\n   {\n      if (eof()) {\n         return 0;\n      }\n\n      if (mBitPosition == 0) {\n         return mBuffer[mBytePosition++];\n      }\n\n      return static_cast<uint8_t>(readU(8));\n   }\n\n   uint16_t readU16()\n   {\n      return static_cast<uint16_t>(readU(16));\n   }\n\n   uint32_t readUE()\n   {\n      auto r = uint32_t { 0 };\n      auto i = 0;\n\n      while (readU1() == 0 && i < 32 && !eof()) {\n         i++;\n      }\n\n      r = readU(i);\n      r += (1 << i) - 1;\n      return r;\n   }\n\n   int32_t readSE()\n   {\n      auto r = static_cast<int32_t>(readUE());\n\n      if (r & 0x01) {\n         r = (r + 1) / 2;\n      } else {\n         r = -(r / 2);\n      }\n\n      return r;\n   }\n\n   bool byteAligned()\n   {\n      return mBitPosition == 0;\n   }\n\n   bool eof()\n   {\n      return mBytePosition >= mSize;\n   }\n\n   size_t bytesRead()\n   {\n      return mBytePosition;\n   }\n\nprivate:\n   const uint8_t *mBuffer;\n   size_t mSize;\n   size_t mBytePosition;\n   size_t mBitPosition;\n};\n\n} // namespace cafe::h264\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264_decode.cpp",
    "content": "#include \"h264.h\"\n#include \"h264_decode.h\"\n#include \"h264_decode_ffmpeg.h\"\n#include \"h264_decode_null.h\"\n#include \"h264_stream.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n\nnamespace cafe::h264\n{\n\nconstexpr auto BaseMemoryRequirement = 0x480000u;\nconstexpr auto WorkMemoryAlign = 0x100u;\n\nconstexpr auto MinimumWidth = 64u;\nconstexpr auto MinimumHeight = 64u;\nconstexpr auto MaximumWidth = 2800u;\nconstexpr auto MaximumHeight = 1408u;\n\nnamespace internal\n{\n\nvirt_ptr<H264WorkMemory>\ngetWorkMemory(virt_ptr<void> memory)\n{\n   auto workMemory = byte_swap<virt_addr>(*virt_cast<virt_addr *>(memory));\n   auto addrDiff = workMemory - virt_cast<virt_addr>(memory);\n   if (addrDiff > WorkMemoryAlign || addrDiff < 0) {\n      return nullptr;\n   }\n\n   return virt_cast<H264WorkMemory *>(workMemory);\n}\n\nstatic void\ninitialiseWorkMemory(virt_ptr<H264WorkMemory> workMemory,\n                     virt_addr alignedMemoryEnd)\n{\n   auto baseAddress = virt_cast<virt_addr>(workMemory);\n   workMemory->streamMemory = virt_cast<H264StreamMemory *>(baseAddress + 0x100u);\n   workMemory->unk0x10 = virt_cast<void *>(baseAddress + 0x439C00);\n   workMemory->bitStream = virt_cast<H264Bitstream *>(baseAddress + 0x439D00);\n   workMemory->database = virt_cast<void *>(baseAddress + 0x439E00);\n   workMemory->codecMemory = virt_cast<H264CodecMemory *>(baseAddress + 0x442300);\n   workMemory->l1Memory = virt_cast<void *>(baseAddress + 0x445F00);\n   workMemory->unk0x24 = virt_cast<void *>(baseAddress + 0x447F00);\n   workMemory->internalFrameMemory = virt_cast<void *>(baseAddress + 0x44FC00);\n\n   workMemory->streamMemory->workMemoryEnd =\n      virt_cast<void *>(alignedMemoryEnd);\n   workMemory->streamMemory->unkMemory =\n      virt_cast<void *>(alignedMemoryEnd - 0x450000 + 0x300);\n}\n\nstatic uint32_t\ngetLevelMemoryRequirement(int32_t level)\n{\n   switch (level) {\n   case 10:\n      return 0x63000;\n   case 11:\n      return 0xE1000;\n   case 12:\n   case 13:\n   case 20:\n      return 0x252000;\n   case 21:\n      return 0x4A4000;\n   case 22:\n   case 30:\n      return 0x7E9000;\n   case 31:\n      return 0x1194000;\n   case 32:\n      return 0x1400000;\n   case 40:\n   case 41:\n      return 0x2000000;\n   case 42:\n      return 0x2200000;\n   case 50:\n      return 0x6BD0000;\n   case 51:\n      return 0xB400000;\n   default:\n      decaf_abort(fmt::format(\"Unexpected H264 level {}\", level));\n   }\n}\n\n} // namespace internal\n\n\n/**\n * Calculate the amount of memory required for the specified parameters.\n */\nH264Error\nH264DECMemoryRequirement(int32_t profile,\n                         int32_t level,\n                         int32_t maxWidth,\n                         int32_t maxHeight,\n                         virt_ptr<uint32_t> outMemoryRequirement)\n{\n   if (maxWidth < MinimumWidth || maxHeight < MinimumHeight ||\n       maxWidth > MaximumWidth || maxHeight > MaximumHeight) {\n      return H264Error::InvalidParameter;\n   }\n\n   if (!outMemoryRequirement) {\n      return H264Error::InvalidParameter;\n   }\n\n   if (level > 51) {\n      return H264Error::InvalidParameter;\n   }\n\n   if (profile != 66 && profile != 77 && profile != 100) {\n      return H264Error::InvalidParameter;\n   }\n\n   *outMemoryRequirement =\n      BaseMemoryRequirement + internal::getLevelMemoryRequirement(level) + 1023u;\n   return H264Error::OK;\n}\n\n\n/**\n * Initialise a H264 decoder in the given memory.\n */\nH264Error\nH264DECInitParam(int32_t memorySize,\n                 virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   if (memorySize < 0x44F900) {\n      return H264Error::OutOfMemory;\n   }\n\n   std::memset(memory.get(), 0, memorySize);\n\n   // Calculate aligned memory\n   auto alignedMemory = align_up(memory, WorkMemoryAlign);\n   auto alignOffset =\n      virt_cast<virt_addr>(memory) - virt_cast<virt_addr>(alignedMemory);\n\n   auto alignedMemoryStart = virt_cast<virt_addr>(alignedMemory);\n   auto alignedMemoryEnd = alignedMemoryStart + memorySize - alignOffset;\n\n   // Write aligned memory start reversed to memory\n   *virt_cast<virt_addr *>(memory) = byte_swap(alignedMemoryStart);\n\n   // Initialise our memory\n   auto workMemory = virt_cast<H264WorkMemory *>(alignedMemoryStart);\n   internal::initialiseWorkMemory(workMemory, alignedMemoryEnd);\n\n   // TODO: More things.\n   return H264Error::OK;\n}\n\n\n/**\n * Set H264 decoder parameter.\n */\nH264Error\nH264DECSetParam(virt_ptr<void> memory,\n                H264Parameter parameter,\n                virt_ptr<void> value)\n{\n   if (!memory || !value) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto streamMemory = workMemory->streamMemory;\n\n   switch (parameter) {\n   case H264Parameter::FramePointerOutput:\n      streamMemory->paramFramePointerOutput =\n         virt_func_cast<H264DECFptrOutputFn>(virt_cast<virt_addr>(value));\n      break;\n   case H264Parameter::OutputPerFrame:\n      streamMemory->paramOutputPerFrame = *virt_cast<uint8_t *>(value);\n      break;\n   case H264Parameter::UserMemory:\n      streamMemory->paramUserMemory = value;\n      break;\n   case H264Parameter::Unknown0x20000030:\n      streamMemory->param_0x20000030 = *virt_cast<uint32_t *>(value);\n      break;\n   case H264Parameter::Unknown0x20000040:\n      streamMemory->param_0x20000040 = *virt_cast<uint32_t *>(value);\n      break;\n   case H264Parameter::Unknown0x20000010:\n      // TODO\n   default:\n      return H264Error::InvalidParameter;\n   }\n\n   return H264Error::OK;\n}\n\n\n/**\n * Set the callback which is called when a frame is output from the decoder.\n */\nH264Error\nH264DECSetParam_FPTR_OUTPUT(virt_ptr<void> memory,\n                            H264DECFptrOutputFn value)\n{\n   if (!memory || !value) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   workMemory->streamMemory->paramFramePointerOutput = value;\n   return H264Error::OK;\n}\n\n\n/**\n * Set whether the decoder should internally buffer frames or call the callback\n * immediately as soon as a frame is emitted.\n */\nH264Error\nH264DECSetParam_OUTPUT_PER_FRAME(virt_ptr<void> memory,\n                                 uint32_t value)\n{\n   if (!memory || !value) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   if (value) {\n      workMemory->streamMemory->paramOutputPerFrame = uint8_t { 1 };\n   } else {\n      workMemory->streamMemory->paramOutputPerFrame = uint8_t { 0 };\n   }\n\n   return H264Error::OK;\n}\n\n\n/**\n * Set a user memory pointer which is passed to the frame output callback.\n */\nH264Error\nH264DECSetParam_USER_MEMORY(virt_ptr<void> memory,\n                            virt_ptr<void> value)\n{\n   if (!memory || !value) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   workMemory->streamMemory->paramUserMemory = value;\n   return H264Error::OK;\n}\n\nH264Error\nH264DECCheckMemSegmentation(virt_ptr<void> memory,\n                            uint32_t size)\n{\n   if (!memory || !size) {\n      return H264Error::InvalidParameter;\n   }\n\n   return H264Error::OK;\n}\n\nH264Error\nH264DECOpen(virt_ptr<void> memory)\n{\n#ifdef DECAF_FFMPEG\n   return ffmpeg::H264DECOpen(memory);\n#else\n   return null::H264DECOpen(memory);\n#endif\n}\n\nH264Error\nH264DECBegin(virt_ptr<void> memory)\n{\n#ifdef DECAF_FFMPEG\n   return ffmpeg::H264DECBegin(memory);\n#else\n   return null::H264DECBegin(memory);\n#endif\n}\n\nH264Error\nH264DECSetBitstream(virt_ptr<void> memory,\n                    virt_ptr<uint8_t> buffer,\n                    uint32_t bufferLength,\n                    double timestamp)\n{\n#ifdef DECAF_FFMPEG\n   return ffmpeg::H264DECSetBitstream(memory, buffer, bufferLength, timestamp);\n#else\n   return null::H264DECSetBitstream(memory, buffer, bufferLength, timestamp);\n#endif\n}\n\nH264Error\nH264DECExecute(virt_ptr<void> memory,\n               virt_ptr<void> frameBuffer)\n{\n#ifdef DECAF_FFMPEG\n   return ffmpeg::H264DECExecute(memory, frameBuffer);\n#else\n   return null::H264DECExecute(memory, frameBuffer);\n#endif\n}\n\nH264Error\nH264DECFlush(virt_ptr<void> memory)\n{\n#ifdef DECAF_FFMPEG\n   return ffmpeg::H264DECFlush(memory);\n#else\n   return null::H264DECFlush(memory);\n#endif\n}\n\nH264Error\nH264DECEnd(virt_ptr<void> memory)\n{\n#ifdef DECAF_FFMPEG\n   return ffmpeg::H264DECEnd(memory);\n#else\n   return null::H264DECEnd(memory);\n#endif\n}\n\nH264Error\nH264DECClose(virt_ptr<void> memory)\n{\n#ifdef DECAF_FFMPEG\n   return ffmpeg::H264DECClose(memory);\n#else\n   return null::H264DECClose(memory);\n#endif\n}\n\nvoid\nLibrary::registerDecodeSymbols()\n{\n   RegisterFunctionExport(H264DECMemoryRequirement);\n   RegisterFunctionExport(H264DECInitParam);\n   RegisterFunctionExport(H264DECSetParam);\n   RegisterFunctionExport(H264DECSetParam_FPTR_OUTPUT);\n   RegisterFunctionExport(H264DECSetParam_OUTPUT_PER_FRAME);\n   RegisterFunctionExport(H264DECSetParam_USER_MEMORY);\n   RegisterFunctionExport(H264DECCheckMemSegmentation);\n\n   RegisterFunctionExport(H264DECOpen);\n   RegisterFunctionExport(H264DECBegin);\n   RegisterFunctionExport(H264DECSetBitstream);\n   RegisterFunctionExport(H264DECExecute);\n   RegisterFunctionExport(H264DECFlush);\n   RegisterFunctionExport(H264DECClose);\n   RegisterFunctionExport(H264DECEnd);\n}\n\n} // namespace cafe::h264\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264_decode.h",
    "content": "#pragma once\n#include \"h264_enum.h\"\n#include \"h264_stream.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::h264\n{\n\nstruct H264Bitstream;\nstruct H264CodecMemory;\nstruct H264StreamMemory;\n\nstruct H264WorkMemory\n{\n   //! Space for pointer to aligned memory in case the original memory was\n   //! already aligned.\n   PADDING(4);\n\n   //! End of the h264 work memory\n   be2_virt_ptr<void> workMemoryEnd;\n   be2_virt_ptr<void> unk0x08;\n   be2_virt_ptr<H264StreamMemory> streamMemory;\n   be2_virt_ptr<void> unk0x10;\n   be2_virt_ptr<H264Bitstream> bitStream;\n   be2_virt_ptr<void> database;\n   be2_virt_ptr<H264CodecMemory> codecMemory;\n   be2_virt_ptr<void> l1Memory;\n   be2_virt_ptr<void> unk0x24;\n   be2_virt_ptr<void> internalFrameMemory;\n};\nCHECK_OFFSET(H264WorkMemory, 0x04, workMemoryEnd);\nCHECK_OFFSET(H264WorkMemory, 0x08, unk0x08);\nCHECK_OFFSET(H264WorkMemory, 0x0C, streamMemory);\nCHECK_OFFSET(H264WorkMemory, 0x10, unk0x10);\nCHECK_OFFSET(H264WorkMemory, 0x14, bitStream);\nCHECK_OFFSET(H264WorkMemory, 0x18, database);\nCHECK_OFFSET(H264WorkMemory, 0x1C, codecMemory);\nCHECK_OFFSET(H264WorkMemory, 0x20, l1Memory);\nCHECK_OFFSET(H264WorkMemory, 0x24, unk0x24);\nCHECK_OFFSET(H264WorkMemory, 0x28, internalFrameMemory);\nCHECK_SIZE(H264WorkMemory, 0x2C);\n\nH264Error\nH264DECMemoryRequirement(int32_t profile,\n                         int32_t level,\n                         int32_t maxWidth,\n                         int32_t maxHeight,\n                         virt_ptr<uint32_t> outMemoryRequirement);\n\nH264Error\nH264DECInitParam(int32_t memorySize,\n                 virt_ptr<void> memory);\n\nH264Error\nH264DECSetParam(virt_ptr<void> memory,\n                H264Parameter parameter,\n                virt_ptr<void> value);\n\nH264Error\nH264DECSetParam_FPTR_OUTPUT(virt_ptr<void> memory,\n                            H264DECFptrOutputFn value);\n\nH264Error\nH264DECSetParam_OUTPUT_PER_FRAME(virt_ptr<void> memory,\n                                 uint32_t value);\n\nH264Error\nH264DECSetParam_USER_MEMORY(virt_ptr<void> memory,\n                            virt_ptr<void> value);\n\nH264Error\nH264DECCheckMemSegmentation(virt_ptr<void> memory,\n                            uint32_t size);\n\nH264Error\nH264DECOpen(virt_ptr<void> memory);\n\nH264Error\nH264DECBegin(virt_ptr<void> memory);\n\nH264Error\nH264DECSetBitstream(virt_ptr<void> memory,\n                    virt_ptr<uint8_t> buffer,\n                    uint32_t bufferLength,\n                    double timestamp);\n\nH264Error\nH264DECExecute(virt_ptr<void> memory,\n               virt_ptr<void> frameBuffer);\n\nH264Error\nH264DECFlush(virt_ptr<void> memory);\n\nH264Error\nH264DECEnd(virt_ptr<void> memory);\n\nH264Error\nH264DECClose(virt_ptr<void> memory);\n\nnamespace internal\n{\n\nvirt_ptr<H264WorkMemory>\ngetWorkMemory(virt_ptr<void> memory);\n\n} // namespace internal\n\n} // namespace cafe::h264\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264_decode_ffmpeg.cpp",
    "content": "#ifdef DECAF_FFMPEG\n#include \"h264.h\"\n#include \"h264_decode.h\"\n#include \"h264_stream.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <fmt/core.h>\n#include <libcpu/cpu_formatters.h>\n\n// ffmpeg unfortunately does not validate with high warning levels\n#ifdef _MSC_VER\n#   pragma warning(push)\n#   pragma warning(disable: 4244)\n#endif\nextern \"C\" {\n#include <libavcodec/avcodec.h>\n#include <libavfilter/avfilter.h>\n#include <libswscale/swscale.h>\n}\n#ifdef _MSC_VER\n#   pragma warning(pop)\n#endif\n\nnamespace cafe::h264\n{\n\n// This is decaf specific stuff - does not match structure in h264.rpl\nstruct H264CodecMemory\n{\n   AVCodecContext *context;\n   AVFrame *frame;\n   AVCodecParserContext *parser;\n   SwsContext *sws;\n   int swsWidth;\n   int swsHeight;\n   int inputFrameIndex;\n   int outputFrameIndex;\n\n   //! HACK: This is just a copy of the most recently seen vui_parameters in\n   //! the stream, technically it should probably be the ones that are in the\n   //! SPS for the given PPS in given frame's slice headers.\n   uint8_t vui_parameters_present_flag;\n   H264DecodedVuiParameters vui_parameters;\n};\n\n} // namespace cafe::h264\n\nnamespace cafe::h264::ffmpeg\n{\n\nstatic int\nreceiveFrames(virt_ptr<H264WorkMemory> workMemory)\n{\n   auto codecMemory = workMemory->codecMemory;\n   auto streamMemory = workMemory->streamMemory;\n   auto frame = codecMemory->frame;\n   auto result = 0;\n\n   while (result == 0) {\n      result = avcodec_receive_frame(codecMemory->context, frame);\n      if (result != 0) {\n         break;\n      }\n\n      // Get the decoded frame info\n      auto &decodedFrameInfo = streamMemory->decodedFrameInfos[codecMemory->outputFrameIndex];\n      codecMemory->outputFrameIndex =\n         (codecMemory->outputFrameIndex + 1) % streamMemory->decodedFrameInfos.size();\n\n\n      auto decodeResult = StackObject<H264DecodeResult> { };\n      decodeResult->status = 100;\n      decodeResult->timestamp = decodedFrameInfo.timestamp;\n\n      const auto pitch = align_up(frame->width, 256);\n\n      // Destroy previously created SWS if there is different width/height\n      if (codecMemory->sws &&\n         (codecMemory->swsWidth != frame->width ||\n          codecMemory->swsHeight != frame->height)) {\n         sws_freeContext(codecMemory->sws);\n         codecMemory->sws = nullptr;\n      }\n\n      // Create SWS context if needed\n      if (!codecMemory->sws) {\n         codecMemory->sws =\n            sws_getContext(frame->width, frame->height,\n                           static_cast<AVPixelFormat>(frame->format),\n                           frame->width, frame->height, AV_PIX_FMT_NV12,\n                           0, nullptr, nullptr, nullptr);\n      }\n\n      // Use SWS to convert frame output to NV12 format\n      decaf_check(codecMemory->sws);\n      auto frameBuffer = virt_cast<uint8_t *>(decodedFrameInfo.buffer);\n      uint8_t *dstBuffers[] = {\n         frameBuffer.get(),\n         frameBuffer.get() + frame->height * pitch,\n      };\n      int dstStride[] = {\n         pitch, pitch\n      };\n\n      sws_scale(codecMemory->sws,\n                frame->data, frame->linesize,\n                0, frame->height,\n                dstBuffers, dstStride);\n\n      decodeResult->framebuffer = frameBuffer;\n      decodeResult->width = frame->width;\n      decodeResult->height = frame->height;\n      decodeResult->nextLine = pitch;\n\n      // Copy crop\n      if (frame->crop_top || frame->crop_bottom || frame->crop_left || frame->crop_right) {\n         decodeResult->cropEnableFlag = uint8_t { 1 };\n      } else {\n         decodeResult->cropEnableFlag = uint8_t { 0 };\n      }\n\n      decodeResult->cropTop = static_cast<int32_t>(frame->crop_top);\n      decodeResult->cropBottom = static_cast<int32_t>(frame->crop_bottom);\n      decodeResult->cropLeft = static_cast<int32_t>(frame->crop_left);\n      decodeResult->cropRight = static_cast<int32_t>(frame->crop_right);\n\n      // Copy pan scan\n      decodeResult->panScanEnableFlag = uint8_t { 0 };\n      decodeResult->panScanTop = 0;\n      decodeResult->panScanBottom = 0;\n      decodeResult->panScanLeft = 0;\n      decodeResult->panScanRight = 0;\n\n      for (auto i = 0; i < frame->nb_side_data; ++i) {\n         auto sideData = frame->side_data[i];\n         if (sideData->type == AV_FRAME_DATA_PANSCAN) {\n            auto panScan = reinterpret_cast<AVPanScan *>(sideData->data);\n\n            decodeResult->panScanEnableFlag = uint8_t { 1 };\n            decodeResult->panScanTop = panScan->position[0][0];\n            decodeResult->panScanLeft = panScan->position[0][1];\n            decodeResult->panScanRight = decodeResult->panScanLeft + panScan->width;\n            decodeResult->panScanBottom = decodeResult->panScanTop + panScan->height;\n         }\n      }\n\n      // Copy vui_parameters from decoded frame info\n      decodeResult->vui_parameters_present_flag = decodedFrameInfo.vui_parameters_present_flag;\n      if (decodeResult->vui_parameters_present_flag) {\n         decodeResult->vui_parameters = virt_addrof(decodedFrameInfo.vui_parameters);\n      } else {\n         decodeResult->vui_parameters = nullptr;\n      }\n\n      // Invoke the frame output callback, right now this is 1 frame at a time\n      // in future we may want to hoist this outside of the loop and emit N frames\n      auto results = StackArray<virt_ptr<H264DecodeResult>, 5> { };\n      auto output = StackObject<H264DecodeOutput> { };\n      output->frameCount = 1;\n      output->decodeResults = results;\n      output->userMemory = streamMemory->paramUserMemory;\n      results[0] = decodeResult;\n\n      cafe::invoke(cpu::this_core::state(),\n                   streamMemory->paramFramePointerOutput,\n                   output);\n   }\n\n   if (result == AVERROR_EOF || result == AVERROR(EAGAIN)) {\n      // Expected return values are not an error!\n      result = 0;\n   } else {\n      char buffer[255];\n      av_strerror(result, buffer, 255);\n      gLog->error(\"avcodec_receive_frame error: {}\", buffer);\n   }\n\n   return result;\n}\n\n\n/**\n * Open a H264 decoder.\n */\nH264Error\nH264DECOpen(virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto codec = avcodec_find_decoder(AV_CODEC_ID_H264);\n   if (!codec) {\n      return H264Error::GenericError;\n   }\n\n   auto context = avcodec_alloc_context3(codec);\n   if (!context) {\n      return H264Error::GenericError;\n   }\n\n   context->flags |= AV_CODEC_FLAG_LOW_DELAY;\n   context->thread_type = FF_THREAD_SLICE;\n   context->pix_fmt = AV_PIX_FMT_NV12;\n\n   if (avcodec_open2(context, codec, NULL) < 0) {\n      return H264Error::GenericError;\n   }\n\n   workMemory->codecMemory->context = context;\n   workMemory->codecMemory->frame = av_frame_alloc();\n   return H264Error::OK;\n}\n\n\n/**\n * Prepare for decoding.\n */\nH264Error\nH264DECBegin(virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   std::memset(virt_addrof(workMemory->streamMemory->decodedFrameInfos).get(),\n               0,\n               sizeof(workMemory->streamMemory->decodedFrameInfos));\n\n   // Open a new parser, because there is no reset function for it and I don't\n   // know if it has internal state which is important :).\n   workMemory->codecMemory->parser = av_parser_init(AV_CODEC_ID_H264);\n   workMemory->codecMemory->inputFrameIndex = 0;\n   workMemory->codecMemory->outputFrameIndex = 0;\n\n   return H264Error::OK;\n}\n\n\n/**\n * Set the bit stream to be read for decoding.\n */\nH264Error\nH264DECSetBitstream(virt_ptr<void> memory,\n                    virt_ptr<uint8_t> buffer,\n                    uint32_t bufferLength,\n                    double timestamp)\n{\n   if (!memory || !buffer || bufferLength < 4) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   workMemory->bitStream->buffer = buffer;\n   workMemory->bitStream->buffer_length = bufferLength;\n   workMemory->bitStream->bit_position = 0u;\n   workMemory->bitStream->timestamp = timestamp;\n   return H264Error::OK;\n}\n\n\n/**\n * Perform decoding of the bitstream and put the output frame into frameBuffer.\n */\nH264Error\nH264DECExecute(virt_ptr<void> memory,\n               virt_ptr<void> frameBuffer)\n{\n   if (!memory || !frameBuffer) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto bitStream = workMemory->bitStream;\n   auto codecMemory = workMemory->codecMemory;\n   auto streamMemory = workMemory->streamMemory;\n\n   if (!bitStream->buffer_length) {\n      return H264Error::GenericError;\n   }\n\n   // Parse the bitstream looking for any SPS to grab latest vui parameters\n   auto sps = StackObject<H264SequenceParameterSet> { };\n   if (internal::decodeNaluSps(bitStream->buffer.get(), bitStream->buffer_length,\n                               0, sps) == H264Error::OK) {\n      // Copy VUI parameters from the SPS\n      codecMemory->vui_parameters_present_flag = sps->vui_parameters_present_flag;\n\n      if (sps->vui_parameters_present_flag) {\n         auto &vui = codecMemory->vui_parameters;\n         vui.aspect_ratio_info_present_flag = sps->vui_aspect_ratio_info_present_flag;\n         vui.aspect_ratio_idc = sps->vui_aspect_ratio_idc;\n         vui.sar_width = sps->vui_sar_width;\n         vui.sar_height = sps->vui_sar_height;\n         vui.overscan_info_present_flag = sps->vui_overscan_info_present_flag;\n         vui.overscan_appropriate_flag = sps->vui_overscan_appropriate_flag;\n         vui.video_signal_type_present_flag = sps->vui_video_signal_type_present_flag;\n         vui.video_format = sps->vui_video_format;\n         vui.video_full_range_flag = sps->vui_video_full_range_flag;\n         vui.colour_description_present_flag = sps->vui_colour_description_present_flag;\n         vui.colour_primaries = sps->vui_colour_primaries;\n         vui.transfer_characteristics = sps->vui_transfer_characteristics;\n         vui.matrix_coefficients = sps->vui_matrix_coefficients;\n         vui.chroma_loc_info_present_flag = sps->vui_chroma_loc_info_present_flag;\n         vui.chroma_sample_loc_type_top_field = sps->vui_chroma_sample_loc_type_top_field;\n         vui.chroma_sample_loc_type_bottom_field = sps->vui_chroma_sample_loc_type_bottom_field;\n         vui.timing_info_present_flag = sps->vui_timing_info_present_flag;\n         vui.num_units_in_tick = sps->vui_num_units_in_tick;\n         vui.time_scale = sps->vui_time_scale;\n         vui.fixed_frame_rate_flag = sps->vui_fixed_frame_rate_flag;\n         vui.nal_hrd_parameters_present_flag = sps->vui_nal_hrd_parameters_present_flag;\n         vui.vcl_hrd_parameters_present_flag = sps->vui_vcl_hrd_parameters_present_flag;\n         vui.low_delay_hrd_flag = sps->vui_low_delay_hrd_flag;\n         vui.pic_struct_present_flag = sps->vui_pic_struct_present_flag;\n         vui.bitstream_restriction_flag = sps->vui_bitstream_restriction_flag;\n         vui.motion_vectors_over_pic_boundaries_flag = sps->vui_motion_vectors_over_pic_boundaries_flag;\n         vui.max_bytes_per_pic_denom = sps->vui_max_bytes_per_pic_denom;\n         vui.max_bits_per_mb_denom = sps->vui_max_bits_per_mb_denom;\n         vui.log2_max_mv_length_horizontal = sps->vui_log2_max_mv_length_horizontal;\n         vui.log2_max_mv_length_vertical = sps->vui_log2_max_mv_length_vertical;\n         vui.num_reorder_frames = sps->vui_num_reorder_frames;\n         vui.max_dec_frame_buffering = sps->vui_max_dec_frame_buffering;\n      }\n   }\n\n   // Update the decoded frame info for this frame\n   auto &decodedFrameInfo = streamMemory->decodedFrameInfos[codecMemory->inputFrameIndex];\n   codecMemory->inputFrameIndex =\n      (codecMemory->inputFrameIndex + 1) % streamMemory->decodedFrameInfos.size();\n\n   decodedFrameInfo.buffer = frameBuffer;\n   decodedFrameInfo.timestamp = bitStream->timestamp;\n\n   // Copy the latest VUI parameters\n   // HACK: This is not technically correct and we should probably parse the\n   // slice headers to see which SPS they are referencing.\n   decodedFrameInfo.vui_parameters_present_flag = codecMemory->vui_parameters_present_flag;\n   if (decodedFrameInfo.vui_parameters_present_flag) {\n      std::memcpy(virt_addrof(decodedFrameInfo.vui_parameters).get(),\n                  &codecMemory->vui_parameters,\n                  sizeof(decodedFrameInfo.vui_parameters));\n   }\n\n   // Submit packet to ffmpeg\n   auto* packet = av_packet_alloc();\n   packet->data = bitStream->buffer.get();\n   packet->size = bitStream->buffer_length;\n\n   auto result = avcodec_send_packet(codecMemory->context, packet);\n   if (result != 0) {\n      char buffer[255];\n      av_strerror(result, buffer, 255);\n      gLog->error(\"H264DECExecute avcodec_send_packet error: {}\", buffer);\n      av_packet_free(&packet);\n      return static_cast<H264Error>(result);\n   }\n\n   av_packet_free(&packet);\n\n   bitStream->buffer_length = 0u;\n\n   // Read any completed frames back from ffmpeg\n   result = receiveFrames(workMemory);\n   if (result != 0) {\n      return static_cast<H264Error>(result);\n   }\n\n   // Return 100% decoded frame\n   return static_cast<H264Error>(0x80 | 100);\n}\n\n\n/**\n * Flush any internally buffered frames.\n */\nH264Error\nH264DECFlush(virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   // Discard internal state and buffered frames\n   if (workMemory->codecMemory->context) {\n      avcodec_flush_buffers(workMemory->codecMemory->context);\n   }\n\n   return H264Error::OK;\n}\n\n\n/**\n * End decoding of the current stream.\n */\nH264Error\nH264DECEnd(virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   // Flush the stream\n   H264DECFlush(memory);\n\n   if (workMemory->codecMemory->parser) {\n      av_parser_close(workMemory->codecMemory->parser);\n      workMemory->codecMemory->parser = nullptr;\n   }\n\n   return H264Error::OK;\n}\n\n\n/**\n * Cleanup the decoder.\n */\nH264Error\nH264DECClose(virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   av_frame_free(&workMemory->codecMemory->frame);\n   avcodec_free_context(&workMemory->codecMemory->context);\n\n   sws_freeContext(workMemory->codecMemory->sws);\n   workMemory->codecMemory->sws = nullptr;\n\n   // Just in case someone did not call H264DECEnd\n   if (workMemory->codecMemory->parser) {\n      av_parser_close(workMemory->codecMemory->parser);\n      workMemory->codecMemory->parser = nullptr;\n   }\n\n   return H264Error::OK;\n}\n\n#if 0\n// ffmpeg based H264DECCheckDecunitLength\nH264Error\nH264DECCheckDecunitLength(virt_ptr<void> memory,\n                          virt_ptr<const uint8_t> buffer,\n                          int32_t bufferLength,\n                          int32_t offset,\n                          virt_ptr<int32_t> outLength)\n{\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   uint8_t *outBuf = nullptr;\n   int outBufSize = 0;\n   auto ret = av_parser_parse2(workMemory->codecMemory->parser,\n                               workMemory->codecMemory->context,\n                               &outBuf, &outBufSize,\n                               buffer.get() + offset, bufferLength - offset,\n                               AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);\n\n   if (ret < 0) {\n      return H264Error::GenericError;\n   }\n\n   *outLength = outBufSize;\n   return H264Error::OK;\n}\n#endif // if 0\n\n} // namespace cafe::h264::ffmpeg\n\n#endif // ifdef DECAF_FFMPEG\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264_decode_ffmpeg.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::h264::ffmpeg\n{\n\nH264Error\nH264DECOpen(virt_ptr<void> memory);\n\nH264Error\nH264DECBegin(virt_ptr<void> memory);\n\nH264Error\nH264DECSetBitstream(virt_ptr<void> memory,\n                    virt_ptr<uint8_t> buffer,\n                    uint32_t bufferLength,\n                    double timestamp);\n\nH264Error\nH264DECExecute(virt_ptr<void> memory,\n               virt_ptr<void> frameBuffer);\n\nH264Error\nH264DECFlush(virt_ptr<void> memory);\n\nH264Error\nH264DECEnd(virt_ptr<void> memory);\n\nH264Error\nH264DECClose(virt_ptr<void> memory);\n\n} // namespace cafe::h264::ffmpeg\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264_decode_null.cpp",
    "content": "#include \"h264.h\"\n#include \"h264_decode.h\"\n#include \"h264_stream.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\n#include <fmt/core.h>\n\nnamespace cafe::h264\n{\n\n// This is decaf specific stuff - does not match structure in h264.rpl\nstruct H264CodecMemory\n{\n   int inputFrameIndex;\n   int outputFrameIndex;\n   int width;\n   int height;\n\n   //! HACK: This is just a copy of the most recently seen vui_parameters in\n   //! the stream, technically it should probably be the ones that are in the\n   //! SPS for the given PPS in given frame's slice headers.\n   uint8_t vui_parameters_present_flag;\n   H264DecodedVuiParameters vui_parameters;\n};\n\n} // namespace cafe::h264\n\nnamespace cafe::h264::null\n{\n\nstatic constexpr uint8_t NullImage[6][20] = {\n   // H264\n   { 1,0,0,1, 0, 1,1,1,0, 0, 0,1,1,0, 0, 1,0,0,1, 0, },\n   { 1,0,0,1, 0, 0,0,0,1, 0, 1,0,0,0, 0, 1,0,0,1, 0, },\n   { 1,1,1,1, 0, 0,1,1,0, 0, 1,1,1,0, 0, 1,1,1,1, 0, },\n   { 1,0,0,1, 0, 1,0,0,0, 0, 1,0,0,1, 0, 0,0,0,1, 0, },\n   { 1,0,0,1, 0, 1,1,1,1, 0, 0,1,1,0, 0, 0,0,0,1, 0, },\n   { 0,0,0,0, 0, 0,0,0,0, 0, 0,0,0,0, 0, 0,0,0,0, 0, },\n};\n\nstatic int\nreceiveFrames(virt_ptr<H264WorkMemory> workMemory)\n{\n   auto codecMemory = workMemory->codecMemory;\n   auto streamMemory = workMemory->streamMemory;\n   const auto pitch = align_up(codecMemory->width, 256);\n\n   while (codecMemory->outputFrameIndex != codecMemory->inputFrameIndex) {\n      // Get the decoded frame info\n      auto &decodedFrameInfo = streamMemory->decodedFrameInfos[codecMemory->outputFrameIndex];\n      codecMemory->outputFrameIndex =\n         (codecMemory->outputFrameIndex + 1) % streamMemory->decodedFrameInfos.size();\n\n      // Fill framebuffer with our fake image\n      auto frameBuffer = virt_cast<uint8_t *>(decodedFrameInfo.buffer);\n      auto dst = frameBuffer.get();\n\n      // Y\n      for (auto y = 0; y < codecMemory->height; ++y) {\n         for (auto x = 0; x < codecMemory->width; ++x) {\n            dst[x] = NullImage[(y / 2) % 6][(x / 2) % 20] ? 0xFF : 0x00;\n         }\n\n         dst += pitch;\n      }\n\n      // Interleaved UV\n      for (auto y = 0; y < codecMemory->height / 2; ++y) {\n         for (auto x = 0; x < codecMemory->width / 2; ++x) {\n            dst[x * 2 + 0] = x % 255;\n            dst[x * 2 + 1] = y % 255;\n         }\n\n         dst += pitch;\n      }\n\n      auto decodeResult = StackObject<H264DecodeResult> { };\n      decodeResult->status = 100;\n      decodeResult->timestamp = decodedFrameInfo.timestamp;\n      decodeResult->framebuffer = frameBuffer;\n      decodeResult->width = codecMemory->width;\n      decodeResult->height = codecMemory->height;\n      decodeResult->nextLine = pitch;\n\n      // Crop\n      decodeResult->cropEnableFlag = uint8_t { 0 };\n      decodeResult->cropTop = 0;\n      decodeResult->cropBottom = 0;\n      decodeResult->cropLeft = 0;\n      decodeResult->cropRight = 0;\n\n      // Copy pan scan\n      decodeResult->panScanEnableFlag = uint8_t { 0 };\n      decodeResult->panScanTop = 0;\n      decodeResult->panScanBottom = 0;\n      decodeResult->panScanLeft = 0;\n      decodeResult->panScanRight = 0;\n\n      // Copy vui_parameters from decoded frame info\n      decodeResult->vui_parameters_present_flag = decodedFrameInfo.vui_parameters_present_flag;\n      if (decodeResult->vui_parameters_present_flag) {\n         decodeResult->vui_parameters = virt_addrof(decodedFrameInfo.vui_parameters);\n      } else {\n         decodeResult->vui_parameters = nullptr;\n      }\n\n      // Invoke the frame output callback\n      auto results = StackArray<virt_ptr<H264DecodeResult>, 5> { };\n      auto output = StackObject<H264DecodeOutput> { };\n      output->frameCount = 1;\n      output->decodeResults = results;\n      output->userMemory = streamMemory->paramUserMemory;\n      results[0] = decodeResult;\n\n      cafe::invoke(cpu::this_core::state(),\n                   streamMemory->paramFramePointerOutput,\n                   output);\n   }\n\n   return H264Error::OK;\n}\n\n\n/**\n * Open a H264 decoder.\n */\nH264Error\nH264DECOpen(virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   return H264Error::OK;\n}\n\n\n/**\n * Prepare for decoding.\n */\nH264Error\nH264DECBegin(virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   std::memset(virt_addrof(workMemory->streamMemory->decodedFrameInfos).get(),\n               0,\n               sizeof(workMemory->streamMemory->decodedFrameInfos));\n   workMemory->codecMemory->inputFrameIndex = 0;\n   workMemory->codecMemory->outputFrameIndex = 0;\n   return H264Error::OK;\n}\n\n\n/**\n * Set the bit stream to be read for decoding.\n */\nH264Error\nH264DECSetBitstream(virt_ptr<void> memory,\n                    virt_ptr<uint8_t> buffer,\n                    uint32_t bufferLength,\n                    double timestamp)\n{\n   if (!memory || !buffer || bufferLength < 4) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   workMemory->bitStream->buffer = buffer;\n   workMemory->bitStream->buffer_length = bufferLength;\n   workMemory->bitStream->bit_position = 0u;\n   workMemory->bitStream->timestamp = timestamp;\n   return H264Error::OK;\n}\n\n\n/**\n * Perform decoding of the bitstream and put the output frame into frameBuffer.\n */\nH264Error\nH264DECExecute(virt_ptr<void> memory,\n               virt_ptr<void> frameBuffer)\n{\n   if (!memory || !frameBuffer) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto bitStream = workMemory->bitStream;\n   auto codecMemory = workMemory->codecMemory;\n   auto streamMemory = workMemory->streamMemory;\n\n   if (!bitStream->buffer_length) {\n      return H264Error::GenericError;\n   }\n\n   // Parse the bitstream looking for any SPS\n   auto sps = StackObject<H264SequenceParameterSet> { };\n   if (internal::decodeNaluSps(bitStream->buffer.get(),\n                               bitStream->buffer_length,\n                               0, sps) == H264Error::OK) {\n      codecMemory->width = sps->pic_width;\n      codecMemory->height = sps->pic_height;\n\n      if (!sps->frame_mbs_only_flag) {\n         codecMemory->height = 2 * sps->pic_height;\n      }\n\n      // Copy VUI parameters from the SPS\n      codecMemory->vui_parameters_present_flag = sps->vui_parameters_present_flag;\n\n      if (sps->vui_parameters_present_flag) {\n         auto &vui = codecMemory->vui_parameters;\n         vui.aspect_ratio_info_present_flag = sps->vui_aspect_ratio_info_present_flag;\n         vui.aspect_ratio_idc = sps->vui_aspect_ratio_idc;\n         vui.sar_width = sps->vui_sar_width;\n         vui.sar_height = sps->vui_sar_height;\n         vui.overscan_info_present_flag = sps->vui_overscan_info_present_flag;\n         vui.overscan_appropriate_flag = sps->vui_overscan_appropriate_flag;\n         vui.video_signal_type_present_flag = sps->vui_video_signal_type_present_flag;\n         vui.video_format = sps->vui_video_format;\n         vui.video_full_range_flag = sps->vui_video_full_range_flag;\n         vui.colour_description_present_flag = sps->vui_colour_description_present_flag;\n         vui.colour_primaries = sps->vui_colour_primaries;\n         vui.transfer_characteristics = sps->vui_transfer_characteristics;\n         vui.matrix_coefficients = sps->vui_matrix_coefficients;\n         vui.chroma_loc_info_present_flag = sps->vui_chroma_loc_info_present_flag;\n         vui.chroma_sample_loc_type_top_field = sps->vui_chroma_sample_loc_type_top_field;\n         vui.chroma_sample_loc_type_bottom_field = sps->vui_chroma_sample_loc_type_bottom_field;\n         vui.timing_info_present_flag = sps->vui_timing_info_present_flag;\n         vui.num_units_in_tick = sps->vui_num_units_in_tick;\n         vui.time_scale = sps->vui_time_scale;\n         vui.fixed_frame_rate_flag = sps->vui_fixed_frame_rate_flag;\n         vui.nal_hrd_parameters_present_flag = sps->vui_nal_hrd_parameters_present_flag;\n         vui.vcl_hrd_parameters_present_flag = sps->vui_vcl_hrd_parameters_present_flag;\n         vui.low_delay_hrd_flag = sps->vui_low_delay_hrd_flag;\n         vui.pic_struct_present_flag = sps->vui_pic_struct_present_flag;\n         vui.bitstream_restriction_flag = sps->vui_bitstream_restriction_flag;\n         vui.motion_vectors_over_pic_boundaries_flag = sps->vui_motion_vectors_over_pic_boundaries_flag;\n         vui.max_bytes_per_pic_denom = sps->vui_max_bytes_per_pic_denom;\n         vui.max_bits_per_mb_denom = sps->vui_max_bits_per_mb_denom;\n         vui.log2_max_mv_length_horizontal = sps->vui_log2_max_mv_length_horizontal;\n         vui.log2_max_mv_length_vertical = sps->vui_log2_max_mv_length_vertical;\n         vui.num_reorder_frames = sps->vui_num_reorder_frames;\n         vui.max_dec_frame_buffering = sps->vui_max_dec_frame_buffering;\n      }\n   }\n   bitStream->buffer_length = 0u;\n\n   // Update the decoded frame info for this frame\n   auto &decodedFrameInfo = streamMemory->decodedFrameInfos[codecMemory->inputFrameIndex];\n   codecMemory->inputFrameIndex =\n      (codecMemory->inputFrameIndex + 1) % streamMemory->decodedFrameInfos.size();\n\n   decodedFrameInfo.buffer = frameBuffer;\n   decodedFrameInfo.timestamp = bitStream->timestamp;\n   decodedFrameInfo.vui_parameters_present_flag = codecMemory->vui_parameters_present_flag;\n   if (decodedFrameInfo.vui_parameters_present_flag) {\n      std::memcpy(virt_addrof(decodedFrameInfo.vui_parameters).get(),\n                  &codecMemory->vui_parameters,\n                  sizeof(decodedFrameInfo.vui_parameters));\n   }\n\n\n   auto result = receiveFrames(workMemory);\n   if (result == 0) {\n      return static_cast<H264Error>(0x80 | 100);\n   }\n\n   return static_cast<H264Error>(result);\n}\n\n\n/**\n * Flush any internally buffered frames.\n */\nH264Error\nH264DECFlush(virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   // We do not buffer frames, so nothing to flush.\n   return H264Error::OK;\n}\n\n\n/**\n * End decoding of the current stream.\n */\nH264Error\nH264DECEnd(virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   H264DECFlush(memory);\n   return H264Error::OK;\n}\n\n\n/**\n * Cleanup the decoder.\n */\nH264Error\nH264DECClose(virt_ptr<void> memory)\n{\n   if (!memory) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   if (!workMemory) {\n      return H264Error::InvalidParameter;\n   }\n\n   return H264Error::OK;\n}\n\n} // namespace cafe::h264::null\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264_decode_null.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::h264::null\n{\n\nH264Error\nH264DECOpen(virt_ptr<void> memory);\n\nH264Error\nH264DECBegin(virt_ptr<void> memory);\n\nH264Error\nH264DECSetBitstream(virt_ptr<void> memory,\n                    virt_ptr<uint8_t> buffer,\n                    uint32_t bufferLength,\n                    double timestamp);\n\nH264Error\nH264DECExecute(virt_ptr<void> memory,\n               virt_ptr<void> frameBuffer);\n\nH264Error\nH264DECFlush(virt_ptr<void> memory);\n\nH264Error\nH264DECEnd(virt_ptr<void> memory);\n\nH264Error\nH264DECClose(virt_ptr<void> memory);\n\n} // namespace cafe::h264::null\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264_enum.h",
    "content": "#ifndef H264_ENUM_H\n#define H264_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(h264)\n\nENUM_BEG(EntryPoint, int8_t)\n   ENUM_VALUE(InitParam,            4)\n   ENUM_VALUE(SetParam,             5)\n   ENUM_VALUE(Open,                 6)\n   ENUM_VALUE(Begin,                7)\n   ENUM_VALUE(SetBitStream,         8)\n   ENUM_VALUE(Execute,              9)\n   ENUM_VALUE(End,                  10)\n   ENUM_VALUE(Close,                11)\nENUM_END(EntryPoint)\n\nENUM_BEG(H264Error, int32_t)\n   ENUM_VALUE(OK,                   0)\n   ENUM_VALUE(InvalidPps,           24)\n   ENUM_VALUE(InvalidSps,           26)\n   ENUM_VALUE(InvalidSliceHeader,   61)\n   ENUM_VALUE(GenericError,         0x1000000)\n   ENUM_VALUE(InvalidParameter,     0x1010000)\n   ENUM_VALUE(OutOfMemory,          0x1020000)\n   ENUM_VALUE(InvalidProfile,       0x1080000)\nENUM_END(H264Error)\n\nENUM_BEG(H264Parameter, int32_t)\n   ENUM_VALUE(FramePointerOutput,   1)\n   ENUM_VALUE(OutputPerFrame,       0x20000002)\n   ENUM_VALUE(Unknown0x20000010,    0x20000010)\n   ENUM_VALUE(Unknown0x20000030,    0x20000030)\n   ENUM_VALUE(Unknown0x20000040,    0x20000040)\n   ENUM_VALUE(UserMemory,           0x70000001)\nENUM_END(H264Parameter)\n\nENUM_BEG(SliceType, int32_t)\n   ENUM_VALUE(P,                    0)\n   ENUM_VALUE(B,                    1)\n   ENUM_VALUE(I,                    2)\n\n   ENUM_VALUE(EP,                   0)\n   ENUM_VALUE(EB,                   1)\n   ENUM_VALUE(EI,                   2)\n   ENUM_VALUE(SP,                   3)\n   ENUM_VALUE(SI,                   4)\n\n   ENUM_VALUE(OnlyP,                5)\n   ENUM_VALUE(OnlyB,                6)\n   ENUM_VALUE(OnlyI,                7)\n\n   ENUM_VALUE(OnlyEP,               5)\n   ENUM_VALUE(OnlyEB,               6)\n   ENUM_VALUE(OnlyEI,               7)\n   ENUM_VALUE(OnlySP,               8)\n   ENUM_VALUE(OnlySI,               9)\nENUM_END(SliceType)\n\nENUM_BEG(NaluType, int32_t)\n   ENUM_VALUE(NonIdr,               1)\n   ENUM_VALUE(DataPartitionA,       2)\n   ENUM_VALUE(DataPartitionB,       3)\n   ENUM_VALUE(DataPartitionC,       4)\n   ENUM_VALUE(Idr,                  5)\n   ENUM_VALUE(Sei,                  6)\n   ENUM_VALUE(Sps,                  7)\n   ENUM_VALUE(Pps,                  8)\n   ENUM_VALUE(Aud,                  9)\n   ENUM_VALUE(EndOfSequence,        10)\n   ENUM_VALUE(EndOfStream,          11)\n   ENUM_VALUE(Filler,               12)\n   ENUM_VALUE(SpsExt,               13)\n   ENUM_VALUE(PrefixNal,            14)\n   ENUM_VALUE(SubsetSps,            15)\n   ENUM_VALUE(Dps,                  16)\n   ENUM_VALUE(CodedSliceAux,        19)\n   ENUM_VALUE(CodedSliceSvcExt,     20)\nENUM_END(NaluType)\n\nENUM_NAMESPACE_EXIT(h264)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef SWKBD_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264_stream.cpp",
    "content": "#include \"h264.h\"\n#include \"h264_bitstream.h\"\n#include \"h264_decode.h\"\n#include \"h264_enum.h\"\n#include \"h264_stream.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n\n#include <cmath>\n\nnamespace cafe::h264\n{\n\nnamespace internal\n{\n\nstatic void\nclearPictureParameterSet(virt_ptr<H264PictureParameterSet> pps)\n{\n   std::memset(pps.get(), 0, sizeof(H264PictureParameterSet));\n   pps->scalingList4x4.fill(16u);\n   pps->scalingList8x8.fill(16u);\n}\n\nstatic void\nclearSequenceParameterSet(virt_ptr<H264SequenceParameterSet> sps)\n{\n   std::memset(sps.get(), 0, sizeof(H264SequenceParameterSet));\n   sps->scalingList4x4.fill(16u);\n   sps->scalingList8x8.fill(16u);\n}\n\nstatic bool\nrbspHasMoreData(BitStream &bs)\n{\n   if (bs.eof()) {\n      return false;\n   }\n\n   // No rbsp_stop_bit yet\n   if (bs.peekU1() == 0) {\n      return true;\n   }\n\n   // Next bit is 1, is it the rsbp_stop_bit? only if the rest of bits are 0\n   auto bsTmp = BitStream { bs };\n   bsTmp.readU1();\n   while (!bsTmp.eof()) {\n      // A later bit was 1, it wasn't the rsbp_stop_bit\n      if (bsTmp.readU1() == 1) {\n         return true;\n      }\n   }\n\n   // All following bits were 0, it was the rsbp_stop_bit\n   return false;\n}\n\nstatic void\nrbspReadTrailingBits(BitStream &bs)\n{\n   // Read rbsp_stop_one_bit\n   bs.readU1();\n\n   // Read rbsp_alignment_zero_bit until we are aligned\n   while (!bs.byteAligned()) {\n      bs.readU1();\n   }\n}\n\nstatic void\nreadScalingList(BitStream &bs,\n                uint8_t *scalingList,\n                int sizeOfScalingList)\n{\n   uint8_t lastScale = 8;\n   uint8_t nextScale = 8;\n\n   for (int i = 0; i < sizeOfScalingList; ++i) {\n      if (nextScale != 0) {\n         nextScale = (lastScale + bs.readSE() + 256) % 256;\n      }\n\n      scalingList[i] = (nextScale == 0) ? lastScale : nextScale;\n      lastScale = scalingList[i];\n   }\n}\n\nstatic void\nreadHrdParameters(BitStream &bs,\n                  virt_ptr<H264SequenceParameterSet> sps)\n{\n   sps->hrd_cpb_cnt_minus1 = static_cast<uint16_t>(bs.readUE());\n   if (sps->hrd_cpb_cnt_minus1 < 32) {\n      sps->hrd_bit_rate_scale = bs.readU4();\n      sps->hrd_cpb_size_scale = bs.readU4();\n\n      for (auto i = 0; i <= sps->hrd_cpb_cnt_minus1; ++i) {\n         sps->hrd_bit_rate_value_minus1[i] = static_cast<uint16_t>(bs.readUE());\n         sps->hrd_cpb_size_value_minus1[i] = static_cast<uint16_t>(bs.readUE());\n         sps->hrd_cbr_flag[i] = bs.readU1();\n      }\n\n      sps->hrd_initial_cpb_removal_delay_length_minus1 = bs.readU5();\n      sps->hrd_cpb_removal_delay_length_minus1 = bs.readU5();\n      sps->hrd_dpb_output_delay_length_minus1 = bs.readU5();\n      sps->hrd_time_offset_length = bs.readU5();\n   }\n}\n\nstatic void\nreadVuiParameters(BitStream &bs,\n                  virt_ptr<H264SequenceParameterSet> sps)\n{\n   sps->vui_aspect_ratio_info_present_flag = bs.readU1();\n   if (sps->vui_aspect_ratio_info_present_flag) {\n      sps->vui_aspect_ratio_idc = bs.readU8();\n      if (sps->vui_aspect_ratio_idc == SarExtended) {\n         sps->vui_sar_width = bs.readU16();\n         sps->vui_sar_height = bs.readU16();\n      }\n   }\n\n   sps->vui_overscan_info_present_flag = bs.readU1();\n   if (sps->vui_overscan_info_present_flag) {\n      sps->vui_overscan_appropriate_flag = bs.readU1();\n   }\n\n   sps->vui_video_signal_type_present_flag = bs.readU1();\n   if (sps->vui_video_signal_type_present_flag) {\n      sps->vui_video_format = bs.readU3();\n      sps->vui_video_full_range_flag = bs.readU1();\n      sps->vui_colour_description_present_flag = bs.readU1();\n\n      if (sps->vui_colour_description_present_flag) {\n         sps->vui_colour_primaries = bs.readU8();\n         sps->vui_transfer_characteristics = bs.readU8();\n         sps->vui_matrix_coefficients = bs.readU8();\n      }\n   }\n\n   sps->vui_chroma_loc_info_present_flag = bs.readU1();\n   if (sps->vui_chroma_loc_info_present_flag) {\n      sps->vui_chroma_sample_loc_type_top_field = static_cast<uint8_t>(bs.readUE());\n      sps->vui_chroma_sample_loc_type_bottom_field = static_cast<uint8_t>(bs.readUE());\n   }\n\n   sps->vui_timing_info_present_flag = bs.readU1();\n   if (sps->vui_timing_info_present_flag) {\n      sps->vui_num_units_in_tick = bs.readU(32);\n      sps->vui_time_scale = bs.readU(32);\n      sps->vui_fixed_frame_rate_flag = bs.readU1();\n   }\n\n   sps->vui_nal_hrd_parameters_present_flag = bs.readU1();\n   if (sps->vui_nal_hrd_parameters_present_flag) {\n      sps->unk0x95E = uint8_t { 1 };\n      readHrdParameters(bs, sps);\n   }\n\n   sps->vui_vcl_hrd_parameters_present_flag = bs.readU1();\n   if (sps->vui_vcl_hrd_parameters_present_flag) {\n      sps->unk0x95F = uint8_t { 1 };\n      readHrdParameters(bs, sps);\n   }\n\n   if (sps->vui_nal_hrd_parameters_present_flag || sps->vui_vcl_hrd_parameters_present_flag) {\n      sps->unk0x960 = uint8_t { 1 };\n      sps->vui_low_delay_hrd_flag = bs.readU1();\n   }\n\n   sps->vui_pic_struct_present_flag = bs.readU1();\n   sps->vui_bitstream_restriction_flag = bs.readU1();\n   if (sps->vui_bitstream_restriction_flag) {\n      sps->vui_motion_vectors_over_pic_boundaries_flag = bs.readU1();\n      sps->vui_max_bytes_per_pic_denom = static_cast<uint16_t>(bs.readUE());\n      sps->vui_max_bits_per_mb_denom = static_cast<uint16_t>(bs.readUE());\n      sps->vui_log2_max_mv_length_horizontal = static_cast<uint16_t>(bs.readUE());\n      sps->vui_log2_max_mv_length_vertical = static_cast<uint16_t>(bs.readUE());\n      sps->vui_num_reorder_frames = static_cast<uint16_t>(bs.readUE());\n      sps->unk0x7B0 = uint16_t { 5 };\n      sps->vui_max_dec_frame_buffering = static_cast<uint16_t>(bs.readUE() + 1);\n   }\n}\n\nstatic H264Error\nreadSequenceParameterSet(virt_ptr<H264StreamMemory> streamMemory,\n                         const uint8_t *buffer,\n                         uint32_t bufferSize,\n                         virt_ptr<H264SequenceParameterSet> outSps,\n                         size_t *outBytesRead)\n{\n   auto sps = StackObject<H264SequenceParameterSet> { };\n   clearSequenceParameterSet(sps);\n\n   auto bs = BitStream { buffer, bufferSize };\n   sps->profile_idc = bs.readU8();\n   if (sps->profile_idc != 66 &&\n       sps->profile_idc != 77 &&\n       sps->profile_idc != 100) {\n      return H264Error::InvalidProfile;\n   }\n\n   sps->constraint_set = bs.readU8();\n   sps->level_idc = bs.readU8();\n   sps->seq_parameter_set_id = static_cast<uint8_t>(bs.readUE());\n   if (sps->seq_parameter_set_id >= MaxSeqParameterSets) {\n      return H264Error::InvalidSps;\n   }\n\n   if (sps->profile_idc == 100 || sps->profile_idc == 110 ||\n       sps->profile_idc == 122 || sps->profile_idc == 144) {\n      sps->chroma_format_idc = static_cast<uint8_t>(bs.readUE());\n      if (sps->chroma_format_idc == 3) {\n         sps->residual_colour_transform_flag = bs.readU1();\n      }\n\n      sps->bit_depth_luma_minus8 = static_cast<uint8_t>(bs.readUE());\n      sps->bit_depth_chroma_minus8 = static_cast<uint8_t>(bs.readUE());\n      sps->qpprime_y_zero_transform_bypass_flag = bs.readU1();\n      sps->seq_scaling_matrix_present_flag = bs.readU1();\n\n      if (sps->seq_scaling_matrix_present_flag) {\n         for (auto i = 0u; i < 8; ++i) {\n            auto seq_scaling_list_present_flag = bs.readU1();\n            if (seq_scaling_list_present_flag) {\n               if (i < 6) {\n                  readScalingList(bs, (virt_addrof(sps->scalingList4x4) + 16 * i).get(), 16);\n               } else {\n                  readScalingList(bs, (virt_addrof(sps->scalingList8x8) + 64 * i).get(), 64);\n               }\n            }\n         }\n      }\n   }\n\n   sps->log2_max_frame_num_minus4 = static_cast<uint16_t>(bs.readUE());\n   if (sps->log2_max_frame_num_minus4 > 12) {\n      return H264Error::InvalidSps;\n   }\n\n   sps->log2_max_frame_num = static_cast<uint8_t>(sps->log2_max_frame_num_minus4 + 4);\n   sps->max_frame_num = 1u << sps->log2_max_frame_num;\n\n   sps->pic_order_cnt_type = static_cast<uint16_t>(bs.readUE());\n   if (sps->pic_order_cnt_type > 2) {\n      return H264Error::InvalidSps;\n   }\n\n   if (sps->pic_order_cnt_type == 0) {\n      sps->log2_max_pic_order_cnt_lsb_minus4 = static_cast<uint16_t>(bs.readUE());\n      sps->max_pic_order_cnt_lsb = 1u << (sps->log2_max_pic_order_cnt_lsb_minus4 + 4);\n   } else if (sps->pic_order_cnt_type == 1) {\n      sps->delta_pic_order_always_zero_flag = bs.readU1();\n      sps->offset_for_non_ref_pic = bs.readSE();\n      sps->offset_for_top_to_bottom_field = bs.readSE();\n      sps->num_ref_frames_in_pic_order_cnt_cycle = static_cast<uint16_t>(bs.readUE());\n\n      for (int i = 0; i < sps->num_ref_frames_in_pic_order_cnt_cycle; ++i) {\n         sps->offset_for_ref_frame[i] = bs.readSE();\n      }\n   }\n\n   sps->num_ref_frames = static_cast<uint16_t>(bs.readUE());\n   if (sps->num_ref_frames > 16) {\n      return H264Error::InvalidSps;\n   }\n\n   sps->gaps_in_frame_num_value_allowed_flag = bs.readU1();\n   sps->pic_width_in_mbs = static_cast<uint16_t>(bs.readUE() + 1);\n   sps->pic_width = static_cast<uint16_t>(16 * sps->pic_width_in_mbs);\n   sps->pic_height_in_map_units = static_cast<uint16_t>(bs.readUE() + 1);\n   sps->pic_height = static_cast<uint16_t>(16 * sps->pic_height_in_map_units);\n   sps->pic_size_in_mbs = static_cast<uint16_t>(sps->pic_width_in_mbs * sps->pic_height_in_map_units);\n   sps->frame_mbs_only_flag = bs.readU1();\n   if (!sps->frame_mbs_only_flag) {\n      sps->mb_adaptive_frame_field_flag = bs.readU1();\n   }\n   sps->unk0x7B8 = static_cast<uint16_t>((2 - sps->frame_mbs_only_flag) * sps->pic_height_in_map_units);\n\n   sps->direct_8x8_inference_flag = bs.readU1();\n   sps->frame_cropping_flag = bs.readU1();\n   if (sps->frame_cropping_flag) {\n      sps->frame_crop_left_offset = static_cast<uint16_t>(bs.readUE());\n      sps->frame_crop_right_offset = static_cast<uint16_t>(bs.readUE());\n      sps->frame_crop_top_offset = static_cast<uint16_t>(bs.readUE());\n      sps->frame_crop_bottom_offset = static_cast<uint16_t>(bs.readUE());\n   }\n\n   if (sps->level_idc > 51) {\n      sps->level_idc = uint16_t { 51 };\n   }\n\n   sps->unk0x7B0 = uint16_t { 5 };\n   sps->vui_parameters_present_flag = bs.readU1();\n   if (sps->vui_parameters_present_flag) {\n      readVuiParameters(bs, sps);\n   }\n\n   if (sps->unk0x7B0 > 5) {\n      sps->unk0x7B0 = uint16_t { 5 };\n   }\n\n   rbspReadTrailingBits(bs);\n\n   sps->valid = uint8_t { 1 };\n   sps->unk0x7CC = static_cast<uint16_t>(sps->pic_width_in_mbs * sps->unk0x7B8);\n   sps->unk0x7C6 = static_cast<uint16_t>(sps->pic_width_in_mbs * sps->pic_height_in_map_units);\n\n   if (outSps) {\n      *outSps = *sps;\n   } else {\n      streamMemory->spsTable[sps->seq_parameter_set_id] = *sps;\n   }\n\n   if (outBytesRead) {\n      *outBytesRead = bs.bytesRead();\n   }\n\n   return H264Error::OK;\n}\n\nstatic H264Error\nreadPictureParameterSet(virt_ptr<H264StreamMemory> streamMemory,\n                        const uint8_t *buffer,\n                        uint32_t bufferSize,\n                        size_t *outBytesRead)\n{\n   auto bs = BitStream { buffer, bufferSize };\n   auto pic_parameter_set_id = bs.readUE();\n   if (pic_parameter_set_id >= MaxPicParameterSets) {\n      return H264Error::InvalidPps;\n   }\n\n   auto pps = virt_addrof(streamMemory->ppsTable[pic_parameter_set_id]);\n   clearPictureParameterSet(pps);\n\n   pps->seq_parameter_set_id = static_cast<uint16_t>(bs.readUE());\n   if (pps->seq_parameter_set_id >= MaxSeqParameterSets) {\n      return H264Error::InvalidPps;\n   }\n\n   pps->entropy_coding_mode_flag = bs.readU1();\n   pps->pic_order_present_flag = bs.readU1();\n   pps->num_slice_groups_minus1 = static_cast<uint16_t>(bs.readUE());\n   if (pps->num_slice_groups_minus1 > 7) {\n      return H264Error::InvalidPps;\n   }\n\n   if (pps->num_slice_groups_minus1 > 0) {\n      pps->slice_group_map_type = static_cast<uint16_t>(bs.readUE());\n      if (pps->slice_group_map_type == 0) {\n         for (int i = 0u; i <= pps->num_slice_groups_minus1; ++i) {\n            pps->run_length_minus1[i] = static_cast<uint16_t>(bs.readUE());\n         }\n      } else if (pps->slice_group_map_type == 2) {\n         for (auto i = 0u; i < pps->num_slice_groups_minus1; i++) {\n            pps->top_left[i] = static_cast<uint16_t>(bs.readUE());\n            pps->bottom_right[i] = static_cast<uint16_t>(bs.readUE());\n         }\n      } else if (pps->slice_group_map_type == 3 ||\n                 pps->slice_group_map_type == 4 ||\n                 pps->slice_group_map_type == 5) {\n         pps->slice_group_change_direction_flag = bs.readU1();\n         pps->slice_group_change_rate_minus1 = static_cast<uint16_t>(bs.readUE());\n      } else if (pps->slice_group_map_type == 6) {\n         pps->pic_size_in_map_units_minus1 = static_cast<uint16_t>(bs.readUE());\n         if (pps->pic_size_in_map_units_minus1 > 30800) {\n            return H264Error::InvalidPps;\n         }\n\n         auto sliceGroupIdNumBits = static_cast<size_t>(std::log2(pps->num_slice_groups_minus1 + 1));\n         for (int i = 0; i <= pps->pic_size_in_map_units_minus1; i++) {\n            // pps->slice_group_id\n            bs.readU(sliceGroupIdNumBits);\n         }\n      }\n   }\n   pps->num_ref_idx_l0_active = static_cast<uint8_t>(bs.readUE() + 1);\n   if (pps->num_ref_idx_l0_active > 31) {\n      return H264Error::InvalidPps;\n   }\n\n   pps->num_ref_idx_l1_active = static_cast<uint8_t>(bs.readUE() + 1);\n   if (pps->num_ref_idx_l1_active > 31) {\n      return H264Error::InvalidPps;\n   }\n\n   pps->weighted_pred_flag = bs.readU1();\n   pps->weighted_bipred_idc = bs.readU2();\n   pps->pic_init_qp_minus26 = static_cast<int16_t>(bs.readSE());\n   pps->pic_init_qs_minus26 = static_cast<int16_t>(bs.readSE());\n   pps->chroma_qp_index_offset = static_cast<int16_t>(bs.readSE());\n   pps->deblocking_filter_control_present_flag = bs.readU1();\n   pps->constrained_intra_pred_flag = bs.readU1();\n   pps->redundant_pic_cnt_present_flag = bs.readU1();\n\n   auto &sps = streamMemory->spsTable[pps->seq_parameter_set_id];\n   if ((sps.profile_idc == 100 || sps.profile_idc == 110 ||\n        sps.profile_idc == 122 || sps.profile_idc == 144)\n       && rbspHasMoreData(bs)) {\n      pps->transform_8x8_mode_flag = bs.readU1();\n      pps->pic_scaling_matrix_present_flag = bs.readU1();\n\n      if (pps->pic_scaling_matrix_present_flag) {\n         for (auto i = 0; i < 6 + 2 * pps->transform_8x8_mode_flag; i++) {\n            pps->pic_scaling_list_present_flag[i] = bs.readU1();\n            if (pps->pic_scaling_list_present_flag[i]) {\n               if (i < 6) {\n                  readScalingList(bs, (virt_addrof(pps->scalingList4x4) + 16 * i).get(), 16);\n               } else {\n                  readScalingList(bs, (virt_addrof(pps->scalingList8x8) + 64 * i).get(), 64);\n               }\n            }\n         }\n      }\n\n      pps->second_chroma_qp_index_offset = static_cast<int16_t>(bs.readSE());\n   } else {\n      pps->second_chroma_qp_index_offset = pps->chroma_qp_index_offset;\n   }\n\n   pps->valid = uint8_t { 1 };\n   rbspReadTrailingBits(bs);\n   streamMemory->ppsTable[pic_parameter_set_id] = *pps;\n\n   if (outBytesRead) {\n      *outBytesRead = bs.bytesRead();\n   }\n\n   return H264Error::OK;\n}\n\nstatic uint8_t\ndecodeSliceType(uint32_t sliceType)\n{\n   if (sliceType <= 2u) {\n      return static_cast<uint8_t>(sliceType);\n   } else if (sliceType >= 5u && sliceType <= 7u) {\n      return static_cast<uint8_t>(sliceType - 5);\n   }\n\n   return 0;\n}\n\nstatic H264Error\nreadSliceHeader(virt_ptr<H264StreamMemory> streamMemory,\n                NaluType naluType,\n                const uint8_t *buffer,\n                uint32_t bufferSize,\n                virt_ptr<H264SliceHeader> slice,\n                size_t *outBytesRead)\n{\n   auto bs = BitStream { buffer, bufferSize };\n   slice->field_02 = uint8_t { 1 };\n   slice->first_mb_in_slice = static_cast<uint16_t>(bs.readUE());\n   slice->slice_type = decodeSliceType(bs.readUE());\n   slice->pic_parameter_set_id = static_cast<uint16_t>(bs.readUE());\n   if (slice->pic_parameter_set_id >= MaxPicParameterSets) {\n      return H264Error::InvalidSliceHeader;\n   }\n\n   auto &pps = streamMemory->ppsTable[slice->pic_parameter_set_id];\n   if (!pps.valid) {\n      return H264Error::InvalidSliceHeader;\n   }\n\n   auto &sps = streamMemory->spsTable[pps.seq_parameter_set_id];\n   if (!sps.valid) {\n      return H264Error::InvalidSliceHeader;\n   }\n\n   streamMemory->currentSps = sps;\n   streamMemory->currentPps = pps;\n\n   // TODO: More error checking...\n\n   slice->frame_num = static_cast<uint16_t>(bs.readU(sps.log2_max_frame_num));\n\n   if (!sps.frame_mbs_only_flag) {\n      slice->field_pic_flag = bs.readU1();\n\n      if (slice->field_pic_flag) {\n         slice->bottom_field_flag = bs.readU1();\n      }\n   }\n\n   if (naluType == NaluType::Idr) {\n      slice->idr_pic_id = bs.readUE();\n   }\n\n   if (sps.pic_order_cnt_type == 0) {\n      slice->pic_order_cnt_lsb = bs.readU(sps.log2_max_pic_order_cnt_lsb_minus4 + 4);\n\n      if (pps.pic_order_present_flag && !slice->field_pic_flag) {\n         slice->delta_pic_order_cnt_bottom = bs.readSE();\n      } else {\n         slice->delta_pic_order_cnt_bottom = 0;\n      }\n   }\n\n   if (sps.pic_order_cnt_type == 1 && !sps.delta_pic_order_always_zero_flag) {\n      slice->delta_pic_order_cnt[0] = bs.readSE();\n\n      if (pps.pic_order_present_flag && !slice->field_pic_flag) {\n         slice->delta_pic_order_cnt[1] = bs.readSE();\n      }\n   }\n\n   if (pps.redundant_pic_cnt_present_flag) {\n      slice->redundant_pic_cnt = static_cast<uint16_t>(bs.readUE());\n      if (slice->redundant_pic_cnt > 137) {\n         return H264Error::InvalidSliceHeader;\n      }\n   }\n\n   if (slice->slice_type == SliceType::B) {\n      slice->direct_spatial_mv_pred_flag = bs.readU1();\n   }\n\n   if (slice->slice_type == SliceType::P || slice->slice_type == SliceType::B) {\n      slice->num_ref_idx_active_override_flag = bs.readU1();\n      if (slice->num_ref_idx_active_override_flag) {\n         slice->num_ref_idx_l0_active = static_cast<uint8_t>(bs.readUE() + 1);\n         if (slice->num_ref_idx_l0_active > 31) {\n            return H264Error::InvalidSliceHeader;\n         }\n\n         if (slice->num_ref_idx_l0_active) {\n            slice->num_ref_idx_l1_active = static_cast<uint8_t>(bs.readUE() + 1);\n\n            if (slice->num_ref_idx_l1_active > 31) {\n               return H264Error::InvalidSliceHeader;\n            }\n         }\n      }\n   }\n\n   *outBytesRead = bs.bytesRead();\n   return H264Error::OK;\n}\n\n/**\n * Find and decode SPS from a NALU stream.\n */\nH264Error\ndecodeNaluSps(const uint8_t *buffer,\n              int bufferLength,\n              int offset,\n              virt_ptr<H264SequenceParameterSet> sps)\n{\n   // Find SPS\n   while (offset + 4 < bufferLength) {\n      if (buffer[offset + 0] == 0 &&\n          buffer[offset + 1] == 0 &&\n          buffer[offset + 2] == 1 &&\n          H264NaluHeader::get(buffer[offset + 3]).type() == NaluType::Sps) {\n         break;\n      }\n\n      ++offset;\n   }\n\n   offset += 4;\n   if (offset >= bufferLength) {\n      return H264Error::GenericError;\n   }\n\n   // Decode SPS\n   internal::readSequenceParameterSet(nullptr,\n                                      buffer + offset,\n                                      bufferLength - offset,\n                                      sps,\n                                      nullptr);\n   return H264Error::OK;\n}\n\n} // namespace internal\n\n\n/**\n * Check that the stream contains sufficient data to decode an entire frame.\n */\nH264Error\nH264DECCheckDecunitLength(virt_ptr<void> memory,\n                          virt_ptr<const uint8_t> buffer,\n                          int32_t bufferLength,\n                          int32_t offset,\n                          virt_ptr<int32_t> outLength)\n{\n   if (!memory || !buffer || !outLength || bufferLength < 4 || offset < 0 ||\n       bufferLength <= offset) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto workMemory = internal::getWorkMemory(memory);\n   auto readSlice = false;\n   auto readField = false;\n   auto readFrameStart = false;\n   auto readFrameEnd = false;\n   auto start = offset;\n\n   while (offset < bufferLength - 4) {\n      // Search for NALU header\n      if (buffer[offset + 0] != 0 || buffer[offset + 1] != 0 ||\n          buffer[offset + 2] != 1) {\n         offset++;\n         continue;\n      }\n\n      auto naluType = H264NaluHeader::get(buffer[offset + 3]).type();\n      auto readBuffer = buffer.get() + (offset + 4);\n      auto readLength = bufferLength - (offset + 4);\n\n      if (naluType == NaluType::Idr || naluType == NaluType::NonIdr) {\n         auto bytesRead = size_t { 0 };\n         auto slice = StackObject<H264SliceHeader> { };\n\n         if (internal::readSliceHeader(workMemory->streamMemory, naluType,\n                                       readBuffer, readLength,\n                                       slice, &bytesRead)) {\n            return H264Error::InvalidParameter;\n         }\n\n         if (readSlice && slice->first_mb_in_slice == 0) {\n            // If we have read a slice and first_mb_in_slice == 0 then this\n            // slice is part of the next frame.\n            readFrameEnd = true;\n         } else if (slice->field_pic_flag == 0) {\n            // If this is not a field, then we have a frame, but we still need\n            // to find the end.\n            readFrameStart = true;\n         } else if (readField) {\n            // If this is the second field, then we have a frame, but we still\n            // need to find the end.\n            readFrameStart = true;\n         } else {\n            // First field, we need to read next field.\n            readField = true;\n         }\n\n         readSlice = true;\n\n         if (readFrameEnd) {\n            break;\n         }\n\n         offset += static_cast<int32_t>(bytesRead);\n      } else if (readFrameStart && naluType >= NaluType::Sps) {\n         // If we are looking for the frame end, and this is a non-slice NALU\n         // then we have found it!\n         readFrameEnd = true;\n         break;\n      }\n\n      if (naluType == NaluType::Sps) {\n         auto bytesRead = size_t { 0 };\n\n         if (internal::readSequenceParameterSet(workMemory->streamMemory,\n                                                readBuffer, readLength,\n                                                nullptr, &bytesRead)) {\n            return H264Error::InvalidParameter;\n         }\n\n         offset += static_cast<int32_t>(bytesRead);\n      } else if (naluType == NaluType::Pps) {\n         auto bytesRead = size_t { 0 };\n\n         if (internal::readPictureParameterSet(workMemory->streamMemory,\n                                               readBuffer, readLength,\n                                               &bytesRead)) {\n            return H264Error::InvalidParameter;\n         }\n\n         offset += static_cast<int32_t>(bytesRead);\n      }\n\n      offset += 4;\n   }\n\n   if (!readFrameEnd) {\n      return H264Error::GenericError;\n   }\n\n   if (offset >= bufferLength) {\n      return H264Error::GenericError;\n   }\n\n   // We scan for 0 0 1, but it could be 0 0 0 1\n   if (offset > 0 && buffer[offset] == 0) {\n      --offset;\n   }\n\n   *outLength = offset - start;\n\n   auto bs = BitStream {\n      buffer.get() + offset,\n      static_cast<size_t>(bufferLength - offset)\n   };\n   bs.readUE();\n   if (bs.eof()) {\n      return H264Error::InvalidParameter;\n   }\n\n   bs.readUE();\n   if (bs.eof()) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto unkValue = bs.readUE();\n   if (bs.eof()) {\n      return H264Error::InvalidParameter;\n   }\n\n   if (unkValue >= 0x100) {\n      return H264Error::GenericError;\n   }\n\n   return H264Error::OK;\n}\n\n\n/**\n * Check if the next NALU can be skipped without breaking decoding.\n */\nH264Error\nH264DECCheckSkipableFrame(virt_ptr<const uint8_t> buffer,\n                          int32_t bufferLength,\n                          virt_ptr<BOOL> outSkippable)\n{\n   if (!buffer || bufferLength < 4 || !outSkippable) {\n      return H264Error::InvalidParameter;\n   }\n\n   *outSkippable = TRUE;\n\n   for (auto i = 0; i < bufferLength - 4; ++i) {\n      if (buffer[i + 0] == 0 &&\n          buffer[i + 1] == 0 &&\n          buffer[i + 2] == 1 &&\n          H264NaluHeader::get(buffer[i + 3]).refIdc()) {\n         *outSkippable = FALSE;\n         return H264Error::OK;\n      }\n   }\n\n   return H264Error::OK;\n}\n\n\n/**\n * Find the first SPS in the stream.\n */\nH264Error\nH264DECFindDecstartpoint(virt_ptr<const uint8_t> buffer,\n                         int32_t bufferLength,\n                         virt_ptr<int32_t> outOffset)\n{\n   if (!buffer || bufferLength < 4 || !outOffset) {\n      return H264Error::InvalidParameter;\n   }\n\n   for (auto i = 0; i < bufferLength - 4; i++) {\n      if (buffer[i + 0] == 0 &&\n          buffer[i + 1] == 0 &&\n          buffer[i + 2] == 1 &&\n          H264NaluHeader::get(buffer[i + 3]).type() == NaluType::Sps) {\n         *outOffset = std::max(i - 1, 0);\n         return H264Error::OK;\n      }\n   }\n\n   return H264Error::GenericError;\n}\n\n\n/**\n * Find the first \"IDR point\" in the stream.\n *\n * An IDR point is either:\n *  - If an SPS or PPS header is found before the IDR and there are no non-IDR\n *    inbetween the SPS/PPS and IDR then return the first of the SPS/PPS.\n *  - The first found IDR.\n */\nint32_t\nH264DECFindIdrpoint(virt_ptr<const uint8_t> buffer,\n                    int32_t bufferLength,\n                    virt_ptr<int32_t> outOffset)\n{\n   if (!buffer || bufferLength < 4 || !outOffset) {\n      return H264Error::InvalidParameter;\n   }\n\n   auto ppsOffset = int32_t { -1 };\n   auto spsOffset = int32_t { -1 };\n   for (auto i = 0; i < bufferLength - 4; i++) {\n      if (buffer[i + 0] != 0 || buffer[i + 1] != 0 || buffer[i + 2] != 1) {\n         continue;\n      }\n\n      auto type = H264NaluHeader::get(buffer[i + 3]).type();\n      if (type == NaluType::NonIdr) {\n         // When we encounter a non-idr frame reset the pps / sps offset\n         ppsOffset = -1;\n         spsOffset = -1;\n      } else if (type == NaluType::Pps) {\n         ppsOffset = i - 1;\n      } else if (type == NaluType::Sps) {\n         spsOffset = i - 1;\n      } else if (type == NaluType::Idr) {\n         // Found an IDR frame!\n         auto offset = std::max(i - 1, 0);\n\n         if (ppsOffset >= 0 && spsOffset >= 0) {\n            offset = std::min(ppsOffset, spsOffset);\n         } else if (ppsOffset >= 0) {\n            offset = ppsOffset;\n         } else if (spsOffset >= 0) {\n            offset = spsOffset;\n         }\n\n         return H264Error::OK;\n      }\n   }\n\n   return H264Error::GenericError;\n}\n\n\n/**\n * Parse the H264 stream and read the width & height from the first found SPS.\n */\nH264Error\nH264DECGetImageSize(virt_ptr<const uint8_t> buffer,\n                    int32_t bufferLength,\n                    int32_t offset,\n                    virt_ptr<int32_t> outWidth,\n                    virt_ptr<int32_t> outHeight)\n{\n   // Check parameters\n   if (!buffer || !outWidth || !outHeight || bufferLength < 4 || offset < 0 ||\n       bufferLength <= offset) {\n      return H264Error::InvalidParameter;\n   }\n\n   // Find and parse SPS\n   auto sps = StackObject<H264SequenceParameterSet> { };\n   auto result = internal::decodeNaluSps(buffer.get(), bufferLength,\n                                         offset, sps);\n   if (result != H264Error::OK) {\n      return result;\n   }\n\n   // Set output\n   *outWidth = sps->pic_width;\n   *outHeight = sps->pic_height;\n\n   if (!sps->frame_mbs_only_flag) {\n      *outHeight = 2 * sps->pic_height;\n   }\n\n   if (!*outWidth || !*outHeight) {\n      return H264Error::GenericError;\n   }\n\n   return H264Error::OK;\n}\n\nvoid\nLibrary::registerStreamSymbols()\n{\n   RegisterFunctionExport(H264DECCheckDecunitLength);\n   RegisterFunctionExport(H264DECCheckSkipableFrame);\n   RegisterFunctionExport(H264DECFindDecstartpoint);\n   RegisterFunctionExport(H264DECFindIdrpoint);\n   RegisterFunctionExport(H264DECGetImageSize);\n}\n\n} // namespace cafe::h264\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/h264/h264_stream.h",
    "content": "#pragma once\n#include \"h264_enum.h\"\n\n#include <common/bitfield.h>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::h264\n{\n\nstatic constexpr auto MaxPicParameterSets = 256;\nstatic constexpr auto MaxSeqParameterSets = 32;\nstatic constexpr auto MaxBufferedFrames = 5;\nstatic constexpr auto SarExtended = uint8_t { 255 };\n\n#pragma pack(push, 1)\n\nstruct H264Bitstream;\nstruct H264DecodeOutput;\nstruct H264DecodedFrameInfo;\nstruct H264DecodedVuiParameters;\nstruct H264PictureParameterSet;\nstruct H264SequenceParameterSet;\nstruct H264SliceHeader;\nstruct H264StreamMemory;\n\nusing H264DECFptrOutputFn = virt_func_ptr<\n   void (virt_ptr<H264DecodeOutput> output)>;\n\nstruct H264Bitstream\n{\n   be2_virt_ptr<uint8_t> buffer;\n   be2_val<uint32_t> buffer_length;\n   be2_val<uint32_t> bit_position;\n\n   //! Actually stored at +0x8F0 but that would go over into other structs\n   //! memory..?\n   be2_val<double> timestamp;\n};\nCHECK_OFFSET(H264Bitstream, 0x00, buffer);\nCHECK_OFFSET(H264Bitstream, 0x04, buffer_length);\nCHECK_OFFSET(H264Bitstream, 0x08, bit_position);\n\nBITFIELD_BEG(H264NaluHeader, uint8_t)\n   BITFIELD_ENTRY(7, 1, bool, forbiddenZeroBit)\n   BITFIELD_ENTRY(5, 2, uint8_t, refIdc)\n   BITFIELD_ENTRY(0, 5, NaluType, type)\nBITFIELD_END\n\nstruct H264PictureParameterSet\n{\n   be2_val<uint8_t> valid;\n   UNKNOWN(1);\n   be2_val<uint16_t> seq_parameter_set_id;\n   be2_val<uint8_t> entropy_coding_mode_flag;\n   be2_val<uint8_t> pic_order_present_flag;\n   be2_val<uint16_t> num_slice_groups_minus1;\n   be2_val<uint16_t> slice_group_map_type;\n   be2_val<uint16_t> slice_group_change_direction_flag;\n   be2_val<uint16_t> slice_group_change_rate_minus1;\n   be2_val<uint16_t> pic_size_in_map_units_minus1;\n   be2_virt_ptr<void> ptr_to_slice_group_id;\n   be2_val<uint8_t> num_ref_idx_l0_active;\n   be2_val<uint8_t> num_ref_idx_l1_active;\n   be2_val<uint8_t> weighted_pred_flag;\n   be2_val<uint8_t> weighted_bipred_idc;\n   be2_val<int16_t> pic_init_qp_minus26;\n   be2_val<int16_t> pic_init_qs_minus26;\n   be2_val<int16_t> chroma_qp_index_offset;\n   be2_val<uint8_t> deblocking_filter_control_present_flag;\n   be2_val<uint8_t> constrained_intra_pred_flag;\n   be2_val<uint8_t> redundant_pic_cnt_present_flag;\n   be2_val<uint8_t> transform_8x8_mode_flag;\n   be2_val<uint8_t> pic_scaling_matrix_present_flag;\n   be2_array<uint8_t, 8> pic_scaling_list_present_flag;\n   be2_array<uint8_t, 6 * 16> scalingList4x4;\n   be2_array<uint8_t, 2 * 64> scalingList8x8;\n   UNKNOWN(1);\n   be2_val<int16_t> second_chroma_qp_index_offset;\n   UNKNOWN(4);\n   be2_array<uint16_t, 10> run_length_minus1;\n   be2_array<uint16_t, 10> top_left;\n   be2_array<uint16_t, 10> bottom_right;\n   UNKNOWN(2);\n};\nCHECK_OFFSET(H264PictureParameterSet, 0x000, valid);\nCHECK_OFFSET(H264PictureParameterSet, 0x002, seq_parameter_set_id);\nCHECK_OFFSET(H264PictureParameterSet, 0x004, entropy_coding_mode_flag);\nCHECK_OFFSET(H264PictureParameterSet, 0x005, pic_order_present_flag);\nCHECK_OFFSET(H264PictureParameterSet, 0x006, num_slice_groups_minus1);\nCHECK_OFFSET(H264PictureParameterSet, 0x008, slice_group_map_type);\nCHECK_OFFSET(H264PictureParameterSet, 0x00A, slice_group_change_direction_flag);\nCHECK_OFFSET(H264PictureParameterSet, 0x00C, slice_group_change_rate_minus1);\nCHECK_OFFSET(H264PictureParameterSet, 0x00E, pic_size_in_map_units_minus1);\nCHECK_OFFSET(H264PictureParameterSet, 0x010, ptr_to_slice_group_id);\nCHECK_OFFSET(H264PictureParameterSet, 0x014, num_ref_idx_l0_active);\nCHECK_OFFSET(H264PictureParameterSet, 0x015, num_ref_idx_l1_active);\nCHECK_OFFSET(H264PictureParameterSet, 0x016, weighted_pred_flag);\nCHECK_OFFSET(H264PictureParameterSet, 0x017, weighted_bipred_idc);\nCHECK_OFFSET(H264PictureParameterSet, 0x018, pic_init_qp_minus26);\nCHECK_OFFSET(H264PictureParameterSet, 0x01A, pic_init_qs_minus26);\nCHECK_OFFSET(H264PictureParameterSet, 0x01C, chroma_qp_index_offset);\nCHECK_OFFSET(H264PictureParameterSet, 0x01E, deblocking_filter_control_present_flag);\nCHECK_OFFSET(H264PictureParameterSet, 0x01F, constrained_intra_pred_flag);\nCHECK_OFFSET(H264PictureParameterSet, 0x020, redundant_pic_cnt_present_flag);\nCHECK_OFFSET(H264PictureParameterSet, 0x021, transform_8x8_mode_flag);\nCHECK_OFFSET(H264PictureParameterSet, 0x022, pic_scaling_matrix_present_flag);\nCHECK_OFFSET(H264PictureParameterSet, 0x023, pic_scaling_list_present_flag);\nCHECK_OFFSET(H264PictureParameterSet, 0x02B, scalingList4x4);\nCHECK_OFFSET(H264PictureParameterSet, 0x08B, scalingList8x8);\nCHECK_OFFSET(H264PictureParameterSet, 0x10C, second_chroma_qp_index_offset);\nCHECK_OFFSET(H264PictureParameterSet, 0x112, run_length_minus1);\nCHECK_OFFSET(H264PictureParameterSet, 0x126, top_left);\nCHECK_OFFSET(H264PictureParameterSet, 0x13A, bottom_right);\nCHECK_SIZE(H264PictureParameterSet, 0x150);\n\nstruct H264SequenceParameterSet\n{\n   be2_val<uint8_t> valid;\n   UNKNOWN(0x1);\n   be2_val<uint16_t> profile_idc;\n   be2_val<uint16_t> level_idc;\n   be2_val<uint8_t> constraint_set;\n   be2_val<uint8_t> seq_parameter_set_id;\n   be2_val<uint8_t> chroma_format_idc;\n   be2_val<uint8_t> residual_colour_transform_flag;\n   be2_val<uint8_t> bit_depth_luma_minus8;\n   be2_val<uint8_t> bit_depth_chroma_minus8;\n   be2_val<uint8_t> qpprime_y_zero_transform_bypass_flag;\n   be2_val<uint8_t> seq_scaling_matrix_present_flag;\n   be2_array<uint8_t, 6 * 16> scalingList4x4;\n   be2_array<uint8_t, 2 * 64> scalingList8x8;\n   be2_val<uint16_t> log2_max_frame_num_minus4;\n   be2_val<uint16_t> pic_order_cnt_type;\n   be2_val<uint16_t> log2_max_pic_order_cnt_lsb_minus4;\n   be2_val<uint16_t> delta_pic_order_always_zero_flag;\n   PADDING(0x2);\n   be2_val<int32_t> offset_for_non_ref_pic;\n   be2_val<int32_t> offset_for_top_to_bottom_field;\n   be2_val<uint16_t> num_ref_frames_in_pic_order_cnt_cycle;\n   PADDING(0x2);\n   be2_array<int32_t, 256> offset_for_ref_frame;\n   be2_val<uint16_t> num_ref_frames;\n   be2_val<uint8_t> gaps_in_frame_num_value_allowed_flag;\n   PADDING(0x1);\n   be2_val<uint16_t> pic_width_in_mbs;\n   be2_val<uint16_t> pic_height_in_map_units;\n   be2_val<uint8_t> frame_mbs_only_flag;\n   be2_val<uint8_t> mb_adaptive_frame_field_flag;\n   be2_val<uint8_t> direct_8x8_inference_flag;\n   be2_val<uint8_t> frame_cropping_flag;\n   be2_val<uint16_t> frame_crop_left_offset;\n   be2_val<uint16_t> frame_crop_right_offset;\n   be2_val<uint16_t> frame_crop_top_offset;\n   be2_val<uint16_t> frame_crop_bottom_offset;\n   be2_val<uint8_t> vui_parameters_present_flag;\n   be2_val<uint8_t> vui_aspect_ratio_info_present_flag;\n   be2_val<uint8_t> vui_aspect_ratio_idc;\n   PADDING(0x1);\n   be2_val<uint16_t> vui_sar_width;\n   be2_val<uint16_t> vui_sar_height;\n   be2_val<uint8_t> vui_overscan_info_present_flag;\n   be2_val<uint8_t> vui_overscan_appropriate_flag;\n   be2_val<uint8_t> vui_video_signal_type_present_flag;\n   be2_val<uint8_t> vui_video_format;\n   be2_val<uint8_t> vui_video_full_range_flag;\n   be2_val<uint8_t> vui_colour_description_present_flag;\n   be2_val<uint8_t> vui_colour_primaries;\n   be2_val<uint8_t> vui_transfer_characteristics;\n   be2_val<uint8_t> vui_matrix_coefficients;\n   be2_val<uint8_t> vui_chroma_loc_info_present_flag;\n   be2_val<uint8_t> vui_chroma_sample_loc_type_top_field;\n   be2_val<uint8_t> vui_chroma_sample_loc_type_bottom_field;\n   be2_val<uint8_t> vui_timing_info_present_flag;\n   PADDING(0x3);\n   be2_val<uint32_t> vui_num_units_in_tick;\n   be2_val<uint32_t> vui_time_scale;\n   be2_val<uint8_t> vui_fixed_frame_rate_flag;\n   be2_val<uint8_t> vui_nal_hrd_parameters_present_flag;\n   be2_val<uint8_t> vui_vcl_hrd_parameters_present_flag;\n   PADDING(0x1);\n   be2_val<uint16_t> hrd_cpb_cnt_minus1;\n   be2_val<uint16_t> hrd_bit_rate_scale;\n   be2_val<uint16_t> hrd_cpb_size_scale;\n   be2_array<uint16_t, 100> hrd_bit_rate_value_minus1;\n   be2_array<uint16_t, 100> hrd_cpb_size_value_minus1;\n   be2_array<uint16_t, 100> hrd_cbr_flag;\n   be2_val<uint16_t> hrd_initial_cpb_removal_delay_length_minus1;\n   be2_val<uint16_t> hrd_cpb_removal_delay_length_minus1;\n   be2_val<uint16_t> hrd_dpb_output_delay_length_minus1;\n   be2_val<uint16_t> hrd_time_offset_length;\n   be2_val<uint8_t> vui_low_delay_hrd_flag;\n   be2_val<uint8_t> vui_pic_struct_present_flag;\n   be2_val<uint8_t> vui_bitstream_restriction_flag;\n   be2_val<uint8_t> vui_motion_vectors_over_pic_boundaries_flag;\n   be2_val<uint16_t> vui_max_bytes_per_pic_denom;\n   be2_val<uint16_t> vui_max_bits_per_mb_denom;\n   be2_val<uint16_t> vui_log2_max_mv_length_horizontal;\n   be2_val<uint16_t> vui_log2_max_mv_length_vertical;\n   be2_val<uint16_t> vui_num_reorder_frames;\n   be2_val<uint16_t> unk0x7B0;\n   be2_val<uint16_t> vui_max_dec_frame_buffering;\n   be2_val<uint32_t> max_pic_order_cnt_lsb;\n   be2_val<uint16_t> unk0x7B8;\n   UNKNOWN(0x2);\n   be2_val<uint16_t> pic_size_in_mbs;\n   UNKNOWN(0x2);\n   be2_val<uint32_t> max_frame_num;\n   be2_val<uint8_t> log2_max_frame_num;\n   be2_val<uint8_t> unk0x7C5;\n   be2_val<uint16_t> unk0x7C6;\n   be2_val<uint16_t> pic_width;\n   be2_val<uint16_t> pic_height;\n   be2_val<uint16_t> unk0x7CC;\n   UNKNOWN(0x95E - 0x7CE);\n   be2_val<uint8_t> unk0x95E;\n   be2_val<uint8_t> unk0x95F;\n   be2_val<uint8_t> unk0x960;\n   UNKNOWN(0x978 - 0x961);\n};\nCHECK_OFFSET(H264SequenceParameterSet, 0x000, valid);\nCHECK_OFFSET(H264SequenceParameterSet, 0x002, profile_idc);\nCHECK_OFFSET(H264SequenceParameterSet, 0x004, level_idc);\nCHECK_OFFSET(H264SequenceParameterSet, 0x006, constraint_set);\nCHECK_OFFSET(H264SequenceParameterSet, 0x007, seq_parameter_set_id);\nCHECK_OFFSET(H264SequenceParameterSet, 0x008, chroma_format_idc);\nCHECK_OFFSET(H264SequenceParameterSet, 0x00A, bit_depth_luma_minus8);\nCHECK_OFFSET(H264SequenceParameterSet, 0x00B, bit_depth_chroma_minus8);\nCHECK_OFFSET(H264SequenceParameterSet, 0x00C, qpprime_y_zero_transform_bypass_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x00D, seq_scaling_matrix_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x00E, scalingList4x4);\nCHECK_OFFSET(H264SequenceParameterSet, 0x06E, scalingList8x8);\nCHECK_OFFSET(H264SequenceParameterSet, 0x0EE, log2_max_frame_num_minus4);\nCHECK_OFFSET(H264SequenceParameterSet, 0x0F0, pic_order_cnt_type);\nCHECK_OFFSET(H264SequenceParameterSet, 0x0F2, log2_max_pic_order_cnt_lsb_minus4);\nCHECK_OFFSET(H264SequenceParameterSet, 0x0F4, delta_pic_order_always_zero_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x0F8, offset_for_non_ref_pic);\nCHECK_OFFSET(H264SequenceParameterSet, 0x0FC, offset_for_top_to_bottom_field);\nCHECK_OFFSET(H264SequenceParameterSet, 0x100, num_ref_frames_in_pic_order_cnt_cycle);\nCHECK_OFFSET(H264SequenceParameterSet, 0x104, offset_for_ref_frame);\nCHECK_OFFSET(H264SequenceParameterSet, 0x504, num_ref_frames);\nCHECK_OFFSET(H264SequenceParameterSet, 0x506, gaps_in_frame_num_value_allowed_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x508, pic_width_in_mbs);\nCHECK_OFFSET(H264SequenceParameterSet, 0x50A, pic_height_in_map_units);\nCHECK_OFFSET(H264SequenceParameterSet, 0x50C, frame_mbs_only_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x50D, mb_adaptive_frame_field_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x50E, direct_8x8_inference_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x50F, frame_cropping_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x510, frame_crop_left_offset);\nCHECK_OFFSET(H264SequenceParameterSet, 0x512, frame_crop_right_offset);\nCHECK_OFFSET(H264SequenceParameterSet, 0x514, frame_crop_top_offset);\nCHECK_OFFSET(H264SequenceParameterSet, 0x516, frame_crop_bottom_offset);\nCHECK_OFFSET(H264SequenceParameterSet, 0x518, vui_parameters_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x519, vui_aspect_ratio_info_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x51A, vui_aspect_ratio_idc);\nCHECK_OFFSET(H264SequenceParameterSet, 0x51C, vui_sar_width);\nCHECK_OFFSET(H264SequenceParameterSet, 0x51E, vui_sar_height);\nCHECK_OFFSET(H264SequenceParameterSet, 0x520, vui_overscan_info_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x521, vui_overscan_appropriate_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x522, vui_video_signal_type_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x523, vui_video_format);\nCHECK_OFFSET(H264SequenceParameterSet, 0x524, vui_video_full_range_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x525, vui_colour_description_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x526, vui_colour_primaries);\nCHECK_OFFSET(H264SequenceParameterSet, 0x527, vui_transfer_characteristics);\nCHECK_OFFSET(H264SequenceParameterSet, 0x528, vui_matrix_coefficients);\nCHECK_OFFSET(H264SequenceParameterSet, 0x529, vui_chroma_loc_info_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x52A, vui_chroma_sample_loc_type_top_field);\nCHECK_OFFSET(H264SequenceParameterSet, 0x52B, vui_chroma_sample_loc_type_bottom_field);\nCHECK_OFFSET(H264SequenceParameterSet, 0x52C, vui_timing_info_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x530, vui_num_units_in_tick);\nCHECK_OFFSET(H264SequenceParameterSet, 0x534, vui_time_scale);\nCHECK_OFFSET(H264SequenceParameterSet, 0x538, vui_fixed_frame_rate_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x539, vui_nal_hrd_parameters_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x53A, vui_vcl_hrd_parameters_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x53C, hrd_cpb_cnt_minus1);\nCHECK_OFFSET(H264SequenceParameterSet, 0x53E, hrd_bit_rate_scale);\nCHECK_OFFSET(H264SequenceParameterSet, 0x540, hrd_cpb_size_scale);\nCHECK_OFFSET(H264SequenceParameterSet, 0x542, hrd_bit_rate_value_minus1);\nCHECK_OFFSET(H264SequenceParameterSet, 0x60A, hrd_cpb_size_value_minus1);\nCHECK_OFFSET(H264SequenceParameterSet, 0x6D2, hrd_cbr_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x79A, hrd_initial_cpb_removal_delay_length_minus1);\nCHECK_OFFSET(H264SequenceParameterSet, 0x79C, hrd_cpb_removal_delay_length_minus1);\nCHECK_OFFSET(H264SequenceParameterSet, 0x79E, hrd_dpb_output_delay_length_minus1);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7A0, hrd_time_offset_length);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7A2, vui_low_delay_hrd_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7A3, vui_pic_struct_present_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7A4, vui_bitstream_restriction_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7A5, vui_motion_vectors_over_pic_boundaries_flag);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7A6, vui_max_bytes_per_pic_denom);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7A8, vui_max_bits_per_mb_denom);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7AA, vui_log2_max_mv_length_horizontal);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7AC, vui_log2_max_mv_length_vertical);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7AE, vui_num_reorder_frames);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7B2, vui_max_dec_frame_buffering);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7B4, max_pic_order_cnt_lsb);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7BC, pic_size_in_mbs);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7C0, max_frame_num);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7C4, log2_max_frame_num);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7C8, pic_width);\nCHECK_OFFSET(H264SequenceParameterSet, 0x7CA, pic_height);\nCHECK_SIZE(H264SequenceParameterSet, 0x978);\n\nstruct H264SliceHeader\n{\n   UNKNOWN(2);\n   be2_val<uint8_t> field_02;\n   UNKNOWN(1);\n   be2_val<uint16_t> pic_parameter_set_id;\n   be2_val<uint16_t> frame_num;\n   be2_val<uint32_t> idr_pic_id;\n   be2_val<uint32_t> pic_order_cnt_lsb;\n   be2_val<int32_t> delta_pic_order_cnt_bottom;\n   be2_val<uint32_t> field_14;\n   be2_val<uint32_t> field_18;\n   be2_val<uint8_t> field_1C;\n   be2_val<uint8_t> field_1D;\n   be2_val<uint8_t> field_pic_flag;\n   be2_val<uint8_t> bottom_field_flag;\n   be2_val<uint16_t> field_20;\n   be2_val<uint16_t> first_mb_in_slice;\n   be2_val<uint8_t> slice_type;\n   be2_val<uint8_t> field_25;\n   be2_val<uint8_t> field_26;\n   be2_val<uint8_t> field_27;\n   be2_array<int32_t, 2> delta_pic_order_cnt;\n   be2_val<uint16_t> redundant_pic_cnt;\n   be2_val<uint8_t> direct_spatial_mv_pred_flag;\n   be2_val<uint8_t> num_ref_idx_active_override_flag;\n   be2_val<uint8_t> num_ref_idx_l0_active;\n   be2_val<uint8_t> num_ref_idx_l1_active;\n};\nCHECK_OFFSET(H264SliceHeader, 0x02, field_02);\nCHECK_OFFSET(H264SliceHeader, 0x04, pic_parameter_set_id);\nCHECK_OFFSET(H264SliceHeader, 0x06, frame_num);\nCHECK_OFFSET(H264SliceHeader, 0x08, idr_pic_id);\nCHECK_OFFSET(H264SliceHeader, 0x0C, pic_order_cnt_lsb);\nCHECK_OFFSET(H264SliceHeader, 0x10, delta_pic_order_cnt_bottom);\nCHECK_OFFSET(H264SliceHeader, 0x14, field_14);\nCHECK_OFFSET(H264SliceHeader, 0x18, field_18);\nCHECK_OFFSET(H264SliceHeader, 0x1C, field_1C);\nCHECK_OFFSET(H264SliceHeader, 0x1D, field_1D);\nCHECK_OFFSET(H264SliceHeader, 0x1E, field_pic_flag);\nCHECK_OFFSET(H264SliceHeader, 0x1F, bottom_field_flag);\nCHECK_OFFSET(H264SliceHeader, 0x20, field_20);\nCHECK_OFFSET(H264SliceHeader, 0x22, first_mb_in_slice);\nCHECK_OFFSET(H264SliceHeader, 0x24, slice_type);\nCHECK_OFFSET(H264SliceHeader, 0x25, field_25);\nCHECK_OFFSET(H264SliceHeader, 0x26, field_26);\nCHECK_OFFSET(H264SliceHeader, 0x27, field_27);\nCHECK_OFFSET(H264SliceHeader, 0x28, delta_pic_order_cnt);\nCHECK_OFFSET(H264SliceHeader, 0x30, redundant_pic_cnt);\nCHECK_OFFSET(H264SliceHeader, 0x32, direct_spatial_mv_pred_flag);\nCHECK_OFFSET(H264SliceHeader, 0x33, num_ref_idx_active_override_flag);\nCHECK_OFFSET(H264SliceHeader, 0x34, num_ref_idx_l0_active);\nCHECK_OFFSET(H264SliceHeader, 0x35, num_ref_idx_l1_active);\nCHECK_SIZE(H264SliceHeader, 0x36);\n\nstruct H264DecodedVuiParameters\n{\n   be2_val<uint8_t> aspect_ratio_info_present_flag;\n   be2_val<uint8_t> aspect_ratio_idc;\n   be2_val<int16_t> sar_width;\n   be2_val<int16_t> sar_height;\n   be2_val<uint8_t> overscan_info_present_flag;\n   be2_val<uint8_t> overscan_appropriate_flag;\n   be2_val<uint8_t> video_signal_type_present_flag;\n   be2_val<uint8_t> video_format;\n   be2_val<uint8_t> video_full_range_flag;\n   be2_val<uint8_t> colour_description_present_flag;\n   be2_val<uint8_t> colour_primaries;\n   be2_val<uint8_t> transfer_characteristics;\n   be2_val<uint8_t> matrix_coefficients;\n   be2_val<uint8_t> chroma_loc_info_present_flag;\n   be2_val<uint8_t> chroma_sample_loc_type_top_field;\n   be2_val<uint8_t> chroma_sample_loc_type_bottom_field;\n   be2_val<uint8_t> timing_info_present_flag;\n   PADDING(1);\n   be2_val<uint32_t> num_units_in_tick;\n   be2_val<uint32_t> time_scale;\n   be2_val<uint8_t> fixed_frame_rate_flag;\n   be2_val<uint8_t> nal_hrd_parameters_present_flag;\n   be2_val<uint8_t> vcl_hrd_parameters_present_flag;\n   be2_val<uint8_t> low_delay_hrd_flag;\n   be2_val<uint8_t> pic_struct_present_flag;\n   be2_val<uint8_t> bitstream_restriction_flag;\n   be2_val<uint8_t> motion_vectors_over_pic_boundaries_flag;\n   PADDING(1);\n   be2_val<int16_t> max_bytes_per_pic_denom;\n   be2_val<int16_t> max_bits_per_mb_denom;\n   be2_val<int16_t> log2_max_mv_length_horizontal;\n   be2_val<int16_t> log2_max_mv_length_vertical;\n   be2_val<int16_t> num_reorder_frames;\n   be2_val<int16_t> max_dec_frame_buffering;\n};\nCHECK_OFFSET(H264DecodedVuiParameters, 0x00, aspect_ratio_info_present_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x01, aspect_ratio_idc);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x02, sar_width);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x04, sar_height);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x06, overscan_info_present_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x07, overscan_appropriate_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x08, video_signal_type_present_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x09, video_format);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x0A, video_full_range_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x0B, colour_description_present_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x0C, colour_primaries);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x0D, transfer_characteristics);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x0E, matrix_coefficients);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x0F, chroma_loc_info_present_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x10, chroma_sample_loc_type_top_field);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x11, chroma_sample_loc_type_bottom_field);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x12, timing_info_present_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x14, num_units_in_tick);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x18, time_scale);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x1C, fixed_frame_rate_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x1D, nal_hrd_parameters_present_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x1E, vcl_hrd_parameters_present_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x1F, low_delay_hrd_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x20, pic_struct_present_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x21, bitstream_restriction_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x22, motion_vectors_over_pic_boundaries_flag);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x24, max_bytes_per_pic_denom);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x26, max_bits_per_mb_denom);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x28, log2_max_mv_length_horizontal);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x2A, log2_max_mv_length_vertical);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x2C, num_reorder_frames);\nCHECK_OFFSET(H264DecodedVuiParameters, 0x2E, max_dec_frame_buffering);\nCHECK_SIZE(H264DecodedVuiParameters, 0x30);\n\nstruct H264DecodedFrameInfo\n{\n   // Timestamp is actually stored in dpb but we are lazy and don't have dpb\n   be2_val<double> timestamp;\n\n   UNKNOWN(0x30 - 8);\n\n   be2_virt_ptr<void> buffer;\n   be2_val<uint8_t> pan_scan_enable_flag;\n   PADDING(1);\n   be2_val<uint16_t> left_pan_scan;\n   be2_val<uint16_t> right_pan_scan;\n   be2_val<uint16_t> top_pan_scan;\n   be2_val<uint16_t> bottom_pan_scan;\n   UNKNOWN(1);\n   be2_val<uint8_t> vui_parameters_present_flag;\n   be2_struct<H264DecodedVuiParameters> vui_parameters;\n};\nCHECK_OFFSET(H264DecodedFrameInfo, 0x30, buffer);\nCHECK_OFFSET(H264DecodedFrameInfo, 0x34, pan_scan_enable_flag);\nCHECK_OFFSET(H264DecodedFrameInfo, 0x36, left_pan_scan);\nCHECK_OFFSET(H264DecodedFrameInfo, 0x38, right_pan_scan);\nCHECK_OFFSET(H264DecodedFrameInfo, 0x3A, top_pan_scan);\nCHECK_OFFSET(H264DecodedFrameInfo, 0x3C, bottom_pan_scan);\nCHECK_OFFSET(H264DecodedFrameInfo, 0x3F, vui_parameters_present_flag);\nCHECK_OFFSET(H264DecodedFrameInfo, 0x40, vui_parameters);\nCHECK_SIZE(H264DecodedFrameInfo, 0x70);\n\nstruct H264StreamMemory\n{\n   be2_virt_ptr<void> workMemoryEnd;\n   be2_virt_ptr<void> unkMemory;\n   UNKNOWN(0x7C4 - 0x8);\n   be2_array<H264DecodedFrameInfo, 6> decodedFrameInfos;\n   be2_struct<H264SequenceParameterSet> currentSps;\n   be2_virt_ptr<H264SequenceParameterSet> currentSpsPtr;\n   be2_struct<H264PictureParameterSet> currentPps;\n   UNKNOWN(0x107BC - 0x1530);\n   be2_val<H264DECFptrOutputFn> paramFramePointerOutput;\n   be2_array<H264SequenceParameterSet, MaxSeqParameterSets> spsTable;\n   be2_array<H264PictureParameterSet, MaxPicParameterSets> ppsTable;\n   UNKNOWN(0x38AD8 - 0x386C0);\n   be2_val<uint32_t> param_0x20000030;\n   be2_val<uint32_t> param_0x20000040;\n   UNKNOWN(0x8);\n   be2_val<uint32_t> frameBufferIndex;\n   be2_virt_ptr<void> frameBufferPtr;\n   be2_virt_ptr<void> paramUserMemory;\n   be2_val<uint8_t> paramOutputPerFrame;\n   UNKNOWN(0x10E6B4 - (0x38AF4 + 1));\n};\nCHECK_OFFSET(H264StreamMemory, 0x0, workMemoryEnd);\nCHECK_OFFSET(H264StreamMemory, 0x4, unkMemory);\nCHECK_OFFSET(H264StreamMemory, 0x7C4, decodedFrameInfos);\nCHECK_OFFSET(H264StreamMemory, 0xA64, currentSps);\nCHECK_OFFSET(H264StreamMemory, 0x13DC, currentSpsPtr);\nCHECK_OFFSET(H264StreamMemory, 0x13E0, currentPps);\nCHECK_OFFSET(H264StreamMemory, 0x107BC, paramFramePointerOutput);\nCHECK_OFFSET(H264StreamMemory, 0x107C0, spsTable);\nCHECK_OFFSET(H264StreamMemory, 0x236C0, ppsTable);\nCHECK_OFFSET(H264StreamMemory, 0x38AD8, param_0x20000030);\nCHECK_OFFSET(H264StreamMemory, 0x38ADC, param_0x20000040);\nCHECK_OFFSET(H264StreamMemory, 0x38AE8, frameBufferIndex);\nCHECK_OFFSET(H264StreamMemory, 0x38AEC, frameBufferPtr);\nCHECK_OFFSET(H264StreamMemory, 0x38AF0, paramUserMemory);\nCHECK_OFFSET(H264StreamMemory, 0x38AF4, paramOutputPerFrame);\nCHECK_SIZE(H264StreamMemory, 0x10E6B4);\n\nstruct H264DecodeResult\n{\n   be2_val<int32_t> status;\n   PADDING(4);\n   be2_val<double> timestamp;\n   be2_val<int32_t> width;\n   be2_val<int32_t> height;\n   be2_val<int32_t> nextLine;\n   be2_val<uint8_t> cropEnableFlag;\n   PADDING(3);\n   be2_val<int32_t> cropTop;\n   be2_val<int32_t> cropBottom;\n   be2_val<int32_t> cropLeft;\n   be2_val<int32_t> cropRight;\n   be2_val<uint8_t> panScanEnableFlag;\n   PADDING(3);\n   be2_val<int32_t> panScanTop;\n   be2_val<int32_t> panScanBottom;\n   be2_val<int32_t> panScanLeft;\n   be2_val<int32_t> panScanRight;\n   be2_virt_ptr<void> framebuffer;\n   be2_val<uint8_t> vui_parameters_present_flag;\n   PADDING(3);\n   be2_virt_ptr<H264DecodedVuiParameters> vui_parameters;\n   UNKNOWN(40);\n};\nCHECK_OFFSET(H264DecodeResult, 0x00, status);\nCHECK_OFFSET(H264DecodeResult, 0x08, timestamp);\nCHECK_OFFSET(H264DecodeResult, 0x10, width);\nCHECK_OFFSET(H264DecodeResult, 0x14, height);\nCHECK_OFFSET(H264DecodeResult, 0x18, nextLine);\nCHECK_OFFSET(H264DecodeResult, 0x1C, cropEnableFlag);\nCHECK_OFFSET(H264DecodeResult, 0x20, cropTop);\nCHECK_OFFSET(H264DecodeResult, 0x24, cropBottom);\nCHECK_OFFSET(H264DecodeResult, 0x28, cropLeft);\nCHECK_OFFSET(H264DecodeResult, 0x2C, cropRight);\nCHECK_OFFSET(H264DecodeResult, 0x30, panScanEnableFlag);\nCHECK_OFFSET(H264DecodeResult, 0x34, panScanTop);\nCHECK_OFFSET(H264DecodeResult, 0x38, panScanBottom);\nCHECK_OFFSET(H264DecodeResult, 0x3C, panScanLeft);\nCHECK_OFFSET(H264DecodeResult, 0x40, panScanRight);\nCHECK_OFFSET(H264DecodeResult, 0x44, framebuffer);\nCHECK_OFFSET(H264DecodeResult, 0x48, vui_parameters_present_flag);\nCHECK_OFFSET(H264DecodeResult, 0x4C, vui_parameters);\nCHECK_SIZE(H264DecodeResult, 0x78);\n\nstruct H264DecodeOutput\n{\n   //! Number of frames output\n   be2_val<int32_t> frameCount;\n\n   //! Frames\n   be2_virt_ptr<virt_ptr<H264DecodeResult>> decodeResults;\n\n   //! User memory pointer passed into SetParam\n   be2_virt_ptr<void> userMemory;\n};\nCHECK_OFFSET(H264DecodeOutput, 0x00, frameCount);\nCHECK_OFFSET(H264DecodeOutput, 0x04, decodeResults);\nCHECK_OFFSET(H264DecodeOutput, 0x08, userMemory);\nCHECK_SIZE(H264DecodeOutput, 0x0C);\n\n#pragma pack(pop)\n\nH264Error\nH264DECCheckDecunitLength(virt_ptr<void> memory,\n                          virt_ptr<const uint8_t> buffer,\n                          int32_t bufferLength,\n                          int32_t offset,\n                          virt_ptr<int32_t> outLength);\n\nH264Error\nH264DECCheckSkipableFrame(virt_ptr<const uint8_t> buffer,\n                          int32_t bufferLength,\n                          virt_ptr<BOOL> outSkippable);\n\nH264Error\nH264DECFindDecstartpoint(virt_ptr<const uint8_t> buffer,\n                         int32_t bufferLength,\n                         virt_ptr<int32_t> outOffset);\n\nint32_t\nH264DECFindIdrpoint(virt_ptr<const uint8_t> buffer,\n                    int32_t bufferLength,\n                    virt_ptr<int32_t> outOffset);\n\nH264Error\nH264DECGetImageSize(virt_ptr<const uint8_t> buffer,\n                    int32_t bufferLength,\n                    int32_t offset,\n                    virt_ptr<int32_t> outWidth,\n                    virt_ptr<int32_t> outHeight);\n\nnamespace internal\n{\n\nH264Error\ndecodeNaluSps(const uint8_t *buffer,\n              int bufferLength,\n              int offset,\n              virt_ptr<H264SequenceParameterSet> sps);\n\n} // namespace internal\n\n} // namespace cafe::h264\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/lzma920/lzma920.cpp",
    "content": "#include \"lzma920.h\"\n\nnamespace cafe::lzma920\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::lzma920\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/lzma920/lzma920.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::lzma920\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::lzma920, \"lzma920.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::lzma920\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/mic/mic.cpp",
    "content": "#include \"mic.h\"\n\nnamespace cafe::mic\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerMicSymbols();\n}\n\n} // namespace cafe::mic\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/mic/mic.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::mic\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::mic, \"mic.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerMicSymbols();\n};\n\n} // namespace cafe::mic\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/mic/mic_mic.cpp",
    "content": "#include \"mic.h\"\n#include \"mic_mic.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::mic\n{\n\nMICHandle\nMICInit(uint32_t a1,\n        virt_ptr<void> a2,\n        virt_ptr<void> a3,\n        virt_ptr<MICError> outError)\n{\n   decaf_warn_stub();\n\n   if (outError) {\n      *outError = -1;\n   }\n\n   return 0;\n}\n\nMICError\nMICUninit(MICHandle handle)\n{\n   return -1;\n}\n\nMICError\nMICOpen(MICHandle handle)\n{\n   return -1;\n}\n\nMICError\nMICClose(MICHandle handle)\n{\n   return -1;\n}\n\nMICError\nMICGetStatus(MICHandle handle,\n             virt_ptr<MICStatus> status)\n{\n   return -1;\n}\n\nvoid\nLibrary::registerMicSymbols()\n{\n   RegisterFunctionExport(MICInit);\n   RegisterFunctionExport(MICUninit);\n   RegisterFunctionExport(MICOpen);\n   RegisterFunctionExport(MICClose);\n   RegisterFunctionExport(MICGetStatus);\n}\n\n} // namespace cafe::mic\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/mic/mic_mic.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::mic\n{\n\nusing MICHandle = uint32_t;\nusing MICError = int32_t;\n\nstruct MICStatus;\n\nMICHandle\nMICInit(uint32_t a1,\n        virt_ptr<void> a2,\n        virt_ptr<void> a3,\n        virt_ptr<MICError> outError);\n\nMICError\nMICUninit(MICHandle handle);\n\nMICError\nMICOpen(MICHandle handle);\n\nMICError\nMICClose(MICHandle handle);\n\nMICError\nMICGetStatus(MICHandle handle,\n             virt_ptr<MICStatus> status);\n\n} // namespace cafe::mic\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nfc/nfc.cpp",
    "content": "#include \"nfc.h\"\n\nnamespace cafe::nfc\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nfc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nfc/nfc.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nfc\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nfc, \"nfc.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nfc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nio_prof/nio_prof.cpp",
    "content": "#include \"nio_prof.h\"\n\nnamespace cafe::nio_prof\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nio_prof\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nio_prof/nio_prof.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nio_prof\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nio_prof, \"nio_prof.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nio_prof\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl.cpp",
    "content": "#include \"nlibcurl.h\"\n\nnamespace cafe::nlibcurl\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerCurlSymbols();\n   registerEasySymbols();\n}\n\n} // namespace cafe::nlibcurl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nlibcurl\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nlibcurl, \"nlibcurl.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerCurlSymbols();\n   void registerEasySymbols();\n};\n\n} // namespace cafe::nlibcurl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl_curl.cpp",
    "content": "#include \"nlibcurl.h\"\n#include \"nlibcurl_curl.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::nlibcurl\n{\n\nCURLcode\ncurl_global_init(int32_t flags)\n{\n   // Host curl_global_init is called during decaf initialisation\n   return ::CURLE_OK;\n}\n\nCURLcode\ncurl_global_init_mem(int32_t flags,\n                     virt_ptr<void> mallocCallback,\n                     virt_ptr<void> freeCallback,\n                     virt_ptr<void> reallocCallback,\n                     virt_ptr<void> strdupCallback,\n                     virt_ptr<void> callocCallback)\n{\n   decaf_warn_stub();\n   return ::CURLE_OK;\n}\n\nvoid\ncurl_global_cleanup()\n{\n   // Host curl_global_cleanup is called during decaf exit\n}\n\nvoid\nLibrary::registerCurlSymbols()\n{\n   RegisterFunctionExport(curl_global_init);\n   RegisterFunctionExport(curl_global_init_mem);\n   RegisterFunctionExport(curl_global_cleanup);\n}\n\n} // namespace cafe::nlibcurl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl_curl.h",
    "content": "#pragma once\n#include <cstdint>\n#include <curl/curl.h>\n\nnamespace cafe::nlibcurl\n{\n\nstruct CURL\n{\n   ::CURL *hostHandle;\n};\n\nusing CURLcode = int32_t;\n\nCURLcode\ncurl_global_init(int32_t flags);\n\nCURLcode\ncurl_global_init_mem(int32_t flags,\n                     virt_ptr<void> mallocCallback,\n                     virt_ptr<void> freeCallback,\n                     virt_ptr<void> reallocCallback,\n                     virt_ptr<void> strdupCallback,\n                     virt_ptr<void> callocCallback);\n\nvoid\ncurl_global_cleanup();\n\n} // namespace cafe::nlibcurl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl_easy.cpp",
    "content": "#include \"nlibcurl.h\"\n#include \"nlibcurl_easy.h\"\n\n#include \"cafe/cafe_ppc_interface_varargs.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\n#include <curl/curl.h>\n\nnamespace cafe::nlibcurl\n{\n\nstruct StaticEasyData\n{\n   be2_array<CURL, 128> handles;\n};\n\nstatic virt_ptr<StaticEasyData>\nsEasyData = nullptr;\n\nvirt_ptr<CURL>\ncurl_easy_init()\n{\n   auto handle = ::curl_easy_init();\n   if (!handle) {\n      return nullptr;\n   }\n\n   for (auto i = 0u; i < sEasyData->handles.size(); ++i) {\n      if (!sEasyData->handles[i].hostHandle) {\n         sEasyData->handles[i].hostHandle = handle;\n         return virt_addrof(sEasyData->handles[i]);\n      }\n   }\n\n   ::curl_easy_cleanup(handle);\n   return nullptr;\n}\n\nvoid\ncurl_easy_cleanup(virt_ptr<CURL> handle)\n{\n   ::curl_easy_cleanup(handle->hostHandle);\n   handle->hostHandle = nullptr;\n}\n\nCURLcode\ncurl_easy_setopt(virt_ptr<CURL> handle,\n                 CURLoption option,\n                 var_args args)\n{\n   auto vaList = make_va_list(args);\n   //auto curl = handle->hostHandle;\n\n   // TODO: Translate to ::curl_easy_setopt\n   decaf_warn_stub();\n\n   free_va_list(vaList);\n   return ::CURLE_OK;\n}\n\nvoid\nLibrary::registerEasySymbols()\n{\n   RegisterFunctionExport(curl_easy_init);\n   RegisterFunctionExport(curl_easy_cleanup);\n   RegisterFunctionExport(curl_easy_setopt);\n\n   RegisterDataInternal(sEasyData);\n}\n\n} // namespace cafe::nlibcurl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nlibcurl/nlibcurl_easy.h",
    "content": "#pragma once\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include \"nlibcurl_curl.h\"\n\nnamespace cafe::nlibcurl\n{\n\nusing CURLoption = int32_t;\n\nvirt_ptr<CURL>\ncurl_easy_init();\n\nvoid\ncurl_easy_cleanup(virt_ptr<CURL> handle);\n\nCURLcode\ncurl_easy_setopt(virt_ptr<CURL> handle,\n                 CURLoption option,\n                 var_args args);\n\n} // namespace cafe::nlibcurl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nlibnss/nlibnss.cpp",
    "content": "#include \"nlibnss.h\"\n\nnamespace cafe::nlibnss\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nlibnss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nlibnss/nlibnss.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nlibnss\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nlibnss, \"nlibnss.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nlibnss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nlibnss2/nlibnss2.cpp",
    "content": "#include \"nlibnss2.h\"\n\nnamespace cafe::nlibnss2\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nlibnss2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nlibnss2/nlibnss2.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nlibnss2\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nlibnss2, \"nlibnss2.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nlibnss2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ac/nn_ac.cpp",
    "content": "#include \"nn_ac.h\"\n\nnamespace cafe::nn_ac\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerCApiFunctions();\n   registerClientSymbols();\n   registerServiceSymbols();\n}\n\n} // namespace cafe::nn_ac\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ac/nn_ac.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_ac\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_ac, \"nn_ac.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerCApiFunctions();\n   void registerClientSymbols();\n   void registerServiceSymbols();\n};\n\n} // namespace cafe::nn_ac\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_capi.cpp",
    "content": "#include \"nn_ac.h\"\n#include \"nn_ac_capi.h\"\n#include \"nn_ac_client.h\"\n#include \"nn_ac_service.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/cafe_stackobject.h\"\n\nnamespace cafe::nn_ac\n{\n\nnn::Result\nACInitialize()\n{\n   return Initialize();\n}\n\nvoid\nACFinalize()\n{\n   return Finalize();\n}\n\nnn::Result\nACConnect()\n{\n   return Connect();\n}\n\nnn::Result\nACConnectAsync()\n{\n   return ConnectAsync();\n}\n\nnn::Result\nACIsApplicationConnected(virt_ptr<BOOL> connected)\n{\n   auto isConnected = StackObject<bool> { };\n   *isConnected = *connected ? true : false;\n   auto result = IsApplicationConnected(isConnected);\n   *connected = *isConnected ? TRUE : FALSE;\n   return result;\n}\n\nnn::Result\nACGetAssignedAddress(virt_ptr<uint32_t> outAddress)\n{\n   return GetAssignedAddress(outAddress);\n}\n\nnn::Result\nACGetConnectStatus(virt_ptr<Status> outStatus)\n{\n   return GetConnectStatus(outStatus);\n}\n\nnn::Result\nACGetLastErrorCode(virt_ptr<int32_t> outError)\n{\n   return GetLastErrorCode(outError);\n}\n\nnn::Result\nACGetStatus(virt_ptr<Status> outStatus)\n{\n   return GetStatus(outStatus);\n}\n\nnn::Result\nACGetStartupId(virt_ptr<ConfigId> outStartupId)\n{\n   return GetStartupId(outStartupId);\n}\n\nnn::Result\nACReadConfig(ConfigId id,\n             virt_ptr<Config> config)\n{\n   return ReadConfig(id, config);\n}\n\nvoid\nLibrary::registerCApiFunctions()\n{\n   RegisterFunctionExport(ACInitialize);\n   RegisterFunctionExport(ACFinalize);\n   RegisterFunctionExport(ACConnect);\n   RegisterFunctionExport(ACConnectAsync);\n   RegisterFunctionExport(ACIsApplicationConnected);\n   RegisterFunctionExport(ACGetAssignedAddress);\n   RegisterFunctionExport(ACGetConnectStatus);\n   RegisterFunctionExport(ACGetLastErrorCode);\n   RegisterFunctionExport(ACGetStatus);\n   RegisterFunctionExport(ACGetStartupId);\n   RegisterFunctionExport(ACReadConfig);\n}\n\n}  // namespace cafe::nn_ac\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_capi.h",
    "content": "#pragma once\n#include \"nn_ac_enum.h\"\n#include \"nn_ac_service.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ac\n{\n\nnn::Result\nACInitialize();\n\nvoid\nACFinalize();\n\nnn::Result\nACConnect();\n\nnn::Result\nACConnectAsync();\n\nnn::Result\nACIsApplicationConnected(virt_ptr<BOOL> connected);\n\nnn::Result\nACGetAssignedAddress(virt_ptr<uint32_t> outAddress);\n\nnn::Result\nACGetConnectStatus(virt_ptr<Status> outStatus);\n\nnn::Result\nACGetLastErrorCode(virt_ptr<int32_t> outError);\n\nnn::Result\nACGetStatus(virt_ptr<Status> outStatus);\n\nnn::Result\nACGetStartupId(virt_ptr<ConfigId> outStartupId);\n\nnn::Result\nACReadConfig(ConfigId id,\n             virt_ptr<Config> config);\n\n}  // namespace cafe::nn_ac\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_client.cpp",
    "content": "#include \"nn_ac.h\"\n#include \"nn_ac_client.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/ac/nn_ac_service.h\"\n\nusing namespace cafe::coreinit;\nusing namespace nn::ac;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_ac\n{\n\nstruct StaticClientData\n{\n   StaticClientData()\n   {\n      OSInitMutex(virt_addrof(mutex));\n   }\n\n   alignas(256) be2_array<uint8_t, 0x10000> allocatorMemory;\n\n   be2_struct<OSMutex> mutex;\n   be2_val<uint32_t> refCount = 0u;\n   be2_struct<nn::ipc::Client> client;\n   be2_struct<nn::ipc::BufferAllocator> allocator;\n};\n\nstatic virt_ptr<StaticClientData> sClientData = nullptr;\n\nnn::Result\nInitialize()\n{\n   auto result = nn::ResultSuccess;\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount == 0) {\n      result = sClientData->client.initialise(make_stack_string(\"/dev/ac_main\"));\n      sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory),\n                                        sClientData->allocatorMemory.size());\n      if (result) {\n         internal::getClient()->sendSyncRequest(\n            ClientCommand<services::AcService::Initialise> { internal::getAllocator() });\n      }\n   }\n\n   sClientData->refCount++;\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n   return result;\n}\n\nvoid\nFinalize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount > 0) {\n      sClientData->refCount--;\n\n      if (sClientData->refCount == 0) {\n         internal::getClient()->sendSyncRequest(\n            ClientCommand<services::AcService::Finalise> { internal::getAllocator() });\n         sClientData->client.close();\n      }\n   }\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n}\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient()\n{\n   return virt_addrof(sClientData->client);\n}\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator()\n{\n   return virt_addrof(sClientData->allocator);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerClientSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn2acFv\",\n                              Initialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn2acFv\",\n                              Finalize);\n\n   RegisterDataInternal(sClientData);\n}\n\n}  // namespace cafe::nn_ac\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_client.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"cafe/nn/cafe_nn_ipc_bufferallocator.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ac\n{\n\nnn::Result\nInitialize();\n\nvoid\nFinalize();\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient();\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator();\n\n} // namespace internal\n\n}  // namespace cafe::nn_ac\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_enum.h",
    "content": "#ifndef CAFE_NN_AC_ENUM_H\n#define CAFE_NN_AC_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(nn_ac)\n\nENUM_BEG(Status, int32_t)\n   ENUM_VALUE(OK,             0)\n   ENUM_VALUE(Error,          -1)\nENUM_END(Status)\n\nENUM_NAMESPACE_EXIT(nn_ac)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_NN_AC_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_service.cpp",
    "content": "#include \"nn_ac.h\"\n#include \"nn_ac_client.h\"\n#include \"nn_ac_service.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"nn/ac/nn_ac_result.h\"\n#include \"nn/ac/nn_ac_service.h\"\n#include \"nn/ipc/nn_ipc_command.h\"\n\nusing namespace nn::ac;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_ac\n{\n\nnn::Result\nConnect()\n{\n   decaf_warn_stub();\n   return ResultSuccess;\n}\n\nnn::Result\nConnectAsync()\n{\n   decaf_warn_stub();\n   return ResultSuccess;\n}\n\nnn::Result\nIsApplicationConnected(virt_ptr<bool> connected)\n{\n   decaf_warn_stub();\n   *connected = false;\n   return ResultSuccess;\n}\n\nnn::Result\nGetAssignedAddress(virt_ptr<uint32_t> outAddress)\n{\n   if (!internal::getClient()->isInitialised()) {\n      return ResultLibraryNotInitialiased;\n   }\n\n   if (!outAddress) {\n      return ResultInvalidArgument;\n   }\n\n   auto command = ClientCommand<services::AcService::GetAssignedAddress> { internal::getAllocator() };\n   command.setParameters(0);\n\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      auto address = uint32_t{ 0 };\n      result = command.readResponse(address);\n      if (result.ok()) {\n         *outAddress = address;\n      }\n   }\n\n   return result;\n}\n\n\nnn::Result\nGetConnectStatus(virt_ptr<Status> outStatus)\n{\n   decaf_warn_stub();\n   *outStatus = Status::Error;\n   return ResultSuccess;\n}\n\nnn::Result\nGetLastErrorCode(virt_ptr<int32_t> outError)\n{\n   decaf_warn_stub();\n   *outError = -1;\n   return ResultSuccess;\n}\n\nnn::Result\nGetStatus(virt_ptr<Status> outStatus)\n{\n   decaf_warn_stub();\n   *outStatus = Status::Error;\n   return ResultSuccess;\n}\n\nnn::Result\nGetStartupId(virt_ptr<ConfigId> outStartupId)\n{\n   decaf_warn_stub();\n   *outStartupId = 0;\n   return ResultSuccess;\n}\n\nnn::Result\nReadConfig(ConfigId id,\n           virt_ptr<Config> config)\n{\n   decaf_warn_stub();\n   std::memset(config.get(), 0, sizeof(Config));\n   return ResultSuccess;\n}\n\nvoid\nLibrary::registerServiceSymbols()\n{\n   RegisterFunctionExportName(\"Connect__Q2_2nn2acFv\",\n                              Connect);\n   RegisterFunctionExportName(\"ConnectAsync__Q2_2nn2acFv\",\n                              ConnectAsync);\n   RegisterFunctionExportName(\"IsApplicationConnected__Q2_2nn2acFPb\",\n                              IsApplicationConnected);\n   RegisterFunctionExportName(\"GetAssignedAddress__Q2_2nn2acFPUl\",\n                              GetAssignedAddress);\n   RegisterFunctionExportName(\"GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status\",\n                              GetConnectStatus);\n   RegisterFunctionExportName(\"GetLastErrorCode__Q2_2nn2acFPUi\",\n                              GetLastErrorCode);\n   RegisterFunctionExportName(\"GetStatus__Q2_2nn2acFPQ3_2nn2ac6Status\",\n                              GetStatus);\n   RegisterFunctionExportName(\"GetStartupId__Q2_2nn2acFPQ3_2nn2ac11ConfigIdNum\",\n                              GetStartupId);\n   RegisterFunctionExportName(\"ReadConfig__Q2_2nn2acFQ3_2nn2ac11ConfigIdNumP16netconf_profile_\",\n                              ReadConfig);\n}\n\n}  // namespace cafe::nn_ac\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ac/nn_ac_service.h",
    "content": "#pragma once\n#include \"nn_ac_enum.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ac\n{\n\nusing ConfigId = int32_t;\n\nstruct Config\n{\n   UNKNOWN(0x280);\n};\nCHECK_SIZE(Config, 0x280);\n\nnn::Result\nConnect();\n\nnn::Result\nConnectAsync();\n\nnn::Result\nIsApplicationConnected(virt_ptr<bool> connected);\n\nnn::Result\nGetAssignedAddress(virt_ptr<uint32_t> outAddress);\n\nnn::Result\nGetConnectStatus(virt_ptr<Status> outStatus);\n\nnn::Result\nGetLastErrorCode(virt_ptr<int32_t> outError);\n\nnn::Result\nGetStatus(virt_ptr<Status> outStatus);\n\nnn::Result\nGetStartupId(virt_ptr<ConfigId> outStartupId);\n\nnn::Result\nReadConfig(ConfigId id,\n           virt_ptr<Config> config);\n\n}  // namespace cafe::nn_ac\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp.cpp",
    "content": "#include \"nn_acp.h\"\n#include \"nn_acp_internal_driver.h\"\n\n#include \"cafe/libraries/cafe_hle.h\"\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::nn_acp\n{\n\nstatic int32_t\nrpl_entry(OSDynLoad_ModuleHandle moduleHandle,\n          OSDynLoad_EntryReason reason)\n{\n   if (reason == OSDynLoad_EntryReason::Loaded) {\n      internal::startDriver(moduleHandle);\n   } else if (reason == OSDynLoad_EntryReason::Unloaded) {\n      internal::stopDriver(moduleHandle);\n   }\n\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerClientSymbols();\n   registerDeviceSymbols();\n   registerDriverSymbols();\n   registerMiscServiceSymbols();\n   registerSaveServiceSymbols();\n}\n\n} // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_acp\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_acp, \"nn_acp.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerClientSymbols();\n   void registerDeviceSymbols();\n   void registerDriverSymbols();\n   void registerMiscServiceSymbols();\n   void registerSaveServiceSymbols();\n};\n\n} // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_acpresult.cpp",
    "content": "#include \"nn_acp_acpresult.h\"\n#include \"nn/acp/nn_acp_result.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_cosreport.h\"\n#include \"cafe/libraries/coreinit/coreinit_osreport.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n#include \"cafe/cafe_stackobject.h\"\n\nusing namespace cafe::coreinit;\nusing namespace nn::acp;\n\nnamespace cafe::nn_acp\n{\n\nstatic void\nACPSendCOSFatalError(coreinit::OSFatalErrorMessageType type,\n                     uint32_t errorCode,\n                     nn::Result result,\n                     const char *funcName,\n                     int32_t lineNo)\n{\n   internal::COSWarn(COSReportModule::Unknown1,\n      fmt::format(\"ACP: ##### FATAL ERROR at {} ({})#####\\n\",\n         funcName, result.code()));\n\n   if (OSGetUPID() == cafe::kernel::UniqueProcessId::ErrorDisplay) {\n      internal::COSWarn(COSReportModule::Unknown1,\n         fmt::format(\"ACP: Skip to call OSSendFatalError from {} (UPID={}).\\n\",\n            funcName,\n            static_cast<int>(cafe::kernel::UniqueProcessId::ErrorDisplay)));\n      return;\n   }\n\n   StackObject<OSFatalError> fatalError;\n   fatalError->messageType = type;\n   fatalError->errorCode = errorCode;\n   fatalError->internalErrorCode = static_cast<uint32_t>(result);\n   if (funcName) {\n      OSSendFatalError(fatalError, make_stack_string(funcName), lineNo);\n   } else {\n      OSSendFatalError(fatalError, make_stack_string(\"ACPSendCOSFatalError\"), 103);\n   }\n}\n\nACPResult\nACPConvertToACPResult(nn::Result result,\n                      const char *funcName,\n                      int32_t lineNo)\n{\n   if (result.ok()) {\n       return ACPResult::Success;\n   }\n\n   if (result == ResultInvalidParameter) {\n      return ACPResult::InvalidParameter;\n   } else if (result == ResultInvalidFile) {\n      return ACPResult::InvalidFile;\n   } else if (result == ResultInvalidXmlFile) {\n      return ACPResult::InvalidXmlFile;\n   } else if (result == ResultFileAccessMode) {\n      return ACPResult::FileAccessMode;\n   } else if (result == ResultInvalidNetworkTime) {\n      return ACPResult::InvalidNetworkTime;\n   } else if (result == ResultInvalid) {\n      return ACPResult::Invalid;\n   }\n\n   if (result == ResultFileNotFound) {\n      return ACPResult::FileNotFound;\n   } else if (result == ResultDirNotFound) {\n      return ACPResult::DirNotFound;\n   } else if (result == ResultDeviceNotFound) {\n      return ACPResult::DeviceNotFound;\n   } else if (result == ResultTitleNotFound) {\n      return ACPResult::TitleNotFound;\n   } else if (result == ResultApplicationNotFound) {\n      return ACPResult::ApplicationNotFound;\n   } else if (result == ResultSystemConfigNotFound) {\n      return ACPResult::SystemConfigNotFound;\n   } else if (result == ResultXmlItemNotFound) {\n      return ACPResult::XmlItemNotFound;\n   } else if (result == ResultNotFound) {\n      return ACPResult::NotFound;\n   }\n\n   if (result == ResultFileAlreadyExists) {\n      return ACPResult::FileAlreadyExists;\n   } else if (result == ResultDirAlreadyExists) {\n      return ACPResult::DirAlreadyExists;\n   } else if (result == ResultAlreadyExists) {\n      return ACPResult::AlreadyExists;\n   }\n\n   if (result == ResultAlreadyDone) {\n      return ACPResult::AlreadyDone;\n   }\n\n   if (result == ResultInvalidRegion) {\n      return ACPResult::InvalidRegion;\n   } else if (result == ResultRestrictedRating) {\n      return ACPResult::RestrictedRating;\n   } else if (result == ResultNotPresentRating) {\n      return ACPResult::NotPresentRating;\n   } else if (result == ResultPendingRating) {\n      return ACPResult::PendingRating;\n   } else if (result == ResultNetSettingRequired) {\n      return ACPResult::NetSettingRequired;\n   } else if (result == ResultNetAccountRequired) {\n      return ACPResult::NetAccountRequired;\n   } else if (result == ResultNetAccountError) {\n      return ACPResult::NetAccountError;\n   } else if (result == ResultBrowserRequired) {\n      return ACPResult::BrowserRequired;\n   } else if (result == ResultOlvRequired) {\n      return ACPResult::OlvRequired;\n   } else if (result == ResultPincodeRequired) {\n      return ACPResult::PincodeRequired;\n   } else if (result == ResultIncorrectPincode) {\n      return ACPResult::IncorrectPincode;\n   } else if (result == ResultInvalidLogo) {\n      return ACPResult::InvalidLogo;\n   } else if (result == ResultDemoExpiredNumber) {\n      return ACPResult::DemoExpiredNumber;\n   } else if (result == ResultDrcRequired) {\n      return ACPResult::DrcRequired;\n   } else if (result == ResultAuthentication) {\n      return ACPResult::Authentication;\n   }\n\n   if (result == ResultNoFilePermission) {\n      return ACPResult::NoFilePermission;\n   } else if (result == ResultNoDirPermission) {\n      return ACPResult::NoDirPermission;\n   } else if (result == ResultNoPermission) {\n      return ACPResult::NoPermission;\n   }\n\n   if (result == ResultUsbStorageNotReady) {\n      return ACPResult::UsbStorageNotReady;\n   } else if (result == ResultBusy) {\n      return ACPResult::Busy;\n   }\n\n   if (result == ResultCancelled) {\n      return ACPResult::Cancelled;\n   }\n\n   if (result == ResultDeviceFull) {\n      return ACPResult::DeviceFull;\n   } else if (result == ResultJournalFull) {\n      return ACPResult::JournalFull;\n   } else if (result == ResultSystemMemory) {\n      return ACPResult::SystemMemory;\n   } else if (result == ResultFsResource) {\n      return ACPResult::FsResource;\n   } else if (result == ResultIpcResource) {\n      return ACPResult::IpcResource;\n   } else if (result == ResultResource) {\n      return ACPResult::Resource;\n   }\n\n   if (result == ResultNotInitialised) {\n      return ACPResult::NotInitialised;\n   }\n\n   if (result == ResultAccountError) {\n      return ACPResult::AccountError;\n   }\n\n   if (result == ResultUnsupported) {\n      return ACPResult::Unsupported;\n   }\n\n   if (result == ResultSlcDataCorrupted) {\n      ACPSendCOSFatalError(4, 0x1870AD, result, funcName, lineNo);\n      return ACPResult::SlcDataCorrupted;\n   } else if (result == ResultMlcDataCorrupted) {\n      ACPSendCOSFatalError(2, 0x1870ae, result, funcName, lineNo);\n      return ACPResult::MlcDataCorrupted;\n   } else if (result == ResultUsbDataCorrupted) {\n      ACPSendCOSFatalError(5, 0x187c67, result, funcName, lineNo);\n      return ACPResult::UsbDataCorrupted;\n   } else if (result == ResultDataCorrupted) {\n      ACPSendCOSFatalError(3, 0x187c68, result, funcName, lineNo);\n      return ACPResult::DataCorrupted;\n   } else if (result == ResultDevice) {\n      return ACPResult::Device;\n   }\n\n   if (result == ResultOddMediaNotReady) {\n      return ACPResult::OddMediaNotReady;\n   } else if (result == ResultOddMediaBroken) {\n      return ACPResult::OddMediaBroken;\n   } else if (result == ResultUsbMediaNotReady) {\n      ACPSendCOSFatalError(6, 0x187499, result, funcName, lineNo);\n      return ACPResult::UsbMediaNotReady;\n   } else if (result == ResultUsbMediaBroken) {\n      ACPSendCOSFatalError(5, 0x187c6a, result, funcName, lineNo);\n      return ACPResult::UsbMediaBroken;\n   } else if (result == ResultMediaNotReady) {\n      return ACPResult::MediaNotReady;\n   } else if (result == ResultMediaBroken) {\n      return ACPResult::MediaBroken;\n   } else if (result == ResultMediaWriteProtected) {\n      return ACPResult::MediaWriteProtected;\n   } else if (result == ResultUsbWriteProtected) {\n      return ACPResult::UsbWriteProtected;\n   } else if (result == ResultUsbWriteProtected) {\n      return ACPResult::UsbWriteProtected;\n   } else if (result == ResultMedia) {\n      return ACPResult::Media;\n   }\n\n   if (result == ResultEncryptionError) {\n      return ACPResult::EncryptionError;\n   } else if (result == ResultMii) {\n      return ACPResult::Mii;\n   }\n\n   if (result == ResultFsaFatal) {\n      ACPSendCOSFatalError(1, 0x18748d, result, funcName, lineNo);\n   } else if (result == ResultFsaAddClientFatal) {\n      ACPSendCOSFatalError(1, 0x18748e, result, funcName, lineNo);\n   } else if (result == ResultMcpTitleFatal) {\n      ACPSendCOSFatalError(1, 0x18748f, result, funcName, lineNo);\n   } else if (result == ResultMcpPatchFatal) {\n      ACPSendCOSFatalError(1, 0x187490, result, funcName, lineNo);\n   } else if (result == ResultMcpFatal) {\n      ACPSendCOSFatalError(1, 0x187491, result, funcName, lineNo);\n   } else if (result == ResultSaveFatal) {\n      ACPSendCOSFatalError(1, 0x187492, result, funcName, lineNo);\n   } else if (result == ResultUcFatal) {\n      ACPSendCOSFatalError(1, 0x187493, result, funcName, lineNo);\n   } else if (result == nn::ipc::ResultCapabilityFailed) {\n      ACPSendCOSFatalError(1, 0x187494, result, funcName, lineNo);\n   } else if (result == ResultFatal) {\n      ACPSendCOSFatalError(1, 0x18748c, result, funcName, lineNo);\n   } else {\n      ACPSendCOSFatalError(1, 0x18749f, result, funcName, lineNo);\n   }\n\n   return ACPResult::GenericError;\n}\n\n} // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_acpresult.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\n#include <cstdint>\n\nnamespace cafe::nn_acp\n{\n\nenum class ACPResult : int32_t\n{\n   Success                 = 0,\n\n   Invalid                 = -200,\n   InvalidParameter        = -201,\n   InvalidFile             = -202,\n   InvalidXmlFile          = -203,\n   FileAccessMode          = -204,\n   InvalidNetworkTime      = -205,\n\n   NotFound                = -500,\n   FileNotFound            = -501,\n   DirNotFound             = -502,\n   DeviceNotFound          = -503,\n   TitleNotFound           = -504,\n   ApplicationNotFound     = -505,\n   SystemConfigNotFound    = -506,\n   XmlItemNotFound         = -507,\n\n   AlreadyExists           = -600,\n   FileAlreadyExists       = -601,\n   DirAlreadyExists        = -602,\n\n   AlreadyDone             = -700,\n\n   Authentication          = -1000,\n   InvalidRegion           = -1001,\n   RestrictedRating        = -1002,\n   NotPresentRating        = -1003,\n   PendingRating           = -1004,\n   NetSettingRequired      = -1005,\n   NetAccountRequired      = -1006,\n   NetAccountError         = -1007,\n   BrowserRequired         = -1008,\n   OlvRequired             = -1009,\n   PincodeRequired         = -1010,\n   IncorrectPincode        = -1011,\n   InvalidLogo             = -1012,\n   DemoExpiredNumber       = -1013,\n   DrcRequired             = -1014,\n\n   NoPermission            = -1100,\n   NoFilePermission        = -1101,\n   NoDirPermission         = -1102,\n\n   Busy                    = -1300,\n   UsbStorageNotReady      = -1301,\n\n   Cancelled               = -1400,\n\n   Resource                = -1500,\n   DeviceFull              = -1501,\n   JournalFull             = -1502,\n   SystemMemory            = -1503,\n   FsResource              = -1504,\n   IpcResource             = -1505,\n\n   NotInitialised          = -1600,\n\n   AccountError            = -1700,\n\n   Unsupported             = -1800,\n\n   DataCorrupted           = -2000,\n   Device                  = -2001,\n   SlcDataCorrupted        = -2002,\n   MlcDataCorrupted        = -2003,\n   UsbDataCorrupted        = -2004,\n\n   Media                   = -2100,\n   MediaNotReady           = -2101,\n   MediaBroken             = -2102,\n   OddMediaNotReady        = -2103,\n   OddMediaBroken          = -2104,\n   UsbMediaNotReady        = -2105,\n   UsbMediaBroken          = -2106,\n   MediaWriteProtected     = -2107,\n   UsbWriteProtected       = -2108,\n\n   Mii                     = -2200,\n   EncryptionError         = -2201,\n\n   GenericError            = -4096,\n};\n\nACPResult\nACPConvertToACPResult(nn::Result result,\n                      const char *funcName,\n                      int32_t lineNo);\n\n} // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_client.cpp",
    "content": "#include \"nn_acp.h\"\n#include \"nn_acp_client.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/acp/nn_acp_result.h\"\n\nusing namespace cafe::coreinit;\nusing namespace nn::acp;\n\nnamespace cafe::nn_acp\n{\n\nstruct StaticClientData\n{\n   StaticClientData()\n   {\n      OSInitMutex(virt_addrof(mutex));\n   }\n\n   alignas(64) be2_array<uint8_t, 0x3000> allocatorMemory;\n\n   be2_struct<OSMutex> mutex;\n   be2_val<uint32_t> refCount = 0u;\n   be2_struct<nn::ipc::Client> client;\n   be2_struct<nn::ipc::BufferAllocator> allocator;\n};\n\nstatic virt_ptr<StaticClientData> sClientData = nullptr;\n\nACPResult\nACPInitialize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount == 0) {\n      sClientData->client.initialise(make_stack_string(\"/dev/acp_main\"));\n      sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory),\n                                        sClientData->allocatorMemory.size());\n\n      // TODO: MCP_Open\n      // TODO: nn::spm::Initialize\n      // TODO: nn::spm::StartFatalDetection\n\n      auto upid = OSGetUPID();\n      if (upid == kernel::UniqueProcessId::HomeMenu ||\n          upid == kernel::UniqueProcessId::Game) {\n         // TODO: PrepareToSetOwnApplicationTitleId\n         // TODO: ACPSetOwnApplicationTitleId\n      }\n   }\n\n   sClientData->refCount++;\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n   return ACPResult::Success;\n}\n\nvoid\nACPFinalize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount > 0) {\n      sClientData->refCount--;\n\n      if (sClientData->refCount == 0) {\n         sClientData->client.close();\n         // TODO: Cleanup the above TODO things in initialize!\n      }\n   }\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n}\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient()\n{\n   return virt_addrof(sClientData->client);\n}\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator()\n{\n   return virt_addrof(sClientData->allocator);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerClientSymbols()\n{\n   RegisterFunctionExport(ACPInitialize);\n   RegisterFunctionExport(ACPFinalize);\n\n   RegisterDataInternal(sClientData);\n}\n\n}  // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_client.h",
    "content": "#pragma once\n#include \"nn_acp_acpresult.h\"\n\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"cafe/nn/cafe_nn_ipc_bufferallocator.h\"\n\nnamespace cafe::nn_acp\n{\n\nACPResult\nACPInitialize();\n\nvoid\nACPFinalize();\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient();\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator();\n\n} // namespace internal\n\n} // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_device.cpp",
    "content": "#include \"nn_acp.h\"\n#include \"nn_acp_device.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::nn_acp\n{\n\nACPResult\nACPCheckApplicationDeviceEmulation(virt_ptr<BOOL> outValue)\n{\n   decaf_warn_stub();\n   *outValue = FALSE;\n   return ACPResult::Success;\n}\n\nvoid\nLibrary::registerDeviceSymbols()\n{\n   RegisterFunctionExportName(\"ACPCheckApplicationDeviceEmulation\",\n                              ACPCheckApplicationDeviceEmulation);\n}\n\n}  // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_device.h",
    "content": "#pragma once\n#include \"nn_acp_acpresult.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_acp\n{\n\nACPResult\nACPCheckApplicationDeviceEmulation(virt_ptr<BOOL> outValue);\n\n}  // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_internal_driver.cpp",
    "content": "#include \"nn_acp.h\"\n#include \"nn_acp_client.h\"\n#include \"nn_acp_internal_driver.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_driver.h\"\n#include \"cafe/libraries/coreinit/coreinit_cosreport.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::nn_acp::internal\n{\n\nstruct StaticDriverData\n{\n   be2_val<BOOL> registered = FALSE;\n   be2_val<BOOL> initialised = FALSE;\n   be2_array<char, 16> name = \"ACP\";\n   be2_struct<OSDriverInterface> driverInterface;\n};\n\nstatic virt_ptr<StaticDriverData> sDriverData = nullptr;\nstatic OSDriver_GetNameFn sDriverGetName = nullptr;\nstatic OSDriver_OnInitFn sDriverOnInit = nullptr;\nstatic OSDriver_OnAcquiredForegroundFn sDriverOnAcquiredForeground = nullptr;\nstatic OSDriver_OnReleasedForegroundFn sDriverOnReleasedForeground = nullptr;\nstatic OSDriver_OnDoneFn sDriverOnDone = nullptr;\n\nstatic virt_ptr<const char>\ngetName(OSDriver_UserDriverId id)\n{\n   return virt_addrof(sDriverData->name);\n}\n\nstatic void\nonInit(OSDriver_UserDriverId id)\n{\n   coreinit::internal::COSWarn(COSReportModule::Unknown1,\n                               \"   ACP_AutoInit: start\\n\");\n   ACPInitialize();\n   coreinit::internal::COSWarn(COSReportModule::Unknown1,\n                               \"   ACP_AutoInit: ACPInitialize complete\\n\");\n\n   // TODO: ACPSaveDataInit()\n   coreinit::internal::COSWarn(COSReportModule::Unknown1,\n                               \"   ACP_AutoInit: ACPSaveDataInit complete\\n\");\n\n   // TODO: ACPNotifyPlayEvent(1)\n   coreinit::internal::COSWarn(COSReportModule::Unknown1,\n                               \"   ACP_AutoInit: ACPNotifyPlayEvent complete\\n\");\n\n   // TODO: NDMInitialize\n   coreinit::internal::COSWarn(COSReportModule::Unknown1,\n                               \"   ACP_AutoInit: NDMInitialize complete\\n\");\n\n   auto upid = OSGetUPID();\n   if (upid == kernel::UniqueProcessId::Game ||\n       upid == kernel::UniqueProcessId::HomeMenu) {\n      // Check for bg daemon enable\n   }\n\n   sDriverData->initialised = TRUE;\n}\n\nstatic void\nonAcquiredForeground(OSDriver_UserDriverId id)\n{\n}\n\nstatic void\nonReleasedForeground(OSDriver_UserDriverId id)\n{\n}\n\nstatic void\nonDone(OSDriver_UserDriverId id)\n{\n}\n\nvoid\nstartDriver(OSDynLoad_ModuleHandle moduleHandle)\n{\n   if (sDriverData->registered) {\n      return;\n   }\n\n   auto driversAlreadyInitialised = StackObject<BOOL> { };\n   sDriverData->driverInterface.getName = sDriverGetName;\n   sDriverData->driverInterface.onInit = sDriverOnInit;\n   sDriverData->driverInterface.onAcquiredForeground = sDriverOnAcquiredForeground;\n   sDriverData->driverInterface.onReleasedForeground = sDriverOnReleasedForeground;\n   sDriverData->driverInterface.onDone = sDriverOnDone;\n\n   OSDriver_Register(moduleHandle, 910,\n                     virt_addrof(sDriverData->driverInterface),\n                     0,\n                     nullptr, nullptr,\n                     driversAlreadyInitialised);\n\n   if (*driversAlreadyInitialised) {\n      onInit(0);\n   }\n\n   sDriverData->registered = TRUE;\n}\n\nvoid\nstopDriver(OSDynLoad_ModuleHandle moduleHandle)\n{\n   if (sDriverData->registered) {\n      OSDriver_Deregister(moduleHandle, 0);\n      sDriverData->registered = FALSE;\n   }\n}\n\n} // namespace cafe::nn_acp::internal\n\nnamespace cafe::nn_acp\n{\n\nvoid\nLibrary::registerDriverSymbols()\n{\n   RegisterFunctionInternal(internal::getName,\n                            internal::sDriverGetName);\n   RegisterFunctionInternal(internal::onInit,\n                            internal::sDriverOnInit);\n   RegisterFunctionInternal(internal::onAcquiredForeground,\n                            internal::sDriverOnAcquiredForeground);\n   RegisterFunctionInternal(internal::onReleasedForeground,\n                            internal::sDriverOnReleasedForeground);\n   RegisterFunctionInternal(internal::onDone,\n                            internal::sDriverOnDone);\n\n   RegisterDataInternal(internal::sDriverData);\n}\n\n} // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_internal_driver.h",
    "content": "#pragma once\n#include \"cafe/libraries/coreinit/coreinit_driver.h\"\n\nnamespace cafe::nn_acp::internal\n{\n\nvoid\nstartDriver(coreinit::OSDynLoad_ModuleHandle moduleHandle);\n\nvoid\nstopDriver(coreinit::OSDynLoad_ModuleHandle moduleHandle);\n\n} // namespace cafe::nn_acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_miscservice.cpp",
    "content": "#include \"nn_acp.h\"\n#include \"nn_acp_client.h\"\n#include \"nn_acp_miscservice.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/coreinit/coreinit_time.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/acp/nn_acp_result.h\"\n#include \"nn/acp/nn_acp_miscservice.h\"\n\n#include <chrono>\n#include <common/platform_time.h>\n\nusing namespace nn::acp;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_acp\n{\n\n//! Microseconds for Unix-epoch timestamp 01/01/2000 @ 12:00am\nstatic constexpr auto NetworkTimeEpoch = 946684800000000;\n\n\n/**\n * Convert network time to calendar time.\n *\n * networkTime is microseconds since NetworkTimeEpoch.\n */\nvoid\nACPConvertNetworkTimeToOSCalendarTime(int64_t networkTime,\n                                      virt_ptr<coreinit::OSCalendarTime> calendarTime)\n{\n   auto time = std::chrono::microseconds(networkTime) + std::chrono::microseconds(NetworkTimeEpoch);\n\n   auto tm = platform::localtime(std::chrono::duration_cast<std::chrono::seconds>(time).count());\n   calendarTime->tm_sec = tm.tm_sec;\n   calendarTime->tm_min = tm.tm_min;\n   calendarTime->tm_hour = tm.tm_hour;\n   calendarTime->tm_mday = tm.tm_mday;\n   calendarTime->tm_mon = tm.tm_mon;\n   calendarTime->tm_year = tm.tm_year + 1900; // posix tm_year is year - 1900\n   calendarTime->tm_wday = tm.tm_wday;\n   calendarTime->tm_yday = tm.tm_yday;\n\n   auto timeOffset = time - std::chrono::duration_cast<std::chrono::seconds>(time);\n   auto msOffset = std::chrono::duration_cast<std::chrono::milliseconds>(timeOffset);\n   auto uOffset = std::chrono::duration_cast<std::chrono::microseconds>(timeOffset - msOffset);\n   calendarTime->tm_msec = static_cast<int32_t>(msOffset.count());\n   calendarTime->tm_usec = static_cast<int32_t>(uOffset.count());\n}\n\n\n/**\n * Sets outTime to microseconds since NetworkTimeEpoch\n */\nACPResult\nACPGetNetworkTime(virt_ptr<int64_t> outTime,\n                  virt_ptr<uint32_t> outUnknown)\n{\n   auto command = ClientCommand<services::MiscService::GetNetworkTime> { internal::getAllocator() };\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      auto time = int64_t { 0 };\n      auto unk = uint32_t { 0 };\n      result = command.readResponse(time, unk);\n      if (result.ok()) {\n         *outTime = time - NetworkTimeEpoch;\n         *outUnknown = unk;\n      }\n   }\n\n   return ACPConvertToACPResult(result, \"ACPGetNetworkTime\", 79);\n}\n\nACPResult\nACPGetTitleIdOfMainApplication(virt_ptr<ACPTitleId> outTitleId)\n{\n   auto command = ClientCommand<services::MiscService::GetTitleIdOfMainApplication> { internal::getAllocator() };\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      auto titleId = ACPTitleId{ 0 };\n      result = command.readResponse(titleId);\n      if (result.ok()) {\n         *outTitleId = titleId;\n      }\n   }\n\n   return ACPConvertToACPResult(result, \"GetTitleIdOfMainApplication\", 126);\n}\n\nACPResult\nACPGetTitleMetaXml(ACPTitleId titleId,\n                   virt_ptr<ACPMetaXml> outData)\n{\n   auto command = ClientCommand<services::MiscService::GetTitleMetaXml> { internal::getAllocator() };\n   command.setParameters(outData, titleId);\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return ACPConvertToACPResult(result, \"GetTitleMetaXml\", 40);\n}\n\nvoid\nLibrary::registerMiscServiceSymbols()\n{\n   RegisterFunctionExport(ACPConvertNetworkTimeToOSCalendarTime);\n   RegisterFunctionExport(ACPGetNetworkTime);\n   RegisterFunctionExport(ACPGetTitleIdOfMainApplication);\n   RegisterFunctionExport(ACPGetTitleMetaXml);\n   RegisterFunctionExportName(\"GetTitleMetaXml__Q2_2nn3acpFULP11_ACPMetaXml\",\n                              ACPGetTitleMetaXml);\n}\n\n}  // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_miscservice.h",
    "content": "#pragma once\n#include \"nn_acp_acpresult.h\"\n#include \"nn/acp/nn_acp_miscservice.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::coreinit\n{\nstruct OSCalendarTime;\n} // namespace cafe::coreinit\n\nnamespace cafe::nn_acp\n{\n\nusing ACPMetaXml = nn::acp::ACPMetaXml;\nusing ACPTitleId = nn::acp::ACPTitleId;\n\nACPResult\nACPGetNetworkTime(virt_ptr<int64_t> outTime,\n                  virt_ptr<uint32_t> outUnknown);\n\nvoid\nACPConvertNetworkTimeToOSCalendarTime(int64_t networkTime,\n                                      virt_ptr<coreinit::OSCalendarTime> calendarTime);\n\nACPResult\nACPGetTitleIdOfMainApplication(virt_ptr<ACPTitleId> outTitleId);\n\nACPResult\nACPGetTitleMetaXml(ACPTitleId titleId,\n                   virt_ptr<ACPMetaXml> outData);\n\n}  // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_saveservice.cpp",
    "content": "#include \"nn_acp.h\"\n#include \"nn_acp_client.h\"\n#include \"nn_acp_saveservice.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/acp/nn_acp_result.h\"\n#include \"nn/acp/nn_acp_saveservice.h\"\n\nusing namespace nn::acp;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_acp\n{\n\nACPResult\nACPCreateSaveDir(uint32_t persistentId,\n                 ACPDeviceType deviceType)\n{\n   auto command = ClientCommand<services::SaveService::CreateSaveDir> { internal::getAllocator() };\n   command.setParameters(persistentId, deviceType);\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return ACPConvertToACPResult(result, \"ACPCreateSaveDir\", 771);\n}\n\nACPResult\nACPCreateSaveDirEx(uint32_t persistentId,\n                   ACPTitleId titleId,\n                   ACPDeviceType deviceType)\n{\n   auto command = ClientCommand<services::SaveService::CreateSaveDirEx> { internal::getAllocator() };\n   command.setParameters(persistentId, titleId, deviceType);\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return ACPConvertToACPResult(result, \"ACPCreateSaveDirEx\", 783);\n}\n\nACPResult\nACPIsExternalStorageRequired(virt_ptr<int32_t> outRequired)\n{\n   auto command = ClientCommand<services::SaveService::IsExternalStorageRequired> { internal::getAllocator() };\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      auto required = int32_t{ 0 };\n      result = command.readResponse(required);\n      if (result.ok()) {\n         *outRequired = required;\n      }\n   }\n\n   return ACPConvertToACPResult(result, \"ACPIsExternalStorageRequired\", 1464);\n}\n\nACPResult\nACPMountExternalStorage()\n{\n   auto command = ClientCommand<services::SaveService::MountExternalStorage> { internal::getAllocator() };\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return ACPConvertToACPResult(result, \"ACPMountExternalStorage\", 1452);\n}\n\nACPResult\nACPMountSaveDir()\n{\n   auto command = ClientCommand<services::SaveService::MountSaveDir> { internal::getAllocator() };\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return ACPConvertToACPResult(result, \"ACPMountSaveDir\", 96);\n}\n\nACPResult\nACPRepairSaveMetaDir()\n{\n   auto command = ClientCommand<services::SaveService::RepairSaveMetaDir> { internal::getAllocator() };\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return ACPConvertToACPResult(result, \"ACPRepairSaveMetaDir\", 1538);\n}\n\nACPResult\nACPUnmountExternalStorage()\n{\n   auto command = ClientCommand<services::SaveService::UnmountExternalStorage> { internal::getAllocator() };\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return ACPConvertToACPResult(result, \"ACPUnmountExternalStorage\", 1458);\n}\n\nACPResult\nACPUnmountSaveDir()\n{\n   auto command = ClientCommand<services::SaveService::UnmountSaveDir> { internal::getAllocator() };\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return ACPConvertToACPResult(result, \"ACPUnmountSaveDir\", 105);\n}\n\nvoid\nLibrary::registerSaveServiceSymbols()\n{\n   RegisterFunctionExport(ACPCreateSaveDir);\n   RegisterFunctionExport(ACPCreateSaveDirEx);\n   RegisterFunctionExport(ACPIsExternalStorageRequired);\n   RegisterFunctionExport(ACPMountExternalStorage);\n   RegisterFunctionExport(ACPMountSaveDir);\n   RegisterFunctionExport(ACPRepairSaveMetaDir);\n   RegisterFunctionExport(ACPUnmountExternalStorage);\n   RegisterFunctionExport(ACPUnmountSaveDir);\n\n   RegisterFunctionExportName(\"CreateSaveDir__Q2_2nn3acpFUi13ACPDeviceType\",\n                              ACPCreateSaveDir);\n   RegisterFunctionExportName(\"RepairSaveMetaDir__Q2_2nn3acpFv\",\n                              ACPRepairSaveMetaDir);\n}\n\n}  // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_acp/nn_acp_saveservice.h",
    "content": "#pragma once\n#include \"nn_acp_acpresult.h\"\n#include \"nn/acp/nn_acp_saveservice.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_acp\n{\n\nusing ACPDeviceType = nn::acp::ACPDeviceType;\n\nACPResult\nACPCreateSaveDir(uint32_t persistentId,\n                 ACPDeviceType deviceType);\n\nACPResult\nACPIsExternalStorageRequired(virt_ptr<int32_t> outRequired);\n\nACPResult\nACPMountExternalStorage();\n\nACPResult\nACPMountSaveDir();\n\nACPResult\nACPRepairSaveMetaDir();\n\nACPResult\nACPUnmountExternalStorage();\n\nACPResult\nACPUnmountSaveDir();\n\n}  // namespace cafe::nn_acp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act.cpp",
    "content": "#include \"nn_act.h\"\n\nnamespace cafe::nn_act\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerClientSymbols();\n   registerAccountLoaderServiceSymbols();\n   registerAccountManagerServiceSymbols();\n   registerClientStandardServiceSymbols();\n   registerServerStandardServiceSymbols();\n}\n\n} // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_act\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_act, \"nn_act.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerClientSymbols();\n   void registerAccountLoaderServiceSymbols();\n   void registerAccountManagerServiceSymbols();\n   void registerClientStandardServiceSymbols();\n   void registerServerStandardServiceSymbols();\n};\n\n} // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act_accountloaderservice.cpp",
    "content": "#include \"nn_act.h\"\n#include \"nn_act_client.h\"\n#include \"nn_act_accountloaderservice.h\"\n\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/act/nn_act_accountloaderservice.h\"\n#include \"nn/act/nn_act_result.h\"\n\n#include <common/strutils.h>\n#include <chrono>\n\nusing namespace nn::act;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_act\n{\n\nnn::Result\nLoadConsoleAccount(SlotNo slot,\n                   ACTLoadOption loadOption,\n                   virt_ptr<const char> arg3,\n                   bool arg4)\n{\n   auto command = ClientCommand<services::AccountLoaderService::LoadConsoleAccount> { internal::getAllocator() };\n   if (arg3) {\n      auto arg3Size = static_cast<uint32_t>(strnlen(arg3.get(), 16) + 1);\n      if (arg3Size > 0x11) {\n         return ResultInvalidSize;\n      }\n\n      command.setParameters(slot, loadOption, { arg3, arg3Size }, !!arg4);\n   } else {\n      command.setParameters(slot, loadOption, { nullptr, 0 }, !!arg4);\n   }\n\n   return internal::getClient()->sendSyncRequest(command);\n}\n\nvoid\nLibrary::registerAccountLoaderServiceSymbols()\n{\n   RegisterFunctionExportName(\"LoadConsoleAccount__Q2_2nn3actFUc13ACTLoadOptionPCcb\",\n                              LoadConsoleAccount);\n}\n\n}  // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act_accountloaderservice.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n#include \"nn/act/nn_act_enum.h\"\n#include \"nn/act/nn_act_types.h\"\n\nnamespace cafe::nn_act\n{\n\nnn::Result\nLoadConsoleAccount(nn::act::SlotNo slot,\n                   nn::act::ACTLoadOption option,\n                   virt_ptr<const char> arg3,\n                   bool arg4);\n\n}  // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act_accountmanagerservice.cpp",
    "content": "#include \"nn_act.h\"\n#include \"nn_act_client.h\"\n#include \"nn_act_accountmanagerservice.h\"\n\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/act/nn_act_accountmanagerservice.h\"\n\n#include <chrono>\n\nusing namespace nn::act;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_act\n{\n\nnn::Result\nCreateConsoleAccount()\n{\n   auto command = ClientCommand<services::AccountManagerService::CreateConsoleAccount> { internal::getAllocator() };\n   command.setParameters();\n   return internal::getClient()->sendSyncRequest(command);\n}\n\nvoid\nLibrary::registerAccountManagerServiceSymbols()\n{\n   RegisterFunctionExportName(\"CreateConsoleAccount__Q2_2nn3actFv\",\n                              CreateConsoleAccount);\n}\n\n}  // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act_accountmanagerservice.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace cafe::nn_act\n{\nnn::Result\nCreateConsoleAccount();\n\n}  // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act_client.cpp",
    "content": "#include \"nn_act.h\"\n#include \"nn_act_client.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/act/nn_act_result.h\"\n\nusing namespace cafe::coreinit;\nusing namespace nn::act;\n\nnamespace cafe::nn_act\n{\n\nstruct StaticClientData\n{\n   StaticClientData()\n   {\n      OSInitMutex(virt_addrof(mutex));\n   }\n\n   alignas(64) be2_array<uint8_t, 0x10000> allocatorMemory;\n\n   be2_struct<OSMutex> mutex;\n   be2_val<uint32_t> refCount = 0u;\n   be2_struct<nn::ipc::Client> client;\n   be2_struct<nn::ipc::BufferAllocator> allocator;\n};\n\nstatic virt_ptr<StaticClientData> sClientData = nullptr;\n\nnn::Result\nInitialize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount == 0) {\n      sClientData->client.initialise(make_stack_string(\"/dev/act\"));\n      sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory),\n                                        sClientData->allocatorMemory.size());\n\n   }\n\n   sClientData->refCount++;\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n   return nn::ResultSuccess;\n}\n\nnn::Result\nFinalize()\n{\n   auto result = nn::ResultSuccess;\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount > 0) {\n      sClientData->refCount--;\n\n      if (sClientData->refCount == 0) {\n         sClientData->client.close();\n      }\n   } else {\n      result = 0xC070FA80;\n   }\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n   return result;\n}\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient()\n{\n   return virt_addrof(sClientData->client);\n}\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator()\n{\n   return virt_addrof(sClientData->allocator);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerClientSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn3actFv\", Initialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn3actFv\", Finalize);\n\n   RegisterDataInternal(sClientData);\n}\n\n}  // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act_client.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"cafe/nn/cafe_nn_ipc_bufferallocator.h\"\n\nnamespace cafe::nn_act\n{\n\nnn::Result\nInitialize();\n\nnn::Result\nFinalize();\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient();\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator();\n\n} // namespace internal\n\n} // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act_clientstandardservice.cpp",
    "content": "#include \"nn_act.h\"\n#include \"nn_act_client.h\"\n#include \"nn_act_clientstandardservice.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/act/nn_act_result.h\"\n#include \"nn/act/nn_act_clientstandardservice.h\"\n#include \"nn/ffl/nn_ffl_miidata.h\"\n\n#include <chrono>\n\nusing namespace nn::act;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_act\n{\n\nnn::Result\nGetAccountId(virt_ptr<char> accountId)\n{\n   if (!accountId) {\n      return ResultInvalidPointer;\n   }\n\n   return internal::GetAccountInfo(CurrentUserSlot, accountId, AccountIdSize,\n                                   InfoType::AccountId);\n}\n\nnn::Result\nGetAccountIdEx(virt_ptr<char> accountId,\n               SlotNo slotNo)\n{\n   if (!accountId) {\n      return ResultInvalidPointer;\n   }\n\n   return internal::GetAccountInfo(slotNo, accountId, AccountIdSize,\n                                   InfoType::AccountId);\n}\n\nnn::Result\nGetBirthday(virt_ptr<uint16_t> year,\n            virt_ptr<uint8_t> month,\n            virt_ptr<uint8_t> day)\n{\n   return GetBirthdayEx(year, month, day, CurrentUserSlot);\n}\n\nnn::Result\nGetBirthdayEx(virt_ptr<uint16_t> year,\n              virt_ptr<uint8_t> month,\n              virt_ptr<uint8_t> day,\n              SlotNo slotNo)\n{\n   StackObject<Birthday> birthday;\n   if (!year || !month || !day) {\n      return ResultInvalidPointer;\n   }\n\n   auto result = internal::GetAccountInfo(CurrentUserSlot, birthday,\n                                          sizeof(Birthday), InfoType::Birthday);\n   if (result) {\n      *year = birthday->year;\n      *month = birthday->month;\n      *day = birthday->day;\n   }\n\n   return result;\n}\n\nSlotNo\nGetDefaultAccount()\n{\n   StackObject<SlotNo> slotNo;\n   if (!internal::GetAccountInfo(CurrentUserSlot, slotNo, sizeof(SlotNo),\n                                 InfoType::DefaultAccount)) {\n      return 0;\n   }\n\n   return *slotNo;\n}\n\nnn::Result\nGetDeviceHash(virt_ptr<uint64_t> hash)\n{\n   if (!hash) {\n      return ResultInvalidPointer;\n   }\n\n   return internal::GetCommonInfo(hash, sizeof(uint64_t), InfoType::DeviceHash);\n}\n\nnn::Result\nGetMii(virt_ptr<nn::ffl::FFLStoreData> mii)\n{\n   if (!mii) {\n      return ResultInvalidPointer;\n   }\n\n   return internal::GetAccountInfo(CurrentUserSlot, mii,\n                                   sizeof(nn::ffl::FFLStoreData),\n                                   InfoType::Mii);\n}\n\nnn::Result\nGetMiiEx(virt_ptr<nn::ffl::FFLStoreData> mii,\n         SlotNo slotNo)\n{\n   if (!mii) {\n      return ResultInvalidPointer;\n   }\n\n   return internal::GetAccountInfo(slotNo, mii,\n                                   sizeof(nn::ffl::FFLStoreData),\n                                   InfoType::Mii);\n}\n\nnn::Result\nGetMiiImageEx(virt_ptr<uint32_t> outImageSize,\n              virt_ptr<void> buffer,\n              uint32_t bufferSize,\n              MiiImageType miiImageType,\n              SlotNo slot)\n{\n   auto command = ClientCommand<services::ClientStandardService::GetMiiImage>{ internal::getAllocator() };\n   command.setParameters(slot, { buffer, bufferSize }, miiImageType);\n\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result) {\n      auto imageSize = uint32_t { 0 };\n      result = command.readResponse(imageSize);\n      if (result) {\n         *outImageSize = imageSize;\n      }\n   }\n\n   return result;\n}\n\nnn::Result\nGetMiiName(virt_ptr<char16_t> name)\n{\n   return GetMiiNameEx(name, CurrentUserSlot);\n}\n\nnn::Result\nGetMiiNameEx(virt_ptr<char16_t> name,\n             SlotNo slotNo)\n{\n   if (!name) {\n      return ResultInvalidPointer;\n   }\n\n   return internal::GetAccountInfo(slotNo, name, MiiNameSize * sizeof(char16_t),\n                                   InfoType::MiiName);\n}\n\nnn::Result\nGetNfsPassword(virt_ptr<char> password)\n{\n   return GetNfsPasswordEx(password, CurrentUserSlot);\n}\n\nnn::Result\nGetNfsPasswordEx(virt_ptr<char> password,\n                 SlotNo slotNo)\n{\n   if (!password) {\n      return ResultInvalidPointer;\n   }\n\n   return internal::GetAccountInfo(slotNo, password, 17, InfoType::NfsPassword);\n}\n\nuint8_t\nGetNumOfAccounts()\n{\n   StackObject<uint8_t> num;\n   if (!internal::GetCommonInfo(num, sizeof(uint8_t), InfoType::NumOfAccounts)) {\n      return 0;\n   }\n\n   return *num;\n}\n\nSlotNo\nGetParentalControlSlotNo()\n{\n   StackObject<SlotNo> slotNo;\n   if (!internal::GetAccountInfo(CurrentUserSlot, slotNo, sizeof(SlotNo),\n                                 InfoType::ParentalControlSlot)) {\n      return 0;\n   }\n\n   return *slotNo;\n}\n\nnn::Result\nGetParentalControlSlotNoEx(virt_ptr<SlotNo> parentalSlotNo,\n                           SlotNo slotNo)\n{\n   if (!parentalSlotNo) {\n      return ResultInvalidPointer;\n   }\n\n   return internal::GetAccountInfo(slotNo, parentalSlotNo, sizeof(SlotNo),\n                                   InfoType::ParentalControlSlot);\n}\n\nPersistentId\nGetPersistentId()\n{\n   StackObject<PersistentId> id;\n   if (!internal::GetAccountInfo(CurrentUserSlot, id, sizeof(PersistentId),\n                                 InfoType::PersistentId)) {\n      return 0;\n   }\n\n   return *id;\n}\n\nPersistentId\nGetPersistentIdEx(SlotNo slotNo)\n{\n   StackObject<PersistentId> id;\n   if (!internal::GetAccountInfo(slotNo, id, sizeof(PersistentId),\n                                 InfoType::PersistentId)) {\n      return 0;\n   }\n\n   return *id;\n}\n\nPrincipalId\nGetPrincipalId()\n{\n   StackObject<PrincipalId> id;\n   if (!internal::GetAccountInfo(CurrentUserSlot, id, sizeof(PrincipalId),\n                                 InfoType::PrincipalId)) {\n      return 0;\n   }\n\n   return *id;\n}\n\nnn::Result\nGetPrincipalIdEx(virt_ptr<PrincipalId> id,\n                 SlotNo slot)\n{\n   if (!id) {\n      return ResultInvalidPointer;\n   }\n\n   return internal::GetAccountInfo(slot, id, sizeof(PrincipalId),\n                                   InfoType::PrincipalId);\n}\n\nSimpleAddressId\nGetSimpleAddressId()\n{\n   StackObject<SimpleAddressId> id;\n   if (!internal::GetAccountInfo(CurrentUserSlot, id, sizeof(SimpleAddressId),\n                                 InfoType::SimpleAddressId)) {\n      return 0;\n   }\n\n   return *id;\n}\n\nnn::Result\nGetSimpleAddressIdEx(virt_ptr<SimpleAddressId> id,\n                     SlotNo slot)\n{\n   if (!id) {\n      return ResultInvalidPointer;\n   }\n\n   return internal::GetAccountInfo(slot, id, sizeof(SimpleAddressId),\n                                   InfoType::SimpleAddressId);\n}\n\nSlotNo\nGetSlotNo()\n{\n   StackObject<SlotNo> slotNo;\n   if (!internal::GetCommonInfo(slotNo, sizeof(SlotNo), InfoType::SlotNo)) {\n      return 0;\n   }\n\n   return *slotNo;\n}\n\nuint64_t\nGetTransferableId(uint32_t unk1)\n{\n   StackObject<uint64_t> id;\n   GetTransferableIdEx(id, unk1, CurrentUserSlot);\n   return *id;\n}\n\nnn::Result\nGetTransferableIdEx(virt_ptr<TransferrableId> id,\n                    uint32_t unk1,\n                    SlotNo slotNo)\n{\n   if (!id) {\n      return ResultInvalidPointer;\n   }\n\n   auto command = ClientCommand<services::ClientStandardService::GetTransferableId> { internal::getAllocator() };\n   command.setParameters(slotNo, unk1);\n\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      auto value = TransferrableId { };\n      result = command.readResponse(value);\n      if (result.ok()) {\n         *id = value;\n      }\n   }\n\n   return result;\n}\n\nnn::Result\nGetUuidEx(virt_ptr<Uuid> uuid,\n          SlotNo slotNo,\n          int32_t arg3)\n{\n   auto command = ClientCommand<services::ClientStandardService::GetUuid> { internal::getAllocator() };\n   command.setParameters(slotNo, uuid, arg3);\n\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return result;\n}\n\nnn::Result\nGetUuidEx(virt_ptr<Uuid> uuid,\n          SlotNo slot)\n{\n   return GetUuidEx(uuid, slot, -2);\n}\n\nnn::Result\nGetUuid(virt_ptr<Uuid> uuid,\n        int32_t arg3)\n{\n   return GetUuidEx(uuid, CurrentUserSlot, arg3);\n}\n\nnn::Result\nGetUuid(virt_ptr<Uuid> uuid)\n{\n   return GetUuidEx(uuid, CurrentUserSlot, -2);\n}\n\nbool\nHasNfsAccount()\n{\n   StackArray<char, 17> nfsPassword;\n   if (!GetNfsPassword(nfsPassword)) {\n      return false;\n   }\n\n   return nfsPassword[0] != '\\0';\n}\n\nbool\nIsCommitted()\n{\n   return IsCommittedEx(CurrentUserSlot);\n}\n\nbool\nIsCommittedEx(SlotNo slotNo)\n{\n   StackObject<uint8_t> value;\n   if (!internal::GetAccountInfo(slotNo, value, sizeof(uint8_t),\n                                 InfoType::IsCommitted)) {\n      return 0;\n   }\n\n   return *value != 0;\n}\n\nbool\nIsPasswordCacheEnabled()\n{\n   return IsPasswordCacheEnabledEx(GetSlotNo());\n}\n\nbool\nIsPasswordCacheEnabledEx(SlotNo slotNo)\n{\n   StackObject<uint8_t> value;\n   if (!internal::GetAccountInfo(slotNo, value, sizeof(uint8_t),\n                                 InfoType::IsPasswordCacheEnabled)) {\n      return 0;\n   }\n\n   return *value != 0;\n}\n\nbool\nIsNetworkAccount()\n{\n   StackArray<char, AccountIdSize> accountId;\n   if (!GetAccountId(accountId)) {\n      return false;\n   }\n\n   return accountId[0] != 0;\n}\n\nbool\nIsNetworkAccountEx(SlotNo slotNo)\n{\n   StackArray<char, AccountIdSize> accountId;\n   if (!GetAccountIdEx(accountId, slotNo)) {\n      return false;\n   }\n\n   return accountId[0] != 0;\n}\n\nbool\nIsServerAccountActive()\n{\n   return IsServerAccountActiveEx(CurrentUserSlot);\n}\n\nbool\nIsServerAccountActiveEx(SlotNo slotNo)\n{\n   StackObject<uint32_t> value;\n   if (!internal::GetAccountInfo(slotNo, value, sizeof(uint32_t),\n                                 InfoType::ServerAccountStatus)) {\n      return 0;\n   }\n\n   return *value == 0;\n}\n\nbool\nIsServerAccountDeleted()\n{\n   return IsServerAccountDeletedEx(CurrentUserSlot);\n}\n\nbool\nIsServerAccountDeletedEx(SlotNo slotNo)\n{\n   StackObject<uint8_t> value;\n   if (!internal::GetAccountInfo(slotNo, value, sizeof(uint8_t),\n                                 InfoType::IsServerAccountDeleted)) {\n      return 0;\n   }\n\n   return !!*value;\n}\n\nbool\nIsSlotOccupied(SlotNo slot)\n{\n   return GetPersistentIdEx(slot) != 0;\n}\n\nnamespace internal\n{\n\nnn::Result\nGetAccountInfo(SlotNo slotNo,\n               virt_ptr<void> buffer,\n               uint32_t bufferSize,\n               InfoType type)\n{\n   auto command = ClientCommand<services::ClientStandardService::GetAccountInfo> { internal::getAllocator() };\n   command.setParameters(slotNo, { buffer, bufferSize }, type);\n\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return result;\n}\n\nnn::Result\nGetCommonInfo(virt_ptr<void> buffer,\n              uint32_t bufferSize,\n              InfoType type)\n{\n   auto command = ClientCommand<services::ClientStandardService::GetCommonInfo> { internal::getAllocator() };\n   command.setParameters({ buffer, bufferSize }, type);\n\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return result;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerClientStandardServiceSymbols()\n{\n   RegisterFunctionExportName(\"GetAccountId__Q2_2nn3actFPc\",\n                              GetAccountId);\n   RegisterFunctionExportName(\"GetAccountIdEx__Q2_2nn3actFPcUc\",\n                              GetAccountIdEx);\n   RegisterFunctionExportName(\"GetBirthday__Q2_2nn3actFPUsPUcT2\",\n                              GetBirthday);\n   RegisterFunctionExportName(\"GetBirthdayEx__Q2_2nn3actFPUsPUcT2Uc\",\n                              GetBirthdayEx);\n   RegisterFunctionExportName(\"GetDefaultAccount__Q2_2nn3actFv\",\n                              GetDefaultAccount);\n   RegisterFunctionExportName(\"GetDeviceHash__Q2_2nn3actFPUL\",\n                              GetDeviceHash);\n   RegisterFunctionExportName(\"GetMii__Q2_2nn3actFP12FFLStoreData\",\n                              GetMii);\n   RegisterFunctionExportName(\"GetMiiEx__Q2_2nn3actFP12FFLStoreDataUc\",\n                              GetMiiEx);\n   RegisterFunctionExportName(\"GetMiiImageEx__Q2_2nn3actFPUiPvUi15ACTMiiImageTypeUc\",\n                              GetMiiImageEx);\n   RegisterFunctionExportName(\"GetMiiName__Q2_2nn3actFPw\",\n                              GetMiiName);\n   RegisterFunctionExportName(\"GetMiiNameEx__Q2_2nn3actFPwUc\",\n                              GetMiiNameEx);\n   RegisterFunctionExportName(\"GetNfsPassword__Q2_2nn3actFPc\",\n                              GetNfsPassword);\n   RegisterFunctionExportName(\"GetNfsPasswordEx__Q2_2nn3actFPcUc\",\n                              GetNfsPasswordEx);\n   RegisterFunctionExportName(\"GetNumOfAccounts__Q2_2nn3actFv\",\n                              GetNumOfAccounts);\n   RegisterFunctionExportName(\"GetParentalControlSlotNo__Q2_2nn3actFv\",\n                              GetParentalControlSlotNo);\n   RegisterFunctionExportName(\"GetParentalControlSlotNoEx__Q2_2nn3actFPUcUc\",\n                              GetParentalControlSlotNoEx);\n   RegisterFunctionExportName(\"GetPersistentId__Q2_2nn3actFv\",\n                              GetPersistentId);\n   RegisterFunctionExportName(\"GetPersistentIdEx__Q2_2nn3actFUc\",\n                              GetPersistentIdEx);\n   RegisterFunctionExportName(\"GetPrincipalId__Q2_2nn3actFv\",\n                              GetPrincipalId);\n   RegisterFunctionExportName(\"GetPrincipalIdEx__Q2_2nn3actFPUiUc\",\n                              GetPrincipalIdEx);\n   RegisterFunctionExportName(\"GetSimpleAddressId__Q2_2nn3actFv\",\n                              GetSimpleAddressId);\n   RegisterFunctionExportName(\"GetSimpleAddressIdEx__Q2_2nn3actFPUiUc\",\n                              GetSimpleAddressIdEx);\n   RegisterFunctionExportName(\"GetSlotNo__Q2_2nn3actFv\",\n                              GetSlotNo);\n   RegisterFunctionExportName(\"GetTransferableId__Q2_2nn3actFUi\",\n                              GetTransferableId);\n   RegisterFunctionExportName(\"GetTransferableIdEx__Q2_2nn3actFPULUiUc\",\n                              GetTransferableIdEx);\n   RegisterFunctionExportName(\"GetUuid__Q2_2nn3actFP7ACTUuid\",\n                              static_cast<nn::Result(*)(virt_ptr<Uuid>)>(GetUuid));\n   RegisterFunctionExportName(\"GetUuid__Q2_2nn3actFP7ACTUuidUi\",\n                              static_cast<nn::Result(*)(virt_ptr<Uuid>, int32_t)>(GetUuid));\n   RegisterFunctionExportName(\"GetUuidEx__Q2_2nn3actFP7ACTUuidUc\",\n                              static_cast<nn::Result(*)(virt_ptr<Uuid>, SlotNo)>(GetUuidEx));\n   RegisterFunctionExportName(\"GetUuidEx__Q2_2nn3actFP7ACTUuidUcUi\",\n                              static_cast<nn::Result(*)(virt_ptr<Uuid>, SlotNo, int32_t)>(GetUuidEx));\n   RegisterFunctionExportName(\"HasNfsAccount__Q2_2nn3actFv\",\n                              HasNfsAccount);\n   RegisterFunctionExportName(\"IsCommitted__Q2_2nn3actFv\",\n                              IsCommitted);\n   RegisterFunctionExportName(\"IsCommittedEx__Q2_2nn3actFUc\",\n                              IsCommittedEx);\n   RegisterFunctionExportName(\"IsPasswordCacheEnabled__Q2_2nn3actFv\",\n                              IsPasswordCacheEnabled);\n   RegisterFunctionExportName(\"IsPasswordCacheEnabledEx__Q2_2nn3actFUc\",\n                              IsPasswordCacheEnabledEx);\n   RegisterFunctionExportName(\"IsNetworkAccount__Q2_2nn3actFv\",\n                              IsNetworkAccount);\n   RegisterFunctionExportName(\"IsNetworkAccountEx__Q2_2nn3actFUc\",\n                              IsNetworkAccountEx);\n   RegisterFunctionExportName(\"IsServerAccountActive__Q2_2nn3actFv\",\n                              IsServerAccountActive);\n   RegisterFunctionExportName(\"IsServerAccountActiveEx__Q2_2nn3actFUc\",\n                              IsServerAccountActiveEx);\n   RegisterFunctionExportName(\"IsServerAccountDeleted__Q2_2nn3actFv\",\n                              IsServerAccountDeleted);\n   RegisterFunctionExportName(\"IsServerAccountDeletedEx__Q2_2nn3actFUc\",\n                              IsServerAccountDeletedEx);\n   RegisterFunctionExportName(\"IsSlotOccupied__Q2_2nn3actFUc\",\n                              IsSlotOccupied);\n}\n\n}  // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act_clientstandardservice.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n#include \"nn/act/nn_act_enum.h\"\n#include \"nn/act/nn_act_types.h\"\n#include \"nn/ffl/nn_ffl_miidata.h\"\n\n#include <cstdint>\n\nnamespace cafe::nn_act\n{\n\nusing nn::act::MiiImageType;\nusing nn::act::PersistentId;\nusing nn::act::PrincipalId;\nusing nn::act::SlotNo;\nusing nn::act::SimpleAddressId;\nusing nn::act::TransferrableId;\nusing nn::act::Uuid;\n\nnn::Result\nGetAccountId(virt_ptr<char> accountId);\n\nnn::Result\nGetAccountIdEx(virt_ptr<char> accountId,\n               SlotNo slotNo);\n\nnn::Result\nGetBirthday(virt_ptr<uint16_t> year,\n            virt_ptr<uint8_t> month,\n            virt_ptr<uint8_t> day);\n\nnn::Result\nGetBirthdayEx(virt_ptr<uint16_t> year,\n              virt_ptr<uint8_t> month,\n              virt_ptr<uint8_t> day,\n              SlotNo slotNo);\n\nSlotNo\nGetDefaultAccount();\n\nnn::Result\nGetDeviceHash(virt_ptr<uint64_t> hash);\n\nnn::Result\nGetMii(virt_ptr<nn::ffl::FFLStoreData> mii);\n\nnn::Result\nGetMiiEx(virt_ptr<nn::ffl::FFLStoreData> mii,\n         SlotNo slotNo);\n\nnn::Result\nGetMiiImageEx(virt_ptr<uint32_t> outImageSize,\n              virt_ptr<void> buffer,\n              uint32_t bufferSize,\n              MiiImageType miiImageType,\n              SlotNo slot);\n\nnn::Result\nGetMiiName(virt_ptr<char16_t> name);\n\nnn::Result\nGetMiiNameEx(virt_ptr<char16_t> name,\n             SlotNo slotNo);\n\nnn::Result\nGetNfsPassword(virt_ptr<char> password);\n\nnn::Result\nGetNfsPasswordEx(virt_ptr<char> password,\n                 SlotNo slotNo);\n\nuint8_t\nGetNumOfAccounts();\n\nSlotNo\nGetParentalControlSlotNo();\n\nnn::Result\nGetParentalControlSlotNoEx(virt_ptr<SlotNo> parentalSlotNo,\n                           SlotNo slotNo);\n\nPersistentId\nGetPersistentId();\n\nPersistentId\nGetPersistentIdEx(SlotNo slotNo);\n\nPrincipalId\nGetPrincipalId();\n\nnn::Result\nGetPrincipalIdEx(virt_ptr<PrincipalId> id,\n                 SlotNo slot);\n\nSimpleAddressId\nGetSimpleAddressId();\n\nnn::Result\nGetSimpleAddressIdEx(virt_ptr<SimpleAddressId> id,\n                     SlotNo slot);\n\nSlotNo\nGetSlotNo();\n\nuint64_t\nGetTransferableId(uint32_t unk1);\n\nnn::Result\nGetTransferableIdEx(virt_ptr<TransferrableId> id,\n                    uint32_t unk1,\n                    SlotNo slotNo);\n\nnn::Result\nGetUuidEx(virt_ptr<Uuid> uuid,\n          SlotNo slotNo,\n          int32_t arg3);\n\nnn::Result\nGetUuidEx(virt_ptr<Uuid> uuid,\n          SlotNo slot);\n\nnn::Result\nGetUuid(virt_ptr<Uuid> uuid,\n        int32_t arg3);\n\nnn::Result\nGetUuid(virt_ptr<Uuid> uuid);\n\nbool\nHasNfsAccount();\n\nbool\nIsCommitted();\n\nbool\nIsCommittedEx(SlotNo slotNo);\n\nbool\nIsPasswordCacheEnabled();\n\nbool\nIsPasswordCacheEnabledEx(SlotNo slotNo);\n\nbool\nIsNetworkAccount();\n\nbool\nIsNetworkAccountEx(SlotNo slotNo);\n\nbool\nIsServerAccountActive();\n\nbool\nIsServerAccountActiveEx(SlotNo slotNo);\n\nbool\nIsServerAccountDeleted();\n\nbool\nIsServerAccountDeletedEx(SlotNo slotNo);\n\nbool\nIsSlotOccupied(SlotNo slot);\n\nnamespace internal\n{\n\nnn::Result\nGetAccountInfo(nn::act::SlotNo slotNo,\n               virt_ptr<void> buffer,\n               uint32_t bufferSize,\n               nn::act::InfoType type);\n\nnn::Result\nGetCommonInfo(virt_ptr<void> buffer,\n              uint32_t bufferSize,\n              nn::act::InfoType type);\n\n} // namespace internal\n\n}  // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act_serverstandardservice.cpp",
    "content": "#include \"nn_act.h\"\n#include \"nn_act_client.h\"\n#include \"nn_act_serverstandardservice.h\"\n\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/act/nn_act_serverstandardservice.h\"\n\n#include <chrono>\n\nusing namespace nn::act;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_act\n{\n\nusing services::ServerStandardService;\n\nstruct StaticServerData\n{\n   be2_val<bool> parentalControlCheckEnabled = false;\n};\n\nstatic virt_ptr<StaticServerData> sStaticServerData = nullptr;\n\nnn::Result\nAcquireNexServiceToken(virt_ptr<ACTNexAuthenticationResult> result,\n                       uint32_t gameId)\n{\n   auto command = ClientCommand<ServerStandardService::AcquireNexServiceToken> {\n         internal::getAllocator()\n      };\n   command.setParameters(CurrentUserSlot, result, gameId,\n                         sStaticServerData->parentalControlCheckEnabled);\n   return internal::getClient()->sendSyncRequest(command);\n}\n\nnn::Result\nCancel()\n{\n   auto command = ClientCommand<ServerStandardService::Cancel> {\n         internal::getAllocator()\n      };\n   command.setParameters();\n   return internal::getClient()->sendSyncRequest(command);\n}\n\nvoid\nEnableParentalControlCheck(bool enable)\n{\n   sStaticServerData->parentalControlCheckEnabled = enable;\n}\n\nbool\nIsParentalControlCheckEnabled()\n{\n   return sStaticServerData->parentalControlCheckEnabled;\n}\n\nvoid\nLibrary::registerServerStandardServiceSymbols()\n{\n   RegisterFunctionExportName(\"AcquireNexServiceToken__Q2_2nn3actFP26ACTNexAuthenticationResultUi\",\n                              AcquireNexServiceToken);\n   RegisterFunctionExportName(\"Cancel__Q2_2nn3actFv\",\n                              Cancel);\n   RegisterFunctionExportName(\"IsParentalControlCheckEnabled__Q2_2nn3actFv\",\n                              IsParentalControlCheckEnabled);\n   RegisterFunctionExportName(\"EnableParentalControlCheck__Q2_2nn3actFb\",\n                              EnableParentalControlCheck);\n\n   RegisterDataInternal(sStaticServerData);\n}\n\n}  // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_act/nn_act_serverstandardservice.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n#include \"nn/act/nn_act_types.h\"\n\nnamespace cafe::nn_act\n{\n\nusing ACTNexAuthenticationResult = nn::act::NexAuthenticationResult;\n\nnn::Result\nAcquireNexServiceToken(virt_ptr<ACTNexAuthenticationResult> result,\n                       uint32_t unk);\n\nnn::Result\nCancel();\n\nbool\nIsParentalControlCheckEnabled();\n\nvoid\nEnableParentalControlCheck(bool enable);\n\n}  // namespace cafe::nn_act\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_aoc/nn_aoc.cpp",
    "content": "#include \"nn_aoc.h\"\n\nnamespace cafe::nn_aoc\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerLibSymbols();\n}\n\n} // namespace cafe::nn_aoc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_aoc/nn_aoc.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_aoc\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_aoc, \"nn_aoc.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerLibSymbols();\n};\n\n} // namespace cafe::nn_aoc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_aoc/nn_aoc_enum.h",
    "content": "#ifndef CAFE_NN_AOC_ENUM_H\n#define CAFE_NN_AOC_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(nn_aoc)\n\nENUM_BEG(AOCError, int32_t)\n   ENUM_VALUE(OK,                0)\n   ENUM_VALUE(GenericError,      -1)\nENUM_END(AOCError)\n\nENUM_NAMESPACE_EXIT(nn_aoc)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_NN_AOC_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_aoc/nn_aoc_lib.cpp",
    "content": "#include \"nn_aoc.h\"\n#include \"nn_aoc_lib.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::nn_aoc\n{\n\nAOCError\nAOC_Initialize()\n{\n   decaf_warn_stub();\n   return AOCError::OK;\n}\n\nAOCError\nAOC_Finalize()\n{\n   decaf_warn_stub();\n   return AOCError::OK;\n}\n\nuint32_t\nAOC_CalculateWorkBufferSize(uint32_t maxTitles)\n{\n   if (maxTitles > 256) {\n      maxTitles = 256;\n   }\n\n   return (0x61 * maxTitles) + 0x80;\n}\n\nAOCError\nAOC_ListTitle(virt_ptr<uint32_t> outTitleCount,\n              virt_ptr<AOCTitle> titles,\n              uint32_t maxTitles,\n              virt_ptr<void> workBuffer,\n              uint32_t workBufferSize)\n{\n   decaf_warn_stub();\n   *outTitleCount = 0u;\n   return AOCError::OK;\n}\n\nvoid\nLibrary::registerLibSymbols()\n{\n   RegisterFunctionExportName(\"AOC_Initialize\",\n                              AOC_Initialize);\n   RegisterFunctionExportName(\"AOC_Finalize\",\n                              AOC_Finalize);\n   RegisterFunctionExportName(\"AOC_CalculateWorkBufferSize\",\n                              AOC_CalculateWorkBufferSize);\n   RegisterFunctionExportName(\"AOC_ListTitle\",\n                              AOC_ListTitle);\n}\n\n} // namespace cafe::nn_aoc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_aoc/nn_aoc_lib.h",
    "content": "#pragma once\n#include \"nn_aoc_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_aoc\n{\n\n#pragma pack(push, 1)\n\nstruct AOCTitle\n{\n   UNKNOWN(0x61);\n};\nCHECK_SIZE(AOCTitle, 0x61);\n\n#pragma pack(pop)\n\nAOCError\nAOC_Initialize();\n\nAOCError\nAOC_Finalize();\n\nuint32_t\nAOC_CalculateWorkBufferSize(uint32_t maxTitles);\n\nAOCError\nAOC_ListTitle(virt_ptr<uint32_t> outTitleCount,\n              virt_ptr<AOCTitle> titles,\n              uint32_t maxTitles,\n              virt_ptr<void> workBuffer,\n              uint32_t workBufferSize);\n\n} // namespace cafe::nn_aoc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss.cpp",
    "content": "#include \"nn_boss.h\"\n\nnamespace cafe::nn_boss\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerAccountSymbols();\n   registerAlmightyStorageSymbols();\n   registerAlmightyTaskSymbols();\n   registerClientSymbols();\n   registerDataNameSymbols();\n   registerNbdlTaskSettingSymbols();\n   registerNetTaskSettingSymbols();\n   registerPlayLogUploadTaskSettingSymbols();\n   registerPlayReportSettingSymbols();\n   registerPrivilegedTaskSymbols();\n   registerRawUlTaskSettingSymbols();\n   registerStorageSymbols();\n   registerTaskSymbols();\n   registerTaskIdSymbols();\n   registerTaskSettingSymbols();\n   registerTitleSymbols();\n   registerTitleIdSymbols();\n}\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_boss\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_boss, \"nn_boss.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerAccountSymbols();\n   void registerAlmightyStorageSymbols();\n   void registerAlmightyTaskSymbols();\n   void registerClientSymbols();\n   void registerDataNameSymbols();\n   void registerNbdlTaskSettingSymbols();\n   void registerNetTaskSettingSymbols();\n   void registerPlayLogUploadTaskSettingSymbols();\n   void registerPlayReportSettingSymbols();\n   void registerPrivilegedTaskSymbols();\n   void registerRawUlTaskSettingSymbols();\n   void registerStorageSymbols();\n   void registerTaskSymbols();\n   void registerTaskIdSymbols();\n   void registerTaskSettingSymbols();\n   void registerTitleSymbols();\n   void registerTitleIdSymbols();\n};\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_account.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_account.h\"\n#include \"nn_boss_client.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/boss/nn_boss_result.h\"\n#include \"nn/boss/nn_boss_privileged_service.h\"\n\nusing namespace nn::boss;\nusing namespace nn::boss::services;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> Account::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> Account::TypeDescriptor = nullptr;\n\nvirt_ptr<Account>\nAccount_Constructor(virt_ptr<Account> self,\n                    uint32_t a1)\n{\n   if (!self) {\n      self = virt_cast<Account *>(ghs::malloc(sizeof(Account)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->virtualTable = Account::VirtualTable;\n   self->unk0x00 = a1;\n   return self;\n}\n\nvoid\nAccount_Destructor(virt_ptr<Account> self,\n                   ghs::DestructorFlags flags)\n{\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nnn::Result\nAccount_AddAccount(PersistentId persistentId)\n{\n   if (!IsInitialized()) {\n      return ResultLibraryNotInitialiased;\n   }\n\n   auto command = ClientCommand<PrivilegedService::AddAccount> { internal::getAllocator() };\n   command.setParameters(persistentId);\n\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return result;\n}\n\nvoid\nLibrary::registerAccountSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss7AccountFUi\",\n                              Account_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss7AccountFv\",\n                              Account_Destructor);\n\n   RegisterFunctionExportName(\"AddAccount__Q3_2nn4boss7AccountSFUi\",\n                              Account_AddAccount);\n\n   RegisterTypeInfo(\n      Account,\n      \"nn::boss::Account\",\n      {\n         \"__dt__Q3_2nn4boss7AccountFv\",\n      },\n      {});\n}\n\n}  // namespace namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_account.h",
    "content": "#pragma once\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n#include \"nn/boss/nn_boss_types.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_boss\n{\n\nusing PersistentId = nn::boss::PersistentId;\n\n#pragma pack(push, 1)\n\nstruct Account\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   be2_val<uint32_t> unk0x00;\n   be2_virt_ptr<ghs::VirtualTable> virtualTable;\n};\nCHECK_OFFSET(Account, 0x00, unk0x00);\nCHECK_OFFSET(Account, 0x04, virtualTable);\nCHECK_SIZE(Account, 0x8);\n\n#pragma pack(pop)\n\nvirt_ptr<Account>\nAccount_Constructor(virt_ptr<Account> self,\n                    uint32_t a1);\n\nvoid\nAccount_Destructor(virt_ptr<Account> self,\n                   ghs::DestructorFlags flags);\n\nnn::Result\nAccount_AddAccount(PersistentId persistentId);\n\n}  // namespace namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_almightystorage.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_almightystorage.h\"\n#include \"nn_boss_enum.h\"\n#include \"nn_boss_storage.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"nn/boss/nn_boss_result.h\"\n\n#include <common/strutils.h>\n\nusing namespace nn::boss;\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> AlmightyStorage::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> AlmightyStorage::TypeDescriptor = nullptr;\n\nvirt_ptr<AlmightyStorage>\nAlmightyStorage_Constructor(virt_ptr<AlmightyStorage> self)\n{\n   if (!self) {\n      self = virt_cast<AlmightyStorage *>(ghs::malloc(sizeof(AlmightyStorage)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   Storage_Constructor(virt_cast<Storage *>(self));\n   self->virtualTable = AlmightyStorage::VirtualTable;\n   return self;\n}\n\nvoid\nAlmightyStorage_Destructor(virt_ptr<AlmightyStorage> self,\n                           ghs::DestructorFlags flags)\n{\n   Storage_Destructor(virt_cast<Storage *>(self), ghs::DestructorFlags::None);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nnn::Result\nAlmightyStorage_Initialize(virt_ptr<AlmightyStorage> self,\n                           virt_ptr<TitleID> titleId,\n                           virt_ptr<const char> directory,\n                           nn::act::PersistentId persistentId,\n                           StorageKind storageKind)\n{\n   if (!directory) {\n      return ResultInvalidParameter;\n   }\n\n   self->titleID = *titleId;\n   self->persistentId = persistentId;\n   self->storageKind = storageKind;\n   self->directoryName = directory.get();\n   self->directoryName[7] = '\\0';\n   return ResultSuccess;\n}\n\nvoid\nLibrary::registerAlmightyStorageSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss15AlmightyStorageFv\",\n                             static_cast<virt_ptr<AlmightyStorage> (*)(virt_ptr<AlmightyStorage>)>(AlmightyStorage_Constructor));\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss15AlmightyStorageFv\",\n                              AlmightyStorage_Destructor);\n   RegisterFunctionExportName(\"Initialize__Q3_2nn4boss15AlmightyStorageFQ3_2nn4boss7TitleIDPCcUiQ3_2nn4boss11StorageKind\",\n                              AlmightyStorage_Initialize);\n\n   RegisterTypeInfo(\n      AlmightyStorage,\n      \"nn::boss::AlmightyStorage\",\n      {\n         \"__dt__Q3_2nn4boss15AlmightyStorageFv\",\n      },\n      {\n         \"nn::boss::Storage\",\n      });\n}\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_almightystorage.h",
    "content": "#pragma once\n#include \"nn_boss_storage.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\n/*\nUnimplemented functions:\nnn::boss::AlmightyStorage::GetReadFlagFromNsDatas(const nn::boss::DataName *, unsigned int, bool *) const\nnn::boss::AlmightyStorage::GetBossStorageDirectoryList(unsigned int, nn::boss::TitleID, nn::boss::StorageKind, nn::boss::DataName *, unsigned int, unsigned int *, unsigned int)\nnn::boss::AlmightyStorage::SetReadFlagToNsDatas(const nn::boss::DataName *, unsigned int, bool *)\nnn::boss::AlmightyStorage::SetReadFlagToNsDatas(const nn::boss::DataName *, unsigned int, bool)\n*/\n\nnamespace cafe::nn_boss\n{\n\nstruct AlmightyStorage : public Storage\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n};\nCHECK_SIZE(AlmightyStorage, 0x28);\n\nvirt_ptr<AlmightyStorage>\nAlmightyStorage_Constructor(virt_ptr<AlmightyStorage> self);\n\nvoid\nAlmightyStorage_Destructor(virt_ptr<AlmightyStorage> self,\n                           ghs::DestructorFlags flags);\n\nnn::Result\nAlmightyStorage_Initialize(virt_ptr<AlmightyStorage> self,\n                           virt_ptr<TitleID> titleId,\n                           virt_ptr<const char> directory,\n                           nn::act::PersistentId persistentId,\n                           StorageKind storageKind);\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_almightytask.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_almightytask.h\"\n#include \"nn_boss_privilegedtask.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"nn/boss/nn_boss_result.h\"\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> AlmightyTask::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> AlmightyTask::TypeDescriptor = nullptr;\n\nvirt_ptr<AlmightyTask>\nAlmightyTask_Constructor(virt_ptr<AlmightyTask> self)\n{\n   if (!self) {\n      self = virt_cast<AlmightyTask *>(ghs::malloc(sizeof(AlmightyTask)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   PrivilegedTask_Constructor(virt_cast<PrivilegedTask *>(self));\n   self->virtualTable = AlmightyTask::VirtualTable;\n   return self;\n}\n\nvoid\nAlmightyTask_Destructor(virt_ptr<AlmightyTask> self,\n                        ghs::DestructorFlags flags)\n{\n   if (!self) {\n      return;\n   }\n\n   self->virtualTable = AlmightyTask::VirtualTable;\n   PrivilegedTask_Destructor(virt_cast<PrivilegedTask *>(self),\n                             ghs::DestructorFlags::None);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nnn::Result\nAlmightyTask_Initialize(virt_ptr<AlmightyTask> self,\n                        virt_ptr<TitleID> titleId,\n                        virt_ptr<const char> taskId,\n                        uint32_t accountId)\n{\n   if (!taskId) {\n      return nn::boss::ResultInvalidParameter;\n   }\n\n   self->titleId = *titleId;\n   TaskID_OperatorAssign(virt_addrof(self->taskId), taskId);\n   self->accountId = accountId;\n   return nn::boss::ResultSuccess;\n}\n\nvoid\nLibrary::registerAlmightyTaskSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss12AlmightyTaskFv\",\n                              AlmightyTask_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss12AlmightyTaskFv\",\n                              AlmightyTask_Destructor);\n\n   RegisterFunctionExportName(\"Initialize__Q3_2nn4boss12AlmightyTaskFQ3_2nn4boss7TitleIDPCcUi\",\n                              AlmightyTask_Initialize);\n\n   RegisterTypeInfo(\n      AlmightyTask,\n      \"nn::boss::AlmightyTask\",\n      {\n         \"__dt__Q3_2nn4boss12AlmightyTaskFv\",\n      },\n      {});\n}\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_almightytask.h",
    "content": "#pragma once\n#include \"nn_boss_privilegedtask.h\"\n#include \"nn_boss_titleid.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\n/*\nUnimplemented functions:\nnn::boss::AlmightyTask::GetTaskRecord((nn::boss::TaskRecord *,uint *))\nnn::boss::AlmightyTask::SetHttpOption((ushort))\nnn::boss::AlmightyTask::SetPermission((uchar))\n*/\n\nnamespace cafe::nn_boss\n{\n\nstruct AlmightyTask : PrivilegedTask\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n};\nCHECK_SIZE(AlmightyTask, 0x20);\n\nvirt_ptr<AlmightyTask>\nAlmightyTask_Constructor(virt_ptr<AlmightyTask> self);\n\nvoid\nAlmightyTask_Destructor(virt_ptr<AlmightyTask> self,\n                        ghs::DestructorFlags flags);\n\nnn::Result\nAlmightyTask_Initialize(virt_ptr<AlmightyTask> self,\n                        virt_ptr<TitleID> titleId,\n                        virt_ptr<const char> taskId,\n                        uint32_t accountId);\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_client.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_client.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n\nusing namespace cafe::coreinit;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_boss\n{\n\nstruct StaticClientData\n{\n   StaticClientData()\n   {\n      OSInitMutex(virt_addrof(mutex));\n   }\n\n   alignas(256) be2_array<uint8_t, 0x10000> allocatorMemory;\n\n   be2_struct<OSMutex> mutex;\n   be2_val<uint32_t> refCount = 0u;\n   be2_struct<nn::ipc::Client> client;\n   be2_struct<nn::ipc::BufferAllocator> allocator;\n};\n\nstatic virt_ptr<StaticClientData> sClientData = nullptr;\n\nnn::Result\nInitialize()\n{\n   auto result = nn::ResultSuccess;\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount == 0) {\n      result = sClientData->client.initialise(make_stack_string(\"/dev/boss\"));\n      sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory),\n                                        sClientData->allocatorMemory.size());\n   }\n\n   sClientData->refCount++;\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n   return result;\n}\n\nvoid\nFinalize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount > 0) {\n      sClientData->refCount--;\n\n      if (sClientData->refCount == 0) {\n         sClientData->client.close();\n      }\n   }\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n}\n\nbool\nIsInitialized()\n{\n   return sClientData->client.isInitialised();\n}\n\nBossState\nGetBossState()\n{\n   decaf_warn_stub();\n   return BossState::Unknown0;\n}\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient()\n{\n   return virt_addrof(sClientData->client);\n}\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator()\n{\n   return virt_addrof(sClientData->allocator);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerClientSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn4bossFv\",\n                              Initialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn4bossFv\",\n                              Finalize);\n   RegisterFunctionExportName(\"IsInitialized__Q2_2nn4bossFv\",\n                              IsInitialized);\n   RegisterFunctionExportName(\"GetBossState__Q2_2nn4bossFv\",\n                              GetBossState);\n\n   RegisterDataInternal(sClientData);\n}\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_client.h",
    "content": "#pragma once\n#include \"nn_boss_enum.h\"\n\n#include \"cafe/nn/cafe_nn_ipc_bufferallocator.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/nn_result.h\"\n\nnamespace cafe::nn_boss\n{\n\nnn::Result\nInitialize();\n\nvoid\nFinalize();\n\nbool\nIsInitialized();\n\nBossState\nGetBossState();\n\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient();\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator();\n\n} // namespace internal\n\n}  // namespace namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_dataname.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_dataname.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"common/strutils.h\"\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<DataName>\nDataName_Constructor(virt_ptr<DataName> self)\n{\n   if (!self) {\n      self = virt_cast<DataName *>(ghs::malloc(sizeof(DataName)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->name.fill('\\0');\n   return self;\n}\n\nvirt_ptr<DataName>\nDataName_Constructor(virt_ptr<DataName> self,\n                     virt_ptr<const char> name)\n{\n   if (!self) {\n      self = virt_cast<DataName *>(ghs::malloc(sizeof(DataName)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   string_copy(virt_addrof(self->name).getRawPointer(),\n               name.getRawPointer(),\n               self->name.size());\n   self->name[31] = '\\0';\n   return self;\n}\n\nvirt_ptr<DataName>\nDataName_Constructor(virt_ptr<DataName> self,\n                     virt_ptr<const DataName> other)\n{\n   if (!self) {\n      self = virt_cast<DataName *>(ghs::malloc(sizeof(DataName)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   memcpy(self.get(), other.get(), sizeof(DataName));\n   return self;\n}\n\nvirt_ptr<const char>\nDataName_OperatorCastConstCharPtr(virt_ptr<const DataName> self)\n{\n   return virt_addrof(self->name);\n}\n\nvirt_ptr<DataName>\nDataName_OperatorAssign(virt_ptr<DataName> self,\n                        virt_ptr<const char> name)\n{\n   string_copy(virt_addrof(self->name).getRawPointer(),\n               name.getRawPointer(),\n               self->name.size());\n   self->name[31] = '\\0';\n   return self;\n}\n\nbool\nDataName_OperatorEqual(virt_ptr<const DataName> self,\n                       virt_ptr<const DataName> other)\n{\n   return std::strncmp(virt_addrof(self->name).getRawPointer(),\n                       virt_addrof(other->name).getRawPointer(),\n                       31) == 0;\n}\n\nbool\nDataName_OperatorEqual(virt_ptr<const DataName> self,\n                       virt_ptr<const char> name)\n{\n   return std::strncmp(virt_addrof(self->name).getRawPointer(),\n                       name.getRawPointer(),\n                       31) == 0;\n}\n\nbool\nDataName_OperatorNotEqual(virt_ptr<const DataName> self,\n                          virt_ptr<const DataName> other)\n{\n   return !DataName_OperatorEqual(self, other);\n}\n\nbool\nDataName_OperatorNotEqual(virt_ptr<const DataName> self,\n                          virt_ptr<const char> name)\n{\n   return !DataName_OperatorEqual(self, name);\n}\n\nvoid\nLibrary::registerDataNameSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss8DataNameFv\",\n                              static_cast<virt_ptr<DataName> (*)(virt_ptr<DataName>)>(DataName_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss8DataNameFPCc\",\n                              static_cast<virt_ptr<DataName> (*)(virt_ptr<DataName>, virt_ptr<const char>)>(DataName_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss8DataNameFRCQ3_2nn4boss8DataName\",\n                              static_cast<virt_ptr<DataName> (*)(virt_ptr<DataName>, virt_ptr<const DataName>)>(DataName_Constructor));\n\n   RegisterFunctionExportName(\"__opPCc__Q3_2nn4boss8DataNameCFv\",\n                              DataName_OperatorCastConstCharPtr);\n\n   RegisterFunctionExportName(\"__as__Q3_2nn4boss8DataNameFPCc\",\n                              DataName_OperatorAssign);\n\n   RegisterFunctionExportName(\"__eq__Q3_2nn4boss8DataNameCFRCQ3_2nn4boss8DataName\",\n                              static_cast<bool(*)(virt_ptr<const DataName>, virt_ptr<const DataName>)>(DataName_OperatorEqual));\n   RegisterFunctionExportName(\"__eq__Q3_2nn4boss8DataNameCFPCc\",\n                              static_cast<bool(*)(virt_ptr<const DataName>, virt_ptr<const char>)>(DataName_OperatorEqual));\n\n   RegisterFunctionExportName(\"__ne__Q3_2nn4boss8DataNameCFRCQ3_2nn4boss8DataName\",\n                              static_cast<bool(*)(virt_ptr<const DataName>, virt_ptr<const DataName>)>(DataName_OperatorNotEqual));\n   RegisterFunctionExportName(\"__ne__Q3_2nn4boss8DataNameCFPCc\",\n                              static_cast<bool(*)(virt_ptr<const DataName>, virt_ptr<const char>)>(DataName_OperatorNotEqual));\n}\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_dataname.h",
    "content": "#pragma once\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_boss\n{\n\nstruct DataName\n{\n   be2_array<char, 32> name;\n};\nCHECK_SIZE(DataName, 0x20);\n\nvirt_ptr<DataName>\nDataName_Constructor(virt_ptr<DataName> self);\n\nvirt_ptr<DataName>\nDataName_Constructor(virt_ptr<DataName> self,\n                     virt_ptr<const char> name);\n\nvirt_ptr<DataName>\nDataName_Constructor(virt_ptr<DataName> self,\n                     virt_ptr<const DataName> other);\n\nvirt_ptr<const char>\nDataName_OperatorCastConstCharPtr(virt_ptr<const DataName> self);\n\nvirt_ptr<DataName>\nDataName_OperatorAssign(virt_ptr<DataName> self,\n                        virt_ptr<const char> name);\n\nbool\nDataName_OperatorEqual(virt_ptr<const DataName> self,\n                       virt_ptr<const DataName> other);\n\nbool\nDataName_OperatorEqual(virt_ptr<const DataName> self,\n                       virt_ptr<const char> name);\n\nbool\nDataName_OperatorNotEqual(virt_ptr<const DataName> self,\n                          virt_ptr<const DataName> other);\n\nbool\nDataName_OperatorNotEqual(virt_ptr<const DataName> self,\n                          virt_ptr<const char> name);\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_enum.h",
    "content": "#ifndef CAFE_NN_BOSS_ENUM_H\n#define CAFE_NN_BOSS_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(nn_boss)\n\nENUM_BEG(BossState, uint32_t)\n   ENUM_VALUE(Unknown0,       0)\nENUM_END(BossState)\n\nENUM_BEG(StorageKind, uint32_t)\nENUM_END(StorageKind)\n\nENUM_NAMESPACE_EXIT(nn_boss)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_NN_BOSS_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_nbdltasksetting.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_nbdltasksetting.h\"\n#include \"nn_boss_nettasksetting.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"nn/boss/nn_boss_result.h\"\n\n#include <common/strutils.h>\n\nnamespace cafe::nn_boss\n{\n\nusing namespace ::nn::boss;\n\nvirt_ptr<ghs::VirtualTable> NbdlTaskSetting::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> NbdlTaskSetting::TypeDescriptor = nullptr;\n\nvirt_ptr<NbdlTaskSetting>\nNbdlTaskSetting_Constructor(virt_ptr<NbdlTaskSetting> self)\n{\n   if (!self) {\n      self = virt_cast<NbdlTaskSetting *>(ghs::malloc(sizeof(NbdlTaskSetting)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   NetTaskSetting_Constructor(virt_cast<NetTaskSetting *>(self));\n   self->virtualTable = NbdlTaskSetting::VirtualTable;\n   return self;\n}\n\nvoid\nNbdlTaskSetting_Destructor(virt_ptr<NbdlTaskSetting> self,\n                           ghs::DestructorFlags flags)\n{\n   NetTaskSetting_Destructor(virt_cast<NetTaskSetting *>(self),\n                             ghs::DestructorFlags::None);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nnn::Result\nNbdlTaskSetting_Initialize(virt_ptr<NbdlTaskSetting> self,\n                           virt_ptr<const char> bossCode,\n                           int64_t a3,\n                           virt_ptr<const char> directoryName)\n{\n   if (!bossCode || strnlen(bossCode.get(), 32) == 32) {\n      return ResultInvalidParameter;\n   }\n\n   if (directoryName && strnlen(directoryName.get(), 8) == 8) {\n      return ResultInvalidParameter;\n   }\n\n   // TODO: Add appropriate fields to TaskSetting structure\n   string_copy(reinterpret_cast<char *>(self.get()) + 0x7C0, 32, bossCode.get(), 32);\n   *(reinterpret_cast<char *>(self.get()) + 0x7C0 + 32 - 1) = 0;\n\n   if (directoryName) {\n      string_copy(reinterpret_cast<char *>(self.get()) + 0x7E0, 8, directoryName.get(), 8);\n      *(reinterpret_cast<char *>(self.get()) + 0x7E0 + 8 - 1) = 0;\n   }\n\n   *virt_cast<int64_t *>(virt_cast<char *>(self) + 0x7F0) = a3;\n   return ResultSuccess;\n}\n\nnn::Result\nNbdlTaskSetting_SetFileName(virt_ptr<NbdlTaskSetting> self,\n                            virt_ptr<const char> fileName)\n{\n   if (!fileName || strnlen(fileName.get(), 8) == 8) {\n      return ResultInvalidParameter;\n   }\n\n   // TODO: Add appropriate fields to TaskSetting structure\n   string_copy(reinterpret_cast<char *>(self.get()) + 0x7F8, 32, fileName.get(), 32);\n   *(reinterpret_cast<char *>(self.get()) + 0x7F8 + 32 - 1) = 0;\n   return ResultSuccess;\n}\n\nnn::Result\nPrivateNbdlTaskSetting_SetCountryCode(virt_ptr<NbdlTaskSetting> self,\n                                      virt_ptr<const char> countryCode)\n{\n   if (!countryCode || strnlen(countryCode.get(), 3) != 2) {\n      return ResultInvalidParameter;\n   }\n\n   // TODO: Add appropriate fields to TaskSetting structure\n   string_copy(reinterpret_cast<char *>(self.get()) + 0x81C, 3, countryCode.get(), 3);\n   *(reinterpret_cast<char *>(self.get()) + 0x81C + 3 - 1) = 0;\n   return ResultSuccess;\n}\n\nnn::Result\nPrivateNbdlTaskSetting_SetLanguageCode(virt_ptr<NbdlTaskSetting> self,\n                                       virt_ptr<const char> languageCode)\n{\n   if (!languageCode || strnlen(languageCode.get(), 3) != 2) {\n      return ResultInvalidParameter;\n   }\n\n   // TODO: Add appropriate fields to TaskSetting structure\n   string_copy(reinterpret_cast<char *>(self.get()) + 0x818, 3, languageCode.get(), 3);\n   *(reinterpret_cast<char *>(self.get()) + 0x818 + 3 - 1) = 0;\n   return ResultSuccess;\n}\n\nnn::Result\nPrivateNbdlTaskSetting_SetOption(virt_ptr<NbdlTaskSetting> self,\n                                 uint8_t option)\n{\n   // TODO: Add appropriate fields to TaskSetting structure\n   *(virt_cast<uint8_t *>(self) + 0x81F) = option;\n   return ResultSuccess;\n}\n\nvoid\nLibrary::registerNbdlTaskSettingSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss15NbdlTaskSettingFv\",\n                              NbdlTaskSetting_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss15NbdlTaskSettingFv\",\n                              NbdlTaskSetting_Destructor);\n   RegisterFunctionExportName(\"Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1\",\n                              NbdlTaskSetting_Initialize);\n   RegisterFunctionExportName(\"SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc\",\n                              NbdlTaskSetting_SetFileName);\n\n   RegisterFunctionExportName(\n      \"SetCountryCodeA2__Q3_2nn4boss22PrivateNbdlTaskSettingSFRQ3_2nn4boss15NbdlTaskSettingPCc\",\n      PrivateNbdlTaskSetting_SetCountryCode);\n   RegisterFunctionExportName(\n      \"SetLanguageCodeA2__Q3_2nn4boss22PrivateNbdlTaskSettingSFRQ3_2nn4boss15NbdlTaskSettingPCc\",\n      PrivateNbdlTaskSetting_SetLanguageCode);\n   RegisterFunctionExportName(\n      \"SetOption__Q3_2nn4boss22PrivateNbdlTaskSettingSFRQ3_2nn4boss15NbdlTaskSettingUc\",\n      PrivateNbdlTaskSetting_SetOption);\n\n   RegisterTypeInfo(\n      NbdlTaskSetting,\n      \"nn::boss::NbdlTaskSetting\",\n      {\n         \"__dt__Q3_2nn4boss15NbdlTaskSettingFv\",\n         \"RegisterPreprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCc\",\n         \"RegisterPostprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result\",\n      },\n      {\n         \"nn::boss::NetTaskSetting\",\n      });\n}\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_nbdltasksetting.h",
    "content": "#pragma once\n#include \"nn_boss_nettasksetting.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_boss\n{\n\nstruct NbdlTaskSetting : NetTaskSetting\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n};\nCHECK_SIZE(NbdlTaskSetting, 0x1004);\n\nvirt_ptr<NbdlTaskSetting>\nNbdlTaskSetting_Constructor(virt_ptr<NbdlTaskSetting> self);\n\nvoid\nNbdlTaskSetting_Destructor(virt_ptr<NbdlTaskSetting> self,\n                           ghs::DestructorFlags flags);\n\nnn::Result\nNbdlTaskSetting_Initialize(virt_ptr<NbdlTaskSetting> self,\n                           virt_ptr<const char> bossCode,\n                           int64_t a3,\n                           virt_ptr<const char> directoryName);\n\nnn::Result\nNbdlTaskSetting_SetFileName(virt_ptr<NbdlTaskSetting> self,\n                            virt_ptr<const char> fileName);\n\nnn::Result\nPrivateNbdlTaskSetting_SetCountryCode(virt_ptr<NbdlTaskSetting> self,\n                                      virt_ptr<const char> countryCode);\n\nnn::Result\nPrivateNbdlTaskSetting_SetLanguageCode(virt_ptr<NbdlTaskSetting> self,\n                                       virt_ptr<const char> languageCode);\n\nnn::Result\nPrivateNbdlTaskSetting_SetOption(virt_ptr<NbdlTaskSetting> self,\n                                 uint8_t option);\n\n} // namespace cafe::nn_boss"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_nettasksetting.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_nettasksetting.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> NetTaskSetting::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> NetTaskSetting::TypeDescriptor = nullptr;\n\nvirt_ptr<NetTaskSetting>\nNetTaskSetting_Constructor(virt_ptr<NetTaskSetting> self)\n{\n   if (!self) {\n      self = virt_cast<NetTaskSetting *>(ghs::malloc(sizeof(NetTaskSetting)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   TaskSetting_Constructor(virt_cast<TaskSetting *>(self));\n   self->virtualTable = NetTaskSetting::VirtualTable;\n   self->unk0x18C = 120u;\n   return self;\n}\n\nvoid\nNetTaskSetting_Destructor(virt_ptr<NetTaskSetting> self,\n                          ghs::DestructorFlags flags)\n{\n   TaskSetting_Destructor(virt_cast<TaskSetting *>(self),\n                          ghs::DestructorFlags::None);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nvoid\nLibrary::registerNetTaskSettingSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss14NetTaskSettingFv\",\n                              NetTaskSetting_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss14NetTaskSettingFv\",\n                              NetTaskSetting_Destructor);\n\n   RegisterTypeInfo(\n      NetTaskSetting,\n      \"nn::boss::NetTaskSetting\",\n      {\n         \"__dt__Q3_2nn4boss14NetTaskSettingFv\",\n         \"RegisterPreprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCc\",\n         \"RegisterPostprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result\",\n      },\n      {\n         \"nn::boss::TaskSetting\",\n      });\n}\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_nettasksetting.h",
    "content": "#pragma once\n#include \"nn_boss_tasksetting.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\n/*\nUnimplemented functions:\nnn::boss::NetTaskSetting::AddCaCert(char const *)\nnn::boss::NetTaskSetting::AddHttpHeader(char const *, char const *)\nnn::boss::NetTaskSetting::AddHttpQueryString(char const *, nn::boss::QueryKind)\nnn::boss::NetTaskSetting::AddInternalCaCert(signed char)\nnn::boss::NetTaskSetting::ClearCaCertSetting(void)\nnn::boss::NetTaskSetting::ClearClientCertSetting(void)\nnn::boss::NetTaskSetting::ClearHttpHeaders(void)\nnn::boss::NetTaskSetting::ClearHttpQueryStrings(void)\nnn::boss::NetTaskSetting::SetClientCert(char const *, char const *)\nnn::boss::NetTaskSetting::SetConnectionSetting(nn::boss::HttpProtocol, char const *, unsigned short)\nnn::boss::NetTaskSetting::SetFirstLastModifiedTime(char const *)\nnn::boss::NetTaskSetting::SetHttpOption(unsigned short)\nnn::boss::NetTaskSetting::SetHttpTimeout(unsigned int)\nnn::boss::NetTaskSetting::SetInternalClientCert(signed char)\nnn::boss::NetTaskSetting::SetServiceToken(unsigned char const *)\n*/\n\nnamespace cafe::nn_boss\n{\n\nstruct NetTaskSetting : public TaskSetting\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n};\nCHECK_SIZE(NetTaskSetting, 0x1004);\n\nvirt_ptr<NetTaskSetting>\nNetTaskSetting_Constructor(virt_ptr<NetTaskSetting> self);\n\nvoid\nNetTaskSetting_Destructor(virt_ptr<NetTaskSetting> self,\n                          ghs::DestructorFlags flags);\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_playloguploadtasksetting.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_playloguploadtasksetting.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"nn/boss/nn_boss_result.h\"\n\nusing namespace nn::boss;\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> PlayLogUploadTaskSetting::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> PlayLogUploadTaskSetting::TypeDescriptor = nullptr;\n\nvirt_ptr<PlayLogUploadTaskSetting>\nPlayLogUploadTaskSetting_Constructor(virt_ptr<PlayLogUploadTaskSetting> self)\n{\n   if (!self) {\n      self = virt_cast<PlayLogUploadTaskSetting *>(ghs::malloc(sizeof(PlayLogUploadTaskSetting)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   RawUlTaskSetting_Constructor(virt_cast<RawUlTaskSetting *>(self));\n   self->virtualTable = PlayLogUploadTaskSetting::VirtualTable;\n   return self;\n}\n\nvoid\nPlayLogUploadTaskSetting_Destructor(virt_ptr<PlayLogUploadTaskSetting> self,\n                                    ghs::DestructorFlags flags)\n{\n   RawUlTaskSetting_Destructor(virt_cast<RawUlTaskSetting *>(self),\n                               ghs::DestructorFlags::None);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nnn::Result\nPlayLogUploadTaskSetting_Initialize(virt_ptr<PlayLogUploadTaskSetting> self)\n{\n   PlayLogUploadTaskSetting_SetPlayLogUploadTaskSettingToRecord(self);\n   return ResultSuccess;\n}\n\nnn::Result\nPlayLogUploadTaskSetting_RegisterPreprocess(virt_ptr<PlayLogUploadTaskSetting> self,\n                                            uint32_t a1,\n                                            virt_ptr<TitleID> a2,\n                                            virt_ptr<const char> a3)\n{\n   return ResultSuccess;\n}\n\nvoid\nPlayLogUploadTaskSetting_SetPlayLogUploadTaskSettingToRecord(virt_ptr<PlayLogUploadTaskSetting> self)\n{\n   self->unk0x28 = uint16_t { 5 };\n   self->permission |= 0x1A;\n}\n\nvoid\nLibrary::registerPlayLogUploadTaskSettingSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss24PlayLogUploadTaskSettingFv\",\n                              PlayLogUploadTaskSetting_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss24PlayLogUploadTaskSettingFv\",\n                              PlayLogUploadTaskSetting_Destructor);\n\n   RegisterFunctionExportName(\"Initialize__Q3_2nn4boss24PlayLogUploadTaskSettingFv\",\n                              PlayLogUploadTaskSetting_Initialize);\n   RegisterFunctionExportName(\"RegisterPreprocess__Q3_2nn4boss24PlayLogUploadTaskSettingFUiQ3_2nn4boss7TitleIDPCc\",\n                              PlayLogUploadTaskSetting_RegisterPreprocess);\n   RegisterFunctionExportName(\"SetPlayLogUploadTaskSettingToRecord__Q3_2nn4boss24PlayLogUploadTaskSettingFv\",\n                              PlayLogUploadTaskSetting_SetPlayLogUploadTaskSettingToRecord);\n\n   RegisterTypeInfo(\n      PlayLogUploadTaskSetting,\n      \"nn::boss::PlayLogUploadTaskSetting\",\n      {\n         \"__dt__Q3_2nn4boss24PlayLogUploadTaskSettingFv\",\n         \"RegisterPreprocess__Q3_2nn4boss24PlayLogUploadTaskSettingFUiQ3_2nn4boss7TitleIDPCc\",\n         \"RegisterPostprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result\",\n      },\n      {\n         \"nn::boss::PlayLogUploadTaskSetting\",\n      });\n}\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_playloguploadtasksetting.h",
    "content": "#pragma once\n#include \"nn_boss_rawultasksetting.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_boss\n{\n\nstruct PlayLogUploadTaskSetting : public RawUlTaskSetting\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n};\nCHECK_SIZE(PlayLogUploadTaskSetting, 0x1210);\n\nvirt_ptr<PlayLogUploadTaskSetting>\nPlayLogUploadTaskSetting_Constructor(virt_ptr<PlayLogUploadTaskSetting> self);\n\nvoid\nPlayLogUploadTaskSetting_Destructor(virt_ptr<PlayLogUploadTaskSetting> self,\n                                    ghs::DestructorFlags flags);\n\nnn::Result\nPlayLogUploadTaskSetting_Initialize(virt_ptr<PlayLogUploadTaskSetting> self);\n\nnn::Result\nPlayLogUploadTaskSetting_RegisterPreprocess(virt_ptr<PlayLogUploadTaskSetting> self,\n                                            uint32_t a1,\n                                            virt_ptr<TitleID> a2,\n                                            virt_ptr<const char> a3);\n\nvoid\nPlayLogUploadTaskSetting_SetPlayLogUploadTaskSettingToRecord(virt_ptr<PlayLogUploadTaskSetting> self);\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_playreportsetting.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_playreportsetting.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"nn/boss/nn_boss_result.h\"\n\nusing namespace nn::boss;\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> PlayReportSetting::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> PlayReportSetting::TypeDescriptor = nullptr;\n\nvirt_ptr<PlayReportSetting>\nPlayReportSetting_Constructor(virt_ptr<PlayReportSetting> self)\n{\n   if (!self) {\n      self = virt_cast<PlayReportSetting *>(ghs::malloc(sizeof(PlayReportSetting)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   RawUlTaskSetting_Constructor(virt_cast<RawUlTaskSetting *>(self));\n   self->virtualTable = PlayReportSetting::VirtualTable;\n   self->playReportUnk1 = 0u;\n   self->playReportUnk2 = 0u;\n   self->playReportUnk3 = 0u;\n   self->playReportUnk4 = 0u;\n   return self;\n}\n\nvoid\nPlayReportSetting_Destructor(virt_ptr<PlayReportSetting> self,\n                             ghs::DestructorFlags flags)\n{\n   RawUlTaskSetting_Destructor(virt_cast<RawUlTaskSetting *>(self),\n                               ghs::DestructorFlags::None);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nvoid\nPlayReportSetting_Initialize(virt_ptr<PlayReportSetting> self,\n                             virt_ptr<void> a1,\n                             uint32_t a2)\n{\n   decaf_warn_stub();\n}\n\nnn::Result\nPlayReportSetting_RegisterPreprocess(virt_ptr<PlayReportSetting> self,\n                                     uint32_t a1,\n                                     virt_ptr<TitleID> a2,\n                                     virt_ptr<const char> a3)\n{\n   decaf_warn_stub();\n   return ResultInvalidParameter;\n}\n\nbool\nPlayReportSetting_Set(virt_ptr<PlayReportSetting> self,\n                      virt_ptr<const char> key,\n                      uint32_t value)\n{\n   decaf_warn_stub();\n   return true;\n}\n\nbool\nPlayReportSetting_Set(virt_ptr<PlayReportSetting> self,\n                      uint32_t key,\n                      uint32_t value)\n{\n   decaf_warn_stub();\n   return true;\n}\n\nvoid\nLibrary::registerPlayReportSettingSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss17PlayReportSettingFv\",\n                              PlayReportSetting_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss17PlayReportSettingFv\",\n                              PlayReportSetting_Destructor);\n\n   RegisterFunctionExportName(\"RegisterPreprocess__Q3_2nn4boss17PlayReportSettingFUiQ3_2nn4boss7TitleIDPCc\",\n                              PlayReportSetting_RegisterPreprocess);\n   RegisterFunctionExportName(\"Initialize__Q3_2nn4boss17PlayReportSettingFPvUi\",\n                              PlayReportSetting_Initialize);\n   RegisterFunctionExportName(\"Set__Q3_2nn4boss17PlayReportSettingFPCcUi\",\n                              static_cast<bool(*)(virt_ptr<PlayReportSetting>, virt_ptr<const char>, uint32_t)>(PlayReportSetting_Set));\n   RegisterFunctionExportName(\"Set__Q3_2nn4boss17PlayReportSettingFUiT1\",\n                              static_cast<bool(*)(virt_ptr<PlayReportSetting>, uint32_t, uint32_t)>(PlayReportSetting_Set));\n\n   RegisterTypeInfo(\n      PlayReportSetting,\n      \"nn::boss::PlayReportSetting\",\n      {\n         \"__dt__Q3_2nn4boss17PlayReportSettingFv\",\n         \"RegisterPreprocess__Q3_2nn4boss17PlayReportSettingFUiQ3_2nn4boss7TitleIDPCc\",\n         \"RegisterPostprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result\",\n      },\n      {\n         \"nn::boss::RawUlTaskSetting\",\n      });\n}\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_playreportsetting.h",
    "content": "#pragma once\n#include \"nn_boss_rawultasksetting.h\"\n\n#include \"cafe/libraries/cafe_hle_library_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\n/*\nUnimplemented functions:\nnn::boss::PlayReportSetting::FindValueHead(char const *) const\nnn::boss::PlayReportSetting::GetValue(char const *, unsigned int *) const\nnn::boss::PlayReportSetting::GetValue(unsigned int, unsigned int *) const\nnn::boss::PlayReportSetting::RegisterPreprocess(unsigned int, nn::boss::TitleID, char const *)\n*/\n\nnamespace cafe::nn_boss\n{\n\nstruct PlayReportSetting : public RawUlTaskSetting\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   be2_val<uint32_t> playReportUnk1;\n   be2_val<uint32_t> playReportUnk2;\n   be2_val<uint32_t> playReportUnk3;\n   be2_val<uint32_t> playReportUnk4;\n};\nCHECK_OFFSET(PlayReportSetting, 0x1210, playReportUnk1);\nCHECK_OFFSET(PlayReportSetting, 0x1214, playReportUnk2);\nCHECK_OFFSET(PlayReportSetting, 0x1218, playReportUnk3);\nCHECK_OFFSET(PlayReportSetting, 0x121C, playReportUnk4);\nCHECK_SIZE(PlayReportSetting, 0x1220);\n\nvirt_ptr<PlayReportSetting>\nPlayReportSetting_Constructor(virt_ptr<PlayReportSetting> self);\n\nvoid\nPlayReportSetting_Destructor(virt_ptr<PlayReportSetting> self,\n                             ghs::DestructorFlags flags);\n\nvoid\nPlayReportSetting_Initialize(virt_ptr<PlayReportSetting> self,\n                             virt_ptr<void> a1,\n                             uint32_t a2);\n\nnn::Result\nPlayReportSetting_RegisterPreprocess(virt_ptr<PlayReportSetting> self,\n                                     uint32_t a1,\n                                     virt_ptr<TitleID> a2,\n                                     virt_ptr<const char> a3);\n\nbool\nPlayReportSetting_Set(virt_ptr<PlayReportSetting> self,\n                      virt_ptr<const char> key,\n                      uint32_t value);\n\nbool\nPlayReportSetting_Set(virt_ptr<PlayReportSetting> self,\n                      uint32_t key,\n                      uint32_t value);\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_privilegedtask.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_privilegedtask.h\"\n#include \"nn_boss_task.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> PrivilegedTask::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> PrivilegedTask::TypeDescriptor = nullptr;\n\nvirt_ptr<PrivilegedTask>\nPrivilegedTask_Constructor(virt_ptr<PrivilegedTask> self)\n{\n   if (!self) {\n      self = virt_cast<PrivilegedTask *>(ghs::malloc(sizeof(PrivilegedTask)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   Task_Constructor(virt_cast<Task *>(self));\n   self->virtualTable = PrivilegedTask::VirtualTable;\n   return self;\n}\n\nvirt_ptr<PrivilegedTask>\nPrivilegedTask_Constructor(virt_ptr<PrivilegedTask> self,\n                           virt_ptr<const char> taskId,\n                           uint32_t accountId)\n{\n   if (!self) {\n      self = virt_cast<PrivilegedTask *>(ghs::malloc(sizeof(PrivilegedTask)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   Task_Constructor(virt_cast<Task *>(self), taskId, accountId);\n   self->virtualTable = PrivilegedTask::VirtualTable;\n   return self;\n}\n\nvoid\nPrivilegedTask_Destructor(virt_ptr<PrivilegedTask> self,\n                          ghs::DestructorFlags flags)\n{\n   if (!self) {\n      return;\n   }\n\n   self->virtualTable = PrivilegedTask::VirtualTable;\n   Task_Destructor(virt_cast<Task *>(self), ghs::DestructorFlags::None);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nvoid\nLibrary::registerPrivilegedTaskSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss14PrivilegedTaskFv\",\n                              static_cast<virt_ptr<PrivilegedTask> (*)(virt_ptr<PrivilegedTask>)>(PrivilegedTask_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss14PrivilegedTaskFPCcUi\",\n                              static_cast<virt_ptr<PrivilegedTask>(*)(virt_ptr<PrivilegedTask>, virt_ptr<const char>, uint32_t)>(PrivilegedTask_Constructor));\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss14PrivilegedTaskFv\",\n                              PrivilegedTask_Destructor);\n\n   RegisterTypeInfo(\n      PrivilegedTask,\n      \"nn::boss::PrivilegedTask\",\n      {\n         \"__dt__Q3_2nn4boss14PrivilegedTaskFv\",\n      },\n      {});\n}\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_privilegedtask.h",
    "content": "#pragma once\n#include \"nn_boss_task.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\n/*\nUnimplemented functions:\nnn::boss::PrivilegedTask::GetAction(const(void))\nnn::boss::PrivilegedTask::SetHttpOption((ushort))\nnn::boss::PrivilegedTask::SetPermission((uchar))\nnn::boss::PrivilegedTask::SetUserAgentMode((nn::boss::UserAgentMode))\n*/\n\nnamespace cafe::nn_boss\n{\n\nstruct PrivilegedTask : Task\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n};\nCHECK_SIZE(PrivilegedTask, 0x20);\n\nvirt_ptr<PrivilegedTask>\nPrivilegedTask_Constructor(virt_ptr<PrivilegedTask> self);\n\nvirt_ptr<PrivilegedTask>\nPrivilegedTask_Constructor(virt_ptr<PrivilegedTask> self,\n                           virt_ptr<const char> taskId,\n                           uint32_t accountId);\nvoid\nPrivilegedTask_Destructor(virt_ptr<PrivilegedTask> self,\n                          ghs::DestructorFlags flags);\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_rawultasksetting.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_rawultasksetting.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"nn/boss/nn_boss_result.h\"\n\nusing namespace nn::boss;\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> RawUlTaskSetting::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> RawUlTaskSetting::TypeDescriptor = nullptr;\n\nvirt_ptr<RawUlTaskSetting>\nRawUlTaskSetting_Constructor(virt_ptr<RawUlTaskSetting> self)\n{\n   if (!self) {\n      self = virt_cast<RawUlTaskSetting *>(ghs::malloc(sizeof(RawUlTaskSetting)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   NetTaskSetting_Constructor(virt_cast<NetTaskSetting *>(self));\n   self->virtualTable = RawUlTaskSetting::VirtualTable;\n   self->rawUlUnk1 = 0u;\n   self->rawUlUnk2 = 0u;\n   self->rawUlUnk3 = 0u;\n   std::memset(virt_addrof(self->rawUlData).get(), 0, self->rawUlData.size());\n   return self;\n}\n\nvoid\nRawUlTaskSetting_Destructor(virt_ptr<RawUlTaskSetting> self,\n                            ghs::DestructorFlags flags)\n{\n   NetTaskSetting_Destructor(virt_cast<NetTaskSetting *>(self),\n                             ghs::DestructorFlags::None);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nnn::Result\nRawUlTaskSetting_RegisterPreprocess(virt_ptr<RawUlTaskSetting> self,\n                                    uint32_t a1,\n                                    virt_ptr<TitleID> a2,\n                                    virt_ptr<const char> a3)\n{\n   decaf_warn_stub();\n   return ResultInvalidParameter;\n}\n\nvoid\nRawUlTaskSetting_RegisterPostprocess(virt_ptr<RawUlTaskSetting> self,\n                                     uint32_t a1,\n                                     virt_ptr<TitleID> a2,\n                                     virt_ptr<const char> a3,\n                                     virt_ptr<nn::Result> a4)\n{\n   decaf_warn_stub();\n}\n\nvoid\nLibrary::registerRawUlTaskSettingSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss16RawUlTaskSettingFv\",\n                              RawUlTaskSetting_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss16RawUlTaskSettingFv\",\n                              RawUlTaskSetting_Destructor);\n\n   RegisterFunctionExportName(\"RegisterPreprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCc\",\n                              RawUlTaskSetting_RegisterPreprocess);\n   RegisterFunctionExportName(\"RegisterPostprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result\",\n                              RawUlTaskSetting_RegisterPostprocess);\n\n   RegisterTypeInfo(\n      RawUlTaskSetting,\n      \"nn::boss::RawUlTaskSetting\",\n      {\n         \"__dt__Q3_2nn4boss16RawUlTaskSettingFv\",\n         \"RegisterPreprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCc\",\n         \"RegisterPostprocess__Q3_2nn4boss16RawUlTaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result\",\n      },\n      {\n         \"nn::boss::NetTaskSetting\",\n      });\n}\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_rawultasksetting.h",
    "content": "#pragma once\n#include \"nn_boss_nettasksetting.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\n/*\nUnimplemented functions:\nnn::boss::RawUlTaskSetting::AddLargeHttpHeader(char const *, char const *)\nnn::boss::RawUlTaskSetting::ClearLargeHttpHeader(void)\nnn::boss::RawUlTaskSetting::CopyUploadFileToBossStorage(unsigned int, nn::boss::TitleID, char const *) const\nnn::boss::RawUlTaskSetting::Initialize(char const *, unsigned char const *, long long)\nnn::boss::RawUlTaskSetting::Initialize(char const *, char const *)\nnn::boss::RawUlTaskSetting::Initialize(char const *, char const *, unsigned char *, long long)\nnn::boss::RawUlTaskSetting::RegisterPreprocess(unsigned int, nn::boss::TitleID, char const *)\nnn::boss::RawUlTaskSetting::SetOption(unsigned int)\nnn::boss::RawUlTaskSetting::SetRawUlTaskSettingToRecord(char const *)\n*/\n\nnamespace cafe::nn_boss\n{\n\nstruct RawUlTaskSetting : public NetTaskSetting\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   be2_val<uint32_t> rawUlUnk1;\n   be2_val<uint32_t> rawUlUnk2;\n   be2_val<uint32_t> rawUlUnk3;\n   be2_array<char, 0x200> rawUlData;\n};\nCHECK_SIZE(RawUlTaskSetting, 0x1210);\n\nvirt_ptr<RawUlTaskSetting>\nRawUlTaskSetting_Constructor(virt_ptr<RawUlTaskSetting> self);\n\nvoid\nRawUlTaskSetting_Destructor(virt_ptr<RawUlTaskSetting> self,\n                            ghs::DestructorFlags flags);\n\nnn::Result\nRawUlTaskSetting_RegisterPreprocess(virt_ptr<RawUlTaskSetting> self,\n                                    uint32_t a1,\n                                    virt_ptr<TitleID> a2,\n                                    virt_ptr<const char> a3);\n\nvoid\nRawUlTaskSetting_RegisterPostprocess(virt_ptr<RawUlTaskSetting> self,\n                                     uint32_t a1,\n                                     virt_ptr<TitleID> a2,\n                                     virt_ptr<const char> a3,\n                                     virt_ptr<nn::Result> a4);\n\n}  // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_storage.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_storage.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"nn/boss/nn_boss_result.h\"\n\nusing namespace nn::boss;\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> Storage::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> Storage::TypeDescriptor = nullptr;\n\nvirt_ptr<Storage>\nStorage_Constructor(virt_ptr<Storage> self)\n{\n   if (!self) {\n      self = virt_cast<Storage *>(ghs::malloc(sizeof(Storage)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->virtualTable = Storage::VirtualTable;\n   TitleID_Constructor(virt_addrof(self->titleID), 0ull);\n   return self;\n}\n\nvoid\nStorage_Destructor(virt_ptr<Storage> self,\n                   ghs::DestructorFlags flags)\n{\n   self->virtualTable = Storage::VirtualTable;\n   // TODO: Call Storage_Finalize(self);\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nvoid\nLibrary::registerStorageSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss7StorageFv\",\n                             static_cast<virt_ptr<Storage> (*)(virt_ptr<Storage>)>(Storage_Constructor));\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss7StorageFv\",\n                              Storage_Destructor);\n\n   RegisterTypeInfo(\n      Storage,\n      \"nn::boss::Storage\",\n      {\n         \"__dt__Q3_2nn4boss7StorageFv\",\n      },\n      {});\n}\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_storage.h",
    "content": "#pragma once\n#include \"nn_boss_enum.h\"\n#include \"nn_boss_titleid.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/act/nn_act_types.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\n/*\nUnimplemented functions:\nnn::boss::Storage::Storage(const char *, nn::boss::StorageKind)\nnn::boss::Storage::Storage(const char *, unsigned int, nn::boss::StorageKind)\nnn::boss::Storage::Storage(const nn::boss::Storage &)\nnn::boss::Storage::Storage(unsigned char, const char *, nn::boss::StorageKind)\nnn::boss::Storage::ClearAllHistories()\nnn::boss::Storage::ClearHistory()\nnn::boss::Storage::Exist() const\nnn::boss::Storage::Finalize()\nnn::boss::Storage::GetDataList(nn::boss::DataName *, unsigned int, unsigned int *, unsigned int) const\nnn::boss::Storage::GetReadFlagFromNsDatas(const nn::boss::DataName *, unsigned int, bool *) const\nnn::boss::Storage::GetUnreadDataList(nn::boss::DataName *, unsigned int, unsigned int *, unsigned int) const\nnn::boss::Storage::Initialize(const char *, nn::boss::StorageKind)\nnn::boss::Storage::Initialize(const char *, unsigned int, nn::boss::StorageKind)\nnn::boss::Storage::Initialize(unsigned char, const char *, nn::boss::StorageKind)\nnn::boss::Storage::SetReadFlagToNsDatas(const nn::boss::DataName *, unsigned int, bool *)\nnn::boss::Storage::SetReadFlagToNsDatas(const nn::boss::DataName *, unsigned int, bool)\n*/\n\nnamespace cafe::nn_boss\n{\n\nstruct Storage\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   be2_val<nn::act::PersistentId> persistentId;\n   be2_val<StorageKind> storageKind;\n   UNKNOWN(3);\n   be2_array<char, 8> directoryName;\n   UNKNOWN(5);\n   be2_val<TitleID> titleID;\n   be2_virt_ptr<ghs::VirtualTable> virtualTable;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(Storage, 0x00, persistentId);\nCHECK_OFFSET(Storage, 0x04, storageKind);\nCHECK_OFFSET(Storage, 0x0B, directoryName);\nCHECK_OFFSET(Storage, 0x18, titleID);\nCHECK_OFFSET(Storage, 0x20, virtualTable);\nCHECK_SIZE(Storage, 0x28);\n\nvirt_ptr<Storage>\nStorage_Constructor(virt_ptr<Storage> self);\n\nvoid\nStorage_Destructor(virt_ptr<Storage> self,\n                   ghs::DestructorFlags flags);\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_task.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_task.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"cafe/libraries/nn_act/nn_act_clientstandardservice.h\"\n#include \"nn/boss/nn_boss_result.h\"\n\nusing namespace nn::boss;\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> Task::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> Task::TypeDescriptor = nullptr;\n\nvirt_ptr<Task>\nTask_Constructor(virt_ptr<Task> self)\n{\n   if (!self) {\n      self = virt_cast<Task *>(ghs::malloc(sizeof(Task)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->virtualTable = Task::VirtualTable;\n   TaskID_Constructor(virt_addrof(self->taskId));\n   TitleID_Constructor(virt_addrof(self->titleId));\n   std::memset(virt_addrof(self->taskId).get(), 0, sizeof(TaskID));\n   return self;\n}\n\nvirt_ptr<Task>\nTask_Constructor(virt_ptr<Task> self,\n                 virt_ptr<const char> taskId)\n{\n   self = Task_Constructor(self);\n   if (self) {\n      Task_Initialize(self, taskId);\n   }\n\n   return self;\n}\n\nvirt_ptr<Task>\nTask_Constructor(virt_ptr<Task> self,\n                 virt_ptr<const char> taskId,\n                 uint32_t accountId)\n{\n   self = Task_Constructor(self);\n   if (self) {\n      Task_Initialize(self, taskId, accountId);\n   }\n\n   return self;\n}\n\nvirt_ptr<Task>\nTask_Constructor(virt_ptr<Task> self,\n                 uint8_t slot,\n                 virt_ptr<const char> taskId)\n{\n   self = Task_Constructor(self);\n   if (self) {\n      Task_Initialize(self, slot, taskId);\n   }\n\n   return self;\n}\n\nvoid\nTask_Destructor(virt_ptr<Task> self,\n                ghs::DestructorFlags flags)\n{\n   if (!self) {\n      return;\n   }\n\n   self->virtualTable = Task::VirtualTable;\n   Task_Finalize(self);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nnn::Result\nTask_Initialize(virt_ptr<Task> self,\n                virt_ptr<const char> taskId)\n{\n   return Task_Initialize(self, taskId, 0u);\n}\n\nnn::Result\nTask_Initialize(virt_ptr<Task> self,\n                virt_ptr<const char> taskId,\n                uint32_t accountId)\n{\n   if (!taskId || strnlen(taskId.get(), 8) == 8) {\n      return ResultInvalidParameter;\n   }\n\n   self->accountId = accountId;\n   TaskID_OperatorAssign(virt_addrof(self->taskId), taskId);\n   return ResultSuccess;\n}\n\nnn::Result\nTask_Initialize(virt_ptr<Task> self,\n                uint8_t slot,\n                virt_ptr<const char> taskId)\n{\n   if (!slot) {\n      return Task_Initialize(self, taskId, 0u);\n   } else if (auto accountId = nn_act::GetPersistentIdEx(slot)) {\n      return Task_Initialize(self, taskId, accountId);\n   }\n\n   return ResultInvalidParameter;\n}\n\nvoid\nTask_Finalize(virt_ptr<Task> self)\n{\n   decaf_warn_stub();\n   TitleID_Constructor(virt_addrof(self->titleId));\n}\n\nbool\nTask_IsRegistered(virt_ptr<Task> self)\n{\n   decaf_warn_stub();\n   return false;\n}\n\nnn::Result\nTask_Register(virt_ptr<Task> self,\n              virt_ptr<TaskSetting> taskSetting)\n{\n   decaf_warn_stub();\n   return ResultSuccess;\n}\n\nuint32_t\nTask_GetAccountID(virt_ptr<Task> self)\n{\n   return self->accountId;\n}\n\nvoid\nTask_GetTaskID(virt_ptr<Task> self,\n               virt_ptr<TaskID> id)\n{\n   *id = self->taskId;\n}\n\nvoid\nTask_GetTitleID(virt_ptr<Task> self,\n                virt_ptr<TitleID> id)\n{\n   *id = self->titleId;\n}\n\nvoid\nLibrary::registerTaskSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss4TaskFv\",\n                              static_cast<virt_ptr<Task> (*)(virt_ptr<Task>)>(Task_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss4TaskFPCc\",\n                              static_cast<virt_ptr<Task>(*)(virt_ptr<Task>, virt_ptr<const char>)>(Task_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss4TaskFPCcUi\",\n                              static_cast<virt_ptr<Task>(*)(virt_ptr<Task>, virt_ptr<const char>, uint32_t)>(Task_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss4TaskFUcPCc\",\n                              static_cast<virt_ptr<Task>(*)(virt_ptr<Task>, uint8_t, virt_ptr<const char>)>(Task_Constructor));\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss4TaskFv\",\n                              Task_Destructor);\n\n   RegisterFunctionExportName(\"Initialize__Q3_2nn4boss4TaskFPCc\",\n                              static_cast<nn::Result (*)(virt_ptr<Task>, virt_ptr<const char>)>(Task_Initialize));\n   RegisterFunctionExportName(\"Initialize__Q3_2nn4boss4TaskFPCcUi\",\n                              static_cast<nn::Result (*)(virt_ptr<Task>, virt_ptr<const char>, uint32_t)>(Task_Initialize));\n   RegisterFunctionExportName(\"Initialize__Q3_2nn4boss4TaskFUcPCc\",\n                              static_cast<nn::Result (*)(virt_ptr<Task>, uint8_t, virt_ptr<const char>)>(Task_Initialize));\n   RegisterFunctionExportName(\"Finalize__Q3_2nn4boss4TaskFv\",\n                              Task_Finalize);\n\n   RegisterFunctionExportName(\"IsRegistered__Q3_2nn4boss4TaskCFv\",\n                              Task_IsRegistered);\n   RegisterFunctionExportName(\"Register__Q3_2nn4boss4TaskFRQ3_2nn4boss11TaskSetting\",\n                              Task_Register);\n   RegisterFunctionExportName(\"GetAccountID__Q3_2nn4boss4TaskCFv\",\n                              Task_GetAccountID);\n   RegisterFunctionExportName(\"GetTaskID__Q3_2nn4boss4TaskCFv\",\n                              Task_GetTaskID);\n   RegisterFunctionExportName(\"GetTitleID__Q3_2nn4boss4TaskCFv\",\n                              Task_GetTitleID);\n\n   RegisterTypeInfo(\n      Task,\n      \"nn::boss::Task\",\n      {\n         \"__dt__Q3_2nn4boss4TaskFv\",\n      },\n      {});\n}\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_task.h",
    "content": "#pragma once\n#include \"nn_boss_taskid.h\"\n#include \"nn_boss_tasksetting.h\"\n#include \"nn_boss_titleid.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\n/*\nUnimplemented functions:\nnn::boss::Task::CancelSync(nn::boss::CancelMode)\nnn::boss::Task::CancelSync(unsigned int, nn::boss::CancelMode)\nnn::boss::Task::Cancel(nn::boss::CancelMode)\nnn::boss::Task::ClearTurnState(void)\nnn::boss::Task::GetContentLength(unsigned int *) const\nnn::boss::Task::GetExecCount(void) const\nnn::boss::Task::GetHttpStatusCode(unsigned int *) const\nnn::boss::Task::GetIntervalSec(void) const\nnn::boss::Task::GetLifeTimeSec(void) const\nnn::boss::Task::GetOptionResult(unsigned int, unsigned int *) const\nnn::boss::Task::GetPriority(void) const\nnn::boss::Task::GetProcessedLength(unsigned int *) const\nnn::boss::Task::GetRemainingLifeTimeSec(void) const\nnn::boss::Task::GetResult(unsigned int *) const\nnn::boss::Task::GetRunningState(unsigned int *) const\nnn::boss::Task::GetServiceStatus(void) const\nnn::boss::Task::GetState(unsigned int *) const\nnn::boss::Task::GetTaskID(void) const\nnn::boss::Task::GetTitleID(void) const\nnn::boss::Task::GetTurnState(unsigned int *) const\nnn::boss::Task::GetUrl(char *, unsigned int) const\nnn::boss::Task::IsFinished(void) const\nnn::boss::Task::Reconfigure(nn::boss::TaskSetting const &)\nnn::boss::Task::RegisterForImmediateRun(nn::boss::TaskSetting const &)\nnn::boss::Task::RestoreLifeTime(void)\nnn::boss::Task::Run(bool)\nnn::boss::Task::StartScheduling(bool)\nnn::boss::Task::StopScheduling(void)\nnn::boss::Task::Unregister(void)\nnn::boss::Task::UpdateIntervalSec(unsigned int)\nnn::boss::Task::UpdateLifeTimeSec(long long)\nnn::boss::Task::Wait(nn::boss::TaskWaitState)\nnn::boss::Task::Wait(unsigned int, nn::boss::TaskWaitState)\n*/\n\nnamespace cafe::nn_boss\n{\n\nstruct Task\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   be2_val<uint32_t> accountId;\n   UNKNOWN(4);\n   be2_struct<TaskID> taskId;\n   be2_struct<TitleID> titleId;\n   be2_virt_ptr<ghs::VirtualTable> virtualTable;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(Task, 0x00, accountId);\nCHECK_OFFSET(Task, 0x08, taskId);\nCHECK_OFFSET(Task, 0x10, titleId);\nCHECK_OFFSET(Task, 0x18, virtualTable);\nCHECK_SIZE(Task, 0x20);\n\nvirt_ptr<Task>\nTask_Constructor(virt_ptr<Task> self);\n\nvirt_ptr<Task>\nTask_Constructor(virt_ptr<Task> self,\n                 virt_ptr<const char> taskId);\n\nvirt_ptr<Task>\nTask_Constructor(virt_ptr<Task> self,\n                 virt_ptr<const char> taskId,\n                 uint32_t accountId);\n\nvirt_ptr<Task>\nTask_Constructor(virt_ptr<Task> self,\n                 uint8_t slot,\n                 virt_ptr<const char> taskId);\n\nvoid\nTask_Destructor(virt_ptr<Task> self,\n                ghs::DestructorFlags flags);\n\nnn::Result\nTask_Initialize(virt_ptr<Task> self,\n                virt_ptr<const char> taskId);\n\nnn::Result\nTask_Initialize(virt_ptr<Task> self,\n                virt_ptr<const char> taskId,\n                uint32_t accountId);\n\nnn::Result\nTask_Initialize(virt_ptr<Task> self,\n                uint8_t slot,\n                virt_ptr<const char> taskId);\n\nvoid\nTask_Finalize(virt_ptr<Task> self);\n\nbool\nTask_IsRegistered(virt_ptr<Task> self);\n\nnn::Result\nTask_Register(virt_ptr<Task> self,\n              virt_ptr<TaskSetting> taskSetting);\n\nuint32_t\nTask_GetAccountID(virt_ptr<Task> self);\n\nvoid\nTask_GetTaskID(virt_ptr<Task> self,\n               virt_ptr<TaskID> id);\n\nvoid\nTask_GetTitleID(virt_ptr<Task> self,\n                virt_ptr<TitleID> id);\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_taskid.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_taskid.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include <common/strutils.h>\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<TaskID>\nTaskID_Constructor(virt_ptr<TaskID> self)\n{\n   if (!self) {\n      self = virt_cast<TaskID *>(ghs::malloc(sizeof(TaskID)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->value[0] = char { 0 };\n   return self;\n}\n\nvirt_ptr<TaskID>\nTaskID_Constructor(virt_ptr<TaskID> self,\n                   virt_ptr<const char> id)\n{\n   if (!self) {\n      self = virt_cast<TaskID *>(ghs::malloc(sizeof(TaskID)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   string_copy(virt_addrof(self->value).get(), self->value.size(), id.get(), 8);\n   self->value[7] = char { 0 };\n   return self;\n}\n\nvirt_ptr<TaskID>\nTaskID_Constructor(virt_ptr<TaskID> self,\n                   virt_ptr<TaskID> other)\n{\n   if (!self) {\n      self = virt_cast<TaskID *>(ghs::malloc(sizeof(TaskID)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   std::memcpy(virt_addrof(self->value).get(),\n               virt_addrof(other->value).get(),\n               8);\n   return self;\n}\n\nvirt_ptr<TaskID>\nTaskID_OperatorAssign(virt_ptr<TaskID> self,\n                      virt_ptr<const char> id)\n{\n   string_copy(virt_addrof(self->value).get(), self->value.size(), id.get(), 8);\n   self->value[7] = char { 0 };\n   return self;\n}\n\nbool\nTaskID_OperatorEqual(virt_ptr<TaskID> self,\n                     virt_ptr<const char> id)\n{\n   return std::strncmp(virt_addrof(self->value).get(), id.get(), 8) == 0;\n}\n\nbool\nTaskID_OperatorEqual(virt_ptr<TaskID> self,\n                     virt_ptr<TaskID> other)\n{\n   return std::strncmp(virt_addrof(self->value).get(),\n                       virt_addrof(other->value).get(),\n                       8) == 0;\n}\n\nbool\nTaskID_OperatorNotEqual(virt_ptr<TaskID> self,\n                        virt_ptr<const char> id)\n{\n   return !TaskID_OperatorEqual(self, id);\n}\n\nbool\nTaskID_OperatorNotEqual(virt_ptr<TaskID> self,\n                        virt_ptr<TaskID> other)\n{\n   return !TaskID_OperatorEqual(self, other);\n}\n\nvirt_ptr<const char>\nTaskID_OperatorCastConstCharPtr(virt_ptr<TaskID> self)\n{\n   return virt_addrof(self->value);\n}\n\nvoid\nLibrary::registerTaskIdSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss6TaskIDFv\",\n                              static_cast<virt_ptr<TaskID> (*)(virt_ptr<TaskID>)>(TaskID_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss6TaskIDFPCc\",\n                              static_cast<virt_ptr<TaskID>(*)(virt_ptr<TaskID>, virt_ptr<const char>)>(TaskID_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss6TaskIDFRCQ3_2nn4boss6TaskID\",\n                              static_cast<virt_ptr<TaskID>(*)(virt_ptr<TaskID>, virt_ptr<TaskID>)>(TaskID_Constructor));\n   RegisterFunctionExportName(\"__as__Q3_2nn4boss6TaskIDFPCc\",\n                              static_cast<virt_ptr<TaskID> (*)(virt_ptr<TaskID>, virt_ptr<const char>)>(TaskID_OperatorAssign));\n   RegisterFunctionExportName(\"__eq__Q3_2nn4boss6TaskIDCFPCc\",\n                              static_cast<bool (*)(virt_ptr<TaskID>, virt_ptr<const char>)>(TaskID_OperatorEqual));\n   RegisterFunctionExportName(\"__eq__Q3_2nn4boss6TaskIDCFRCQ3_2nn4boss6TaskID\",\n                              static_cast<bool (*)(virt_ptr<TaskID>, virt_ptr<TaskID>)>(TaskID_OperatorEqual));\n   RegisterFunctionExportName(\"__ne__Q3_2nn4boss6TaskIDCFPCc\",\n                              static_cast<bool (*)(virt_ptr<TaskID>, virt_ptr<const char>)>(TaskID_OperatorNotEqual));\n   RegisterFunctionExportName(\"__ne__Q3_2nn4boss6TaskIDCFRCQ3_2nn4boss6TaskID\",\n                              static_cast<bool(*)(virt_ptr<TaskID>, virt_ptr<TaskID>)>(TaskID_OperatorNotEqual));\n   RegisterFunctionExportName(\"__opPCc__Q3_2nn4boss6TaskIDCFv\",\n                              TaskID_OperatorCastConstCharPtr);\n}\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_taskid.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_boss\n{\n\nstruct TaskID\n{\n   be2_array<char, 8> value;\n};\nCHECK_OFFSET(TaskID, 0, value);\nCHECK_SIZE(TaskID, 8);\n\nvirt_ptr<TaskID>\nTaskID_Constructor(virt_ptr<TaskID> self);\n\nvirt_ptr<TaskID>\nTaskID_Constructor(virt_ptr<TaskID> self,\n                   virt_ptr<const char> id);\n\nvirt_ptr<TaskID>\nTaskID_Constructor(virt_ptr<TaskID> self,\n                   virt_ptr<TaskID> other);\n\nvirt_ptr<TaskID>\nTaskID_OperatorAssign(virt_ptr<TaskID> self,\n                      virt_ptr<const char> id);\n\nbool\nTaskID_OperatorEqual(virt_ptr<TaskID> self,\n                     virt_ptr<const char> id);\n\nbool\nTaskID_OperatorEqual(virt_ptr<TaskID> self,\n                     virt_ptr<TaskID> other);\n\nbool\nTaskID_OperatorNotEqual(virt_ptr<TaskID> self,\n                        virt_ptr<const char> id);\n\nbool\nTaskID_OperatorNotEqual(virt_ptr<TaskID> self,\n                        virt_ptr<TaskID> other);\n\nvirt_ptr<const char>\nTaskID_OperatorCastConstCharPtr(virt_ptr<TaskID> self);\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_tasksetting.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_tasksetting.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"nn/boss/nn_boss_result.h\"\n\nnamespace cafe::nn_boss\n{\n\nusing namespace nn::boss;\n\nvirt_ptr<ghs::VirtualTable> TaskSetting::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> TaskSetting::TypeDescriptor = nullptr;\n\nvirt_ptr<TaskSetting>\nTaskSetting_Constructor(virt_ptr<TaskSetting> self)\n{\n   if (!self) {\n      self = virt_cast<TaskSetting *>(ghs::malloc(sizeof(TaskSetting)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->virtualTable = TaskSetting::VirtualTable;\n   TaskSetting_InitializeSetting(self);\n   return self;\n}\n\nvoid\nTaskSetting_Destructor(virt_ptr<TaskSetting> self,\n                       ghs::DestructorFlags flags)\n{\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nvoid\nTaskSetting_InitializeSetting(virt_ptr<TaskSetting> self)\n{\n   std::memset(self.get(), 0, 0x1000);\n   self->unk0x00 = 0u;\n   self->unk0x08 = 0u;\n   self->unk0x0C = 0u;\n   self->priority = uint8_t { 125 };\n   self->intervalSec = 28800u;\n   self->lifeTimeSec = 7776000u;\n}\n\nvoid\nTaskSetting_SetRunPermissionInParentalControlRestriction(virt_ptr<TaskSetting> self,\n                                                         bool value)\n{\n   if (value) {\n      self->permission |= 2u;\n   } else {\n      self->permission &= ~2u;\n   }\n}\n\nnn::Result\nTaskSetting_RegisterPreprocess(virt_ptr<TaskSetting> self,\n                               uint32_t a1,\n                               virt_ptr<TitleID> a2,\n                               virt_ptr<const char> a3)\n{\n   return ResultSuccess;\n}\n\nvoid\nTaskSetting_RegisterPostprocess(virt_ptr<TaskSetting> self,\n                                uint32_t a1,\n                                virt_ptr<TitleID> a2,\n                                virt_ptr<const char> a3,\n                                virt_ptr<nn::Result> a4)\n{\n}\n\nnn::Result\nPrivateTaskSetting_SetIntervalSecForShortSpanRetry(virt_ptr<TaskSetting> self,\n                                                   uint16_t sec)\n{\n   self->intervalSecForShortSpanRetry = sec;\n   return ResultSuccess;\n}\n\nnn::Result\nPrivateTaskSetting_SetIntervalSec(virt_ptr<TaskSetting> self,\n                                  uint32_t sec)\n{\n   self->intervalSec = sec;\n   return ResultSuccess;\n}\n\nnn::Result\nPrivateTaskSetting_SetLifeTimeSec(virt_ptr<TaskSetting> self,\n                                  uint64_t lifeTimeSec)\n{\n   self->lifeTimeSec = lifeTimeSec;\n   return ResultSuccess;\n}\n\nnn::Result\nPrivateTaskSetting_SetPermission(virt_ptr<TaskSetting> self,\n                                 uint8_t permission)\n{\n   self->permission = permission;\n   return ResultSuccess;\n}\n\nnn::Result\nPrivateTaskSetting_SetPriority(virt_ptr<TaskSetting> self,\n                               TaskPriority priority)\n{\n   self->priority = priority;\n   return ResultSuccess;\n}\n\nvoid\nLibrary::registerTaskSettingSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss11TaskSettingFv\",\n                              TaskSetting_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss11TaskSettingFv\",\n                              TaskSetting_Destructor);\n   RegisterFunctionExportName(\"InitializeSetting__Q3_2nn4boss11TaskSettingFv\",\n                              TaskSetting_InitializeSetting);\n   RegisterFunctionExportName(\"RegisterPreprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCc\",\n                              TaskSetting_RegisterPreprocess);\n   RegisterFunctionExportName(\"RegisterPostprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result\",\n                              TaskSetting_RegisterPostprocess);\n   RegisterFunctionExportName(\"SetRunPermissionInParentalControlRestriction__Q3_2nn4boss11TaskSettingFb\",\n                              TaskSetting_SetRunPermissionInParentalControlRestriction);\n\n   RegisterFunctionExportName(\"SetIntervalSecForShortSpanRetry__Q3_2nn4boss18PrivateTaskSettingSFRQ3_2nn4boss11TaskSettingUs\",\n                              PrivateTaskSetting_SetIntervalSecForShortSpanRetry);\n   RegisterFunctionExportName(\"SetIntervalSec__Q3_2nn4boss18PrivateTaskSettingSFRQ3_2nn4boss11TaskSettingUi\",\n                              PrivateTaskSetting_SetIntervalSec);\n   RegisterFunctionExportName(\"SetLifeTimeSec__Q3_2nn4boss18PrivateTaskSettingSFRQ3_2nn4boss11TaskSettingUL\",\n                              PrivateTaskSetting_SetLifeTimeSec);\n   RegisterFunctionExportName(\"SetPermission__Q3_2nn4boss18PrivateTaskSettingSFRQ3_2nn4boss11TaskSettingUc\",\n                              PrivateTaskSetting_SetPermission);\n   RegisterFunctionExportName(\"SetPriority__Q3_2nn4boss18PrivateTaskSettingSFRQ3_2nn4boss11TaskSettingQ3_2nn4boss12TaskPriority\",\n                              PrivateTaskSetting_SetPriority);\n\n   RegisterTypeInfo(\n      TaskSetting,\n      \"nn::boss::TaskSetting\",\n      {\n         \"__dt__Q3_2nn4boss11TaskSettingFv\",\n         \"RegisterPreprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCc\",\n         \"RegisterPostprocess__Q3_2nn4boss11TaskSettingFUiQ3_2nn4boss7TitleIDPCcQ2_2nn6Result\",\n      },\n      {});\n}\n\n}  // namespace namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_tasksetting.h",
    "content": "#pragma once\n#include \"nn_boss_titleid.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_boss\n{\n\n#pragma pack(push, 1)\n\nusing TaskPriority = uint8_t;\n\nstruct TaskSetting\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   be2_val<uint32_t> unk0x00;\n   UNKNOWN(4);\n   be2_val<uint32_t> unk0x08;\n   be2_val<uint32_t> unk0x0C;\n   UNKNOWN(0x28 - 0x10);\n   be2_val<uint16_t> unk0x28;\n   be2_val<TaskPriority> priority;\n   UNKNOWN(1);\n   be2_val<uint8_t> permission;\n   UNKNOWN(1);\n   be2_val<uint16_t> intervalSecForShortSpanRetry;\n   be2_val<uint32_t> intervalSec;\n   UNKNOWN(4);\n   be2_val<uint64_t> lifeTimeSec;\n   UNKNOWN(0x18C - 0x40);\n   be2_val<uint32_t> unk0x18C;\n   UNKNOWN(0x1000 - 0x190);\n   be2_virt_ptr<ghs::VirtualTable> virtualTable;\n};\nCHECK_OFFSET(TaskSetting, 0x0, unk0x00);\nCHECK_OFFSET(TaskSetting, 0x8, unk0x08);\nCHECK_OFFSET(TaskSetting, 0xC, unk0x0C);\nCHECK_OFFSET(TaskSetting, 0x28, unk0x28);\nCHECK_OFFSET(TaskSetting, 0x2A, priority);\nCHECK_OFFSET(TaskSetting, 0x2C, permission);\nCHECK_OFFSET(TaskSetting, 0x2E, intervalSecForShortSpanRetry);\nCHECK_OFFSET(TaskSetting, 0x30, intervalSec);\nCHECK_OFFSET(TaskSetting, 0x38, lifeTimeSec);\nCHECK_OFFSET(TaskSetting, 0x18C, unk0x18C);\nCHECK_OFFSET(TaskSetting, 0x1000, virtualTable);\nCHECK_SIZE(TaskSetting, 0x1004);\n\n#pragma pack(pop)\n\nvirt_ptr<TaskSetting>\nTaskSetting_Constructor(virt_ptr<TaskSetting> self);\n\nvoid\nTaskSetting_Destructor(virt_ptr<TaskSetting> self,\n                       ghs::DestructorFlags flags);\n\nvoid\nTaskSetting_InitializeSetting(virt_ptr<TaskSetting> self);\n\nvoid\nTaskSetting_SetRunPermissionInParentalControlRestriction(virt_ptr<TaskSetting> self,\n                                                         bool value);\n\nnn::Result\nTaskSetting_RegisterPreprocess(virt_ptr<TaskSetting> self,\n                               uint32_t a1,\n                               virt_ptr<TitleID> a2,\n                               virt_ptr<const char> a3);\n\nvoid\nTaskSetting_RegisterPostprocess(virt_ptr<TaskSetting> self,\n                                uint32_t a1,\n                                virt_ptr<TitleID> a2,\n                                virt_ptr<const char> a3,\n                                virt_ptr<nn::Result> a4);\n\nnn::Result\nPrivateTaskSetting_SetIntervalSecForShortSpanRetry(virt_ptr<TaskSetting> self,\n                                                   uint16_t sec);\n\nnn::Result\nPrivateTaskSetting_SetIntervalSec(virt_ptr<TaskSetting> self,\n                                  uint32_t sec);\n\nnn::Result\nPrivateTaskSetting_SetLifeTimeSec(virt_ptr<TaskSetting> self,\n                                  uint64_t lifeTimeSec);\n\nnn::Result\nPrivateTaskSetting_SetPermission(virt_ptr<TaskSetting> self,\n                                 uint8_t permission);\n\nnn::Result\nPrivateTaskSetting_SetPriority(virt_ptr<TaskSetting> self,\n                               TaskPriority priority);\n\n}  // namespace namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_title.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_title.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"cafe/libraries/nn_act/nn_act_clientstandardservice.h\"\n#include \"nn/boss/nn_boss_result.h\"\n\nusing namespace nn::boss;\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<ghs::VirtualTable> Title::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> Title::TypeDescriptor = nullptr;\n\nvirt_ptr<Title>\nTitle_Constructor(virt_ptr<Title> self)\n{\n   if (!self) {\n      self = virt_cast<Title *>(ghs::malloc(sizeof(Title)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->virtualTable = Title::VirtualTable;\n   self->accountID = 0u;\n   TitleID_Constructor(virt_addrof(self->titleID), 0ull);\n   return self;\n}\n\nvirt_ptr<Title>\nTitle_Constructor(virt_ptr<Title> self,\n                  uint32_t accountId,\n                  virt_ptr<TitleID> titleId)\n{\n   if (!self) {\n      self = virt_cast<Title *>(ghs::malloc(sizeof(Title)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->virtualTable = Title::VirtualTable;\n   self->accountID = accountId;\n   TitleID_Constructor(virt_addrof(self->titleID), titleId);\n   return self;\n}\n\nvoid\nTitle_Destructor(virt_ptr<Title> self,\n                 ghs::DestructorFlags flags)\n{\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nnn::Result\nTitle_ChangeAccount(virt_ptr<Title> self,\n                    uint8_t slot)\n{\n   if (!slot) {\n      self->accountID = slot;\n   } else if (auto accountId = nn_act::GetPersistentIdEx(slot)) {\n      self->accountID = accountId;\n   } else {\n      return ResultInvalidParameter;\n   }\n\n   return ResultSuccess;\n}\n\nvoid\nLibrary::registerTitleSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss5TitleFv\",\n                             static_cast<virt_ptr<Title> (*)(virt_ptr<Title>)>(Title_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss5TitleFUiQ3_2nn4boss7TitleID\",\n                              static_cast<virt_ptr<Title>(*)(virt_ptr<Title>, uint32_t, virt_ptr<TitleID>)>(Title_Constructor));\n   RegisterFunctionExportName(\"__dt__Q3_2nn4boss5TitleFv\",\n                              Title_Destructor);\n\n   RegisterFunctionExportName(\"ChangeAccount__Q3_2nn4boss5TitleFUc\",\n                              Title_ChangeAccount);\n\n   RegisterTypeInfo(\n      Title,\n      \"nn::boss::Title\",\n      {\n         \"__dt__Q3_2nn4boss5TitleFv\",\n      },\n      {});\n}\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_title.h",
    "content": "#pragma once\n#include \"nn_boss_titleid.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_boss\n{\n\nstruct Title\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   be2_val<uint32_t> accountID;\n   UNKNOWN(4);\n   be2_val<TitleID> titleID;\n   be2_virt_ptr<ghs::VirtualTable> virtualTable;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(Title, 0x00, accountID);\nCHECK_OFFSET(Title, 0x08, titleID);\nCHECK_OFFSET(Title, 0x10, virtualTable);\nCHECK_SIZE(Title, 0x18);\n\nvirt_ptr<Title>\nTitle_Constructor(virt_ptr<Title> self);\n\nvirt_ptr<Title>\nTitle_Constructor(virt_ptr<Title> self,\n                  uint32_t accountId,\n                  virt_ptr<TitleID> titleId);\n\nvoid\nTitle_Destructor(virt_ptr<Title> self,\n                 ghs::DestructorFlags flags);\n\nnn::Result\nTitle_ChangeAccount(virt_ptr<Title> self,\n                    uint8_t slot);\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_titleid.cpp",
    "content": "#include \"nn_boss.h\"\n#include \"nn_boss_titleid.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n\nnamespace cafe::nn_boss\n{\n\nvirt_ptr<TitleID>\nTitleID_Constructor(virt_ptr<TitleID> self)\n{\n   if (!self) {\n      self = virt_cast<TitleID *>(ghs::malloc(sizeof(TitleID)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->value = 0ull;\n   return self;\n}\n\nvirt_ptr<TitleID>\nTitleID_Constructor(virt_ptr<TitleID> self,\n                    virt_ptr<TitleID> other)\n{\n   if (!self) {\n      self = virt_cast<TitleID *>(ghs::malloc(sizeof(TitleID)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->value = other->value;\n   return self;\n}\n\nvirt_ptr<TitleID>\nTitleID_Constructor(virt_ptr<TitleID> self,\n                    uint64_t id)\n{\n   if (!self) {\n      self = virt_cast<TitleID *>(ghs::malloc(sizeof(TitleID)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->value = id;\n   return self;\n}\n\nbool\nTitleID_IsValid(virt_ptr<TitleID> self)\n{\n   return self->value != 0ull;\n}\n\nuint64_t\nTitleID_GetValue(virt_ptr<TitleID> self)\n{\n   return self->value;\n}\n\nuint32_t\nTitleID_GetTitleID(virt_ptr<TitleID> self)\n{\n   return static_cast<uint32_t>(self->value & 0xFFFFFFFF);\n}\n\nuint32_t\nTitleID_GetTitleCode(virt_ptr<TitleID> self)\n{\n   return TitleID_GetTitleID(self);\n}\n\nuint32_t\nTitleID_GetUniqueId(virt_ptr<TitleID> self)\n{\n   return (TitleID_GetTitleID(self) >> 8) & 0xFFFF;\n}\n\nbool\nTitleID_OperatorEqual(virt_ptr<TitleID> self,\n                      virt_ptr<TitleID> other)\n{\n   if (self->value & 0x2000000000ull) {\n      return (self->value  & 0xFFFFFF00FFFFFFFFull) ==\n             (other->value & 0xFFFFFF00FFFFFFFFull);\n   }\n\n   return self->value == other->value;\n}\n\nbool\nTitleID_OperatorNotEqual(virt_ptr<TitleID> self,\n                         virt_ptr<TitleID> other)\n{\n   return !TitleID_OperatorEqual(self, other);\n}\n\nvoid\nLibrary::registerTitleIdSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss7TitleIDFv\",\n                              static_cast<virt_ptr<TitleID> (*)(virt_ptr<TitleID>)>(TitleID_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID\",\n                              static_cast<virt_ptr<TitleID>(*)(virt_ptr<TitleID>, virt_ptr<TitleID>)>(TitleID_Constructor));\n   RegisterFunctionExportName(\"__ct__Q3_2nn4boss7TitleIDFUL\",\n                              static_cast<virt_ptr<TitleID>(*)(virt_ptr<TitleID>, uint64_t)>(TitleID_Constructor));\n\n   RegisterFunctionExportName(\"IsValid__Q3_2nn4boss7TitleIDCFv\",\n                              TitleID_IsValid);\n   RegisterFunctionExportName(\"GetValue__Q3_2nn4boss7TitleIDCFv\",\n                              TitleID_GetValue);\n   RegisterFunctionExportName(\"GetTitleId__Q3_2nn4boss7TitleIDCFv\",\n                              TitleID_GetTitleID);\n   RegisterFunctionExportName(\"GetTitleCode__Q3_2nn4boss7TitleIDCFv\",\n                              TitleID_GetTitleCode);\n   RegisterFunctionExportName(\"GetUniqueId__Q3_2nn4boss7TitleIDCFv\",\n                              TitleID_GetUniqueId);\n   RegisterFunctionExportName(\"__eq__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID\",\n                              TitleID_OperatorEqual);\n   RegisterFunctionExportName(\"__ne__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID\",\n                              TitleID_OperatorNotEqual);\n}\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_boss/nn_boss_titleid.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_boss\n{\n\nstruct TitleID\n{\n   be2_val<uint64_t> value;\n};\nCHECK_OFFSET(TitleID, 0, value);\nCHECK_SIZE(TitleID, 8);\n\nvirt_ptr<TitleID>\nTitleID_Constructor(virt_ptr<TitleID> self);\n\nvirt_ptr<TitleID>\nTitleID_Constructor(virt_ptr<TitleID> self,\n                    virt_ptr<TitleID> other);\n\nvirt_ptr<TitleID>\nTitleID_Constructor(virt_ptr<TitleID> self,\n                    uint64_t id);\n\nbool\nTitleID_IsValid(virt_ptr<TitleID> self);\n\nuint64_t\nTitleID_GetValue(virt_ptr<TitleID> self);\n\nuint32_t\nTitleID_GetTitleID(virt_ptr<TitleID> self);\n\nuint32_t\nTitleID_GetTitleCode(virt_ptr<TitleID> self);\n\nuint32_t\nTitleID_GetUniqueId(virt_ptr<TitleID> self);\n\nbool\nTitleID_OperatorEqual(virt_ptr<TitleID> self,\n                      virt_ptr<TitleID> other);\n\nbool\nTitleID_OperatorNotEqual(virt_ptr<TitleID> self,\n                         virt_ptr<TitleID> other);\n\n} // namespace cafe::nn_boss\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ccr/nn_ccr.cpp",
    "content": "#include \"nn_ccr.h\"\n\nnamespace cafe::nn_ccr\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nn_ccr\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ccr/nn_ccr.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_ccr\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_ccr, \"nn_ccr.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nn_ccr\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_cmpt/nn_cmpt.cpp",
    "content": "#include \"nn_cmpt.h\"\n\nnamespace cafe::nn_cmpt\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerLibSymbols();\n}\n\n} // namespace cafe::nn_cmpt\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_cmpt/nn_cmpt.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_cmpt\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_cmpt, \"nn_cmpt.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerLibSymbols();\n};\n\n} // namespace cafe::nn_cmpt\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_cmpt/nn_cmpt_enum.h",
    "content": "#ifndef CAFE_NN_CMPT_ENUM_H\n#define CAFE_NN_CMPT_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(nn_cmpt)\n\nENUM_BEG(CMPTError, int32_t)\n   ENUM_VALUE(OK,                0)\n   ENUM_VALUE(UserConfigError,   -512)\nENUM_END(CMPTError)\n\nFLAGS_BEG(CMPTPcConfFlags, uint32_t)\n   FLAGS_VALUE(None,             0)\n   FLAGS_VALUE(RstInternetCh,    1 << 0)\n   FLAGS_VALUE(RstNwAccess,      1 << 1)\n   FLAGS_VALUE(RstPtOrder,       1 << 2)\nFLAGS_END(CMPTPcConfFlags)\n\nENUM_NAMESPACE_EXIT(nn_cmpt)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_NN_CMPT_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_cmpt/nn_cmpt_lib.cpp",
    "content": "#include \"nn_cmpt.h\"\n#include \"nn_cmpt_lib.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_userconfig.h\"\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::nn_cmpt\n{\n\nCMPTError\nCMPTGetDataSize(virt_ptr<uint32_t> outDataSize)\n{\n   *outDataSize = 12u * 1024u * 1024u;\n   return CMPTError::OK;\n}\n\nCMPTError\nCMPTAcctGetPcConf(virt_ptr<CMPTPcConf> outConf)\n{\n   StackObject<uint32_t> rating { 0 };\n   StackObject<uint32_t> organisation { 0 };\n   StackObject<uint8_t> rst_internet_ch { 0 };\n   StackObject<uint8_t> rst_nw_access { 0 };\n   StackObject<uint8_t> rst_pt_order { 0 };\n   StackArray<UCSysConfig, 5> config { {\n      { \"wii_acct.pc.rating\", 0u, UCDataType::UnsignedInt, UCError::OK, 4u, rating },\n      { \"wii_acct.pc.organization\", 0u, UCDataType::UnsignedInt, UCError::OK, 4u, organisation },\n      { \"wii_acct.pc.rst_internet_ch\", 0u, UCDataType::UnsignedByte, UCError::OK, 1u, rst_internet_ch },\n      { \"wii_acct.pc.rst_nw_access\", 0u, UCDataType::UnsignedByte, UCError::OK, 1u, rst_nw_access },\n      { \"wii_acct.pc.rst_pt_order\", 0u, UCDataType::UnsignedByte, UCError::OK, 1u, rst_pt_order },\n   } };\n\n   auto error = UCOpen();\n   if (error < 0) {\n      return CMPTError::UserConfigError;\n   }\n\n   auto handle = static_cast<IOSHandle>(error);\n   error = UCReadSysConfig(handle, 5, config);\n   UCClose(handle);\n\n   if (error != UCError::OK) {\n      return CMPTError::UserConfigError;\n   }\n\n   outConf->rating = *rating;\n   outConf->organisation = *organisation;\n   outConf->flags = CMPTPcConfFlags::None;\n\n   if (*rst_internet_ch) {\n      outConf->flags = CMPTPcConfFlags::RstInternetCh;\n   }\n\n   if (*rst_nw_access) {\n      outConf->flags = CMPTPcConfFlags::RstNwAccess;\n   }\n\n   if (*rst_pt_order) {\n      outConf->flags = CMPTPcConfFlags::RstPtOrder;\n   }\n\n   return CMPTError::OK;\n}\n\nvoid\nLibrary::registerLibSymbols()\n{\n   RegisterFunctionExport(CMPTAcctGetPcConf);\n   RegisterFunctionExport(CMPTGetDataSize);\n}\n\n} // namespace cafe::nn_cmpt\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_cmpt/nn_cmpt_lib.h",
    "content": "#pragma once\n#include \"nn_cmpt_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_cmpt\n{\n\nstruct CMPTPcConf\n{\n   be2_val<uint32_t> rating;\n   be2_val<uint32_t> organisation;\n   be2_val<CMPTPcConfFlags> flags;\n};\nCHECK_OFFSET(CMPTPcConf, 0x00, rating);\nCHECK_OFFSET(CMPTPcConf, 0x04, organisation);\nCHECK_OFFSET(CMPTPcConf, 0x08, flags);\nCHECK_SIZE(CMPTPcConf, 0x0C);\n\nCMPTError\nCMPTGetDataSize(virt_ptr<uint32_t> outDataSize);\n\nCMPTError\nCMPTAcctGetPcConf(virt_ptr<CMPTPcConf> outConf);\n\n} // namespace cafe::nn_cmpt\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_dlp/nn_dlp.cpp",
    "content": "#include \"nn_dlp.h\"\n\nnamespace cafe::nn_dlp\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nn_dlp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_dlp/nn_dlp.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_dlp\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_dlp, \"nn_dlp.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nn_dlp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec.cpp",
    "content": "#include \"nn_ec.h\"\n\nnamespace cafe::nn_ec\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerCatalogSymbols();\n   registerItemListSymbols();\n   registerLibSymbols();\n   registerMemoryManagerSymbols();\n   registerMoneySymbols();\n   registerQuerySymbols();\n   registerRootObjectSymbols();\n   registerShoppingCatalogSymbols();\n}\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_ec\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_ec, \"nn_ec.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\n   void registerCatalogSymbols();\n   void registerItemListSymbols();\n   void registerLibSymbols();\n   void registerMemoryManagerSymbols();\n   void registerMoneySymbols();\n   void registerQuerySymbols();\n   void registerRootObjectSymbols();\n   void registerShoppingCatalogSymbols();\n\nprivate:\n};\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_catalog.cpp",
    "content": "#include \"cafe/libraries/nn_ec/nn_ec.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_catalog.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_rootobject.h\"\n\nnamespace cafe::nn_ec\n{\n\nvirt_ptr<ghs::VirtualTable> Catalog::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> Catalog::TypeDescriptor = nullptr;\n\nvirt_ptr<Catalog>\nCatalog_Constructor(virt_ptr<Catalog> self)\n{\n   if (!self) {\n      self = virt_cast<Catalog *>(RootObject_New(sizeof(Catalog)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   ItemList_Constructor(virt_cast<ItemList *>(self));\n\n   self->impl = nullptr;\n   self->virtualTable = Catalog::VirtualTable;\n   return self;\n}\n\nvoid\nCatalog_Destructor(virt_ptr<Catalog> self,\n                  ghs::DestructorFlags flags)\n{\n   self->virtualTable = Catalog::VirtualTable;\n\n   ItemList_Destructor(virt_cast<ItemList *>(self),\n                        ghs::DestructorFlags::None);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      RootObject_Free(self);\n   }\n}\n\nvoid\nLibrary::registerCatalogSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn2ec7CatalogFv\",\n                              Catalog_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn2ec7CatalogFv\",\n                              Catalog_Destructor);\n\n   RegisterTypeInfo(\n      Catalog,\n      \"nn::ec::Catalog\",\n      {\n         \"__dt__Q3_2nn2ec7CatalogFv\",\n      },\n      {\n         \"nn::ec::ItemList\",\n      });\n}\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_catalog.h",
    "content": "#pragma once\n#include \"cafe/libraries/nn_ec/nn_ec_itemlist.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ec\n{\n\nstruct Catalog : ItemList\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n};\n\nvirt_ptr<Catalog>\nCatalog_Constructor(virt_ptr<Catalog> self);\n\nvoid\nCatalog_Destructor(virt_ptr<Catalog> self,\n                  ghs::DestructorFlags flags);\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_itemlist.cpp",
    "content": "#include \"cafe/libraries/nn_ec/nn_ec.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_itemlist.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_rootobject.h\"\n\nnamespace cafe::nn_ec\n{\n\ntemplate<>\nvirt_ptr<ghs::TypeDescriptor> NonCopyable<ItemList>::TypeDescriptor = nullptr;\n\nvirt_ptr<ghs::VirtualTable> ItemList::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> ItemList::TypeDescriptor = nullptr;\n\nvirt_ptr<ItemList>\nItemList_Constructor(virt_ptr<ItemList> self)\n{\n   if (!self) {\n      self = virt_cast<ItemList *>(RootObject_New(sizeof(ItemList)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->impl = nullptr;\n   self->virtualTable = ItemList::VirtualTable;\n   return self;\n}\n\nvoid\nItemList_Destructor(virt_ptr<ItemList> self,\n                    ghs::DestructorFlags flags)\n{\n   self->virtualTable = ItemList::VirtualTable;\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      RootObject_Free(self);\n   }\n}\n\n\nvoid\nLibrary::registerItemListSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn2ec8ItemListFv\",\n                              ItemList_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn2ec8ItemListFv\",\n                              ItemList_Destructor);\n\n   RegisterTypeInfo(\n      NonCopyable<ItemList>,\n      \"nn::ec::NonCopyable<nn::ec::ItemList>\",\n      {\n      },\n      {\n      });\n\n   RegisterTypeInfo(\n      ItemList,\n      \"nn::ec::ItemList\",\n      {\n         \"__dt__Q3_2nn2ec8ItemListFv\",\n      },\n      {\n         \"nn::ec::RootObject\",\n         \"nn::ec::NonCopyable<nn::ec::ItemList>\",\n      });\n}\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_itemlist.h",
    "content": "#pragma once\n#include \"cafe/libraries/nn_ec/nn_ec_rootobject.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_noncopyable.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ec\n{\n\nstruct ItemList : RootObject, NonCopyable<ItemList>\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   be2_virt_ptr<void> impl;\n   be2_virt_ptr<ghs::VirtualTable> virtualTable;\n};\n\nvirt_ptr<ItemList>\nItemList_Constructor(virt_ptr<ItemList> self);\n\nvoid\nItemList_Destructor(virt_ptr<ItemList> self,\n                    ghs::DestructorFlags flags);\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_lib.cpp",
    "content": "#include \"cafe/libraries/nn_ec/nn_ec.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_lib.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_memorymanager.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_result.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::nn_ec\n{\n\nnn::Result\nInitialize(uint32_t unk0)\n{\n   decaf_warn_stub();\n   return nn::ResultSuccess;\n}\n\nnn::Result\nFinalize()\n{\n   decaf_warn_stub();\n   return nn::ResultSuccess;\n}\n\nnn::Result\nSetAllocator(MemoryManager::AllocFn allocFn,\n             MemoryManager::FreeFn freeFn)\n{\n   auto test = nn::Result(0xC1603C80);\n   if (!allocFn || !freeFn) {\n      return ResultInvalidArgument;\n   }\n\n   internal::MemoryManager_SetAllocator(allocFn, freeFn);\n   return nn::ResultSuccess;\n}\n\nvoid\nLibrary::registerLibSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn2ecFUi\",\n                              Initialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn2ecFv\",\n                              Finalize);\n   RegisterFunctionExportName(\"SetAllocator__Q2_2nn2ecFPFUii_PvPFPv_v\",\n                              SetAllocator);\n}\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_lib.h",
    "content": "#pragma once\n#include \"nn_ec_memorymanager.h\"\n\n#include \"nn/nn_result.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ec\n{\n\nnn::Result\nInitialize(uint32_t unk0);\n\nnn::Result\nFinalize();\n\nnn::Result\nSetAllocator(MemoryManager::AllocFn allocate, MemoryManager::FreeFn free);\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_memorymanager.cpp",
    "content": "#include \"cafe/libraries/nn_ec/nn_ec.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_memorymanager.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/libraries/coreinit/coreinit_memdefaultheap.h\"\n\n#include <libcpu/be2_struct.h>\n#include <mutex>\n\nnamespace cafe::nn_ec\n{\n\nstruct StaticMemoryManagerData\n{\n   StaticMemoryManagerData()\n   {\n      MemoryManager_Constructor(virt_addrof(memoryManager));\n   }\n\n   be2_struct<MemoryManager> memoryManager;\n};\n\nstatic virt_ptr<StaticMemoryManagerData> sMemoryManagerData = nullptr;\nstatic MemoryManager::AllocFn sDefaultAllocFn;\nstatic MemoryManager::FreeFn sDefaultFreeFn;\n\nvirt_ptr<MemoryManager>\nMemoryManager_GetSingleton()\n{\n   return virt_addrof(sMemoryManagerData->memoryManager);\n}\n\nvirt_ptr<MemoryManager>\nMemoryManager_Constructor(virt_ptr<MemoryManager> self)\n{\n   self->allocFn = sDefaultAllocFn;\n   self->freeFn = sDefaultFreeFn;\n   return self;\n}\n\nvirt_ptr<void>\nMemoryManager_Allocate(virt_ptr<MemoryManager> self,\n                       uint32_t size,\n                       uint32_t align)\n{\n   std::unique_lock<nn::os::CriticalSection> lock { self->mutex };\n   return cafe::invoke(cpu::this_core::state(), self->allocFn, size, align);\n}\n\nvoid\nMemoryManager_Free(virt_ptr<MemoryManager> self,\n                   virt_ptr<void> ptr)\n{\n   std::unique_lock<nn::os::CriticalSection> lock { self->mutex };\n   return cafe::invoke(cpu::this_core::state(), self->freeFn, ptr);\n}\n\nnamespace internal\n{\n\nvoid\nMemoryManager_SetAllocator(MemoryManager::AllocFn allocFn,\n                           MemoryManager::FreeFn freeFn)\n{\n   auto memoryManager = MemoryManager_GetSingleton();\n\n   std::unique_lock<nn::os::CriticalSection> lock { memoryManager->mutex };\n   memoryManager->allocFn = allocFn;\n   memoryManager->freeFn = freeFn;\n}\n\nstatic virt_ptr<void> defaultAllocFn(uint32_t size, uint32_t align)\n{\n   return coreinit::MEMAllocFromDefaultHeapEx(size, align);\n}\n\nstatic void defaultFreeFn(virt_ptr<void> ptr)\n{\n   coreinit::MEMFreeToDefaultHeap(ptr);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerMemoryManagerSymbols()\n{\n   RegisterDataInternal(sMemoryManagerData);\n\n   RegisterFunctionInternal(internal::defaultAllocFn, sDefaultAllocFn);\n   RegisterFunctionInternal(internal::defaultFreeFn, sDefaultFreeFn);\n}\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_memorymanager.h",
    "content": "#pragma once\n#include \"cafe/nn/cafe_nn_os_criticalsection.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ec\n{\n\nstruct MemoryManager\n{\n   using AllocFn = virt_func_ptr<virt_ptr<void>(uint32_t size, uint32_t align)>;\n   using FreeFn = virt_func_ptr<void(virt_ptr<void> ptr)>;\n\n   be2_struct<nn::os::CriticalSection> mutex;\n   be2_val<AllocFn> allocFn;\n   be2_val<FreeFn> freeFn;\n   UNKNOWN(0x48 - 0x34);\n};\nCHECK_OFFSET(MemoryManager, 0x00, mutex);\nCHECK_OFFSET(MemoryManager, 0x2C, allocFn);\nCHECK_OFFSET(MemoryManager, 0x30, freeFn);\nCHECK_SIZE(MemoryManager, 0x48);\n\nvirt_ptr<MemoryManager>\nMemoryManager_GetSingleton();\n\nvirt_ptr<MemoryManager>\nMemoryManager_Constructor(virt_ptr<MemoryManager> self);\n\nvirt_ptr<void>\nMemoryManager_Allocate(virt_ptr<MemoryManager> self,\n                       uint32_t size,\n                       uint32_t align);\n\nvoid\nMemoryManager_Free(virt_ptr<MemoryManager> self,\n                   virt_ptr<void> ptr);\n\nnamespace internal\n{\n\nvoid\nMemoryManager_SetAllocator(MemoryManager::AllocFn allocFn,\n                           MemoryManager::FreeFn freeFn);\n\n} // internal\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_money.cpp",
    "content": "#include \"cafe/libraries/nn_ec/nn_ec.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_money.h\"\n\n#include \"common/strutils.h\"\n\nnamespace cafe::nn_ec\n{\n\nvirt_ptr<Money>\nMoney_Constructor(virt_ptr<Money> self,\n                  virt_ptr<const char> value,\n                  virt_ptr<const char> currency,\n                  virt_ptr<const char> amount)\n{\n   if (!self) {\n      self = virt_cast<Money *>(RootObject_New(sizeof(Money)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   if (value) {\n      string_copy(virt_addrof(self->value).get(), value.get(), self->value.size() - 1);\n   } else {\n      self->value[0] = '\\0';\n   }\n\n   if (currency && strnlen(currency.get(), 4) == 3) {\n      memcpy(virt_addrof(self->currency).get(), currency.get(), 4);\n   } else {\n      self->currency[0] = '\\0';\n   }\n\n   if (amount) {\n      string_copy(virt_addrof(self->amount).get(), amount.get(), self->amount.size() - 1);\n   } else {\n      // TODO: Does something with currency and value to generate an amount string\n      self->amount = \"0\";\n   }\n\n   return self;\n}\n\nvoid\nLibrary::registerMoneySymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn2ec5MoneyFPCcN21\",\n                              Money_Constructor);\n}\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_money.h",
    "content": "#pragma once\n#include \"cafe/libraries/nn_ec/nn_ec_rootobject.h\"\n\n#include \"cafe/nn/cafe_nn_os_criticalsection.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ec\n{\n\nstruct Money : RootObject\n{\n   be2_array<char, 44> amount;\n   be2_array<char, 16> value;\n   be2_array<char, 4> currency;\n};\nCHECK_OFFSET(Money, 0x00, amount);\nCHECK_OFFSET(Money, 0x2C, value);\nCHECK_OFFSET(Money, 0x3C, currency);\nCHECK_SIZE(Money, 0x40);\n\nvirt_ptr<Money>\nMoney_Constructor(virt_ptr<Money> self,\n                  virt_ptr<const char> value,\n                  virt_ptr<const char> currency,\n                  virt_ptr<const char> amount);\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_noncopyable.h",
    "content": "#pragma once\n#include \"cafe/libraries/nn_ec/nn_ec_rootobject.h\"\n\n#include \"cafe/nn/cafe_nn_os_criticalsection.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ec\n{\n\ntemplate<typename T>\nstruct NonCopyable\n{\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   NonCopyable(const NonCopyable &) = delete;\n   NonCopyable &operator =(const NonCopyable &) = delete;\n};\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_query.cpp",
    "content": "#include \"cafe/libraries/nn_ec/nn_ec.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_query.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_rootobject.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::nn_ec\n{\n\nvirt_ptr<Query>\nQuery_Constructor(virt_ptr<Query> self)\n{\n   if (!self) {\n      self = virt_cast<Query *>(RootObject_New(sizeof(Query)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->impl = nullptr;\n   return self;\n}\n\nvoid\nQuery_Destructor(virt_ptr<Query> self,\n                 ghs::DestructorFlags flags)\n{\n   Query_Clear(self);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      RootObject_Free(self);\n   }\n}\n\nvoid\nQuery_Clear(virt_ptr<Query> self)\n{\n   if (self->impl) {\n      decaf_warn_stub();\n   }\n\n   self->impl = nullptr;\n}\n\nvoid\nLibrary::registerQuerySymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn2ec5QueryFv\",\n                              Query_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn2ec5QueryFv\",\n                              Query_Destructor);\n   RegisterFunctionExportName(\"Clear__Q3_2nn2ec5QueryFv\",\n                              Query_Clear);\n}\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_query.h",
    "content": "#pragma once\n#include \"cafe/libraries/nn_ec/nn_ec_rootobject.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_noncopyable.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ec\n{\n\nstruct Query : RootObject\n{\n   be2_virt_ptr<void> impl;\n};\n\nvirt_ptr<Query>\nQuery_Constructor(virt_ptr<Query> self);\n\nvoid\nQuery_Destructor(virt_ptr<Query> self,\n                 ghs::DestructorFlags flags);\n\nvoid\nQuery_Clear(virt_ptr<Query> self);\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_result.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace cafe::nn_ec\n{\n\nstatic constexpr nn::Result ResultInvalidArgument {\n   nn::Result::MODULE_NN_EC, nn::Result::LEVEL_USAGE, 0x3C80\n};\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_rootobject.cpp",
    "content": "#include \"cafe/libraries/nn_ec/nn_ec.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_memorymanager.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_rootobject.h\"\n\nnamespace cafe::nn_ec\n{\n\nvirt_ptr<ghs::TypeDescriptor> RootObject::TypeDescriptor = nullptr;\n\nvirt_ptr<void>\nRootObject_New(uint32_t size)\n{\n   return MemoryManager_Allocate(MemoryManager_GetSingleton(), size, 8);\n}\n\nvirt_ptr<void>\nRootObject_PlacementNew(uint32_t size,\n                        virt_ptr<void> ptr)\n{\n   return ptr;\n}\n\nvoid\nRootObject_Free(virt_ptr<void> ptr)\n{\n   MemoryManager_Free(MemoryManager_GetSingleton(), ptr);\n}\n\nvoid\nLibrary::registerRootObjectSymbols()\n{\n   RegisterFunctionExportName(\"__nw__Q3_2nn2ec10RootObjectSFUi\",\n                              RootObject_New);\n   RegisterFunctionExportName(\"__nwa__Q3_2nn2ec10RootObjectSFUi\",\n                              RootObject_New);\n   RegisterFunctionExportName(\"__nw__Q3_2nn2ec10RootObjectSFUiPv\",\n                              RootObject_PlacementNew);\n   RegisterFunctionExportName(\"__nwa__Q3_2nn2ec10RootObjectSFUiPv\",\n                              RootObject_PlacementNew);\n   RegisterFunctionExportName(\"__dl__Q3_2nn2ec10RootObjectSFPv\",\n                              RootObject_Free);\n   RegisterFunctionExportName(\"__dla__Q3_2nn2ec10RootObjectSFPv\",\n                              RootObject_Free);\n\n   RegisterTypeInfo(\n      RootObject,\n      \"nn::ec::RootObject\",\n      {\n      },\n      {\n      });\n}\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_rootobject.h",
    "content": "#pragma once\n#include \"cafe/nn/cafe_nn_os_criticalsection.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ec\n{\n\nstruct RootObject\n{\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n};\n\nvirt_ptr<void>\nRootObject_New(uint32_t size);\n\nvirt_ptr<void>\nRootObject_PlacementNew(uint32_t size,\n                        virt_ptr<void> ptr);\n\nvoid\nRootObject_Free(virt_ptr<void> ptr);\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_shoppingcatalog.cpp",
    "content": "#include \"cafe/libraries/nn_ec/nn_ec.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_shoppingcatalog.h\"\n#include \"cafe/libraries/nn_ec/nn_ec_rootobject.h\"\n\nnamespace cafe::nn_ec\n{\n\nvirt_ptr<ghs::VirtualTable> ShoppingCatalog::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> ShoppingCatalog::TypeDescriptor = nullptr;\n\nvirt_ptr<ShoppingCatalog>\nShoppingCatalog_Constructor(virt_ptr<ShoppingCatalog> self)\n{\n   if (!self) {\n      self = virt_cast<ShoppingCatalog *>(RootObject_New(sizeof(ShoppingCatalog)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   Catalog_Constructor(virt_cast<Catalog *>(self));\n\n   self->impl = nullptr;\n   self->virtualTable = ShoppingCatalog::VirtualTable;\n   return self;\n}\n\nvoid\nShoppingCatalog_Destructor(virt_ptr<ShoppingCatalog> self,\n                           ghs::DestructorFlags flags)\n{\n   self->virtualTable = ShoppingCatalog::VirtualTable;\n\n   Catalog_Destructor(virt_cast<Catalog *>(self),\n                      ghs::DestructorFlags::None);\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      RootObject_Free(self);\n   }\n}\n\nvoid\nLibrary::registerShoppingCatalogSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn2ec15ShoppingCatalogFv\",\n                              ShoppingCatalog_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn2ec15ShoppingCatalogFv\",\n                              ShoppingCatalog_Destructor);\n\n   RegisterTypeInfo(\n      ShoppingCatalog,\n      \"nn::ec::ShoppingCatalog\",\n      {\n         \"__dt__Q3_2nn2ec15ShoppingCatalogFv\",\n      },\n      {\n         \"nn::ec::Catalog\",\n      });\n}\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ec/nn_ec_shoppingcatalog.h",
    "content": "#pragma once\n#include \"cafe/libraries/nn_ec/nn_ec_catalog.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_ec\n{\n\nstruct ShoppingCatalog : Catalog\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n};\n\nvirt_ptr<ShoppingCatalog>\nShoppingCatalog_Constructor(virt_ptr<ShoppingCatalog> self);\n\nvoid\nShoppingCatalog_Destructor(virt_ptr<ShoppingCatalog> self,\n                           ghs::DestructorFlags flags);\n\n} // namespace cafe::nn_ec\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_fp/nn_fp.cpp",
    "content": "#include \"nn_fp.h\"\n\nnamespace cafe::nn_fp\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerLibSymbols();\n}\n\n} // namespace cafe::nn_fp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_fp/nn_fp.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_fp\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_fp, \"nn_fp.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerLibSymbols();\n};\n\n} // namespace cafe::nn_fp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_fp/nn_fp_lib.cpp",
    "content": "#include \"nn_fp.h\"\n#include \"nn_fp_lib.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::nn_fp\n{\n\nstruct StaticLibData\n{\n   be2_val<uint32_t> initialiseCount;\n};\n\nstatic virt_ptr<StaticLibData>\nsLibData = nullptr;\n\nnn::Result\nInitialize()\n{\n   decaf_warn_stub();\n   sLibData->initialiseCount++;\n   return nn::ResultSuccess;\n}\n\nnn::Result\nFinalize()\n{\n   decaf_warn_stub();\n   if (sLibData->initialiseCount > 0) {\n      sLibData->initialiseCount--;\n   }\n   return nn::ResultSuccess;\n}\n\nbool\nIsInitialized()\n{\n   decaf_warn_stub();\n   return sLibData->initialiseCount > 0;\n}\n\nbool\nIsOnline()\n{\n   decaf_warn_stub();\n   return false;\n}\n\nnn::Result\nGetFriendList(virt_ptr<void> list,\n              virt_ptr<uint32_t> outLength,\n              uint32_t index,\n              uint32_t listSize)\n{\n   decaf_warn_stub();\n\n   if (outLength) {\n      *outLength = 0u;\n   }\n\n   return nn::ResultSuccess;\n}\n\nvoid\nLibrary::registerLibSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn2fpFv\",\n                              Initialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn2fpFv\",\n                              Finalize);\n   RegisterFunctionExportName(\"IsInitialized__Q2_2nn2fpFv\",\n                              IsInitialized);\n   RegisterFunctionExportName(\"IsOnline__Q2_2nn2fpFv\",\n                              IsOnline);\n   RegisterFunctionExportName(\"GetFriendList__Q2_2nn2fpFPUiT1UiT3\",\n                              GetFriendList);\n\n   RegisterDataInternal(sLibData);\n}\n\n} // namespace cafe::nn_fp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_fp/nn_fp_lib.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_fp\n{\n\nnn::Result\nInitialize();\n\nnn::Result\nFinalize();\n\nbool\nIsInitialized();\n\nbool\nIsOnline();\n\nnn::Result\nGetFriendList(virt_ptr<void> list,\n              virt_ptr<uint32_t> outLength,\n              uint32_t index,\n              uint32_t listSize);\n\n} // namespace cafe::nn_fp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_hai/nn_hai.cpp",
    "content": "#include \"nn_hai.h\"\n\nnamespace cafe::nn_hai\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nn_hai\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_hai/nn_hai.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_hai\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_hai, \"nn_hai.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nn_hai\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_hpad/nn_hpad.cpp",
    "content": "#include \"nn_hpad.h\"\n\nnamespace cafe::nn_hpad\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nn_hpad\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_hpad/nn_hpad.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_hpad\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_hpad, \"nn_hpad.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nn_hpad\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_idbe/nn_idbe.cpp",
    "content": "#include \"nn_idbe.h\"\n\nnamespace cafe::nn_idbe\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nn_idbe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_idbe/nn_idbe.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_idbe\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_idbe, \"nn_idbe.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nn_idbe\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ndm/nn_ndm.cpp",
    "content": "#include \"nn_ndm.h\"\n\nnamespace cafe::nn_ndm\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerClientSymbols();\n}\n\n} // namespace cafe::nn_ndm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ndm/nn_ndm.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_ndm\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_ndm, \"nn_ndm.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerClientSymbols();\n};\n\n} // namespace cafe::nn_ndm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ndm/nn_ndm_client.cpp",
    "content": "#include \"nn_ndm.h\"\n#include \"nn_ndm_client.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::nn_ndm\n{\n\nstruct StaticClientData\n{\n   StaticClientData()\n   {\n      OSInitMutex(virt_addrof(mutex));\n   }\n\n   alignas(64) be2_array<uint8_t, 0x1000> allocatorMemory;\n\n   be2_struct<OSMutex> mutex;\n   be2_val<uint32_t> refCount = 0u;\n   be2_struct<nn::ipc::Client> client;\n   be2_struct<nn::ipc::BufferAllocator> allocator;\n};\n\nstatic virt_ptr<StaticClientData> sClientData = nullptr;\n\nnn::Result\nInitialize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount == 0) {\n      sClientData->client.initialise(make_stack_string(\"/dev/ndm\"));\n      sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory),\n                                        sClientData->allocatorMemory.size());\n   }\n\n   sClientData->refCount++;\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n   return nn::ResultSuccess;\n}\n\nvoid\nFinalize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount > 0) {\n      sClientData->refCount--;\n\n      if (sClientData->refCount == 0) {\n         sClientData->client.close();\n      }\n   }\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n}\n\nbool\nIsInitialized()\n{\n   return sClientData->client.isInitialised();\n}\n\nnn::Result\nEnableResumeDaemons()\n{\n   decaf_warn_stub();\n   return nn::ResultSuccess;\n}\n\nnn::Result\nGetDaemonStatus(virt_ptr<uint32_t> status, // nn::ndm::IDaemon::Status *\n                uint32_t unknown) // nn::ndm::Cafe::DaemonName\n{\n   decaf_warn_stub();\n   *status = 3u;\n   return nn::ResultSuccess;\n}\n\nvoid\nLibrary::registerClientSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn3ndmFv\",\n                              Initialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn3ndmFv\",\n                              Finalize);\n   RegisterFunctionExportName(\"IsInitialized__Q2_2nn3ndmFv\",\n                              IsInitialized);\n   RegisterFunctionExportName(\"EnableResumeDaemons__Q2_2nn3ndmFv\",\n                              EnableResumeDaemons);\n   RegisterFunctionExportName(\"GetDaemonStatus__Q2_2nn3ndmFPQ4_2nn3ndm7IDaemon6StatusQ4_2nn3ndm4Cafe10DaemonName\",\n                              GetDaemonStatus);\n\n   RegisterFunctionExportName(\"NDMInitialize\", Initialize);\n   RegisterFunctionExportName(\"NDMFinalize\", Finalize);\n   RegisterFunctionExportName(\"NDMEnableResumeDaemons\", EnableResumeDaemons);\n\n   RegisterDataInternal(sClientData);\n}\n\n} // namespace cafe::nn_ndm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_ndm/nn_ndm_client.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace cafe::nn_ndm\n{\n\nnn::Result\nInitialize();\n\nvoid\nFinalize();\n\nbool\nIsInitialized();\n\nnn::Result\nEnableResumeDaemons();\n\n} // namespace cafe::nn_ndm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nets2/nn_nets2.cpp",
    "content": "#include \"nn_nets2.h\"\n\nnamespace cafe::nn_nets2\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nn_nets2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nets2/nn_nets2.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_nets2\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_nets2, \"nn_nets2.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nn_nets2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nfp/nn_nfp.cpp",
    "content": "#include \"nn_nfp.h\"\n\nnamespace cafe::nn_nfp\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerLibSymbols();\n}\n\n} // namespace cafe::nn_nfp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nfp/nn_nfp.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_nfp\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_nfp, \"nn_nfp.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerLibSymbols();\n};\n\n} // namespace cafe::nn_nfp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nfp/nn_nfp_enum.h",
    "content": "#ifndef NN_NFP_ENUM_H\n#define NN_NFP_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(nn_nfp)\n\nENUM_BEG(State, uint32_t)\n   ENUM_VALUE(Uninitialised,  0)\n   ENUM_VALUE(Initialised,    1)\n   ENUM_VALUE(Detecting,      2)\nENUM_END(State)\n\nENUM_NAMESPACE_EXIT(nn_nfp)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef NN_NFP_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nfp/nn_nfp_lib.cpp",
    "content": "#include \"nn_nfp.h\"\n#include \"nn_nfp_lib.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"nn/nfp/nn_nfp_result.h\"\n\nusing namespace nn::nfp;\n\nnamespace cafe::nn_nfp\n{\n\nstruct StaticLibData\n{\n   be2_val<uint32_t> initialiseCount;\n};\n\nstatic virt_ptr<StaticLibData>\nsLibData = nullptr;\n\nnn::Result\nInitialize()\n{\n   decaf_warn_stub();\n   sLibData->initialiseCount++;\n   return ResultSuccess;\n}\n\nnn::Result\nFinalize()\n{\n   decaf_warn_stub();\n   if (sLibData->initialiseCount > 0) {\n      sLibData->initialiseCount--;\n   }\n   return ResultSuccess;\n}\n\nnn::Result\nGetAmiiboSettingsArgs(virt_ptr<AmiiboSettingsArgs> args)\n{\n   decaf_warn_stub();\n   std::memset(args.get(), 0, sizeof(AmiiboSettingsArgs));\n   return ResultSuccess;\n}\n\nState\nGetNfpState()\n{\n   decaf_warn_stub();\n   return State::Uninitialised;\n}\n\nnn::Result\nSetActivateEvent(uint32_t a1)\n{\n   decaf_warn_stub();\n   return ResultSuccess;\n}\n\nnn::Result\nSetDeactivateEvent(uint32_t a1)\n{\n   decaf_warn_stub();\n   return ResultSuccess;\n}\n\nnn::Result\nStartDetection()\n{\n   decaf_warn_stub();\n   return ResultSuccess;\n}\n\nnn::Result\nStopDetection()\n{\n   decaf_warn_stub();\n   return ResultSuccess;\n}\n\nvoid\nLibrary::registerLibSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn3nfpFv\",\n                              Initialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn3nfpFv\",\n                              Finalize);\n   RegisterFunctionExportName(\"GetAmiiboSettingsArgs__Q2_2nn3nfpFPQ3_2nn3nfp18AmiiboSettingsArgs\",\n                              GetAmiiboSettingsArgs);\n   RegisterFunctionExportName(\"GetNfpState__Q2_2nn3nfpFv\",\n                              GetNfpState);\n   RegisterFunctionExportName(\"SetActivateEvent__Q2_2nn3nfpFP7OSEvent\",\n                              SetActivateEvent);\n   RegisterFunctionExportName(\"SetDeactivateEvent__Q2_2nn3nfpFP7OSEvent\",\n                              SetDeactivateEvent);\n   RegisterFunctionExportName(\"StartDetection__Q2_2nn3nfpFv\",\n                              StartDetection);\n   RegisterFunctionExportName(\"StopDetection__Q2_2nn3nfpFv\",\n                              StopDetection);\n\n   RegisterDataInternal(sLibData);\n}\n\n}  // namespace cafe::nn_nfp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nfp/nn_nfp_lib.h",
    "content": "#pragma once\n#include \"nn_nfp_enum.h\"\n\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_nfp\n{\n\nstruct AmiiboSettingsArgs\n{\n   UNKNOWN(0x5D);\n};\nCHECK_SIZE(AmiiboSettingsArgs, 0x5D);\n\nnn::Result\nInitialize();\n\nnn::Result\nFinalize();\n\nnn::Result\nGetAmiiboSettingsArgs(virt_ptr<AmiiboSettingsArgs> args);\n\nState\nGetNfpState();\n\nnn::Result\nSetActivateEvent(uint32_t a1);\n\nnn::Result\nSetDeactivateEvent(uint32_t a1);\n\nnn::Result\nStartDetection();\n\nnn::Result\nStopDetection();\n\n}  // namespace cafe::nn_nfp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nim/nn_nim.cpp",
    "content": "#include \"nn_nim.h\"\n\nnamespace cafe::nn_nim\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerClientSymbols();\n}\n\n} // namespace cafe::nn_nim\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nim/nn_nim.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_nim\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_nim, \"nn_nim.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerClientSymbols();\n};\n\n} // namespace cafe::nn_nim\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nim/nn_nim_client.cpp",
    "content": "#include \"nn_nim.h\"\n#include \"nn_nim_client.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::nn_nim\n{\n\nstruct StaticClientData\n{\n   StaticClientData()\n   {\n      OSInitMutex(virt_addrof(mutex));\n   }\n\n   alignas(64) be2_array<uint8_t, 0x10000> allocatorMemory;\n\n   be2_struct<OSMutex> mutex;\n   be2_val<uint32_t> refCount = 0u;\n   be2_struct<nn::ipc::Client> client;\n   be2_struct<nn::ipc::BufferAllocator> allocator;\n};\n\nstatic virt_ptr<StaticClientData> sClientData = nullptr;\n\nnn::Result\nInitialize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount == 0) {\n      sClientData->client.initialise(make_stack_string(\"/dev/nim\"));\n      sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory),\n                                        sClientData->allocatorMemory.size());\n   }\n\n   sClientData->refCount++;\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n   return nn::ResultSuccess;\n}\n\nvoid\nFinalize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount > 0) {\n      sClientData->refCount--;\n\n      if (sClientData->refCount == 0) {\n         sClientData->client.close();\n      }\n   }\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n}\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient()\n{\n   return virt_addrof(sClientData->client);\n}\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator()\n{\n   return virt_addrof(sClientData->allocator);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerClientSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn3nimFv\", Initialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn3nimFv\", Finalize);\n\n   RegisterDataInternal(sClientData);\n}\n\n}  // namespace cafe::nn_nim\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_nim/nn_nim_client.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"cafe/nn/cafe_nn_ipc_bufferallocator.h\"\n\nnamespace cafe::nn_nim\n{\n\nnn::Result\nInitialize();\n\nvoid\nFinalize();\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient();\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator();\n\n} // namespace internal\n\n} // namespace cafe::nn_nim\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv.cpp",
    "content": "#include \"nn_olv.h\"\n#include \"cafe/libraries/coreinit/coreinit_ghs.h\"\n#include \"cafe/libraries/coreinit/coreinit_osreport.h\"\n\nnamespace cafe::nn_olv\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerDownloadedCommunityDataSymbols();\n   registerDownloadedDataBaseSymbols();\n   registerDownloadedPostDataSymbols();\n   registerDownloadedTopicDataSymbols();\n   registerInitSymbols();\n   registerInitializeParamSymbols();\n   registerUploadedDataBaseSymbols();\n   registerUploadedPostDataSymbols();\n}\n\n} // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_olv\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_olv, \"nn_olv.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerDownloadedCommunityDataSymbols();\n   void registerDownloadedDataBaseSymbols();\n   void registerDownloadedPostDataSymbols();\n   void registerDownloadedTopicDataSymbols();\n   void registerInitSymbols();\n   void registerInitializeParamSymbols();\n   void registerUploadedDataBaseSymbols();\n   void registerUploadedPostDataSymbols();\n};\n\n} // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedcommunitydata.cpp",
    "content": "#include \"nn_olv.h\"\n#include \"nn_olv_downloadedcommunitydata.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"nn/olv/nn_olv_result.h\"\n\nusing namespace nn::olv;\nusing nn::ffl::FFLStoreData;\n\nnamespace cafe::nn_olv\n{\n\nvirt_ptr<DownloadedCommunityData>\nDownloadedCommunityData_Constructor(virt_ptr<DownloadedCommunityData> self)\n{\n   if (!self) {\n      self = virt_cast<DownloadedCommunityData *>(ghs::malloc(sizeof(DownloadedCommunityData)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   std::memset(self.get(), 0, sizeof(DownloadedCommunityData));\n   return self;\n}\n\nuint32_t\nDownloadedCommunityData_GetAppDataSize(virt_ptr<DownloadedCommunityData> self)\n{\n   if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasAppData)) {\n      return 0;\n   }\n\n   return self->appDataLength;\n}\n\nnn::Result\nDownloadedCommunityData_GetAppData(virt_ptr<DownloadedCommunityData> self,\n                                   virt_ptr<uint8_t> buffer,\n                                   virt_ptr<uint32_t> outDataSize,\n                                   uint32_t bufferSize)\n{\n   if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasAppData)) {\n      return ResultNoData;\n   }\n\n   if (!buffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (!bufferSize) {\n      return ResultInvalidSize;\n   }\n\n   auto length = std::min<uint32_t>(bufferSize, self->appDataLength);\n   std::memcpy(buffer.get(), virt_addrof(self->appData).get(), length);\n\n   if (outDataSize) {\n      *outDataSize = length;\n   }\n\n   return ResultSuccess;\n}\n\nuint32_t\nDownloadedCommunityData_GetCommunityId(virt_ptr<DownloadedCommunityData> self)\n{\n   return self->communityId;\n}\n\nnn::Result\nDownloadedCommunityData_GetDescriptionText(virt_ptr<DownloadedCommunityData> self,\n                                           virt_ptr<char16_t> buffer,\n                                           uint32_t bufferSize)\n{\n   if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasDescriptionText)) {\n      return ResultNoData;\n   }\n\n   if (!buffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (!bufferSize) {\n      return ResultInvalidSize;\n   }\n\n   auto length = std::min<uint32_t>(bufferSize, self->descriptionTextLength);\n   std::memcpy(buffer.get(),\n               virt_addrof(self->descriptionText).get(),\n               length * sizeof(char16_t));\n\n   if (length < bufferSize) {\n      buffer[length] = char16_t { 0 };\n   }\n\n   return ResultSuccess;\n}\n\nnn::Result\nDownloadedCommunityData_GetIconData(virt_ptr<DownloadedCommunityData> self,\n                                    virt_ptr<uint8_t> buffer,\n                                    virt_ptr<uint32_t> outIconSize,\n                                    uint32_t bufferSize)\n{\n   if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasIconData)) {\n      return ResultNoData;\n   }\n\n   if (!buffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (!bufferSize) {\n      return ResultInvalidSize;\n   }\n\n   auto length = std::min<uint32_t>(bufferSize, self->iconDataLength);\n   std::memcpy(buffer.get(), virt_addrof(self->iconData).get(), length);\n\n   if (outIconSize) {\n      *outIconSize = length;\n   }\n\n   return ResultSuccess;\n}\n\nnn::Result\nDownloadedCommunityData_GetOwnerMiiData(virt_ptr<DownloadedCommunityData> self,\n                                        virt_ptr<nn::ffl::FFLStoreData> data)\n{\n   if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasOwnerMiiData)) {\n      return ResultNoData;\n   }\n\n   if (!data) {\n      return ResultInvalidPointer;\n   }\n\n   std::memcpy(data.get(),\n               virt_addrof(self->ownerMiiData).get(),\n               sizeof(FFLStoreData));\n   return ResultSuccess;\n}\n\nvirt_ptr<char16_t>\nDownloadedCommunityData_GetOwnerMiiNickname(virt_ptr<DownloadedCommunityData> self)\n{\n   if (!self->ownerMiiNickname[0]) {\n      return nullptr;\n   }\n\n   return virt_addrof(self->ownerMiiNickname);\n}\n\nuint32_t\nDownloadedCommunityData_GetOwnerPid(virt_ptr<DownloadedCommunityData> self)\n{\n   return self->ownerPid;\n}\n\nnn::Result\nDownloadedCommunityData_GetTitleText(virt_ptr<DownloadedCommunityData> self,\n                                     virt_ptr<char16_t> buffer,\n                                     uint32_t bufferSize)\n{\n   if (!DownloadedCommunityData_TestFlags(self, DownloadedCommunityData::HasTitleText)) {\n      return ResultNoData;\n   }\n\n   if (!buffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (!bufferSize) {\n      return ResultInvalidSize;\n   }\n\n   auto length = std::min<uint32_t>(bufferSize, self->titleTextLength);\n   std::memcpy(buffer.get(),\n               virt_addrof(self->titleText).get(),\n               length * sizeof(char16_t));\n\n   if (length < bufferSize) {\n      buffer[length] = char16_t { 0 };\n   }\n\n   return ResultSuccess;\n}\n\nbool\nDownloadedCommunityData_TestFlags(virt_ptr<DownloadedCommunityData> self,\n                                  uint32_t flags)\n{\n   return !!(self->flags & flags);\n}\n\nvoid\nLibrary::registerDownloadedCommunityDataSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn3olv23DownloadedCommunityDataFv\",\n                              DownloadedCommunityData_Constructor);\n   RegisterFunctionExportName(\"GetAppDataSize__Q3_2nn3olv23DownloadedCommunityDataCFv\",\n                              DownloadedCommunityData_GetAppDataSize);\n   RegisterFunctionExportName(\"GetAppData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi\",\n                              DownloadedCommunityData_GetAppData);\n   RegisterFunctionExportName(\"GetCommunityId__Q3_2nn3olv23DownloadedCommunityDataCFv\",\n                              DownloadedCommunityData_GetCommunityId);\n   RegisterFunctionExportName(\"GetDescriptionText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi\",\n                              DownloadedCommunityData_GetDescriptionText);\n   RegisterFunctionExportName(\"GetIconData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi\",\n                              DownloadedCommunityData_GetIconData);\n   RegisterFunctionExportName(\"GetOwnerMiiData__Q3_2nn3olv23DownloadedCommunityDataCFP12FFLStoreData\",\n                              DownloadedCommunityData_GetOwnerMiiData);\n   RegisterFunctionExportName(\"GetOwnerMiiNickname__Q3_2nn3olv23DownloadedCommunityDataCFv\",\n                              DownloadedCommunityData_GetOwnerMiiNickname);\n   RegisterFunctionExportName(\"GetOwnerPid__Q3_2nn3olv23DownloadedCommunityDataCFv\",\n                              DownloadedCommunityData_GetOwnerPid);\n   RegisterFunctionExportName(\"GetTitleText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi\",\n                              DownloadedCommunityData_GetTitleText);\n   RegisterFunctionExportName(\"TestFlags__Q3_2nn3olv23DownloadedCommunityDataCFUi\",\n                              DownloadedCommunityData_TestFlags);\n}\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedcommunitydata.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n#include \"nn/ffl/nn_ffl_miidata.h\"\n\n#include <libcpu/be2_struct.h>\n\n/*\nUnimplemented functions:\n   nn::olv::DownloadedCommunityData::GetCommunityCode(char *, unsigned int) const\n   GetCommunityCode__Q3_2nn3olv23DownloadedCommunityDataCFPcUi\n*/\n\nnamespace cafe::nn_olv\n{\n\nstruct DownloadedCommunityData\n{\n   enum Flags\n   {\n      Empty                = 0,\n      HasTitleText         = 1 << 0,\n      HasDescriptionText   = 1 << 1,\n      HasAppData           = 1 << 2,\n      HasIconData          = 1 << 3,\n      HasOwnerMiiData      = 1 << 4,\n   };\n\n   be2_val<uint32_t> flags;\n   be2_val<uint32_t> communityId;\n   be2_val<uint32_t> ownerPid;\n   be2_array<char16_t, 128> titleText;\n   be2_val<uint32_t> titleTextLength;\n   be2_array<char16_t, 256> descriptionText;\n   be2_val<uint32_t> descriptionTextLength;\n   be2_array<uint8_t, 1024> appData;\n   be2_val<uint32_t> appDataLength;\n   be2_array<uint8_t, 0x1002C> iconData;\n   be2_val<uint32_t> iconDataLength;\n   be2_array<uint8_t, 96> ownerMiiData;\n   be2_array<char16_t, 32> ownerMiiNickname;\n   UNKNOWN(0x1818);\n};\nCHECK_OFFSET(DownloadedCommunityData, 0x00, flags);\nCHECK_OFFSET(DownloadedCommunityData, 0x04, communityId);\nCHECK_OFFSET(DownloadedCommunityData, 0x08, ownerPid);\nCHECK_OFFSET(DownloadedCommunityData, 0x0C, titleText);\nCHECK_OFFSET(DownloadedCommunityData, 0x10C, titleTextLength);\nCHECK_OFFSET(DownloadedCommunityData, 0x110, descriptionText);\nCHECK_OFFSET(DownloadedCommunityData, 0x310, descriptionTextLength);\nCHECK_OFFSET(DownloadedCommunityData, 0x314, appData);\nCHECK_OFFSET(DownloadedCommunityData, 0x714, appDataLength);\nCHECK_OFFSET(DownloadedCommunityData, 0x718, iconData);\nCHECK_OFFSET(DownloadedCommunityData, 0x10744, iconDataLength);\nCHECK_OFFSET(DownloadedCommunityData, 0x10748, ownerMiiData);\nCHECK_OFFSET(DownloadedCommunityData, 0x107A8, ownerMiiNickname);\nCHECK_SIZE(DownloadedCommunityData, 0x12000);\n\nvirt_ptr<DownloadedCommunityData>\nDownloadedCommunityData_Constructor(virt_ptr<DownloadedCommunityData> self);\n\nuint32_t\nDownloadedCommunityData_GetAppDataSize(virt_ptr<DownloadedCommunityData> self);\n\nnn::Result\nDownloadedCommunityData_GetAppData(virt_ptr<DownloadedCommunityData> self,\n                                   virt_ptr<uint8_t> buffer,\n                                   virt_ptr<uint32_t> outDataSize,\n                                   uint32_t bufferSize);\n\nuint32_t\nDownloadedCommunityData_GetCommunityId(virt_ptr<DownloadedCommunityData> self);\n\nnn::Result\nDownloadedCommunityData_GetDescriptionText(virt_ptr<DownloadedCommunityData> self,\n                                           virt_ptr<char16_t> buffer,\n                                           uint32_t bufferSize);\n\nnn::Result\nDownloadedCommunityData_GetIconData(virt_ptr<DownloadedCommunityData> self,\n                                    virt_ptr<uint8_t> buffer,\n                                    virt_ptr<uint32_t> outIconSize,\n                                    uint32_t bufferSize);\n\nnn::Result\nDownloadedCommunityData_GetOwnerMiiData(virt_ptr<DownloadedCommunityData> self,\n                                        virt_ptr<nn::ffl::FFLStoreData> data);\n\nvirt_ptr<char16_t>\nDownloadedCommunityData_GetOwnerMiiNickname(virt_ptr<DownloadedCommunityData> self);\n\nuint32_t\nDownloadedCommunityData_GetOwnerPid(virt_ptr<DownloadedCommunityData> self);\n\nnn::Result\nDownloadedCommunityData_GetTitleText(virt_ptr<DownloadedCommunityData> self,\n                                     virt_ptr<char16_t> buffer,\n                                     uint32_t bufferSize);\n\nbool\nDownloadedCommunityData_TestFlags(virt_ptr<DownloadedCommunityData> self,\n                                  uint32_t flags);\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadeddatabase.cpp",
    "content": "#include \"nn_olv.h\"\n#include \"nn_olv_downloadeddatabase.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"nn/olv/nn_olv_result.h\"\n\n#include <common/strutils.h>\n\nusing namespace nn::olv;\nusing nn::ffl::FFLStoreData;\n\nnamespace cafe::nn_olv\n{\n\nconstexpr auto MinMemoBufferSize = 0x2582Cu;\n\nvirt_ptr<ghs::VirtualTable> DownloadedDataBase::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> DownloadedDataBase::TypeDescriptor = nullptr;\n\n\nvirt_ptr<DownloadedDataBase>\nDownloadedDataBase_Constructor(virt_ptr<DownloadedDataBase> self)\n{\n   if (!self) {\n      self = virt_cast<DownloadedDataBase *>(ghs::malloc(sizeof(DownloadedDataBase)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   std::memset(self.get(), 0, sizeof(DownloadedDataBase));\n   self->virtualTable = DownloadedDataBase::VirtualTable;\n   return self;\n}\n\nvoid\nDownloadedDataBase_Destructor(virt_ptr<DownloadedDataBase> self,\n                              ghs::DestructorFlags flags)\n{\n   if (!self) {\n      return;\n   }\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nnn::Result\nDownloadedDataBase_DownloadExternalBinaryData(virt_ptr<const DownloadedDataBase> self,\n                                              virt_ptr<void> dataBuffer,\n                                              virt_ptr<uint32_t> outDataSize,\n                                              uint32_t dataBufferSize)\n{\n   return ResultNotOnline;\n}\n\nnn::Result\nDownloadedDataBase_DownloadExternalImageData(virt_ptr<const DownloadedDataBase> self,\n                                             virt_ptr<void> dataBuffer,\n                                             virt_ptr<uint32_t> outDataSize,\n                                             uint32_t dataBufferSize)\n{\n   return ResultNotOnline;\n}\n\nnn::Result\nDownloadedDataBase_GetAppData(virt_ptr<const DownloadedDataBase> self,\n                              virt_ptr<uint32_t> dataBuffer,\n                              virt_ptr<uint32_t> outSize,\n                              uint32_t dataBufferSize)\n{\n   if (!dataBuffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (!dataBufferSize) {\n      return ResultInvalidSize;\n   }\n\n   if (!DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasAppData)) {\n      return ResultNoData;\n   }\n\n   auto copySize = std::min<uint32_t>(self->appDataSize, dataBufferSize);\n   std::memcpy(dataBuffer.get(), virt_addrof(self->appData).get(), copySize);\n\n   if (outSize) {\n      *outSize = copySize;\n   }\n\n   return ResultSuccess;\n}\n\nuint32_t\nDownloadedDataBase_GetAppDataSize(virt_ptr<const DownloadedDataBase> self)\n{\n   if (DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasAppData)) {\n      return self->appDataSize;\n   }\n\n   return 0;\n}\n\nnn::Result\nDownloadedDataBase_GetBodyMemo(virt_ptr<const DownloadedDataBase> self,\n                               virt_ptr<uint8_t> memoBuffer,\n                               virt_ptr<uint32_t> outSize,\n                               uint32_t memoBufferSize)\n{\n   if (!memoBuffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (memoBufferSize < MinMemoBufferSize) {\n      return ResultInvalidSize;\n   }\n\n   if (!DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasBodyMemo)) {\n      return ResultNoData;\n   }\n\n   // TODO: TGA uncompress mBodyMemo\n   return ResultNoData;\n}\n\nnn::Result\nDownloadedDataBase_GetBodyText(virt_ptr<const DownloadedDataBase> self,\n                               virt_ptr<char16_t> textBuffer,\n                               uint32_t textBufferSize)\n{\n   if (!textBuffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (!textBufferSize) {\n      return ResultInvalidSize;\n   }\n\n   if (!DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasBodyText)) {\n      return ResultNoData;\n   }\n\n   string_copy<char16_t>(textBuffer.get(),\n                         textBufferSize,\n                         virt_addrof(self->bodyText).get(),\n                         static_cast<size_t>(self->bodyTextLength));\n   return ResultSuccess;\n}\n\nuint8_t\nDownloadedDataBase_GetCountryId(virt_ptr<const DownloadedDataBase> self)\n{\n   return self->countryId;\n}\n\nuint32_t\nDownloadedDataBase_GetExternalBinaryDataSize(virt_ptr<const DownloadedDataBase> self)\n{\n   if (DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasExternalBinaryData)) {\n      return self->externalBinaryDataSize;\n   }\n\n   return 0;\n}\n\nuint32_t\nDownloadedDataBase_GetExternalImageDataSize(virt_ptr<const DownloadedDataBase> self)\n{\n   if (DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasExternalImageData)) {\n      return self->externalImageDataSize;\n   }\n\n   return 0;\n}\n\nvirt_ptr<const char>\nDownloadedDataBase_GetExternalUrl(virt_ptr<const DownloadedDataBase> self)\n{\n   if (DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasExternalUrl)) {\n      return virt_addrof(self->externalUrl);\n   }\n\n   return nullptr;\n}\n\nuint8_t\nDownloadedDataBase_GetFeeling(virt_ptr<const DownloadedDataBase> self)\n{\n   return self->feeling;\n}\n\nuint8_t\nDownloadedDataBase_GetLanguageId(virt_ptr<const DownloadedDataBase> self)\n{\n   return self->languageId;\n}\n\nnn::Result\nDownloadedDataBase_GetMiiData(virt_ptr<const DownloadedDataBase> self,\n                              virt_ptr<FFLStoreData> outData)\n{\n   if (!DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasMiiData)) {\n      return ResultNoData;\n   }\n\n   if (!outData) {\n      return ResultInvalidPointer;\n   }\n\n   std::memcpy(outData.get(),\n               virt_addrof(self->miiData).get(),\n               sizeof(FFLStoreData));\n   return ResultSuccess;\n}\n\nvirt_ptr<nn::ffl::FFLStoreData>\nDownloadedDataBase_GetMiiData(virt_ptr<const DownloadedDataBase> self)\n{\n   if (DownloadedDataBase_TestFlags(self, DownloadedDataBase::HasMiiData)) {\n      return virt_addrof(self->miiData);\n   }\n\n   return nullptr;\n}\n\nvirt_ptr<const char16_t>\nDownloadedDataBase_GetMiiNickname(virt_ptr<const DownloadedDataBase> self)\n{\n   if (self->miiNickname[0]) {\n      return virt_addrof(self->miiNickname);\n   }\n\n   return nullptr;\n}\n\nuint8_t\nDownloadedDataBase_GetPlatformId(virt_ptr<const DownloadedDataBase> self)\n{\n   return self->platformId;\n}\n\nuint64_t\nDownloadedDataBase_GetPostDate(virt_ptr<const DownloadedDataBase> self)\n{\n   return self->postDate;\n}\n\nvirt_ptr<const uint8_t>\nDownloadedDataBase_GetPostId(virt_ptr<const DownloadedDataBase> self)\n{\n   return virt_addrof(self->postId);\n}\n\nuint32_t\nDownloadedDataBase_GetRegionId(virt_ptr<const DownloadedDataBase> self)\n{\n   return self->regionId;\n}\n\nvirt_ptr<const uint8_t>\nDownloadedDataBase_GetTopicTag(virt_ptr<const DownloadedDataBase> self)\n{\n   return virt_addrof(self->topicTag);\n}\n\nuint32_t\nDownloadedDataBase_GetUserPid(virt_ptr<const DownloadedDataBase> self)\n{\n   return self->userPid;\n}\n\nbool\nDownloadedDataBase_TestFlags(virt_ptr<const DownloadedDataBase> self,\n                             uint32_t flagMask)\n{\n   return (self->flags & flagMask) != 0;\n}\n\nvoid\nLibrary::registerDownloadedDataBaseSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn3olv18DownloadedDataBaseFv\",\n                              DownloadedDataBase_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn3olv18DownloadedDataBaseFv\",\n                              DownloadedDataBase_Destructor);\n   RegisterFunctionExportName(\"DownloadExternalBinaryData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi\",\n                              DownloadedDataBase_DownloadExternalBinaryData);\n   RegisterFunctionExportName(\"DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi\",\n                              DownloadedDataBase_DownloadExternalImageData);\n   RegisterFunctionExportName(\"GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetAppDataSize);\n   RegisterFunctionExportName(\"GetAppData__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi\",\n                              DownloadedDataBase_GetAppData);\n   RegisterFunctionExportName(\"GetBodyMemo__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi\",\n                              DownloadedDataBase_GetBodyMemo);\n   RegisterFunctionExportName(\"GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi\",\n                              DownloadedDataBase_GetBodyText);\n   RegisterFunctionExportName(\"GetCountryId__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetCountryId);\n   RegisterFunctionExportName(\"GetExternalBinaryDataSize__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetExternalBinaryDataSize);\n   RegisterFunctionExportName(\"GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetExternalImageDataSize);\n   RegisterFunctionExportName(\"GetExternalUrl__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetExternalUrl);\n   RegisterFunctionExportName(\"GetFeeling__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetFeeling);\n   RegisterFunctionExportName(\"GetLanguageId__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetLanguageId);\n   RegisterFunctionExportName(\"GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFP12FFLStoreData\",\n                              static_cast<nn::Result (*)(virt_ptr<const DownloadedDataBase>, virt_ptr<FFLStoreData>)>(DownloadedDataBase_GetMiiData));\n   RegisterFunctionExportName(\"GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              static_cast<virt_ptr<FFLStoreData> (*)(virt_ptr<const DownloadedDataBase>)>(DownloadedDataBase_GetMiiData));\n   RegisterFunctionExportName(\"GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetMiiNickname);\n   RegisterFunctionExportName(\"GetPlatformId__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetPlatformId);\n   RegisterFunctionExportName(\"GetPostDate__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetPostDate);\n   RegisterFunctionExportName(\"GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetPostId);\n   RegisterFunctionExportName(\"GetRegionId__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetRegionId);\n   RegisterFunctionExportName(\"GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetTopicTag);\n   RegisterFunctionExportName(\"GetUserPid__Q3_2nn3olv18DownloadedDataBaseCFv\",\n                              DownloadedDataBase_GetUserPid);\n   RegisterFunctionExportName(\"TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi\",\n                              DownloadedDataBase_TestFlags);\n\n   RegisterTypeInfo(\n      DownloadedDataBase,\n      \"nn::olv::DownloadedDataBase\",\n      {\n         \"__pure_virtual_called\",\n      },\n      {});\n}\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadeddatabase.h",
    "content": "#pragma once\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n#include \"nn/ffl/nn_ffl_miidata.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_olv\n{\n\nstruct DownloadedDataBase\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   enum Flags\n   {\n      Empty                   = 0,\n      HasBodyText             = 1 << 0,\n      HasBodyMemo             = 1 << 1,\n      HasExternalImageData    = 1 << 2,\n      HasExternalBinaryData   = 1 << 3,\n      HasMiiData              = 1 << 4,\n      HasExternalUrl          = 1 << 5,\n      HasAppData              = 1 << 6,\n   };\n\n   be2_val<uint32_t> flags;\n   be2_val<uint32_t> userPid;\n   be2_array<uint8_t, 32> postId;\n   be2_val<uint64_t> postDate;\n   be2_val<uint8_t> feeling;\n   PADDING(3);\n   be2_val<uint32_t> regionId;\n   be2_val<uint8_t> platformId;\n   be2_val<uint8_t> languageId;\n   be2_val<uint8_t> countryId;\n   PADDING(1);\n   be2_array<char16_t, 256> bodyText;\n   be2_val<uint32_t> bodyTextLength;\n   be2_array<uint8_t, 40960> bodyMemo;\n   be2_val<uint32_t> bodyMemoSize;\n   be2_array<uint8_t, 304> topicTag;\n   be2_array<uint8_t, 1024> appData;\n   be2_val<uint32_t> appDataSize;\n   be2_array<char, 256> externalBinaryUrl;\n   be2_val<uint32_t> externalBinaryDataSize;\n   be2_array<char, 256> externalImageUrl;\n   be2_val<uint32_t> externalImageDataSize;\n   be2_array<char, 256> externalUrl;\n   be2_struct<nn::ffl::FFLStoreData> miiData;\n   be2_array<char16_t, 128> miiNickname; // Actual size unknown\n   UNKNOWN(0xC000 - 0xABE0);\n   be2_virt_ptr<ghs::VirtualTable> virtualTable;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(DownloadedDataBase, 0x00, flags);\nCHECK_OFFSET(DownloadedDataBase, 0x04, userPid);\nCHECK_OFFSET(DownloadedDataBase, 0x08, postId);\nCHECK_OFFSET(DownloadedDataBase, 0x28, postDate);\nCHECK_OFFSET(DownloadedDataBase, 0x30, feeling);\nCHECK_OFFSET(DownloadedDataBase, 0x34, regionId);\nCHECK_OFFSET(DownloadedDataBase, 0x38, platformId);\nCHECK_OFFSET(DownloadedDataBase, 0x39, languageId);\nCHECK_OFFSET(DownloadedDataBase, 0x3A, countryId);\nCHECK_OFFSET(DownloadedDataBase, 0x3C, bodyText);\nCHECK_OFFSET(DownloadedDataBase, 0x23C, bodyTextLength);\nCHECK_OFFSET(DownloadedDataBase, 0x240, bodyMemo);\nCHECK_OFFSET(DownloadedDataBase, 0xA240, bodyMemoSize);\nCHECK_OFFSET(DownloadedDataBase, 0xA244, topicTag);\nCHECK_OFFSET(DownloadedDataBase, 0xA374, appData);\nCHECK_OFFSET(DownloadedDataBase, 0xA774, appDataSize);\nCHECK_OFFSET(DownloadedDataBase, 0xA778, externalBinaryUrl);\nCHECK_OFFSET(DownloadedDataBase, 0xA878, externalBinaryDataSize);\nCHECK_OFFSET(DownloadedDataBase, 0xA87C, externalImageUrl);\nCHECK_OFFSET(DownloadedDataBase, 0xA97C, externalImageDataSize);\nCHECK_OFFSET(DownloadedDataBase, 0xA980, externalUrl);\nCHECK_OFFSET(DownloadedDataBase, 0xAA80, miiData);\nCHECK_OFFSET(DownloadedDataBase, 0xAAE0, miiNickname);\nCHECK_OFFSET(DownloadedDataBase, 0xC000, virtualTable);\nCHECK_SIZE(DownloadedDataBase, 0xC008);\n\nvirt_ptr<DownloadedDataBase>\nDownloadedDataBase_Constructor(virt_ptr<DownloadedDataBase> self);\n\nvoid\nDownloadedDataBase_Destructor(virt_ptr<DownloadedDataBase> self,\n                              ghs::DestructorFlags flags);\n\nnn::Result\nDownloadedDataBase_DownloadExternalBinaryData(virt_ptr<const DownloadedDataBase> self,\n                                              virt_ptr<void> dataBuffer,\n                                              virt_ptr<uint32_t> outDataSize,\n                                              uint32_t dataBufferSize);\n\nnn::Result\nDownloadedDataBase_DownloadExternalImageData(virt_ptr<const DownloadedDataBase> self,\n                                             virt_ptr<void> dataBuffer,\n                                             virt_ptr<uint32_t> outDataSize,\n                                             uint32_t dataBufferSize);\n\nnn::Result\nDownloadedDataBase_GetAppData(virt_ptr<const DownloadedDataBase> self,\n                              virt_ptr<uint32_t> dataBuffer,\n                              virt_ptr<uint32_t> outSize,\n                              uint32_t dataBufferSize);\n\nuint32_t\nDownloadedDataBase_GetAppDataSize(virt_ptr<const DownloadedDataBase> self);\n\nnn::Result\nDownloadedDataBase_GetBodyMemo(virt_ptr<const DownloadedDataBase> self,\n                               virt_ptr<uint8_t> memoBuffer,\n                               virt_ptr<uint32_t> outSize,\n                               uint32_t memoBufferSize);\n\nnn::Result\nDownloadedDataBase_GetBodyText(virt_ptr<const DownloadedDataBase> self,\n                               virt_ptr<char16_t> textBuffer,\n                               uint32_t textBufferSize);\n\nuint8_t\nDownloadedDataBase_GetCountryId(virt_ptr<const DownloadedDataBase> self);\n\nuint32_t\nDownloadedDataBase_GetExternalBinaryDataSize(virt_ptr<const DownloadedDataBase> self);\n\nuint32_t\nDownloadedDataBase_GetExternalImageDataSize(virt_ptr<const DownloadedDataBase> self);\n\nvirt_ptr<const char>\nDownloadedDataBase_GetExternalUrl(virt_ptr<const DownloadedDataBase> self);\n\nuint8_t\nDownloadedDataBase_GetFeeling(virt_ptr<const DownloadedDataBase> self);\n\nuint8_t\nDownloadedDataBase_GetLanguageId(virt_ptr<const DownloadedDataBase> self);\n\nnn::Result\nDownloadedDataBase_GetMiiData(virt_ptr<const DownloadedDataBase> self,\n                              virt_ptr<nn::ffl::FFLStoreData> outData);\n\nvirt_ptr<nn::ffl::FFLStoreData>\nDownloadedDataBase_GetMiiData(virt_ptr<const DownloadedDataBase> self);\n\nvirt_ptr<const char16_t>\nDownloadedDataBase_GetMiiNickname(virt_ptr<const DownloadedDataBase> self);\n\nuint8_t\nDownloadedDataBase_GetPlatformId(virt_ptr<const DownloadedDataBase> self);\n\nuint64_t\nDownloadedDataBase_GetPostDate(virt_ptr<const DownloadedDataBase> self);\n\nvirt_ptr<const uint8_t>\nDownloadedDataBase_GetPostId(virt_ptr<const DownloadedDataBase> self);\n\nuint32_t\nDownloadedDataBase_GetRegionId(virt_ptr<const DownloadedDataBase> self);\n\nvirt_ptr<const uint8_t>\nDownloadedDataBase_GetTopicTag(virt_ptr<const DownloadedDataBase> self);\n\nuint32_t\nDownloadedDataBase_GetUserPid(virt_ptr<const DownloadedDataBase> self);\n\nbool\nDownloadedDataBase_TestFlags(virt_ptr<const DownloadedDataBase> self,\n                             uint32_t flagMask);\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedpostdata.cpp",
    "content": "#include \"nn_olv.h\"\n#include \"nn_olv_downloadeddatabase.h\"\n#include \"nn_olv_downloadedpostdata.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n\nnamespace cafe::nn_olv\n{\n\nvirt_ptr<ghs::VirtualTable> DownloadedPostData::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> DownloadedPostData::TypeDescriptor = nullptr;\n\nvirt_ptr<DownloadedPostData>\nDownloadedPostData_Constructor(virt_ptr<DownloadedPostData> self)\n{\n   if (!self) {\n      self = virt_cast<DownloadedPostData *>(ghs::malloc(sizeof(DownloadedPostData)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   DownloadedDataBase_Constructor(virt_cast<DownloadedDataBase *>(self));\n   self->virtualTable = DownloadedPostData::VirtualTable;\n   self->communityId = 0u;\n   self->empathyCount = 0u;\n   self->commentCount = 0u;\n   return self;\n}\n\nvoid\nDownloadedPostData_Destructor(virt_ptr<DownloadedPostData> self,\n                              ghs::DestructorFlags flags)\n{\n   if (!self) {\n      return;\n   }\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nuint32_t\nDownloadedPostData_GetCommentCount(virt_ptr<const DownloadedPostData> self)\n{\n   return self->commentCount;\n}\n\nuint32_t\nDownloadedPostData_GetCommunityId(virt_ptr<const DownloadedPostData> self)\n{\n   return self->communityId;\n}\n\nuint32_t\nDownloadedPostData_GetEmpathyCount(virt_ptr<const DownloadedPostData> self)\n{\n   return self->empathyCount;\n}\n\nvirt_ptr<const uint8_t>\nDownloadedPostData_GetPostId(virt_ptr<const DownloadedPostData> self)\n{\n   return DownloadedDataBase_GetPostId(virt_cast<const DownloadedDataBase *>(self));\n}\n\nvoid\nLibrary::registerDownloadedPostDataSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn3olv18DownloadedPostDataFv\",\n                              DownloadedPostData_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn3olv18DownloadedPostDataFv\",\n                              DownloadedPostData_Destructor);\n   RegisterFunctionExportName(\"GetCommentCount__Q3_2nn3olv18DownloadedPostDataCFv\",\n                              DownloadedPostData_GetCommentCount);\n   RegisterFunctionExportName(\"GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv\",\n                              DownloadedPostData_GetCommunityId);\n   RegisterFunctionExportName(\"GetEmpathyCount__Q3_2nn3olv18DownloadedPostDataCFv\",\n                              DownloadedPostData_GetEmpathyCount);\n   RegisterFunctionExportName(\"GetPostId__Q3_2nn3olv18DownloadedPostDataCFv\",\n                              DownloadedPostData_GetPostId);\n\n   RegisterTypeInfo(\n      DownloadedPostData,\n      \"nn::olv::DownloadedPostData\",\n      {\n         \"__dt__Q3_2nn3olv18DownloadedPostDataFv\",\n      },\n      {});\n}\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedpostdata.h",
    "content": "#pragma once\n#include \"nn_olv_downloadeddatabase.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_olv\n{\n\nstruct DownloadedPostData : public DownloadedDataBase\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   be2_val<uint32_t> communityId;\n   be2_val<uint32_t> empathyCount;\n   be2_val<uint32_t> commentCount;\n   UNKNOWN(0x1F4);\n};\nCHECK_OFFSET(DownloadedPostData, 0xC008, communityId);\nCHECK_OFFSET(DownloadedPostData, 0xC00C, empathyCount);\nCHECK_OFFSET(DownloadedPostData, 0xC010, commentCount);\nCHECK_SIZE(DownloadedPostData, 0xC208);\n\nvirt_ptr<DownloadedPostData>\nDownloadedPostData_Constructor(virt_ptr<DownloadedPostData> self);\n\nvoid\nDownloadedPostData_Destructor(virt_ptr<DownloadedPostData> self,\n                              ghs::DestructorFlags flags);\n\nuint32_t\nDownloadedPostData_GetCommentCount(virt_ptr<const DownloadedPostData> self);\n\nuint32_t\nDownloadedPostData_GetCommunityId(virt_ptr<const DownloadedPostData> self);\n\nuint32_t\nDownloadedPostData_GetEmpathyCount(virt_ptr<const DownloadedPostData> self);\n\nvirt_ptr<const uint8_t>\nDownloadedPostData_GetPostId(virt_ptr<const DownloadedPostData> self);\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedtopicdata.cpp",
    "content": "#include \"nn_olv.h\"\n#include \"nn_olv_downloadedtopicdata.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n\nnamespace cafe::nn_olv\n{\n\nvirt_ptr<DownloadedTopicData>\nDownloadedTopicData_Constructor(virt_ptr<DownloadedTopicData> self)\n{\n   if (!self) {\n      self = virt_cast<DownloadedTopicData *>(ghs::malloc(sizeof(DownloadedTopicData)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->unk1 = 0u;\n   self->communityId = 0u;\n   return self;\n}\n\nuint32_t\nDownloadedTopicData_GetCommunityId(virt_ptr<const DownloadedTopicData> self)\n{\n   return self->communityId;\n}\n\nuint32_t\nDownloadedTopicData_GetUserCount(virt_ptr<const DownloadedTopicData> self)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerDownloadedTopicDataSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn3olv19DownloadedTopicDataFv\",\n                              DownloadedTopicData_Constructor);\n   RegisterFunctionExportName(\"GetCommunityId__Q3_2nn3olv19DownloadedTopicDataCFv\",\n                              DownloadedTopicData_GetCommunityId);\n   RegisterFunctionExportName(\"GetUserCount__Q3_2nn3olv19DownloadedTopicDataCFv\",\n                              DownloadedTopicData_GetUserCount);\n}\n\n}  // namespace namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_downloadedtopicdata.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_olv\n{\n\nstruct DownloadedTopicData\n{\n   be2_val<uint32_t> unk1;\n   be2_val<uint32_t> communityId;\n   UNKNOWN(0xFF8);\n};\nCHECK_OFFSET(DownloadedTopicData, 0x00, unk1);\nCHECK_OFFSET(DownloadedTopicData, 0x04, communityId);\nCHECK_SIZE(DownloadedTopicData, 0x1000);\n\nvirt_ptr<DownloadedTopicData>\nDownloadedTopicData_Constructor(virt_ptr<DownloadedTopicData> self);\n\nuint32_t\nDownloadedTopicData_GetCommunityId(virt_ptr<const DownloadedTopicData> self);\n\nuint32_t\nDownloadedTopicData_GetUserCount(virt_ptr<const DownloadedTopicData> self);\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_init.cpp",
    "content": "#include \"nn_olv.h\"\n#include \"nn_olv_init.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::nn_olv\n{\n\nstatic bool\ngInitialised = false;\n\nnn::Result\nInitialize(virt_ptr<InitializeParam> initParam)\n{\n   decaf_warn_stub();\n   gInitialised = true;\n   return nn::ResultSuccess;\n}\n\nnn::Result\nInitialize(virt_ptr<MainAppParam> mainAppParam,\n           virt_ptr<InitializeParam> initParam)\n{\n   decaf_warn_stub();\n   gInitialised = true;\n   return nn::ResultSuccess;\n}\n\nnn::Result\nFinalize()\n{\n   decaf_warn_stub();\n   gInitialised = false;\n   return nn::ResultSuccess;\n}\n\nbool\nIsInitialized()\n{\n   decaf_warn_stub();\n   return gInitialised;\n}\n\nvoid\nLibrary::registerInitSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn3olvFPCQ3_2nn3olv15InitializeParam\",\n                              static_cast<nn::Result (*)(virt_ptr<InitializeParam>)>(Initialize));\n   RegisterFunctionExportName(\"Initialize__Q2_2nn3olvFPQ3_2nn3olv12MainAppParamPCQ3_2nn3olv15InitializeParam\",\n                              static_cast<nn::Result (*)(virt_ptr<MainAppParam>, virt_ptr<InitializeParam>)>(Initialize));\n   RegisterFunctionExportName(\"Finalize__Q2_2nn4bossFv\", Finalize);\n   RegisterFunctionExportName(\"IsInitialized__Q2_2nn3olvFv\", IsInitialized);\n}\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_init.h",
    "content": "#pragma once\n#include \"nn_olv_initializeparam.h\"\n\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_olv\n{\n\nstruct MainAppParam\n{\n};\nUNKNOWN_SIZE(MainAppParam);\n\nnn::Result\nInitialize(virt_ptr<InitializeParam> initParam);\n\nnn::Result\nInitialize(virt_ptr<MainAppParam> mainAppParam,\n           virt_ptr<InitializeParam> initParam);\n\nnn::Result\nFinalize();\n\nbool\nIsInitialized();\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_initializeparam.cpp",
    "content": "#include \"nn_olv.h\"\n#include \"nn_olv_initializeparam.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"nn/olv/nn_olv_result.h\"\n\nusing namespace nn::olv;\n\nnamespace cafe::nn_olv\n{\n\nconstexpr auto MinWorkBufferSize = 0x10000u;\n\nvirt_ptr<InitializeParam>\nInitializeParam_Constructor(virt_ptr<InitializeParam> self)\n{\n   if (!self) {\n      self = virt_cast<InitializeParam *>(ghs::malloc(sizeof(InitializeParam)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->flags = 0u;\n   self->reportTypes = 0x1B7Fu;\n   self->workBuffer = nullptr;\n   self->workBufferSize = 0u;\n   self->sysArgs = nullptr;\n   self->sysArgsSize = 0u;\n   return self;\n}\n\nnn::Result\nInitializeParam_SetFlags(virt_ptr<InitializeParam> self,\n                         uint32_t flags)\n{\n   self->flags = flags;\n   return ResultSuccess;\n}\n\nnn::Result\nInitializeParam_SetWork(virt_ptr<InitializeParam> self,\n                        virt_ptr<uint8_t> workBuffer,\n                        uint32_t workBufferSize)\n{\n   if (!workBuffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (workBufferSize < MinWorkBufferSize) {\n      return ResultInvalidSize;\n   }\n\n   self->workBuffer = workBuffer;\n   self->workBufferSize = workBufferSize;\n   return ResultSuccess;\n}\n\nnn::Result\nInitializeParam_SetReportTypes(virt_ptr<InitializeParam> self,\n                               uint32_t types)\n{\n   self->reportTypes = types;\n   return ResultSuccess;\n}\n\nnn::Result\nInitializeParam_SetSysArgs(virt_ptr<InitializeParam> self,\n                           virt_ptr<uint8_t> sysArgs,\n                           uint32_t sysArgsSize)\n{\n   if (!sysArgs) {\n      return ResultInvalidPointer;\n   }\n\n   if (!sysArgsSize) {\n      return ResultInvalidSize;\n   }\n\n   self->sysArgs = sysArgs;\n   self->sysArgsSize = sysArgsSize;\n   return ResultSuccess;\n}\n\nvoid\nLibrary::registerInitializeParamSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn3olv15InitializeParamFv\",\n                              InitializeParam_Constructor);\n   RegisterFunctionExportName(\"SetFlags__Q3_2nn3olv15InitializeParamFUi\",\n                              InitializeParam_SetFlags);\n   RegisterFunctionExportName(\"SetWork__Q3_2nn3olv15InitializeParamFPUcUi\",\n                              InitializeParam_SetWork);\n   RegisterFunctionExportName(\"SetReportTypes__Q3_2nn3olv15InitializeParamFUi\",\n                              InitializeParam_SetReportTypes);\n   RegisterFunctionExportName(\"SetSysArgs__Q3_2nn3olv15InitializeParamFPCvUi\",\n                              InitializeParam_SetSysArgs);\n}\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_initializeparam.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_olv\n{\n\nstruct InitializeParam\n{\n   be2_val<uint32_t> flags;\n   be2_val<uint32_t> reportTypes;\n   be2_virt_ptr<uint8_t> workBuffer;\n   be2_val<uint32_t> workBufferSize;\n   be2_virt_ptr<uint8_t> sysArgs;\n   be2_val<uint32_t> sysArgsSize;\n   UNKNOWN(0x28);\n};\nCHECK_OFFSET(InitializeParam, 0x00, flags);\nCHECK_OFFSET(InitializeParam, 0x04, reportTypes);\nCHECK_OFFSET(InitializeParam, 0x08, workBuffer);\nCHECK_OFFSET(InitializeParam, 0x0C, workBufferSize);\nCHECK_OFFSET(InitializeParam, 0x10, sysArgs);\nCHECK_OFFSET(InitializeParam, 0x14, sysArgsSize);\nCHECK_SIZE(InitializeParam, 0x40);\n\nvirt_ptr<InitializeParam>\nInitializeParam_Constructor(virt_ptr<InitializeParam> self);\n\nnn::Result\nInitializeParam_SetFlags(virt_ptr<InitializeParam> self,\n                         uint32_t flags);\n\nnn::Result\nInitializeParam_SetWork(virt_ptr<InitializeParam> self,\n                        virt_ptr<uint8_t> workBuffer,\n                        uint32_t workBufferSize);\n\nnn::Result\nInitializeParam_SetReportTypes(virt_ptr<InitializeParam> self,\n                               uint32_t types);\n\nnn::Result\nInitializeParam_SetSysArgs(virt_ptr<InitializeParam> self,\n                           virt_ptr<uint8_t> sysArgs,\n                           uint32_t sysArgsSize);\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_uploadeddatabase.cpp",
    "content": "#include \"nn_olv.h\"\n#include \"nn_olv_uploadeddatabase.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n#include \"nn/olv/nn_olv_result.h\"\n\nusing namespace nn::olv;\n\nnamespace cafe::nn_olv\n{\n\nvirt_ptr<ghs::VirtualTable> UploadedDataBase::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> UploadedDataBase::TypeDescriptor = nullptr;\n\n\nvirt_ptr<UploadedDataBase>\nUploadedDataBase_Constructor(virt_ptr<UploadedDataBase> self)\n{\n   if (!self) {\n      self = virt_cast<UploadedDataBase *>(ghs::malloc(sizeof(UploadedDataBase)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->virtualTable = UploadedDataBase::VirtualTable;\n   self->flags = 0u;\n   self->bodyTextLength = 0u;\n   self->bodyMemoLength = 0u;\n   self->appDataLength = 0u;\n   self->feeling = int8_t { 0 };\n   self->commonDataUnknown = 0u;\n   self->commonDataLength = 0u;\n   self->postID[0] = char { 0 };\n   return self;\n}\n\nvoid\nUploadedDataBase_Destructor(virt_ptr<UploadedDataBase> self,\n                            ghs::DestructorFlags flags)\n{\n   if (!self) {\n      return;\n   }\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nuint32_t\nUploadedDataBase_GetAppDataSize(virt_ptr<const UploadedDataBase> self)\n{\n   if (!UploadedDataBase_TestFlags(self, UploadedDataBase::HasAppData)) {\n      return 0;\n   }\n\n   return self->appDataLength;\n}\n\nnn::Result\nUploadedDataBase_GetAppData(virt_ptr<const UploadedDataBase> self,\n                            virt_ptr<uint8_t> buffer,\n                            virt_ptr<uint32_t> outDataSize,\n                            uint32_t bufferSize)\n{\n   if (!UploadedDataBase_TestFlags(self, UploadedDataBase::HasAppData)) {\n      return ResultNoData;\n   }\n\n   if (!buffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (!bufferSize) {\n      return ResultInvalidSize;\n   }\n\n   auto length = std::min<uint32_t>(bufferSize, self->appDataLength);\n   std::memcpy(buffer.get(),\n               virt_addrof(self->appData).get(),\n               length);\n\n   if (outDataSize) {\n      *outDataSize = length;\n   }\n\n   return ResultSuccess;\n}\n\nnn::Result\nUploadedDataBase_GetBodyText(virt_ptr<const UploadedDataBase> self,\n                             virt_ptr<char16_t> buffer,\n                             uint32_t bufferSize)\n{\n   if (!UploadedDataBase_TestFlags(self, UploadedDataBase::HasBodyText)) {\n      return ResultNoData;\n   }\n\n   if (!buffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (!bufferSize) {\n      return ResultInvalidSize;\n   }\n\n   auto length = std::min<uint32_t>(bufferSize, self->bodyTextLength);\n   std::memcpy(buffer.get(),\n               virt_addrof(self->bodyText).get(),\n               length * sizeof(char16_t));\n\n   if (length < bufferSize) {\n      buffer[length] = char16_t { 0 };\n   }\n\n   return ResultSuccess;\n}\n\nnn::Result\nUploadedDataBase_GetBodyMemo(virt_ptr<const UploadedDataBase> self,\n                             virt_ptr<uint8_t> buffer,\n                             virt_ptr<uint32_t> outMemoSize,\n                             uint32_t bufferSize)\n{\n   if (!UploadedDataBase_TestFlags(self, UploadedDataBase::HasBodyMemo)) {\n      return ResultNoData;\n   }\n\n   if (!buffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (!bufferSize) {\n      return ResultInvalidSize;\n   }\n\n   auto length = std::min<uint32_t>(bufferSize, self->bodyMemoLength);\n   std::memcpy(buffer.get(),\n               virt_addrof(self->bodyMemo).get(),\n               length);\n\n   if (outMemoSize) {\n      *outMemoSize = length;\n   }\n\n   return ResultSuccess;\n}\n\nnn::Result\nUploadedDataBase_GetCommonData(virt_ptr<const UploadedDataBase> self,\n                               virt_ptr<uint32_t> unk,\n                               virt_ptr<uint8_t> buffer,\n                               virt_ptr<uint32_t> outDataSize,\n                               uint32_t bufferSize)\n{\n   if (!self->commonDataLength) {\n      return ResultNoData;\n   }\n\n   if (!buffer) {\n      return ResultInvalidPointer;\n   }\n\n   if (!bufferSize) {\n      return ResultInvalidSize;\n   }\n\n   auto length = std::min<uint32_t>(bufferSize, self->commonDataLength);\n   std::memcpy(buffer.get(),\n               virt_addrof(self->commonData).get(),\n               length);\n\n   if (unk) {\n      *unk = self->commonDataUnknown;\n   }\n\n   if (outDataSize) {\n      *outDataSize = length;\n   }\n\n   return ResultSuccess;\n}\n\nint32_t\nUploadedDataBase_GetFeeling(virt_ptr<const UploadedDataBase> self)\n{\n   return self->feeling;\n}\n\nvirt_ptr<const char>\nUploadedDataBase_GetPostId(virt_ptr<const UploadedDataBase> self)\n{\n   return virt_addrof(self->postID);\n}\n\nbool\nUploadedDataBase_TestFlags(virt_ptr<const UploadedDataBase> self,\n                           uint32_t flag)\n{\n   return !!(self->flags & flag);\n}\n\nvoid\nLibrary::registerUploadedDataBaseSymbols()\n{\n   RegisterFunctionExportName(\"__dt__Q3_2nn3olv16UploadedDataBaseFv\",\n                              UploadedDataBase_Destructor);\n   RegisterFunctionExportName(\"GetCommonData__Q3_2nn3olv16UploadedDataBaseFPUiPUcT1Ui\",\n                              UploadedDataBase_GetCommonData);\n\n   RegisterTypeInfo(\n      UploadedDataBase,\n      \"nn::olv::UploadedDataBase\",\n      {\n         \"__pure_virtual_called\",\n      },\n      {});\n}\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_uploadeddatabase.h",
    "content": "#pragma once\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_olv\n{\n\nstruct UploadedDataBase\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   enum Flags\n   {\n      Empty          = 0,\n      HasBodyText    = 1 << 0,\n      HasBodyMemo    = 1 << 1,\n      HasAppData     = 1 << 2,\n   };\n\n   be2_val<uint32_t> flags;\n   be2_array<char, 32> postID;\n   be2_array<char16_t, 256> bodyText;\n   be2_val<uint32_t> bodyTextLength;\n   be2_array<uint8_t, 0xA000> bodyMemo;\n   be2_val<uint32_t> bodyMemoLength;\n   be2_array<uint8_t, 1024> appData;\n   be2_val<uint32_t> appDataLength;\n   be2_val<int8_t> feeling;\n   UNKNOWN(3);\n   be2_val<uint32_t> commonDataUnknown;\n   be2_val<uint32_t> commonDataLength;\n   be2_array<uint8_t, 0x1000> commonData;\n   UNKNOWN(0x9C8);\n   be2_virt_ptr<ghs::VirtualTable> virtualTable;\n};\nCHECK_OFFSET(UploadedDataBase, 0x00, flags);\nCHECK_OFFSET(UploadedDataBase, 0x04, postID);\nCHECK_OFFSET(UploadedDataBase, 0x24, bodyText);\nCHECK_OFFSET(UploadedDataBase, 0x224, bodyTextLength);\nCHECK_OFFSET(UploadedDataBase, 0x228, bodyMemo);\nCHECK_OFFSET(UploadedDataBase, 0xA228, bodyMemoLength);\nCHECK_OFFSET(UploadedDataBase, 0xA22C, appData);\nCHECK_OFFSET(UploadedDataBase, 0xA62C, appDataLength);\nCHECK_OFFSET(UploadedDataBase, 0xA630, feeling);\nCHECK_OFFSET(UploadedDataBase, 0xA634, commonDataUnknown);\nCHECK_OFFSET(UploadedDataBase, 0xA638, commonDataLength);\nCHECK_OFFSET(UploadedDataBase, 0xA63C, commonData);\nCHECK_OFFSET(UploadedDataBase, 0xC004, virtualTable);\nCHECK_SIZE(UploadedDataBase, 0xC008);\n\nvirt_ptr<UploadedDataBase>\nUploadedDataBase_Constructor(virt_ptr<UploadedDataBase> self);\n\nvoid\nUploadedDataBase_Destructor(virt_ptr<UploadedDataBase> self,\n                            ghs::DestructorFlags flags);\n\nuint32_t\nUploadedDataBase_GetAppDataSize(virt_ptr<const UploadedDataBase> self);\n\nnn::Result\nUploadedDataBase_GetAppData(virt_ptr<const UploadedDataBase> self,\n                            virt_ptr<uint8_t> buffer,\n                            virt_ptr<uint32_t> outDataSize,\n                            uint32_t bufferSize);\n\nnn::Result\nUploadedDataBase_GetBodyText(virt_ptr<const UploadedDataBase> self,\n                             virt_ptr<char16_t> buffer,\n                             uint32_t bufferSize);\n\nnn::Result\nUploadedDataBase_GetBodyMemo(virt_ptr<const UploadedDataBase> self,\n                             virt_ptr<uint8_t> buffer,\n                             virt_ptr<uint32_t> outMemoSize,\n                             uint32_t bufferSize);\n\nnn::Result\nUploadedDataBase_GetCommonData(virt_ptr<const UploadedDataBase> self,\n                               virt_ptr<uint32_t> unk,\n                               virt_ptr<uint8_t> buffer,\n                               virt_ptr<uint32_t> outDataSize,\n                               uint32_t bufferSize);\n\nint32_t\nUploadedDataBase_GetFeeling(virt_ptr<const UploadedDataBase> self);\n\nvirt_ptr<const char>\nUploadedDataBase_GetPostId(virt_ptr<const UploadedDataBase> self);\n\nbool\nUploadedDataBase_TestFlags(virt_ptr<const UploadedDataBase> self,\n                           uint32_t flag);\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_uploadedpostdata.cpp",
    "content": "#include \"nn_olv.h\"\n#include \"nn_olv_uploadedpostdata.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n\nnamespace cafe::nn_olv\n{\n\nvirt_ptr<ghs::VirtualTable> UploadedPostData::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> UploadedPostData::TypeDescriptor = nullptr;\n\nvirt_ptr<UploadedPostData>\nUploadedPostData_Constructor(virt_ptr<UploadedPostData> self)\n{\n   if (!self) {\n      self = virt_cast<UploadedPostData *>(ghs::malloc(sizeof(UploadedPostData)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   UploadedDataBase_Constructor(virt_cast<UploadedDataBase *>(self));\n   self->virtualTable = UploadedPostData::VirtualTable;\n   return self;\n}\n\nvoid\nUploadedPostData_Destructor(virt_ptr<UploadedPostData> self,\n                            ghs::DestructorFlags flags)\n{\n   if (!self) {\n      return;\n   }\n\n   if (flags & ghs::DestructorFlags::FreeMemory) {\n      ghs::free(self);\n   }\n}\n\nuint32_t\nUploadedPostData_GetAppDataSize(virt_ptr<const UploadedPostData> self)\n{\n   return UploadedDataBase_GetAppDataSize(virt_cast<const UploadedDataBase *>(self));\n}\n\nnn::Result\nUploadedPostData_GetAppData(virt_ptr<const UploadedPostData> self,\n                            virt_ptr<uint8_t> buffer,\n                            virt_ptr<uint32_t> outDataSize,\n                            uint32_t bufferSize)\n{\n   return UploadedDataBase_GetAppData(virt_cast<const UploadedDataBase *>(self),\n                                      buffer, outDataSize, bufferSize);\n}\n\nnn::Result\nUploadedPostData_GetBodyText(virt_ptr<const UploadedPostData> self,\n                             virt_ptr<char16_t> buffer,\n                             uint32_t bufferSize)\n{\n   return UploadedDataBase_GetBodyText(virt_cast<const UploadedDataBase *>(self),\n                                       buffer, bufferSize);\n}\n\nnn::Result\nUploadedPostData_GetBodyMemo(virt_ptr<const UploadedPostData> self,\n                             virt_ptr<uint8_t> buffer,\n                             virt_ptr<uint32_t> outMemoSize,\n                             uint32_t bufferSize)\n{\n   return UploadedDataBase_GetBodyMemo(virt_cast<const UploadedDataBase *>(self),\n                                       buffer, outMemoSize, bufferSize);\n}\n\nint32_t\nUploadedPostData_GetFeeling(virt_ptr<const UploadedPostData> self)\n{\n   return UploadedDataBase_GetFeeling(virt_cast<const UploadedDataBase *>(self));\n}\n\nvirt_ptr<const char>\nUploadedPostData_GetPostId(virt_ptr<const UploadedPostData> self)\n{\n   return UploadedDataBase_GetPostId(virt_cast<const UploadedDataBase *>(self));\n}\n\nbool\nUploadedPostData_TestFlags(virt_ptr<const UploadedPostData> self,\n                           uint32_t flag)\n{\n   return UploadedDataBase_TestFlags(virt_cast<const UploadedDataBase *>(self),\n                                     flag);\n}\n\nvoid\nLibrary::registerUploadedPostDataSymbols()\n{\n   RegisterFunctionExportName(\"__ct__Q3_2nn3olv16UploadedPostDataFv\",\n                              UploadedPostData_Constructor);\n   RegisterFunctionExportName(\"__dt__Q3_2nn3olv16UploadedPostDataFv\",\n                              UploadedPostData_Destructor);\n   RegisterFunctionExportName(\"GetAppDataSize__Q3_2nn3olv16UploadedPostDataCFv\",\n                              UploadedPostData_GetAppDataSize);\n   RegisterFunctionExportName(\"GetAppData__Q3_2nn3olv16UploadedPostDataCFPUcPUiUi\",\n                              UploadedPostData_GetAppData);\n   RegisterFunctionExportName(\"GetBodyMemo__Q3_2nn3olv16UploadedPostDataCFPUcPUiUi\",\n                              UploadedPostData_GetBodyMemo);\n   RegisterFunctionExportName(\"GetBodyText__Q3_2nn3olv16UploadedPostDataCFPwUi\",\n                              UploadedPostData_GetBodyText);\n   RegisterFunctionExportName(\"GetFeeling__Q3_2nn3olv16UploadedPostDataCFv\",\n                              UploadedPostData_GetFeeling);\n   RegisterFunctionExportName(\"GetPostId__Q3_2nn3olv16UploadedPostDataCFv\",\n                              UploadedPostData_GetPostId);\n   RegisterFunctionExportName(\"TestFlags__Q3_2nn3olv16UploadedPostDataCFUi\",\n                              UploadedPostData_TestFlags);\n\n   RegisterTypeInfo(\n      UploadedPostData,\n      \"nn::olv::UploadedPostData\",\n      {\n         \"__dt__Q3_2nn3olv16UploadedPostDataFv\",\n      },\n      {\n         \"nn::olv::UploadedDataBase\",\n      });\n}\n\n}  // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_olv/nn_olv_uploadedpostdata.h",
    "content": "#pragma once\n#include \"nn_olv_uploadeddatabase.h\"\n\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_olv\n{\n\nstruct UploadedPostData : public UploadedDataBase\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   UNKNOWN(0x200);\n};\nCHECK_SIZE(UploadedPostData, 0xC208);\n\nvirt_ptr<UploadedPostData>\nUploadedPostData_Constructor(virt_ptr<UploadedPostData> self);\n\nvoid\nUploadedPostData_Destructor(virt_ptr<UploadedPostData> self,\n                            ghs::DestructorFlags flags);\n\nuint32_t\nUploadedPostData_GetAppDataSize(virt_ptr<const UploadedPostData> self);\n\nnn::Result\nUploadedPostData_GetAppData(virt_ptr<const UploadedPostData> self,\n                            virt_ptr<uint8_t> buffer,\n                            virt_ptr<uint32_t> outDataSize,\n                            uint32_t bufferSize);\n\nnn::Result\nUploadedPostData_GetBodyText(virt_ptr<const UploadedPostData> self,\n                             virt_ptr<char16_t> buffer,\n                             uint32_t bufferSize);\n\nnn::Result\nUploadedPostData_GetBodyMemo(virt_ptr<const UploadedPostData> self,\n                             virt_ptr<uint8_t> buffer,\n                             virt_ptr<uint32_t> outMemoSize,\n                             uint32_t bufferSize);\n\nint32_t\nUploadedPostData_GetFeeling(virt_ptr<const UploadedPostData> self);\n\nvirt_ptr<const char>\nUploadedPostData_GetPostId(virt_ptr<const UploadedPostData> self);\n\nbool\nUploadedPostData_TestFlags(virt_ptr<const UploadedPostData> self,\n                           uint32_t flag);\n\n} // namespace cafe::nn_olv\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm.cpp",
    "content": "#include \"nn_pdm.h\"\n\nnamespace cafe::nn_pdm\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerClientSymbols();\n   registerCosServiceSymbols();\n}\n\n} // namespace cafe::nn_pdm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_pdm\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_pdm, \"nn_pdm.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerClientSymbols();\n   void registerCosServiceSymbols();\n};\n\n} // namespace cafe::nn_pdm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm_client.cpp",
    "content": "#include \"nn_pdm.h\"\n#include \"nn_pdm_client.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/pdm/nn_pdm_result.h\"\n\nusing namespace cafe::coreinit;\nusing namespace nn::pdm;\n\nnamespace cafe::nn_pdm\n{\n\nstruct StaticClientData\n{\n   StaticClientData()\n   {\n      OSInitMutex(virt_addrof(mutex));\n   }\n\n   alignas(64) be2_array<uint8_t, 4096> allocatorMemory;\n\n   be2_struct<OSMutex> mutex;\n   be2_val<uint32_t> refCount = 0u;\n   be2_struct<nn::ipc::Client> client;\n   be2_struct<nn::ipc::BufferAllocator> allocator;\n};\n\nstatic virt_ptr<StaticClientData> sClientData = nullptr;\n\nnn::Result\nPDMInitialize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount == 0) {\n      sClientData->client.initialise(make_stack_string(\"/dev/pdm\"));\n      sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory),\n                                        sClientData->allocatorMemory.size());\n   }\n\n   sClientData->refCount++;\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n   return ResultSuccess;\n}\n\nvoid\nPDMFinalize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount > 0) {\n      sClientData->refCount--;\n      if (sClientData->refCount == 0) {\n         sClientData->client.close();\n      }\n   }\n\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n}\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient()\n{\n   return virt_addrof(sClientData->client);\n}\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator()\n{\n   return virt_addrof(sClientData->allocator);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerClientSymbols()\n{\n   RegisterFunctionExport(PDMInitialize);\n   RegisterFunctionExport(PDMFinalize);\n\n   RegisterFunctionExportName(\"Initialize__Q2_2nn3pdmFv\", PDMInitialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn3pdmFv\", PDMFinalize);\n\n   RegisterDataInternal(sClientData);\n}\n\n}  // namespace cafe::nn_pdm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm_client.h",
    "content": "#pragma once\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"cafe/nn/cafe_nn_ipc_bufferallocator.h\"\n\n#include \"nn/nn_result.h\"\n\nnamespace cafe::nn_pdm\n{\n\nnn::Result\nPDMInitialize();\n\nvoid\nPDMFinalize();\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient();\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator();\n\n} // namespace internal\n\n} // namespace cafe::nn_pdm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm_cosservice.cpp",
    "content": "#include \"nn_pdm.h\"\n#include \"nn_pdm_client.h\"\n#include \"nn_pdm_cosservice.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/pdm/nn_pdm_result.h\"\n#include \"nn/pdm/nn_pdm_cosservice.h\"\n\nusing namespace nn::ipc;\nusing namespace nn::pdm;\n\nnamespace cafe::nn_pdm\n{\n\nnn::Result\nPDMGetPlayDiaryMaxLength(virt_ptr<uint32_t> outMaxLength)\n{\n   auto command = ClientCommand<services::CosService::GetPlayDiaryMaxLength> { internal::getAllocator() };\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.failed()) {\n      return result;\n   }\n\n   auto maxLength = uint32_t { 0 };\n   result = command.readResponse(maxLength);\n   if (result.ok()) {\n      *outMaxLength = maxLength;\n   }\n\n   return result;\n}\n\nnn::Result\nPDMGetPlayStatsMaxLength(virt_ptr<uint32_t> outMaxLength)\n{\n   auto command = ClientCommand<services::CosService::GetPlayStatsMaxLength> { internal::getAllocator() };\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.failed()) {\n      return result;\n   }\n\n   auto maxLength = uint32_t { 0 };\n   result = command.readResponse(maxLength);\n   if (result.ok()) {\n      *outMaxLength = maxLength;\n   }\n\n   return result;\n}\n\nvoid\nLibrary::registerCosServiceSymbols()\n{\n   RegisterFunctionExportName(\"GetPlayDiaryMaxLength__Q2_2nn3pdmFPi\",\n                              PDMGetPlayDiaryMaxLength);\n   RegisterFunctionExportName(\"GetPlayStatsMaxLength__Q2_2nn3pdmFPi\",\n                              PDMGetPlayStatsMaxLength);\n}\n\n}  // namespace cafe::nn_pdm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_pdm/nn_pdm_cosservice.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n#include \"nn/pdm/nn_pdm_cosservice.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_pdm\n{\n\nnn::Result\nPDMGetPlayDiaryMaxLength(virt_ptr<uint32_t> outMaxLength);\n\nnn::Result\nPDMGetPlayStatsMaxLength(virt_ptr<uint32_t> outMaxLength);\n\n}  // namespace cafe::nn_pdm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_save/nn_save.cpp",
    "content": "#include \"nn_save.h\"\n\nnamespace cafe::nn_save\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerLibraryDependency(\"coreinit\");\n   registerLibraryDependency(\"nn_act\");\n   registerLibraryDependency(\"nn_acp\");\n\n   registerCmdSymbols();\n   registerPathSymbols();\n}\n\n} // namespace cafe::nn_save\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_save/nn_save.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_save\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_save, \"nn_save.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerCmdSymbols();\n   void registerPathSymbols();\n};\n\n} // namespace cafe::nn_save\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_save/nn_save_cmd.cpp",
    "content": "#include \"nn_save.h\"\n#include \"nn_save_cmd.h\"\n#include \"nn_save_path.h\"\n#include \"cafe/libraries/coreinit/coreinit_fs_cmd.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/cafe_stackobject.h\"\n\nnamespace cafe::nn_save\n{\n\nSaveStatus\nSAVEChangeGroupAndOthersMode(virt_ptr<FSClient> client,\n                             virt_ptr<FSCmdBlock> block,\n                             uint8_t account,\n                             virt_ptr<const char> path,\n                             uint32_t mode,\n                             FSErrorFlag errorMask)\n{\n   // TODO: SAVEChangeGroupAndOthersMode\n   decaf_warn_stub();\n   return SaveStatus::OK;\n}\n\nSaveStatus\nSAVEFlushQuota(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               uint8_t account,\n               FSErrorFlag errorMask)\n{\n   auto fsPath = internal::getSaveDirectory(account);\n\n   return FSFlushQuota(client,\n                       block,\n                       make_stack_string(fsPath.path()),\n                       errorMask);\n}\n\n\nSaveStatus\nSAVEFlushQuotaAsync(virt_ptr<FSClient> client,\n                    virt_ptr<FSCmdBlock> block,\n                    uint8_t account,\n                    FSErrorFlag errorMask,\n                    virt_ptr<FSAsyncData> asyncData)\n{\n   auto fsPath = internal::getSaveDirectory(account);\n\n   return FSFlushQuotaAsync(client,\n                            block,\n                            make_stack_string(fsPath.path()),\n                            errorMask,\n                            asyncData);\n}\n\n\nSaveStatus\nSAVEGetFreeSpaceSize(virt_ptr<FSClient> client,\n                     virt_ptr<FSCmdBlock> block,\n                     uint8_t account,\n                     virt_ptr<uint64_t> freeSpace,\n                     FSErrorFlag errorMask)\n{\n   auto fsPath = internal::getSaveDirectory(account);\n\n   return FSGetFreeSpaceSize(client,\n                             block,\n                             make_stack_string(fsPath.path()),\n                             freeSpace,\n                             errorMask);\n}\n\n\nSaveStatus\nSAVEGetFreeSpaceSizeAsync(virt_ptr<FSClient> client,\n                          virt_ptr<FSCmdBlock> block,\n                          uint8_t account,\n                          virt_ptr<uint64_t> freeSpace,\n                          FSErrorFlag errorMask,\n                          virt_ptr<FSAsyncData> asyncData)\n{\n   auto fsPath = internal::getSaveDirectory(account);\n\n   return FSGetFreeSpaceSizeAsync(client,\n                                  block,\n                                  make_stack_string(fsPath.path()),\n                                  freeSpace,\n                                  errorMask,\n                                  asyncData);\n}\n\n\nSaveStatus\nSAVEGetStat(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            uint8_t account,\n            virt_ptr<const char> path,\n            virt_ptr<FSStat> stat,\n            FSErrorFlag errorMask)\n{\n   auto fsPath = internal::getSavePath(account, path.get());\n\n   return FSGetStat(client,\n                    block,\n                    make_stack_string(fsPath.path()),\n                    stat,\n                    errorMask);\n}\n\n\nSaveStatus\nSAVEGetStatAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 uint8_t account,\n                 virt_ptr<const char> path,\n                 virt_ptr<FSStat> stat,\n                 FSErrorFlag errorMask,\n                 virt_ptr<FSAsyncData> asyncData)\n{\n   auto fsPath = internal::getSavePath(account, path.get());\n\n   return FSGetStatAsync(client,\n                         block,\n                         make_stack_string(fsPath.path()),\n                         stat,\n                         errorMask,\n                         asyncData);\n}\n\nSaveStatus\nSAVEGetStatOtherApplication(virt_ptr<FSClient> client,\n                            virt_ptr<FSCmdBlock> block,\n                            uint64_t titleId,\n                            uint8_t account,\n                            virt_ptr<const char> path,\n                            virt_ptr<FSStat> stat,\n                            FSErrorFlag errorMask)\n{\n   auto fsPath = internal::getTitleSavePath(titleId, account, path.get());\n\n   return FSGetStat(client,\n                    block,\n                    make_stack_string(fsPath.path()),\n                    stat,\n                    errorMask);\n}\n\nSaveStatus\nSAVEGetStatOtherApplicationAsync(virt_ptr<FSClient> client,\n                                 virt_ptr<FSCmdBlock> block,\n                                 uint64_t titleId,\n                                 uint8_t account,\n                                 virt_ptr<const char> path,\n                                 virt_ptr<FSStat> stat,\n                                 FSErrorFlag errorMask,\n                                 virt_ptr<FSAsyncData> asyncData)\n{\n   auto fsPath = internal::getTitleSavePath(titleId, account, path.get());\n\n   return FSGetStatAsync(client,\n                         block,\n                         make_stack_string(fsPath.path()),\n                         stat,\n                         errorMask,\n                         asyncData);\n}\n\n\nSaveStatus\nSAVEMakeDir(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            uint8_t account,\n            virt_ptr<const char> path,\n            FSErrorFlag errorMask)\n{\n   auto fsPath = internal::getSavePath(account, path.get());\n\n   return FSMakeDir(client,\n                    block,\n                    make_stack_string(fsPath.path()),\n                    errorMask);\n}\n\n\nSaveStatus\nSAVEMakeDirAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 uint8_t account,\n                 virt_ptr<const char> path,\n                 FSErrorFlag errorMask,\n                 virt_ptr<FSAsyncData> asyncData)\n{\n   auto fsPath = internal::getSavePath(account, path.get());\n\n   return FSMakeDirAsync(client,\n                         block,\n                         make_stack_string(fsPath.path()),\n                         errorMask,\n                         asyncData);\n}\n\n\nSaveStatus\nSAVEOpenDir(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            uint8_t account,\n            virt_ptr<const char> path,\n            virt_ptr<FSDirHandle> handle,\n            FSErrorFlag errorMask)\n{\n   auto fsPath = internal::getSavePath(account, path.get());\n\n   return FSOpenDir(client,\n                    block,\n                    make_stack_string(fsPath.path()),\n                    handle,\n                    errorMask);\n}\n\n\nSaveStatus\nSAVEOpenDirAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 uint8_t account,\n                 virt_ptr<const char> path,\n                 virt_ptr<FSDirHandle> handle,\n                 FSErrorFlag errorMask,\n                 virt_ptr<FSAsyncData> asyncData)\n{\n   auto fsPath = internal::getSavePath(account, path.get());\n\n   return FSOpenDirAsync(client,\n                         block,\n                         make_stack_string(fsPath.path()),\n                         handle,\n                         errorMask,\n                         asyncData);\n}\n\n\nSaveStatus\nSAVEOpenFile(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             uint8_t account,\n             virt_ptr<const char> path,\n             virt_ptr<const char> mode,\n             virt_ptr<FSFileHandle> handle,\n             FSErrorFlag errorMask)\n{\n   auto fsPath = internal::getSavePath(account, path.get());\n\n   return FSOpenFile(client,\n                     block,\n                     make_stack_string(fsPath.path()),\n                     mode,\n                     handle,\n                     errorMask);\n}\n\n\nSaveStatus\nSAVEOpenFileAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  uint8_t account,\n                  virt_ptr<const char> path,\n                  virt_ptr<const char> mode,\n                  virt_ptr<FSFileHandle> handle,\n                  FSErrorFlag errorMask,\n                  virt_ptr<FSAsyncData> asyncData)\n{\n   auto fsPath = internal::getSavePath(account, path.get());\n\n   return FSOpenFileAsync(client,\n                          block,\n                          make_stack_string(fsPath.path()),\n                          mode,\n                          handle,\n                          errorMask,\n                          asyncData);\n}\n\n\nSaveStatus\nSAVERemove(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           uint8_t account,\n           virt_ptr<const char> path,\n           FSErrorFlag errorMask)\n{\n   auto fsPath = internal::getSavePath(account, path.get());\n\n   return FSRemove(client,\n                   block,\n                   make_stack_string(fsPath.path()),\n                   errorMask);\n}\n\n\nSaveStatus\nSAVERemoveAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                uint8_t account,\n                virt_ptr<const char> path,\n                FSErrorFlag errorMask,\n                virt_ptr<FSAsyncData> asyncData)\n{\n   auto fsPath = internal::getSavePath(account, path.get());\n\n   return FSRemoveAsync(client,\n                        block,\n                        make_stack_string(fsPath.path()),\n                        errorMask,\n                        asyncData);\n}\n\n\nSaveStatus\nSAVERename(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           uint8_t account,\n           virt_ptr<const char> src,\n           virt_ptr<const char> dst,\n           FSErrorFlag errorMask)\n{\n   auto srcPath = internal::getSavePath(account, src.get());\n   auto dstPath = internal::getSavePath(account, dst.get());\n\n   return FSRename(client,\n                   block,\n                   make_stack_string(srcPath.path()),\n                   make_stack_string(dstPath.path()),\n                   errorMask);\n}\n\n\nSaveStatus\nSAVERenameAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                uint8_t account,\n                virt_ptr<const char> src,\n                virt_ptr<const char> dst,\n                FSErrorFlag errorMask,\n                virt_ptr<FSAsyncData> asyncData)\n{\n   auto srcPath = internal::getSavePath(account, src.get());\n   auto dstPath = internal::getSavePath(account, dst.get());\n\n   return FSRenameAsync(client,\n                        block,\n                        make_stack_string(srcPath.path()),\n                        make_stack_string(dstPath.path()),\n                        errorMask,\n                        asyncData);\n}\n\nvoid\nLibrary::registerCmdSymbols()\n{\n   RegisterFunctionExport(SAVEChangeGroupAndOthersMode);\n   RegisterFunctionExport(SAVEFlushQuotaAsync);\n   RegisterFunctionExport(SAVEFlushQuota);\n   RegisterFunctionExport(SAVEGetFreeSpaceSizeAsync);\n   RegisterFunctionExport(SAVEGetFreeSpaceSize);\n   RegisterFunctionExport(SAVEGetStatAsync);\n   RegisterFunctionExport(SAVEGetStat);\n   RegisterFunctionExport(SAVEGetStatOtherApplication);\n   RegisterFunctionExport(SAVEGetStatOtherApplicationAsync);\n   RegisterFunctionExport(SAVEMakeDir);\n   RegisterFunctionExport(SAVEMakeDirAsync);\n   RegisterFunctionExport(SAVEOpenDir);\n   RegisterFunctionExport(SAVEOpenDirAsync);\n   RegisterFunctionExport(SAVEOpenFile);\n   RegisterFunctionExport(SAVEOpenFileAsync);\n   RegisterFunctionExport(SAVERemoveAsync);\n   RegisterFunctionExport(SAVERemove);\n   RegisterFunctionExport(SAVERenameAsync);\n   RegisterFunctionExport(SAVERename);\n}\n\n} // namespace cafe::nn_save\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_save/nn_save_cmd.h",
    "content": "#pragma once\n#include \"cafe/libraries/coreinit/coreinit_fs.h\"\n#include \"nn_save_path.h\"\n\nnamespace cafe::nn_save\n{\n\nusing coreinit::FSAsyncData;\nusing coreinit::FSClient;\nusing coreinit::FSCmdBlock;\nusing coreinit::FSDirHandle;\nusing coreinit::FSErrorFlag;\nusing coreinit::FSFileHandle;\nusing coreinit::FSStat;\n\nSaveStatus\nSAVEChangeGroupAndOthersMode(virt_ptr<FSClient> client,\n                             virt_ptr<FSCmdBlock> block,\n                             uint8_t account,\n                             virt_ptr<const char> path,\n                             uint32_t mode,\n                             FSErrorFlag errorMask);\n\nSaveStatus\nSAVEFlushQuota(virt_ptr<FSClient> client,\n               virt_ptr<FSCmdBlock> block,\n               uint8_t account,\n               FSErrorFlag errorMask);\n\nSaveStatus\nSAVEFlushQuotaAsync(virt_ptr<FSClient> client,\n                    virt_ptr<FSCmdBlock> block,\n                    uint8_t account,\n                    FSErrorFlag errorMask,\n                    virt_ptr<FSAsyncData> asyncData);\n\nSaveStatus\nSAVEGetFreeSpaceSize(virt_ptr<FSClient> client,\n                     virt_ptr<FSCmdBlock> block,\n                     uint8_t account,\n                     virt_ptr<uint64_t> freeSpace,\n                     FSErrorFlag errorMask);\n\nSaveStatus\nSAVEGetFreeSpaceSizeAsync(virt_ptr<FSClient> client,\n                          virt_ptr<FSCmdBlock> block,\n                          uint8_t account,\n                          virt_ptr<uint64_t> freeSpace,\n                          FSErrorFlag errorMask,\n                          virt_ptr<FSAsyncData> asyncData);\n\nSaveStatus\nSAVEGetStat(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            uint8_t account,\n            virt_ptr<const char> path,\n            virt_ptr<FSStat> stat,\n            FSErrorFlag errorMask);\n\nSaveStatus\nSAVEGetStatAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 uint8_t account,\n                 virt_ptr<const char> path,\n                 virt_ptr<FSStat> stat,\n                 FSErrorFlag errorMask,\n                 virt_ptr<FSAsyncData> asyncData);\n\nSaveStatus\nSAVEGetStatOtherApplication(virt_ptr<FSClient> client,\n                            virt_ptr<FSCmdBlock> block,\n                            uint64_t titleId,\n                            uint8_t account,\n                            virt_ptr<const char> path,\n                            virt_ptr<FSStat> stat,\n                            FSErrorFlag errorMask);\n\nSaveStatus\nSAVEGetStatOtherApplicationAsync(virt_ptr<FSClient> client,\n                                 virt_ptr<FSCmdBlock> block,\n                                 uint64_t titleId,\n                                 uint8_t account,\n                                 virt_ptr<const char> path,\n                                 virt_ptr<FSStat> stat,\n                                 FSErrorFlag errorMask,\n                                 virt_ptr<FSAsyncData> asyncData);\n\nSaveStatus\nSAVEMakeDir(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            uint8_t account,\n            virt_ptr<const char> path,\n            FSErrorFlag errorMask);\n\nSaveStatus\nSAVEMakeDirAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 uint8_t account,\n                 virt_ptr<const char> path,\n                 FSErrorFlag errorMask,\n                 virt_ptr<FSAsyncData> asyncData);\n\nSaveStatus\nSAVEOpenDir(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            uint8_t account,\n            virt_ptr<const char> path,\n            virt_ptr<FSDirHandle> handle,\n            FSErrorFlag errorMask);\n\nSaveStatus\nSAVEOpenDirAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 uint8_t account,\n                 virt_ptr<const char> path,\n                 virt_ptr<FSDirHandle> handle,\n                 FSErrorFlag errorMask,\n                 virt_ptr<FSAsyncData> asyncData);\n\nSaveStatus\nSAVEOpenFile(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             uint8_t account,\n             virt_ptr<const char> path,\n             virt_ptr<const char> mode,\n             virt_ptr<FSFileHandle> handle,\n             FSErrorFlag errorMask);\n\nSaveStatus\nSAVEOpenFileAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  uint8_t account,\n                  virt_ptr<const char> path,\n                  virt_ptr<const char> mode,\n                  virt_ptr<FSFileHandle> handle,\n                  FSErrorFlag errorMask,\n                  virt_ptr<FSAsyncData> asyncData);\n\nSaveStatus\nSAVERemove(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           uint8_t account,\n           virt_ptr<const char> path,\n           FSErrorFlag errorMask);\n\nSaveStatus\nSAVERemoveAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                uint8_t account,\n                virt_ptr<const char> path,\n                FSErrorFlag errorMask,\n                virt_ptr<FSAsyncData> asyncData);\n\nSaveStatus\nSAVERename(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           uint8_t account,\n           virt_ptr<const char> src,\n           virt_ptr<const char> dst,\n           FSErrorFlag errorMask);\n\nSaveStatus\nSAVERenameAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                uint8_t account,\n                virt_ptr<const char> src,\n                virt_ptr<const char> dst,\n                FSErrorFlag errorMask,\n                virt_ptr<FSAsyncData> asyncData);\n\n} // namespace cafe::nn_save\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_save/nn_save_path.cpp",
    "content": "#include \"nn_save.h\"\n#include \"nn_save_path.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_fs_client.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n#include \"cafe/libraries/coreinit/coreinit_osreport.h\"\n#include \"cafe/libraries/nn_act/nn_act_client.h\"\n#include \"cafe/libraries/nn_act/nn_act_clientstandardservice.h\"\n#include \"cafe/libraries/nn_acp/nn_acp_client.h\"\n#include \"cafe/libraries/nn_acp/nn_acp_saveservice.h\"\n\n#include <fmt/core.h>\n\nusing namespace cafe::coreinit;\nusing namespace nn::act;\nusing namespace nn::acp;\n\nnamespace cafe::nn_save\n{\n\nstruct StaticSaveData\n{\n   be2_val<bool> initialised;\n   be2_struct<OSMutex> mutex;\n   be2_struct<FSClient> fsClient;\n   be2_struct<FSCmdBlock> fsCmdBlock;\n   be2_array<uint32_t, NumSlots> persistentIdCache;\n};\n\nstatic virt_ptr<StaticSaveData> sSaveData = nullptr;\n\nSaveStatus\nSAVEInit()\n{\n   if (sSaveData->initialised) {\n      return SaveStatus::OK;\n   }\n\n   OSInitMutex(virt_addrof(sSaveData->mutex));\n\n   nn_act::Initialize();\n   nn_acp::ACPInitialize();\n   FSAddClient(virt_addrof(sSaveData->fsClient), FSErrorFlag::None);\n   FSInitCmdBlock(virt_addrof(sSaveData->fsCmdBlock));\n\n   for (auto i = SlotNo { 1 }; i <= NumSlots; ++i) {\n      sSaveData->persistentIdCache[i - 1] = nn_act::GetPersistentIdEx(i);\n   }\n\n   // Mount external storage if it is required\n   if (OSGetUPID() == kernel::UniqueProcessId::Game) {\n      auto externalStorageRequired = StackObject<int32_t> { };\n      if (nn_acp::ACPIsExternalStorageRequired(externalStorageRequired) == nn_acp::ACPResult::Success &&\n          *externalStorageRequired) {\n         nn_acp::ACPMountExternalStorage();\n      }\n   }\n\n   nn_acp::ACPMountSaveDir();\n   nn_acp::ACPRepairSaveMetaDir();\n   sSaveData->initialised = true;\n   return SaveStatus::OK;\n}\n\nvoid\nSAVEShutdown()\n{\n   if (sSaveData->initialised) {\n      FSDelClient(virt_addrof(sSaveData->fsClient), FSErrorFlag::None);\n      nn_acp::ACPFinalize();\n      nn_act::Finalize();\n      sSaveData->initialised = false;\n   }\n}\n\nSaveStatus\nSAVEInitSaveDir(uint8_t slotNo)\n{\n   if (!sSaveData->initialised) {\n      coreinit::internal::OSPanic(\"save.cpp\", 630,\n         \"SAVE library is not initialized. Call SAVEInit() prior to this function.\\n\");\n   }\n\n   auto persistentId = uint32_t { 0 };\n   if (!internal::getPersistentId(slotNo, persistentId)) {\n      return SaveStatus::NotFound;\n   }\n\n   auto result = nn_acp::ACPCreateSaveDir(persistentId,\n                                          ACPDeviceType::Unknown1);\n   // TODO: Update ACPGetApplicationBox\n   return internal::translateResult(result);\n}\n\n\nSaveStatus\nSAVEGetSharedDataTitlePath(uint64_t titleID,\n                           virt_ptr<const char> dir,\n                           virt_ptr<char> buffer,\n                           uint32_t bufferSize)\n{\n   if (!sSaveData->initialised) {\n      coreinit::internal::OSPanic(\"save.cpp\", 2543,\n                                  \"SAVE library is not initialized. Call SAVEInit() prior to this function.\\n\");\n   }\n\n   auto externalStorageRequired = StackObject<int32_t> { };\n   auto storage = \"storage_mlc01\";\n\n   if (nn_acp::ACPIsExternalStorageRequired(externalStorageRequired) == nn_acp::ACPResult::Success) {\n      if (*externalStorageRequired) {\n         storage = \"storage_hfiomlc01\";\n      }\n   } else {\n      return SaveStatus::FatalError;\n   }\n\n   auto titleLo = static_cast<uint32_t>(titleID & 0xffffffff);\n   auto titleHi = static_cast<uint32_t>(titleID >> 32);\n   auto result = std::snprintf(buffer.get(), bufferSize,\n                               \"/vol/%s/sys/title/%08x/%08x/content/%s\",\n                               storage, titleHi, titleLo, dir.get());\n\n   if (result < 0 || static_cast<uint32_t>(result) >= bufferSize) {\n      return SaveStatus::FatalError;\n   }\n\n   return SaveStatus::OK;\n}\n\n\nSaveStatus\nSAVEGetSharedSaveDataPath(uint64_t titleID,\n                          virt_ptr<const char> dir,\n                          virt_ptr<char> buffer,\n                          uint32_t bufferSize)\n{\n   if (!sSaveData->initialised) {\n      coreinit::internal::OSPanic(\"save.cpp\", 2543,\n                                  \"SAVE library is not initialized. Call SAVEInit() prior to this function.\\n\");\n   }\n\n   auto externalStorageRequired = StackObject<int32_t> { };\n   auto storage = \"storage_mlc01\";\n\n   if (nn_acp::ACPIsExternalStorageRequired(externalStorageRequired) == nn_acp::ACPResult::Success) {\n      if (*externalStorageRequired) {\n         storage = \"storage_hfiomlc01\";\n      }\n   } else {\n      return SaveStatus::FatalError;\n   }\n\n   auto titleLo = static_cast<uint32_t>(titleID & 0xffffffff);\n   auto titleHi = static_cast<uint32_t>(titleID >> 32);\n   auto result = std::snprintf(buffer.get(), bufferSize,\n                               \"/vol/%s/usr/save/%08x/%08x/user/common/%s\",\n                               storage, titleHi, titleLo, dir.get());\n\n   if (result < 0 || static_cast<uint32_t>(result) >= bufferSize) {\n      return SaveStatus::FatalError;\n   }\n\n   return SaveStatus::OK;\n}\n\n\nnamespace internal\n{\n\nSaveStatus\ntranslateResult(nn_acp::ACPResult result)\n{\n   // TODO: Reverse this completely\n   if (result == nn_acp::ACPResult::Success) {\n      return SaveStatus::OK;\n   }\n\n   if (result == nn_acp::ACPResult::NotFound ||\n       result == nn_acp::ACPResult::DirNotFound ||\n       result == nn_acp::ACPResult::FileNotFound){\n      return SaveStatus::NotFound;\n   }\n\n   if (result == nn_acp::ACPResult::DeviceFull) {\n      return SaveStatus::StorageFull;\n   }\n\n   return SaveStatus::MediaError;\n}\n\nbool\ngetPersistentId(SlotNo slot,\n                uint32_t &outPersistentId)\n{\n   if (slot == SystemSlot) {\n      outPersistentId = 0;\n      return true;\n   } else if (slot >= 1 && slot <= NumSlots) {\n      outPersistentId = sSaveData->persistentIdCache[slot - 1];\n      return true;\n   }\n\n   return false;\n}\n\nvfs::Path\ngetSaveDirectory(SlotNo slot)\n{\n   auto persistentId = uint32_t { 0 };\n   getPersistentId(slot, persistentId);\n\n   if (persistentId == 0) {\n      return fmt::format(\"/vol/save/common\");\n   } else {\n      return fmt::format(\"/vol/save/{:08X}\", persistentId);\n   }\n}\n\nvfs::Path\ngetSavePath(SlotNo slot,\n            std::string_view path)\n{\n   return getSaveDirectory(slot) / path;\n}\n\nvfs::Path\ngetTitleSaveDirectory(uint64_t title,\n                      SlotNo slot)\n{\n   auto titleLo = static_cast<uint32_t>(title & 0xffffffff);\n   auto titleHi = static_cast<uint32_t>(title >> 32);\n   auto persistentId = uint32_t { 0 };\n   getPersistentId(slot, persistentId);\n\n   if (persistentId == 0) {\n      return fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user/common\",\n                         titleHi, titleLo);\n   } else {\n      return fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user/{:08X}\",\n                         titleHi, titleLo, persistentId);\n   }\n}\n\nvfs::Path\ngetTitleSavePath(uint64_t title,\n                 nn_act::SlotNo slot,\n                 std::string_view path)\n{\n   return getTitleSaveDirectory(title, slot) / path;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerPathSymbols()\n{\n   RegisterFunctionExport(SAVEInit);\n   RegisterFunctionExport(SAVEShutdown);\n   RegisterFunctionExport(SAVEInitSaveDir);\n   RegisterFunctionExport(SAVEGetSharedDataTitlePath);\n   RegisterFunctionExport(SAVEGetSharedSaveDataPath);\n\n   RegisterDataInternal(sSaveData);\n}\n\n} // namespace cafe::nn_save\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_save/nn_save_path.h",
    "content": "#pragma once\n#include \"cafe/libraries/coreinit/coreinit_fs.h\"\n#include \"cafe/libraries/nn_acp/nn_acp_acpresult.h\"\n#include \"nn/nn_result.h\"\n#include \"nn/act/nn_act_types.h\"\n#include \"vfs/vfs_path.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::nn_save\n{\n\nusing SaveStatus = coreinit::FSStatus;\n\nSaveStatus\nSAVEInit();\n\nvoid\nSAVEShutdown();\n\nSaveStatus\nSAVEInitSaveDir(uint8_t userID);\n\nSaveStatus\nSAVEGetSharedDataTitlePath(uint64_t titleID,\n                           virt_ptr<const char> dir,\n                           virt_ptr<char> buffer,\n                           uint32_t bufferSize);\n\nSaveStatus\nSAVEGetSharedSaveDataPath(uint64_t titleID,\n                          virt_ptr<const char> dir,\n                          virt_ptr<char> buffer,\n                          uint32_t bufferSize);\n\nnamespace internal\n{\n\nSaveStatus\ntranslateResult(nn_acp::ACPResult result);\n\nbool\ngetPersistentId(nn::act::SlotNo slot,\n                uint32_t &outPersistentId);\n\nvfs::Path\ngetSaveDirectory(nn::act::SlotNo slot);\n\nvfs::Path\ngetSavePath(nn::act::SlotNo slot,\n            std::string_view path);\n\nvfs::Path\ngetTitleSaveDirectory(uint64_t title,\n                      nn::act::SlotNo slot);\n\nvfs::Path\ngetTitleSavePath(uint64_t title,\n                 nn::act::SlotNo slot,\n                 std::string_view path);\n\n} // namespace internal\n\n} // namespace cafe::nn_save\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_sl/nn_sl.cpp",
    "content": "#include \"nn_sl.h\"\n\nnamespace cafe::nn_sl\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerDrcTransferrerSymbols();\n   registerLibSymbols();\n}\n\n} // namespace cafe::nn_sl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_sl/nn_sl.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_sl\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_sl, \"nn_sl.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerDrcTransferrerSymbols();\n   void registerLibSymbols();\n};\n\n} // namespace cafe::nn_sl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_sl/nn_sl_drctransferrer.cpp",
    "content": "#include \"nn_sl.h\"\n#include \"nn_sl_drctransferrer.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/ghs/cafe_ghs_malloc.h\"\n\nnamespace cafe::nn_sl\n{\n\nvirt_ptr<ghs::TypeIDStorage> ITransferrer::TypeID = nullptr;\nvirt_ptr<ghs::TypeDescriptor> ITransferrer::TypeDescriptor = nullptr;\n\nvirt_ptr<ghs::VirtualTable> DrcTransferrer::VirtualTable = nullptr;\nvirt_ptr<ghs::TypeDescriptor> DrcTransferrer::TypeDescriptor = nullptr;\n\nvirt_ptr<DrcTransferrer>\nDrcTransferrer_Constructor(virt_ptr<DrcTransferrer> self)\n{\n   if (!self) {\n      self = virt_cast<DrcTransferrer *>(ghs::malloc(sizeof(DrcTransferrer)));\n      if (!self) {\n         return nullptr;\n      }\n   }\n\n   self->virtualTable = DrcTransferrer::VirtualTable;\n   return self;\n}\n\nvoid\nDrcTransferrer_Destructor(virt_ptr<DrcTransferrer> self,\n                          ghs::DestructorFlags flags)\n{\n   if (self && (flags & ghs::DestructorFlags::FreeMemory)) {\n      ghs::free(self);\n   }\n}\n\nnn::Result\nDrcTransferrer_Transfer1(virt_ptr<DrcTransferrer> self,\n                         virt_ptr<void> arg1,\n                         uint32_t arg2,\n                         uint32_t arg3,\n                         bool arg4)\n{\n   return DrcTransferrer_Transfer2(self, arg1, arg2, arg3, arg4 ? 3 : 1);\n}\n\nnn::Result\nDrcTransferrer_Transfer2(virt_ptr<DrcTransferrer> self,\n                         virt_ptr<void> arg1,\n                         uint32_t arg2,\n                         uint32_t arg3,\n                         uint32_t arg4)\n{\n   decaf_warn_stub();\n   return { 0xA183ED80 };\n}\n\nnn::Result\nDrcTransferrer_AbortTransfer(virt_ptr<DrcTransferrer> self)\n{\n   decaf_warn_stub();\n   return { 0xA183ED80 };\n}\n\nnn::Result\nDrcTransferrer_UnkFunc4(virt_ptr<DrcTransferrer> self)\n{\n   // Not a stub, this is actual implementation!\n   return { 0xA183E800 };\n}\n\nnn::Result\nDrcTransferrer_DisplayNotification(virt_ptr<DrcTransferrer> self,\n                                   uint32_t arg2,\n                                   uint32_t arg3,\n                                   uint32_t arg4)\n{\n   decaf_warn_stub();\n   return { 0xA183E800 };\n}\n\nvoid\nLibrary::registerDrcTransferrerSymbols()\n{\n   RegisterFunctionInternalName(\"__dt__Q5_2nn2sl4core6detail14DrcTransferrerFv\",\n                                DrcTransferrer_Destructor);\n   RegisterFunctionInternalName(\"DrcTransferrer_Transfer1\",\n                                DrcTransferrer_Transfer1);\n   RegisterFunctionInternalName(\"DrcTransferrer_Transfer2\",\n                                DrcTransferrer_Transfer2);\n   RegisterFunctionInternalName(\"DrcTransferrer_AbortTransfer\",\n                                DrcTransferrer_AbortTransfer);\n   RegisterFunctionInternalName(\"DrcTransferrer_UnkFunc4\",\n                                DrcTransferrer_UnkFunc4);\n   RegisterFunctionInternalName(\"DrcTransferrer_DisplayNotification\",\n                                DrcTransferrer_DisplayNotification);\n   RegisterDataExportName(\"__TID_Q3_2nn2sl12ITransferrer\", ITransferrer::TypeID);\n\n   RegisterTypeInfo(\n      ITransferrer,\n      \"nn::sl::ITransferrer\",\n      {},\n      {},\n      \"__TID_Q3_2nn2sl12ITransferrer\");\n\n   RegisterTypeInfo(\n      DrcTransferrer,\n      \"nn::sl::core::detail::DrcTransferrer\",\n      {\n         \"__dt__Q5_2nn2sl4core6detail14DrcTransferrerFv\",\n         \"DrcTransferrer_Transfer1\",\n         \"DrcTransferrer_Transfer2\",\n         \"DrcTransferrer_AbortTransfer\",\n         \"DrcTransferrer_UnkFunc4\",\n         \"DrcTransferrer_DisplayNotification\",\n      },\n      {\n         \"nn::sl::ITransferrer\",\n      });\n}\n\n}  // namespace cafe::nn_sl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_sl/nn_sl_drctransferrer.h",
    "content": "#pragma once\n#include \"cafe/libraries/ghs/cafe_ghs_typeinfo.h\"\n#include \"nn/nn_result.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_sl\n{\n\nstruct ITransferrer\n{\n   static virt_ptr<ghs::TypeIDStorage> TypeID;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n   be2_virt_ptr<ghs::VirtualTable> virtualTable;\n};\nCHECK_OFFSET(ITransferrer, 0x00, virtualTable);\nCHECK_SIZE(ITransferrer, 0x4);\n\nstruct DrcTransferrer : public ITransferrer\n{\n   static virt_ptr<ghs::VirtualTable> VirtualTable;\n   static virt_ptr<ghs::TypeDescriptor> TypeDescriptor;\n\n};\nCHECK_SIZE(DrcTransferrer, 0x4);\n\nvirt_ptr<DrcTransferrer>\nDrcTransferrer_Constructor(virt_ptr<DrcTransferrer> self);\n\nvoid\nDrcTransferrer_Destructor(virt_ptr<DrcTransferrer> self,\n                          ghs::DestructorFlags flags);\n\nnn::Result\nDrcTransferrer_Transfer1(virt_ptr<DrcTransferrer> self,\n                         virt_ptr<void> arg1,\n                         uint32_t arg2,\n                         uint32_t arg3,\n                         bool arg4);\n\n\nnn::Result\nDrcTransferrer_Transfer2(virt_ptr<DrcTransferrer> self,\n                         virt_ptr<void> arg1,\n                         uint32_t arg2,\n                         uint32_t arg3,\n                         uint32_t arg4);\n\nnn::Result\nDrcTransferrer_AbortTransfer(virt_ptr<DrcTransferrer> self);\n\nnn::Result\nDrcTransferrer_UnkFunc4(virt_ptr<DrcTransferrer> self);\n\n}  // namespace cafe::nn_sl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_sl/nn_sl_lib.cpp",
    "content": "#include \"nn_sl.h\"\n#include \"nn_sl_lib.h\"\n#include \"nn_sl_drctransferrer.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/coreinit/coreinit_cosreport.h\"\n\nnamespace cafe::nn_sl\n{\n\nstruct StaticLibData\n{\n   StaticLibData()\n   {\n      DrcTransferrer_Constructor(virt_addrof(drcTransferrer));\n   }\n\n   be2_val<BOOL> initialised;\n   be2_struct<DrcTransferrer> drcTransferrer;\n\n   be2_val<AllocFn> allocFn;\n   be2_val<FreeFn> freeFn;\n   be2_virt_ptr<ITransferrer> transferrer;\n};\n\nstatic virt_ptr<StaticLibData>\nsLibData = nullptr;\n\nnn::Result\nInitialize(AllocFn allocFn,\n           FreeFn freeFn)\n{\n   return Initialize(allocFn, freeFn, virt_cast<ITransferrer *>(GetDrcTransferrer()));\n}\n\nnn::Result\nInitialize(AllocFn allocFn,\n           FreeFn freeFn,\n           virt_ptr<ITransferrer> transferrer)\n{\n   if (!sLibData->initialised) {\n      sLibData->allocFn = allocFn;\n      sLibData->freeFn = freeFn;\n      sLibData->transferrer = transferrer;\n      if (!transferrer) {\n         coreinit::internal::COSWarn(coreinit::COSReportModule::Unknown1,\n                                     \"NULL transferrer is set!\");\n      }\n\n      // nn::sl::FileSystemAccessor::Initialize(nn::sl::GetFileSystemAccessor())\n      sLibData->initialised = TRUE;\n   }\n\n   return nn::ResultSuccess;\n}\n\nnn::Result\nFinalize()\n{\n   if (sLibData->initialised) {\n      // nn::sl::FileSystemAccessor::Finalize(nn::sl::GetFileSystemAccessor())\n      sLibData->initialised = FALSE;\n   }\n\n   return nn::ResultSuccess;\n}\n\nvirt_ptr<DrcTransferrer>\nGetDrcTransferrer()\n{\n   return virt_addrof(sLibData->drcTransferrer);\n}\n\nvoid\nLibrary::registerLibSymbols()\n{\n   RegisterFunctionExportName(\n      \"Initialize__Q2_2nn2slFPFUiT1_PvPFPv_v\",\n      static_cast<nn::Result(*)(AllocFn,FreeFn)>(Initialize));\n   RegisterFunctionExportName(\n      \"Initialize__Q4_2nn2sl4core6detailFPFUiT1_PvPFPv_vRQ3_2nn2sl12ITransferrer\",\n      static_cast<nn::Result(*)(AllocFn, FreeFn, virt_ptr<ITransferrer>)>(Initialize));\n   RegisterFunctionExportName(\"Finalize__Q2_2nn2slFv\", Finalize);\n   RegisterFunctionExportName(\"GetDrcTransferrer__Q2_2nn2slFv\", GetDrcTransferrer);\n\n   RegisterDataInternal(sLibData);\n}\n\n}  // namespace namespace cafe::nn_sl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_sl/nn_sl_lib.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nn_sl\n{\n\nusing AllocFn = virt_func_ptr<virt_ptr<void> (uint32_t size, uint32_t align)>;\nusing FreeFn = virt_func_ptr<void (virt_ptr<void> ptr)>;\n\nstruct ITransferrer;\nstruct DrcTransferrer;\n\nnn::Result\nInitialize(AllocFn alloc, FreeFn free);\n\nnn::Result\nInitialize(AllocFn alloc, FreeFn free, virt_ptr<ITransferrer> transferrer);\n\nnn::Result\nFinalize();\n\nvirt_ptr<DrcTransferrer>\nGetDrcTransferrer();\n\n}  // namespace namespace cafe::nn_sl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_spm/nn_spm.cpp",
    "content": "#include \"nn_spm.h\"\n\nnamespace cafe::nn_spm\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerClientSymbols();\n   registerExtendedStorageServiceSymbols();\n}\n\n} // namespace cafe::nn_spm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_spm/nn_spm.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_spm\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_spm, \"nn_spm.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerClientSymbols();\n   void registerExtendedStorageServiceSymbols();\n};\n\n} // namespace cafe::nn_spm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_spm/nn_spm_client.cpp",
    "content": "#include \"nn_spm.h\"\n#include \"nn_spm_client.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::nn_spm\n{\n\nstruct StaticClientData\n{\n   StaticClientData()\n   {\n      OSInitMutex(virt_addrof(mutex));\n   }\n\n   alignas(64) be2_array<uint8_t, 0x4000> allocatorMemory;\n\n   be2_struct<OSMutex> mutex;\n   be2_val<uint32_t> refCount = 0u;\n   be2_struct<nn::ipc::Client> client;\n   be2_struct<nn::ipc::BufferAllocator> allocator;\n};\n\nstatic virt_ptr<StaticClientData> sClientData = nullptr;\n\nnn::Result\nInitialize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount == 0) {\n      sClientData->client.initialise(make_stack_string(\"/dev/acp_main\"));\n      sClientData->allocator.initialise(virt_addrof(sClientData->allocatorMemory),\n                                        sClientData->allocatorMemory.size());\n   }\n\n   sClientData->refCount++;\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n   return nn::ResultSuccess;\n}\n\nvoid\nFinalize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount > 0) {\n      sClientData->refCount--;\n\n      if (sClientData->refCount == 0) {\n         sClientData->client.close();\n      }\n   }\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n}\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient()\n{\n   return virt_addrof(sClientData->client);\n}\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator()\n{\n   return virt_addrof(sClientData->allocator);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerClientSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn3spmFv\", Initialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn3spmFv\", Finalize);\n\n   RegisterDataInternal(sClientData);\n}\n\n}  // namespace cafe::nn_spm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_spm/nn_spm_client.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"cafe/nn/cafe_nn_ipc_bufferallocator.h\"\n\nnamespace cafe::nn_spm\n{\n\nnn::Result\nInitialize();\n\nvoid\nFinalize();\n\nnamespace internal\n{\n\nvirt_ptr<nn::ipc::Client>\ngetClient();\n\nvirt_ptr<nn::ipc::BufferAllocator>\ngetAllocator();\n\n} // namespace internal\n\n} // namespace cafe::nn_spm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_spm/nn_spm_extendedstorageservice.cpp",
    "content": "#include \"nn_spm.h\"\n#include \"nn_spm_client.h\"\n#include \"nn_spm_extendedstorageservice.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/nn/cafe_nn_ipc_client.h\"\n#include \"nn/spm/nn_spm_extendedstorageservice.h\"\n\nusing namespace nn::spm;\nusing namespace nn::ipc;\n\nnamespace cafe::nn_spm\n{\n\nusing nn::spm::services::ExtendedStorageService;\n\nnn::Result\nSetAutoFatal(uint8_t autoFatal)\n{\n   auto command = ClientCommand<ExtendedStorageService::SetAutoFatal> { internal::getAllocator() };\n   command.setParameters(autoFatal);\n   auto result = internal::getClient()->sendSyncRequest(command);\n   if (result.ok()) {\n      result = command.readResponse();\n   }\n\n   return result;\n}\n\nvoid\nLibrary::registerExtendedStorageServiceSymbols()\n{\n   RegisterFunctionExportName(\"SetAutoFatal__Q2_2nn3spmFb\", SetAutoFatal);\n}\n\n}  // namespace cafe::nn_spm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_spm/nn_spm_extendedstorageservice.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\n#include <cstdint>\n\nnamespace cafe::nn_spm\n{\n\nnn::Result\nSetAutoFatal(uint8_t autoFatal);\n\n}  // namespace cafe::nn_spm\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_temp/nn_temp.cpp",
    "content": "#include \"nn_temp.h\"\n\nnamespace cafe::nn_temp\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerTempDirSymbols();\n}\n\n} // namespace cafe::nn_temp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_temp/nn_temp.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_temp\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_temp, \"nn_temp.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerTempDirSymbols();\n};\n\n} // namespace cafe::nn_temp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_temp/nn_temp_enum.h",
    "content": "#ifndef CAFE_TEMP_ENUM_H\n#define CAFE_TEMP_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(nn_temp)\n\nENUM_BEG(TEMPDevicePreference, uint32_t)\n   //! Largest free space between USB and MLC\n   ENUM_VALUE(LargestFreeSpace,     0)\n\n   //! Prefer USB instead of MLC\n   ENUM_VALUE(USB,                  1)\nENUM_END(TEMPDevicePreference)\n\nENUM_BEG(TEMPDeviceType, uint32_t)\n   ENUM_VALUE(Invalid,              0)\n   ENUM_VALUE(MLC,                  1)\n   ENUM_VALUE(USB,                  2)\nENUM_END(TEMPDeviceType)\n\nENUM_BEG(TEMPStatus, int32_t)\n   ENUM_VALUE(OK,                   0)\n   ENUM_VALUE(Cancelled,            -1)\n   ENUM_VALUE(End,                  -2)\n   ENUM_VALUE(Max,                  -3)\n   ENUM_VALUE(AlreadyOpen,          -4)\n   ENUM_VALUE(Exists,               -5)\n   ENUM_VALUE(NotFound,             -6)\n   ENUM_VALUE(NotFile,              -7)\n   ENUM_VALUE(NotDirectory,         -8)\n   ENUM_VALUE(AccessError,          -9)\n   ENUM_VALUE(PermissionError,      -10)\n   ENUM_VALUE(FileTooBig,           -11)\n   ENUM_VALUE(StorageFull,          -12)\n   ENUM_VALUE(JournalFull,          -13)\n   ENUM_VALUE(UnsupportedCmd,       -14)\n   ENUM_VALUE(MediaNotReady,        -15)\n   ENUM_VALUE(MediaError,           -17)\n   ENUM_VALUE(Corrupted,            -18)\n   ENUM_VALUE(FatalError,           -0x400)\n   ENUM_VALUE(InvalidParam,         -0x401)\nENUM_END(TEMPStatus)\n\nENUM_NAMESPACE_EXIT(nn_temp)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_TEMP_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_temp/nn_temp_tempdir.cpp",
    "content": "#include \"nn_temp.h\"\n#include \"nn_temp_tempdir.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_cosreport.h\"\n#include \"cafe/libraries/coreinit/coreinit_event.h\"\n#include \"cafe/libraries/coreinit/coreinit_fs_client.h\"\n#include \"cafe/libraries/coreinit/coreinit_fs_cmdblock.h\"\n#include \"cafe/libraries/coreinit/coreinit_fs_cmd.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n#include \"cafe/libraries/coreinit/coreinit_osreport.h\"\n\n#include <cinttypes>\n#include <common/strutils.h>\n#include <cstring>\n#include <fmt/core.h>\n#include <libcpu/cpu_formatters.h>\n\nusing namespace cafe::coreinit;\n\ntemplate<typename... Args>\nvoid tempLogInfo(const char *file, unsigned line, const char *msg,\n                 const Args &... args)\n{\n   auto str = fmt::format(msg, args...);\n   cafe::coreinit::internal::COSInfo(\n      cafe::coreinit::COSReportModule::Unknown1,\n      fmt::format(\"TEMP: [INFO]:{}({}):{}\", file, line, str.c_str()));\n}\n\ntemplate<typename... Args>\nvoid tempLogWarn(const char *file, unsigned line, const char *msg,\n                 const Args &... args)\n{\n   auto str = fmt::format(msg, args...);\n   cafe::coreinit::internal::COSInfo(\n      cafe::coreinit::COSReportModule::Unknown1,\n      fmt::format(\"TEMP: [WARN]:{}({}):{}\", file, line, str.c_str()));\n}\n\ntemplate<typename... Args>\nvoid tempLogError(const char *file, unsigned line, const char *msg,\n                  const Args &... args)\n{\n   auto str = fmt::format(msg, args...);\n   cafe::coreinit::internal::COSInfo(\n      cafe::coreinit::COSReportModule::Unknown1,\n      fmt::format(\"TEMP: [ERROR]:{}({}):{}\", file, line, str.c_str()));\n}\n\nnamespace cafe::nn_temp\n{\n\nconstexpr uint32_t NumSyncEvents = 1u;\n\nstruct TEMPSyncEventContext\n{\n   be2_virt_ptr<OSEvent> event;\n   be2_val<TEMPStatus> result;\n};\n\nstruct TempDirData\n{\n   TempDirData()\n   {\n      OSInitCond(virt_addrof(syncEventListCond));\n      OSInitMutex(virt_addrof(syncEventListMutex));\n   }\n\n   be2_val<int32_t> initCount;\n   be2_struct<OSMutex> mutex;\n   be2_struct<FSClient> fsClient;\n   be2_struct<FSCmdBlock> fsCmdBlock;\n   be2_array<char, FSMaxPathLength> globalDirPath;\n   be2_array<char, FSMaxPathLength> dirPath;\n\n   be2_array<OSEvent, NumSyncEvents> syncEventList;\n   be2_struct<OSCondition> syncEventListCond;\n   be2_struct<OSMutex> syncEventListMutex;\n   be2_val<uint32_t> syncEventListFreeBitmask = static_cast<uint32_t>((1 << syncEventList.size()) - 1);\n};\n\nstatic virt_ptr<TempDirData> sTempDirData = nullptr;\nstatic FSAsyncCallbackFn sSyncEventCallbackFn = nullptr;\n\nnamespace internal\n{\n\nbool\ncheckIsInitialised()\n{\n   if (sTempDirData->initCount > 0) {\n      return true;\n   }\n\n   coreinit::internal::OSPanic(\n      \"temp.cpp\", 181,\n      \"TEMP: [PANIC]:TEMP library is not initialized. Call TEMPInit() prior to \"\n      \"this function.\");\n   return false;\n}\n\nstatic virt_ptr<OSEvent>\nacquireSyncEvent()\n{\n   auto event = virt_ptr<OSEvent> { nullptr };\n   OSLockMutex(virt_addrof(sTempDirData->syncEventListMutex));\n\n   while (!event) {\n      for (auto i = 0u; i < sTempDirData->syncEventList.size(); ++i) {\n         if (sTempDirData->syncEventListFreeBitmask & (1 << i)) {\n            event = virt_addrof(sTempDirData->syncEventList[i]);\n            sTempDirData->syncEventListFreeBitmask &= ~(1 << i);\n            break;\n         }\n      }\n\n      if (!event) {\n         // No free events, wait for one to become free\n         OSWaitCond(virt_addrof(sTempDirData->syncEventListCond),\n                    virt_addrof(sTempDirData->syncEventListMutex));\n      }\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->syncEventListMutex));\n   OSInitEvent(event, FALSE, OSEventMode::AutoReset);\n   return event;\n}\n\nstatic void\nreleaseSyncEvent(virt_ptr<OSEvent> event)\n{\n   OSLockMutex(virt_addrof(sTempDirData->syncEventListMutex));\n   for (auto i = 0u; i < sTempDirData->syncEventList.size(); ++i) {\n      if (virt_addrof(sTempDirData->syncEventList[i]) == event) {\n         sTempDirData->syncEventListFreeBitmask |= (1 << i);\n      }\n   }\n   OSUnlockMutex(virt_addrof(sTempDirData->syncEventListMutex));\n   OSSignalCond(virt_addrof(sTempDirData->syncEventListCond));\n}\n\nstatic void\nsyncEventCallback(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> cmd,\n                  FSStatus status,\n                  virt_ptr<void> context)\n{\n   auto syncEventContext = virt_cast<TEMPSyncEventContext *>(context);\n   syncEventContext->result = static_cast<TEMPStatus>(status);\n   OSSignalEvent(syncEventContext->event);\n}\n\nstatic TEMPDirId\nmakeDirId(uint32_t deviceIndex,\n          TEMPDeviceType deviceType,\n          uint32_t upid)\n{\n   return deviceIndex | (deviceType << 8) |\n      static_cast<uint64_t>(OSGetUPID()) << 32;\n}\n\nstatic void\nsetDeviceInfo(virt_ptr<TEMPDeviceInfo> deviceInfo,\n              TEMPDeviceType deviceType,\n              uint8_t deviceIndex,\n              virt_ptr<char> devicePath)\n{\n   deviceInfo->dirId = makeDirId(deviceIndex, deviceType,\n                                 static_cast<uint32_t>(OSGetUPID()));\n\n   std::snprintf(virt_addrof(deviceInfo->targetPath).get(),\n                 deviceInfo->targetPath.size(),\n                 \"%s/usr/tmp/app\",\n                 devicePath.get());\n}\n\nstatic TEMPStatus\nupdatePreferentialDeviceInfo(virt_ptr<TEMPDeviceInfo> deviceInfo,\n                             uint32_t maxSize,\n                             TEMPDevicePreference devicePreference)\n{\n   auto freeMlcSize = StackObject<uint64_t> { };\n   auto freeUsbSize = StackObject<uint64_t> { };\n\n   FSGetFreeSpaceSize(virt_addrof(sTempDirData->fsClient),\n                      virt_addrof(sTempDirData->fsCmdBlock),\n                      make_stack_string(\"/vol/storage_mlc01\"),\n                      freeMlcSize,\n                      FSErrorFlag::None);\n   tempLogInfo(\"UpdatePreferentialDeviceInfo\", 544,\n               \"MLC freeSpaceSize={}\", *freeMlcSize);\n\n   // TODO: Use nn::spm to find device index for USB\n   if (FSGetFreeSpaceSize(virt_addrof(sTempDirData->fsClient),\n                          virt_addrof(sTempDirData->fsCmdBlock),\n                          make_stack_string(\"/vol/storage_usb01\"),\n                          freeUsbSize,\n                          FSErrorFlag::All) != FSStatus::OK) {\n      *freeUsbSize = 0ull;\n   }\n   tempLogInfo(\"UpdatePreferentialDeviceInfo\", 562,\n               \"USB freeSpaceSize={}\", *freeUsbSize);\n\n   if ((devicePreference == TEMPDevicePreference::USB\n        || *freeUsbSize >= *freeMlcSize)\n       && (*freeUsbSize >= maxSize)) {\n      setDeviceInfo(deviceInfo, TEMPDeviceType::USB, 1,\n                    make_stack_string(\"/vol/storage_usb01\"));\n   } else if (*freeMlcSize >= maxSize) {\n      setDeviceInfo(deviceInfo, TEMPDeviceType::MLC, 1,\n                    make_stack_string(\"/vol/storage_mlc01\"));\n   } else {\n      setDeviceInfo(deviceInfo, TEMPDeviceType::Invalid, 0,\n                    make_stack_string(\"\"));\n\n      tempLogInfo(\"UpdatePreferentialDeviceInfo\", 648,\n                  \"Cannot create temp dir: ({})\",  -12);\n      return static_cast<TEMPStatus>(TEMPStatus::StorageFull);\n   }\n\n   tempLogInfo(\"UpdatePreferentialDeviceInfo\", 644,\n               \"Preferential Device Path for temp dir: {}\",\n               virt_addrof(deviceInfo->targetPath));\n\n   return TEMPStatus::OK;\n}\n\nstatic TEMPStatus\ncreateAndMountTempDir(virt_ptr<TEMPDeviceInfo> deviceInfo)\n{\n   tempLogInfo(\"CreateAndMountTempDir\", 297,\n               \"(ENTR): dirID={}, targetPath={}\",\n               deviceInfo->dirId, virt_addrof(deviceInfo->targetPath));\n\n   auto error = static_cast<TEMPStatus>(\n      FSMakeDir(virt_addrof(sTempDirData->fsClient),\n                virt_addrof(sTempDirData->fsCmdBlock),\n                virt_addrof(deviceInfo->targetPath),\n                FSErrorFlag::All));\n   tempLogInfo(\"CreateAndMountTempDir\", 305,\n               \"Make dir done at {}, returned {}\",\n               virt_addrof(deviceInfo->targetPath), error);\n   if (error == TEMPStatus::StorageFull) {\n      goto out;\n   }\n\n   if (error != TEMPStatus::Exists) {\n      error = static_cast<TEMPStatus>(\n         FSChangeMode(virt_addrof(sTempDirData->fsClient),\n                      virt_addrof(sTempDirData->fsCmdBlock),\n                      virt_addrof(deviceInfo->targetPath),\n                      0x666,\n                      0x666,\n                      FSErrorFlag::None));\n      tempLogInfo(\"CreateAndMountTempDir\", 316,\n                  \"Change mode done at {}, returned {}\",\n                  virt_addrof(deviceInfo->targetPath), error);\n   }\n\n   error = TEMPGetDirGlobalPath(deviceInfo->dirId,\n                                virt_addrof(sTempDirData->globalDirPath),\n                                GlobalPathMaxLength);\n   if (error != TEMPStatus::OK) {\n      goto out;\n   }\n   tempLogInfo(\"CreateAndMountTempDir\", 325,\n               \"Global Path={}\", virt_addrof(sTempDirData->globalDirPath));\n\n   error = static_cast<TEMPStatus>(\n      FSMakeDir(virt_addrof(sTempDirData->fsClient),\n                virt_addrof(sTempDirData->fsCmdBlock),\n                virt_addrof(sTempDirData->globalDirPath),\n                FSErrorFlag::All));\n   tempLogInfo(\"CreateAndMountTempDir\", 333,\n               \"Make dir done at {}, returned {}\",\n               virt_addrof(sTempDirData->globalDirPath), error);\n   if (error == TEMPStatus::StorageFull) {\n      goto out;\n   }\n\n   error = TEMPGetDirPath(deviceInfo->dirId,\n                          virt_addrof(sTempDirData->dirPath),\n                          sTempDirData->dirPath.size());\n   if (error != TEMPStatus::OK) {\n      goto out;\n   }\n   tempLogInfo(\"CreateAndMountTempDir\", 346,\n               \"Dir Path={}\", virt_addrof(sTempDirData->dirPath));\n\n   error = static_cast<TEMPStatus>(\n      FSBindMount(virt_addrof(sTempDirData->fsClient),\n                  virt_addrof(sTempDirData->fsCmdBlock),\n                  virt_addrof(sTempDirData->globalDirPath),\n                  virt_addrof(sTempDirData->dirPath),\n                  FSErrorFlag::None));\n   tempLogInfo(\"CreateAndMountTempDir\", 353,\n               \"Bind mount done to {}, returned {}\",\n               virt_addrof(sTempDirData->dirPath), error);\n\nout:\n   tempLogInfo(\"CreateAndMountTempDir\", 356,\n               \"(EXIT): return {}\", error);\n   return error;\n}\n\nstatic TEMPDeviceType\ngetDeviceType(TEMPDirId dirId)\n{\n   return static_cast<TEMPDeviceType>((dirId >> 8) & 0xFF);\n}\n\nstatic TEMPDeviceType\ngetDeviceIndex(TEMPDirId dirId)\n{\n   return static_cast<TEMPDeviceType>(dirId & 0xFF);\n}\n\nstatic TEMPStatus\ngetDeviceInfo(virt_ptr<TEMPDeviceInfo> deviceInfo,\n              TEMPDirId dirId)\n{\n   auto deviceType = getDeviceType(dirId);\n   auto deviceIndex = getDeviceIndex(dirId);\n   deviceInfo->dirId = dirId;\n\n   if (deviceType == TEMPDeviceType::MLC) {\n      std::snprintf(virt_addrof(deviceInfo->targetPath).get(),\n                    deviceInfo->targetPath.size(),\n                    \"/vol/storage_mlc%02d/usr/tmp/app\",\n                    deviceIndex);\n   } else if (deviceType == TEMPDeviceType::USB) {\n      std::snprintf(virt_addrof(deviceInfo->targetPath).get(),\n                    deviceInfo->targetPath.size(),\n                    \"/vol/storage_usb%02d/usr/tmp/app\",\n                    deviceIndex);\n   } else {\n      return static_cast<TEMPStatus>(TEMPStatus::NotFound);\n   }\n\n   return TEMPStatus::OK;\n}\n\nstatic TEMPStatus\ngetTempAbsolutePath(TEMPDirId dirId,\n                    virt_ptr<const char> relativePath,\n                    virt_ptr<char> pathBuffer,\n                    uint32_t pathBufferSize)\n{\n   auto error = TEMPGetDirPath(dirId, pathBuffer, pathBufferSize);\n   if (error >= TEMPStatus::OK) {\n      auto tempDirLength = std::strlen(pathBuffer.get());\n      auto relPathLength = std::strlen(relativePath.get());\n      if (tempDirLength + relPathLength + 2 > pathBufferSize) {\n         return TEMPStatus::FatalError;\n      }\n\n      pathBuffer[tempDirLength++] = '/';\n      string_copy(pathBuffer.get() + tempDirLength, relativePath.get(),\n                  pathBufferSize - tempDirLength);\n   }\n\n   return error;\n}\n\nstatic TEMPStatus\nremoveRecursiveBody(virt_ptr<FSClient> client,\n                    virt_ptr<FSCmdBlock> block,\n                    virt_ptr<char> path,\n                    uint32_t pathBufferSize,\n                    FSStatFlags statFlags);\n\nstatic TEMPStatus\nremoveDirectoryEntry(virt_ptr<FSClient> client,\n                     virt_ptr<FSCmdBlock> block,\n                     virt_ptr<char> path,\n                     uint32_t pathBufferSize)\n{\n   auto handle = StackObject<FSDirHandle> { };\n   auto entry = StackObject<FSDirEntry> { };\n   auto error = static_cast<TEMPStatus>(\n      FSOpenDir(client, block, path, handle, FSErrorFlag::All));\n   if (error) {\n      tempLogError(\"RemoveDirectoryEntry\", 264,\n                 \"FSOpenDir failed with {}\", error);\n      return error;\n   }\n\n   while (FSReadDir(client, block, *handle, entry, FSErrorFlag::All) == 0) {\n      // Append directory entry name to path\n      auto pathLen = strnlen(path.get(), pathBufferSize);\n      std::strncat(path.get(),\n                   \"/\",\n                   pathBufferSize - pathLen - 1);\n\n      std::strncat(path.get(),\n                   virt_addrof(entry->name).get(),\n                   pathBufferSize - pathLen - 2);\n\n      error = removeRecursiveBody(client, block, path, pathBufferSize,\n                                  entry->stat.flags);\n\n      // Reset path\n      path[pathLen] = char { 0 };\n\n      if (error) {\n         break;\n      }\n   }\n\n   FSCloseDir(client, block, *handle, FSErrorFlag::None);\n   return error;\n}\n\nstatic TEMPStatus\nremoveRecursiveBody(virt_ptr<FSClient> client,\n                    virt_ptr<FSCmdBlock> block,\n                    virt_ptr<char> path,\n                    uint32_t pathBufferSize,\n                    FSStatFlags statFlags)\n{\n   auto error = TEMPStatus::OK;\n\n   if (statFlags & FSStatFlags::Quota) {\n      tempLogWarn(\"RemoveRecursiveBody\", 219,\n                  \"Quota is found inside temp directory!\");\n      // TODO: FSRemoveQuota\n   } else if (statFlags & FSStatFlags::Directory) {\n      error = removeDirectoryEntry(client, block, path, pathBufferSize);\n\n      if (error == TEMPStatus::OK) {\n         tempLogInfo(\"RemoveRecursiveBody\", 242,\n                     \"Removing {}\", path);\n\n         error = static_cast<TEMPStatus>(\n            FSRemove(client, block, path, FSErrorFlag::All));\n         if (error) {\n            tempLogError(\"RemoveRecursiveBody\", 236,\n                       \"FSRemove failed with {}\", error);\n         }\n      }\n   } else {\n      tempLogInfo(\"RemoveRecursiveBody\", 242,\n                  \"Removing {}\", path);\n\n      error = static_cast<TEMPStatus>(\n         FSRemove(client, block, path, FSErrorFlag::All));\n      if (error) {\n         tempLogError(\"RemoveRecursiveBody\", 236,\n                    \"FSRemove failed with {}\", error);\n      }\n   }\n\n   return error;\n}\n\nstatic TEMPStatus\nremoveRecursive(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                virt_ptr<char> path,\n                uint32_t pathBufferSize)\n{\n   auto stat = StackObject<FSStat> { };\n   auto error = static_cast<TEMPStatus>(\n      FSGetStat(client, block, path, stat, FSErrorFlag::All));\n   if (error) {\n      tempLogError(\"RemoveRecursive\", 202,\n                 \"FSGetStat failed with {}\", error);\n      return error;\n   }\n\n   return removeRecursiveBody(client, block, path, pathBufferSize, stat->flags);\n}\n\nstatic TEMPStatus\nteardownTempDir(TEMPDirId id)\n{\n   TEMPGetDirPath(id,\n                  virt_addrof(sTempDirData->globalDirPath),\n                  GlobalPathMaxLength);\n   tempLogInfo(\"TeardownTempDir\", 365,\n               \"(ENTR): path={}\", virt_addrof(sTempDirData->globalDirPath));\n\n   auto error =\n      internal::removeRecursive(\n         virt_addrof(sTempDirData->fsClient),\n         virt_addrof(sTempDirData->fsCmdBlock),\n         virt_addrof(sTempDirData->globalDirPath),\n         sTempDirData->globalDirPath.size() - 1);\n   if (error) {\n      tempLogError(\"TeardownTempDir\", 373,\n                 \"Failed to clean up temp dir, status={}\", error);\n   } else {\n      FSBindUnmount(virt_addrof(sTempDirData->fsClient),\n                    virt_addrof(sTempDirData->fsCmdBlock),\n                    virt_addrof(sTempDirData->globalDirPath),\n                    FSErrorFlag::None);\n      tempLogInfo(\"TeardownTempDir\", 379,\n                  \"TEMP: [INFO]:%s(%d):Bind unmount done at {}\",\n                  virt_addrof(sTempDirData->globalDirPath));\n   }\n\n   tempLogInfo(\"TeardownTempDir\", 382, \"(EXIT): return {}\", error);\n   return error;\n}\n\nstatic TEMPStatus\nparseDirId(virt_ptr<const char> path,\n           TEMPDirId *outDirId)\n{\n   if (!strstr(path.get(), \"/vol/storage_\")) {\n      return TEMPStatus::InvalidParam;\n   }\n\n   if (strstr(path.get() + 13, \"mlc\")) {\n      auto deviceIndex = strtoul(path.get() + 16, nullptr, 10);\n      auto deviceType = TEMPDeviceType::MLC;\n      auto upid = 0u;\n\n      if (auto pos = strstr(path.get() + 16, \"/usr/tmp/app\")) {\n         upid = strtoul(pos + 14, nullptr, 16);\n      }\n\n      *outDirId = makeDirId(deviceIndex, deviceType, upid);\n   } else if (strstr(path.get() + 13, \"usb\")) {\n      auto deviceIndex = strtoul(path.get() + 16, nullptr, 10);\n      auto deviceType = TEMPDeviceType::USB;\n      auto upid = 0u;\n\n      if (auto pos = strstr(path.get() + 16, \"/usr/tmp/app\")) {\n         upid = strtoul(pos + 14, nullptr, 16);\n      }\n\n      *outDirId = makeDirId(deviceIndex, deviceType, upid);\n   } else {\n      return TEMPStatus::InvalidParam;\n   }\n\n   return TEMPStatus::OK;\n}\n\nstatic TEMPStatus\nforceRemoveTempDir(virt_ptr<const char> rootPath)\n{\n   if (std::snprintf(virt_addrof(sTempDirData->globalDirPath).get(),\n                     GlobalPathMaxLength,\n                     \"%s/%08x\",\n                     rootPath.get(),\n                     static_cast<uint32_t>(OSGetUPID())) >= GlobalPathMaxLength) {\n      coreinit::internal::OSPanic(\n         \"temp.cpp\", 442,\n         fmt::format(\"The specified path is too long: {}\",\n                     virt_addrof(sTempDirData->globalDirPath).get()));\n      return TEMPStatus::FatalError;\n   }\n\n   auto handle = StackObject<FSDirHandle> { };\n   auto error = static_cast<TEMPStatus>(\n      FSOpenDir(virt_addrof(sTempDirData->fsClient),\n                virt_addrof(sTempDirData->fsCmdBlock),\n                rootPath,\n                handle,\n                FSErrorFlag::NotFound));\n   if (error) {\n      return error;\n   }\n\n   FSCloseDir(virt_addrof(sTempDirData->fsClient),\n              virt_addrof(sTempDirData->fsCmdBlock),\n              *handle,\n              FSErrorFlag::None);\n\n   auto dirId = TEMPDirId { };\n   error = parseDirId(virt_addrof(sTempDirData->globalDirPath), &dirId);\n   if (error) {\n      return error;\n   }\n\n   error = TEMPGetDirPath(dirId, virt_addrof(sTempDirData->dirPath),\n                          DirPathMaxLength);\n   if (error) {\n      return error;\n   }\n\n   FSBindUnmount(virt_addrof(sTempDirData->fsClient),\n                 virt_addrof(sTempDirData->fsCmdBlock),\n                 virt_addrof(sTempDirData->dirPath),\n                 FSErrorFlag::None);\n\n   error = internal::removeRecursive(virt_addrof(sTempDirData->fsClient),\n                                     virt_addrof(sTempDirData->fsCmdBlock),\n                                     virt_addrof(sTempDirData->globalDirPath),\n                                     sTempDirData->globalDirPath.size() - 1);\n   if (error) {\n      tempLogError(\"ForceRemoveTempDir\", 478,\n                 \"Failed to clean up temp dir, status={}\", error);\n   }\n\n   return error;\n}\n\nstatic void\ntempShutdownBody(bool isDriverDone)\n{\n   tempLogInfo(\"_TEMPShutdownBody\", 695, \"(ENTRY)\");\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n\n   if (sTempDirData->initCount <= 0) {\n      if (!isDriverDone) {\n         tempLogWarn(\"_TEMPShutdownBody\", 749,\n                     \"Library is not initialized.\");\n      }\n   } else if (sTempDirData->initCount == 1) {\n      auto error =\n         forceRemoveTempDir(make_stack_string(\"/vol/storage_mlc01/usr/tmp/app\"));\n      if (error && error != TEMPStatus::NotFound) {\n         tempLogError(\"_TEMPShutdownBody\", 708,\n                    \"Failed to delete temp dir in MLC.\");\n      }\n\n      // TODO: Use nn::spm to find device index for USB\n      error =\n         forceRemoveTempDir(make_stack_string(\"/vol/storage_usb01/usr/tmp/app\"));\n      if (error && error != TEMPStatus::NotFound) {\n         tempLogError(\"_TEMPShutdownBody\", 729,\n                    \"Failed to delete temp dir in USB.\");\n      }\n\n      FSDelClient(virt_addrof(sTempDirData->fsClient), FSErrorFlag::None);\n      sTempDirData->initCount = 0;\n      tempLogInfo(\"_TEMPShutdownBody\", 737,\n                  \"Deleted client\");\n   } else {\n      sTempDirData->initCount--;\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   tempLogInfo(\"_TEMPShutdownBody\", 754, \"(EXIT)\");\n}\n\n} // namespace internal\n\nTEMPStatus\nTEMPInit()\n{\n   tempLogInfo(\"TEMPInit\", 668, \"(ENTR)\");\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n\n   if (!sTempDirData->initCount) {\n      FSInit();\n      FSAddClient(virt_addrof(sTempDirData->fsClient), FSErrorFlag::None);\n      FSInitCmdBlock(virt_addrof(sTempDirData->fsCmdBlock));\n   } else {\n      tempLogInfo(\"TEMPInit\", 661, \"Library is already initialized.\");\n   }\n\n   sTempDirData->initCount++;\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   tempLogInfo(\"TEMPInit\", 668, \"(EXIT): return {}\", 0);\n   return TEMPStatus::OK;\n}\n\nvoid\nTEMPShutdown()\n{\n   internal::tempShutdownBody(false);\n}\n\nTEMPStatus\nTEMPChangeDir(virt_ptr<FSClient> client,\n              virt_ptr<FSCmdBlock> block,\n              TEMPDirId dirId,\n              virt_ptr<const char> path,\n              FSErrorFlag errorMask)\n{\n   auto syncEventContext = StackObject<TEMPSyncEventContext> { };\n   syncEventContext->event = internal::acquireSyncEvent();\n\n   auto asyncData = StackObject<FSAsyncData> { };\n   asyncData->ioMsgQueue = nullptr;\n   asyncData->userCallback = sSyncEventCallbackFn;\n   asyncData->userContext = syncEventContext;\n\n   auto error = TEMPChangeDirAsync(client, block, dirId, path, errorMask,\n                                   asyncData);\n   if (error >= TEMPStatus::OK) {\n      OSWaitEvent(syncEventContext->event);\n      error = syncEventContext->result;\n   }\n\n   internal::releaseSyncEvent(syncEventContext->event);\n   return error;\n}\n\nTEMPStatus\nTEMPChangeDirAsync(virt_ptr<FSClient> client,\n                   virt_ptr<FSCmdBlock> block,\n                   TEMPDirId dirId,\n                   virt_ptr<const char> path,\n                   FSErrorFlag errorMask,\n                   virt_ptr<const FSAsyncData> asyncData)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = internal::getTempAbsolutePath(dirId, path,\n                                              virt_addrof(sTempDirData->globalDirPath),\n                                              sTempDirData->globalDirPath.size());\n   if (error >= TEMPStatus::OK) {\n      error = static_cast<TEMPStatus>(\n         FSChangeDirAsync(client, block, virt_addrof(sTempDirData->globalDirPath),\n                          errorMask, asyncData));\n   } else {\n      error = TEMPStatus::NotFound;\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   return error;\n}\n\nTEMPStatus\nTEMPCreateAndInitTempDir(uint32_t maxSize,\n                         TEMPDevicePreference devicePreference,\n                         virt_ptr<TEMPDirId> outDirId)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n   auto error = TEMPStatus::OK;\n\n   tempLogInfo(\"TEMPCreateAndInitTempDir\", 779,\n               \"(ENTR): maxSize={}, pref={}\", maxSize, devicePreference);\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n\n   if (!internal::checkIsInitialised()) {\n      error = TEMPStatus::FatalError;\n      goto out;\n   }\n\n   if (!outDirId) {\n      tempLogError(\"TEMPCreateAndInitTempDir\", 793,\n                 \"pDirID was NULL.\");\n      error = TEMPStatus::InvalidParam;\n      goto out;\n   }\n\n   if (devicePreference != TEMPDevicePreference::LargestFreeSpace &&\n       devicePreference != TEMPDevicePreference::USB) {\n      tempLogError(\"TEMPCreateAndInitTempDir\", 800,\n                 \"Invalid value was specified to pref.\");\n      error = TEMPStatus::InvalidParam;\n      goto out;\n   }\n\n   error = internal::updatePreferentialDeviceInfo(deviceInfo,\n                                                  maxSize,\n                                                  devicePreference);\n   if (error < TEMPStatus::OK) {\n      goto out;\n   }\n\n   error = internal::createAndMountTempDir(deviceInfo);\n   if (error < TEMPStatus::OK) {\n      goto out;\n   }\n\n   *outDirId = deviceInfo->dirId;\n\nout:\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   tempLogInfo(\"TEMPCreateAndInitTempDir\", 826,\n               \"(EXIT): return {}\", error);\n   return error;\n}\n\nTEMPStatus\nTEMPGetDirPath(TEMPDirId dirId,\n               virt_ptr<char> pathBuffer,\n               uint32_t pathBufferSize)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n\n   if (!internal::checkIsInitialised()) {\n      return TEMPStatus::FatalError;\n   }\n\n   if (!pathBuffer) {\n      tempLogError(\"TEMPGetDirPath\", 865,\n                 \"path was NULL\");\n      return TEMPStatus::InvalidParam;\n   }\n\n   if (pathBufferSize < DirPathMaxLength) {\n      tempLogError(\"TEMPGetDirPath\", 870,\n                   \"pathLen(={}) was too short. Must be equal or bigger than TEMP_DIR_PATH_LENGTH_MAX(={})\",\n                   pathBufferSize, DirPathMaxLength);\n      return TEMPStatus::InvalidParam;\n   }\n\n   if (std::snprintf(pathBuffer.get(),\n                     pathBufferSize,\n                     \"/vol/temp/%016\" PRIx64,\n                     dirId) >= DirPathMaxLength) {\n      tempLogError(\"TEMPGetDirPath\", 881, \"Failed to generate path\");\n      return TEMPStatus::FatalError;\n   }\n\n   return TEMPStatus::OK;\n}\n\nTEMPStatus\nTEMPGetDirGlobalPath(TEMPDirId dirId,\n                     virt_ptr<char> pathBuffer,\n                     uint32_t pathBufferSize)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n\n   if (!internal::checkIsInitialised()) {\n      return static_cast<TEMPStatus>(TEMPStatus::FatalError);\n   }\n\n   if (!pathBuffer) {\n      tempLogError(\"TEMPGetDirGlobalPath\", 898,\n                 \"path was NULL\");\n      return TEMPStatus::InvalidParam;\n   }\n\n   if (pathBufferSize < GlobalPathMaxLength) {\n      tempLogError(\"TEMPGetDirGlobalPath\", 903,\n                   \"pathLen(={}) was too short. Must be equal or bigger than TEMP_DIR_PATH_LENGTH_MAX(={})\",\n                   pathBufferSize, DirPathMaxLength);\n      return TEMPStatus::InvalidParam;\n   }\n\n   if (auto error = internal::getDeviceInfo(deviceInfo, dirId)) {\n      return error;\n   }\n\n   if (std::snprintf(pathBuffer.get(),\n                     pathBufferSize,\n                     \"%s/%08x\",\n                     virt_addrof(deviceInfo->targetPath).get(),\n                     static_cast<uint32_t>(deviceInfo->dirId & 0xFFFFFFFF)) >= GlobalPathMaxLength) {\n      tempLogError(\"TEMPGetDirGlobalPath\", 922, \"Failed to generate path\");\n      return TEMPStatus::FatalError;\n   }\n\n   return TEMPStatus::OK;\n}\n\nTEMPStatus\nTEMPGetFreeSpaceSize(virt_ptr<FSClient> client,\n                     virt_ptr<FSCmdBlock> block,\n                     TEMPDirId dirId,\n                     virt_ptr<uint64_t> outFreeSize,\n                     FSErrorFlag errorMask)\n{\n   auto syncEventContext = StackObject<TEMPSyncEventContext> { };\n   syncEventContext->event = internal::acquireSyncEvent();\n\n   auto asyncData = StackObject<FSAsyncData> { };\n   asyncData->ioMsgQueue = nullptr;\n   asyncData->userCallback = sSyncEventCallbackFn;\n   asyncData->userContext = syncEventContext;\n\n   auto error = TEMPGetFreeSpaceSizeAsync(client, block, dirId, outFreeSize,\n                                          errorMask, asyncData);\n   if (error >= TEMPStatus::OK) {\n      OSWaitEvent(syncEventContext->event);\n      error = syncEventContext->result;\n   }\n\n   internal::releaseSyncEvent(syncEventContext->event);\n   return error;\n}\n\nTEMPStatus\nTEMPGetFreeSpaceSizeAsync(virt_ptr<FSClient> client,\n                          virt_ptr<FSCmdBlock> block,\n                          TEMPDirId dirId,\n                          virt_ptr<uint64_t> outFreeSize,\n                          FSErrorFlag errorMask,\n                          virt_ptr<const FSAsyncData> asyncData)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = TEMPGetDirPath(dirId, virt_addrof(sTempDirData->dirPath),\n                               sTempDirData->dirPath.size());\n   if (error >= TEMPStatus::OK) {\n      tempLogInfo(\"TEMPGetFreeSpaceSizeAsync\", 1730, \"Path = {}\",\n                  virt_addrof(sTempDirData->dirPath).get());\n      error = static_cast<TEMPStatus>(\n         FSGetFreeSpaceSizeAsync(client, block,\n                                 virt_addrof(sTempDirData->dirPath),\n                                 outFreeSize, errorMask, asyncData));\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   return error;\n}\n\nTEMPStatus\nTEMPGetStat(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            TEMPDirId dirId,\n            virt_ptr<const char> path,\n            virt_ptr<FSStat> outStat,\n            FSErrorFlag errorMask)\n{\n   auto syncEventContext = StackObject<TEMPSyncEventContext> { };\n   syncEventContext->event = internal::acquireSyncEvent();\n\n   auto asyncData = StackObject<FSAsyncData> { };\n   asyncData->ioMsgQueue = nullptr;\n   asyncData->userCallback = sSyncEventCallbackFn;\n   asyncData->userContext = syncEventContext;\n\n   auto error = TEMPGetStatAsync(client, block, dirId, path, outStat,\n                                 errorMask, asyncData);\n   if (error >= TEMPStatus::OK) {\n      OSWaitEvent(syncEventContext->event);\n      error = syncEventContext->result;\n   }\n\n   internal::releaseSyncEvent(syncEventContext->event);\n   return error;\n}\n\nTEMPStatus\nTEMPGetStatAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 TEMPDirId dirId,\n                 virt_ptr<const char> path,\n                 virt_ptr<FSStat> outStat,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = internal::getTempAbsolutePath(dirId, path,\n                                              virt_addrof(sTempDirData->globalDirPath),\n                                              sTempDirData->globalDirPath.size());\n   if (error >= TEMPStatus::OK) {\n      error = static_cast<TEMPStatus>(\n         FSGetStatAsync(client, block, virt_addrof(sTempDirData->globalDirPath),\n                        outStat, errorMask, asyncData));\n   } else {\n      error = TEMPStatus::NotFound;\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   return error;\n}\n\nTEMPStatus\nTEMPMakeDir(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            TEMPDirId dirId,\n            virt_ptr<const char> path,\n            FSErrorFlag errorMask)\n{\n   auto syncEventContext = StackObject<TEMPSyncEventContext> { };\n   syncEventContext->event = internal::acquireSyncEvent();\n\n   auto asyncData = StackObject<FSAsyncData> { };\n   asyncData->ioMsgQueue = nullptr;\n   asyncData->userCallback = sSyncEventCallbackFn;\n   asyncData->userContext = syncEventContext;\n\n   auto error = TEMPMakeDirAsync(client, block, dirId, path, errorMask,\n                                 asyncData);\n   if (error >= TEMPStatus::OK) {\n      OSWaitEvent(syncEventContext->event);\n      error = syncEventContext->result;\n   }\n\n   internal::releaseSyncEvent(syncEventContext->event);\n   return error;\n}\n\nTEMPStatus\nTEMPMakeDirAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 TEMPDirId dirId,\n                 virt_ptr<const char> path,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = internal::getTempAbsolutePath(dirId, path,\n                                              virt_addrof(sTempDirData->globalDirPath),\n                                              sTempDirData->globalDirPath.size());\n   if (error >= TEMPStatus::OK) {\n      error = static_cast<TEMPStatus>(\n         FSMakeDirAsync(client, block, virt_addrof(sTempDirData->globalDirPath),\n                        errorMask, asyncData));\n   } else {\n      error = TEMPStatus::NotFound;\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   return error;\n}\n\nTEMPStatus\nTEMPMountTempDir(TEMPDirId dirId)\n{\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = TEMPGetDirGlobalPath(dirId,\n                                     virt_addrof(sTempDirData->globalDirPath),\n                                     GlobalPathMaxLength);\n   if (error >= TEMPStatus::OK) {\n      tempLogInfo(\"TEMPMountTempDir\", 1619,\n                  \"Global Path={}\", virt_addrof(sTempDirData->globalDirPath).get());\n      error = TEMPGetDirPath(dirId, virt_addrof(sTempDirData->dirPath),\n                             DirPathMaxLength);\n      if (error >= TEMPStatus::OK) {\n         tempLogInfo(\"TEMPMountTempDir\", 1629,\n                     \"Dir Path={}\", virt_addrof(sTempDirData->dirPath).get());\n         error = static_cast<TEMPStatus>(\n            FSBindMount(virt_addrof(sTempDirData->fsClient),\n                        virt_addrof(sTempDirData->fsCmdBlock),\n                        virt_addrof(sTempDirData->globalDirPath),\n                        virt_addrof(sTempDirData->dirPath),\n                        FSErrorFlag::None));\n         tempLogInfo(\"TEMPMountTempDir\", 1636,\n                     \"Bind mount done to {}, returned {}\",\n                     virt_addrof(sTempDirData->dirPath).get(), error);\n      }\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   return error;\n}\n\nTEMPStatus\nTEMPOpenDir(virt_ptr<FSClient> client,\n            virt_ptr<FSCmdBlock> block,\n            TEMPDirId dirId,\n            virt_ptr<const char> path,\n            virt_ptr<FSDirHandle> outDirHandle,\n            FSErrorFlag errorMask)\n{\n   auto syncEventContext = StackObject<TEMPSyncEventContext> { };\n   syncEventContext->event = internal::acquireSyncEvent();\n\n   auto asyncData = StackObject<FSAsyncData> { };\n   asyncData->ioMsgQueue = nullptr;\n   asyncData->userCallback = sSyncEventCallbackFn;\n   asyncData->userContext = syncEventContext;\n\n   auto error = TEMPOpenDirAsync(client, block, dirId, path, outDirHandle,\n                                 errorMask, asyncData);\n   if (error >= TEMPStatus::OK) {\n      OSWaitEvent(syncEventContext->event);\n      error = syncEventContext->result;\n   }\n\n   internal::releaseSyncEvent(syncEventContext->event);\n   return error;\n}\n\nTEMPStatus\nTEMPOpenDirAsync(virt_ptr<FSClient> client,\n                 virt_ptr<FSCmdBlock> block,\n                 TEMPDirId dirId,\n                 virt_ptr<const char> path,\n                 virt_ptr<FSDirHandle> outDirHandle,\n                 FSErrorFlag errorMask,\n                 virt_ptr<const FSAsyncData> asyncData)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = internal::getTempAbsolutePath(dirId, path,\n                                              virt_addrof(sTempDirData->globalDirPath),\n                                              sTempDirData->globalDirPath.size());\n   if (error >= TEMPStatus::OK) {\n      error = static_cast<TEMPStatus>(\n         FSOpenDirAsync(client, block, virt_addrof(sTempDirData->globalDirPath),\n                        outDirHandle, errorMask, asyncData));\n   } else {\n      error = TEMPStatus::NotFound;\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   return error;\n}\n\nTEMPStatus\nTEMPOpenFile(virt_ptr<FSClient> client,\n             virt_ptr<FSCmdBlock> block,\n             TEMPDirId dirId,\n             virt_ptr<const char> path,\n             virt_ptr<const char> mode,\n             virt_ptr<FSFileHandle> outFileHandle,\n             FSErrorFlag errorMask)\n{\n   auto syncEventContext = StackObject<TEMPSyncEventContext> { };\n   syncEventContext->event = internal::acquireSyncEvent();\n\n   auto asyncData = StackObject<FSAsyncData> { };\n   asyncData->ioMsgQueue = nullptr;\n   asyncData->userCallback = sSyncEventCallbackFn;\n   asyncData->userContext = syncEventContext;\n\n   auto error = TEMPOpenFileAsync(client, block, dirId, path, mode,\n                                  outFileHandle, errorMask, asyncData);\n   if (error >= TEMPStatus::OK) {\n      OSWaitEvent(syncEventContext->event);\n      error = syncEventContext->result;\n   }\n\n   internal::releaseSyncEvent(syncEventContext->event);\n   return error;\n}\n\nTEMPStatus\nTEMPOpenFileAsync(virt_ptr<FSClient> client,\n                  virt_ptr<FSCmdBlock> block,\n                  TEMPDirId dirId,\n                  virt_ptr<const char> path,\n                  virt_ptr<const char> mode,\n                  virt_ptr<FSFileHandle> outFileHandle,\n                  FSErrorFlag errorMask,\n                  virt_ptr<const FSAsyncData> asyncData)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = internal::getTempAbsolutePath(dirId, path,\n                                              virt_addrof(sTempDirData->globalDirPath),\n                                              sTempDirData->globalDirPath.size());\n   if (error >= TEMPStatus::OK) {\n      error = static_cast<TEMPStatus>(\n         FSOpenFileAsync(client, block, virt_addrof(sTempDirData->globalDirPath),\n                         mode, outFileHandle, errorMask, asyncData));\n   } else {\n      error = TEMPStatus::NotFound;\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   return error;\n}\n\nTEMPStatus\nTEMPOpenNewFile(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                TEMPDirId dirId,\n                virt_ptr<const char> path,\n                virt_ptr<const char> mode,\n                virt_ptr<FSFileHandle> outFileHandle,\n                FSErrorFlag errorMask)\n{\n   // Yes, this is the real implementation.\n   return TEMPStatus::UnsupportedCmd;\n}\n\nTEMPStatus\nTEMPOpenNewFileAsync(virt_ptr<FSClient> client,\n                     virt_ptr<FSCmdBlock> block,\n                     TEMPDirId dirId,\n                     virt_ptr<const char> path,\n                     virt_ptr<const char> mode,\n                     virt_ptr<FSFileHandle> outFileHandle,\n                     FSErrorFlag errorMask,\n                     virt_ptr<const FSAsyncData> asyncData)\n{\n   // Yes, this is the real implementation.\n   return TEMPStatus::UnsupportedCmd;\n}\n\nTEMPStatus\nTEMPRemove(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           TEMPDirId dirId,\n           virt_ptr<const char> path,\n           FSErrorFlag errorMask)\n{\n   auto syncEventContext = StackObject<TEMPSyncEventContext> { };\n   syncEventContext->event = internal::acquireSyncEvent();\n\n   auto asyncData = StackObject<FSAsyncData> { };\n   asyncData->ioMsgQueue = nullptr;\n   asyncData->userCallback = sSyncEventCallbackFn;\n   asyncData->userContext = syncEventContext;\n\n   auto error = TEMPRemoveAsync(client, block, dirId, path, errorMask, asyncData);\n   if (error >= TEMPStatus::OK) {\n      OSWaitEvent(syncEventContext->event);\n      error = syncEventContext->result;\n   }\n\n   internal::releaseSyncEvent(syncEventContext->event);\n   return error;\n}\n\nTEMPStatus\nTEMPRemoveAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                TEMPDirId dirId,\n                virt_ptr<const char> path,\n                FSErrorFlag errorMask,\n                virt_ptr<const FSAsyncData> asyncData)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = internal::getTempAbsolutePath(dirId, path,\n                                              virt_addrof(sTempDirData->globalDirPath),\n                                              sTempDirData->globalDirPath.size());\n   if (error >= TEMPStatus::OK) {\n      error = static_cast<TEMPStatus>(\n         FSRemoveAsync(client, block, virt_addrof(sTempDirData->globalDirPath),\n                       errorMask, asyncData));\n   } else {\n      error = TEMPStatus::NotFound;\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   return error;\n}\n\nTEMPStatus\nTEMPRename(virt_ptr<FSClient> client,\n           virt_ptr<FSCmdBlock> block,\n           TEMPDirId dirId,\n           virt_ptr<const char> src,\n           virt_ptr<const char> dst,\n           FSErrorFlag errorMask)\n{\n   auto syncEventContext = StackObject<TEMPSyncEventContext> { };\n   syncEventContext->event = internal::acquireSyncEvent();\n\n   auto asyncData = StackObject<FSAsyncData> { };\n   asyncData->ioMsgQueue = nullptr;\n   asyncData->userCallback = sSyncEventCallbackFn;\n   asyncData->userContext = syncEventContext;\n\n   auto error = TEMPRenameAsync(client, block, dirId, src, dst, errorMask, asyncData);\n   if (error >= TEMPStatus::OK) {\n      OSWaitEvent(syncEventContext->event);\n      error = syncEventContext->result;\n   }\n\n   internal::releaseSyncEvent(syncEventContext->event);\n   return error;\n}\n\nTEMPStatus\nTEMPRenameAsync(virt_ptr<FSClient> client,\n                virt_ptr<FSCmdBlock> block,\n                TEMPDirId dirId,\n                virt_ptr<const char> src,\n                virt_ptr<const char> dst,\n                FSErrorFlag errorMask,\n                virt_ptr<const FSAsyncData> asyncData)\n{\n   auto deviceInfo = StackObject<TEMPDeviceInfo> { };\n\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = internal::getTempAbsolutePath(dirId, src,\n                                              virt_addrof(sTempDirData->globalDirPath),\n                                              sTempDirData->globalDirPath.size());\n   if (error >= TEMPStatus::OK) {\n      error = internal::getTempAbsolutePath(dirId, dst,\n                                            virt_addrof(sTempDirData->dirPath),\n                                            sTempDirData->dirPath.size());\n      if (error >= TEMPStatus::OK) {\n         error = static_cast<TEMPStatus>(\n            FSRenameAsync(client, block, virt_addrof(sTempDirData->globalDirPath),\n                          virt_addrof(sTempDirData->dirPath), errorMask, asyncData));\n      }\n   } else {\n      error = TEMPStatus::NotFound;\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   return error;\n}\n\nTEMPStatus\nTEMPShutdownTempDir(TEMPDirId id)\n{\n   tempLogInfo(\"TEMPShutdownTempDir\", 834, \"(ENTR): dirID={}\", id);\n\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = internal::teardownTempDir(id);\n   if (error && error != TEMPStatus::NotFound) {\n      tempLogError(\"TEMPShutdownTempDir\", 848,\n                   \"Failed to delete temp dir ({}).\", id);\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   tempLogInfo(\"TEMPShutdownTempDir\", 853, \"(EXIT): return {}\", error);\n   return error;\n}\n\nTEMPStatus\nTEMPUnmountTempDir(TEMPDirId dirId)\n{\n   OSLockMutex(virt_addrof(sTempDirData->mutex));\n   if (!internal::checkIsInitialised()) {\n      OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n      return TEMPStatus::FatalError;\n   }\n\n   auto error = TEMPGetDirPath(dirId, virt_addrof(sTempDirData->globalDirPath),\n                               DirPathMaxLength);\n   if (error >= TEMPStatus::OK) {\n      tempLogInfo(\"TEMPUnmountTempDir\", 1661,\n                  \"Global Path={}\", virt_addrof(sTempDirData->globalDirPath).get());\n      error = static_cast<TEMPStatus>(\n         FSBindUnmount(virt_addrof(sTempDirData->fsClient),\n                       virt_addrof(sTempDirData->fsCmdBlock),\n                       virt_addrof(sTempDirData->globalDirPath),\n                       FSErrorFlag::None));\n      tempLogInfo(\"TEMPUnmountTempDir\", 1669,\n                  \"Bind unmount done at {}\",\n                  virt_addrof(sTempDirData->globalDirPath).get());\n   }\n\n   OSUnlockMutex(virt_addrof(sTempDirData->mutex));\n   return error;\n}\n\nvoid\nLibrary::registerTempDirSymbols()\n{\n   RegisterFunctionExport(TEMPChangeDir);\n   RegisterFunctionExport(TEMPChangeDirAsync);\n   RegisterFunctionExport(TEMPCreateAndInitTempDir);\n   RegisterFunctionExport(TEMPGetDirGlobalPath);\n   RegisterFunctionExport(TEMPGetDirPath);\n   RegisterFunctionExport(TEMPGetFreeSpaceSize);\n   RegisterFunctionExport(TEMPGetFreeSpaceSizeAsync);\n   RegisterFunctionExport(TEMPGetStat);\n   RegisterFunctionExport(TEMPGetStatAsync);\n   RegisterFunctionExport(TEMPInit);\n   RegisterFunctionExport(TEMPMakeDir);\n   RegisterFunctionExport(TEMPMakeDirAsync);\n   RegisterFunctionExport(TEMPMountTempDir);\n   RegisterFunctionExport(TEMPOpenDir);\n   RegisterFunctionExport(TEMPOpenDirAsync);\n   RegisterFunctionExport(TEMPOpenFile);\n   RegisterFunctionExport(TEMPOpenFileAsync);\n   RegisterFunctionExport(TEMPOpenNewFile);\n   RegisterFunctionExport(TEMPOpenNewFileAsync);\n   RegisterFunctionExport(TEMPRemove);\n   RegisterFunctionExport(TEMPRemoveAsync);\n   RegisterFunctionExport(TEMPRename);\n   RegisterFunctionExport(TEMPRenameAsync);\n   RegisterFunctionExport(TEMPShutdown);\n   RegisterFunctionExport(TEMPShutdownTempDir);\n   RegisterFunctionExport(TEMPUnmountTempDir);\n\n   RegisterDataInternal(sTempDirData);\n   RegisterFunctionInternal(internal::syncEventCallback, sSyncEventCallbackFn);\n}\n\n} // namespace cafe::nn_temp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_temp/nn_temp_tempdir.h",
    "content": "#pragma once\n#include \"nn_temp_enum.h\"\n#include \"cafe/libraries/coreinit/coreinit_fs.h\"\n\n#include <libcpu/be2_struct.h>\n#include <string>\n\nnamespace cafe::nn_temp\n{\n\n#pragma pack(push, 1)\n\nusing TEMPDirId = uint64_t;\nconstexpr auto DirPathMaxLength = 0x20u;\nconstexpr auto GlobalPathMaxLength = 0x40u;\n\nstruct TEMPDeviceInfo\n{\n   be2_val<uint64_t> dirId;\n   be2_array<char, GlobalPathMaxLength> targetPath;\n};\nCHECK_OFFSET(TEMPDeviceInfo, 0x00, dirId);\nCHECK_OFFSET(TEMPDeviceInfo, 0x08, targetPath);\nCHECK_SIZE(TEMPDeviceInfo, 0x48);\n\n#pragma pack(pop)\n\nTEMPStatus\nTEMPInit();\n\nvoid\nTEMPShutdown();\n\nTEMPStatus\nTEMPChangeDir(virt_ptr<coreinit::FSClient> client,\n              virt_ptr<coreinit::FSCmdBlock> block,\n              TEMPDirId dirId,\n              virt_ptr<const char> path,\n              coreinit::FSErrorFlag errorMask);\n\nTEMPStatus\nTEMPChangeDirAsync(virt_ptr<coreinit::FSClient> client,\n                   virt_ptr<coreinit::FSCmdBlock> block,\n                   TEMPDirId dirId,\n                   virt_ptr<const char> path,\n                   coreinit::FSErrorFlag errorMask,\n                   virt_ptr<const coreinit::FSAsyncData> asyncData);\n\nTEMPStatus\nTEMPCreateAndInitTempDir(uint32_t maxSize,\n                         TEMPDevicePreference pref,\n                         virt_ptr<TEMPDirId> outDirId);\n\nTEMPStatus\nTEMPGetDirPath(TEMPDirId dirId,\n               virt_ptr<char> pathBuffer,\n               uint32_t pathBufferSize);\n\nTEMPStatus\nTEMPGetDirGlobalPath(TEMPDirId dirId,\n                     virt_ptr<char> pathBuffer,\n                     uint32_t pathBufferSize);\n\nTEMPStatus\nTEMPGetFreeSpaceSize(virt_ptr<coreinit::FSClient> client,\n                     virt_ptr<coreinit::FSCmdBlock> block,\n                     TEMPDirId dirId,\n                     virt_ptr<uint64_t> outFreeSize,\n                     coreinit::FSErrorFlag errorMask);\n\nTEMPStatus\nTEMPGetFreeSpaceSizeAsync(virt_ptr<coreinit::FSClient> client,\n                          virt_ptr<coreinit::FSCmdBlock> block,\n                          TEMPDirId dirId,\n                          virt_ptr<uint64_t> outFreeSize,\n                          coreinit::FSErrorFlag errorMask,\n                          virt_ptr<const coreinit::FSAsyncData> asyncData);\n\nTEMPStatus\nTEMPGetStat(virt_ptr<coreinit::FSClient> client,\n            virt_ptr<coreinit::FSCmdBlock> block,\n            TEMPDirId dirId,\n            virt_ptr<const char> path,\n            virt_ptr<coreinit::FSStat> outStat,\n            coreinit::FSErrorFlag errorMask);\n\nTEMPStatus\nTEMPGetStatAsync(virt_ptr<coreinit::FSClient> client,\n                 virt_ptr<coreinit::FSCmdBlock> block,\n                 TEMPDirId dirId,\n                 virt_ptr<const char> path,\n                 virt_ptr<coreinit::FSStat> outStat,\n                 coreinit::FSErrorFlag errorMask,\n                 virt_ptr<const coreinit::FSAsyncData> asyncData);\n\nTEMPStatus\nTEMPMakeDir(virt_ptr<coreinit::FSClient> client,\n            virt_ptr<coreinit::FSCmdBlock> block,\n            TEMPDirId dirId,\n            virt_ptr<const char> path,\n            coreinit::FSErrorFlag errorMask);\n\nTEMPStatus\nTEMPMakeDirAsync(virt_ptr<coreinit::FSClient> client,\n                 virt_ptr<coreinit::FSCmdBlock> block,\n                 TEMPDirId dirId,\n                 virt_ptr<const char> path,\n                 coreinit::FSErrorFlag errorMask,\n                 virt_ptr<const coreinit::FSAsyncData> asyncData);\n\nTEMPStatus\nTEMPMountTempDir(TEMPDirId dirId);\n\nTEMPStatus\nTEMPOpenDir(virt_ptr<coreinit::FSClient> client,\n            virt_ptr<coreinit::FSCmdBlock> block,\n            TEMPDirId dirId,\n            virt_ptr<const char> path,\n            virt_ptr<coreinit::FSDirHandle> outDirHandle,\n            coreinit::FSErrorFlag errorMask);\n\nTEMPStatus\nTEMPOpenDirAsync(virt_ptr<coreinit::FSClient> client,\n                 virt_ptr<coreinit::FSCmdBlock> block,\n                 TEMPDirId dirId,\n                 virt_ptr<const char> path,\n                 virt_ptr<coreinit::FSDirHandle> outDirHandle,\n                 coreinit::FSErrorFlag errorMask,\n                 virt_ptr<const coreinit::FSAsyncData> asyncData);\n\nTEMPStatus\nTEMPOpenFile(virt_ptr<coreinit::FSClient> client,\n             virt_ptr<coreinit::FSCmdBlock> block,\n             TEMPDirId dirId,\n             virt_ptr<const char> path,\n             virt_ptr<const char> mode,\n             virt_ptr<coreinit::FSFileHandle> outFileHandle,\n             coreinit::FSErrorFlag errorMask);\n\nTEMPStatus\nTEMPOpenFileAsync(virt_ptr<coreinit::FSClient> client,\n                  virt_ptr<coreinit::FSCmdBlock> block,\n                  TEMPDirId dirId,\n                  virt_ptr<const char> path,\n                  virt_ptr<const char> mode,\n                  virt_ptr<coreinit::FSFileHandle> outFileHandle,\n                  coreinit::FSErrorFlag errorMask,\n                  virt_ptr<const coreinit::FSAsyncData> asyncData);\n\nTEMPStatus\nTEMPOpenNewFile(virt_ptr<coreinit::FSClient> client,\n                virt_ptr<coreinit::FSCmdBlock> block,\n                TEMPDirId dirId,\n                virt_ptr<const char> path,\n                virt_ptr<const char> mode,\n                virt_ptr<coreinit::FSFileHandle> outFileHandle,\n                coreinit::FSErrorFlag errorMask);\n\nTEMPStatus\nTEMPOpenNewFileAsync(virt_ptr<coreinit::FSClient> client,\n                     virt_ptr<coreinit::FSCmdBlock> block,\n                     TEMPDirId dirId,\n                     virt_ptr<const char> path,\n                     virt_ptr<const char> mode,\n                     virt_ptr<coreinit::FSFileHandle> outFileHandle,\n                     coreinit::FSErrorFlag errorMask,\n                     virt_ptr<const coreinit::FSAsyncData> asyncData);\n\nTEMPStatus\nTEMPRemove(virt_ptr<coreinit::FSClient> client,\n           virt_ptr<coreinit::FSCmdBlock> block,\n           TEMPDirId dirId,\n           virt_ptr<const char> path,\n           coreinit::FSErrorFlag errorMask);\n\nTEMPStatus\nTEMPRemoveAsync(virt_ptr<coreinit::FSClient> client,\n                virt_ptr<coreinit::FSCmdBlock> block,\n                TEMPDirId dirId,\n                virt_ptr<const char> path,\n                coreinit::FSErrorFlag errorMask,\n                virt_ptr<const coreinit::FSAsyncData> asyncData);\n\nTEMPStatus\nTEMPRename(virt_ptr<coreinit::FSClient> client,\n           virt_ptr<coreinit::FSCmdBlock> block,\n           TEMPDirId dirId,\n           virt_ptr<const char> src,\n           virt_ptr<const char> dst,\n           coreinit::FSErrorFlag errorMask);\n\nTEMPStatus\nTEMPRenameAsync(virt_ptr<coreinit::FSClient> client,\n                virt_ptr<coreinit::FSCmdBlock> block,\n                TEMPDirId dirId,\n                virt_ptr<const char> src,\n                virt_ptr<const char> dst,\n                coreinit::FSErrorFlag errorMask,\n                virt_ptr<const coreinit::FSAsyncData> asyncData);\n\nTEMPStatus\nTEMPShutdownTempDir(TEMPDirId id);\n\nTEMPStatus\nTEMPUnmountTempDir(TEMPDirId dirId);\n\n} // namespace cafe::nn_temp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_uds/nn_uds.cpp",
    "content": "#include \"nn_uds.h\"\n\nnamespace cafe::nn_uds\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerApiSymbols();\n}\n\n} // namespace cafe::nn_uds\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_uds/nn_uds.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_uds\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_uds, \"nn_uds.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerApiSymbols();\n};\n\n} // namespace cafe::nn_uds\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_uds/nn_uds_api.cpp",
    "content": "#include \"nn_uds.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_ios.h\"\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::nn_uds\n{\n\nstatic virt_ptr<OSMutex> sLock = nullptr;\nstatic virt_ptr<IOSHandle> sUdsIpcHandle = nullptr;\n\nvoid\nudsApiCppGlobalConstructor()\n{\n   *sUdsIpcHandle = -1;\n   OSInitMutex(sLock);\n}\n\nvoid\nLibrary::registerApiSymbols()\n{\n   RegisterFunctionExportName(\"__sti___11_uds_Api_cpp_f5d9abb2\", udsApiCppGlobalConstructor);\n\n   RegisterDataExportName(\"s_Lock__Q3_2nn3uds4Cafe\", sLock);\n   RegisterDataExportName(\"s_UdsIpc__Q3_2nn3uds4Cafe\", sUdsIpcHandle);\n}\n\n} // namespace cafe::nn_uds\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_vctl/nn_vctl.cpp",
    "content": "#include \"nn_vctl.h\"\n\nnamespace cafe::nn_vctl\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerClientSymbols();\n}\n\n} // namespace cafe::nn_vctl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_vctl/nn_vctl.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nn_vctl\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nn_vctl, \"nn_vctl.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerClientSymbols();\n};\n\n} // namespace cafe::nn_vctl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_vctl/nn_vctl_client.cpp",
    "content": "#include \"nn_vctl.h\"\n#include \"nn_vctl_client.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::nn_vctl\n{\n\nstruct StaticClientData\n{\n   StaticClientData()\n   {\n      OSInitMutex(virt_addrof(mutex));\n   }\n\n   be2_struct<OSMutex> mutex;\n   be2_val<uint32_t> refCount = 0u;\n   be2_val<BOOL> initialised = FALSE;\n};\n\nstatic virt_ptr<StaticClientData> sClientData = nullptr;\n\nnn::Result\nInitialize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount == 0) {\n      sClientData->initialised = TRUE;\n   }\n   sClientData->refCount++;\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n   return nn::ResultSuccess;\n}\n\nvoid\nFinalize()\n{\n   OSLockMutex(virt_addrof(sClientData->mutex));\n   if (sClientData->refCount > 0) {\n      sClientData->refCount--;\n\n      if (sClientData->refCount == 0) {\n         sClientData->initialised = FALSE;\n      }\n   }\n   OSUnlockMutex(virt_addrof(sClientData->mutex));\n}\n\nvoid\nLibrary::registerClientSymbols()\n{\n   RegisterFunctionExportName(\"Initialize__Q2_2nn4vctlFv\", Initialize);\n   RegisterFunctionExportName(\"Finalize__Q2_2nn4vctlFv\", Finalize);\n\n   RegisterDataInternal(sClientData);\n}\n\n}  // namespace cafe::nn_vctl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nn_vctl/nn_vctl_client.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace cafe::nn_vctl\n{\n\nnn::Result\nInitialize();\n\nvoid\nFinalize();\n\n} // namespace cafe::nn_vctl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysccr/nsysccr.cpp",
    "content": "#include \"nsysccr.h\"\n\nnamespace cafe::nsysccr\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nsysccr\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysccr/nsysccr.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nsysccr\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nsysccr, \"nsysccr.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nsysccr\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsyshid/nsyshid.cpp",
    "content": "#include \"nsyshid.h\"\n\nnamespace cafe::nsyshid\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nsyshid\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsyshid/nsyshid.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nsyshid\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nsyshid, \"nsyshid.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nsyshid\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd.cpp",
    "content": "#include \"nsyskbd.h\"\n\nnamespace cafe::nsyskbd\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerKprSymbols();\n   registerSkbdSymbols();\n}\n\n} // namespace cafe::nsyskbd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nsyskbd\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nsyskbd, \"nsyskbd.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerKprSymbols();\n   void registerSkbdSymbols();\n};\n\n} // namespace cafe::nsyskbd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd_enum.h",
    "content": "#ifndef CAFE_NSYSKBD_ENUM_H\n#define CAFE_NSYSKBD_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(nsyskbd)\n\nENUM_BEG(KPRMode, uint32_t)\n   ENUM_VALUE(AltCode,           1 << 0)\nENUM_END(KPRMode)\n\nENUM_BEG(SKBDChannelStatus, uint32_t)\n   ENUM_VALUE(Connected,         0)\n   ENUM_VALUE(Disconnected,      1)\nENUM_END(SKBDChannelStatus)\n\nENUM_BEG(SKBDCountry, uint32_t)\n   ENUM_VALUE(Max,               0x13)\nENUM_END(SKBDCountry)\n\nENUM_BEG(SKBDError, uint32_t)\n   ENUM_VALUE(OK,                0)\n   ENUM_VALUE(InvalidCountry,    4)\nENUM_END(SKBDError)\n\nENUM_BEG(SKBDModState, uint32_t)\n   ENUM_VALUE(NoModifiers,       0)\nENUM_END(SKBDModState)\n\nENUM_NAMESPACE_EXIT(nsyskbd)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_NSYSKBD_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd_kpr.cpp",
    "content": "#include \"nsyskbd.h\"\n#include \"nsyskbd_kpr.h\"\n\nnamespace cafe::nsyskbd\n{\n\nvoid\nKPRInitQueue(virt_ptr<KPRQueue> queue)\n{\n   KPRSetMode(queue, KPRMode::AltCode);\n}\n\nKPRMode\nKPRGetMode(virt_ptr<KPRQueue> queue)\n{\n   return queue->mode;\n}\n\nvoid\nKPRSetMode(virt_ptr<KPRQueue> queue,\n           KPRMode mode)\n{\n   queue->mode = mode;\n   KPRClearQueue(queue);\n}\n\nvoid\nKPRClearQueue(virt_ptr<KPRQueue> queue)\n{\n   queue->numCharsOut = uint8_t { 0 };\n   queue->numCharsIn = uint8_t { 0 };\n   queue->unk0x14 = 0u;\n}\n\nkpr_char_t\nKPRGetChar(virt_ptr<KPRQueue> queue)\n{\n   auto result = kpr_char_t { 0 };\n\n   // TODO: Once we implement processing of input -> ouput, use numCharsOut\n   if (queue->numCharsIn > 0) { // numCharsOut > 0\n      result = queue->buffer[0];\n      queue->numCharsIn -= 1;\n   }\n\n   return result;\n}\n\nuint8_t\nKPRPutChar(virt_ptr<KPRQueue> queue,\n           kpr_char_t chr)\n{\n   decaf_check(queue->numCharsOut + queue->numCharsIn < 5);\n\n   queue->buffer[queue->numCharsOut + queue->numCharsIn] = chr;\n   queue->numCharsIn += 1;\n\n   // TODO: Process characters from out -> in\n   return queue->numCharsIn; // return queue->numCharsOut;\n}\n\nkpr_char_t\nKPRRemoveChar(virt_ptr<KPRQueue> queue)\n{\n   if (queue->numCharsIn == 0) {\n      return 0;\n   }\n\n   auto result = queue->buffer[queue->numCharsOut + queue->numCharsIn - 1];\n   queue->numCharsIn -= 1;\n\n   return result;\n}\n\nuint8_t\nKPRLookAhead(virt_ptr<KPRQueue> queue,\n             virt_ptr<kpr_char_t> buffer,\n             uint32_t size)\n{\n   if (!buffer || !size) {\n      return 0;\n   }\n\n   auto length = static_cast<uint8_t>(queue->numCharsOut + queue->numCharsIn);\n\n   for (auto i = 0u; i < length && i < size; ++i) {\n      buffer[i] = queue->buffer[i];\n   }\n\n   if (length < size) {\n      buffer[length] = kpr_char_t { 0 };\n   }\n\n   return length;\n}\n\nvoid\nLibrary::registerKprSymbols()\n{\n   RegisterFunctionExport(KPRInitQueue);\n   RegisterFunctionExport(KPRSetMode);\n   RegisterFunctionExport(KPRGetMode);\n   RegisterFunctionExport(KPRClearQueue);\n   RegisterFunctionExport(KPRPutChar);\n   RegisterFunctionExport(KPRGetChar);\n   RegisterFunctionExport(KPRRemoveChar);\n   RegisterFunctionExport(KPRLookAhead);\n}\n\n} // namespace cafe::nsyskbd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd_kpr.h",
    "content": "#pragma once\n#include \"nsyskbd_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nsyskbd\n{\n\n/**\n * \\defgroup nsyskbd_kbr KBR\n * \\ingroup nsyskbd\n *\n * This is used for combining characters. Which is useful for ALT+Num unicode\n * characters and for typing japanese things where you input multiple characters\n * which combine together into one character.\n *\n * @{\n */\n\n#pragma pack(push, 1)\n\nusing kpr_char_t = int16_t;\n\nstruct KPRQueue\n{\n   be2_array<kpr_char_t, 6> buffer;\n   be2_val<KPRMode> mode;\n   be2_val<uint8_t> numCharsOut;\n   be2_val<uint8_t> numCharsIn;\n   PADDING(2);\n   be2_val<uint32_t> unk0x14;\n};\nCHECK_OFFSET(KPRQueue, 0x00, buffer);\nCHECK_OFFSET(KPRQueue, 0x0C, mode);\nCHECK_OFFSET(KPRQueue, 0x10, numCharsOut);\nCHECK_OFFSET(KPRQueue, 0x11, numCharsIn);\nCHECK_OFFSET(KPRQueue, 0x14, unk0x14);\nCHECK_SIZE(KPRQueue, 0x18);\n\n#pragma pack(pop)\n\nvoid\nKPRInitQueue(virt_ptr<KPRQueue> queue);\n\nvoid\nKPRSetMode(virt_ptr<KPRQueue> queue,\n           KPRMode mode);\n\nKPRMode\nKPRGetMode(virt_ptr<KPRQueue> queue);\n\nvoid\nKPRClearQueue(virt_ptr<KPRQueue> queue);\n\nuint8_t\nKPRPutChar(virt_ptr<KPRQueue> queue,\n           kpr_char_t chr);\n\nkpr_char_t\nKPRGetChar(virt_ptr<KPRQueue> queue);\n\nkpr_char_t\nKPRRemoveChar(virt_ptr<KPRQueue> queue);\n\nuint8_t\nKPRLookAhead(virt_ptr<KPRQueue> queue,\n             virt_ptr<kpr_char_t> buffer,\n             uint32_t size);\n\n/** @} */\n\n} // namespace cafe::nsyskbd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd_skbd.cpp",
    "content": "#include \"nsyskbd.h\"\n#include \"nsyskbd_skbd.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::nsyskbd\n{\n\nSKBDError\nSKBDSetup(uint32_t unk_r3)\n{\n   decaf_warn_stub();\n   return SKBDError::OK;\n}\n\nSKBDError\nSKBDTeardown()\n{\n   decaf_warn_stub();\n   return SKBDError::OK;\n}\n\nSKBDError\nSKBDGetChannelStatus(SKBDChannel channel,\n                     virt_ptr<SKBDChannelStatus> outStatus)\n{\n   decaf_warn_stub();\n\n   if (channel == 0) {\n      *outStatus = SKBDChannelStatus::Connected;\n   } else {\n      *outStatus = SKBDChannelStatus::Disconnected;\n   }\n\n   return SKBDError::OK;\n}\n\nSKBDError\nSKBDGetKey(SKBDChannel channel,\n           virt_ptr<SKBDKeyData> keyData)\n{\n   decaf_warn_stub();\n   std::memset(keyData.get(), 0, sizeof(SKBDKeyData));\n   keyData->channel = channel;\n   return SKBDError::OK;\n}\n\nSKBDError\nSKBDGetModState(SKBDChannel channel,\n                virt_ptr<SKBDModState> outModState)\n{\n   decaf_warn_stub();\n   *outModState = SKBDModState::NoModifiers;\n   return SKBDError::OK;\n}\n\nSKBDError\nSKBDResetChannel(SKBDChannel channel)\n{\n   decaf_warn_stub();\n   return SKBDError::OK;\n}\n\nSKBDError\nSKBDSetCountry(SKBDChannel channel,\n               SKBDCountry country)\n{\n   decaf_warn_stub();\n   if (country >= SKBDCountry::Max) {\n      return SKBDError::InvalidCountry;\n   }\n\n   return SKBDError::OK;\n}\n\nSKBDError\nSKBDSetMode(uint32_t mode)\n{\n   decaf_warn_stub();\n   return SKBDError::OK;\n}\n\nvoid\nLibrary::registerSkbdSymbols()\n{\n   RegisterFunctionExport(SKBDSetup);\n   RegisterFunctionExport(SKBDTeardown);\n   RegisterFunctionExport(SKBDGetChannelStatus);\n   RegisterFunctionExport(SKBDGetKey);\n   RegisterFunctionExport(SKBDGetModState);\n   RegisterFunctionExport(SKBDResetChannel);\n   RegisterFunctionExport(SKBDSetCountry);\n   RegisterFunctionExport(SKBDSetMode);\n}\n\n} // namespace cafe::nsyskbd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsyskbd/nsyskbd_skbd.h",
    "content": "#pragma once\n#include \"nsyskbd_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nsyskbd\n{\n\n/**\n * \\defgroup nsyskbd_skbd SKBD\n * \\ingroup nsyskbd\n * @{\n */\n\n#pragma pack(push, 1)\n\nusing SKBDChannel = uint8_t;\n\nstruct SKBDKeyData\n{\n   be2_val<SKBDChannel> channel;\n   UNKNOWN(0x0F);\n};\nCHECK_OFFSET(SKBDKeyData, 0x00, channel);\nCHECK_SIZE(SKBDKeyData, 0x10);\n\n#pragma pack(pop)\n\nSKBDError\nSKBDSetup(uint32_t unk_r3);\n\nSKBDError\nSKBDTeardown();\n\nSKBDError\nSKBDGetChannelStatus(SKBDChannel channel,\n                     virt_ptr<SKBDChannelStatus> outStatus);\n\nSKBDError\nSKBDGetKey(SKBDChannel channel,\n           virt_ptr<SKBDKeyData> keyData);\n\nSKBDError\nSKBDGetModState(SKBDChannel channel,\n                virt_ptr<SKBDModState> outModState);\n\nSKBDError\nSKBDResetChannel(SKBDChannel channel);\n\nSKBDError\nSKBDSetCountry(SKBDChannel channel,\n               SKBDCountry country);\n\nSKBDError\nSKBDSetMode(uint32_t mode);\n\n/** @} */\n\n} // namespace cafe::nsyskbd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysnet/nsysnet.cpp",
    "content": "#include \"nsysnet.h\"\n#include \"nsysnet_socket_lib.h\"\n#include \"nsysnet_nssl.h\"\n\nnamespace cafe::nsysnet\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerEndianSymbols();\n   registerSocketLibSymbols();\n   registerSslSymbols();\n}\n\n} // namespace cafe::nsysnet\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysnet/nsysnet.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nsysnet\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nsysnet, \"nsysnet.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerEndianSymbols();\n   void registerSocketLibSymbols();\n   void registerSslSymbols();\n};\n\n} // namespace cafe::nsysnet\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_endian.cpp",
    "content": "#include \"nsysnet.h\"\n\nnamespace cafe::nsysnet\n{\n\n/*\n * These are all no-op because the Wii U's host byte order is net order.\n */\n\nstatic uint16_t\nnsysnet_htons(uint16_t value)\n{\n   return value;\n}\n\nstatic uint32_t\nnsysnet_htonl(uint32_t value)\n{\n   return value;\n}\n\nstatic uint16_t\nnsysnet_ntohs(uint16_t value)\n{\n   return value;\n}\n\nstatic uint32_t\nnsysnet_ntohl(uint32_t value)\n{\n   return value;\n}\n\nvoid\nLibrary::registerEndianSymbols()\n{\n   RegisterFunctionExportName(\"htons\", nsysnet_htons);\n   RegisterFunctionExportName(\"htonl\", nsysnet_htonl);\n   RegisterFunctionExportName(\"ntohs\", nsysnet_ntohs);\n   RegisterFunctionExportName(\"ntohl\", nsysnet_ntohl);\n}\n\n} // namespace cafe::nsysnet\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_enum.h",
    "content": "#ifndef NSYSNET_ENUM_H\n#define NSYSNET_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(nsysnet)\n\nFLAGS_BEG(GetHostError, int32_t)\n   FLAGS_VALUE(OK,            0)\n   FLAGS_VALUE(HostNotFound,  1)\n   FLAGS_VALUE(TryAgain,      2)\n   FLAGS_VALUE(NoRecovery,    3)\n   FLAGS_VALUE(NoData,        4)\nFLAGS_END(GetHostError)\n\nFLAGS_BEG(Error, int32_t)\n   FLAGS_VALUE(OK,            0)\nFLAGS_END(Error)\n\nENUM_NAMESPACE_EXIT(nsysnet)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef NSYSNET_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_nssl.cpp",
    "content": "#include \"nsysnet.h\"\n#include \"nsysnet_nssl.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_ios.h\"\n#include \"cafe/libraries/coreinit/coreinit_ipcbufpool.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"ios/nsec/ios_nsec_nssl.h\"\n\nnamespace cafe::nsysnet\n{\n\nusing namespace coreinit;\n\nusing ios::nsec::NSSLCertType;\nusing ios::nsec::NSSLCommand;\nusing ios::nsec::NSSLCreateContextRequest;\nusing ios::nsec::NSSLDestroyContextRequest;\nusing ios::nsec::NSSLAddServerPKIRequest;\nusing ios::nsec::NSSLAddServerPKIExternalRequest;\nusing ios::nsec::NSSLExportInternalClientCertificateRequest;\nusing ios::nsec::NSSLExportInternalClientCertificateResponse;\nusing ios::nsec::NSSLExportInternalServerCertificateRequest;\nusing ios::nsec::NSSLExportInternalServerCertificateResponse;\n\nstruct SslData\n{\n   static constexpr uint32_t MessageCount = 0x40;\n   static constexpr uint32_t MessageSize = 0x100;\n\n   SslData()\n   {\n      OSInitMutex(virt_addrof(lock));\n   }\n\n   be2_struct<OSMutex> lock;\n   be2_val<IOSHandle> handle = IOSHandle { -1 };\n   be2_virt_ptr<IPCBufPool> messagePool;\n   be2_array<uint8_t, MessageCount * MessageSize> messageBuffer;\n   be2_val<uint32_t> messageCount;\n};\n\nstatic virt_ptr<SslData>\nsSslData;\n\nnamespace internal\n{\n\nstatic bool isInitialised();\nstatic virt_ptr<void> allocateIpcBuffer(uint32_t size);\nstatic void freeIpcBuffer(virt_ptr<void> buf);\n\n} // namespace internal\n\nNSSLError\nNSSLInit()\n{\n   OSLockMutex(virt_addrof(sSslData->lock));\n\n   if (sSslData->handle < 0) {\n      auto iosError = IOS_Open(make_stack_string(\"/dev/nsec/nssl\"),\n                               IOSOpenMode::None);\n\n      if (iosError < 0) {\n         OSUnlockMutex(virt_addrof(sSslData->lock));\n         return NSSLError::IpcError;\n      }\n\n      sSslData->handle = static_cast<IOSHandle>(iosError);\n   }\n\n   if (!sSslData->messagePool) {\n      sSslData->messagePool =\n         IPCBufPoolCreate(virt_addrof(sSslData->messageBuffer),\n                          static_cast<uint32_t>(sSslData->messageBuffer.size()),\n                          SslData::MessageSize,\n                          virt_addrof(sSslData->messageCount),\n                          1);\n\n      if (!sSslData->messagePool) {\n         IOS_Close(sSslData->handle);\n         sSslData->handle = -1;\n         OSUnlockMutex(virt_addrof(sSslData->lock));\n         return NSSLError::NsslLibError;\n      }\n   }\n\n   OSUnlockMutex(virt_addrof(sSslData->lock));\n   return NSSLError::OK;\n}\n\nNSSLError\nNSSLFinish()\n{\n   OSLockMutex(virt_addrof(sSslData->lock));\n   if (sSslData->handle != -1) {\n      IOS_Close(sSslData->handle);\n      sSslData->handle = -1;\n   }\n   OSUnlockMutex(virt_addrof(sSslData->lock));\n   return NSSLError::OK;\n}\n\nNSSLError\nNSSLCreateContext(ios::nsec::NSSLVersion version)\n{\n   if (!internal::isInitialised()) {\n      return NSSLError::LibNotReady;\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(NSSLCreateContextRequest));\n   if (!buf) {\n      return NSSLError::OutOfMemory;\n   }\n\n   auto request = virt_cast<NSSLCreateContextRequest *>(buf);\n   request->version = version;\n\n   auto error = IOS_Ioctl(sSslData->handle,\n                          NSSLCommand::CreateContext,\n                          request,\n                          sizeof(NSSLCreateContextRequest),\n                          NULL,\n                          0);\n\n   internal::freeIpcBuffer(buf);\n   return static_cast<NSSLError>(error);\n}\n\nNSSLError\nNSSLDestroyContext(NSSLContextHandle context)\n{\n   if (!internal::isInitialised()) {\n      return NSSLError::LibNotReady;\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(NSSLDestroyContextRequest));\n   if (!buf) {\n      return NSSLError::OutOfMemory;\n   }\n\n   auto request = virt_cast<NSSLDestroyContextRequest *>(buf);\n   request->context = context;\n\n   auto error = IOS_Ioctl(sSslData->handle,\n                          NSSLCommand::DestroyContext,\n                          request,\n                          sizeof(NSSLDestroyContextRequest),\n                          NULL,\n                          0);\n\n   internal::freeIpcBuffer(buf);\n   return static_cast<NSSLError>(error);\n}\n\nNSSLError\nNSSLAddServerPKI(NSSLContextHandle context,\n                 NSSLCertID certId)\n{\n   if (!internal::isInitialised()) {\n      return NSSLError::LibNotReady;\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(NSSLAddServerPKIRequest));\n   if (!buf) {\n      return NSSLError::OutOfMemory;\n   }\n\n   auto request = virt_cast<NSSLAddServerPKIRequest *>(buf);\n   request->context = context;\n   request->cert = certId;\n\n   auto error = IOS_Ioctl(sSslData->handle,\n                          NSSLCommand::AddServerPKI,\n                          request,\n                          sizeof(NSSLAddServerPKIRequest),\n                          NULL,\n                          0);\n\n   internal::freeIpcBuffer(buf);\n   return static_cast<NSSLError>(error);\n}\n\nNSSLError\nNSSLAddServerPKIExternal(NSSLContextHandle context,\n                         virt_ptr<uint8_t> cert,\n                         uint32_t certSize,\n                         NSSLCertType certType)\n{\n   if (!internal::isInitialised()) {\n      return NSSLError::LibNotReady;\n   }\n\n   if (!cert || !certSize) {\n      return NSSLError::InvalidArg;\n   }\n\n   if (certType != NSSLCertType::Unknown0) {\n      return NSSLError::InvalidCertType;\n   }\n\n   auto buf = internal::allocateIpcBuffer(0x88);\n   if (!buf) {\n      return NSSLError::OutOfMemory;\n   }\n\n   auto vec = virt_cast<IOSVec *>(buf);\n   vec[0].vaddr = virt_cast<virt_addr>(cert);\n   vec[0].len = certSize;\n\n   auto request = virt_cast<NSSLAddServerPKIExternalRequest *>(virt_cast<virt_addr>(buf) + 0x80);\n   vec[1].vaddr = virt_cast<virt_addr>(request);\n   vec[1].len = static_cast<uint32_t>(sizeof(NSSLAddServerPKIExternalRequest));\n\n   request->context = context;\n   request->certType = certType;\n\n   auto error = IOS_Ioctlv(sSslData->handle,\n                           NSSLCommand::AddServerPKIExternal,\n                           2, 0, vec);\n\n   internal::freeIpcBuffer(buf);\n   return static_cast<NSSLError>(error);\n}\n\nNSSLError\nNSSLExportInternalClientCertificate(NSSLCertID certId,\n                                    virt_ptr<uint8_t> certBuffer,\n                                    virt_ptr<uint32_t> certBufferSize,\n                                    virt_ptr<NSSLCertType> certType,\n                                    virt_ptr<uint8_t> privateKeyBuffer,\n                                    virt_ptr<uint32_t> privateKeyBufferSize,\n                                    virt_ptr<NSSLPrivateKeyType> privateKeyType)\n{\n   if (!internal::isInitialised()) {\n      return NSSLError::LibNotReady;\n   }\n\n   if (!certBufferSize || !certType || !privateKeyBufferSize || !privateKeyType) {\n      return NSSLError::InvalidArg;\n   }\n\n   auto buf = internal::allocateIpcBuffer(0x80 + sizeof(NSSLExportInternalClientCertificateRequest));\n   if (!buf) {\n      return NSSLError::OutOfMemory;\n   }\n\n   auto bufResponse = internal::allocateIpcBuffer(0x8);\n   if (!bufResponse) {\n      internal::freeIpcBuffer(buf);\n      return NSSLError::OutOfMemory;\n   }\n\n   auto request = virt_cast<NSSLExportInternalClientCertificateRequest *>(virt_cast<virt_addr>(buf) + 0x80);\n   request->certId = certId;\n\n   auto vec = virt_cast<IOSVec *>(buf);\n   vec[0].vaddr = virt_cast<virt_addr>(request);\n   vec[0].len = static_cast<uint32_t>(sizeof(NSSLExportInternalClientCertificateRequest));\n\n   vec[1].vaddr = virt_cast<virt_addr>(certBuffer);\n   vec[1].len = *certBufferSize;\n\n   vec[2].vaddr = virt_cast<virt_addr>(privateKeyBuffer);\n   vec[2].len = *privateKeyBufferSize;\n\n   auto response = virt_cast<NSSLExportInternalClientCertificateResponse *>(bufResponse);\n   vec[3].vaddr = virt_cast<virt_addr>(response);\n   vec[3].len = static_cast<uint32_t>(sizeof(NSSLExportInternalClientCertificateResponse));\n\n   auto error = IOS_Ioctlv(sSslData->handle,\n                           NSSLCommand::ExportInternalClientCertificate,\n                           1, 3, vec);\n   if (error >= IOSError::OK) {\n      *certType = response->certType;\n      *certBufferSize = response->certSize;\n      *privateKeyType = response->privateKeyType;\n      *privateKeyBufferSize = response->privateKeySize;\n   }\n\n   internal::freeIpcBuffer(buf);\n   internal::freeIpcBuffer(bufResponse);\n   return static_cast<NSSLError>(error);\n}\n\nNSSLError\nNSSLExportInternalServerCertificate(NSSLCertID certId,\n                                    virt_ptr<uint8_t> certBuffer,\n                                    virt_ptr<uint32_t> certBufferSize,\n                                    virt_ptr<NSSLCertType> certType)\n{\n   if (!internal::isInitialised()) {\n      return NSSLError::LibNotReady;\n   }\n\n   if (!certBufferSize || !certType) {\n      return NSSLError::InvalidArg;\n   }\n\n   auto buf = internal::allocateIpcBuffer(0x80 + sizeof(NSSLExportInternalServerCertificateRequest));\n   if (!buf) {\n      return NSSLError::OutOfMemory;\n   }\n\n   auto bufResponse = internal::allocateIpcBuffer(0x8);\n   if (!bufResponse) {\n      internal::freeIpcBuffer(buf);\n      return NSSLError::OutOfMemory;\n   }\n\n   auto request = virt_cast<NSSLExportInternalServerCertificateRequest *>(virt_cast<virt_addr>(buf) + 0x80);\n   request->certId = certId;\n\n   auto vec = virt_cast<IOSVec *>(buf);\n   vec[0].vaddr = virt_cast<virt_addr>(request);\n   vec[0].len = static_cast<uint32_t>(sizeof(NSSLExportInternalServerCertificateRequest));\n\n   vec[1].vaddr = virt_cast<virt_addr>(certBuffer);\n   vec[1].len = *certBufferSize;\n\n   auto response = virt_cast<NSSLExportInternalServerCertificateResponse *>(bufResponse);\n   vec[2].vaddr = virt_cast<virt_addr>(response);\n   vec[2].len = static_cast<uint32_t>(sizeof(NSSLExportInternalServerCertificateResponse));\n\n   auto error = IOS_Ioctlv(sSslData->handle,\n                           NSSLCommand::ExportInternalServerCertificate,\n                           1, 2, vec);\n   if (error >= IOSError::OK) {\n      *certType = response->certType;\n      *certBufferSize = response->certSize;\n   }\n\n   internal::freeIpcBuffer(buf);\n   internal::freeIpcBuffer(bufResponse);\n   return static_cast<NSSLError>(error);\n}\n\nnamespace internal\n{\n\nstatic bool\nisInitialised()\n{\n   auto initialised = true;\n   OSLockMutex(virt_addrof(sSslData->lock));\n\n   if (sSslData->handle < 0 || !sSslData->messagePool) {\n      initialised = false;\n   }\n\n   OSUnlockMutex(virt_addrof(sSslData->lock));\n   return initialised;\n}\n\nstatic virt_ptr<void>\nallocateIpcBuffer(uint32_t size)\n{\n   auto buf = IPCBufPoolAllocate(sSslData->messagePool, size);\n   if (buf) {\n      std::memset(buf.get(), 0, size);\n   }\n\n   return buf;\n}\n\nstatic void\nfreeIpcBuffer(virt_ptr<void> buf)\n{\n   IPCBufPoolFree(sSslData->messagePool, buf);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerSslSymbols()\n{\n   RegisterFunctionExport(NSSLInit);\n   RegisterFunctionExport(NSSLFinish);\n   RegisterFunctionExport(NSSLCreateContext);\n   RegisterFunctionExport(NSSLDestroyContext);\n   RegisterFunctionExport(NSSLAddServerPKI);\n   RegisterFunctionExport(NSSLAddServerPKIExternal);\n   RegisterFunctionExport(NSSLExportInternalClientCertificate);\n   RegisterFunctionExport(NSSLExportInternalServerCertificate);\n\n   RegisterDataInternal(sSslData);\n}\n\n} // namespace cafe::nsysnet\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_nssl.h",
    "content": "#pragma once\n#include \"ios/nsec/ios_nsec_nssl.h\"\n\nnamespace cafe::nsysnet\n{\n\nusing NSSLContextHandle = ios::nsec::NSSLContextHandle;\nusing NSSLError = ios::nsec::NSSLError;\nusing NSSLVersion = ios::nsec::NSSLVersion;\nusing NSSLCertID = ios::nsec::NSSLCertID;\nusing NSSLCertType = ios::nsec::NSSLCertType;\nusing NSSLPrivateKeyType = ios::nsec::NSSLPrivateKeyType;\n\nNSSLError\nNSSLInit();\n\nNSSLError\nNSSLFinish();\n\nNSSLError\nNSSLCreateContext(NSSLVersion version);\n\nNSSLError\nNSSLDestroyContext(NSSLContextHandle context);\n\nNSSLError\nNSSLAddServerPKI(NSSLContextHandle context,\n                 NSSLCertID certId);\n\nNSSLError\nNSSLAddServerPKIExternal(NSSLContextHandle context,\n                         virt_ptr<uint8_t> cert,\n                         uint32_t certSize,\n                         NSSLCertType certType);\n\nNSSLError\nNSSLExportInternalClientCertificate(NSSLCertID certId,\n                                    virt_ptr<uint8_t> certBuffer,\n                                    virt_ptr<uint32_t> certBufferSize,\n                                    virt_ptr<NSSLCertType> certType,\n                                    virt_ptr<uint8_t> privateKeyBuffer,\n                                    virt_ptr<uint32_t> privateKeyBufferSize,\n                                    virt_ptr<NSSLPrivateKeyType> privateKeyType);\n\nNSSLError\nNSSLExportInternalServerCertificate(NSSLCertID certId,\n                                    virt_ptr<uint8_t> certBuffer,\n                                    virt_ptr<uint32_t> certBufferSize,\n                                    virt_ptr<NSSLCertType> certType);\n\n} // namespace cafe::nsysnet\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_socket_lib.cpp",
    "content": "#include \"nsysnet.h\"\n#include \"nsysnet_enum.h\"\n#include \"nsysnet_socket_lib.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_ghs.h\"\n#include \"cafe/libraries/coreinit/coreinit_ios.h\"\n#include \"cafe/libraries/coreinit/coreinit_memdefaultheap.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n#include \"cafe/libraries/coreinit/coreinit_ipcbufpool.h\"\n#include \"ios/net/ios_net_socket.h\"\n#include \"ios/ios_error.h\"\n\n#include <charconv>\n#include <common/strutils.h>\n#include <optional>\n#include <string_view>\n\nnamespace cafe::nsysnet\n{\n\nusing namespace coreinit;\n\nusing ios::net::SocketAddr;\nusing ios::net::SocketAddrIn;\nusing ios::net::SocketCommand;\nusing ios::net::SocketDnsQueryType;\nusing ios::net::SocketError;\nusing ios::net::SocketFamily;\n\nusing ios::net::SocketAcceptRequest;\nusing ios::net::SocketBindRequest;\nusing ios::net::SocketCloseRequest;\nusing ios::net::SocketConnectRequest;\nusing ios::net::SocketDnsQueryRequest;\nusing ios::net::SocketGetPeerNameRequest;\nusing ios::net::SocketGetSockNameRequest;\nusing ios::net::SocketListenRequest;\nusing ios::net::SocketRecvRequest;\nusing ios::net::SocketSendRequest;\nusing ios::net::SocketSetSockOptRequest;\nusing ios::net::SocketSelectRequest;\nusing ios::net::SocketSocketRequest;\n\nusing ios::net::SocketAcceptResponse;\nusing ios::net::SocketGetPeerNameResponse;\nusing ios::net::SocketGetSockNameResponse;\nusing ios::net::SocketSelectResponse;\n\n// We do not get this from ios::net because that uses phys_ptr, and here we\n// want virt_ptr.\nstruct SocketDnsQueryResponse\n{\n   be2_val<uint32_t> unk0x00;\n   be2_val<uint32_t> unk0x04;\n   be2_val<uint32_t> sendTime;\n   be2_val<uint32_t> expireTime;\n   be2_val<uint16_t> tries;\n   be2_val<uint16_t> lport;\n   be2_val<uint16_t> id;\n   UNKNOWN(0x2);\n   be2_val<uint32_t> unk0x18;\n   be2_val<uint32_t> replies;\n   be2_val<uint32_t> ipaddrs;\n   be2_array<uint32_t, 10> ipaddrList;\n   be2_array<virt_ptr<uint32_t>, 10> hostentIpaddrList;\n   be2_val<uint32_t> err;\n   be2_val<uint32_t> rcode;\n   be2_array<char, 256> dnsNames;\n   be2_array<char, 129> unk0x17C;\n   UNKNOWN(0x27C - 0x1FD);\n   be2_val<uint32_t> authsIp;\n   be2_array<virt_ptr<char>, 2> aliases;\n   UNKNOWN(0x290 - 0x288);\n   be2_struct<SocketHostEnt> hostent;\n   be2_val<SocketDnsQueryType> queryType;\n   be2_array<uint8_t, 2> unk0x2A5;\n   UNKNOWN(0x2B0 - 0x2A7);\n   be2_val<uint32_t> dnsReq;\n   be2_val<uint32_t> next;\n\n   //! Used to adjust pointers in hostent\n   be2_val<virt_addr> selfPointerOffset;\n};\nCHECK_OFFSET(SocketDnsQueryResponse, 0x00, unk0x00);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x04, unk0x04);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x08, sendTime);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x0C, expireTime);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x10, tries);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x12, lport);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x14, id);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x18, unk0x18);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x1C, replies);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x20, ipaddrs);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x24, ipaddrList);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x4C, hostentIpaddrList);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x74, err);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x78, rcode);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x7C, dnsNames);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x17C, unk0x17C);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x27C, authsIp);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x280, aliases);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x290, hostent);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x2A4, queryType);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x2A5, unk0x2A5);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x2B0, dnsReq);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x2B4, next);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x2B8, selfPointerOffset);\nCHECK_SIZE(SocketDnsQueryResponse, 0x2BC);\n\nstruct SocketLibData\n{\n   static constexpr uint32_t MessageCount = 0x20;\n   static constexpr uint32_t MessageSize = 0x100;\n\n   SocketLibData()\n   {\n      OSInitMutex(virt_addrof(lock));\n   }\n\n   be2_struct<OSMutex> lock;\n   be2_val<IOSHandle> handle = IOSHandle { -1 };\n   be2_virt_ptr<IPCBufPool> messagePool;\n   be2_array<uint8_t, MessageCount * MessageSize> messageBuffer;\n   be2_val<uint32_t> messageCount;\n   be2_val<ResolverAlloc> userResolverAlloc;\n   be2_val<ResolverFree> userResolverFree;\n   be2_val<GetHostError> getHostError;\n\n   be2_struct<SocketHostEnt> getHostByNameHostEnt;\n   be2_array<char, 32> getHostByNameName;\n   be2_val<uint32_t> getHostByNameIpAddr;\n   be2_array<virt_ptr<uint32_t>, 2> getHostByNameAddrList;\n   be2_struct<SocketDnsQueryResponse> getHostByNameQuery;\n};\n\nstatic virt_ptr<SocketLibData>\nsSocketLibData;\n\nnamespace internal\n{\n\nstatic bool\nisInitialised();\n\nstatic virt_ptr<void>\nallocateIpcBuffer(uint32_t size);\n\nstatic void\nfreeIpcBuffer(virt_ptr<void> buf);\n\nstatic int32_t\ndecodeIosError(IOSError err);\n\nstatic int32_t\nperformDnsQuery(virt_ptr<const char> name,\n                SocketDnsQueryType queryType,\n                uint32_t a3, uint32_t a4,\n                virt_ptr<SocketDnsQueryResponse> outResponse,\n                bool isAsync);\n\n} // namespace internal\n\nint32_t\nsocket_lib_init()\n{\n   auto error = 0;\n   OSLockMutex(virt_addrof(sSocketLibData->lock));\n\n   if (sSocketLibData->handle < 0) {\n      auto iosError = IOS_Open(make_stack_string(\"/dev/socket\"),\n                               IOSOpenMode::None);\n\n      if (iosError < 0) {\n         sSocketLibData->handle = -1;\n         error = SocketError::NoLibRm;\n         goto out;\n      }\n\n      sSocketLibData->handle = static_cast<IOSHandle>(iosError);\n   }\n\n   if (!sSocketLibData->messagePool) {\n      sSocketLibData->messagePool =\n         IPCBufPoolCreate(virt_addrof(sSocketLibData->messageBuffer),\n                          static_cast<uint32_t>(sSocketLibData->messageBuffer.size()),\n                          SocketLibData::MessageSize,\n                          virt_addrof(sSocketLibData->messageCount),\n                          1);\n\n      if (!sSocketLibData->messagePool) {\n         error = SocketError::NoMem;\n         IOS_Close(sSocketLibData->handle);\n         sSocketLibData->handle = -1;\n         goto out;\n      }\n   }\n\nout:\n   OSUnlockMutex(virt_addrof(sSocketLibData->lock));\n   gh_set_errno(error);\n   return error == 0 ? 0 : -1;\n}\n\n\nint32_t\nsocket_lib_finish()\n{\n   auto error = 0;\n   OSLockMutex(virt_addrof(sSocketLibData->lock));\n\n   if (sSocketLibData->handle >= 0) {\n      IOS_Close(sSocketLibData->handle);\n      sSocketLibData->handle = -1;\n   } else {\n      error = SocketError::NoLibRm;\n   }\n\n   OSUnlockMutex(virt_addrof(sSocketLibData->lock));\n   gh_set_errno(error);\n   return error == 0 ? 0 : -1;\n}\n\n\nint32_t\nset_resolver_allocator(ResolverAlloc allocFn,\n                       ResolverFree freeFn)\n{\n   if (!allocFn || !freeFn) {\n      return -1;\n   }\n\n   sSocketLibData->userResolverAlloc = allocFn;\n   sSocketLibData->userResolverFree = freeFn;\n   return 0;\n}\n\n\nint32_t\naccept(int32_t sockfd,\n       virt_ptr<SocketAddr> addr,\n       virt_ptr<int32_t> addrlen)\n{\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (addr && (!addrlen || *addrlen != sizeof(SocketAddrIn))) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(SocketAcceptRequest));\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto request = virt_cast<SocketAcceptRequest *>(buf);\n   request->fd = sockfd;\n\n   if (addrlen) {\n      request->addrlen = *addrlen;\n   } else {\n      request->addrlen = static_cast<int32_t>(sizeof(SocketAddrIn));\n   }\n\n   auto error = IOS_Ioctl(sSocketLibData->handle,\n                          SocketCommand::Accept,\n                          request,\n                          sizeof(SocketAcceptRequest),\n                          request,\n                          sizeof(SocketAcceptResponse));\n   if (error >= IOSError::OK) {\n      auto response = virt_cast<SocketAcceptRequest *>(buf);\n      if (addr) {\n         std::memcpy(addr.get(), std::addressof(response->addr),\n                     response->addrlen);\n         *addrlen = response->addrlen;\n      }\n   }\n\n   auto result = internal::decodeIosError(error);\n\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\n\nint32_t\nbind(int32_t sockfd,\n     virt_ptr<SocketAddr> addr,\n     int32_t addrlen)\n{\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (!addr || addr->sa_family != SocketFamily::Inet || addrlen != sizeof(SocketAddrIn)) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(SocketBindRequest));\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto request = virt_cast<SocketBindRequest *>(buf);\n   request->fd = sockfd;\n   request->addr = *virt_cast<SocketAddrIn *>(addr);\n   request->addrlen = addrlen;\n\n   auto error = IOS_Ioctl(sSocketLibData->handle,\n                                    SocketCommand::Bind,\n                                    request,\n                                    sizeof(SocketBindRequest),\n                                    NULL,\n                                    0);\n\n   auto result = internal::decodeIosError(error);\n\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\n\nint32_t\nconnect(int32_t sockfd,\n        virt_ptr<SocketAddr> addr,\n        int32_t addrlen)\n{\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (!addr || addr->sa_family != SocketFamily::Inet || addrlen != sizeof(SocketAddrIn)) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   // TODO: if set_multicast_state(TRUE)\n\n   auto buf = internal::allocateIpcBuffer(sizeof(SocketConnectRequest));\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto request = virt_cast<SocketConnectRequest *>(buf);\n   request->fd = sockfd;\n   request->addr = *virt_cast<SocketAddrIn *>(addr);\n   request->addrlen = addrlen;\n\n   auto error = IOS_Ioctl(sSocketLibData->handle,\n                          SocketCommand::Connect,\n                          request,\n                          sizeof(SocketConnectRequest),\n                          NULL,\n                          0);\n\n   auto result = internal::decodeIosError(error);\n\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\n\n/**\n * Parse IP address from a string.\n *\n * Similar to inet_aton but different:\n * - Requires 1-3 dots\n * - Only supports 0-255 for each number\n *\n * Which means unlike inet_aton:\n * - a       = invalid\n * - a.b     = a.0.0.b\n * - a.b.c   = a.0.b.c\n * - a.b.c.d = a.b.c.d\n */\nstatic std::optional<uint32_t>\nparseIpAddress(const char *host)\n{\n   if (!host) {\n      return {};\n   }\n\n   auto len = strnlen(host, 256);\n   if (!len || len == 256) {\n      return {};\n   }\n\n   auto sv = std::string_view { host, len };\n\n   // Count dots and ensure no invalid characters for an IP address\n   auto numDots = 0;\n   for (auto c : sv) {\n      if (c == '.') {\n         numDots++;\n      } else if (c < '0' || c > '9') {\n         return {};\n      }\n   }\n\n   if (numDots < 1 || numDots > 3) {\n      return {};\n   }\n\n   auto start = std::string_view::size_type { 0 };\n   auto ip = std::array<uint8_t, 4> { 0, 0, 0, 0 };\n\n   for (auto idx = 0; idx < numDots + 1; ++idx) {\n      auto component = 0;\n      auto end = std::min(sv.find_first_of('.', start), sv.size());\n      auto result = std::from_chars(sv.data() + start, sv.data() + end, component);\n      if (result.ec != std::errc() || component < 0 || component > 255) {\n         return {};\n      }\n\n      if (idx == 0) {\n         ip[idx] = static_cast<uint8_t>(component);\n      } else {\n         ip[idx + (3 - numDots)] = static_cast<uint8_t>(component);\n      }\n\n      start = end + 1;\n   }\n\n   auto output = uint32_t { 0 };\n   std::memcpy(&output, ip.data(), 4);\n   return output;\n}\n\nvirt_ptr<GetHostError>\nget_h_errno()\n{\n   return virt_addrof(sSocketLibData->getHostError);\n}\n\nvirt_ptr<SocketHostEnt>\ngethostbyname(virt_ptr<const char> name)\n{\n   if (!name || !name[0]) {\n      sSocketLibData->getHostError = GetHostError::NoRecovery;\n      return nullptr;\n   }\n\n   // If the host name is just an ip address we can return immediately.\n   if (auto ipaddr = parseIpAddress(name.get()); ipaddr.has_value()) {\n      auto result = virt_addrof(sSocketLibData->getHostByNameHostEnt);\n      result->h_aliases = nullptr;\n      result->h_length = 4;\n      result->h_addrtype = 2;\n\n      result->h_name = virt_addrof(sSocketLibData->getHostByNameName);\n      string_copy(result->h_name.get(), name.get(), 32);\n\n      result->h_addr_list = virt_addrof(sSocketLibData->getHostByNameAddrList);\n      result->h_addr_list[0] = virt_addrof(sSocketLibData->getHostByNameIpAddr);\n      result->h_addr_list[1] = nullptr;\n      sSocketLibData->getHostByNameIpAddr = ipaddr.value();\n      return result;\n   }\n\n   auto error = internal::performDnsQuery(\n      name, SocketDnsQueryType::GetHostByName, 0u, 0u,\n      virt_addrof(sSocketLibData->getHostByNameQuery), false);\n   if (error < 0 || !sSocketLibData->getHostByNameQuery.ipaddrs) {\n      sSocketLibData->getHostError = GetHostError::HostNotFound;\n      return nullptr;\n   }\n\n   // Update ip address list\n   sSocketLibData->getHostByNameQuery.hostent.h_addrtype = 2;\n   sSocketLibData->getHostByNameQuery.hostent.h_length = 4;\n   sSocketLibData->getHostByNameQuery.hostent.h_addr_list =\n      virt_addrof(sSocketLibData->getHostByNameQuery.hostentIpaddrList);\n   for (auto i = 0u; i < sSocketLibData->getHostByNameQuery.ipaddrs; ++i) {\n      sSocketLibData->getHostByNameQuery.hostentIpaddrList[i] =\n         virt_addrof(sSocketLibData->getHostByNameQuery.ipaddrList[i]);\n   }\n\n   sSocketLibData->getHostByNameQuery\n      .hostentIpaddrList[sSocketLibData->getHostByNameQuery.ipaddrs] = nullptr;\n   sSocketLibData->getHostError = GetHostError::OK;\n   return virt_addrof(sSocketLibData->getHostByNameQuery.hostent);\n}\n\nint32_t\ngetpeername(int32_t sockfd,\n            virt_ptr<SocketAddr> addr,\n            virt_ptr<uint32_t> addrlen)\n{\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (!addr || !addrlen || *addrlen < sizeof(SocketAddrIn)) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(SocketGetPeerNameRequest));\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto response = virt_cast<SocketGetPeerNameRequest *>(buf);\n   auto request = virt_cast<SocketGetPeerNameResponse *>(buf);\n   request->fd = sockfd;\n   request->addrlen = *addrlen;\n\n   auto error = IOS_Ioctl(sSocketLibData->handle,\n                          SocketCommand::GetPeerName,\n                          request,\n                          sizeof(SocketGetPeerNameRequest),\n                          response,\n                          sizeof(SocketGetPeerNameResponse));\n   if (error >= IOSError::OK) {\n      std::memcpy(addr.get(), std::addressof(response->addr), response->addrlen);\n      *addrlen = response->addrlen;\n   }\n\n   auto result = internal::decodeIosError(error);\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\nint32_t\ngetsockname(int32_t sockfd,\n            virt_ptr<SocketAddr> addr,\n            virt_ptr<uint32_t> addrlen)\n{\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (!addr || !addrlen || *addrlen < sizeof(SocketAddrIn)) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(SocketGetSockNameRequest));\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto response = virt_cast<SocketGetSockNameRequest *>(buf);\n   auto request = virt_cast<SocketGetSockNameResponse *>(buf);\n   request->fd = sockfd;\n   request->addrlen = *addrlen;\n\n   auto error = IOS_Ioctl(sSocketLibData->handle,\n                          SocketCommand::GetSockName,\n                          request,\n                          sizeof(SocketGetSockNameRequest),\n                          response,\n                          sizeof(SocketGetSockNameResponse));\n   if (error >= IOSError::OK) {\n      std::memcpy(addr.get(), std::addressof(response->addr), response->addrlen);\n      *addrlen = response->addrlen;\n   }\n\n   auto result = internal::decodeIosError(error);\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\nint32_t\nlisten(int32_t sockfd,\n       int32_t backlog)\n{\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (backlog < 0) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(SocketListenRequest));\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto request = virt_cast<SocketListenRequest *>(buf);\n   request->fd = sockfd;\n   request->backlog = backlog;\n\n   auto error = IOS_Ioctl(sSocketLibData->handle,\n                          SocketCommand::Listen,\n                          request,\n                          sizeof(SocketListenRequest),\n                          NULL,\n                          0);\n\n   auto result = internal::decodeIosError(error);\n\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\nstatic void\nprepareUnalignedBuffer(virt_ptr<const uint8_t> buffer,\n                       int32_t len,\n                       virt_ptr<uint8_t> alignedBeforeBuffer,\n                       virt_ptr<uint8_t> alignedAfterBuffer,\n                       virt_ptr<IOSVec> alignedBeforeVec,\n                       virt_ptr<IOSVec> alignedMiddleVec,\n                       virt_ptr<IOSVec> alignedAfterVec,\n                       bool input)\n{\n   auto bufferAlignedStart = align_up(buffer, IOSVecAlign);\n   auto bufferAlignedEnd = align_down(buffer + len, IOSVecAlign);\n   auto bufferEnd = buffer + len;\n\n   if (bufferAlignedStart != buffer) {\n      alignedBeforeVec->vaddr = virt_cast<virt_addr>(alignedBeforeBuffer);\n      alignedBeforeVec->len = static_cast<uint32_t>(bufferAlignedStart - buffer);\n\n      if (input) {\n         std::memcpy(alignedBeforeBuffer.get(),\n                     buffer.get(),\n                     alignedBeforeVec->len);\n      }\n   }\n\n   alignedMiddleVec->vaddr = virt_cast<virt_addr>(bufferAlignedStart);\n   alignedMiddleVec->len = static_cast<uint32_t>(bufferEnd - bufferAlignedStart);\n\n   if (bufferAlignedEnd != bufferEnd) {\n      alignedAfterVec->vaddr = virt_cast<virt_addr>(alignedAfterBuffer);\n      alignedAfterVec->len = static_cast<uint32_t>(bufferEnd - bufferAlignedEnd);\n\n      if (input) {\n         std::memcpy(alignedAfterBuffer.get(),\n                     bufferAlignedEnd.get(),\n                     alignedAfterVec->len);\n      }\n   }\n}\n\nstatic void\nparseUnalignedBuffer(virt_ptr<uint8_t> buffer,\n                     int32_t len,\n                     virt_ptr<IOSVec> alignedBeforeVec,\n                     virt_ptr<IOSVec> alignedMiddleVec,\n                     virt_ptr<IOSVec> alignedAfterVec)\n{\n   if (alignedBeforeVec->len) {\n      std::memcpy(buffer.get(),\n                  virt_cast<void *>(alignedBeforeVec->vaddr).get(),\n                  alignedBeforeVec->len);\n   }\n\n   if (alignedAfterVec->len) {\n      auto offset = alignedBeforeVec->len + alignedMiddleVec->len;\n      std::memcpy(buffer.get() + offset,\n                  virt_cast<void *>(alignedAfterVec->vaddr).get(),\n                  alignedAfterVec->len);\n   }\n}\n\nint32_t\nrecv(int32_t sockfd,\n     virt_ptr<void> buffer,\n     int32_t len,\n     int32_t flags)\n{\n   auto alignedBuffers = StackArray<uint8_t, IOSVecAlign * 2, IOSVecAlign> { };\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (!buffer || len < 0) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   auto buf = internal::allocateIpcBuffer(0x88);\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto request = virt_cast<SocketRecvRequest *>(virt_cast<char *>(buf) + 0x80);\n   request->fd = sockfd;\n   request->flags = flags;\n\n   auto vecs = virt_cast<IOSVec *>(buf);\n   vecs[0].vaddr = virt_cast<virt_addr>(request);\n   vecs[0].len = static_cast<uint32_t>(sizeof(SocketRecvRequest));\n\n   vecs[1].vaddr = virt_addr { 0 };\n   vecs[1].len = 0u;\n\n   vecs[2].vaddr = virt_addr { 0 };\n   vecs[2].len = 0u;\n\n   vecs[3].vaddr = virt_addr{ 0 };\n   vecs[3].len = 0u;\n\n   if (align_check(buffer.get(), IOSVecAlign) && align_check(len, IOSVecAlign)) {\n      vecs[1].vaddr = virt_cast<virt_addr>(buffer);\n      vecs[1].len = static_cast<uint32_t>(len);\n   } else {\n      auto alignedBeforeBuffer = virt_ptr<uint8_t> { alignedBuffers };\n      auto alignedAfterBuffer = alignedBeforeBuffer + IOSVecAlign;\n\n      prepareUnalignedBuffer(virt_cast<uint8_t *>(buffer), len,\n                             alignedBeforeBuffer, alignedAfterBuffer,\n                             virt_addrof(vecs[1]), virt_addrof(vecs[2]),\n                             virt_addrof(vecs[3]),\n                             false);\n   }\n\n   auto error = IOS_Ioctlv(sSocketLibData->handle,\n                           SocketCommand::Recv,\n                           1, 3, vecs);\n   if (error >= IOSError::OK) {\n      parseUnalignedBuffer(virt_cast<uint8_t *>(buffer), len,\n                           virt_addrof(vecs[1]), virt_addrof(vecs[2]),\n                           virt_addrof(vecs[3]));\n   }\n\n   auto result = internal::decodeIosError(error);\n\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\n\nint32_t\nsend(int32_t sockfd,\n     virt_ptr<const void> buffer,\n     int32_t len,\n     int32_t flags)\n{\n   auto alignedBuffers = StackArray<uint8_t, IOSVecAlign * 2, IOSVecAlign> { };\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (!buffer || len < 0) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   auto buf = internal::allocateIpcBuffer(0x88);\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto request = virt_cast<SocketSendRequest *>(virt_cast<char *>(buf) + 0x80);\n   request->fd = sockfd;\n   request->flags = flags;\n\n   auto vecs = virt_cast<IOSVec *>(buf);\n   vecs[0].vaddr = virt_cast<virt_addr>(request);\n   vecs[0].len = static_cast<uint32_t>(sizeof(SocketSendRequest));\n\n   vecs[1].vaddr = virt_addr { 0 };\n   vecs[1].len = 0u;\n\n   vecs[2].vaddr = virt_addr { 0 };\n   vecs[2].len = 0u;\n\n   vecs[3].vaddr = virt_addr{ 0 };\n   vecs[3].len = 0u;\n\n   if (align_check(buffer.get(), IOSVecAlign) && align_check(len, IOSVecAlign)) {\n      vecs[1].vaddr = virt_cast<virt_addr>(buffer);\n      vecs[1].len = static_cast<uint32_t>(len);\n   } else {\n      auto alignedBeforeBuffer = virt_ptr<uint8_t> { alignedBuffers };\n      auto alignedAfterBuffer = alignedBeforeBuffer + IOSVecAlign;\n\n      prepareUnalignedBuffer(virt_cast<const uint8_t *>(buffer), len,\n                             alignedBeforeBuffer, alignedAfterBuffer,\n                             virt_addrof(vecs[1]), virt_addrof(vecs[2]),\n                             virt_addrof(vecs[3]),\n                             true);\n   }\n\n   auto error = IOS_Ioctlv(sSocketLibData->handle,\n                           SocketCommand::Send,\n                           4, 0, vecs);\n   auto result = internal::decodeIosError(error);\n\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\n\nint32_t\nsetsockopt(int32_t sockfd,\n           int32_t level,\n           int32_t optname,\n           virt_ptr<void> optval,\n           int32_t optlen)\n{\n   StackArray<uint8_t, 0x40, IOSVecAlign> optvalBuffer;\n\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (level != -1 && level != 0 && level != 6) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   if (optlen < 0 || static_cast<unsigned>(optlen) > optvalBuffer.size()) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   if (!optval && optlen > 0) {\n      gh_set_errno(SocketError::Inval);\n      return -1;\n   }\n\n   auto buf = internal::allocateIpcBuffer(0x24);\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   // Yes they really do overlap IOSVec and SetOptRequest... :)\n   auto request = virt_cast<SocketSetSockOptRequest *>(buf);\n   request->fd = sockfd;\n   request->level = level;\n   request->optname = optname;\n\n   auto vecs = virt_cast<IOSVec *>(buf);\n   vecs[0].vaddr = virt_cast<virt_addr>(optvalBuffer);\n   vecs[0].len = static_cast<uint32_t>(optlen);\n   std::memcpy(optvalBuffer.get(), optval.get(), optlen);\n\n   vecs[1].vaddr = virt_cast<virt_addr>(request);\n   vecs[1].len = static_cast<uint32_t>(sizeof(SocketSetSockOptRequest));\n\n   auto error = IOS_Ioctlv(sSocketLibData->handle,\n                           SocketCommand::SetSockOpt,\n                           2, 0, vecs);\n   auto result = internal::decodeIosError(error);\n\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\n\nint32_t\nselect(int32_t nfds,\n       virt_ptr<SocketFdSet> readfds,\n       virt_ptr<SocketFdSet> writefds,\n       virt_ptr<SocketFdSet> exceptfds,\n       virt_ptr<SocketTimeval> timeout)\n{\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (nfds < 0) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   if (timeout) {\n      if (timeout->tv_sec < 0 || timeout->tv_usec < 0 ||\n         timeout->tv_sec > 1000000 || timeout->tv_usec > 1000000) {\n         gh_set_errno(SocketError::Inval);\n         return -1;\n      }\n   }\n\n   if ((!readfds || !*readfds) && (!writefds || !*writefds) && (!exceptfds || !*exceptfds)) {\n      if (!timeout) {\n         gh_set_errno(SocketError::Inval);\n         return -1;\n      } else if (timeout->tv_sec == 0 && timeout->tv_usec == 0) {\n         // No fds and timeout is 0 = success\n         return 0;\n      }\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(SocketSelectRequest));\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto response = virt_cast<SocketSelectResponse *>(buf);\n   auto request = virt_cast<SocketSelectRequest *>(buf);\n   request->nfds = nfds;\n   request->readfds = 0u;\n   request->writefds = 0u;\n   request->exceptfds = 0u;\n   request->hasTimeout = 0;\n\n   if (readfds) {\n      request->readfds = *readfds;\n   }\n\n   if (writefds) {\n      request->writefds = *writefds;\n   }\n\n   if (exceptfds) {\n      request->exceptfds = *exceptfds;\n   }\n\n   if (timeout) {\n      request->timeout = *timeout;\n      request->hasTimeout = 1;\n   }\n\n   auto result = 0;\n   auto error = IOS_Ioctl(sSocketLibData->handle,\n                          SocketCommand::Select,\n                          request,\n                          sizeof(SocketSelectRequest),\n                          response,\n                          sizeof(SocketSelectResponse));\n\n   result = internal::decodeIosError(error);\n   if (result >= 0) {\n      if (readfds) {\n         *readfds = response->readfds;\n      }\n\n      if (writefds) {\n         *writefds = response->writefds;\n      }\n\n      if (exceptfds) {\n         *exceptfds = response->exceptfds;\n      }\n   }\n\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\n\nint32_t\nsocket(int32_t family,\n       int32_t type,\n       int32_t proto)\n{\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(SocketSocketRequest));\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto request = virt_cast<SocketSocketRequest *>(buf);\n   request->family = family;\n   request->type = type;\n   request->proto = proto;\n\n   auto result = 0;\n   auto error = IOS_Ioctl(sSocketLibData->handle,\n                          SocketCommand::Socket,\n                          request,\n                          sizeof(SocketSocketRequest),\n                          NULL,\n                          0);\n\n   if (ios::getErrorCategory(error) == ios::ErrorCategory::Socket\n    && ios::getErrorCode(error) == SocketError::GenericError) {\n      // Map generic socket error to no memory.\n      gh_set_errno(SocketError::NoMem);\n      result = -1;\n   } else {\n      result = internal::decodeIosError(error);\n   }\n\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\n\nint32_t\nsocketclose(int32_t sockfd)\n{\n   if (!internal::isInitialised()) {\n      gh_set_errno(SocketError::NotInitialised);\n      return -1;\n   }\n\n   auto buf = internal::allocateIpcBuffer(sizeof(SocketCloseRequest));\n   if (!buf) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   auto request = virt_cast<SocketCloseRequest *>(buf);\n   request->fd = sockfd;\n\n   auto error = IOS_Ioctl(sSocketLibData->handle,\n                          SocketCommand::Close,\n                          request,\n                          sizeof(SocketCloseRequest),\n                          NULL,\n                          0);\n\n   auto result = internal::decodeIosError(error);\n   internal::freeIpcBuffer(buf);\n   return result;\n}\n\nint32_t\nsocketlasterr()\n{\n   return gh_get_errno();\n}\n\nnamespace internal\n{\n\nstatic bool\nisInitialised()\n{\n   auto initialised = true;\n   OSLockMutex(virt_addrof(sSocketLibData->lock));\n\n   if (sSocketLibData->handle < 0 || !sSocketLibData->messagePool) {\n      initialised = false;\n   }\n\n   OSUnlockMutex(virt_addrof(sSocketLibData->lock));\n   return initialised;\n}\n\nstatic virt_ptr<void>\nallocateIpcBuffer(uint32_t size)\n{\n   auto buf = IPCBufPoolAllocate(sSocketLibData->messagePool, size);\n   if (buf) {\n      std::memset(buf.get(), 0, size);\n   }\n\n   return buf;\n}\n\nstatic void\nfreeIpcBuffer(virt_ptr<void> buf)\n{\n   IPCBufPoolFree(sSocketLibData->messagePool, buf);\n}\n\nstatic int32_t\ndecodeIosError(IOSError err)\n{\n   if (err >= 0) {\n      gh_set_errno(0);\n      return err;\n   }\n\n   auto category = ios::getErrorCategory(err);\n   auto code = ios::getErrorCode(err);\n   auto error = SocketError::Unknown;\n\n   switch (category) {\n   case ios::ErrorCategory::Socket:\n      error = static_cast<SocketError>(code);\n      break;\n   case ios::ErrorCategory::Kernel:\n      if (code == ios::Error::Access) {\n         error = SocketError::Inval;\n      } else if (code == ios::Error::Intr) {\n         error = SocketError::Aborted;\n      } else if (code == ios::Error::QFull) {\n         error = SocketError::Busy;\n      } else {\n         error = SocketError::Unknown;\n      }\n      break;\n   default:\n      error = SocketError::Unknown;\n   }\n\n   gh_set_errno(error);\n   return -1;\n}\n\nint32_t\nperformDnsQuery(virt_ptr<const char> name,\n                SocketDnsQueryType queryType,\n                uint32_t a3,\n                uint32_t a4,\n                virt_ptr<SocketDnsQueryResponse> outResponse,\n                bool isAsync)\n{\n   auto size = 1152u;\n   auto buffer = virt_ptr<void> { nullptr };\n   if (sSocketLibData->userResolverAlloc) {\n      buffer = cafe::invoke(cpu::this_core::state(),\n                            sSocketLibData->userResolverAlloc,\n                            size + (IOSVecAlign - 1));\n      buffer = align_up(buffer, IOSVecAlign);\n   } else {\n      buffer = MEMAllocFromDefaultHeapEx(size, IOSVecAlign);\n   }\n\n   if (!buffer) {\n      gh_set_errno(SocketError::NoMem);\n      return -1;\n   }\n\n   std::memset(buffer.get(), 0, size);\n\n   auto request = virt_cast<SocketDnsQueryRequest *>(virt_cast<char *>(buffer) + 0x80);\n   string_copy(virt_addrof(request->name).get(), name.get(), request->name.size());\n   request->queryType = queryType;\n   request->unk0x88 = a3;\n   request->unk0x8C = a4;\n   request->isAsync = isAsync ? uint8_t { 1 } : uint8_t { 0 };\n\n   auto response = virt_cast<SocketDnsQueryResponse *>(virt_cast<char *>(buffer) + 0x180);\n\n   auto vecs = virt_cast<IOSVec *>(buffer);\n   vecs[0].vaddr = virt_cast<virt_addr>(request);\n   vecs[0].len = static_cast<uint32_t>(sizeof(SocketDnsQueryRequest));\n\n   vecs[1].vaddr = virt_cast<virt_addr>(response);\n   vecs[1].len = static_cast<uint32_t>(sizeof(SocketDnsQueryResponse));\n\n   auto error = IOS_Ioctlv(sSocketLibData->handle,\n                           SocketCommand::DnsQuery,\n                           1, 1, vecs);\n   std::memcpy(outResponse.get(), response.get(), sizeof(SocketDnsQueryResponse));\n\n   // Repair hostent pointers\n   outResponse->hostent.h_aliases = virt_addrof(outResponse->aliases);\n\n   for (auto i = 0u; i < outResponse->aliases.size(); ++i) {\n      if (outResponse->aliases[i]) {\n         outResponse->aliases[i] = virt_cast<char *>(\n            virt_cast<virt_addr>(outResponse) +\n            (virt_cast<virt_addr>(outResponse->aliases[i]) - outResponse->selfPointerOffset));\n      }\n   }\n\n   outResponse->hostent.h_name = virt_cast<char *>(\n      virt_cast<virt_addr>(outResponse) +\n      (virt_cast<virt_addr>(outResponse->hostent.h_name) - outResponse->selfPointerOffset));\n\n   if (sSocketLibData->userResolverFree) {\n      cafe::invoke(cpu::this_core::state(),\n                   sSocketLibData->userResolverFree,\n                   buffer);\n   } else {\n      MEMFreeToDefaultHeap(buffer);\n   }\n\n   return error;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerSocketLibSymbols()\n{\n   RegisterFunctionExport(socket_lib_init);\n   RegisterFunctionExport(socket_lib_finish);\n   RegisterFunctionExport(accept);\n   RegisterFunctionExport(bind);\n   RegisterFunctionExport(connect);\n   RegisterFunctionExport(get_h_errno);\n   RegisterFunctionExport(gethostbyname);\n   RegisterFunctionExport(getpeername);\n   RegisterFunctionExport(getsockname);\n   RegisterFunctionExport(listen);\n   RegisterFunctionExport(recv);\n   RegisterFunctionExport(send);\n   RegisterFunctionExport(set_resolver_allocator);\n   RegisterFunctionExport(setsockopt);\n   RegisterFunctionExport(select);\n   RegisterFunctionExport(socket);\n   RegisterFunctionExport(socketclose);\n   RegisterFunctionExport(socketlasterr);\n\n   RegisterDataInternal(sSocketLibData);\n}\n\n} // namespace cafe::nsysnet\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysnet/nsysnet_socket_lib.h",
    "content": "#pragma once\n#include \"ios/net/ios_net_socket.h\"\n#include \"nsysnet_enum.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::nsysnet\n{\n\nusing ios::net::SocketAddr;\nusing ios::net::SocketFdSet;\nusing ios::net::SocketTimeval;\n\nstruct SocketHostEnt\n{\n   be2_virt_ptr<char> h_name;\n   be2_virt_ptr<virt_ptr<char>> h_aliases;\n   be2_val<int32_t> h_addrtype;\n   be2_val<int32_t> h_length;\n   be2_virt_ptr<virt_ptr<uint32_t>> h_addr_list;\n};\nCHECK_OFFSET(SocketHostEnt, 0x00, h_name);\nCHECK_OFFSET(SocketHostEnt, 0x04, h_aliases);\nCHECK_OFFSET(SocketHostEnt, 0x08, h_addrtype);\nCHECK_OFFSET(SocketHostEnt, 0x0C, h_length);\nCHECK_OFFSET(SocketHostEnt, 0x10, h_addr_list);\nCHECK_SIZE(SocketHostEnt, 0x14);\n\nusing ResolverAlloc = virt_func_ptr<virt_ptr<void>(uint32_t)>;\nusing ResolverFree = virt_func_ptr<void(virt_ptr<void>)>;\n\nint32_t\nsocket_lib_init();\n\nint32_t\nsocket_lib_finish();\n\nint32_t\nset_resolver_allocator(ResolverAlloc allocFn,\n                       ResolverFree freeFn);\n\nint32_t\naccept(int32_t sockfd,\n       virt_ptr<SocketAddr> addr,\n       virt_ptr<int32_t> addrlen);\n\nint32_t\nbind(int32_t sockfd,\n     virt_ptr<SocketAddr> addr,\n     int32_t addrlen);\n\nint32_t\nconnect(int32_t sockfd,\n        virt_ptr<SocketAddr> addr,\n        int32_t addrlen);\n\nvirt_ptr<GetHostError>\nget_h_errno();\n\nvirt_ptr<SocketHostEnt>\ngethostbyname(virt_ptr<const char> name);\n\nint32_t\ngetpeername(int32_t sockfd,\n            virt_ptr<SocketAddr> addr,\n            virt_ptr<uint32_t> addrlen);\n\nint32_t\ngetsockname(int32_t sockfd,\n            virt_ptr<SocketAddr> addr,\n            virt_ptr<uint32_t> addrlen);\n\nint32_t\nlisten(int32_t sockfd,\n       int32_t backlog);\n\nint32_t\nrecv(int32_t sockfd,\n     virt_ptr<void> buffer,\n     int32_t len,\n     int32_t flags);\n\nint32_t\nsend(int32_t sockfd,\n     virt_ptr<const void> buffer,\n     int32_t len,\n     int32_t flags);\n\nint32_t\nsetsockopt(int32_t sockfd,\n           int32_t level,\n           int32_t optname,\n           virt_ptr<void> optval,\n           int32_t optlen);\n\nint32_t\nselect(int32_t nfds,\n       virt_ptr<SocketFdSet> readfds,\n       virt_ptr<SocketFdSet> writefds,\n       virt_ptr<SocketFdSet> exceptfds,\n       virt_ptr<SocketTimeval> timeout);\n\nint32_t\nsocket(int32_t family,\n       int32_t type,\n       int32_t proto);\n\nint32_t\nsocketclose(int32_t sockfd);\n\n} // namespace cafe::nsysnet\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysuhs/nsysuhs.cpp",
    "content": "#include \"nsysuhs.h\"\n\nnamespace cafe::nsysuhs\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nsysuhs\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysuhs/nsysuhs.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nsysuhs\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nsysuhs, \"nsysuhs.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nsysuhs\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysuvd/nsysuvd.cpp",
    "content": "#include \"nsysuvd.h\"\n\nnamespace cafe::nsysuvd\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::nsysuvd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/nsysuvd/nsysuvd.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::nsysuvd\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::nsysuvd, \"nsysuvd.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::nsysuvd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/ntag/ntag.cpp",
    "content": "#include \"ntag.h\"\n\nnamespace cafe::ntag\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::ntag\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/ntag/ntag.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::ntag\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::ntag, \"ntag.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::ntag\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/padscore/padscore.cpp",
    "content": "#include \"padscore.h\"\n\nnamespace cafe::padscore\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerKpadSymbols();\n   registerWpadSymbols();\n}\n\n} // namespace cafe::padscore\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/padscore/padscore.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::padscore\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::padscore, \"padscore.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerKpadSymbols();\n   void registerWpadSymbols();\n};\n\n} // namespace cafe::padscore\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/padscore/padscore_enum.h",
    "content": "#ifndef CAFE_PADSCORE_ENUM_H\n#define CAFE_PADSCORE_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(padscore)\n\nENUM_BEG(KPADReadError, int32_t)\n   ENUM_VALUE(OK,                      0)\n   ENUM_VALUE(NoController,            -2)\nENUM_END(KPADReadError)\n\nENUM_BEG(WPADBatteryLevel, uint8_t)\n   ENUM_VALUE(Min,                     0x0000)\n   ENUM_VALUE(Low,                     0x0001)\n   ENUM_VALUE(Medium,                  0x0002)\n   ENUM_VALUE(High,                    0x0003)\n   ENUM_VALUE(Max,                     0x0004)\nENUM_END(WPADBatteryLevel)\n\nENUM_BEG(WPADButton, uint32_t)\n   ENUM_VALUE(Left,                    0x0001)\n   ENUM_VALUE(Right,                   0x0002)\n   ENUM_VALUE(Down,                    0x0004)\n   ENUM_VALUE(Up,                      0x0008)\n   ENUM_VALUE(Plus,                    0x0010)\n   ENUM_VALUE(Btn2,                    0x0100)\n   ENUM_VALUE(Btn1,                    0x0200)\n   ENUM_VALUE(B,                       0x0400)\n   ENUM_VALUE(A,                       0x0800)\n   ENUM_VALUE(Minus,                   0x1000)\n   ENUM_VALUE(Z,                       0x2000)\n   ENUM_VALUE(C,                       0x4000)\n   ENUM_VALUE(Home,                    0x8000)\nENUM_END(WPADButton)\n\nENUM_BEG(WPADClassicButton, uint32_t)\n   ENUM_VALUE(Up,                      0x0001)\n   ENUM_VALUE(Left,                    0x0002)\n   ENUM_VALUE(ZR,                      0x0004)\n   ENUM_VALUE(X,                       0x0008)\n   ENUM_VALUE(A,                       0x0010)\n   ENUM_VALUE(Y,                       0x0020)\n   ENUM_VALUE(B,                       0x0040)\n   ENUM_VALUE(ZL,                      0x0080)\n   ENUM_VALUE(R,                       0x0200)\n   ENUM_VALUE(Plus,                    0x0400)\n   ENUM_VALUE(Home,                    0x0800)\n   ENUM_VALUE(Minus,                   0x1000)\n   ENUM_VALUE(L,                       0x2000)\n   ENUM_VALUE(Down,                    0x4000)\n   ENUM_VALUE(Right,                   0x8000)\nENUM_END(WPADClassicButton)\n\nENUM_BEG(WPADChan, uint32_t)\n   ENUM_VALUE(Chan0,                   0)\n   ENUM_VALUE(Chan1,                   1)\n   ENUM_VALUE(Chan2,                   2)\n   ENUM_VALUE(Chan3,                   3)\n   ENUM_VALUE(NumChans,                4)\nENUM_END(WPADChan)\n\nENUM_BEG(WPADDataFormat, uint8_t)\n   ENUM_VALUE(ProController,           22)\nENUM_END(WPADDataFormat)\n\nENUM_BEG(WPADExtensionType, uint8_t)\n   ENUM_VALUE(Core,                    0)\n   ENUM_VALUE(Nunchuk,                 1)\n   ENUM_VALUE(Classic,                 2)\n   ENUM_VALUE(MotionPlus,              5)\n   ENUM_VALUE(MotionPlusNunchuk,       6)\n   ENUM_VALUE(MotionPlusClassic,       7)\n   ENUM_VALUE(ProController,           31)\n   ENUM_VALUE(NoController,            253)\nENUM_END(WPADExtensionType)\n\nENUM_BEG(WPADError, int32_t)\n   ENUM_VALUE(OK,                      0)\n   ENUM_VALUE(NoController,            -1)\nENUM_END(WPADError)\n\nENUM_BEG(WPADLibraryStatus, uint32_t)\n   ENUM_VALUE(Uninitialised,           0)\n   ENUM_VALUE(Initialised,             1)\nENUM_END(WPADLibraryStatus)\n\nENUM_BEG(WPADMotorCommand, uint32_t)\n   ENUM_VALUE(Stop,                    0)\n   ENUM_VALUE(Rumble,                  1)\nENUM_END(WPADMotorCommand)\n\nENUM_BEG(WPADProButton, uint32_t)\n   ENUM_VALUE(Up,                      0x00000001)\n   ENUM_VALUE(Left,                    0x00000002)\n   ENUM_VALUE(ZR,                      0x00000004)\n   ENUM_VALUE(X,                       0x00000008)\n   ENUM_VALUE(A,                       0x00000010)\n   ENUM_VALUE(Y,                       0x00000020)\n   ENUM_VALUE(B,                       0x00000040)\n   ENUM_VALUE(ZL,                      0x00000080)\n   ENUM_VALUE(R,                       0x00000200)\n   ENUM_VALUE(Plus,                    0x00000400)\n   ENUM_VALUE(Home,                    0x00000800)\n   ENUM_VALUE(Minus,                   0x00001000)\n   ENUM_VALUE(L,                       0x00002000)\n   ENUM_VALUE(Down,                    0x00004000)\n   ENUM_VALUE(Right,                   0x00008000)\n   ENUM_VALUE(StickR,                  0x00010000)\n   ENUM_VALUE(StickL,                  0x00020000)\n   ENUM_VALUE(StickLUp,                0x00200000)\n   ENUM_VALUE(StickLDown,              0x00100000)\n   ENUM_VALUE(StickLLeft,              0x00040000)\n   ENUM_VALUE(StickLRight,             0x00080000)\n   ENUM_VALUE(StickRUp,                0x02000000)\n   ENUM_VALUE(StickRDown,              0x01000000)\n   ENUM_VALUE(StickRLeft,              0x00400000)\n   ENUM_VALUE(StickRRight,             0x00800000)\nENUM_END(WPADProButton)\n\nENUM_NAMESPACE_EXIT(padscore)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_PADSCORE_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/padscore/padscore_kpad.cpp",
    "content": "#include \"padscore.h\"\n#include \"padscore_kpad.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::padscore\n{\n\nvoid\nKPADInit()\n{\n   decaf_warn_stub();\n   KPADInitEx(NULL, 0);\n}\n\nvoid\nKPADInitEx(virt_ptr<void> a1,\n           uint32_t a2)\n{\n   decaf_warn_stub();\n   WPADInit();\n}\n\nvoid\nKPADShutdown()\n{\n   decaf_warn_stub();\n}\n\n//! Enable \"Direct Pointing Device\"\nvoid\nKPADEnableDPD(KPADChan chan)\n{\n   decaf_warn_stub();\n}\n\n//! Disable \"Direct Pointing Device\"\nvoid\nKPADDisableDPD(KPADChan chan)\n{\n   decaf_warn_stub();\n}\n\nuint32_t\nKPADGetMplsWorkSize()\n{\n   return 0x5FE0;\n}\n\nvoid\nKPADSetMplsWorkarea(virt_ptr<void> buffer)\n{\n   decaf_warn_stub();\n}\n\nint32_t\nKPADRead(KPADChan chan,\n         virt_ptr<KPADStatus> data,\n         uint32_t size)\n{\n   decaf_warn_stub();\n   auto readError = StackObject<KPADReadError> { };\n   auto result = KPADReadEx(chan, data, size, readError);\n\n   if (*readError != KPADReadError::OK) {\n      return *readError;\n   } else {\n      return result;\n   }\n}\n\nint32_t\nKPADReadEx(KPADChan chan,\n           virt_ptr<KPADStatus> data,\n           uint32_t size,\n           virt_ptr<KPADReadError> outError)\n{\n   decaf_warn_stub();\n\n   if (outError) {\n      *outError = KPADReadError::NoController;\n   }\n\n   return 0;\n}\n\nvoid\nLibrary::registerKpadSymbols()\n{\n   RegisterFunctionExport(KPADInit);\n   RegisterFunctionExport(KPADInitEx);\n   RegisterFunctionExport(KPADShutdown);\n   RegisterFunctionExport(KPADDisableDPD);\n   RegisterFunctionExport(KPADEnableDPD);\n   RegisterFunctionExport(KPADGetMplsWorkSize);\n   RegisterFunctionExport(KPADSetMplsWorkarea);\n   RegisterFunctionExport(KPADRead);\n   RegisterFunctionExport(KPADReadEx);\n}\n\n} // namespace cafe::padscore\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/padscore/padscore_kpad.h",
    "content": "#pragma once\n#include \"padscore_enum.h\"\n#include \"padscore_wpad.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::padscore\n{\n\n#pragma pack(push, 1)\n\nusing KPADChan = WPADChan;\nusing KPADDataFormat = WPADDataFormat;\nusing KPADExtensionType = WPADExtensionType;\n\nstruct KPADVec2D\n{\n   be2_val<float> x;\n   be2_val<float> y;\n};\nCHECK_OFFSET(KPADVec2D, 0x00, x);\nCHECK_OFFSET(KPADVec2D, 0x04, y);\nCHECK_SIZE(KPADVec2D, 0x08);\n\nstruct KPADExtClassicStatus\n{\n   be2_val<uint32_t> hold;\n   be2_val<uint32_t> trigger;\n   be2_val<uint32_t> release;\n   be2_struct<KPADVec2D> leftStick;\n   be2_struct<KPADVec2D> rightStick;\n   be2_val<float> leftTrigger;\n   be2_val<float> rightTrigger;\n};\nCHECK_OFFSET(KPADExtClassicStatus, 0x00, hold);\nCHECK_OFFSET(KPADExtClassicStatus, 0x04, trigger);\nCHECK_OFFSET(KPADExtClassicStatus, 0x08, release);\nCHECK_OFFSET(KPADExtClassicStatus, 0x0C, leftStick);\nCHECK_OFFSET(KPADExtClassicStatus, 0x14, rightStick);\nCHECK_OFFSET(KPADExtClassicStatus, 0x1C, leftTrigger);\nCHECK_OFFSET(KPADExtClassicStatus, 0x20, rightTrigger);\n\nstruct KPADExtNunchukStatus\n{\n   be2_struct<KPADVec2D> stick;\n};\nCHECK_OFFSET(KPADExtNunchukStatus, 0x00, stick);\n\nstruct KPADExtProControllerStatus\n{\n   be2_val<uint32_t> hold;\n   be2_val<uint32_t> trigger;\n   be2_val<uint32_t> release;\n   be2_struct<KPADVec2D> leftStick;\n   be2_struct<KPADVec2D> rightStick;\n   be2_val<int32_t> charging;\n   be2_val<int32_t> wired;\n};\nCHECK_OFFSET(KPADExtProControllerStatus, 0x00, hold);\nCHECK_OFFSET(KPADExtProControllerStatus, 0x04, trigger);\nCHECK_OFFSET(KPADExtProControllerStatus, 0x08, release);\nCHECK_OFFSET(KPADExtProControllerStatus, 0x0C, leftStick);\nCHECK_OFFSET(KPADExtProControllerStatus, 0x14, rightStick);\nCHECK_OFFSET(KPADExtProControllerStatus, 0x1C, charging);\nCHECK_OFFSET(KPADExtProControllerStatus, 0x20, wired);\n\nstruct KPADExtStatus\n{\n   union\n   {\n      be2_struct<KPADExtClassicStatus> classic;\n      be2_struct<KPADExtNunchukStatus> nunchuk;\n      be2_struct<KPADExtProControllerStatus> proController;\n      PADDING(0x50);\n   };\n};\nCHECK_SIZE(KPADExtStatus, 0x50);\n\nstruct KPADStatus\n{\n   //! Indicates what KPADButtons are held down\n   be2_val<uint32_t> hold;\n\n   //! Indicates what KPADButtons have been pressed since last sample\n   be2_val<uint32_t> trigger;\n\n   //! Indicates what KPADButtons have been released since last sample\n   be2_val<uint32_t> release;\n\n   UNKNOWN(5 * 4);\n   be2_struct<KPADVec2D> pos;\n   UNKNOWN(3 * 4);\n   be2_struct<KPADVec2D> angle;\n   UNKNOWN(8 * 4);\n\n   //! Type of data stored in extStatus\n   be2_val<KPADExtensionType> extensionType;\n\n   //! Value from KPADError\n   be2_val<int8_t> error;\n\n   be2_val<uint8_t> posValid;\n   be2_val<KPADDataFormat> format;\n\n   // Extension data, check with extensionType to see what is valid to read\n   be2_struct<KPADExtStatus> extStatus;\n\n   UNKNOWN(16 * 4);\n};\nCHECK_OFFSET(KPADStatus, 0x00, hold);\nCHECK_OFFSET(KPADStatus, 0x04, trigger);\nCHECK_OFFSET(KPADStatus, 0x08, release);\nCHECK_OFFSET(KPADStatus, 0x20, pos);\nCHECK_OFFSET(KPADStatus, 0x34, angle);\nCHECK_OFFSET(KPADStatus, 0x5C, extensionType);\nCHECK_OFFSET(KPADStatus, 0x5D, error);\nCHECK_OFFSET(KPADStatus, 0x5E, posValid);\nCHECK_OFFSET(KPADStatus, 0x5F, format);\nCHECK_OFFSET(KPADStatus, 0x60, extStatus);\nCHECK_SIZE(KPADStatus, 0xF0);\n\n#pragma pack(pop)\n\nvoid\nKPADInit();\n\nvoid\nKPADInitEx(virt_ptr<void> a1,\n           uint32_t a2);\n\nvoid\nKPADShutdown();\n\nvoid\nKPADEnableDPD(KPADChan chan);\n\nvoid\nKPADDisableDPD(KPADChan chan);\n\nuint32_t\nKPADGetMplsWorkSize();\n\nvoid\nKPADSetMplsWorkarea(virt_ptr<void> buffer);\n\nint32_t\nKPADRead(KPADChan chan,\n         virt_ptr<KPADStatus> data,\n         uint32_t size);\n\nint32_t\nKPADReadEx(KPADChan chan,\n           virt_ptr<KPADStatus> data,\n           uint32_t size,\n           virt_ptr<KPADReadError> outError);\n\n} // namespace cafe::padscore\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/padscore/padscore_wpad.cpp",
    "content": "#include \"padscore.h\"\n#include \"padscore_wpad.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::padscore\n{\n\nstruct WpadData\n{\n   struct ChanData\n   {\n      be2_val<WPADDataFormat> dataFormat;\n      be2_val<WPADConnectCallback> connectCallback;\n      be2_val<WPADExtensionCallback> extensionCallback;\n      be2_val<WPADSamplingCallback> samplingCallback;\n   };\n\n   be2_val<WPADLibraryStatus> status;\n   be2_array<ChanData, WPADChan::NumChans> chanData;\n};\n\nstatic virt_ptr<WpadData>\nsWpadData = nullptr;\n\nvoid\nWPADInit()\n{\n   sWpadData->status = WPADLibraryStatus::Initialised;\n}\n\nWPADLibraryStatus\nWPADGetStatus()\n{\n   return sWpadData->status;\n}\n\nvoid\nWPADShutdown()\n{\n   sWpadData->status = WPADLibraryStatus::Uninitialised;\n}\n\nvoid\nWPADControlMotor(WPADChan chan,\n                 WPADMotorCommand command)\n{\n   decaf_warn_stub();\n}\n\nvoid\nWPADDisconnect(WPADChan chan)\n{\n   decaf_warn_stub();\n}\n\nvoid\nWPADEnableURCC(BOOL enable)\n{\n   decaf_warn_stub();\n}\n\nvoid\nWPADEnableWiiRemote(BOOL enable)\n{\n   decaf_warn_stub();\n}\n\nWPADBatteryLevel\nWPADGetBatteryLevel(WPADChan chan)\n{\n   decaf_warn_stub();\n   return WPADBatteryLevel::High;\n}\n\nint8_t\nWPADGetSpeakerVolume()\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nWPADError\nWPADProbe(WPADChan chan,\n          virt_ptr<WPADExtensionType> outExtensionType)\n{\n   decaf_warn_stub();\n\n   if (outExtensionType) {\n      *outExtensionType = WPADExtensionType::NoController;\n   }\n\n   return WPADError::NoController;\n}\n\nvoid\nWPADRead(WPADChan chan,\n         virt_ptr<void> data)\n{\n   decaf_warn_stub();\n\n   if (data) {\n      auto baseStatus = virt_cast<WPADStatus *>(data);\n      baseStatus->err = static_cast<int8_t>(WPADError::NoController);\n      baseStatus->extensionType = WPADExtensionType::NoController;\n   }\n}\n\nvoid\nWPADSetAutoSleepTime(uint8_t time)\n{\n   decaf_warn_stub();\n}\n\nWPADConnectCallback\nWPADSetConnectCallback(WPADChan chan,\n                       WPADConnectCallback callback)\n{\n   decaf_warn_stub();\n   if (chan >= WPADChan::NumChans) {\n      return nullptr;\n   }\n\n   auto prev = sWpadData->chanData[chan].connectCallback;\n   sWpadData->chanData[chan].connectCallback = callback;\n   return prev;\n}\n\nWPADError\nWPADSetDataFormat(WPADChan chan,\n                  WPADDataFormat format)\n{\n   decaf_warn_stub();\n   if (chan < WPADChan::NumChans) {\n      sWpadData->chanData[chan].dataFormat = format;\n   }\n\n   return WPADError::NoController;\n}\n\nWPADExtensionCallback\nWPADSetExtensionCallback(WPADChan chan,\n                         WPADExtensionCallback callback)\n{\n   decaf_warn_stub();\n   if (chan >= WPADChan::NumChans) {\n      return nullptr;\n   }\n\n   auto prev = sWpadData->chanData[chan].extensionCallback;\n   sWpadData->chanData[chan].extensionCallback = callback;\n   return prev;\n}\n\nWPADSamplingCallback\nWPADSetSamplingCallback(WPADChan chan,\n                        WPADSamplingCallback callback)\n{\n   decaf_warn_stub();\n   if (chan >= WPADChan::NumChans) {\n      return nullptr;\n   }\n\n   auto prev = sWpadData->chanData[chan].samplingCallback;\n   sWpadData->chanData[chan].samplingCallback = callback;\n   return prev;\n}\n\nvoid\nLibrary::registerWpadSymbols()\n{\n   RegisterFunctionExport(WPADInit);\n   RegisterFunctionExport(WPADGetStatus);\n   RegisterFunctionExport(WPADShutdown);\n   RegisterFunctionExport(WPADControlMotor);\n   RegisterFunctionExport(WPADDisconnect);\n   RegisterFunctionExport(WPADEnableURCC);\n   RegisterFunctionExport(WPADEnableWiiRemote);\n   RegisterFunctionExport(WPADGetBatteryLevel);\n   RegisterFunctionExport(WPADGetSpeakerVolume);\n   RegisterFunctionExport(WPADProbe);\n   RegisterFunctionExport(WPADRead);\n   RegisterFunctionExport(WPADSetAutoSleepTime);\n   RegisterFunctionExport(WPADSetConnectCallback);\n   RegisterFunctionExport(WPADSetDataFormat);\n   RegisterFunctionExport(WPADSetExtensionCallback);\n   RegisterFunctionExport(WPADSetSamplingCallback);\n\n   RegisterDataInternal(sWpadData);\n}\n\n\n} // namespace cafe::padscore\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/padscore/padscore_wpad.h",
    "content": "#pragma once\n#include \"padscore_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::padscore\n{\n\n#pragma pack(push, 1)\n\nusing  WPADConnectCallback = virt_func_ptr<\n   void (WPADChan chan, WPADError error)>;\n\nusing  WPADExtensionCallback = virt_func_ptr<\n   void (WPADChan chan, WPADError error)>;\n\nusing  WPADSamplingCallback = virt_func_ptr<\n   void (WPADChan chan)>;\n\nstruct WPADVec2D\n{\n   be2_val<int16_t> x;\n   be2_val<int16_t> y;\n};\nCHECK_OFFSET(WPADVec2D, 0x00, x);\nCHECK_OFFSET(WPADVec2D, 0x02, y);\nCHECK_SIZE(WPADVec2D, 0x04);\n\nstruct WPADStatus\n{\n   UNKNOWN(0x28);\n   be2_val<WPADExtensionType> extensionType;\n   be2_val<int8_t> err;\n   PADDING(2);\n};\nCHECK_OFFSET(WPADStatus, 0x28, extensionType);\nCHECK_OFFSET(WPADStatus, 0x29, err);\nCHECK_SIZE(WPADStatus, 0x2C);\n\nstruct WPADStatusProController\n{\n   be2_struct<WPADStatus> base;\n   be2_val<uint32_t> buttons;\n   be2_struct<WPADVec2D> leftStick;\n   be2_struct<WPADVec2D> rightStick;\n   UNKNOWN(8);\n   be2_val<WPADDataFormat> dataFormat;\n   PADDING(3);\n};\nCHECK_OFFSET(WPADStatusProController, 0x00, base);\nCHECK_OFFSET(WPADStatusProController, 0x2C, buttons);\nCHECK_OFFSET(WPADStatusProController, 0x30, leftStick);\nCHECK_OFFSET(WPADStatusProController, 0x34, rightStick);\nCHECK_OFFSET(WPADStatusProController, 0x40, dataFormat);\nCHECK_SIZE(WPADStatusProController, 0x44);\n\nvoid\nWPADInit();\n\nWPADLibraryStatus\nWPADGetStatus();\n\nvoid\nWPADShutdown();\n\nvoid\nWPADControlMotor(WPADChan chan,\n                 WPADMotorCommand command);\n\nvoid\nWPADDisconnect(WPADChan chan);\n\nvoid\nWPADEnableURCC(BOOL enable);\n\nvoid\nWPADEnableWiiRemote(BOOL enable);\n\nWPADBatteryLevel\nWPADGetBatteryLevel(WPADChan chan);\n\nint8_t\nWPADGetSpeakerVolume();\n\nWPADError\nWPADProbe(WPADChan chan,\n          virt_ptr<WPADExtensionType> outExtensionType);\n\nvoid\nWPADRead(WPADChan chan,\n         virt_ptr<void> data);\n\nvoid\nWPADSetAutoSleepTime(uint8_t time);\n\nWPADConnectCallback\nWPADSetConnectCallback(WPADChan chan,\n                       WPADConnectCallback callback);\n\nWPADError\nWPADSetDataFormat(WPADChan chan,\n                  WPADDataFormat format);\n\nWPADExtensionCallback\nWPADSetExtensionCallback(WPADChan chan,\n                         WPADExtensionCallback callback);\n\nWPADSamplingCallback\nWPADSetSamplingCallback(WPADChan chan,\n                        WPADSamplingCallback callback);\n\n#pragma pack(pop)\n\n} // namespace cafe::padscore\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/proc_ui/proc_ui.cpp",
    "content": "#include \"proc_ui.h\"\n\nnamespace cafe::proc_ui\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerMessagesFunctions();\n}\n\n} // namespace cafe::proc_ui\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/proc_ui/proc_ui.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::proc_ui\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::proc_ui, \"proc_ui.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerMessagesFunctions();\n};\n\n} // namespace cafe::proc_ui\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/proc_ui/proc_ui_enum.h",
    "content": "#ifndef CAFE_PROC_UI_ENUM_H\n#define CAFE_PROC_UI_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(proc_ui)\n\nENUM_BEG(ProcUICallbackType, uint32_t)\n   ENUM_VALUE(Acquire,           0)\n   ENUM_VALUE(Release,           1)\n   ENUM_VALUE(Exit,              2)\n   ENUM_VALUE(NetIoStart,        3)\n   ENUM_VALUE(NetIoStop,         4)\n   ENUM_VALUE(HomeButtonDenied,  5)\n   ENUM_VALUE(Max,               6)\nENUM_END(ProcUICallbackType)\n\nENUM_BEG(ProcUIStatus, uint32_t)\n   ENUM_VALUE(InForeground,      0)\n   ENUM_VALUE(InBackground,      1)\n   ENUM_VALUE(ReleaseForeground, 2)\n   ENUM_VALUE(Exiting,           3)\nENUM_END(ProcUIStatus)\n\nENUM_NAMESPACE_EXIT(proc_ui)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // CAFE_PROC_UI_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/proc_ui/proc_ui_messages.cpp",
    "content": "#include \"proc_ui.h\"\n#include \"proc_ui_messages.h\"\n\nnamespace cafe::proc_ui\n{\n\nstruct RegisteredCallback\n{\n   ProcUICallback callback;\n   virt_ptr<void> param;\n};\n\nstruct MessagesData\n{\n   be2_val<BOOL> running;\n   be2_val<BOOL> sentAcquireMessage;\n   be2_val<ProcUISaveCallback> saveCallback;\n   be2_val<ProcUISaveCallbackEx> saveCallbackEx;\n   be2_virt_ptr<void> saveCallbackExUserArg;\n   be2_array<RegisteredCallback, ProcUICallbackType::Max> registeredCallbacks;\n};\n\nstatic virt_ptr<MessagesData>\nsMessagesData = nullptr;\n\nvoid\nProcUIInit(ProcUISaveCallback saveCallback)\n{\n   sMessagesData->saveCallback = saveCallback;\n   sMessagesData->saveCallbackEx = nullptr;\n   sMessagesData->saveCallbackExUserArg = nullptr;\n   sMessagesData->running = TRUE;\n}\n\nvoid\nProcUIInitEx(ProcUISaveCallbackEx saveCallbackEx,\n             virt_ptr<void> arg)\n{\n   sMessagesData->saveCallback = nullptr;\n   sMessagesData->saveCallbackEx = saveCallbackEx;\n   sMessagesData->saveCallbackExUserArg = arg;\n}\n\nBOOL\nProcUIIsRunning()\n{\n   return sMessagesData->running;\n}\n\nProcUIStatus\nProcUIProcessMessages(BOOL block)\n{\n   // TODO: ProcUIProcessMessages\n   return ProcUIStatus::InForeground;\n}\n\nProcUIStatus\nProcUISubProcessMessages(BOOL block)\n{\n   // TODO: ProcUISubProcessMessages\n   return ProcUIStatus::InForeground;\n}\n\nvoid\nProcUIRegisterCallback(ProcUICallbackType type,\n                       ProcUICallback callback,\n                       virt_ptr<void> userArg,\n                       uint32_t unk)\n{\n   if (type < sMessagesData->registeredCallbacks.size()) {\n      sMessagesData->registeredCallbacks[type].callback = callback;\n      sMessagesData->registeredCallbacks[type].param = userArg;\n   }\n}\n\nvoid\nProcUISetMEM1Storage(virt_ptr<void> buffer,\n                     uint32_t size)\n{\n   // TODO: ProcUISetMEM1Storage\n}\n\nvoid\nLibrary::registerMessagesFunctions()\n{\n   RegisterFunctionExport(ProcUIInit);\n   RegisterFunctionExport(ProcUIInitEx);\n   RegisterFunctionExport(ProcUIIsRunning);\n   RegisterFunctionExport(ProcUIProcessMessages);\n   RegisterFunctionExport(ProcUISubProcessMessages);\n   RegisterFunctionExport(ProcUIRegisterCallback);\n   RegisterFunctionExport(ProcUISetMEM1Storage);\n\n   RegisterDataInternal(sMessagesData);\n}\n\n} // namespace cafe::proc_ui\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/proc_ui/proc_ui_messages.h",
    "content": "#pragma once\n#include \"proc_ui_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::proc_ui\n{\n\nusing ProcUISaveCallback = virt_func_ptr<\n   void ()>;\n\nusing ProcUISaveCallbackEx = virt_func_ptr<\n   uint32_t (virt_ptr<void> userArg)>;\n\nusing ProcUICallback = virt_func_ptr<\n   uint32_t (virt_ptr<void> userArg)>;\n\nvoid\nProcUIInit(ProcUISaveCallback saveCallback);\n\nvoid\nProcUIInitEx(ProcUISaveCallbackEx saveCallbackEx,\n             virt_ptr<void> userArg);\n\nBOOL\nProcUIIsRunning();\n\nProcUIStatus\nProcUIProcessMessages(BOOL block);\n\nProcUIStatus\nProcUISubProcessMessages(BOOL block);\n\nvoid\nProcUIRegisterCallback(ProcUICallbackType type,\n                       ProcUICallback callback,\n                       virt_ptr<void> userArg,\n                       uint32_t unk);\n\nvoid\nProcUISetMEM1Storage(virt_ptr<void> buffer,\n                     uint32_t size);\n\n} // namespace cafe::proc_ui\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snd_core/snd_core.h",
    "content": "#pragma once\n#include \"cafe/libraries/sndcore2/sndcore2.h\"\n\nnamespace cafe::snd_core\n{\n\nclass Library : public sndcore2::Library\n{\npublic:\n   Library() :\n      sndcore2::Library(hle::LibraryId::snd_core, \"snd_core.rpl\")\n   {\n   }\n};\n\n} // namespace cafe::snd_core\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snd_user/snd_user.h",
    "content": "#pragma once\n#include \"cafe/libraries/snduser2/snduser2.h\"\n\nnamespace cafe::snd_user\n{\n\nclass Library : public snduser2::Library\n{\npublic:\n   Library() :\n      snduser2::Library(hle::LibraryId::snd_user, \"snd_user.rpl\")\n   {\n   }\n};\n\n} // namespace cafe::snd_user\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2.cpp",
    "content": "#include \"sndcore2.h\"\n\nnamespace cafe::sndcore2\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerAiSymbols();\n   registerConfigSymbols();\n   registerDeviceSymbols();\n   registerRmtSymbols();\n   registerVoiceSymbols();\n   registerVsSymbols();\n}\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::sndcore2\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::sndcore2, \"sndcore2.rpl\")\n   {\n   }\n\n   // Constructor for snd_core to use\n   Library(hle::LibraryId id,\n           const char *name) :\n      hle::Library(id, name)\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerAiSymbols();\n   void registerConfigSymbols();\n   void registerDeviceSymbols();\n   void registerRmtSymbols();\n   void registerVoiceSymbols();\n   void registerVsSymbols();\n};\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_ai.cpp",
    "content": "#include \"sndcore2.h\"\n#include \"sndcore2_ai.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::sndcore2\n{\n\nuint32_t\nAIGetDMALength()\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nuint32_t\nAIGetDMAStartAddr()\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nuint32_t\nAI2GetDMALength()\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nuint32_t\nAI2GetDMAStartAddr()\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nvoid\nLibrary::registerAiSymbols()\n{\n   RegisterFunctionExport(AIGetDMALength);\n   RegisterFunctionExport(AIGetDMAStartAddr);\n   RegisterFunctionExport(AI2GetDMALength);\n   RegisterFunctionExport(AI2GetDMAStartAddr);\n}\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_ai.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace cafe::sndcore2\n{\n\nuint32_t\nAIGetDMALength();\n\nuint32_t\nAIGetDMAStartAddr();\n\nuint32_t\nAI2GetDMALength();\n\nuint32_t\nAI2GetDMAStartAddr();\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_config.cpp",
    "content": "#include \"sndcore2.h\"\n#include \"sndcore2_config.h\"\n#include \"sndcore2_voice.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/coreinit/coreinit_alarm.h\"\n#include \"cafe/libraries/coreinit/coreinit_interrupts.h\"\n#include \"cafe/libraries/coreinit/coreinit_scheduler.h\"\n#include \"cafe/libraries/coreinit/coreinit_systeminfo.h\"\n#include \"cafe/libraries/coreinit/coreinit_thread.h\"\n#include \"cafe/libraries/coreinit/coreinit_time.h\"\n#include \"decaf_sound.h\"\n\n#include <common/log.h>\n#include <fmt/core.h>\n#include <libcpu/cpu_formatters.h>\n\nusing namespace cafe::coreinit;\n\nnamespace cafe::sndcore2\n{\n\nstatic const size_t MaxFrameCallbacks = 64;\n\n// Enough for 6 channels of 3ms at 48kHz\nstatic constexpr auto AXNumBufferSamples = 6 * 144;\n\nstruct StaticConfigData\n{\n   be2_val<BOOL> initialised;\n   be2_val<int32_t> outputRate;\n   be2_val<int32_t> outputChannels;\n   be2_val<uint32_t> defaultMixerSelect;\n   be2_struct<OSAlarm> frameAlarm;\n   be2_array<AXFrameCallback, MaxFrameCallbacks> appFrameCallbacks;\n   be2_val<AXFrameCallback> frameCallback;\n   be2_struct<OSThread> frameCallbackThread;\n   be2_struct<OSThreadQueue> frameCallbackThreadQueue;\n   be2_array<char, 32> frameCallbackThreadName;\n   be2_array<uint8_t, 16 * 1024> frameCallbackThreadStack;\n\n   // Sound is mixed by the DSP in little-endian\n   std::array<int32_t, AXNumBufferSamples> mixBuffer;\n   std::array<int16_t, AXNumBufferSamples> outputBuffer;\n};\n\nstatic OSThreadEntryPointFn FrameCallbackThreadEntryPoint = nullptr;\nstatic AlarmCallbackFn FrameAlarmHandler = nullptr;\nstatic virt_ptr<StaticConfigData> sConfigData = nullptr;\n\nstatic std::atomic<int32_t>\nsProtectLock = { 0 };\n\nvoid\nAXInit()\n{\n   auto params = StackObject<AXInitParams> { };\n   params->renderer = AXRendererFreq::Freq32khz;\n   params->pipeline = AXInitPipeline::Single;\n   AXInitWithParams(params);\n}\n\nvoid\nAXInitWithParams(virt_ptr<AXInitParams> params)\n{\n   if (AXIsInit()) {\n      return;\n   }\n\n   switch (params->renderer) {\n   case AXRendererFreq::Freq32khz:\n      sConfigData->outputRate = 32000;\n      break;\n   case AXRendererFreq::Freq48khz:\n      sConfigData->outputRate = 48000;\n      break;\n   default:\n      decaf_abort(fmt::format(\"Unimplemented AXInitRenderer {}\", params->renderer));\n   }\n\n   sConfigData->outputChannels = 2;  // TODO: surround support\n   internal::initDevices();\n   internal::initVoices();\n   internal::initEvents();\n\n   if (auto driver = decaf::getSoundDriver()) {\n      if (!driver->start(48000, sConfigData->outputChannels)) {\n         gLog->error(\"Sound driver failed to start, disabling sound output\");\n         decaf::setSoundDriver(nullptr);\n      }\n   }\n\n   sConfigData->initialised = TRUE;\n}\n\nBOOL\nAXIsInit()\n{\n   return sConfigData->initialised;\n}\n\nvoid\nAXQuit()\n{\n   decaf_warn_stub();\n}\n\nvoid\nAXInitProfile(virt_ptr<AXProfile> profile,\n              uint32_t count)\n{\n   decaf_warn_stub();\n}\n\nAXRendererFreq\nAXGetRendererFreq()\n{\n   if (sConfigData->outputRate == 32000) {\n      return AXRendererFreq::Freq32khz;\n   } else {\n      return AXRendererFreq::Freq48khz;\n   }\n}\n\nuint32_t\nAXGetSwapProfile(virt_ptr<AXProfile> profile,\n                 uint32_t count)\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nuint32_t\nAXGetDefaultMixerSelect()\n{\n   decaf_warn_stub();\n   return sConfigData->defaultMixerSelect;\n}\n\nAXResult\nAXSetDefaultMixerSelect(uint32_t defaultMixerSelect)\n{\n   decaf_warn_stub();\n   sConfigData->defaultMixerSelect = defaultMixerSelect;\n   return AXResult::Success;\n}\n\nAXResult\nAXRegisterAppFrameCallback(AXFrameCallback callback)\n{\n   if (!callback) {\n      return AXResult::CallbackInvalid;\n   }\n\n   for (auto i = 0; i < MaxFrameCallbacks; ++i) {\n      if (sConfigData->appFrameCallbacks[i] == callback) {\n         decaf_abort(\"Application double-registered app frame callback\");\n      }\n   }\n\n   for (auto i = 0; i < MaxFrameCallbacks; ++i) {\n      if (!sConfigData->appFrameCallbacks[i]) {\n         sConfigData->appFrameCallbacks[i] = callback;\n         return AXResult::Success;\n      }\n   }\n\n   return AXResult::TooManyCallbacks;\n}\n\nAXResult\nAXDeregisterAppFrameCallback(AXFrameCallback callback)\n{\n   if (!callback) {\n      return AXResult::CallbackInvalid;\n   }\n\n   for (auto i = 0; i < MaxFrameCallbacks; ++i) {\n      if (sConfigData->appFrameCallbacks[i] == callback) {\n         sConfigData->appFrameCallbacks[i] = nullptr;\n         return AXResult::Success;\n      }\n   }\n\n   return AXResult::CallbackNotFound;\n}\n\nAXFrameCallback\nAXRegisterFrameCallback(AXFrameCallback callback)\n{\n   auto oldCallback = sConfigData->frameCallback;\n   sConfigData->frameCallback = callback;\n   return oldCallback;\n}\n\nint32_t\nAXUserBegin()\n{\n   decaf_warn_stub();\n\n   // TODO: Implement this properly\n   return sProtectLock.fetch_add(1);\n}\n\nint32_t\nAXUserEnd()\n{\n   decaf_warn_stub();\n\n   // TODO: Implement this properly\n   return sProtectLock.fetch_sub(1);\n}\n\nBOOL\nAXUserIsProtected()\n{\n   decaf_warn_stub();\n\n   // TODO: Implement this properly\n   return sProtectLock.load() > 0;\n}\n\nuint32_t\nAXGetInputSamplesPerFrame()\n{\n   if (sConfigData->outputRate == 32000) {\n      return 96;\n   } else if (sConfigData->outputRate == 48000) {\n      return 144;\n   } else {\n      decaf_abort(fmt::format(\"Unexpected output rate {}\", sConfigData->outputRate));\n   }\n}\n\nuint32_t\nAXGetInputSamplesPerSec()\n{\n   return (AXGetInputSamplesPerFrame() / 3) * 1000;\n}\n\nvoid\nAXPrepareEfxData(virt_ptr<void> buffer,\n                 uint32_t size)\n{\n   // Nothing to do here, we have implicit cache coherency\n}\n\nnamespace internal\n{\n\nstatic uint32_t\nframeCallbackThreadEntry(uint32_t core_id,\n                         virt_ptr<void>)\n{\n   static const int NumOutputSamples = (48000 * 3) / 1000;\n   uint16_t numInputSamples = static_cast<uint16_t>(sConfigData->outputRate * 3 / 1000);\n   uint16_t numOutputChannels = static_cast<uint16_t>(sConfigData->outputChannels);\n\n   while (true) {\n      coreinit::internal::lockScheduler();\n      coreinit::internal::sleepThreadNoLock(virt_addrof(sConfigData->frameCallbackThreadQueue));\n      coreinit::internal::rescheduleSelfNoLock();\n      coreinit::internal::unlockScheduler();\n\n      if (sConfigData->frameCallback) {\n         cafe::invoke(cpu::this_core::state(),\n                      sConfigData->frameCallback);\n      }\n\n      for (auto i = 0; i < MaxFrameCallbacks; ++i) {\n         if (sConfigData->appFrameCallbacks[i]) {\n            cafe::invoke(cpu::this_core::state(),\n                         sConfigData->appFrameCallbacks[i]);\n         }\n      }\n\n      decaf_check(static_cast<size_t>(NumOutputSamples * numOutputChannels) <= sConfigData->mixBuffer.size());\n      internal::mixOutput(&sConfigData->mixBuffer[0], numInputSamples, numOutputChannels);\n\n      auto driver = decaf::getSoundDriver();\n\n      if (driver) {\n         for (int i = 0; i < NumOutputSamples * sConfigData->outputChannels; ++i) {\n            sConfigData->outputBuffer[i] = static_cast<int16_t>(std::min<int32_t>(std::max<int32_t>(sConfigData->mixBuffer[i], -32768), 32767));\n         }\n\n         driver->output(&sConfigData->outputBuffer[0], NumOutputSamples);\n      }\n   }\n\n   return 0;\n}\n\nstatic void\nstartFrameAlarmThread()\n{\n   auto thread = virt_addrof(sConfigData->frameCallbackThread);\n   auto stack = virt_addrof(sConfigData->frameCallbackThreadStack);\n   auto stackSize = sConfigData->frameCallbackThreadStack.size();\n   sConfigData->frameCallbackThreadName = \"AX Callback Thread\";\n\n   OSCreateThreadType(thread,\n                      FrameCallbackThreadEntryPoint,\n                      0, nullptr,\n                      virt_cast<uint32_t *>(stack + stackSize),\n                      stackSize,\n                      15,\n                      static_cast<OSThreadAttributes>(1 << cpu::this_core::id()),\n                      OSThreadType::AppIo);\n   OSSetThreadName(thread, virt_addrof(sConfigData->frameCallbackThreadName));\n   OSResumeThread(thread);\n}\n\nstatic void\nframeAlarmHandler(virt_ptr<OSAlarm> alarm,\n                  virt_ptr<OSContext> context)\n{\n   coreinit::internal::lockScheduler();\n   coreinit::internal::wakeupThreadNoLock(virt_addrof(sConfigData->frameCallbackThreadQueue));\n   coreinit::internal::unlockScheduler();\n}\n\nvoid\ninitEvents()\n{\n   using namespace coreinit;\n\n   sConfigData->frameCallback = nullptr;\n   for (auto i = 0; i < MaxFrameCallbacks; ++i) {\n      sConfigData->appFrameCallbacks[i] = nullptr;\n   }\n\n   startFrameAlarmThread();\n\n   auto ticks = static_cast<OSTime>(OSGetSystemInfo()->busSpeed / 4) * 3 / 1000;\n   OSCreateAlarm(virt_addrof(sConfigData->frameAlarm));\n   OSSetPeriodicAlarm(virt_addrof(sConfigData->frameAlarm),\n                      OSGetTime(), ticks,\n                      FrameAlarmHandler);\n}\n\nint\ngetOutputRate()\n{\n   return sConfigData->outputRate;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerConfigSymbols()\n{\n   RegisterFunctionExport(AXInit);\n   RegisterFunctionExport(AXInitWithParams);\n   RegisterFunctionExport(AXIsInit);\n   RegisterFunctionExport(AXQuit);\n   RegisterFunctionExport(AXInitProfile);\n   RegisterFunctionExport(AXGetRendererFreq);\n   RegisterFunctionExport(AXGetSwapProfile);\n   RegisterFunctionExport(AXGetDefaultMixerSelect);\n   RegisterFunctionExport(AXSetDefaultMixerSelect);\n   RegisterFunctionExport(AXRegisterAppFrameCallback);\n   RegisterFunctionExport(AXDeregisterAppFrameCallback);\n   RegisterFunctionExport(AXRegisterFrameCallback);\n   RegisterFunctionExportName(\"AXRegisterCallback\", AXRegisterFrameCallback);\n   RegisterFunctionExport(AXUserBegin);\n   RegisterFunctionExport(AXUserEnd);\n   RegisterFunctionExport(AXUserIsProtected);\n   RegisterFunctionExport(AXGetInputSamplesPerFrame);\n   RegisterFunctionExport(AXGetInputSamplesPerSec);\n   RegisterFunctionExport(AXPrepareEfxData);\n\n   RegisterDataInternal(sConfigData);\n   RegisterFunctionInternal(internal::frameAlarmHandler, FrameAlarmHandler);\n   RegisterFunctionInternal(internal::frameCallbackThreadEntry, FrameCallbackThreadEntryPoint);\n}\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_config.h",
    "content": "#pragma once\n#include \"sndcore2_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::sndcore2\n{\n\n#pragma pack(push, 1)\n\nstruct AXProfile;\n\nstruct AXInitParams\n{\n   be2_val<AXRendererFreq> renderer;\n   UNKNOWN(4);\n   be2_val<AXInitPipeline> pipeline;\n};\nCHECK_OFFSET(AXInitParams, 0x00, renderer);\nCHECK_OFFSET(AXInitParams, 0x08, pipeline);\nCHECK_SIZE(AXInitParams, 0x0C);\n\n#pragma pack(pop)\n\nusing AXFrameCallback = virt_func_ptr<\n   void()\n>;\n\nvoid\nAXInit();\n\nvoid\nAXInitWithParams(virt_ptr<AXInitParams> params);\n\nBOOL\nAXIsInit();\n\nvoid\nAXQuit();\n\nvoid\nAXInitProfile(virt_ptr<AXProfile> profile,\n              uint32_t count);\n\nAXRendererFreq\nAXGetRendererFreq();\n\nuint32_t\nAXGetSwapProfile(virt_ptr<AXProfile> profile,\n                 uint32_t count);\n\nAXResult\nAXSetDefaultMixerSelect(uint32_t);\n\nAXFrameCallback\nAXRegisterFrameCallback(AXFrameCallback callback);\n\nAXResult\nAXRegisterAppFrameCallback(AXFrameCallback callback);\n\nAXResult\nAXDeregisterAppFrameCallback(AXFrameCallback callback);\n\nuint32_t\nAXGetInputSamplesPerFrame();\n\nuint32_t\nAXGetInputSamplesPerSec();\n\nvoid\nAXPrepareEfxData(virt_ptr<void> buffer,\n                 uint32_t size);\n\nint32_t\nAXUserBegin();\n\nint32_t\nAXUserEnd();\n\nBOOL\nAXUserIsProtected();\n\nnamespace internal\n{\n\nvoid\ninitEvents();\n\nint\ngetOutputRate();\n\n} // namespace internal\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_constants.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace cafe::sndcore2\n{\n\nstatic constexpr auto AXNumTvDevices = 1u;\nstatic constexpr auto AXNumTvChannels = 6u;\nstatic constexpr auto AXNumTvBus = 4u;\n\nstatic constexpr auto AXNumDrcDevices = 2u;\nstatic constexpr auto AXNumDrcChannels = 4u;\nstatic constexpr auto AXNumDrcBus = 4u;\n\nstatic constexpr auto AXNumRmtDevices = 4u;\nstatic constexpr auto AXNumRmtChannels = 1u;\nstatic constexpr auto AXNumRmtBus = 1u;\n\nstatic constexpr auto AXMaxNumVoices = 96u;\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_device.cpp",
    "content": "#include \"sndcore2.h\"\n#include \"sndcore2_config.h\"\n#include \"sndcore2_constants.h\"\n#include \"sndcore2_device.h\"\n#include \"sndcore2_voice.h\"\n#include \"decaf_sound.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n\n#include <array>\n#include <common/fixed.h>\n#include <libcpu/mmu.h>\n\nnamespace cafe::sndcore2\n{\n\nconstexpr auto NumOutputSamples = 48000 * 3 / 1000;\nconstexpr auto DefaultVolume = ufixed_1_15_t { 1.0 };\n\nstruct AuxData\n{\n   AXAuxCallback callback;\n   virt_ptr<void> userData;\n   ufixed_1_15_t returnVolume;\n};\n\nstruct DeviceData\n{\n   std::array<AuxData, AXAuxId::Max> aux;\n   bool linearUpsample;\n   ufixed_1_15_t volume;\n};\n\nstruct DeviceTypeData\n{\n   std::array<DeviceData, 4> devices;\n   bool compressor;\n   AXDeviceFinalMixCallback finalMixCallback;\n   bool upsampleAfterFinalMix;\n   AXDeviceMode mode;\n};\n\nstruct StaticDeviceData\n{\n   be2_struct<DeviceTypeData> tvDevices;\n   be2_struct<DeviceTypeData> drcDevices;\n   be2_struct<DeviceTypeData> rmtDevices;\n\n   be2_array<virt_ptr<int32_t>, 8> samplePtrs;\n   be2_val<int32_t> samples[8][144];\n   be2_struct<AXAuxCallbackData> auxCallbackData;\n   be2_struct<AXDeviceFinalMixData> finalMixCallbackData;\n};\n\nstatic virt_ptr<StaticDeviceData>\nsDeviceData = nullptr;\n\nnamespace internal\n{\n\nstatic DeviceData *\ngetDevice(AXDeviceType type,\n          uint32_t deviceId)\n{\n   switch (type) {\n   case AXDeviceType::TV:\n      decaf_check(deviceId < AXNumTvDevices);\n      return &sDeviceData->tvDevices.devices[deviceId];\n   case AXDeviceType::DRC:\n      decaf_check(deviceId < AXNumDrcDevices);\n      return &sDeviceData->drcDevices.devices[deviceId];\n   case AXDeviceType::RMT:\n      decaf_check(deviceId < AXNumRmtDevices);\n      return &sDeviceData->rmtDevices.devices[deviceId];\n   default:\n      return nullptr;\n   }\n}\n\ntemplate<typename Type>\nstatic Type *\ngetMemPageAddress(uint32_t memPageNumber)\n{\n   // We have to do this this way due to the way that mem::translate handles\n   //  nullptr's.  In the case of AX here, our memPageNumber can be 0, causing\n   //  mem::translate to return 0, which is not what we want.\n   return reinterpret_cast<Type *>(cpu::getBaseVirtualAddress() + (static_cast<uint64_t>(memPageNumber) << 29));\n}\n\nstruct AudioDecoder\n{\n   // Basic information\n   internal::AXCafeVoiceData offsets;\n   AXVoiceType type;\n   uint32_t loopCount;\n   AXVoiceAdpcm adpcm;\n   AXVoiceAdpcmLoopData adpcmLoop;\n   bool isEof;\n\n   void fromVoice(virt_ptr<AXVoiceExtras> extras)\n   {\n      offsets = extras->data;\n      type = extras->type;\n      loopCount = extras->loopCount;\n      adpcm = extras->adpcm;\n      adpcmLoop = extras->adpcmLoop;\n\n      isEof = false;\n   }\n\n   void toVoice(virt_ptr<AXVoiceExtras> extras)\n   {\n      extras->data = offsets;\n      extras->loopCount = loopCount;\n      extras->adpcm = adpcm;\n   }\n\n   AudioDecoder& advance()\n   {\n      // Update prev sample\n      auto sample = read();\n      adpcm.prevSample[1] = adpcm.prevSample[0];\n      adpcm.prevSample[0] = fixed_to_data(sample);\n\n      if (offsets.currentOffsetAbs == offsets.endOffsetAbs) {\n         // According to Dolphin, the loop back happens regardless\n         //  of whether the voice is in looping mode\n         offsets.currentOffsetAbs = offsets.loopOffsetAbs;\n\n         if (offsets.loopFlag) {\n            adpcm.predScale = adpcmLoop.predScale;\n\n            if (type != AXVoiceType::Streaming) {\n               adpcm.prevSample[0] = adpcmLoop.prevSample[0];\n               adpcm.prevSample[1] = adpcmLoop.prevSample[1];\n            }\n         } else {\n            decaf_check(!isEof);\n            isEof = true;\n         }\n\n         loopCount++;\n      } else {\n         offsets.currentOffsetAbs += 1;\n\n         if (offsets.format == AXVoiceFormat::ADPCM) {\n            // Read next header if were there\n            if ((offsets.currentOffsetAbs & 0xf) < virt_addr { 2 }) {\n               decaf_check((offsets.currentOffsetAbs & 0xf) == virt_addr { 0 });\n\n               auto data = getMemPageAddress<uint8_t>(offsets.memPageNumber);\n\n               adpcm.predScale = data[offsets.currentOffsetAbs / 2];\n               offsets.currentOffsetAbs += 2;\n            }\n         }\n      }\n\n      return *this;\n   }\n\n   bool eof()\n   {\n      return isEof;\n   }\n\n   Pcm16Sample read()\n   {\n      decaf_check(!isEof);\n      auto sampleIndex = static_cast<uint32_t>(offsets.currentOffsetAbs);\n\n      if (offsets.format == AXVoiceFormat::ADPCM) {\n         decaf_check((sampleIndex & 0xf) >= 2);\n\n         auto data = getMemPageAddress<uint8_t>(offsets.memPageNumber);\n\n         auto scale = 1 << (adpcm.predScale.value() & 0xF);\n         auto coeffIndex = (adpcm.predScale.value() >> 4) & 7;\n         auto coeff1 = adpcm.coefficients[coeffIndex * 2 + 0].value();\n         auto coeff2 = adpcm.coefficients[coeffIndex * 2 + 1].value();\n         auto yn1 = adpcm.prevSample[0].value();\n         auto yn2 = adpcm.prevSample[1].value();\n\n         // Extract the 4-bit signed sample from the appropriate byte\n         int sampleData = data[sampleIndex / 2];\n\n         if (sampleIndex % 2 == 0) {\n            sampleData >>= 4;\n         } else {\n            sampleData &= 0xF;\n         }\n\n         if (sampleData >= 8) {\n            sampleData -= 16;\n         }\n\n         // Calculate sample\n         auto adpcmSample = (scale * sampleData) + ((0x400 + (coeff1 * yn1) + (coeff2 * yn2)) >> 11);\n\n         // Clamp the output\n         auto clampedSample = std::min(std::max(adpcmSample, -32767), 32767);\n\n         // Write to the output\n         return fixed_from_data<Pcm16Sample>(static_cast<int16_t>(clampedSample));\n      } else if (offsets.format == AXVoiceFormat::LPCM16) {\n         auto data = getMemPageAddress<be2_val<int16_t>>(offsets.memPageNumber);\n         return fixed_from_data<Pcm16Sample>(data[sampleIndex]);\n      } else if (offsets.format == AXVoiceFormat::LPCM8) {\n         auto data = getMemPageAddress<uint8_t>(offsets.memPageNumber);\n         return fixed_from_data<Pcm16Sample>(static_cast<int16_t>(data[sampleIndex] << 8));\n      } else {\n         decaf_abort(\"Unexpected AXVoice data format\");\n      }\n   }\n};\n\nvoid\nsampleVoice(virt_ptr<AXVoice> voice,\n            Pcm16Sample *samples,\n            int numSamples)\n{\n   static const auto FpOne = ufixed_16_16_t(1);\n   static const auto FpZero = ufixed_16_16_t(0);\n\n   memset(samples, 0, numSamples * sizeof(Pcm16Sample));\n\n   auto extras = getVoiceExtras(voice->index);\n   auto offsetFrac = ufixed_16_16_t(extras->src.currentOffsetFrac.value());\n\n   AudioDecoder decoder;\n   decoder.fromVoice(extras);\n\n   for (auto i = 0; i < numSamples; ++i) {\n      // Read in the current sample\n      Pcm16Sample sample;\n      if (offsetFrac == FpZero) {\n         sample = decoder.read();\n      } else {\n         AudioDecoder nextDecoder(decoder);\n         nextDecoder.advance();\n         if (!nextDecoder.eof()) {\n            sample = nextDecoder.read();\n         } else {\n            sample = 0;\n         }\n      }\n\n      if (offsetFrac == FpZero) {\n         samples[i] = sample;\n      } else {\n         auto thisSampleMul = FpOne - offsetFrac;\n         auto lastSampleMul = offsetFrac;\n         auto lastSample = fixed_from_data<Pcm16Sample>(extras->src.lastSample[0]);\n         samples[i] = sample * thisSampleMul + lastSample * lastSampleMul;\n      }\n\n      offsetFrac += extras->src.ratio.value();\n\n      while (offsetFrac >= FpOne) {\n         // Advance the voice by one sample\n         offsetFrac -= FpOne;\n         decoder.advance();\n\n         // Update all the last sample listings.  Most of these are used\n         //  for FFT resampling (which we don't currently handle).\n         extras->src.lastSample[3] = extras->src.lastSample[2];\n         extras->src.lastSample[2] = extras->src.lastSample[1];\n         extras->src.lastSample[1] = extras->src.lastSample[0];\n         extras->src.lastSample[0] = fixed_to_data(sample);\n\n         // If we reached the end of the voice data, we should just leave\n         if (decoder.eof()) {\n            break;\n         }\n\n         // If we read through multiple samples due to a high SRC ratio,\n         //  then we need to actually read the upcoming sample in expectation\n         //  of the fact that its about to be stored in lastSample.\n         if (offsetFrac >= FpOne) {\n            sample = decoder.read();\n         }\n      }\n\n      if (decoder.eof()) {\n         break;\n      }\n   }\n\n   if (decoder.eof()) {\n      voice->state = AXVoiceState::Stopped;\n   }\n\n   decoder.toVoice(extras);\n\n   extras->src.currentOffsetFrac = ufixed_0_16_t { offsetFrac };\n}\n\nvoid applyADSR(virt_ptr<AXVoice> voice,\n               Pcm16Sample *samples,\n               int numSamples)\n{\n   auto extras = getVoiceExtras(voice->index);\n\n   for (int i = 0; i < numSamples; i++) {\n      samples[i] = samples[i] * (extras->ve.volume.value() + extras->ve.delta.value() * i);\n   }\n\n   extras->ve.volume += extras->ve.delta.value() * numSamples;\n}\n\nvoid\ndecodeVoiceSamples(int numSamples)\n{\n   const auto voices = getAcquiredVoices();\n\n   for (auto voice : voices) {\n      auto extras = getVoiceExtras(voice->index);\n\n      if (voice->state == AXVoiceState::Stopped) {\n         extras->numSamples = 0;\n         continue;\n      }\n\n      extras->numSamples = numSamples;\n      sampleVoice(voice, extras->samples, numSamples);\n      applyADSR(voice, extras->samples, numSamples);\n   }\n\n   // TODO: Apply Volume Evelope (ADSR)\n\n   // TODO: Apply Biquad Filter\n\n   // TODO: Apply Low Pass Filter\n}\n\nstatic Pcm16Sample gTvSamples[AXNumTvDevices][AXNumTvChannels][NumOutputSamples];\n\nstatic void\ninvokeAuxCallback(AuxData &aux, uint32_t numChannels, uint32_t numSamples, Pcm16Sample samples[6][144])\n{\n   if (aux.callback) {\n      auto auxCbData = virt_addrof(sDeviceData->auxCallbackData);\n      auxCbData->samples = numSamples;\n      auxCbData->channels = numChannels;\n\n      for (auto ch = 0u; ch < numChannels; ++ch) {\n         for (auto i = 0u; i < numSamples; ++i) {\n            sDeviceData->samples[ch][i] = static_cast<int32_t>(samples[ch][i]);\n         }\n\n         sDeviceData->samplePtrs[ch] = virt_addrof(sDeviceData->samples[ch][0]);\n      }\n\n      cafe::invoke(cpu::this_core::state(),\n                   aux.callback,\n                   virt_addrof(sDeviceData->samplePtrs),\n                   aux.userData,\n                   auxCbData);\n\n      for (auto ch = 0u; ch < numChannels; ++ch) {\n         for (auto i = 0u; i < numSamples; ++i) {\n            samples[ch][i] = fixed_from_data<Pcm16Sample>(\n               static_cast<int16_t>(sDeviceData->samples[ch][i]));\n         }\n      }\n   }\n}\n\nstatic void\ninvokeFinalMixCallback(DeviceTypeData &device,\n                       uint16_t numDevices,\n                       uint16_t numChannels,\n                       uint16_t numSamples,\n                       Pcm16Sample samples[4][6][144])\n{\n   if (device.finalMixCallback) {\n      auto mixCbData = virt_addrof(sDeviceData->finalMixCallbackData);\n      mixCbData->channels = numChannels;\n      mixCbData->samples = numSamples;\n      mixCbData->numDevices = numDevices;\n      mixCbData->channelsOut = mixCbData->channels;\n\n      for (auto dev = 0u; dev < numDevices; ++dev) {\n         for (auto ch = 0u; ch < numChannels; ++ch) {\n            auto axChanId = (dev * numChannels) + ch;\n\n            for (auto i = 0u; i < numSamples; ++i) {\n               int16_t sample = fixed_to_data(samples[dev][ch][i]);\n               sDeviceData->samples[axChanId][i] = static_cast<int32_t>(sample);\n            }\n\n            sDeviceData->samplePtrs[axChanId] = virt_addrof(sDeviceData->samples[axChanId][0]);\n         }\n      }\n      mixCbData->data = virt_addrof(sDeviceData->samplePtrs);\n\n      cafe::invoke(cpu::this_core::state(),\n                   device.finalMixCallback,\n                   mixCbData);\n\n      for (auto dev = 0u; dev < numDevices; ++dev) {\n         for (auto ch = 0u; ch < numChannels; ++ch) {\n            auto axChanId = (dev * numChannels) + ch;\n\n            for (auto i = 0u; i < numSamples; ++i) {\n               samples[dev][ch][i] = fixed_from_data<Pcm16Sample>(\n                  static_cast<int16_t>(sDeviceData->samples[axChanId][i]));\n            }\n         }\n      }\n   }\n}\n\nAXVoiceExtras::MixVolume &\ngetVoiceMixVolume(virt_ptr<AXVoiceExtras> extras,\n                  AXDeviceType type,\n                  uint32_t device,\n                  uint32_t channel,\n                  uint32_t bus)\n{\n   if (type == AXDeviceType::TV) {\n      decaf_check(device < AXNumTvDevices);\n      decaf_check(channel < AXNumTvChannels);\n      decaf_check(bus < AXNumTvBus);\n      return extras->tvVolume[device][channel][bus];\n   } else if (type == AXDeviceType::DRC) {\n      decaf_check(device < AXNumDrcDevices);\n      decaf_check(channel < AXNumDrcChannels);\n      decaf_check(bus < AXNumDrcBus);\n      return extras->drcVolume[device][channel][bus];\n   } else if (type == AXDeviceType::RMT) {\n      decaf_check(device < AXNumRmtDevices);\n      decaf_check(channel < AXNumRmtChannels);\n      decaf_check(bus < AXNumRmtBus);\n      return extras->rmtVolume[device][channel][bus];\n   } else {\n      decaf_abort(\"Unexpected device type\");\n   }\n}\n\nstatic virt_ptr<DeviceTypeData>\ngetDeviceGroup(AXDeviceType type)\n{\n   switch (type) {\n   case AXDeviceType::TV:\n      return virt_addrof(sDeviceData->tvDevices);\n   case AXDeviceType::DRC:\n      return virt_addrof(sDeviceData->drcDevices);\n   case AXDeviceType::RMT:\n      return virt_addrof(sDeviceData->rmtDevices);\n   default:\n      decaf_abort(\"Unexpected device type\");\n   }\n}\n\nstatic uint16_t\ngetDeviceNumDevices(AXDeviceType type)\n{\n   decaf_check(type < AXDeviceType::Max);\n   static const uint16_t devices[] = { AXNumTvDevices, AXNumDrcDevices, AXNumRmtDevices };\n   return devices[type];\n}\n\nstatic uint16_t\ngetDeviceNumBuses(AXDeviceType type)\n{\n   decaf_check(type < AXDeviceType::Max);\n   static const uint16_t busses[] = { AXNumTvBus, AXNumDrcBus, AXNumRmtBus };\n   return busses[type];\n}\n\nstatic uint16_t\ngetDeviceNumChannels(AXDeviceType type)\n{\n   decaf_check(type < AXDeviceType::Max);\n   static const uint16_t channels[] = { AXNumTvChannels, AXNumDrcChannels, AXNumRmtChannels };\n   return channels[type];\n}\n\nstatic void\nmixDevice(AXDeviceType type, uint16_t numSamples)\n{\n   static const auto AXMaxDevices = 4;\n   static const auto AXMaxBuses = 4;\n   static const auto AXMaxChannels = 6;\n\n   auto devices = getDeviceGroup(type);\n   auto numDevices = getDeviceNumDevices(type);\n   auto numBus = getDeviceNumBuses(type);\n   auto numChannels = getDeviceNumChannels(type);\n\n   decaf_check(numDevices <= AXMaxDevices);\n   decaf_check(numBus <= AXMaxBuses);\n   decaf_check(numChannels <= AXMaxChannels);\n   decaf_check(numSamples == 96 || numSamples == 144);\n\n   Pcm16Sample busSamples[AXMaxBuses][AXMaxDevices][AXMaxChannels][NumOutputSamples];\n   const auto voices = getAcquiredVoices();\n\n   memset(busSamples, 0, sizeof(Pcm16Sample) * AXMaxBuses * AXMaxDevices * AXMaxChannels * NumOutputSamples);\n\n   for (auto voice : voices) {\n      auto extras = getVoiceExtras(voice->index);\n\n      if (!extras->numSamples) {\n         continue;\n      }\n\n      decaf_check(extras->numSamples == numSamples);\n\n      for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) {\n         for (auto bus = 0u; bus < numBus; ++bus) {\n            for (auto channel = 0u; channel < numChannels; ++channel) {\n               auto &volume = getVoiceMixVolume(extras, type, deviceId, channel, bus);\n               auto &out = busSamples[bus][deviceId][channel];\n\n               for (auto i = 0u; i < numSamples; ++i) {\n                  out[i] += extras->samples[i] * volume.volume;\n               }\n\n               volume.volume += volume.delta;\n            }\n         }\n      }\n   }\n\n   for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) {\n      auto &device = devices->devices[deviceId];\n\n      for (auto bus = 1u; bus < numBus; ++bus) {\n         invokeAuxCallback(device.aux[bus - 1], numChannels, numSamples, busSamples[bus][deviceId]);\n      }\n   }\n\n   auto &mainBus = busSamples[0];\n\n   // Downmix all aux busses to main bus\n   for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) {\n      auto &device = devices->devices[deviceId];\n\n      for (auto bus = 1u; bus < numBus; ++bus) {\n         auto returnVolume = device.aux[bus - 1].returnVolume;\n         auto subBus = busSamples[bus];\n\n         for (auto channel = 0u; channel < numChannels; ++channel) {\n            for (auto i = 0u; i < numSamples; ++i) {\n               mainBus[deviceId][channel][i] += subBus[deviceId][channel][i] * returnVolume;\n            }\n         }\n      }\n   }\n\n   // Apply overall device volume\n   for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) {\n      auto &device = devices->devices[deviceId];\n\n      for (auto channel = 0u; channel < numChannels; ++channel) {\n         for (auto i = 0u; i < numSamples; ++i) {\n            mainBus[deviceId][channel][i] = mainBus[deviceId][channel][i] * device.volume;\n         }\n      }\n   }\n\n   // Now we need to perform upsampling (I think)\n   auto upsample32to48 = [](Pcm16Sample *samples) {\n      // currently lazy...\n      auto output = samples;\n      Pcm16Sample input[96];\n      memcpy(input, output, sizeof(Pcm16Sample) * 96);\n\n      // Perform upsampling\n      for (auto i = 0u; i < NumOutputSamples; ++i) {\n         float sampleIdx = static_cast<float>(i) / 144.0f * 96.0f;\n         auto sampleLo = static_cast<uint32_t>(std::min(143.0f, std::floor(sampleIdx)));\n         auto sampleHi = static_cast<uint32_t>(std::min(95.0f, std::ceil(sampleIdx)));\n         float sampleFrac = sampleIdx - sampleLo;\n         output[i] = (input[sampleLo] * (1.0f - sampleFrac)) + (input[sampleHi] * sampleFrac);\n      }\n   };\n\n   // Perform upsampling and final mix callback invokation\n   if (devices->upsampleAfterFinalMix) {\n      invokeFinalMixCallback(*devices, numDevices, numChannels, numSamples, mainBus);\n\n      if (numSamples != NumOutputSamples) {\n         for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) {\n            for (auto channel = 0u; channel < numChannels; ++channel) {\n               upsample32to48(mainBus[deviceId][channel]);\n            }\n         }\n      }\n   } else {\n      if (numSamples != NumOutputSamples) {\n         for (auto deviceId = 0u; deviceId < numDevices; ++deviceId) {\n            for (auto channel = 0u; channel < numChannels; ++channel) {\n               upsample32to48(mainBus[deviceId][channel]);\n            }\n         }\n      }\n\n      invokeFinalMixCallback(*devices, numDevices, numChannels, NumOutputSamples, mainBus);\n   }\n\n   // TODO: Apply compressor\n\n   // TODO: Channel upmix/downmix, but I think we should let the audio driver (aka SDL) handle that\n\n   if (type == AXDeviceType::TV) {\n      // Copy the generated data out for later pickup\n      memcpy(gTvSamples, mainBus, sizeof(Pcm16Sample) * numDevices * numChannels * NumOutputSamples);\n   } else if (type == AXDeviceType::DRC) {\n      // We currently just discard the generated DRC audio\n   } else if (type == AXDeviceType::RMT) {\n      // We also discard generated RMT audio\n   } else {\n      decaf_abort(\"Unexpected device type during copy-out\");\n   }\n}\n\nvoid\nmixOutput(int32_t* buffer,\n          uint16_t numSamples,\n          uint16_t numChannels)\n{\n   // Decode audio samples from the source voices\n   decodeVoiceSamples(numSamples);\n\n   // Mix all the devices\n   mixDevice(AXDeviceType::TV, numSamples);\n   mixDevice(AXDeviceType::DRC, numSamples);\n   mixDevice(AXDeviceType::RMT, numSamples);\n\n   // Send off the TV device 0 data to be played on host\n   for (auto i = 0; i < NumOutputSamples; ++i) {\n      for (auto ch = 0; ch < numChannels; ++ch) {\n         buffer[numChannels * i + ch] = fixed_to_data(gTvSamples[0][ch][i]);\n      }\n   }\n}\n\n} // namespace internal\n\nAXResult\nAXGetDeviceMode(AXDeviceType type,\n                virt_ptr<AXDeviceMode> outMode)\n{\n   if (!outMode) {\n      return AXResult::Success;\n   }\n\n   switch (type) {\n   case AXDeviceType::TV:\n      *outMode = sDeviceData->tvDevices.mode;\n      break;\n   case AXDeviceType::DRC:\n      *outMode = sDeviceData->drcDevices.mode;\n      break;\n   case AXDeviceType::RMT:\n      *outMode = sDeviceData->rmtDevices.mode;\n      break;\n   default:\n      return AXResult::InvalidDeviceType;\n   }\n\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDeviceMode(AXDeviceType type,\n                AXDeviceMode mode)\n{\n   auto devices = internal::getDeviceGroup(type);\n\n   switch (type) {\n   case AXDeviceType::TV:\n      devices->mode = mode;\n      break;\n   case AXDeviceType::DRC:\n      devices->mode = mode;\n      break;\n   case AXDeviceType::RMT:\n      devices->mode = mode;\n      break;\n   default:\n      return AXResult::InvalidDeviceType;\n   }\n\n   return AXResult::Success;\n}\n\nAXResult\nAXGetDeviceFinalMixCallback(AXDeviceType type,\n                            virt_ptr<AXDeviceFinalMixCallback> outFunc)\n{\n   if (!outFunc) {\n      return AXResult::Success;\n   }\n\n   auto devices = internal::getDeviceGroup(type);\n\n   switch (type) {\n   case AXDeviceType::TV:\n      *outFunc = devices->finalMixCallback;\n      break;\n   case AXDeviceType::DRC:\n      *outFunc = devices->finalMixCallback;\n      break;\n   case AXDeviceType::RMT:\n      *outFunc = devices->finalMixCallback;\n      break;\n   default:\n      return AXResult::InvalidDeviceType;\n   }\n\n   return AXResult::Success;\n}\n\nAXResult\nAXRegisterDeviceFinalMixCallback(AXDeviceType type,\n                                 AXDeviceFinalMixCallback func)\n{\n   auto devices = internal::getDeviceGroup(type);\n\n   switch (type) {\n   case AXDeviceType::TV:\n      devices->finalMixCallback = func;\n      break;\n   case AXDeviceType::DRC:\n      devices->finalMixCallback = func;\n      break;\n   case AXDeviceType::RMT:\n      devices->finalMixCallback = func;\n      break;\n   default:\n      return AXResult::InvalidDeviceType;\n   }\n\n   return AXResult::Success;\n}\n\nAXResult\nAXGetAuxCallback(AXDeviceType type,\n                 uint32_t deviceId,\n                 AXAuxId auxId,\n                 virt_ptr<AXAuxCallback> outCallback,\n                 virt_ptr<virt_ptr<void>> outUserData)\n{\n   auto device = internal::getDevice(type, deviceId);\n\n   if (!device) {\n      return AXResult::InvalidDeviceType;\n   }\n\n   if (outCallback) {\n      *outCallback = device->aux[auxId].callback;\n   }\n\n   if (outUserData) {\n      *outUserData = device->aux[auxId].userData;\n   }\n\n   return AXResult::Success;\n}\n\nAXResult\nAXRegisterAuxCallback(AXDeviceType type,\n                      uint32_t deviceId,\n                      AXAuxId auxId,\n                      AXAuxCallback callback,\n                      virt_ptr<void> userData)\n{\n   auto device = internal::getDevice(type, deviceId);\n\n   if (!device) {\n      return AXResult::InvalidDeviceType;\n   }\n\n   device->aux[auxId].callback = callback;\n   device->aux[auxId].userData = userData;\n\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDeviceLinearUpsampler(AXDeviceType type,\n                           uint32_t deviceId,\n                           BOOL linear)\n{\n   auto device = internal::getDevice(type, deviceId);\n\n   if (!device) {\n      return AXResult::InvalidDeviceType;\n   }\n\n   device->linearUpsample = !!linear;\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDeviceCompressor(AXDeviceType type,\n                      BOOL compressor)\n{\n   switch (type) {\n   case AXDeviceType::TV:\n      sDeviceData->tvDevices.compressor = !!compressor;\n      break;\n   case AXDeviceType::DRC:\n      sDeviceData->drcDevices.compressor = !!compressor;\n      break;\n   case AXDeviceType::RMT:\n      sDeviceData->rmtDevices.compressor = !!compressor;\n      break;\n   default:\n      return AXResult::InvalidDeviceType;\n   }\n\n   return AXResult::Success;\n}\n\nAXResult\nAXGetDeviceUpsampleStage(AXDeviceType type,\n                         virt_ptr<BOOL> outUpsampleAfterFinalMixCallback)\n{\n   if (!outUpsampleAfterFinalMixCallback) {\n      return AXResult::Success;\n   }\n\n   switch (type) {\n   case AXDeviceType::TV:\n      *outUpsampleAfterFinalMixCallback = sDeviceData->tvDevices.upsampleAfterFinalMix ? TRUE : FALSE;\n      break;\n   case AXDeviceType::DRC:\n      *outUpsampleAfterFinalMixCallback = sDeviceData->drcDevices.upsampleAfterFinalMix ? TRUE : FALSE;\n      break;\n   case AXDeviceType::RMT:\n      *outUpsampleAfterFinalMixCallback = sDeviceData->rmtDevices.upsampleAfterFinalMix ? TRUE : FALSE;\n      break;\n   default:\n      return AXResult::InvalidDeviceType;\n   }\n\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDeviceUpsampleStage(AXDeviceType type,\n                         BOOL upsampleAfterFinalMixCallback)\n{\n   switch (type) {\n   case AXDeviceType::TV:\n      sDeviceData->tvDevices.upsampleAfterFinalMix = !!upsampleAfterFinalMixCallback;\n      break;\n   case AXDeviceType::DRC:\n      sDeviceData->drcDevices.upsampleAfterFinalMix = !!upsampleAfterFinalMixCallback;\n      break;\n   case AXDeviceType::RMT:\n      sDeviceData->rmtDevices.upsampleAfterFinalMix = !!upsampleAfterFinalMixCallback;\n      break;\n   default:\n      return AXResult::InvalidDeviceType;\n   }\n\n   return AXResult::Success;\n}\n\nAXResult\nAXGetDeviceVolume(AXDeviceType type,\n                  uint32_t deviceId,\n                  virt_ptr<uint16_t> outVolume)\n{\n   auto device = internal::getDevice(type, deviceId);\n\n   if (!device) {\n      return AXResult::InvalidDeviceType;\n   }\n\n   if (outVolume) {\n      *outVolume = fixed_to_data(device->volume);\n   }\n\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDeviceVolume(AXDeviceType type,\n                  uint32_t deviceId,\n                  uint16_t volume)\n{\n   auto device = internal::getDevice(type, deviceId);\n\n   if (!device) {\n      return AXResult::InvalidDeviceType;\n   }\n\n   device->volume = fixed_from_data<ufixed_1_15_t>(volume);\n   return AXResult::Success;\n}\n\nAXResult\nAXGetAuxReturnVolume(AXDeviceType type,\n                     uint32_t deviceId,\n                     AXAuxId auxId,\n                     virt_ptr<uint16_t> outVolume)\n{\n   auto device = internal::getDevice(type, deviceId);\n\n   if (!device) {\n      return AXResult::InvalidDeviceType;\n   }\n\n   if (outVolume) {\n      *outVolume = fixed_to_data(device->aux[auxId].returnVolume);\n   }\n\n   return AXResult::Success;\n}\n\nAXResult\nAXSetAuxReturnVolume(AXDeviceType type,\n                     uint32_t deviceId,\n                     AXAuxId auxId,\n                     uint16_t volume)\n{\n   auto device = internal::getDevice(type, deviceId);\n\n   if (!device) {\n      return AXResult::InvalidDeviceType;\n   }\n\n   device->aux[auxId].returnVolume = fixed_from_data<ufixed_1_15_t>(volume);\n   return AXResult::Success;\n}\n\nnamespace internal\n{\n\nvoid\ninitDevices()\n{\n   for (auto &device : sDeviceData->tvDevices.devices) {\n      device.volume = DefaultVolume;\n      for (auto &aux : device.aux) {\n         aux.returnVolume = DefaultVolume;\n      }\n   }\n   for (auto &device : sDeviceData->drcDevices.devices) {\n      device.volume = DefaultVolume;\n      for (auto &aux : device.aux) {\n         aux.returnVolume = DefaultVolume;\n      }\n   }\n   for (auto &device : sDeviceData->rmtDevices.devices) {\n      device.volume = DefaultVolume;\n      for (auto &aux : device.aux) {\n         aux.returnVolume = DefaultVolume;\n      }\n   }\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerDeviceSymbols()\n{\n   RegisterFunctionExport(AXGetDeviceMode);\n   RegisterFunctionExport(AXSetDeviceMode);\n   RegisterFunctionExport(AXGetDeviceFinalMixCallback);\n   RegisterFunctionExport(AXRegisterDeviceFinalMixCallback);\n   RegisterFunctionExport(AXGetAuxCallback);\n   RegisterFunctionExport(AXRegisterAuxCallback);\n   RegisterFunctionExport(AXSetDeviceLinearUpsampler);\n   RegisterFunctionExport(AXSetDeviceCompressor);\n   RegisterFunctionExport(AXGetDeviceUpsampleStage);\n   RegisterFunctionExport(AXSetDeviceUpsampleStage);\n   RegisterFunctionExport(AXGetDeviceVolume);\n   RegisterFunctionExport(AXSetDeviceVolume);\n   RegisterFunctionExport(AXGetAuxReturnVolume);\n   RegisterFunctionExport(AXSetAuxReturnVolume);\n\n   RegisterDataInternal(sDeviceData);\n}\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_device.h",
    "content": "#pragma once\n#include \"sndcore2_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::sndcore2\n{\n\n#pragma pack(push, 1)\n\nstruct AXAuxCallbackData\n{\n   be2_val<uint32_t> channels;\n   be2_val<uint32_t> samples;\n};\nCHECK_OFFSET(AXAuxCallbackData, 0x0, channels);\nCHECK_OFFSET(AXAuxCallbackData, 0x4, samples);\nCHECK_SIZE(AXAuxCallbackData, 0x8);\n\nstruct AXDeviceFinalMixData\n{\n   be2_virt_ptr<virt_ptr<int32_t>> data;\n   be2_val<uint16_t> channels;\n   be2_val<uint16_t> samples;\n   be2_val<uint16_t> numDevices;\n   be2_val<uint16_t> channelsOut;\n};\nCHECK_OFFSET(AXDeviceFinalMixData, 0x0, data);\nCHECK_OFFSET(AXDeviceFinalMixData, 0x4, channels);\nCHECK_OFFSET(AXDeviceFinalMixData, 0x6, samples);\nCHECK_OFFSET(AXDeviceFinalMixData, 0x8, numDevices);\nCHECK_OFFSET(AXDeviceFinalMixData, 0xa, channelsOut);\nCHECK_SIZE(AXDeviceFinalMixData, 0xc);\n\n#pragma pack(pop)\n\nusing AXDeviceFinalMixCallback = virt_func_ptr<\n   void(virt_ptr<AXDeviceFinalMixData>)\n>;\n\nusing AXAuxCallback = virt_func_ptr<\n   void(virt_ptr<virt_ptr<int32_t>>, virt_ptr<void>, virt_ptr<AXAuxCallbackData>)\n>;\n\nAXResult\nAXGetDeviceMode(AXDeviceType type,\n                virt_ptr<AXDeviceMode> mode);\n\nAXResult\nAXSetDeviceMode(AXDeviceType type,\n                AXDeviceMode mode);\n\nAXResult\nAXGetDeviceFinalMixCallback(AXDeviceType type,\n                            virt_ptr<AXDeviceFinalMixCallback> outCallback);\n\nAXResult\nAXRegisterDeviceFinalMixCallback(AXDeviceType type,\n                                 AXDeviceFinalMixCallback callback);\n\nAXResult\nAXGetAuxCallback(AXDeviceType type,\n                 uint32_t deviceId,\n                 AXAuxId auxId,\n                 virt_ptr<AXAuxCallback> outCallback,\n                 virt_ptr<virt_ptr<void>> outUserData);\n\nAXResult\nAXRegisterAuxCallback(AXDeviceType type,\n                      uint32_t deviceId,\n                      AXAuxId auxId,\n                      AXAuxCallback callback,\n                      virt_ptr<void> userData);\n\nAXResult\nAXSetDeviceLinearUpsampler(AXDeviceType type,\n                           uint32_t deviceId,\n                           BOOL linear);\n\nAXResult\nAXSetDeviceCompressor(AXDeviceType type,\n                      BOOL compressor);\n\nAXResult\nAXGetDeviceUpsampleStage(AXDeviceType type,\n                         virt_ptr<BOOL> outUpsampleAfterFinalMixCallback);\n\nAXResult\nAXSetDeviceUpsampleStage(AXDeviceType type,\n                         BOOL upsampleAfterFinalMixCallback);\n\nAXResult\nAXGetDeviceVolume(AXDeviceType type,\n                  uint32_t deviceId,\n                  virt_ptr<uint16_t> outVolume);\n\nAXResult\nAXSetDeviceVolume(AXDeviceType type,\n                  uint32_t deviceId,\n                  uint16_t volume);\n\nAXResult\nAXGetAuxReturnVolume(AXDeviceType type,\n                     uint32_t deviceId,\n                     AXAuxId auxId,\n                     virt_ptr<uint16_t> outVolume);\n\nAXResult\nAXSetAuxReturnVolume(AXDeviceType type,\n                     uint32_t deviceId,\n                     AXAuxId auxId,\n                     uint16_t volume);\n\nnamespace internal\n{\n\nvoid\nmixOutput(int32_t* buffer,\n          uint16_t numSamples,\n          uint16_t numChannels);\n\nvoid\ninitDevices();\n\n} // namespace internal\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_enum.h",
    "content": "#ifndef CAFE_SNDCORE2_ENUM_H\n#define CAFE_SNDCORE2_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(sndcore2)\n\nENUM_BEG(AXAuxId, uint32_t)\n   ENUM_VALUE(A,                 0)\n   ENUM_VALUE(B,                 1)\n   ENUM_VALUE(C,                 2)\n   ENUM_VALUE(Max,               3)\nENUM_END(AXAuxId)\n\nENUM_BEG(AXBusType, uint32_t)\n   ENUM_VALUE(Main,              0)\n   ENUM_VALUE(A,                 1)\n   ENUM_VALUE(B,                 2)\n   ENUM_VALUE(C,                 3)\n   ENUM_VALUE(Max,               4)\nENUM_END(AXBusType)\n\nENUM_BEG(AXChannels, uint32_t)\n   ENUM_VALUE(Left,              0)\n   ENUM_VALUE(Right,             1)\n   ENUM_VALUE(LeftSurround,      2)\n   ENUM_VALUE(RightSurround,     3)\n   ENUM_VALUE(Center,            4)\n   ENUM_VALUE(Sub,               5)\n   ENUM_VALUE(Max,               6)\nENUM_END(AXChannels)\n\nENUM_BEG(AXDeviceMode, uint32_t)\n   // Unknown\nENUM_END(AXDeviceMode)\n\nENUM_BEG(AXDeviceType, uint32_t)\n   ENUM_VALUE(TV,                0)\n   ENUM_VALUE(DRC,               1)\n   ENUM_VALUE(RMT,               2)  // Classic Controller, Wiimote etc.\n   ENUM_VALUE(Max,               3)\nENUM_END(AXDeviceType)\n\nENUM_BEG(AXDRCOutput, uint32_t)\n   // Unknown\nENUM_END(AXDRCOutput)\n\nENUM_BEG(AXDRCVSLC, uint32_t)\n   // Unknown\nENUM_END(AXDRCVSLC)\n\nENUM_BEG(AXDRCVSMode, uint32_t)\n   // Unknown\nENUM_END(AXDRCVSMode)\n\nENUM_BEG(AXDRCVSSpeakerPosition, uint32_t)\n   // Unknown\nENUM_END(AXDRCVSSpeakerPosition)\n\nENUM_BEG(AXDRCVSSurroundLevelGain, uint32_t)\n   // Unknown\nENUM_END(AXDRCVSSurroundLevelGain)\n\nENUM_BEG(AXInitPipeline, uint32_t)\n   ENUM_VALUE(Single,            0)\n   ENUM_VALUE(FourStage,         1)\nENUM_END(AXInitPipeline)\n\nENUM_BEG(AXRendererFreq, uint32_t)\n   ENUM_VALUE(Freq32khz,          0)\n   ENUM_VALUE(Freq48khz,          1)\nENUM_END(AXRendererFreq)\n\nENUM_BEG(AXResult, int32_t)\n   ENUM_VALUE(Success,           0)\n   ENUM_VALUE(InvalidDeviceType, -1)\n   ENUM_VALUE(InvalidDRCVSMode,  -13)\n   ENUM_VALUE(TooManyCallbacks,  -15)\n   ENUM_VALUE(CallbackNotFound,  -16)\n   ENUM_VALUE(CallbackInvalid,   -17)\n   ENUM_VALUE(VoiceIsRunning,    -18)\n   ENUM_VALUE(DelayTooBig,       -19)\nENUM_END(AXResult)\n\nENUM_BEG(AXVoiceFormat, uint16_t)\n   ENUM_VALUE(ADPCM,             0x00)\n   ENUM_VALUE(LPCM16,            0x0A)\n   ENUM_VALUE(LPCM8,             0x19)\nENUM_END(AXVoiceFormat)\n\nENUM_BEG(AXVoiceLoop, uint16_t)\n   ENUM_VALUE(Disabled,          0)\n   ENUM_VALUE(Enabled,           1)\nENUM_END(AXVoiceLoop)\n\nENUM_BEG(AXRenderer, uint32_t)\n   ENUM_VALUE(DSP, 0)\n   ENUM_VALUE(CPU, 1)\n   ENUM_VALUE(Auto, 2)\nENUM_END(AXRenderer)\n\nENUM_BEG(AXVoiceSrcType, uint32_t)\n   ENUM_VALUE(None, 0)\n   ENUM_VALUE(Linear, 1)\n   ENUM_VALUE(Unk0, 2)\n   ENUM_VALUE(Unk1, 3)\n   ENUM_VALUE(Unk2, 4)\nENUM_END(AXVoiceSrcType)\n\nENUM_BEG(AXVoiceState, uint32_t)\n   ENUM_VALUE(Stopped,           0)\n   ENUM_VALUE(Playing,           1)\nENUM_END(AXVoiceState)\n\nENUM_BEG(AXVoiceType, uint16_t)\n   ENUM_VALUE(Default, 0)\n   ENUM_VALUE(Streaming, 1)\nENUM_END(AXVoiceType)\n\nENUM_BEG(AXVoiceSrcRatioResult, int32_t)\n   ENUM_VALUE(Success,                    0)\n   ENUM_VALUE(RatioLessThanZero,          -1)\n   ENUM_VALUE(RatioGreaterThanSomething,  -2)\nENUM_END(AXVoiceSrcRatioResult)\n\nENUM_NAMESPACE_ENTER(internal)\n\nENUM_BEG(AXVoiceSyncBits, uint32_t)\n   ENUM_VALUE(SrcType,       1 << 0)\n\n   ENUM_VALUE(State,         1 << 2)\n   ENUM_VALUE(Type,          1 << 3)\n\n   ENUM_VALUE(Itd,           1 << 5)\n   ENUM_VALUE(ItdTarget,     1 << 6)\n\n   ENUM_VALUE(Ve,            1 << 8)\n   ENUM_VALUE(VeDelta,       1 << 9)\n   ENUM_VALUE(Addr,          1 << 10)\n   ENUM_VALUE(Loop,          1 << 11)\n   ENUM_VALUE(LoopOffset,    1 << 12)\n   ENUM_VALUE(EndOffset,     1 << 13)\n   ENUM_VALUE(CurrentOffset, 1 << 14)\n   ENUM_VALUE(Adpcm,         1 << 15)\n   ENUM_VALUE(Src,           1 << 16)\n   ENUM_VALUE(SrcRatio,      1 << 17)\n   ENUM_VALUE(AdpcmLoop,     1 << 18)\n   ENUM_VALUE(Lpf,           1 << 19)\n   ENUM_VALUE(LpfCoefs,      1 << 20)\n   ENUM_VALUE(Biquad,        1 << 21)\n   ENUM_VALUE(BiquadCoefs,   1 << 22)\n   ENUM_VALUE(RmtOn,         1 << 23)\n\n   ENUM_VALUE(RmtSrc,        1 << 27)\n   ENUM_VALUE(RmtIIR,        1 << 28)\n   ENUM_VALUE(RmtIIRCoefs0,  1 << 29)\n   ENUM_VALUE(RmtIIRCoefs1,  1 << 30)\nENUM_END(AXVoiceSyncBits)\n\nENUM_NAMESPACE_EXIT(internal)\n\nENUM_NAMESPACE_EXIT(sndcore2)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_SNDCORE2_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_rmt.cpp",
    "content": "#include \"sndcore2.h\"\n#include \"sndcore2_rmt.h\"\n#include <cafe/libraries/cafe_hle_stub.h>\n\nnamespace cafe::sndcore2\n{\n\nint32_t\nAXRmtGetSamplesLeft()\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nint32_t\nAXRmtGetSamples(int32_t,\n                virt_ptr<uint8_t> buffer,\n                int32_t samples)\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nint32_t\nAXRmtAdvancePtr(int32_t numSamples)\n{\n   decaf_warn_stub();\n   return 0;\n}\n\nvoid\nLibrary::registerRmtSymbols()\n{\n   RegisterFunctionExport(AXRmtGetSamplesLeft);\n   RegisterFunctionExport(AXRmtGetSamples);\n   RegisterFunctionExport(AXRmtAdvancePtr);\n}\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_rmt.h",
    "content": "#pragma once\n#include \"sndcore2_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::sndcore2\n{\n\nint32_t\nAXRmtGetSamplesLeft();\n\nint32_t\nAXRmtGetSamples(int32_t,\n                virt_ptr<uint8_t> buffer,\n                int32_t samples);\n\nint32_t\nAXRmtAdvancePtr(int32_t numSamples);\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_voice.cpp",
    "content": "#include \"sndcore2.h\"\n#include \"sndcore2_config.h\"\n#include \"sndcore2_constants.h\"\n#include \"sndcore2_voice.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"decaf_config.h\"\n#include \"decaf_sound.h\"\n\n#include <common/decaf_assert.h>\n#include <common/platform_dir.h>\n#include <array>\n#include <fmt/format.h>\n#include <fstream>\n#include <libcpu/cpu_formatters.h>\n#include <queue>\n\nnamespace cafe::sndcore2\n{\n\nstruct StaticVoiceData\n{\n   be2_array<AXVoice, AXMaxNumVoices> voices;\n   be2_array<internal::AXVoiceExtras, AXMaxNumVoices> voiceExtras;\n};\n\n// TODO: Move away from std::vector\nstatic std::vector<virt_ptr<AXVoice>>\nsAcquiredVoices;\n\n// TODO: Move away from std::queue\nstatic std::queue<virt_ptr<AXVoice>>\nsAvailVoiceStack;\n\nstatic virt_ptr<StaticVoiceData>\nsVoiceData = nullptr;\n\nvirt_ptr<AXVoice>\nAXAcquireVoice(uint32_t priority,\n               AXVoiceCallbackFn callback,\n               virt_ptr<void> userContext)\n{\n   return AXAcquireVoiceEx(priority,\n                           virt_func_cast<AXVoiceCallbackExFn>(virt_func_cast<virt_addr>(callback)),\n                           userContext);\n}\n\nvirt_ptr<AXVoice>\nAXAcquireVoiceEx(uint32_t priority,\n                 AXVoiceCallbackExFn callback,\n                 virt_ptr<void> userContext)\n{\n   if (sAvailVoiceStack.empty()) {\n      // If there are no available voices, try to force-deallocate one...\n      for (auto &i : sAcquiredVoices) {\n         if (i->priority < priority) {\n            // TODO: Send callback for forcing the FreeVoice\n            AXFreeVoice(i);\n            break;\n         }\n      }\n   }\n\n   if (sAvailVoiceStack.empty()) {\n      // No voices available to acquire\n      return nullptr;\n   }\n\n   // Grab our voice from the stack\n   auto foundVoice = sAvailVoiceStack.front();\n   sAvailVoiceStack.pop();\n\n   // Reset the voice\n   auto voiceIndex = foundVoice->index;\n   std::memset(foundVoice.get(), 0, sizeof(AXVoice));\n   foundVoice->index = voiceIndex;\n\n   // Configure the voice with stuff we know about\n   foundVoice->priority = priority;\n   foundVoice->callbackEx = callback;\n   foundVoice->userContext = userContext;\n\n   auto extras = internal::getVoiceExtras(foundVoice->index);\n   std::memset(extras.get(), 0, sizeof(internal::AXVoiceExtras));\n   extras->src.ratio = ufixed_16_16_t { 1.0 };\n\n   // Save this to the acquired voice list so that it can be\n   //  forcefully freed if a higher priority voice is needed.\n   sAcquiredVoices.push_back(foundVoice);\n\n   return foundVoice;\n}\n\nBOOL\nAXCheckVoiceOffsets(virt_ptr<AXVoiceOffsets> offsets)\n{\n   return TRUE;\n}\n\nvoid\nAXFreeVoice(virt_ptr<AXVoice> voice)\n{\n   auto voiceIter = std::find(sAcquiredVoices.begin(), sAcquiredVoices.end(), voice);\n   decaf_check(voiceIter != sAcquiredVoices.end());\n\n   // Erase this voice from the acquired list\n   sAcquiredVoices.erase(voiceIter);\n\n   // Make this voice available on the available stack!\n   sAvailVoiceStack.push(voice);\n}\n\nuint32_t\nAXGetMaxVoices()\n{\n   return AXMaxNumVoices;\n}\n\nuint32_t\nAXGetVoiceCurrentOffsetEx(virt_ptr<AXVoice> voice,\n                          virt_ptr<const void> samples)\n{\n   auto offsets = StackObject<AXVoiceOffsets> { };\n   AXGetVoiceOffsetsEx(voice, offsets, samples);\n   return offsets->currentOffset;\n}\n\nuint32_t\nAXGetVoiceLoopCount(virt_ptr<AXVoice> voice)\n{\n   return sVoiceData->voiceExtras[voice->index].loopCount;\n}\n\nuint32_t\nAXGetVoiceMixerSelect(virt_ptr<AXVoice> voice)\n{\n   return voice->renderer;\n}\n\nvoid\nAXGetVoiceOffsetsEx(virt_ptr<AXVoice> voice,\n                    virt_ptr<AXVoiceOffsets> offsets,\n                    virt_ptr<const void> samples)\n{\n   voice->offsets.data = samples;\n   AXGetVoiceOffsets(voice, offsets);\n   voice->offsets = *offsets;\n}\n\nvoid\nAXGetVoiceOffsets(virt_ptr<AXVoice> voice,\n                  virt_ptr<AXVoiceOffsets> offsets)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   *offsets = voice->offsets;\n\n   auto pageAddress = extras->data.memPageNumber << 29;\n   auto baseAddress = virt_cast<virt_addr>(offsets->data);\n   if (extras->data.format == AXVoiceFormat::ADPCM) {\n      auto pageAddressSamples = static_cast<int64_t>(pageAddress) << 1;\n      auto baseAddressSamples = static_cast<int64_t>(baseAddress) << 1;\n      offsets->loopOffset = static_cast<uint32_t>(pageAddressSamples + static_cast<int64_t>(extras->data.loopOffsetAbs) - baseAddressSamples);\n      offsets->endOffset = static_cast<uint32_t>(pageAddressSamples + static_cast<int64_t>(extras->data.endOffsetAbs) - baseAddressSamples);\n      offsets->currentOffset = static_cast<uint32_t>(pageAddressSamples + static_cast<int64_t>(extras->data.currentOffsetAbs) - baseAddressSamples);\n   } else if (extras->data.format == AXVoiceFormat::LPCM16) {\n      auto pageAddressSamples = pageAddress >> 1;\n      auto baseAddressSamples = baseAddress >> 1;\n      offsets->loopOffset = static_cast<uint32_t>(extras->data.loopOffsetAbs + pageAddressSamples - baseAddressSamples);\n      offsets->endOffset = static_cast<uint32_t>(extras->data.endOffsetAbs + pageAddressSamples - baseAddressSamples);\n      offsets->currentOffset = static_cast<uint32_t>(extras->data.currentOffsetAbs + pageAddressSamples - baseAddressSamples);\n   } else if (extras->data.format == AXVoiceFormat::LPCM8) {\n      offsets->loopOffset = static_cast<uint32_t>(extras->data.loopOffsetAbs + pageAddress - baseAddress);\n      offsets->endOffset = static_cast<uint32_t>(extras->data.endOffsetAbs + pageAddress - baseAddress);\n      offsets->currentOffset = static_cast<uint32_t>(extras->data.currentOffsetAbs + pageAddress - baseAddress);\n   } else {\n      decaf_abort(fmt::format(\"Unexpected voice data format {}\", extras->data.format))\n   }\n}\n\nBOOL\nAXIsVoiceRunning(virt_ptr<AXVoice> voice)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   return (extras->state != AXVoiceState::Stopped) ? 1 : 0;\n}\n\nvoid\nAXSetVoiceAdpcm(virt_ptr<AXVoice> voice,\n                virt_ptr<AXVoiceAdpcm> adpcm)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   extras->adpcm = *adpcm;\n   extras->syncBits |= internal::AXVoiceSyncBits::Adpcm;\n}\n\nvoid\nAXSetVoiceAdpcmLoop(virt_ptr<AXVoice> voice,\n                    virt_ptr<AXVoiceAdpcmLoopData> loopData)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   extras->adpcmLoop = *loopData;\n   extras->syncBits |= internal::AXVoiceSyncBits::AdpcmLoop;\n}\n\nvoid\nAXSetVoiceCurrentOffset(virt_ptr<AXVoice> voice,\n                        uint32_t offset)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   auto baseAddress = virt_cast<virt_addr>(voice->offsets.data) & 0x1FFFFFFF;\n\n   if (extras->data.format == AXVoiceFormat::ADPCM) {\n      extras->data.currentOffsetAbs = (baseAddress << 1) + offset;\n   } else if (extras->data.format == AXVoiceFormat::LPCM16) {\n      extras->data.currentOffsetAbs = (baseAddress >> 1) + offset;\n   } else if (extras->data.format == AXVoiceFormat::LPCM8) {\n      extras->data.currentOffsetAbs = baseAddress + offset;\n   } else {\n      decaf_abort(fmt::format(\"Unexpected voice data type {}\", extras->data.format));\n   }\n\n   extras->syncBits |= internal::AXVoiceSyncBits::CurrentOffset;\n}\n\nvoid\nAXSetVoiceCurrentOffsetEx(virt_ptr<AXVoice> voice,\n                          uint32_t offset,\n                          virt_ptr<const void> samples)\n{\n   auto offsets = StackObject<AXVoiceOffsets> { };\n   voice->offsets.data = samples;\n\n   AXGetVoiceOffsets(voice, offsets);\n   AXSetVoiceCurrentOffset(voice, offset);\n}\n\n\nAXResult\nAXSetVoiceDeviceMix(virt_ptr<AXVoice> voice,\n                    AXDeviceType type,\n                    uint32_t deviceId,\n                    virt_ptr<AXVoiceDeviceMixData> mixData)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n\n   switch (type) {\n   case AXDeviceType::TV:\n      decaf_check(deviceId < AXNumTvDevices);\n\n      for (auto c = 0; c < AXNumTvChannels; ++c) {\n         for (auto b = 0; b < AXNumTvBus; ++b) {\n            extras->tvVolume[deviceId][c][b].volume = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].volume);\n            extras->tvVolume[deviceId][c][b].delta = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].delta);\n         }\n      }\n\n      break;\n   case AXDeviceType::DRC:\n      decaf_check(deviceId < AXNumDrcDevices);\n\n      for (auto c = 0; c < AXNumDrcChannels; ++c) {\n         for (auto b = 0; b < AXNumDrcBus; ++b) {\n            extras->drcVolume[deviceId][c][b].volume = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].volume);\n            extras->drcVolume[deviceId][c][b].delta = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].delta);\n         }\n      }\n\n      break;\n   case AXDeviceType::RMT:\n      decaf_check(deviceId < AXNumRmtDevices);\n\n      for (auto c = 0; c < AXNumRmtChannels; ++c) {\n         for (auto b = 0; b < AXNumRmtBus; ++b) {\n            extras->rmtVolume[deviceId][c][b].volume = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].volume);\n            extras->rmtVolume[deviceId][c][b].delta = fixed_from_data<ufixed_1_15_t>(mixData[c].bus[b].delta);\n         }\n      }\n\n      break;\n   }\n\n   return AXResult::Success;\n}\n\nvoid\nAXSetVoiceEndOffset(virt_ptr<AXVoice> voice,\n                    uint32_t offset)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   auto baseAddress = virt_cast<virt_addr>(voice->offsets.data) & 0x1FFFFFFF;\n   voice->offsets.endOffset = offset;\n\n   if (extras->data.format == AXVoiceFormat::ADPCM) {\n      extras->data.endOffsetAbs = virt_addr { (baseAddress << 1) + offset };\n   } else if (extras->data.format == AXVoiceFormat::LPCM16) {\n      extras->data.endOffsetAbs = virt_addr { (baseAddress >> 1) + offset };\n   } else if (extras->data.format == AXVoiceFormat::LPCM8) {\n      extras->data.endOffsetAbs = baseAddress + offset;\n   } else {\n      decaf_abort(fmt::format(\"Unexpected voice data type {}\", extras->data.format));\n   }\n\n   extras->syncBits |= internal::AXVoiceSyncBits::EndOffset;\n}\n\nvoid\nAXSetVoiceEndOffsetEx(virt_ptr<AXVoice> voice,\n                      uint32_t offset,\n                      virt_ptr<const void> samples)\n{\n   auto offsets = StackObject<AXVoiceOffsets> { };\n   voice->offsets.data = samples;\n\n   AXGetVoiceOffsets(voice, offsets);\n   AXSetVoiceEndOffset(voice, offset);\n}\n\nAXResult\nAXSetVoiceInitialTimeDelay(virt_ptr<AXVoice> voice,\n                           uint16_t delay)\n{\n   if (AXIsVoiceRunning(voice)) {\n      return AXResult::VoiceIsRunning;\n   }\n\n   if (delay > AXGetInputSamplesPerFrame()) {\n      return AXResult::DelayTooBig;\n   }\n\n   auto extras = internal::getVoiceExtras(voice->index);\n   extras->itdOn = uint16_t { 1 };\n   extras->itdDelay = delay;\n   extras->syncBits |= internal::AXVoiceSyncBits::Itd;\n   voice->syncBits |= internal::AXVoiceSyncBits::Itd;\n\n   return AXResult::Success;\n}\n\nvoid\nAXSetVoiceLoopOffset(virt_ptr<AXVoice> voice,\n                     uint32_t offset)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   auto baseAddress = virt_cast<virt_addr>(voice->offsets.data) & 0x1FFFFFFF;\n   voice->offsets.loopOffset = offset;\n\n   if (extras->data.format == AXVoiceFormat::ADPCM) {\n      extras->data.loopOffsetAbs = virt_addr { (baseAddress << 1) + offset };\n   } else if (extras->data.format == AXVoiceFormat::LPCM16) {\n      extras->data.loopOffsetAbs = virt_addr { (baseAddress >> 1) + offset };\n   } else if (extras->data.format == AXVoiceFormat::LPCM8) {\n      extras->data.loopOffsetAbs = baseAddress + offset;\n   } else {\n      decaf_abort(fmt::format(\"Unexpected voice data type {}\", extras->data.format));\n   }\n\n   extras->syncBits |= internal::AXVoiceSyncBits::LoopOffset;\n}\n\nvoid\nAXSetVoiceLoopOffsetEx(virt_ptr<AXVoice> voice,\n                       uint32_t offset,\n                       virt_ptr<const void> samples)\n{\n   voice->offsets.data = samples;\n\n   auto offsets = StackObject<AXVoiceOffsets> { };\n   AXGetVoiceOffsets(voice, offsets);\n   AXSetVoiceLoopOffset(voice, offset);\n}\n\nvoid\nAXSetVoiceLoop(virt_ptr<AXVoice> voice,\n               AXVoiceLoop loop)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n\n   voice->offsets.loopingEnabled = loop;\n\n   extras->data.loopFlag = loop;\n   extras->syncBits |= internal::AXVoiceSyncBits::Loop;\n}\n\nuint32_t\nAXSetVoiceMixerSelect(virt_ptr<AXVoice> voice,\n                      uint32_t mixerSelect)\n{\n   auto oldMiderSelect = voice->renderer;\n   decaf_warn_stub();\n   voice->renderer = static_cast<AXRenderer>(mixerSelect);\n   return oldMiderSelect;\n}\n\nvoid\nAXSetVoiceOffsets(virt_ptr<AXVoice> voice,\n                  virt_ptr<AXVoiceOffsets> offsets)\n{\n   decaf_check(offsets->data);\n\n   voice->offsets = *offsets;\n\n   internal::AXCafeVoiceData absOffsets;\n   absOffsets.format = offsets->dataType;\n   absOffsets.loopFlag = offsets->loopingEnabled;\n   auto samples = virt_cast<virt_addr>(offsets->data);\n\n   if (offsets->dataType == AXVoiceFormat::ADPCM) {\n      absOffsets.loopOffsetAbs = ((samples << 1) + offsets->loopOffset) & 0x3fffffff;\n      absOffsets.endOffsetAbs = ((samples << 1) + offsets->endOffset) & 0x3fffffff;\n      absOffsets.currentOffsetAbs = ((samples << 1) + offsets->currentOffset) & 0x3fffffff;\n      absOffsets.memPageNumber = static_cast<uint16_t>((samples + (offsets->currentOffset >> 1)) >> 29);\n   } else if (offsets->dataType == AXVoiceFormat::LPCM16) {\n      absOffsets.loopOffsetAbs = ((samples >> 1) + offsets->loopOffset) & 0x0fffffff;\n      absOffsets.endOffsetAbs = ((samples >> 1) + offsets->endOffset) & 0x0fffffff;\n      absOffsets.currentOffsetAbs = ((samples >> 1) + offsets->currentOffset) & 0x0fffffff;\n      absOffsets.memPageNumber = static_cast<uint16_t>((samples + (offsets->currentOffset << 1)) >> 29);\n   } else if (offsets->dataType == AXVoiceFormat::LPCM8) {\n      absOffsets.loopOffsetAbs = (samples + offsets->loopOffset) & 0x1fffffff;\n      absOffsets.endOffsetAbs = (samples + offsets->endOffset) & 0x1fffffff;\n      absOffsets.currentOffsetAbs = (samples + offsets->currentOffset) & 0x1fffffff;\n      absOffsets.memPageNumber = static_cast<uint16_t>((samples + offsets->currentOffset) >> 29);\n   } else {\n      decaf_abort(fmt::format(\"Unexpected voice data type {}\", offsets->dataType));\n   }\n\n   internal::setVoiceAddresses(voice, absOffsets);\n}\n\nvoid\nAXSetVoiceOffsetsEx(virt_ptr<AXVoice> voice,\n                    virt_ptr<AXVoiceOffsets> offsets,\n                    virt_ptr<void> samples)\n{\n   auto adjOffsets = StackObject<AXVoiceOffsets> { };\n   *adjOffsets = *offsets;\n   adjOffsets->data = samples;\n   AXSetVoiceOffsets(voice, adjOffsets);\n}\n\nvoid\nAXSetVoicePriority(virt_ptr<AXVoice> voice,\n                   uint32_t priority)\n{\n   voice->priority = priority;\n}\n\nvoid\nAXSetVoiceRmtOn(virt_ptr<AXVoice> voice,\n                uint16_t on)\n{\n   decaf_warn_stub();\n}\n\nvoid\nAXSetVoiceRmtIIRCoefs(virt_ptr<AXVoice> voice,\n                      uint16_t filter,\n                      var_args)\n{\n   decaf_warn_stub();\n}\n\nvoid\nAXSetVoiceSrc(virt_ptr<AXVoice> voice,\n              virt_ptr<AXVoiceSrc> src)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   extras->src = *src;\n   voice->syncBits |= internal::AXVoiceSyncBits::Src;\n}\n\nAXVoiceSrcRatioResult\nAXSetVoiceSrcRatio(virt_ptr<AXVoice> voice,\n                   float ratio)\n{\n   if (ratio < 0.0f) {\n      return AXVoiceSrcRatioResult::RatioLessThanZero;\n   }\n\n   auto extras = internal::getVoiceExtras(voice->index);\n   extras->src.ratio = ufixed_16_16_t { ratio };\n   voice->syncBits |= internal::AXVoiceSyncBits::SrcRatio;\n\n   return AXVoiceSrcRatioResult::Success;\n}\n\nvoid\nAXSetVoiceSrcType(virt_ptr<AXVoice> voice,\n                  AXVoiceSrcType type)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n\n   if (type == AXVoiceSrcType::None) {\n      extras->srcMode = uint16_t { 2 };\n   } else if (type == AXVoiceSrcType::Linear) {\n      extras->srcMode = uint16_t { 1 };\n   } else if (type == AXVoiceSrcType::Unk0) {\n      extras->srcMode = uint16_t { 0 };\n      extras->srcModeUnk = uint16_t { 0 };\n   } else if (type == AXVoiceSrcType::Unk1) {\n      extras->srcMode = uint16_t { 0 };\n      extras->srcModeUnk = uint16_t { 1 };\n   } else if (type == AXVoiceSrcType::Unk2) {\n      extras->srcMode = uint16_t { 0 };\n      extras->srcModeUnk = uint16_t { 2 };\n   }\n\n   voice->syncBits |= internal::AXVoiceSyncBits::SrcType;\n}\n\nvoid\nAXSetVoiceState(virt_ptr<AXVoice> voice,\n                AXVoiceState state)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   if (voice->state != state) {\n      extras->state = static_cast<uint16_t>(state);\n      voice->state = state;\n      voice->syncBits |= internal::AXVoiceSyncBits::State;\n   }\n}\n\nvoid\nAXSetVoiceType(virt_ptr<AXVoice> voice,\n               AXVoiceType type)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   extras->type = type;\n   voice->syncBits |= internal::AXVoiceSyncBits::Type;\n}\n\nvoid\nAXSetVoiceVe(virt_ptr<AXVoice> voice,\n             virt_ptr<AXVoiceVeData> veData)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   extras->ve = *veData;\n   voice->syncBits |= internal::AXVoiceSyncBits::Ve;\n}\n\nvoid\nAXSetVoiceVeDelta(virt_ptr<AXVoice> voice,\n                  sfixed_1_0_15_t delta)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   if (extras->ve.delta.value() != delta) {\n      extras->ve.delta = delta;\n      voice->syncBits |= internal::AXVoiceSyncBits::VeDelta;\n   }\n}\n\nint32_t\nAXVoiceBegin(virt_ptr<AXVoice> voice)\n{\n   decaf_warn_stub();\n\n   // TODO: Implement this properly\n   return AXUserBegin();\n}\n\nint32_t\nAXVoiceEnd(virt_ptr<AXVoice> voice)\n{\n   decaf_warn_stub();\n\n   // TODO: Implement this properly\n   return AXUserEnd();\n}\n\nBOOL\nAXVoiceIsProtected(virt_ptr<AXVoice> voice)\n{\n   decaf_warn_stub();\n\n   return FALSE;\n}\n\nnamespace internal\n{\n\nvoid\ninitVoices()\n{\n   auto index = 0u;\n   for (auto &voice : sVoiceData->voices) {\n      voice.index = index++;\n      sAvailVoiceStack.push(virt_addrof(voice));\n   }\n}\n\nvoid\nsetVoiceAddresses(virt_ptr<AXVoice> voice,\n                  AXCafeVoiceData &offsets)\n{\n   auto extras = internal::getVoiceExtras(voice->index);\n   extras->data = offsets;\n\n   if (offsets.format == AXVoiceFormat::ADPCM) {\n      // Ensure offset not on ADPCM header\n      decaf_check((offsets.loopOffsetAbs & 0xF) >= virt_addr { 2 });\n      decaf_check((offsets.endOffsetAbs & 0xF) >= virt_addr { 2 });\n      decaf_check((offsets.currentOffsetAbs & 0xF) >= virt_addr { 2 });\n   } else if (offsets.format == AXVoiceFormat::LPCM8) {\n      std::memset(&extras->adpcm, 0, sizeof(AXVoiceAdpcm));\n      extras->adpcm.gain = uint16_t { 0x100 };\n      voice->syncBits |= internal::AXVoiceSyncBits::Adpcm;\n   } else if (offsets.format == AXVoiceFormat::LPCM16) {\n      std::memset(&extras->adpcm, 0, sizeof(AXVoiceAdpcm));\n      extras->adpcm.gain = uint16_t { 0x800 };\n      voice->syncBits |= internal::AXVoiceSyncBits::Adpcm;\n   } else {\n      decaf_abort(fmt::format(\"Unexpected audio data format {}\", offsets.format));\n   }\n\n   voice->syncBits &= ~(\n      internal::AXVoiceSyncBits::Loop |\n      internal::AXVoiceSyncBits::LoopOffset |\n      internal::AXVoiceSyncBits::EndOffset |\n      internal::AXVoiceSyncBits::CurrentOffset);\n   voice->syncBits |= internal::AXVoiceSyncBits::Addr;\n}\n\nconst std::vector<virt_ptr<AXVoice>>\ngetAcquiredVoices()\n{\n   return sAcquiredVoices;\n}\n\nvirt_ptr<AXVoiceExtras>\ngetVoiceExtras(int index)\n{\n   decaf_check(index >= 0 && index < AXMaxNumVoices);\n   return virt_addrof(sVoiceData->voiceExtras[index]);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerVoiceSymbols()\n{\n   RegisterFunctionExport(AXAcquireVoice);\n   RegisterFunctionExport(AXAcquireVoiceEx);\n   RegisterFunctionExport(AXCheckVoiceOffsets);\n   RegisterFunctionExport(AXFreeVoice);\n   RegisterFunctionExport(AXGetMaxVoices);\n   RegisterFunctionExport(AXGetVoiceCurrentOffsetEx);\n   RegisterFunctionExport(AXGetVoiceLoopCount);\n   RegisterFunctionExport(AXGetVoiceMixerSelect);\n   RegisterFunctionExport(AXGetVoiceOffsets);\n   RegisterFunctionExport(AXGetVoiceOffsetsEx);\n   RegisterFunctionExport(AXIsVoiceRunning);\n   RegisterFunctionExport(AXSetVoiceAdpcm);\n   RegisterFunctionExport(AXSetVoiceAdpcmLoop);\n   RegisterFunctionExport(AXSetVoiceCurrentOffset);\n   RegisterFunctionExport(AXSetVoiceDeviceMix);\n   RegisterFunctionExport(AXSetVoiceEndOffset);\n   RegisterFunctionExport(AXSetVoiceEndOffsetEx);\n   RegisterFunctionExport(AXSetVoiceInitialTimeDelay);\n   RegisterFunctionExport(AXSetVoiceLoopOffset);\n   RegisterFunctionExport(AXSetVoiceLoopOffsetEx);\n   RegisterFunctionExport(AXSetVoiceLoop);\n   RegisterFunctionExport(AXSetVoiceMixerSelect);\n   RegisterFunctionExport(AXSetVoiceOffsets);\n   RegisterFunctionExport(AXSetVoiceOffsetsEx);\n   RegisterFunctionExport(AXSetVoicePriority);\n   RegisterFunctionExport(AXSetVoiceRmtOn);\n   RegisterFunctionExport(AXSetVoiceRmtIIRCoefs);\n   RegisterFunctionExport(AXSetVoiceSrc);\n   RegisterFunctionExport(AXSetVoiceSrcType);\n   RegisterFunctionExport(AXSetVoiceSrcRatio);\n   RegisterFunctionExport(AXSetVoiceState);\n   RegisterFunctionExport(AXSetVoiceType);\n   RegisterFunctionExport(AXSetVoiceVe);\n   RegisterFunctionExport(AXSetVoiceVeDelta);\n   RegisterFunctionExport(AXVoiceBegin);\n   RegisterFunctionExport(AXVoiceEnd);\n   RegisterFunctionExport(AXVoiceIsProtected);\n\n   RegisterDataInternal(sVoiceData);\n}\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_voice.h",
    "content": "#pragma once\n#include \"sndcore2_constants.h\"\n#include \"sndcore2_device.h\"\n#include \"sndcore2_enum.h\"\n#include \"cafe/cafe_ppc_interface.h\"\n\n#include <common/fixed.h>\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <vector>\n\nnamespace cafe::sndcore2\n{\n\nusing Pcm16Sample = sfixed_1_0_15_t;\n\nusing AXVoiceCallbackFn = virt_func_ptr<\n   virt_ptr<void>()\n>;\nusing AXVoiceCallbackExFn = virt_func_ptr<\n   virt_ptr<void>(uint32_t, uint32_t)\n>;\n\n#pragma pack(push, 1)\n\nstruct AXVoice;\n\nstruct AXVoiceLink\n{\n   be2_virt_ptr<AXVoice> next;\n   be2_virt_ptr<AXVoice> prev;\n};\nCHECK_OFFSET(AXVoiceLink, 0x0, next);\nCHECK_OFFSET(AXVoiceLink, 0x4, prev);\nCHECK_SIZE(AXVoiceLink, 0x8);\n\nstruct AXVoiceOffsets\n{\n   be2_val<AXVoiceFormat> dataType;\n   be2_val<AXVoiceLoop> loopingEnabled;\n   be2_val<uint32_t> loopOffset;\n   be2_val<uint32_t> endOffset;\n   be2_val<uint32_t> currentOffset;\n   be2_virt_ptr<const void> data;\n};\nCHECK_OFFSET(AXVoiceOffsets, 0x0, dataType);\nCHECK_OFFSET(AXVoiceOffsets, 0x2, loopingEnabled);\nCHECK_OFFSET(AXVoiceOffsets, 0x4, loopOffset);\nCHECK_OFFSET(AXVoiceOffsets, 0x8, endOffset);\nCHECK_OFFSET(AXVoiceOffsets, 0xc, currentOffset);\nCHECK_OFFSET(AXVoiceOffsets, 0x10, data);\nCHECK_SIZE(AXVoiceOffsets, 0x14);\n\nstruct AXVoice\n{\n   //! The index of this voice out of the total voices\n   be2_val<uint32_t> index;\n\n   //! Current play state of this voice\n   be2_val<AXVoiceState> state;\n\n   //! Current volume of this voice\n   be2_val<uint32_t> volume;\n\n   //! The renderer to use for this voice\n   be2_val<AXRenderer> renderer;\n\n   //! this is a link used in the stack, we do this in host-memory currently\n   be2_struct<AXVoiceLink> link;\n\n   //! A link to the next callback to invoke\n   be2_virt_ptr<AXVoice> cbNext;\n\n   //! The priority of this voice used for force-acquiring a voice\n   be2_val<uint32_t> priority;\n\n   //! The callback to call if this is force-free'd by another acquire\n   be2_val<AXVoiceCallbackFn> callback;\n\n   //! The user context to send to the callbacks\n   be2_virt_ptr<void> userContext;\n\n   //! A bitfield representing different things needing to be synced.\n   be2_val<uint32_t> syncBits;\n\n   UNKNOWN(0x8);\n\n   //! The current offset data!\n   be2_struct<AXVoiceOffsets> offsets;\n\n   //! An extended version of the callback above\n   be2_val<AXVoiceCallbackExFn> callbackEx;\n\n   //! The reason for the callback being invoked\n   be2_val<uint32_t> callbackReason;\n\n   be2_val<float> unk0;\n   be2_val<float> unk1;\n};\nCHECK_OFFSET(AXVoice, 0x0, index);\nCHECK_OFFSET(AXVoice, 0x4, state);\nCHECK_OFFSET(AXVoice, 0x8, volume);\nCHECK_OFFSET(AXVoice, 0xc, renderer);\nCHECK_OFFSET(AXVoice, 0x10, link);\nCHECK_OFFSET(AXVoice, 0x18, cbNext);\nCHECK_OFFSET(AXVoice, 0x1c, priority);\nCHECK_OFFSET(AXVoice, 0x20, callback);\nCHECK_OFFSET(AXVoice, 0x24, userContext);\nCHECK_OFFSET(AXVoice, 0x28, syncBits);\nCHECK_OFFSET(AXVoice, 0x34, offsets);\nCHECK_OFFSET(AXVoice, 0x48, callbackEx);\nCHECK_OFFSET(AXVoice, 0x4c, callbackReason);\nCHECK_OFFSET(AXVoice, 0x50, unk0);\nCHECK_OFFSET(AXVoice, 0x54, unk1);\nCHECK_SIZE(AXVoice, 0x58);\n\nstruct AXVoiceDeviceBusMixData\n{\n   be2_val<uint16_t> volume;\n   be2_val<int16_t> delta;\n};\nCHECK_OFFSET(AXVoiceDeviceBusMixData, 0x0, volume);\nCHECK_OFFSET(AXVoiceDeviceBusMixData, 0x2, delta);\nCHECK_SIZE(AXVoiceDeviceBusMixData, 0x4);\n\nstruct AXVoiceDeviceMixData\n{\n   be2_array<AXVoiceDeviceBusMixData, 4> bus;\n};\nCHECK_OFFSET(AXVoiceDeviceMixData, 0x0, bus);\nCHECK_SIZE(AXVoiceDeviceMixData, 0x10);\n\nstruct AXVoiceVeData\n{\n   be2_val<ufixed_0_16_t> volume;\n   be2_val<sfixed_1_0_15_t> delta;\n};\nCHECK_OFFSET(AXVoiceVeData, 0x0, volume);\nCHECK_OFFSET(AXVoiceVeData, 0x2, delta);\nCHECK_SIZE(AXVoiceVeData, 0x4);\n\nstruct AXVoiceAdpcmLoopData\n{\n   be2_val<uint16_t> predScale;\n   be2_array<int16_t, 2> prevSample;\n};\nCHECK_OFFSET(AXVoiceAdpcmLoopData, 0x0, predScale);\nCHECK_OFFSET(AXVoiceAdpcmLoopData, 0x2, prevSample);\nCHECK_SIZE(AXVoiceAdpcmLoopData, 0x6);\n\nstruct AXVoiceAdpcm\n{\n   be2_array<int16_t, 16> coefficients;\n   be2_val<uint16_t> gain;\n   be2_val<uint16_t> predScale;\n   be2_array<int16_t, 2> prevSample;\n};\nCHECK_OFFSET(AXVoiceAdpcm, 0x0, coefficients);\nCHECK_OFFSET(AXVoiceAdpcm, 0x20, gain);\nCHECK_OFFSET(AXVoiceAdpcm, 0x22, predScale);\nCHECK_OFFSET(AXVoiceAdpcm, 0x24, prevSample);\nCHECK_SIZE(AXVoiceAdpcm, 0x28);\n\n// Note: \"Src\" = \"sample rate converter\", not \"source\"\nstruct AXVoiceSrc\n{\n   // Playback rate\n   be2_val<ufixed_16_16_t> ratio;\n\n   // Used by the resampler\n   be2_val<ufixed_0_16_t> currentOffsetFrac;\n   be2_array<int16_t, 4> lastSample;\n};\nCHECK_OFFSET(AXVoiceSrc, 0x0, ratio);\nCHECK_OFFSET(AXVoiceSrc, 0x4, currentOffsetFrac);\nCHECK_OFFSET(AXVoiceSrc, 0x6, lastSample);\nCHECK_SIZE(AXVoiceSrc, 0xe);\n\n#pragma pack(pop)\n\nvirt_ptr<AXVoice>\nAXAcquireVoice(uint32_t priority,\n               AXVoiceCallbackFn callback,\n               virt_ptr<void> userContext);\n\nvirt_ptr<AXVoice>\nAXAcquireVoiceEx(uint32_t priority,\n                 AXVoiceCallbackExFn callback,\n                 virt_ptr<void> userContext);\n\nBOOL\nAXCheckVoiceOffsets(virt_ptr<AXVoiceOffsets> offsets);\n\nvoid\nAXFreeVoice(virt_ptr<AXVoice> voice);\n\nuint32_t\nAXGetMaxVoices();\n\nuint32_t\nAXGetVoiceCurrentOffsetEx(virt_ptr<AXVoice> voice,\n                          virt_ptr<const void> samples);\n\nuint32_t\nAXGetVoiceLoopCount(virt_ptr<AXVoice> voice);\n\nuint32_t\nAXGetVoiceMixerSelect(virt_ptr<AXVoice> voice);\n\nvoid\nAXGetVoiceOffsetsEx(virt_ptr<AXVoice> voice,\n                    virt_ptr<AXVoiceOffsets> offsets,\n                    virt_ptr<const void> samples);\n\nvoid\nAXGetVoiceOffsets(virt_ptr<AXVoice> voice,\n                  virt_ptr<AXVoiceOffsets> offsets);\n\nBOOL\nAXIsVoiceRunning(virt_ptr<AXVoice> voice);\n\nvoid\nAXSetVoiceAdpcm(virt_ptr<AXVoice> voice,\n                virt_ptr<AXVoiceAdpcm> adpcm);\n\nvoid\nAXSetVoiceAdpcmLoop(virt_ptr<AXVoice> voice,\n                    virt_ptr<AXVoiceAdpcmLoopData> loopData);\n\nvoid\nAXSetVoiceCurrentOffset(virt_ptr<AXVoice> voice,\n                        uint32_t offset);\n\nvoid\nAXSetVoiceCurrentOffsetEx(virt_ptr<AXVoice> voice,\n                          uint32_t offset,\n                          virt_ptr<const void> samples);\n\nAXResult\nAXSetVoiceDeviceMix(virt_ptr<AXVoice> voice,\n                    AXDeviceType type,\n                    uint32_t id,\n                    virt_ptr<AXVoiceDeviceMixData> mixData);\n\nvoid\nAXSetVoiceEndOffset(virt_ptr<AXVoice> voice,\n                    uint32_t offset);\n\nvoid\nAXSetVoiceEndOffsetEx(virt_ptr<AXVoice> voice,\n                      uint32_t offset,\n                      virt_ptr<const void> samples);\n\nAXResult\nAXSetVoiceInitialTimeDelay(virt_ptr<AXVoice> voice,\n                           uint16_t delay);\n\nvoid\nAXSetVoiceLoopOffset(virt_ptr<AXVoice> voice,\n                     uint32_t offset);\n\nvoid\nAXSetVoiceLoopOffsetEx(virt_ptr<AXVoice> voice,\n                       uint32_t offset,\n                       virt_ptr<const void> samples);\n\nvoid\nAXSetVoiceLoop(virt_ptr<AXVoice> voice,\n               AXVoiceLoop loop);\n\nuint32_t\nAXSetVoiceMixerSelect(virt_ptr<AXVoice> voice,\n                      uint32_t mixerSelect);\n\nvoid\nAXSetVoiceOffsets(virt_ptr<AXVoice> voice,\n                  virt_ptr<AXVoiceOffsets> offsets);\n\nvoid\nAXSetVoiceOffsetsEx(virt_ptr<AXVoice> voice,\n                    virt_ptr<AXVoiceOffsets> offsets,\n                    virt_ptr<void> samples);\n\nvoid\nAXSetVoicePriority(virt_ptr<AXVoice> voice,\n                   uint32_t priority);\n\nvoid\nAXSetVoiceRmtOn(virt_ptr<AXVoice> voice,\n                uint16_t on);\n\nvoid\nAXSetVoiceRmtIIRCoefs(virt_ptr<AXVoice> voice,\n                      uint16_t filter,\n                      var_args);\n\nvoid\nAXSetVoiceSrc(virt_ptr<AXVoice> voice,\n              virt_ptr<AXVoiceSrc> src);\n\nAXVoiceSrcRatioResult\nAXSetVoiceSrcRatio(virt_ptr<AXVoice> voice,\n                   float ratio);\n\nvoid\nAXSetVoiceSrcType(virt_ptr<AXVoice> voice,\n                  AXVoiceSrcType type);\n\nvoid\nAXSetVoiceState(virt_ptr<AXVoice> voice,\n                AXVoiceState state);\n\nvoid\nAXSetVoiceType(virt_ptr<AXVoice> voice,\n               AXVoiceType type);\n\nvoid\nAXSetVoiceVe(virt_ptr<AXVoice> voice,\n             virt_ptr<AXVoiceVeData> veData);\n\nvoid\nAXSetVoiceVeDelta(virt_ptr<AXVoice> voice,\n                  sfixed_1_0_15_t delta);\n\nnamespace internal\n{\n\n#pragma pack(push, 1)\n\nstruct AXCafeVoiceData\n{\n   be2_val<AXVoiceLoop> loopFlag;\n   be2_val<AXVoiceFormat> format;\n   be2_val<uint16_t> memPageNumber;\n   be2_val<virt_addr> loopOffsetAbs;\n   be2_val<virt_addr> endOffsetAbs;\n   be2_val<virt_addr> currentOffsetAbs;\n};\nCHECK_OFFSET(AXCafeVoiceData, 0x0, loopFlag);\nCHECK_OFFSET(AXCafeVoiceData, 0x2, format);\nCHECK_OFFSET(AXCafeVoiceData, 0x4, memPageNumber);\nCHECK_OFFSET(AXCafeVoiceData, 0x6, loopOffsetAbs);\nCHECK_OFFSET(AXCafeVoiceData, 0xa, endOffsetAbs);\nCHECK_OFFSET(AXCafeVoiceData, 0xe, currentOffsetAbs);\nCHECK_SIZE(AXCafeVoiceData, 0x12);\n\nstruct AXCafeVoiceExtras\n{\n   UNKNOWN(0x8);\n\n   uint16_t srcMode;\n   uint16_t srcModeUnk;\n\n   UNKNOWN(0x2);\n\n   AXVoiceType type;\n\n   UNKNOWN(0x15a);\n\n   uint16_t state;\n\n   uint16_t itdOn;\n\n   UNKNOWN(0x2);\n\n   uint16_t itdDelay;\n\n   UNKNOWN(0x8);\n\n   AXVoiceVeData ve;\n\n   AXCafeVoiceData data;\n\n   AXVoiceAdpcm adpcm;\n\n   AXVoiceSrc src;\n\n   AXVoiceAdpcmLoopData adpcmLoop;\n\n   UNKNOWN(0xe4);\n\n   uint32_t syncBits;\n\n   UNKNOWN(0xc);\n};\nCHECK_OFFSET(AXCafeVoiceExtras, 0x8, srcMode);\nCHECK_OFFSET(AXCafeVoiceExtras, 0xa, srcModeUnk);\nCHECK_OFFSET(AXCafeVoiceExtras, 0xe, type);\nCHECK_OFFSET(AXCafeVoiceExtras, 0x16a, state);\nCHECK_OFFSET(AXCafeVoiceExtras, 0x16c, itdOn);\nCHECK_OFFSET(AXCafeVoiceExtras, 0x170, itdDelay);\nCHECK_OFFSET(AXCafeVoiceExtras, 0x17a, ve);\nCHECK_OFFSET(AXCafeVoiceExtras, 0x17e, data);\nCHECK_OFFSET(AXCafeVoiceExtras, 0x190, adpcm);\nCHECK_OFFSET(AXCafeVoiceExtras, 0x1b8, src);\nCHECK_OFFSET(AXCafeVoiceExtras, 0x1c6, adpcmLoop);\nCHECK_OFFSET(AXCafeVoiceExtras, 0x2b0, syncBits);\nCHECK_SIZE(AXCafeVoiceExtras, 0x2c0);\n\nstruct AXVoiceExtras : AXCafeVoiceExtras\n{\n   struct MixVolume\n   {\n      ufixed_1_15_t volume;\n      ufixed_1_15_t delta;\n   };\n\n   // Volume on each of 6 surround channels for TV output\n   MixVolume tvVolume[AXNumTvDevices][AXNumTvChannels][AXNumTvBus];\n\n   // Volume on each of 4 channels for DRC output (2 stereo channels + ???)\n   MixVolume drcVolume[AXNumDrcDevices][AXNumDrcChannels][AXNumDrcBus];\n\n   // Volume for each of 4 controller speakers\n   MixVolume rmtVolume[AXNumRmtDevices][AXNumRmtChannels][AXNumRmtBus];\n\n   // Number of loops so far\n   uint32_t loopCount;\n\n   // Used during decoding\n   uint32_t numSamples;\n   Pcm16Sample samples[144];\n\n};\n\n#pragma pack(pop)\n\nvoid\ninitVoices();\n\nvoid\nsetVoiceAddresses(virt_ptr<AXVoice> voice,\n                  AXCafeVoiceData &offsets);\n\nconst std::vector<virt_ptr<AXVoice>>\ngetAcquiredVoices();\n\nvirt_ptr<AXVoiceExtras>\ngetVoiceExtras(int index);\n\n} // namespace internal\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_vs.cpp",
    "content": "#include \"sndcore2.h\"\n#include \"sndcore2_vs.h\"\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::sndcore2\n{\n\nAXResult\nAXGetDRCVSMode(virt_ptr<AXDRCVSMode> outMode)\n{\n   decaf_warn_stub();\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDRCVSMode(AXDRCVSMode mode)\n{\n   decaf_warn_stub();\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDRCVSDownmixBalance(AXDRCOutput output,\n                         float balance)\n{\n   decaf_warn_stub();\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDRCVSLC(AXDRCVSLC lc)\n{\n   decaf_warn_stub();\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDRCVSLimiter(BOOL limit)\n{\n   decaf_warn_stub();\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDRCVSLimiterThreshold(float threshold)\n{\n   decaf_warn_stub();\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDRCVSOutputGain(AXDRCOutput output,\n                     float gain)\n{\n   decaf_warn_stub();\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDRCVSSpeakerPosition(AXDRCOutput output,\n                          AXDRCVSSpeakerPosition pos)\n{\n   decaf_warn_stub();\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDRCVSSurroundDepth(AXDRCOutput output,\n                        float depth)\n{\n   decaf_warn_stub();\n   return AXResult::Success;\n}\n\nAXResult\nAXSetDRCVSSurroundLevelGain(AXDRCVSSurroundLevelGain gain)\n{\n   decaf_warn_stub();\n   return AXResult::Success;\n}\n\nvoid\nLibrary::registerVsSymbols()\n{\n   RegisterFunctionExport(AXGetDRCVSMode);\n   RegisterFunctionExport(AXSetDRCVSMode);\n   RegisterFunctionExport(AXSetDRCVSDownmixBalance);\n   RegisterFunctionExport(AXSetDRCVSLC);\n   RegisterFunctionExport(AXSetDRCVSLimiter);\n   RegisterFunctionExport(AXSetDRCVSLimiterThreshold);\n   RegisterFunctionExport(AXSetDRCVSOutputGain);\n   RegisterFunctionExport(AXSetDRCVSSpeakerPosition);\n   RegisterFunctionExport(AXSetDRCVSSurroundDepth);\n   RegisterFunctionExport(AXSetDRCVSSurroundLevelGain);\n}\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sndcore2/sndcore2_vs.h",
    "content": "#pragma once\n#include \"sndcore2_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::sndcore2\n{\n\nAXResult\nAXGetDRCVSMode(virt_ptr<AXDRCVSMode> outMode);\n\nAXResult\nAXSetDRCVSMode(AXDRCVSMode mode);\n\nAXResult\nAXSetDRCVSDownmixBalance(AXDRCOutput output,\n                         float balance);\n\nAXResult\nAXSetDRCVSLC(AXDRCVSLC lc);\n\nAXResult\nAXSetDRCVSLimiter(BOOL limit);\n\nAXResult\nAXSetDRCVSLimiterThreshold(float threshold);\n\nAXResult\nAXSetDRCVSOutputGain(AXDRCOutput output,\n                     float gain);\n\nAXResult\nAXSetDRCVSSpeakerPosition(AXDRCOutput output,\n                          AXDRCVSSpeakerPosition pos);\n\nAXResult\nAXSetDRCVSSurroundDepth(AXDRCOutput output,\n                        float depth);\n\nAXResult\nAXSetDRCVSSurroundLevelGain(AXDRCVSSurroundLevelGain gain);\n\n} // namespace cafe::sndcore2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_hooks.h\"\n\nnamespace cafe::snduser2\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   internal::initialiseAxfxHooks();\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerAxArtSymbols();\n   registerAxfxChorusSymbols();\n   registerAxfxChorusExpSymbols();\n   registerAxfxDelaySymbols();\n   registerAxfxDelayExpSymbols();\n   registerAxfxHooksSymbols();\n   registerAxfxMultiChReverbSymbols();\n   registerAxfxReverbHiSymbols();\n   registerAxfxReverbHiExpSymbols();\n   registerAxfxReverbStdSymbols();\n   registerAxfxReverbStdExpSymbols();\n   registerMixSymbols();\n   registerSpSymbols();\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::snduser2\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::snduser2, \"snduser2.rpl\")\n   {\n   }\n\n   // For snd_user.rpl to use\n   Library(hle::LibraryId id,\n           const char *name) :\n      hle::Library(id, name)\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerAxArtSymbols();\n   void registerAxfxChorusSymbols();\n   void registerAxfxChorusExpSymbols();\n   void registerAxfxDelaySymbols();\n   void registerAxfxDelayExpSymbols();\n   void registerAxfxHooksSymbols();\n   void registerAxfxMultiChReverbSymbols();\n   void registerAxfxReverbHiSymbols();\n   void registerAxfxReverbHiExpSymbols();\n   void registerAxfxReverbStdSymbols();\n   void registerAxfxReverbStdExpSymbols();\n   void registerMixSymbols();\n   void registerSpSymbols();\n};\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axart.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axart.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::snduser2\n{\n\nvoid\nAXARTServiceSounds()\n{\n   decaf_warn_stub();\n}\n\nvoid\nLibrary::registerAxArtSymbols()\n{\n   RegisterFunctionExport(AXARTServiceSounds);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axart.h",
    "content": "#pragma once\n\nnamespace cafe::snduser2\n{\n\nvoid\nAXARTServiceSounds();\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx.h",
    "content": "#pragma once\n#include \"snduser2_enum.h\"\n\n#include \"cafe/libraries/sndcore2/sndcore2_device.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\nusing AXAuxCallbackData = sndcore2::AXAuxCallbackData;\n\nstruct AXFXBuffers\n{\n   virt_ptr<int32_t> left;\n   virt_ptr<int32_t> right;\n   virt_ptr<int32_t> surround;\n};\nCHECK_OFFSET(AXFXBuffers, 0x00, left);\nCHECK_OFFSET(AXFXBuffers, 0x04, right);\nCHECK_OFFSET(AXFXBuffers, 0x08, surround);\nCHECK_SIZE(AXFXBuffers, 0x0C);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_chorus.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_chorus.h\"\n#include \"snduser2_axfx_chorusexp.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/sndcore2/sndcore2_config.h\"\n\nnamespace cafe::snduser2\n{\n\nusing namespace cafe::sndcore2;\n\n/**\n * Converts the params from the AXFXChorus struct to the AXFXChorusExp params.\n */\nstatic void\ntranslateChorusUserParamsToExp(virt_ptr<AXFXChorus> chorus)\n{\n   // TODO: Implement translateChorusUserParamsToExp\n   decaf_warn_stub();\n}\n\nint32_t\nAXFXChorusGetMemSize(virt_ptr<AXFXChorus> chorus)\n{\n   return AXFXChorusExpGetMemSize(virt_addrof(chorus->chorusExp));\n}\n\nBOOL\nAXFXChorusInit(virt_ptr<AXFXChorus> chorus)\n{\n   translateChorusUserParamsToExp(chorus);\n   return AXFXChorusExpInit(virt_addrof(chorus->chorusExp));\n}\n\nBOOL\nAXFXChorusShutdown(virt_ptr<AXFXChorus> chorus)\n{\n   AXFXChorusExpShutdown(virt_addrof(chorus->chorusExp));\n   return TRUE;\n}\n\nvoid\nAXFXChorusCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXChorus> chorus)\n{\n   AXFXChorusExpCallback(buffers, virt_addrof(chorus->chorusExp));\n}\n\nBOOL\nAXFXChorusSettings(virt_ptr<AXFXChorus> chorus)\n{\n   translateChorusUserParamsToExp(chorus);\n   return AXFXChorusExpSettings(virt_addrof(chorus->chorusExp));\n}\n\nvoid\nLibrary::registerAxfxChorusSymbols()\n{\n   RegisterFunctionExport(AXFXChorusGetMemSize);\n   RegisterFunctionExport(AXFXChorusInit);\n   RegisterFunctionExport(AXFXChorusShutdown);\n   RegisterFunctionExport(AXFXChorusCallback);\n   RegisterFunctionExport(AXFXChorusSettings);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_chorus.h",
    "content": "#pragma once\n#include \"snduser2_axfx.h\"\n#include \"snduser2_axfx_chorusexp.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\nstruct AXFXChorus\n{\n   be2_struct<AXFXChorusExp> chorusExp;\n   UNKNOWN(0xC);\n};\nCHECK_SIZE(AXFXChorus, 0xAC);\n\nint32_t\nAXFXChorusGetMemSize(virt_ptr<AXFXChorus> chorus);\n\nBOOL\nAXFXChorusInit(virt_ptr<AXFXChorus> chorus);\n\nBOOL\nAXFXChorusShutdown(virt_ptr<AXFXChorus> chorus);\n\nvoid\nAXFXChorusCallback(virt_ptr<AXFXBuffers> buffers,\n                   virt_ptr<AXFXChorus> chorus);\n\nBOOL\nAXFXChorusSettings(virt_ptr<AXFXChorus> chorus);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_chorusexp.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_chorusexp.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/sndcore2/sndcore2_config.h\"\n\nnamespace cafe::snduser2\n{\n\nusing namespace cafe::sndcore2;\n\nint32_t\nAXFXChorusExpGetMemSize(virt_ptr<AXFXChorusExp> chorus)\n{\n   return 12 * (AXGetInputSamplesPerSec() / 10);\n}\n\nBOOL\nAXFXChorusExpInit(virt_ptr<AXFXChorusExp> chorus)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nAXFXChorusExpShutdown(virt_ptr<AXFXChorusExp> chorus)\n{\n   decaf_warn_stub();\n}\n\nvoid\nAXFXChorusExpCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXChorusExp> chorus)\n{\n   decaf_warn_stub();\n}\n\nBOOL\nAXFXChorusExpSettings(virt_ptr<AXFXChorusExp> chorus)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nBOOL\nAXFXChorusExpSettingsUpdate(virt_ptr<AXFXChorusExp> chorus)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nLibrary::registerAxfxChorusExpSymbols()\n{\n   RegisterFunctionExport(AXFXChorusExpGetMemSize);\n   RegisterFunctionExport(AXFXChorusExpInit);\n   RegisterFunctionExport(AXFXChorusExpShutdown);\n   RegisterFunctionExport(AXFXChorusExpCallback);\n   RegisterFunctionExport(AXFXChorusExpSettings);\n   RegisterFunctionExport(AXFXChorusExpSettingsUpdate);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_chorusexp.h",
    "content": "#pragma once\n#include \"snduser2_axfx.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\nstruct AXFXChorusExp\n{\n   UNKNOWN(0xA0);\n};\nCHECK_SIZE(AXFXChorusExp, 0xA0);\n\nint32_t\nAXFXChorusExpGetMemSize(virt_ptr<AXFXChorusExp> chorus);\n\nBOOL\nAXFXChorusExpInit(virt_ptr<AXFXChorusExp> chorus);\n\nvoid\nAXFXChorusExpShutdown(virt_ptr<AXFXChorusExp> chorus);\n\nvoid\nAXFXChorusExpCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXChorusExp> chorus);\n\nBOOL\nAXFXChorusExpSettings(virt_ptr<AXFXChorusExp> chorus);\n\nBOOL\nAXFXChorusExpSettingsUpdate(virt_ptr<AXFXChorusExp> chorus);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_delay.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_delay.h\"\n#include \"snduser2_axfx_hooks.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/sndcore2/sndcore2_config.h\"\n\nnamespace cafe::snduser2\n{\n\nusing namespace cafe::sndcore2;\n\nint32_t\nAXFXDelayGetMemSize(virt_ptr<AXFXDelay> delay)\n{\n   auto samplersPerMS = AXGetInputSamplesPerSec() / 1000;\n   auto totalDelayMS =\n      delay->userDelayMS[0] + delay->userDelayMS[1] + delay->userDelayMS[2];\n\n   return sizeof(int32_t) * totalDelayMS * samplersPerMS;\n}\n\nBOOL\nAXFXDelayInit(virt_ptr<AXFXDelay> delay)\n{\n   auto samplersPerMS = AXGetInputSamplesPerSec() / 1000;\n   delay->stateFlags = AXFXDelayStateFlags::Shutdown;\n\n   // Calculate delay buffer size\n   for (auto i = 0u; i < 3; ++i) {\n      if (!delay->userDelayMS[i]) {\n         AXFXDelayShutdown(delay);\n         return FALSE;\n      }\n\n      delay->delayBufferMaxNumSamples[i] =\n         samplersPerMS * delay->userDelayMS[i];\n   }\n\n   // Allocate delay buffers\n   for (auto i = 0u; i < 3; ++i) {\n      delay->delayBuffer[i] =\n         virt_cast<uint32_t *>(\n            internal::axfxAlloc(4 * delay->delayBufferMaxNumSamples[i]));\n\n      if (!delay->delayBuffer[i]) {\n         AXFXDelayShutdown(delay);\n         return FALSE;\n      }\n   }\n\n   // Initialise parameters\n   for (auto i = 0u; i < 3; ++i) {\n      if (delay->userFeedbackGain[i] > 100 ||\n          delay->userOutputGain[i] > 100) {\n         AXFXDelayShutdown(delay);\n         return FALSE;\n      }\n\n      delay->delayBufferSamplePos[i] = 0u;\n      delay->feedbackGain[i] = static_cast<int32_t>(128.0f * (delay->userFeedbackGain[i] / 100.0f));\n      delay->outputGain[i] = static_cast<int32_t>(128.0f * (delay->userOutputGain[i] / 100.0f));\n   }\n\n   return TRUE;\n}\n\nvoid\nAXFXDelayShutdown(virt_ptr<AXFXDelay> delay)\n{\n   delay->stateFlags |= AXFXDelayStateFlags::Shutdown;\n\n   for (auto i = 0u; i < 3; ++i) {\n      internal::axfxFree(delay->delayBuffer[i]);\n      delay->delayBuffer[i] = nullptr;\n   }\n}\n\nvoid\nAXFXDelayCallback(virt_ptr<AXFXBuffers> buffers,\n                  virt_ptr<AXFXDelay> delay)\n{\n   // TODO: Implement AXFXDelayCallback\n   decaf_warn_stub();\n}\n\nvoid\nLibrary::registerAxfxDelaySymbols()\n{\n   RegisterFunctionExport(AXFXDelayGetMemSize);\n   RegisterFunctionExport(AXFXDelayInit);\n   RegisterFunctionExport(AXFXDelayShutdown);\n   RegisterFunctionExport(AXFXDelayCallback);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_delay.h",
    "content": "#pragma once\n#include \"snduser2_axfx.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\n#include \"common/enum_start.inl\"\n\nENUM_BEG(AXFXDelayStateFlags, uint32_t)\n   ENUM_VALUE(Shutdown,      1 << 0)\n   ENUM_VALUE(Initialised,   1 << 1)\nENUM_END(AXFXDelayStateFlags)\n\n#include \"common/enum_end.inl\"\n\nstruct AXFXDelay\n{\n   //! Buffer to store the delayed samples in.\n   be2_array<virt_ptr<uint32_t>, 3> delayBuffer;\n\n   //! Current position of per channel delayBuffer in number of samples.\n   be2_array<uint32_t, 3> delayBufferSamplePos;\n\n   //! Size of the per channel delayBuffer in number of samples.\n   be2_array<uint32_t, 3> delayBufferMaxNumSamples;\n\n   //! Feedback gain.\n   be2_array<int32_t, 3> feedbackGain;\n\n   //! Output gain.\n   be2_array<int32_t, 3> outputGain;\n\n   //! State.\n   be2_val<AXFXDelayStateFlags> stateFlags;\n\n   //! User provided parameter for duration of the delay.\n   be2_array<uint32_t, 3> userDelayMS;\n\n   //! User provided parameter for feedbackGain\n   be2_array<uint32_t, 3> userFeedbackGain;\n\n   //! User provided parameter for outputGain\n   be2_array<uint32_t, 3> userOutputGain;\n};\nCHECK_OFFSET(AXFXDelay, 0x00, delayBuffer);\nCHECK_OFFSET(AXFXDelay, 0x0C, delayBufferSamplePos);\nCHECK_OFFSET(AXFXDelay, 0x18, delayBufferMaxNumSamples);\nCHECK_OFFSET(AXFXDelay, 0x3C, stateFlags);\nCHECK_OFFSET(AXFXDelay, 0x40, userDelayMS);\nCHECK_OFFSET(AXFXDelay, 0x4C, userFeedbackGain);\nCHECK_OFFSET(AXFXDelay, 0x58, userOutputGain);\nCHECK_SIZE(AXFXDelay, 0x64);\n\nint32_t\nAXFXDelayGetMemSize(virt_ptr<AXFXDelay> delay);\n\nBOOL\nAXFXDelayInit(virt_ptr<AXFXDelay> delay);\n\nvoid\nAXFXDelayShutdown(virt_ptr<AXFXDelay> delay);\n\nvoid\nAXFXDelayCallback(virt_ptr<AXFXBuffers> buffers,\n                  virt_ptr<AXFXDelay> delay);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_delayexp.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_delayexp.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/sndcore2/sndcore2_config.h\"\n\nnamespace cafe::snduser2\n{\n\nusing namespace cafe::sndcore2;\n\nint32_t\nAXFXDelayExpGetMemSize(virt_ptr<AXFXDelayExp> delay)\n{\n   auto samplersPerMS = AXGetInputSamplesPerSec() / 1000.0f;\n   auto perChannelSamples = delay->userDelayMS * samplersPerMS;\n   auto numSamples = 3 * perChannelSamples;\n   return static_cast<int32_t>(sizeof(int32_t) * numSamples);\n}\n\nBOOL\nAXFXDelayExpInit(virt_ptr<AXFXDelayExp> delay)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nAXFXDelayExpShutdown(virt_ptr<AXFXDelayExp> delay)\n{\n   decaf_warn_stub();\n}\n\nvoid\nAXFXDelayExpCallback(virt_ptr<AXFXBuffers> buffers,\n                     virt_ptr<AXFXDelayExp> delay)\n{\n   decaf_warn_stub();\n}\n\nBOOL\nAXFXDelayExpSettings(virt_ptr<AXFXDelayExp> delay)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nBOOL\nAXFXDelayExpSettingsUpdate(virt_ptr<AXFXDelayExp> delay)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nLibrary::registerAxfxDelayExpSymbols()\n{\n   RegisterFunctionExport(AXFXDelayExpGetMemSize);\n   RegisterFunctionExport(AXFXDelayExpInit);\n   RegisterFunctionExport(AXFXDelayExpShutdown);\n   RegisterFunctionExport(AXFXDelayExpCallback);\n   RegisterFunctionExport(AXFXDelayExpSettings);\n   RegisterFunctionExport(AXFXDelayExpSettingsUpdate);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_delayexp.h",
    "content": "#pragma once\n#include \"snduser2_axfx.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\n#include \"common/enum_start.inl\"\n\nENUM_BEG(AXFXDelayExpStateFlags, uint32_t)\n   ENUM_VALUE(Shutdown,      1 << 0)\n   ENUM_VALUE(Initialised,   1 << 1)\nENUM_END(AXFXDelayExpStateFlags)\n\n#include \"common/enum_end.inl\"\n\nstruct AXFXDelayExp\n{\n   UNKNOWN(0x34);\n\n   //! State\n   be2_val<AXFXDelayExpStateFlags> stateFlags;\n\n   //! User provided parameter for duration of the delay buffer.\n   be2_val<float> userDelayMS;\n\n   // Unknown size...\n};\nCHECK_OFFSET(AXFXDelayExp, 0x34, stateFlags);\nCHECK_OFFSET(AXFXDelayExp, 0x38, userDelayMS);\nUNKNOWN_SIZE(AXFXDelayExp);\n\nint32_t\nAXFXDelayExpGetMemSize(virt_ptr<AXFXDelayExp> delay);\n\nBOOL\nAXFXDelayExpInit(virt_ptr<AXFXDelayExp> delay);\n\nvoid\nAXFXDelayExpShutdown(virt_ptr<AXFXDelayExp> delay);\n\nvoid\nAXFXDelayExpCallback(virt_ptr<AXFXBuffers> buffers,\n                     virt_ptr<AXFXDelayExp> delay);\n\nBOOL\nAXFXDelayExpSettings(virt_ptr<AXFXDelayExp> delay);\n\nBOOL\nAXFXDelayExpSettingsUpdate(virt_ptr<AXFXDelayExp> delay);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_hooks.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_hooks.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/libraries/coreinit/coreinit_memdefaultheap.h\"\n\n#include <libcpu/state.h>\n\nnamespace cafe::snduser2\n{\n\nstruct StaticAxfxHooksData\n{\n   be2_val<AXFXAllocFn> allocFuncPtr;\n   be2_val<AXFXFreeFn> freeFuncPtr;\n};\n\nstatic virt_ptr<StaticAxfxHooksData>\nsAxfxHooksData = nullptr;\n\nstatic AXFXAllocFn\nsAxfxDefaultAlloc = nullptr;\n\nstatic AXFXFreeFn\nsAxfxDefaultFree = nullptr;\n\nvoid\nAXFXGetHooks(virt_ptr<AXFXAllocFn> allocFn,\n             virt_ptr<AXFXFreeFn> freeFn)\n{\n   *allocFn = sAxfxHooksData->allocFuncPtr;\n   *freeFn = sAxfxHooksData->freeFuncPtr;\n}\n\nvoid\nAXFXSetHooks(AXFXAllocFn allocFn,\n             AXFXFreeFn freeFn)\n{\n   sAxfxHooksData->allocFuncPtr = allocFn;\n   sAxfxHooksData->freeFuncPtr = freeFn;\n}\n\nnamespace internal\n{\n\nstatic virt_ptr<void>\ndefaultAxfxAlloc(uint32_t size)\n{\n   return coreinit::MEMAllocFromDefaultHeap(size);\n}\n\nstatic void\ndefaultAxfxFree(virt_ptr<void> ptr)\n{\n   coreinit::MEMFreeToDefaultHeap(ptr);\n}\n\nvirt_ptr<void>\naxfxAlloc(uint32_t size)\n{\n   return cafe::invoke(cpu::this_core::state(),\n                       sAxfxHooksData->allocFuncPtr,\n                       size);\n}\n\nvoid\naxfxFree(virt_ptr<void> ptr)\n{\n   cafe::invoke(cpu::this_core::state(),\n                sAxfxHooksData->freeFuncPtr,\n                ptr);\n}\n\nvoid\ninitialiseAxfxHooks()\n{\n   sAxfxHooksData->allocFuncPtr = sAxfxDefaultAlloc;\n   sAxfxHooksData->freeFuncPtr = sAxfxDefaultFree;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerAxfxHooksSymbols()\n{\n   RegisterFunctionExport(AXFXGetHooks);\n   RegisterFunctionExport(AXFXSetHooks);\n\n   RegisterDataInternal(sAxfxHooksData);\n   RegisterFunctionInternal(internal::defaultAxfxAlloc, sAxfxDefaultAlloc);\n   RegisterFunctionInternal(internal::defaultAxfxFree, sAxfxDefaultFree);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_hooks.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\nusing AXFXAllocFn = virt_func_ptr<\n   virt_ptr<void> (uint32_t size)\n>;\n\nusing AXFXFreeFn = virt_func_ptr<\n   void (virt_ptr<void> ptr)\n>;\n\nvoid\nAXFXGetHooks(virt_ptr<AXFXAllocFn> allocFn,\n             virt_ptr<AXFXFreeFn> freeFn);\n\nvoid\nAXFXSetHooks(AXFXAllocFn allocFn,\n             AXFXFreeFn freeFn);\n\nnamespace internal\n{\n\nvirt_ptr<void>\naxfxAlloc(uint32_t size);\n\nvoid\naxfxFree(virt_ptr<void> ptr);\n\nvoid\ninitialiseAxfxHooks();\n\n} // namespace internal\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_multichreverb.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_multichreverb.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::snduser2\n{\n\nint32_t\nAXFXMultiChReverbGetMemSize(virt_ptr<AXFXMultiChReverb> reverb)\n{\n   decaf_warn_stub();\n   return 32;\n}\n\nBOOL\nAXFXMultiChReverbInit(virt_ptr<AXFXMultiChReverb> reverb,\n                      AXFXReverbType type,\n                      AXFXSampleRate sampleRate)\n{\n   auto samplesPerSecond = 32000;\n   if (sampleRate == AXFXSampleRate::Rate48khz) {\n      samplesPerSecond = 48000;\n   }\n\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nAXFXMultiChReverbShutdown(virt_ptr<AXFXMultiChReverb> reverb)\n{\n   decaf_warn_stub();\n}\n\nBOOL\nAXFXMultiChReverbParametersPreset(virt_ptr<AXFXMultiChReverb> reverb,\n                                  AXFXReverbPreset preset)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nBOOL\nAXFXMultiChReverbSettingsUpdate(virt_ptr<AXFXMultiChReverb> reverb)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nBOOL\nAXFXMultiChReverbSettingsUpdateNoReset(virt_ptr<AXFXMultiChReverb> reverb)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nAXFXMultiChReverbCallback(virt_ptr<AXFXBuffers> buffers,\n                          virt_ptr<AXFXMultiChReverb> data,\n                          virt_ptr<AXAuxCallbackData> auxData)\n{\n   decaf_warn_stub();\n}\n\nvoid\nLibrary::registerAxfxMultiChReverbSymbols()\n{\n   RegisterFunctionExport(AXFXMultiChReverbGetMemSize);\n   RegisterFunctionExport(AXFXMultiChReverbInit);\n   RegisterFunctionExport(AXFXMultiChReverbShutdown);\n   RegisterFunctionExport(AXFXMultiChReverbParametersPreset);\n   RegisterFunctionExport(AXFXMultiChReverbSettingsUpdate);\n   RegisterFunctionExport(AXFXMultiChReverbSettingsUpdateNoReset);\n   RegisterFunctionExport(AXFXMultiChReverbCallback);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_multichreverb.h",
    "content": "#pragma once\n#include \"snduser2_axfx.h\"\n#include \"snduser2_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\nstruct AXFXMultiChReverb\n{\n   UNKNOWN(0x24);\n   be2_val<AXFXReverbType> type;\n\n   //! Samples per second, set to either 48000 or 32000 in AXFXMultiChReverbInit.\n   be2_val<uint32_t> samplesPerSecond;\n};\nCHECK_OFFSET(AXFXMultiChReverb, 0x24, type);\nCHECK_OFFSET(AXFXMultiChReverb, 0x28, samplesPerSecond);\nUNKNOWN_SIZE(AXFXMultiChReverb);\n\nint32_t\nAXFXMultiChReverbGetMemSize(virt_ptr<AXFXMultiChReverb> reverb);\n\nBOOL\nAXFXMultiChReverbInit(virt_ptr<AXFXMultiChReverb> reverb,\n                      AXFXReverbType type,\n                      AXFXSampleRate sampleRate);\n\nvoid\nAXFXMultiChReverbShutdown(virt_ptr<AXFXMultiChReverb> reverb);\n\nBOOL\nAXFXMultiChReverbParametersPreset(virt_ptr<AXFXMultiChReverb> reverb,\n                                  AXFXReverbPreset preset);\n\nBOOL\nAXFXMultiChReverbSettingsUpdate(virt_ptr<AXFXMultiChReverb> reverb);\n\nBOOL\nAXFXMultiChReverbSettingsUpdateNoReset(virt_ptr<AXFXMultiChReverb> reverb);\n\nvoid\nAXFXMultiChReverbCallback(virt_ptr<AXFXBuffers> buffers,\n                          virt_ptr<AXFXMultiChReverb> data,\n                          virt_ptr<AXAuxCallbackData> auxData);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbhi.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_reverbhi.h\"\n#include \"snduser2_axfx_reverbhiexp.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/sndcore2/sndcore2_config.h\"\n\nnamespace cafe::snduser2\n{\n\nusing namespace cafe::sndcore2;\n\n/**\n * Converts the params from the AXFXReverbHi struct to the AXFXReverbHiExp params.\n */\nstatic void\ntranslateReverbHiUserParamsToExp(virt_ptr<AXFXReverbHi> reverb)\n{\n   // TODO: Implement translateReverbUserParamsToExp\n   decaf_warn_stub();\n}\n\nint32_t\nAXFXReverbHiGetMemSize(virt_ptr<AXFXReverbHi> reverb)\n{\n   reverb->reverbExp.preDelaySeconds = reverb->userPreDelaySeconds;\n   return AXFXReverbHiExpGetMemSize(virt_addrof(reverb->reverbExp));\n}\n\nBOOL\nAXFXReverbHiInit(virt_ptr<AXFXReverbHi> reverb)\n{\n   translateReverbHiUserParamsToExp(reverb);\n   return AXFXReverbHiExpInit(virt_addrof(reverb->reverbExp));\n}\n\nBOOL\nAXFXReverbHiShutdown(virt_ptr<AXFXReverbHi> reverb)\n{\n   AXFXReverbHiExpShutdown(virt_addrof(reverb->reverbExp));\n   return TRUE;\n}\n\nvoid\nAXFXReverbHiCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXReverbHi> reverb)\n{\n   AXFXReverbHiExpCallback(buffers, virt_addrof(reverb->reverbExp));\n}\n\nBOOL\nAXFXReverbHiSettings(virt_ptr<AXFXReverbHi> reverb)\n{\n   translateReverbHiUserParamsToExp(reverb);\n   return AXFXReverbHiExpSettings(virt_addrof(reverb->reverbExp));\n}\n\nvoid\nLibrary::registerAxfxReverbHiSymbols()\n{\n   RegisterFunctionExport(AXFXReverbHiGetMemSize);\n   RegisterFunctionExport(AXFXReverbHiInit);\n   RegisterFunctionExport(AXFXReverbHiShutdown);\n   RegisterFunctionExport(AXFXReverbHiCallback);\n   RegisterFunctionExport(AXFXReverbHiSettings);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbhi.h",
    "content": "#pragma once\n#include \"snduser2_axfx.h\"\n#include \"snduser2_axfx_reverbhiexp.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\nstruct AXFXReverbHi\n{\n   be2_struct<AXFXReverbHiExp> reverbExp;\n   UNKNOWN(0x10);\n\n   //! Reverb Pre-Delay in seconds\n   be2_val<float> userPreDelaySeconds;\n\n   UNKNOWN(0x4);\n};\nCHECK_OFFSET(AXFXReverbHi, 0x158, userPreDelaySeconds);\nCHECK_SIZE(AXFXReverbHi, 0x160);\n\nint32_t\nAXFXReverbHiGetMemSize(virt_ptr<AXFXReverbHi> reverb);\n\nBOOL\nAXFXReverbHiInit(virt_ptr<AXFXReverbHi> reverb);\n\nBOOL\nAXFXReverbHiShutdown(virt_ptr<AXFXReverbHi> reverb);\n\nvoid\nAXFXReverbHiCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXReverbHi> reverb);\n\nBOOL\nAXFXReverbHiSettings(virt_ptr<AXFXReverbHi> reverb);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbhiexp.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_reverbhiexp.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/sndcore2/sndcore2_config.h\"\n\nnamespace cafe::snduser2\n{\n\nusing namespace cafe::sndcore2;\n\nstatic constexpr int32_t\nReverbHiMemSizeSamplesBase = 0xF31;\n\nstatic constexpr int32_t\nReverbHiMemSizeSamplesTable[] = {\n   0x6FD,\n   0x7CF,\n   0x91D,\n   0x1B1,\n   0x95,\n   0x2F,\n   0x49,\n   0x43,\n   0x95,\n   0x125,\n   0x1C1,\n   0xFB,\n   0x67,\n   0x2F,\n   0x49,\n   0x43,\n   0x3B3,\n   0x551,\n   0x5FB,\n   0x1B1,\n   0x89,\n   0x2F,\n   0x49,\n   0x43,\n   0x4FF,\n   0x5FB,\n   0x7B5,\n   0x1FD,\n   0x95,\n   0x2F,\n   0x49,\n   0x43,\n   0x5FB,\n   0x737,\n   0x8F9,\n   0x233,\n   0xB3,\n   0x2F,\n   0x49,\n   0x43,\n   0x71F,\n   0x935,\n   0xA85,\n   0x23B,\n   0x89,\n   0x2F,\n   0x49,\n   0x43,\n   0x71F,\n   0x935,\n   0xA85,\n   0x23B,\n   0xB3,\n   0x2F,\n   0x49,\n   0x43,\n};\n\nint32_t\nAXFXReverbHiExpGetMemSize(virt_ptr<AXFXReverbHiExp> reverb)\n{\n   auto delaySamples =\n      reverb->preDelaySeconds * static_cast<float>(AXGetInputSamplesPerSec());\n\n   auto perChanSamples =\n      delaySamples +\n      ReverbHiMemSizeSamplesBase +\n      ReverbHiMemSizeSamplesTable[48] + ReverbHiMemSizeSamplesTable[49] + ReverbHiMemSizeSamplesTable[50] +\n      ReverbHiMemSizeSamplesTable[51] + ReverbHiMemSizeSamplesTable[52];\n\n   auto numSamples =\n      perChanSamples * 3 +\n      ReverbHiMemSizeSamplesTable[53] + ReverbHiMemSizeSamplesTable[54] + ReverbHiMemSizeSamplesTable[55];\n\n   return static_cast<int32_t>(sizeof(int32_t) * numSamples);\n}\n\nBOOL\nAXFXReverbHiExpInit(virt_ptr<AXFXReverbHiExp> reverb)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nAXFXReverbHiExpShutdown(virt_ptr<AXFXReverbHiExp> reverb)\n{\n   decaf_warn_stub();\n}\n\nvoid\nAXFXReverbHiExpCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXReverbHiExp> reverb)\n{\n   decaf_warn_stub();\n}\n\nBOOL\nAXFXReverbHiExpSettings(virt_ptr<AXFXReverbHiExp> reverb)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nBOOL\nAXFXReverbHiExpSettingsUpdate(virt_ptr<AXFXReverbHiExp> reverb)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nLibrary::registerAxfxReverbHiExpSymbols()\n{\n   RegisterFunctionExport(AXFXReverbHiExpGetMemSize);\n   RegisterFunctionExport(AXFXReverbHiExpInit);\n   RegisterFunctionExport(AXFXReverbHiExpShutdown);\n   RegisterFunctionExport(AXFXReverbHiExpCallback);\n   RegisterFunctionExport(AXFXReverbHiExpSettings);\n   RegisterFunctionExport(AXFXReverbHiExpSettingsUpdate);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbhiexp.h",
    "content": "#pragma once\n#include \"snduser2_axfx.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\nstruct AXFXReverbHiExp\n{\n   UNKNOWN(0x114);\n\n   //! Reverb Pre-Delay in seconds\n   be2_val<float> preDelaySeconds;\n\n   UNKNOWN(0x30);\n};\nCHECK_OFFSET(AXFXReverbHiExp, 0x114, preDelaySeconds);\nCHECK_SIZE(AXFXReverbHiExp, 0x148);\n\nint32_t\nAXFXReverbHiExpGetMemSize(virt_ptr<AXFXReverbHiExp> reverb);\n\nBOOL\nAXFXReverbHiExpInit(virt_ptr<AXFXReverbHiExp> reverb);\n\nvoid\nAXFXReverbHiExpShutdown(virt_ptr<AXFXReverbHiExp> reverb);\n\nvoid\nAXFXReverbHiExpCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXReverbHiExp> reverb);\n\nBOOL\nAXFXReverbHiExpSettings(virt_ptr<AXFXReverbHiExp> reverb);\n\nBOOL\nAXFXReverbHiExpSettingsUpdate(virt_ptr<AXFXReverbHiExp> reverb);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbstd.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_reverbstd.h\"\n#include \"snduser2_axfx_reverbstdexp.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/sndcore2/sndcore2_config.h\"\n\nnamespace cafe::snduser2\n{\n\nusing namespace cafe::sndcore2;\n\n/**\n * Converts the params from the AXFXReverbStd struct to the AXFXReverbStdExp params.\n */\nstatic void\ntranslateReverbStdUserParamsToExp(virt_ptr<AXFXReverbStd> reverb)\n{\n   // TODO: Implement translateReverbUserParamsToExp\n   decaf_warn_stub();\n}\n\nint32_t\nAXFXReverbStdGetMemSize(virt_ptr<AXFXReverbStd> reverb)\n{\n   reverb->reverbExp.preDelaySeconds = reverb->userPreDelaySeconds;\n   return AXFXReverbStdExpGetMemSize(virt_addrof(reverb->reverbExp));\n}\n\nBOOL\nAXFXReverbStdInit(virt_ptr<AXFXReverbStd> reverb)\n{\n   translateReverbStdUserParamsToExp(reverb);\n   return AXFXReverbStdExpInit(virt_addrof(reverb->reverbExp));\n}\n\nBOOL\nAXFXReverbStdShutdown(virt_ptr<AXFXReverbStd> reverb)\n{\n   AXFXReverbStdExpShutdown(virt_addrof(reverb->reverbExp));\n   return TRUE;\n}\n\nvoid\nAXFXReverbStdCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXReverbStd> reverb)\n{\n   AXFXReverbStdExpCallback(buffers, virt_addrof(reverb->reverbExp));\n}\n\nBOOL\nAXFXReverbStdSettings(virt_ptr<AXFXReverbStd> reverb)\n{\n   translateReverbStdUserParamsToExp(reverb);\n   return AXFXReverbStdExpSettings(virt_addrof(reverb->reverbExp));\n}\n\nvoid\nLibrary::registerAxfxReverbStdSymbols()\n{\n   RegisterFunctionExport(AXFXReverbStdGetMemSize);\n   RegisterFunctionExport(AXFXReverbStdInit);\n   RegisterFunctionExport(AXFXReverbStdShutdown);\n   RegisterFunctionExport(AXFXReverbStdCallback);\n   RegisterFunctionExport(AXFXReverbStdSettings);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbstd.h",
    "content": "#pragma once\n#include \"snduser2_axfx.h\"\n#include \"snduser2_axfx_reverbstdexp.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\nstruct AXFXReverbStd\n{\n   be2_struct<AXFXReverbStdExp> reverbExp;\n   UNKNOWN(0x10);\n\n   //! Reverb Pre-Delay in seconds\n   be2_val<float> userPreDelaySeconds;\n};\nCHECK_SIZE(AXFXReverbStd, 0xFC);\n\nint32_t\nAXFXReverbStdGetMemSize(virt_ptr<AXFXReverbStd> reverb);\n\nBOOL\nAXFXReverbStdInit(virt_ptr<AXFXReverbStd> reverb);\n\nBOOL\nAXFXReverbStdShutdown(virt_ptr<AXFXReverbStd> reverb);\n\nvoid\nAXFXReverbStdCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXReverbStd> reverb);\n\nBOOL\nAXFXReverbStdSettings(virt_ptr<AXFXReverbStd> reverb);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbstdexp.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_axfx_reverbstdexp.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n#include \"cafe/libraries/sndcore2/sndcore2_config.h\"\n\nnamespace cafe::snduser2\n{\n\nusing namespace cafe::sndcore2;\n\nstatic constexpr int32_t\nReverbStdMemSizeSamplesBase = 0x503;\n\nstatic constexpr int32_t\nReverbStdMemSizeSamplesTable[] = {\n   0x6FD,\n   0x7CF,\n   0x1B1,\n   0x95,\n   0x95,\n   0x125,\n   0xFB,\n   0x67,\n   0x3B3,\n   0x551,\n   0x1B1,\n   0x89,\n   0x4FF,\n   0x5FB,\n   0x1FD,\n   0x95,\n   0x5FB,\n   0x737,\n   0x233,\n   0xB3,\n   0x71F,\n   0x935,\n   0x23B,\n   0x89,\n   0x71F,\n   0x935,\n   0x23B,\n   0xB3,\n   0xA3,\n   0x13D,\n   0x1DF,\n   0x281,\n   0x31D,\n   0x3C7,\n   0x463,\n};\n\nint32_t\nAXFXReverbStdExpGetMemSize(virt_ptr<AXFXReverbStdExp> reverb)\n{\n   auto delaySamples =\n      reverb->preDelaySeconds * static_cast<float>(AXGetInputSamplesPerSec());\n\n   auto perChanSamples =\n      delaySamples +\n      ReverbStdMemSizeSamplesBase +\n      ReverbStdMemSizeSamplesTable[24] + ReverbStdMemSizeSamplesTable[25] +\n      ReverbStdMemSizeSamplesTable[26] + ReverbStdMemSizeSamplesTable[27];\n\n   auto numSamples =\n      3 * perChanSamples;\n\n   return static_cast<int32_t>(sizeof(int32_t) * numSamples);\n}\n\nBOOL\nAXFXReverbStdExpInit(virt_ptr<AXFXReverbStdExp> reverb)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nAXFXReverbStdExpShutdown(virt_ptr<AXFXReverbStdExp> reverb)\n{\n   decaf_warn_stub();\n}\n\nvoid\nAXFXReverbStdExpCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXReverbStdExp> reverb)\n{\n   decaf_warn_stub();\n}\n\nBOOL\nAXFXReverbStdExpSettings(virt_ptr<AXFXReverbStdExp> reverb)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nBOOL\nAXFXReverbStdExpSettingsUpdate(virt_ptr<AXFXReverbStdExp> reverb)\n{\n   decaf_warn_stub();\n   return TRUE;\n}\n\nvoid\nLibrary::registerAxfxReverbStdExpSymbols()\n{\n   RegisterFunctionExport(AXFXReverbStdExpGetMemSize);\n   RegisterFunctionExport(AXFXReverbStdExpInit);\n   RegisterFunctionExport(AXFXReverbStdExpShutdown);\n   RegisterFunctionExport(AXFXReverbStdExpCallback);\n   RegisterFunctionExport(AXFXReverbStdExpSettings);\n   RegisterFunctionExport(AXFXReverbStdExpSettingsUpdate);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_axfx_reverbstdexp.h",
    "content": "#pragma once\n#include \"snduser2_axfx.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\nstruct AXFXReverbStdExp\n{\n   UNKNOWN(0xB0);\n   be2_val<uint32_t> stateFlags;\n\n   UNKNOWN(4);\n\n   //! Reverb Pre-Delay in seconds\n   be2_val<float> preDelaySeconds;\n\n   UNKNOWN(0x2C);\n};\nCHECK_OFFSET(AXFXReverbStdExp, 0xB0, stateFlags);\nCHECK_OFFSET(AXFXReverbStdExp, 0xB8, preDelaySeconds);\nCHECK_SIZE(AXFXReverbStdExp, 0xE8);\n\nint32_t\nAXFXReverbStdExpGetMemSize(virt_ptr<AXFXReverbStdExp> reverb);\n\nBOOL\nAXFXReverbStdExpInit(virt_ptr<AXFXReverbStdExp> reverb);\n\nvoid\nAXFXReverbStdExpShutdown(virt_ptr<AXFXReverbStdExp> reverb);\n\nvoid\nAXFXReverbStdExpCallback(virt_ptr<AXFXBuffers> buffers,\n                      virt_ptr<AXFXReverbStdExp> reverb);\n\nBOOL\nAXFXReverbStdExpSettings(virt_ptr<AXFXReverbStdExp> reverb);\n\nBOOL\nAXFXReverbStdExpSettingsUpdate(virt_ptr<AXFXReverbStdExp> reverb);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_enum.h",
    "content": "#ifndef CAFE_SNDUSER2_ENUM_H\n#define CAFE_SNDUSER2_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(snduser2)\n\nENUM_BEG(AXFXSampleRate, uint32_t)\n   ENUM_VALUE(Rate32khz,         1)\n   ENUM_VALUE(Rate48khz,         2)\nENUM_END(AXFXSampleRate)\n\nENUM_BEG(AXFXReverbPreset, uint32_t)\n   ENUM_VALUE(Unknown1,          1)\n   ENUM_VALUE(Unknown2,          2)\n   ENUM_VALUE(Unknown3,          3)\n   ENUM_VALUE(Unknown4,          4)\n   ENUM_VALUE(Unknown5,          5)\nENUM_END(AXFXReverbPreset)\n\nENUM_BEG(AXFXReverbType, uint32_t)\n   ENUM_VALUE(Unknown1,          1)\n   ENUM_VALUE(Unknown2,          2)\n   ENUM_VALUE(Unknown3,          3)\n   ENUM_VALUE(Unknown4,          4)\nENUM_END(AXFXReverbType)\n\nENUM_NAMESPACE_EXIT(snduser2)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef CAFE_SNDUSER2_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_mix.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_mix.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::snduser2\n{\n\nvoid\nMIXUpdateSettings()\n{\n   decaf_warn_stub();\n}\n\nvoid\nLibrary::registerMixSymbols()\n{\n   RegisterFunctionExport(MIXUpdateSettings);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_mix.h",
    "content": "#pragma once\n\nnamespace cafe::snduser2\n{\n\nvoid\nMIXUpdateSettings();\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_sp.cpp",
    "content": "#include \"snduser2.h\"\n#include \"snduser2_sp.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::snduser2\n{\n\nvoid\nSPInitSoundTable(virt_ptr<SPSoundTable> table,\n                 virt_ptr<int32_t> samples,\n                 virt_ptr<uint32_t> outSamplesSize)\n{\n   decaf_warn_stub();\n}\n\nvirt_ptr<SPSoundEntry>\nSPGetSoundEntry(virt_ptr<SPSoundTable> table,\n                uint32_t index)\n{\n   if (index >= table->numEntries) {\n      return nullptr;\n   }\n\n   return virt_addrof(table->entries) + index;\n}\n\nvoid\nLibrary::registerSpSymbols()\n{\n   RegisterFunctionExport(SPInitSoundTable);\n   RegisterFunctionExport(SPGetSoundEntry);\n   //RegisterFunctionExport(SPPrepareSound);\n   //RegisterFunctionExport(SPPrepareEnd);\n}\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/snduser2/snduser2_sp.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::snduser2\n{\n\nstruct SPSoundEntry\n{\n   UNKNOWN(0x1C);\n};\nCHECK_SIZE(SPSoundEntry, 0x1C);\n\nstruct SPSoundTable\n{\n   be2_val<uint32_t> numEntries;\n\n   //! This is actually a dynamically sized array.\n   be2_array<SPSoundEntry, 1> entries;\n};\nCHECK_OFFSET(SPSoundTable, 0x00, numEntries);\nCHECK_OFFSET(SPSoundTable, 0x04, entries);\n\nvoid\nSPInitSoundTable(virt_ptr<SPSoundTable> table,\n                 virt_ptr<int32_t> samples,\n                 virt_ptr<uint32_t> outSamplesSize);\n\nvirt_ptr<SPSoundEntry>\nSPGetSoundEntry(virt_ptr<SPSoundTable> table,\n                uint32_t index);\n\n} // namespace cafe::snduser2\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/swkbd/swkbd.cpp",
    "content": "#include \"swkbd.h\"\n\nnamespace cafe::swkbd\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerKeyboardSymbols();\n}\n\n} // namespace cafe::swkbd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/swkbd/swkbd.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::swkbd\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::swkbd, \"swkbd.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerKeyboardSymbols();\n};\n\n} // namespace cafe::swkbd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/swkbd/swkbd_enum.h",
    "content": "#ifndef SWKBD_ENUM_H\n#define SWKBD_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(swkbd)\n\nENUM_BEG(ControllerType, int32_t)\n   ENUM_VALUE(Unknown0,                0)\nENUM_END(ControllerType)\n\nENUM_BEG(LanguageType, int32_t)\n   ENUM_VALUE(Japanese,                0)\n   ENUM_VALUE(English,                 1)\nENUM_END(LanguageType)\n\nENUM_BEG(RegionType, int32_t)\n   ENUM_VALUE(Japan,                   0)\n   ENUM_VALUE(USA,                     1)\n   ENUM_VALUE(Europe,                  2)\nENUM_END(RegionType)\n\nENUM_BEG(State, int32_t)\n   ENUM_VALUE(Hidden,                  0)\n   ENUM_VALUE(FadeIn,                  1)\n   ENUM_VALUE(Visible,                 2)\n   ENUM_VALUE(FadeOut,                 3)\n   ENUM_VALUE(Max,                     4)\nENUM_END(State)\n\nENUM_NAMESPACE_EXIT(swkbd)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef SWKBD_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/swkbd/swkbd_keyboard.cpp",
    "content": "#include \"swkbd.h\"\n#include \"swkbd_keyboard.h\"\n\n#include \"common/strutils.h\"\n#include \"decaf_softwarekeyboard.h\"\n\n#include <codecvt>\n#include <locale>\n#include <mutex>\n#include <string>\n\nnamespace cafe::swkbd\n{\n\nstruct StaticKeyboardData\n{\n   be2_val<State> inputFormState;\n   be2_val<State> keyboardState;\n   be2_val<bool> okButtonPressed;\n   be2_val<bool> cancelButtonPressed;\n   be2_virt_ptr<uint8_t> workMemory;\n   be2_virt_ptr<char16_t> textBuffer;\n   be2_val<uint32_t> textBufferSize;\n};\n\nstatic virt_ptr<StaticKeyboardData> sKeyboardData = nullptr;\nstatic std::mutex sMutex;\n\nstatic void\nsetReceiverArg(const ReceiverArg &receiverArg)\n{\n   if (receiverArg.textBuffer && receiverArg.textBufferSize) {\n      sKeyboardData->textBuffer = receiverArg.textBuffer;\n      sKeyboardData->textBufferSize = receiverArg.textBufferSize;\n   } else {\n      sKeyboardData->textBuffer =\n         virt_cast<char16_t *>(sKeyboardData->workMemory);\n      sKeyboardData->textBufferSize = 0xFFFFu;\n   }\n}\n\nbool\nAppearInputForm(virt_ptr<const AppearArg> arg)\n{\n   {\n      std::unique_lock<std::mutex> lock { sMutex };\n      sKeyboardData->inputFormState = State::Visible;\n      sKeyboardData->keyboardState = State::Visible;\n      sKeyboardData->okButtonPressed = false;\n      sKeyboardData->cancelButtonPressed = false;\n      setReceiverArg(arg->keyboardArg.receiverArg);\n   }\n\n   if (auto driver = decaf::softwareKeyboardDriver()) {\n      driver->onOpen({});\n   }\n\n   return true;\n}\n\n\nbool\nAppearKeyboard(virt_ptr<const KeyboardArg> arg)\n{\n   {\n      std::unique_lock<std::mutex> lock { sMutex };\n      sKeyboardData->keyboardState = State::Visible;\n      sKeyboardData->okButtonPressed = false;\n      sKeyboardData->cancelButtonPressed = false;\n      setReceiverArg(arg->receiverArg);\n   }\n\n   if (auto driver = decaf::softwareKeyboardDriver()) {\n      driver->onOpen({});\n   }\n\n   return true;\n}\n\n\nvoid\nCalcSubThreadFont()\n{\n}\n\n\nvoid\nCalcSubThreadPredict()\n{\n}\n\n\nvoid\nCalc(virt_ptr<const ControllerInfo> info)\n{\n}\n\n\nvoid\nConfirmUnfixAll()\n{\n}\n\n\nvoid\nCreate(virt_ptr<unsigned char> workMemory,\n       RegionType regionType,\n       unsigned int unk,\n       virt_ptr<coreinit::FSClient> fsclient)\n{\n   sKeyboardData->workMemory = workMemory;\n}\n\n\nvoid\nDestroy()\n{\n}\n\n\nbool\nDisappearInputForm()\n{\n   sKeyboardData->keyboardState = State::Hidden;\n\n   if (auto driver = decaf::softwareKeyboardDriver()) {\n      driver->onClose();\n   }\n\n   return true;\n}\n\n\nbool\nDisappearKeyboard()\n{\n   sKeyboardData->keyboardState = State::Hidden;\n\n   if (auto driver = decaf::softwareKeyboardDriver()) {\n      driver->onClose();\n   }\n\n   return true;\n}\n\n\nvoid\nDrawDRC()\n{\n}\n\n\nvoid\nDrawTV()\n{\n}\n\n\nvoid\nGetDrawStringInfo(virt_ptr<DrawStringInfo> info)\n{\n}\n\n\nvirt_ptr<const char16_t>\nGetInputFormString()\n{\n   return virt_cast<const char16_t *>(sKeyboardData->textBuffer);\n}\n\n\nvoid\nGetKeyboardCondition(virt_ptr<KeyboardCondition> condition)\n{\n}\n\n\nState\nGetStateInputForm()\n{\n   return sKeyboardData->inputFormState;\n}\n\n\nState\nGetStateKeyboard()\n{\n   return sKeyboardData->keyboardState;\n}\n\n\nvoid\nInactivateSelectCursor()\n{\n}\n\n\nbool\nInitLearnDic(virt_ptr<void> dictionary)\n{\n   return true;\n}\n\n\nbool\nIsCoveredWithSubWindow()\n{\n   return false;\n}\n\n\nbool\nIsDecideCancelButton(virt_ptr<bool> outIsSelected)\n{\n   return sKeyboardData->cancelButtonPressed;\n}\n\n\nbool\nIsDecideOkButton(virt_ptr<bool> outIsSelected)\n{\n   return sKeyboardData->okButtonPressed;\n}\n\n\nbool\nIsKeyboardTarget(virt_ptr<const IEventReceiver> receiver)\n{\n   return false;\n}\n\n\nbool\nIsNeedCalcSubThreadFont()\n{\n   return false;\n}\n\n\nbool\nIsNeedCalcSubThreadPredict()\n{\n   return false;\n}\n\n\nbool\nIsSelectCursorActive()\n{\n   return false;\n}\n\n\nvoid\nMuteAllSound(bool mute)\n{\n}\n\n\nvoid\nSetControllerRemo(ControllerType controller)\n{\n}\n\n\nvoid\nSetCursorPos(int32_t pos)\n{\n}\n\n\nvoid\nSetEnableOkButton(bool enable)\n{\n}\n\n\nvoid\nSetInputFormString(virt_ptr<const char16_t> str)\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   string_copy<char16_t>(sKeyboardData->textBuffer.get(),\n                         static_cast<size_t>(sKeyboardData->textBufferSize),\n                         str.get(),\n                         static_cast<size_t>(sKeyboardData->textBufferSize - 1));\n\n   if (auto driver = decaf::softwareKeyboardDriver()) {\n      driver->onInputStringChanged(sKeyboardData->textBuffer.getRawPointer());\n   }\n}\n\n\nvoid\nSetReceiver(virt_ptr<const ReceiverArg> arg)\n{\n}\n\n\nvoid\nSetSelectFrom(int32_t pos)\n{\n}\n\n\nvoid\nSetUserControllerEventObj(virt_ptr<IControllerEventObj> obj)\n{\n}\n\n\nvoid\nSetUserSoundObj(virt_ptr<ISoundObj> obj)\n{\n}\n\n\nvoid\nSetVersion(int32_t version)\n{\n}\n\nvoid\nLibrary::registerKeyboardSymbols()\n{\n   RegisterFunctionExportName(\"SwkbdAppearInputForm__3RplFRCQ3_2nn5swkbd9AppearArg\", AppearInputForm);\n   RegisterFunctionExportName(\"SwkbdAppearKeyboard__3RplFRCQ3_2nn5swkbd11KeyboardArg\", AppearKeyboard);\n   RegisterFunctionExportName(\"SwkbdCalcSubThreadFont__3RplFv\", CalcSubThreadFont);\n   RegisterFunctionExportName(\"SwkbdCalcSubThreadPredict__3RplFv\", CalcSubThreadPredict);\n   RegisterFunctionExportName(\"SwkbdCalc__3RplFRCQ3_2nn5swkbd14ControllerInfo\", Calc);\n   RegisterFunctionExportName(\"SwkbdConfirmUnfixAll__3RplFv\", ConfirmUnfixAll);\n   RegisterFunctionExportName(\"SwkbdCreate__3RplFPUcQ3_2nn5swkbd10RegionTypeUiP8FSClient\", Create);\n   RegisterFunctionExportName(\"SwkbdDestroy__3RplFv\", Destroy);\n   RegisterFunctionExportName(\"SwkbdDisappearInputForm__3RplFv\", DisappearInputForm);\n   RegisterFunctionExportName(\"SwkbdDisappearKeyboard__3RplFv\", DisappearKeyboard);\n   RegisterFunctionExportName(\"SwkbdDrawDRC__3RplFv\", DrawDRC);\n   RegisterFunctionExportName(\"SwkbdDrawTV__3RplFv\", DrawTV);\n   RegisterFunctionExportName(\"SwkbdGetDrawStringInfo__3RplFPQ3_2nn5swkbd14DrawStringInfo\", GetDrawStringInfo);\n   RegisterFunctionExportName(\"SwkbdGetInputFormString__3RplFv\", GetInputFormString);\n   RegisterFunctionExportName(\"SwkbdGetKeyboardCondition__3RplFPQ3_2nn5swkbd17KeyboardCondition\", GetKeyboardCondition);\n   RegisterFunctionExportName(\"SwkbdGetStateInputForm__3RplFv\", GetStateInputForm);\n   RegisterFunctionExportName(\"SwkbdGetStateKeyboard__3RplFv\", GetStateKeyboard);\n   RegisterFunctionExportName(\"SwkbdInactivateSelectCursor__3RplFv\", InactivateSelectCursor);\n   RegisterFunctionExportName(\"SwkbdInitLearnDic__3RplFPv\", InitLearnDic);\n   RegisterFunctionExportName(\"SwkbdIsCoveredWithSubWindow__3RplFv\", IsCoveredWithSubWindow);\n   RegisterFunctionExportName(\"SwkbdIsDecideCancelButton__3RplFPb\", IsDecideCancelButton);\n   RegisterFunctionExportName(\"SwkbdIsDecideOkButton__3RplFPb\", IsDecideOkButton);\n   RegisterFunctionExportName(\"SwkbdIsKeyboardTarget__3RplFPQ3_2nn5swkbd14IEventReceiver\", IsKeyboardTarget);\n   RegisterFunctionExportName(\"SwkbdIsNeedCalcSubThreadFont__3RplFv\", IsNeedCalcSubThreadFont);\n   RegisterFunctionExportName(\"SwkbdIsNeedCalcSubThreadPredict__3RplFv\", IsNeedCalcSubThreadPredict);\n   RegisterFunctionExportName(\"SwkbdIsSelectCursorActive__3RplFv\", IsSelectCursorActive);\n   RegisterFunctionExportName(\"SwkbdMuteAllSound__3RplFb\", MuteAllSound);\n   RegisterFunctionExportName(\"SwkbdSetControllerRemo__3RplFQ3_2nn5swkbd14ControllerType\", SetControllerRemo);\n   RegisterFunctionExportName(\"SwkbdSetCursorPos__3RplFi\", SetCursorPos);\n   RegisterFunctionExportName(\"SwkbdSetEnableOkButton__3RplFb\", SetEnableOkButton);\n   RegisterFunctionExportName(\"SwkbdSetInputFormString__3RplFPCw\", SetInputFormString);\n   RegisterFunctionExportName(\"SwkbdSetReceiver__3RplFRCQ3_2nn5swkbd11ReceiverArg\", SetReceiver);\n   RegisterFunctionExportName(\"SwkbdSetSelectFrom__3RplFi\", SetSelectFrom);\n   RegisterFunctionExportName(\"SwkbdSetUserControllerEventObj__3RplFPQ3_2nn5swkbd19IControllerEventObj\", SetUserControllerEventObj);\n   RegisterFunctionExportName(\"SwkbdSetUserSoundObj__3RplFPQ3_2nn5swkbd9ISoundObj\", SetUserSoundObj);\n   RegisterFunctionExportName(\"SwkbdSetVersion__3RplFi\", SetVersion);\n\n   RegisterDataInternal(sKeyboardData);\n}\n\nnamespace internal\n{\n\nvoid\ninputAccept()\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   if (sKeyboardData->keyboardState != State::Visible) {\n      return;\n   }\n\n   sKeyboardData->okButtonPressed = true;\n   sKeyboardData->cancelButtonPressed = false;\n}\n\nvoid\ninputReject()\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   if (sKeyboardData->keyboardState != State::Visible) {\n      return;\n   }\n\n   sKeyboardData->okButtonPressed = false;\n   sKeyboardData->cancelButtonPressed = true;\n}\n\nvoid\nsetInputString(std::u16string_view text)\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   if (sKeyboardData->keyboardState != State::Visible) {\n      return;\n   }\n\n   string_copy<char16_t>(sKeyboardData->textBuffer.get(),\n                         static_cast<size_t>(sKeyboardData->textBufferSize),\n                         text.data(),\n                         text.size());\n}\n\n} // namespace internal\n\n} // namespace cafe::swkbd\n\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/swkbd/swkbd_keyboard.h",
    "content": "#pragma once\n#include \"decaf_input.h\"\n#include \"swkbd_enum.h\"\n\n#include <common/platform.h>\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::coreinit\n{\nstruct FSClient;\n} // namespace cafe::coreinit\n\nnamespace cafe::kpad\n{\nstruct KPADStatus;\n} // namespace cafe::kpad\n\nnamespace cafe::vpad\n{\nstruct VPADStatus;\n} // namespace cafe::vpad\n\nnamespace cafe::swkbd\n{\n\nstruct ConfigArg\n{\n   be2_val<LanguageType> languageType;\n   be2_val<uint32_t> unk_0x04;\n   be2_val<uint32_t> unk_0x08;\n   be2_val<uint32_t> unk_0x0C;\n   be2_val<uint32_t> unk_0x10;\n   be2_val<int32_t> unk_0x14;\n   UNKNOWN(0x9C - 0x18);\n   be2_val<uint32_t> unk_0x9C;\n   UNKNOWN(4);\n   be2_val<int32_t> unk_0xA4;\n};\nCHECK_OFFSET(ConfigArg, 0x00, languageType);\nCHECK_OFFSET(ConfigArg, 0x04, unk_0x04);\nCHECK_OFFSET(ConfigArg, 0x08, unk_0x08);\nCHECK_OFFSET(ConfigArg, 0x0C, unk_0x0C);\nCHECK_OFFSET(ConfigArg, 0x10, unk_0x10);\nCHECK_OFFSET(ConfigArg, 0x14, unk_0x14);\nCHECK_OFFSET(ConfigArg, 0x9C, unk_0x9C);\nCHECK_OFFSET(ConfigArg, 0xA4, unk_0xA4);\nCHECK_SIZE(ConfigArg, 0xA8);\n\nstruct ReceiverArg\n{\n   be2_val<uint32_t> unk_0x00;\n   be2_virt_ptr<char16_t> textBuffer;\n   be2_val<uint32_t> textBufferSize;\n   be2_val<int32_t> unk_0x0C;\n   be2_val<uint32_t> unk_0x10;\n   be2_val<int32_t> unk_0x14;\n};\nCHECK_OFFSET(ReceiverArg, 0x00, unk_0x00);\nCHECK_OFFSET(ReceiverArg, 0x04, textBuffer);\nCHECK_OFFSET(ReceiverArg, 0x08, textBufferSize);\nCHECK_OFFSET(ReceiverArg, 0x0C, unk_0x0C);\nCHECK_OFFSET(ReceiverArg, 0x10, unk_0x10);\nCHECK_OFFSET(ReceiverArg, 0x14, unk_0x14);\nCHECK_SIZE(ReceiverArg, 0x18);\n\nstruct KeyboardArg\n{\n   ConfigArg configArg;\n   ReceiverArg receiverArg;\n};\nCHECK_SIZE(KeyboardArg, 0xC0);\n\nstruct InputFormArg\n{\n   be2_val<uint32_t> unk_0x00;\n   be2_val<int32_t> unk_0x04;\n   be2_val<uint32_t> unk_0x08;\n   be2_val<uint32_t> unk_0x0C;\n   be2_val<int32_t> maxTextLength;\n   be2_val<uint32_t> unk_0x14;\n   be2_val<uint32_t> unk_0x18;\n   be2_val<bool> unk_0x1C;\n   be2_val<bool> unk_0x1D;\n   be2_val<bool> unk_0x1E;\n   PADDING(1);\n};\nCHECK_OFFSET(InputFormArg, 0x00, unk_0x00);\nCHECK_OFFSET(InputFormArg, 0x04, unk_0x04);\nCHECK_OFFSET(InputFormArg, 0x08, unk_0x08);\nCHECK_OFFSET(InputFormArg, 0x0C, unk_0x0C);\nCHECK_OFFSET(InputFormArg, 0x10, maxTextLength);\nCHECK_OFFSET(InputFormArg, 0x14, unk_0x14);\nCHECK_OFFSET(InputFormArg, 0x18, unk_0x18);\nCHECK_OFFSET(InputFormArg, 0x1C, unk_0x1C);\nCHECK_OFFSET(InputFormArg, 0x1D, unk_0x1D);\nCHECK_OFFSET(InputFormArg, 0x1E, unk_0x1E);\nCHECK_SIZE(InputFormArg, 0x20);\n\nstruct AppearArg\n{\n   be2_struct<KeyboardArg> keyboardArg;\n   be2_struct<InputFormArg> inputFormArg;\n};\nCHECK_OFFSET(AppearArg, 0x00, keyboardArg);\nCHECK_OFFSET(AppearArg, 0xC0, inputFormArg);\nCHECK_SIZE(AppearArg, 0xE0);\n\nstruct CreateArg\n{\n   be2_virt_ptr<void> workMemory;\n   be2_val<RegionType> regionType;\n   be2_val<uint32_t> unk_0x08;\n   be2_virt_ptr<coreinit::FSClient> fsClient;\n};\nCHECK_OFFSET(CreateArg, 0x00, workMemory);\nCHECK_OFFSET(CreateArg, 0x04, regionType);\nCHECK_OFFSET(CreateArg, 0x08, unk_0x08);\nCHECK_OFFSET(CreateArg, 0x0C, fsClient);\nCHECK_SIZE(CreateArg, 0x10);\n\nstruct ControllerInfo\n{\n   be2_virt_ptr<vpad::VPADStatus> vpad;\n   be2_array<virt_ptr<kpad::KPADStatus>, 4> kpad;\n};\nCHECK_OFFSET(ControllerInfo, 0x00, vpad);\nCHECK_OFFSET(ControllerInfo, 0x04, kpad);\nCHECK_SIZE(ControllerInfo, 0x14);\n\nstruct DrawStringInfo\n{\n   UNKNOWN(0x1C);\n};\nCHECK_SIZE(DrawStringInfo, 0x1C);\n\nstruct KeyboardCondition\n{\n   be2_val<uint32_t> unk_0x00;\n   be2_val<uint32_t> unk_0x04;\n};\nCHECK_OFFSET(KeyboardCondition, 0x00, unk_0x00);\nCHECK_OFFSET(KeyboardCondition, 0x04, unk_0x04);\nCHECK_SIZE(KeyboardCondition, 0x8);\n\nstruct IEventReceiver;\nstruct IControllerEventObj;\nstruct ISoundObj;\n\nbool\nAppearInputForm(virt_ptr<const AppearArg> arg);\n\nbool\nAppearKeyboard(virt_ptr<const KeyboardArg> arg);\n\nvoid\nCalcSubThreadFont();\n\nvoid\nCalcSubThreadPredict();\n\nvoid\nCalc(virt_ptr<const ControllerInfo> info);\n\nvoid\nConfirmUnfixAll();\n\nvoid\nCreate(virt_ptr<unsigned char> buffer,\n       RegionType regionType,\n       unsigned int unk,\n       virt_ptr<coreinit::FSClient> fsclient);\n\nvoid\nDestroy();\n\nbool\nDisappearInputForm();\n\nbool\nDisappearKeyboard();\n\nvoid\nDrawDRC();\n\nvoid\nDrawTV();\n\nvoid\nGetDrawStringInfo(virt_ptr<DrawStringInfo> info);\n\nvirt_ptr<const char16_t>\nGetInputFormString();\n\nvoid\nGetKeyboardCondition(virt_ptr<KeyboardCondition> condition);\n\nState\nGetStateInputForm();\n\nState\nGetStateKeyboard();\n\nvoid\nInactivateSelectCursor();\n\nbool\nInitLearnDic(virt_ptr<void> dictionary);\n\nbool\nIsCoveredWithSubWindow();\n\nbool\nIsDecideCancelButton(virt_ptr<bool> outIsSelected);\n\nbool\nIsDecideOkButton(virt_ptr<bool> outIsSelected);\n\nbool\nIsKeyboardTarget(virt_ptr<const IEventReceiver> receiver);\n\nbool\nIsNeedCalcSubThreadFont();\n\nbool\nIsNeedCalcSubThreadPredict();\n\nbool\nIsSelectCursorActive();\n\nvoid\nMuteAllSound(bool mute);\n\nvoid\nSetControllerRemo(ControllerType type);\n\nvoid\nSetCursorPos(int32_t pos);\n\nvoid\nSetEnableOkButton(bool enable);\n\nvoid\nSetInputFormString(virt_ptr<const char16_t> str);\n\nvoid\nSetReceiver(virt_ptr<const ReceiverArg> arg);\n\nvoid\nSetSelectFrom(int32_t pos);\n\nvoid\nSetUserControllerEventObj(virt_ptr<IControllerEventObj> obj);\n\nvoid\nSetUserSoundObj(virt_ptr<ISoundObj> obj);\n\nvoid\nSetVersion(int32_t version);\n\nnamespace internal\n{\n\nvoid\ninputAccept();\n\nvoid\ninputReject();\n\nvoid\nsetInputString(std::u16string_view text);\n\n} // namespace internal\n\n} // namespace cafe::swkbd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sysapp/sysapp.cpp",
    "content": "#include \"sysapp.h\"\n\nnamespace cafe::sysapp\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n   registerCallerArgsSymbols();\n   registerTitleSymbols();\n}\n\n} // namespace cafe::sysapp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sysapp/sysapp.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::sysapp\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::sysapp, \"sysapp.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerCallerArgsSymbols();\n   void registerTitleSymbols();\n};\n\n} // namespace cafe::zlib125\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sysapp/sysapp_callerargs.cpp",
    "content": "#include \"sysapp.h\"\n#include \"sysapp_callerargs.h\"\n\n#include <cafe/libraries/cafe_hle_stub.h>\n\nnamespace cafe::sysapp\n{\n\nuint32_t\nSYSGetCallerPFID()\n{\n   return SYSGetCallerUPID();\n}\n\nuint64_t\nSYSGetCallerTitleId()\n{\n   decaf_warn_stub();\n   return 0ull;\n}\n\nuint32_t\nSYSGetCallerUPID()\n{\n   decaf_warn_stub();\n   return 0u;\n}\n\nint32_t\nSYSGetLauncherArgs(virt_ptr<void> args)\n{\n   decaf_warn_stub();\n   std::memset(args.get(), 0, 0xC);\n   return 1;\n}\n\nint32_t\nSYSGetStandardResult(virt_ptr<uint32_t> arg1,\n                     uint32_t arg2,\n                     uint32_t arg3)\n{\n   decaf_warn_stub();\n   std::memset(arg1.get(), 0, 0x4);\n   return 1;\n}\n\nvoid\nLibrary::registerCallerArgsSymbols()\n{\n   RegisterFunctionExport(SYSGetCallerPFID);\n   RegisterFunctionExport(SYSGetCallerTitleId);\n   RegisterFunctionExport(SYSGetCallerUPID);\n   RegisterFunctionExport(SYSGetStandardResult);\n   RegisterFunctionExportName(\"_SYSGetLauncherArgs\", SYSGetLauncherArgs);\n}\n\n} // namespace cafe::sysapp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sysapp/sysapp_callerargs.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace cafe::sysapp\n{\n\nuint32_t\nSYSGetCallerPFID();\n\nuint64_t\nSYSGetCallerTitleId();\n\nuint32_t\nSYSGetCallerUPID();\n\nint32_t\nSYSGetLauncherArgs(virt_ptr<void> args);\n\nint32_t\nSYSGetStandardResult(virt_ptr<uint32_t> arg1,\n                     uint32_t arg2,\n                     uint32_t arg3);\n\n} // namespace cafe::sysapp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sysapp/sysapp_enum.h",
    "content": "#ifndef SYSAPP_ENUM_H\n#define SYSAPP_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(sysapp)\n\nENUM_BEG(SystemAppId, int32_t)\n   ENUM_VALUE(Updater,                 0)\n   ENUM_VALUE(SystemSettings,          1)\n   ENUM_VALUE(ParentalControls,        2)\n   ENUM_VALUE(UserSettings,            3)\n   ENUM_VALUE(MiiMaker,                4)\n   ENUM_VALUE(AccountSettings,         5)\n   ENUM_VALUE(DailyLog,                6)\n   ENUM_VALUE(Notifications,           7)\n   ENUM_VALUE(HealthAndSafety,         8)\n   ENUM_VALUE(ElectronicManual,        9)\n   ENUM_VALUE(WiiUChat,                10)\n   ENUM_VALUE(SoftwareDataTransfer,    11)\n   ENUM_VALUE(Max,                     12)\nENUM_END(SystemAppId)\n\nENUM_NAMESPACE_EXIT(sysapp)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef SYSAPP_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sysapp/sysapp_title.cpp",
    "content": "#include \"sysapp.h\"\n#include \"sysapp_title.h\"\n#include \"cafe/libraries/coreinit/coreinit_mcp.h\"\n#include \"cafe/libraries/coreinit/coreinit_enum_string.h\"\n#include \"cafe/cafe_stackobject.h\"\n\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n\nnamespace cafe::sysapp\n{\n\nusing namespace cafe::coreinit;\n\nstatic const uint64_t\nsSysAppTitleId[][3] =\n{\n   {\n      // Updater\n      0x0005001010040000ull,\n      0x0005001010040100ull,\n      0x0005001010040200ull,\n   },\n\n   {\n      // System Settings\n      0x0005001010047000ull,\n      0x0005001010047100ull,\n      0x0005001010047200ull,\n   },\n\n   {\n      // Parental Controls\n      0x0005001010048000ull,\n      0x0005001010048100ull,\n      0x0005001010048200ull,\n   },\n\n   {\n      // User Settings\n      0x0005001010049000ull,\n      0x0005001010049100ull,\n      0x0005001010049200ull,\n   },\n\n   {\n      // Mii Maker\n      0x000500101004A000ull,\n      0x000500101004A100ull,\n      0x000500101004A200ull,\n   },\n\n   {\n      // Account Settings\n      0x000500101004B000ull,\n      0x000500101004B100ull,\n      0x000500101004B200ull,\n   },\n\n   {\n      // Daily log\n      0x000500101004C000ull,\n      0x000500101004C100ull,\n      0x000500101004C200ull,\n   },\n\n   {\n      // Notifications\n      0x000500101004D000ull,\n      0x000500101004D100ull,\n      0x000500101004D200ull,\n   },\n\n   {\n      // Health and Safety Information\n      0x000500101004E000ull,\n      0x000500101004E100ull,\n      0x000500101004E200ull,\n   },\n\n   {\n      // Electronic Manual\n      0x0005001B10059000ull,\n      0x0005001B10059100ull,\n      0x0005001B10059200ull,\n   },\n\n   {\n      // Wii U Chat\n      0x000500101005A000ull,\n      0x000500101005A100ull,\n      0x000500101005A200ull,\n   },\n\n   {\n      // \"Software/Data Transfer\"\n      0x0005001010062000ull,\n      0x0005001010062100ull,\n      0x0005001010062200ull,\n   },\n};\n\n\n/**\n * _SYSGetSystemApplicationTitleId\n */\nuint64_t\nSYSGetSystemApplicationTitleId(SystemAppId id)\n{\n   auto settings = StackObject<MCPSysProdSettings> { };\n   decaf_check(id < SystemAppId::Max);\n\n   auto mcp = MCP_Open();\n   MCP_GetSysProdSettings(mcp, settings);\n   MCP_Close(mcp);\n\n   return SYSGetSystemApplicationTitleIdByProdArea(id, settings->product_area);\n}\n\n\n/**\n * _SYSGetSystemApplicationTitleIdByProdArea\n */\nuint64_t\nSYSGetSystemApplicationTitleIdByProdArea(SystemAppId id,\n                                         MCPRegion prodArea)\n{\n   auto regionIdx = 1u;\n\n   if (prodArea == coreinit::MCPRegion::Japan) {\n      regionIdx = 0u;\n   } else if (prodArea == coreinit::MCPRegion::Europe ||\n              prodArea == coreinit::MCPRegion::Unknown8) {\n      regionIdx = 2u;\n   }\n\n   return sSysAppTitleId[id][regionIdx];\n}\n\nvoid\nLibrary::registerTitleSymbols()\n{\n   RegisterFunctionExportName(\"_SYSGetSystemApplicationTitleId\",\n                              SYSGetSystemApplicationTitleId);\n   RegisterFunctionExportName(\"_SYSGetSystemApplicationTitleIdByProdArea\",\n                              SYSGetSystemApplicationTitleIdByProdArea);\n}\n\n} // namespace cafe::sysapp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/sysapp/sysapp_title.h",
    "content": "#pragma once\n#include \"cafe/libraries/coreinit/coreinit_mcp.h\"\n#include \"sysapp_enum.h\"\n\n#include <cstdint>\n\nnamespace cafe::sysapp\n{\n\nuint64_t\nSYSGetSystemApplicationTitleId(SystemAppId id);\n\nuint64_t\nSYSGetSystemApplicationTitleIdByProdArea(SystemAppId id,\n                                         coreinit::MCPRegion region);\n\n} // namespace cafe::sysapp\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl.cpp",
    "content": "#include \"tcl.h\"\n#include \"tcl_aperture.h\"\n#include \"tcl_driver.h\"\n#include \"tcl_interrupthandler.h\"\n#include \"tcl_ring.h\"\n\nnamespace cafe::tcl\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   internal::initialiseTclDriver();\n   internal::initialiseApertures();\n   internal::initialiseInterruptHandler();\n   internal::initialiseRing();\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerApertureSymbols();\n   registerDriverSymbols();\n   registerInterruptHandlerSymbols();\n   registerRegisterSymbols();\n   registerRingSymbols();\n}\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::tcl\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::tcl, \"tcl.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerApertureSymbols();\n   void registerDriverSymbols();\n   void registerInterruptHandlerSymbols();\n   void registerRegisterSymbols();\n   void registerRingSymbols();\n};\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_aperture.cpp",
    "content": "#include \"tcl.h\"\n#include \"tcl_aperture.h\"\n#include \"tcl_driver.h\"\n\n#include \"cafe/cafe_tinyheap.h\"\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n\nnamespace cafe::tcl\n{\n\nusing namespace cafe::coreinit;\n\nstruct AllocatedAperture\n{\n   be2_val<phys_addr> surface;\n   be2_virt_ptr<void> apertureMemory;\n};\n\nstruct StaticApertureData\n{\n   be2_val<virt_addr> baseVirtualAddress;\n   be2_val<uint32_t> allocatedMask;\n\n   be2_array<AllocatedAperture, 32> apertures;\n   be2_array<uint8_t, TinyHeapHeaderSize + 32 * TinyHeapBlockSize> trackingHeap;\n};\n\nstatic virt_ptr<StaticApertureData>\nsApertureData = nullptr;\n\nTCLStatus\nTCLAllocTilingAperture(phys_addr addr,\n                       uint32_t pitch,\n                       uint32_t height,\n                       uint32_t bytesPerPixel,\n                       uint32_t tileMode,\n                       uint32_t endian,\n                       virt_ptr<TCLApertureHandle> outHandle,\n                       virt_ptr<virt_addr> outAddress)\n{\n   if (!internal::tclDriverInitialised()) {\n      return TCLStatus::NotInitialised;\n   }\n\n   auto handle = 32u;\n   for (auto i = 0u; i < 32; ++i) {\n      if (sApertureData->allocatedMask & (1 << i)) {\n         continue;\n      }\n\n      handle = i;\n      break;\n   }\n\n   if (handle >= 32) {\n      return TCLStatus::OutOfMemory;\n   }\n\n   auto ptr = virt_ptr<void> { nullptr };\n   auto size = pitch * height * bytesPerPixel;\n   if (TinyHeap_Alloc(virt_cast<TinyHeap *>(virt_addrof(sApertureData->trackingHeap)),\n                      size, 256, &ptr) != TinyHeapError::OK) {\n      return TCLStatus::OutOfMemory;\n   }\n\n   auto address = sApertureData->baseVirtualAddress\n                  + static_cast<uint32_t>(virt_cast<virt_addr>(ptr));\n   sApertureData->allocatedMask |= 1 << handle;\n   sApertureData->apertures[handle].surface = addr;\n   sApertureData->apertures[handle].apertureMemory = ptr;\n\n   if (outHandle) {\n      *outHandle = handle;\n   }\n\n   if (outAddress) {\n      *outAddress = address;\n   }\n\n   return TCLStatus::OK;\n}\n\nTCLStatus\nTCLFreeTilingAperture(TCLApertureHandle handle)\n{\n   if (!internal::tclDriverInitialised()) {\n      return TCLStatus::NotInitialised;\n   }\n\n   if (handle >= 32) {\n      return TCLStatus::InvalidArg;\n   }\n\n   if (!(sApertureData->allocatedMask & (1 << handle))) {\n      return TCLStatus::InvalidArg;\n   }\n\n   TinyHeap_Free(virt_cast<TinyHeap *>(virt_addrof(sApertureData->trackingHeap)),\n                 sApertureData->apertures[handle].apertureMemory);\n\n   sApertureData->apertures[handle].apertureMemory = nullptr;\n   sApertureData->apertures[handle].surface = phys_addr { 0 };\n   sApertureData->allocatedMask &= ~(1 << handle);\n   return TCLStatus::OK;\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseApertures()\n{\n   // Create a heap\n   TinyHeap_Setup(virt_cast<TinyHeap *>(virt_addrof(sApertureData->trackingHeap)),\n                  sApertureData->trackingHeap.size(),\n                  nullptr,\n                  0x02000000);\n\n   sApertureData->baseVirtualAddress = OSPhysicalToEffectiveUncached(phys_addr { 0xD0000000 });\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerApertureSymbols()\n{\n   RegisterFunctionExport(TCLAllocTilingAperture);\n   RegisterFunctionExport(TCLFreeTilingAperture);\n\n   RegisterDataInternal(sApertureData);\n}\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_aperture.h",
    "content": "#pragma once\n#include \"tcl_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::tcl\n{\n\nusing TCLApertureHandle = uint32_t;\n\nTCLStatus\nTCLAllocTilingAperture(phys_addr addr,\n                       uint32_t pitch,\n                       uint32_t height,\n                       uint32_t bytesPerPixel,\n                       uint32_t tileMode,\n                       uint32_t endian,\n                       virt_ptr<TCLApertureHandle> outHandle,\n                       virt_ptr<virt_addr> outAddress);\n\nTCLStatus\nTCLFreeTilingAperture(TCLApertureHandle handle);\n\nnamespace internal\n{\n\nvoid\ninitialiseApertures();\n\n} // namespace internal\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_driver.cpp",
    "content": "#include \"tcl.h\"\n#include \"tcl_driver.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_driver.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::tcl\n{\n\nstruct StaticDriverData\n{\n   be2_val<BOOL> isInitialised;\n   be2_val<BOOL> hangWait;\n};\n\nstatic virt_ptr<StaticDriverData>\nsDriverData = nullptr;\n\nTCLStatus\nTCLGetInfo(virt_ptr<TCLInfo> info)\n{\n   if (!internal::tclDriverInitialised()) {\n      return TCLStatus::NotInitialised;\n   }\n\n   info->asicType = TCLAsicType::Unknown5;\n   info->chipRevision = TCLChipRevision::Unknown78;\n   info->cpMicrocodeVersion = TCLCpMicrocodeVersion::Unknown16;\n   info->quadPipes = 4u;\n   info->parameterCacheWidth = 16u;\n   info->rb = 2u;\n   info->addrLibHandle = virt_cast<void *>(virt_addr { 0xF00DBAAD });\n   info->sclk = 549999755u;\n   return TCLStatus::OK;\n}\n\nvoid\nTCLSetHangWait(BOOL hangWait)\n{\n   sDriverData->hangWait = hangWait;\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseTclDriver()\n{\n   // TODO: OSDriver_Register\n   sDriverData->isInitialised = true;\n}\n\nbool\ntclDriverInitialised()\n{\n   return sDriverData->isInitialised ? true : false;\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerDriverSymbols()\n{\n   RegisterFunctionExport(TCLGetInfo);\n   RegisterFunctionExport(TCLSetHangWait);\n\n   RegisterDataInternal(sDriverData);\n}\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_driver.h",
    "content": "#pragma once\n#include \"tcl_enum.h\"\n\nnamespace cafe::tcl\n{\n\nstruct TCLInfo\n{\n   be2_val<TCLAsicType> asicType;\n   be2_val<TCLChipRevision> chipRevision;\n   be2_val<TCLCpMicrocodeVersion> cpMicrocodeVersion;\n   be2_val<uint32_t> quadPipes;\n   be2_val<uint32_t> parameterCacheWidth;\n   be2_val<uint32_t> rb;\n   be2_virt_ptr<void> addrLibHandle;\n   be2_val<uint32_t> sclk;\n};\n\nTCLStatus\nTCLGetInfo(virt_ptr<TCLInfo> info);\n\nvoid\nTCLSetHangWait(BOOL hangWait);\n\nnamespace internal\n{\n\nvoid\ninitialiseTclDriver();\n\nbool\ntclDriverInitialised();\n\n} // namespace internal\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_enum.h",
    "content": "#ifndef TCL_ENUM_H\n#define TCL_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(tcl)\n\nENUM_BEG(TCLAsicType, uint32_t)\n   ENUM_VALUE(Unknown5,                         5)\nENUM_END(TCLAsicType)\n\nENUM_BEG(TCLChipRevision, uint32_t)\n   ENUM_VALUE(Unknown78,                        78)\nENUM_END(TCLChipRevision)\n\nENUM_BEG(TCLCpMicrocodeVersion, uint32_t)\n   ENUM_VALUE(Unknown16,                        16)\nENUM_END(TCLCpMicrocodeVersion)\n\nENUM_BEG(TCLRegisterID, uint32_t)\n   ENUM_VALUE(Max,                              0x10000)\nENUM_END(TCLRegisterID)\n\nENUM_BEG(TCLStatus, int32_t)\n   ENUM_VALUE(OK,                               0)\n   ENUM_VALUE(InvalidArg,                       3)\n   ENUM_VALUE(NotInitialised,                   5)\n   ENUM_VALUE(OutOfMemory,                      6)\n   ENUM_VALUE(Timeout,                          22)\nENUM_END(TCLStatus)\n\nFLAGS_BEG(TCLSubmitFlags, uint32_t)\n   FLAGS_VALUE(None,                            0)\n   FLAGS_VALUE(NoWriteConfirmTimestamp,         1 << 21)\n   FLAGS_VALUE(NoCacheFlush,                    1 << 27)\n   FLAGS_VALUE(CacheFlushInvalidate,            1 << 28)\n   FLAGS_VALUE(UpdateTimestamp,                 1 << 29)\nFLAGS_END(TCLSubmitFlags)\n\nENUM_BEG(TCLTimestampID, int32_t)\n   ENUM_VALUE(CPSubmitted,                      0)\n   ENUM_VALUE(CPRetired,                        1)\n   ENUM_VALUE(DMAERetired,                      2)\nENUM_END(TCLTimestampID)\n\nENUM_NAMESPACE_EXIT(tcl)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef TCL_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_interrupthandler.cpp",
    "content": "#include \"tcl.h\"\n#include \"tcl_enum.h\"\n#include \"tcl_interrupthandler.h\"\n\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_interrupts.h\"\n\n#include <libcpu/be2_atomic.h>\n#include <libcpu/be2_struct.h>\n#include <libgpu/gpu_ih.h>\n\nnamespace cafe::tcl\n{\n\nusing namespace cafe::coreinit;\n\nconstexpr auto MaxNumInterruptTypes = 256u;\nconstexpr auto MaxNumHandlersPerInterrupt = 4u;\n\nstruct RegisteredInterruptHandler\n{\n   be2_val<TCLInterruptHandlerFn> callback;\n   be2_virt_ptr<void> userData;\n};\n\nstruct StaticInterruptHandlerData\n{\n   be2_array<be2_array<RegisteredInterruptHandler, MaxNumHandlersPerInterrupt>, MaxNumInterruptTypes> handlers;\n\n   be2_atomic<uint32_t> interruptCount;\n   be2_atomic<uint32_t> interruptEntriesRead;\n   std::array<be2_atomic<uint32_t>, 0x100> interruptTypeCount;\n};\n\nstatic virt_ptr<StaticInterruptHandlerData>\nsInterruptHandlerData = nullptr;\n\nstatic OSUserInterruptHandler\nsInterruptHandler = nullptr;\n\nTCLStatus\nTCLIHEnableInterrupt(TCLInterruptType type,\n                     BOOL enable)\n{\n   auto cp_int_cntl = latte::CP_INT_CNTL::get(0);\n   switch (type) {\n   case TCLInterruptType::UNKNOWN_192:\n      cp_int_cntl = cp_int_cntl.UNK17_INT_ENABLE(true);\n      break;\n   case TCLInterruptType::CP_RB:\n      cp_int_cntl = cp_int_cntl.RB_INT_ENABLE(true);\n      break;\n   case TCLInterruptType::CP_IB1:\n      cp_int_cntl = cp_int_cntl.IB1_INT_ENABLE(true);\n      break;\n   case TCLInterruptType::CP_IB2:\n      cp_int_cntl = cp_int_cntl.IB2_INT_ENABLE(true);\n      break;\n   case TCLInterruptType::CP_RESERVED_BITS:\n      cp_int_cntl = cp_int_cntl.RESERVED_BITS_EXCEPTION(true);\n      break;\n   case TCLInterruptType::CP_EOP_EVENT:\n      cp_int_cntl = cp_int_cntl.TIME_STAMP_INT_ENABLE(true);\n      break;\n   case TCLInterruptType::SCRATCH:\n      cp_int_cntl = cp_int_cntl.SCRATCH_INT_ENABLE(true);\n      break;\n   case TCLInterruptType::CP_BAD_OPCODE:\n      cp_int_cntl = cp_int_cntl.BAD_OPCODE_EXCEPTION(true);\n      break;\n   case TCLInterruptType::CP_CTX_EMPTY:\n      cp_int_cntl = cp_int_cntl.CNTX_EMPTY_INT_ENABLE(true);\n      break;\n   case TCLInterruptType::CP_CTX_BUSY:\n      cp_int_cntl = cp_int_cntl.CNTX_BUSY_INT_ENABLE(true);\n      break;\n   case TCLInterruptType::DMA_CTX_EMPTY:\n      // dma_int_cntl.CTXEMPTY_INT_ENABLE(true);\n   case TCLInterruptType::DMA_TRAP_EVENT:\n      // dma_int_cntl.TRAP_ENABLE(true);\n   case TCLInterruptType::DMA_SEM_INCOMPLETE:\n      // dma_int_cntl.SEM_INCOMPLETE_INT_ENABLE(true);\n   case TCLInterruptType::DMA_SEM_WAIT:\n      // dma_int_cntl.SEM_WAIT_INT_ENABLE(true);\n   default:\n      return TCLStatus::InvalidArg;\n   }\n\n   if (enable) {\n      gpu::ih::enable(cp_int_cntl);\n   } else {\n      gpu::ih::disable(cp_int_cntl);\n   }\n\n   return TCLStatus::OK;\n}\n\nTCLStatus\nTCLIHRegister(TCLInterruptType type,\n              TCLInterruptHandlerFn callback,\n              virt_ptr<void> userData)\n{\n   if (type >= sInterruptHandlerData->handlers.size()) {\n      return TCLStatus::InvalidArg;\n   }\n\n   for (auto &handler : sInterruptHandlerData->handlers[type]) {\n      if (!handler.callback) {\n         handler.callback = callback;\n         handler.userData = userData;\n         return TCLStatus::OK;\n      }\n   }\n\n   return TCLStatus::OutOfMemory;\n}\n\nTCLStatus\nTCLIHUnregister(TCLInterruptType type,\n                TCLInterruptHandlerFn callback,\n                virt_ptr<void> userData)\n{\n   if (type >= sInterruptHandlerData->handlers.size()) {\n      return TCLStatus::InvalidArg;\n   }\n\n   for (auto &handler : sInterruptHandlerData->handlers[type]) {\n      if (handler.callback == callback && handler.userData == userData) {\n         handler.callback = nullptr;\n         handler.userData = nullptr;\n         return TCLStatus::OK;\n      }\n   }\n\n   return TCLStatus::InvalidArg;\n}\n\nTCLStatus\nTCLGetInterruptCount(TCLInterruptType type,\n                     BOOL resetCount,\n                     virt_ptr<uint32_t> count)\n{\n   if (type == 0x100) {\n      if (resetCount) {\n         *count = sInterruptHandlerData->interruptCount.fetch_and(0);\n      } else {\n         *count = sInterruptHandlerData->interruptCount.load();\n      }\n\n      return TCLStatus::OK;\n   }\n\n   if (type == 0x101) {\n      if (resetCount) {\n         *count = sInterruptHandlerData->interruptEntriesRead.fetch_and(0);\n      } else {\n         *count = sInterruptHandlerData->interruptEntriesRead.load();\n      }\n\n      return TCLStatus::OK;\n   }\n\n   if (type < sInterruptHandlerData->handlers.size()) {\n      if (resetCount) {\n         *count = sInterruptHandlerData->interruptTypeCount[type].fetch_and(0);\n      } else {\n         *count = sInterruptHandlerData->interruptTypeCount[type].load();\n      }\n\n      return TCLStatus::OK;\n   }\n\n   return TCLStatus::InvalidArg;\n}\n\nnamespace internal\n{\n\nstatic void\ngpuInterruptHandler(OSInterruptType type,\n                    virt_ptr<OSContext> interruptedContext)\n{\n   auto interruptEntry = StackObject<TCLInterruptEntry> { };\n   auto entries = gpu::ih::read();\n\n   sInterruptHandlerData->interruptCount.fetch_add(1);\n\n   for (auto &entry : entries) {\n      interruptEntry->interruptSourceID = static_cast<TCLInterruptType>(entry.word0);\n      interruptEntry->reservedWord1 = entry.word1;\n      interruptEntry->interruptSourceData = entry.word2;\n      interruptEntry->reservedWord3 = entry.word3;\n      sInterruptHandlerData->interruptEntriesRead.fetch_add(1);\n\n      // Dispatch interrupt entry to registered handlers\n      for (auto &handler : sInterruptHandlerData->handlers[entry.word0]) {\n         if (handler.callback) {\n            cafe::invoke(cpu::this_core::state(),\n                         handler.callback,\n                         interruptEntry,\n                         handler.userData);\n         }\n      }\n   }\n}\n\nvoid\ninitialiseInterruptHandler()\n{\n   OSSetInterruptHandler(OSInterruptType::Gpu7, sInterruptHandler);\n   gpu::ih::setInterruptCallback([]() { cpu::interrupt(1, cpu::GPU7_INTERRUPT); });\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerInterruptHandlerSymbols()\n{\n   RegisterFunctionExport(TCLIHEnableInterrupt);\n   RegisterFunctionExport(TCLIHRegister);\n   RegisterFunctionExport(TCLIHUnregister);\n   RegisterFunctionExport(TCLGetInterruptCount);\n\n   RegisterDataInternal(sInterruptHandlerData);\n   RegisterFunctionInternal(internal::gpuInterruptHandler,\n                            sInterruptHandler);\n}\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_interrupthandler.h",
    "content": "#pragma once\n#include \"tcl_enum.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libgpu/latte/latte_enum_cp.h>\n\nnamespace cafe::tcl\n{\n\nusing TCLInterruptType = latte::CP_INT_SRC_ID;\n\nstruct TCLInterruptEntry\n{\n   be2_val<TCLInterruptType> interruptSourceID;\n   be2_val<uint32_t> reservedWord1;\n   be2_val<uint32_t> interruptSourceData;\n   be2_val<uint32_t> reservedWord3;\n};\nCHECK_OFFSET(TCLInterruptEntry, 0x00, interruptSourceID);\nCHECK_OFFSET(TCLInterruptEntry, 0x04, reservedWord1);\nCHECK_OFFSET(TCLInterruptEntry, 0x08, interruptSourceData);\nCHECK_OFFSET(TCLInterruptEntry, 0x0C, reservedWord3);\nCHECK_SIZE(TCLInterruptEntry, 0x10);\n\nusing TCLInterruptHandlerFn = virt_func_ptr<\n   void (virt_ptr<TCLInterruptEntry> interruptEntry, virt_ptr<void> userData)\n>;\n\nTCLStatus\nTCLIHEnableInterrupt(TCLInterruptType type,\n                     BOOL enable);\n\nTCLStatus\nTCLIHRegister(TCLInterruptType type,\n              TCLInterruptHandlerFn callback,\n              virt_ptr<void> userData);\n\nTCLStatus\nTCLIHUnregister(TCLInterruptType type,\n                TCLInterruptHandlerFn callback,\n                virt_ptr<void> userData);\n\nTCLStatus\nTCLGetInterruptCount(TCLInterruptType type,\n                     BOOL resetCount,\n                     virt_ptr<uint32_t> count);\n\nnamespace internal\n{\n\nvoid\ninitialiseInterruptHandler();\n\n} // namespace internal\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_register.cpp",
    "content": "#include \"tcl.h\"\n#include \"tcl_register.h\"\n\nnamespace cafe::tcl\n{\n\nTCLStatus\nTCLReadRegister(TCLRegisterID id,\n                virt_ptr<uint32_t> outValue)\n{\n   if (id >= TCLRegisterID::Max) {\n      return TCLStatus::InvalidArg;\n   }\n\n   return TCLStatus::NotInitialised;\n}\n\nTCLStatus\nTCLWriteRegister(TCLRegisterID id,\n                 uint32_t value)\n{\n   if (id >= TCLRegisterID::Max) {\n      return TCLStatus::InvalidArg;\n   }\n\n   return TCLStatus::NotInitialised;\n}\n\nvoid\nLibrary::registerRegisterSymbols()\n{\n   RegisterFunctionExport(TCLReadRegister);\n   RegisterFunctionExport(TCLWriteRegister);\n}\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_register.h",
    "content": "#pragma once\n#include \"tcl_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::tcl\n{\n\nTCLStatus\nTCLReadRegister(TCLRegisterID id,\n                virt_ptr<uint32_t> outValue);\n\nTCLStatus\nTCLWriteRegister(TCLRegisterID id,\n                 uint32_t value);\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_ring.cpp",
    "content": "#include \"tcl.h\"\n#include \"tcl_interrupthandler.h\"\n#include \"tcl_ring.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/libraries/coreinit/coreinit_event.h\"\n#include \"cafe/libraries/coreinit/coreinit_memory.h\"\n#include \"cafe/libraries/coreinit/coreinit_time.h\"\n\n#include <libcpu/be2_atomic.h>\n#include <libgpu/gpu_ringbuffer.h>\n#include <libgpu/latte/latte_pm4.h>\n#include <libgpu/latte/latte_pm4_commands.h>\n#include <libgpu/latte/latte_pm4_sizer.h>\n#include <libgpu/latte/latte_pm4_writer.h>\n\nnamespace cafe::tcl\n{\n\nusing namespace latte;\nusing namespace cafe::coreinit;\n\nstruct StaticRingData\n{\n   be2_array<char, 32> waitCpRetireTimestampEventName;\n   be2_struct<OSEvent> waitCpRetireTimestampEvent;\n   be2_struct<OSEvent> waitDmaeRetireTimestampEvent;\n\n   be2_atomic<uint64_t> cpSubmitTimestamp;\n   be2_atomic<uint64_t> cpRetireTimestamp;\n   be2_atomic<uint64_t> dmaeRetireTimestamp;\n\n   //! Physical address of the above cpRetireTimestamp variable.\n   be2_val<phys_addr> cpRetireTimestampAddress;\n};\n\nstatic virt_ptr<StaticRingData>\nsRingData;\n\nTCLStatus\nTCLReadTimestamp(TCLTimestampID id,\n                 virt_ptr<TCLTimestamp> outValue)\n{\n   switch (id) {\n   case TCLTimestampID::CPSubmitted:\n      *outValue = sRingData->cpSubmitTimestamp.load() - 1;\n      break;\n   case TCLTimestampID::CPRetired:\n      *outValue = sRingData->cpRetireTimestamp.load();\n      break;\n   case TCLTimestampID::DMAERetired:\n      *outValue = sRingData->dmaeRetireTimestamp.load();\n      break;\n   default:\n      return TCLStatus::InvalidArg;\n   }\n\n   return TCLStatus::OK;\n}\n\nTCLStatus\nTCLWaitTimestamp(TCLTimestampID id,\n                 TCLTimestamp timestamp,\n                 OSTime timeout)\n{\n   auto endTime = OSGetSystemTime() + timeout;\n   if (id == TCLTimestampID::CPRetired) {\n      while (timestamp > sRingData->cpRetireTimestamp.load()) {\n         if (OSGetSystemTime() >= endTime) {\n            return TCLStatus::Timeout;\n         }\n\n         OSWaitEventWithTimeout(virt_addrof(sRingData->waitCpRetireTimestampEvent),\n                                500000);\n      }\n   } else {\n      return TCLStatus::InvalidArg;\n   }\n\n   return TCLStatus::OK;\n}\n\nTCLStatus\nTCLSubmit(phys_ptr<void> buffer,\n          uint32_t bufferSize,\n          virt_ptr<TCLSubmitFlags> submitFlags,\n          virt_ptr<TCLTimestamp> lastSubmittedTimestamp)\n{\n   return TCLStatus::NotInitialised;\n}\n\ntemplate<typename Type>\nvoid\nwritePM4(uint32_t *buffer,\n         uint32_t &bufferPosWords,\n         const Type &command)\n{\n   // Remove const for the .serialise function\n   auto &cmd = const_cast<Type &>(command);\n\n   // Calculate the total size this object will be\n   latte::pm4::PacketSizer sizer;\n   cmd.serialise(sizer);\n   auto totalSize = sizer.getSize() + 1;\n\n   // Serialize the packet to the given buffer\n   auto writer = latte::pm4::PacketWriter {\n      buffer,\n      bufferPosWords,\n      Type::Opcode,\n      totalSize\n   };\n   cmd.serialise(writer);\n}\n\nstatic TCLTimestamp\ninsertRetiredTimestamp(bool cacheFlushTimestamp,\n                       bool cacheFlushInvalidate,\n                       bool writeConfirm)\n{\n   auto submitTimestamp = sRingData->cpSubmitTimestamp.fetch_add(1);\n   auto eventType = VGT_EVENT_TYPE::BOTTOM_OF_PIPE_TS;\n   if (cacheFlushTimestamp) {\n      if (cacheFlushInvalidate) {\n         eventType = VGT_EVENT_TYPE::CACHE_FLUSH_AND_INV_TS_EVENT;\n      } else {\n         eventType = VGT_EVENT_TYPE::CACHE_FLUSH_TS;\n      }\n   }\n\n   // Submit an EVENT_WRITE_EOP to update the retire timestamp\n   std::array<uint32_t, 6> buffer;\n   auto bufferPos = 0u;\n   writePM4(buffer.data(), bufferPos,\n      pm4::EventWriteEOP {\n         VGT_EVENT_INITIATOR::get(0)\n            .EVENT_TYPE(eventType)\n            .EVENT_INDEX(VGT_EVENT_INDEX::TS),\n         pm4::EW_ADDR_LO::get(0)\n            .ADDR_LO(static_cast<uint32_t>(sRingData->cpRetireTimestampAddress) >> 2)\n            .ENDIAN_SWAP(CB_ENDIAN::SWAP_8IN64),\n         pm4::EWP_ADDR_HI::get(0)\n            .DATA_SEL(pm4::EWP_DATA_64)\n            .INT_SEL(writeConfirm ? pm4::EWP_INT_WRITE_CONFIRM : pm4::EWP_INT_NONE),\n         static_cast<uint32_t>(submitTimestamp & 0xFFFFFFFF),\n         static_cast<uint32_t>(submitTimestamp >> 32)\n      });\n   gpu::ringbuffer::write({ buffer.data(), bufferPos });\n   return submitTimestamp;\n}\n\nTCLStatus\nTCLSubmitToRing(virt_ptr<uint32_t> buffer,\n                uint32_t numWords,\n                virt_ptr<TCLSubmitFlags> submitFlags,\n                virt_ptr<TCLTimestamp> lastSubmittedTimestamp)\n{\n   auto flags = TCLSubmitFlags::None;\n   auto submitTimestamp = TCLTimestamp { 0 };\n   if (submitFlags) {\n      flags = *submitFlags;\n   }\n\n   gpu::ringbuffer::write({ buffer.getRawPointer(), numWords });\n\n   if (flags & TCLSubmitFlags::UpdateTimestamp) {\n      submitTimestamp =\n         insertRetiredTimestamp(!(flags & TCLSubmitFlags::NoCacheFlush),\n                                !!(flags & TCLSubmitFlags::CacheFlushInvalidate),\n                                !(flags & TCLSubmitFlags::NoWriteConfirmTimestamp));\n   } else {\n      submitTimestamp = sRingData->cpSubmitTimestamp.load();\n   }\n\n   if (lastSubmittedTimestamp) {\n      *lastSubmittedTimestamp = submitTimestamp;\n   }\n\n   return TCLStatus::OK;\n}\n\nnamespace internal\n{\n\nstatic TCLInterruptHandlerFn sCpEopEventCallback = nullptr;\n\nstatic void\ncpEopEventCallback(virt_ptr<TCLInterruptEntry> interruptEntry,\n                   virt_ptr<void> userData)\n{\n   OSSignalEvent(virt_addrof(sRingData->waitCpRetireTimestampEvent));\n}\n\nvoid\ninitialiseRing()\n{\n   // TODO: be2_atomic virt_addrof\n   sRingData->cpRetireTimestampAddress =\n      coreinit::OSEffectiveToPhysical(cpu::translate(&sRingData->cpRetireTimestamp));\n\n   sRingData->waitCpRetireTimestampEventName = \"{ GX2 CP Retire }\";\n   OSInitEventEx(virt_addrof(sRingData->waitCpRetireTimestampEvent),\n                 FALSE,\n                 OSEventMode::AutoReset,\n                 virt_addrof(sRingData->waitCpRetireTimestampEventName));\n\n   TCLIHRegister(TCLInterruptType::CP_EOP_EVENT, sCpEopEventCallback, nullptr);\n\n   // tcl.rpl also register these but only do a COSWarn in the callbacks, nothing interesting.\n   // TCLIHRegister(TCLInterruptType::CP_RESERVED_BITS, sCpReservedBitsException, nullptr);\n   // TCLIHRegister(TCLInterruptType::CP_BAD_OPCODE, sCpBadOpcodeException, nullptr);\n}\n\n} // namespace internal\n\nvoid\nLibrary::registerRingSymbols()\n{\n   RegisterFunctionExport(TCLReadTimestamp);\n   RegisterFunctionExport(TCLWaitTimestamp);\n   RegisterFunctionExport(TCLSubmit);\n   RegisterFunctionExport(TCLSubmitToRing);\n\n   RegisterDataInternal(sRingData);\n   RegisterFunctionInternal(internal::cpEopEventCallback,\n                            internal::sCpEopEventCallback);\n\n}\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tcl/tcl_ring.h",
    "content": "#pragma once\n#include \"tcl_enum.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_time.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::tcl\n{\n\nusing TCLTimestamp = uint64_t;\n\nTCLStatus\nTCLReadTimestamp(TCLTimestampID id,\n                 virt_ptr<TCLTimestamp> outValue);\n\nTCLStatus\nTCLWaitTimestamp(TCLTimestampID id,\n                 TCLTimestamp timestamp,\n                 coreinit::OSTime timeout);\n\nTCLStatus\nTCLSubmit(phys_ptr<void> buffer,\n          uint32_t bufferSize,\n          virt_ptr<TCLSubmitFlags> submitFlags,\n          virt_ptr<TCLTimestamp> outLastSubmittedTimestamp);\n\nTCLStatus\nTCLSubmitToRing(virt_ptr<uint32_t> buffer,\n                uint32_t numWords,\n                virt_ptr<TCLSubmitFlags> submitFlags,\n                virt_ptr<TCLTimestamp> outLastSubmittedTimestamp);\n\nnamespace internal\n{\n\nvoid\ninitialiseRing();\n\n} // namespace internal\n\n} // namespace cafe::tcl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tve/tve.cpp",
    "content": "#include \"tve.h\"\n\nnamespace cafe::tve\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::tve\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/tve/tve.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::tve\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::tve, \"tve.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::tve\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/uac/uac.cpp",
    "content": "#include \"uac.h\"\n\nnamespace cafe::uac\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::uac\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/uac/uac.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::uac\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::uac, \"uac.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::uac\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/uac_rpl/uac_rpl.cpp",
    "content": "#include \"uac_rpl.h\"\n\nnamespace cafe::uac_rpl\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::uac_rpl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/uac_rpl/uac_rpl.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::uac_rpl\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::uac_rpl, \"uac_rpl.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::uac_rpl\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/usb_mic/usb_mic.cpp",
    "content": "#include \"usb_mic.h\"\n\nnamespace cafe::usb_mic\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::usb_mic\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/usb_mic/usb_mic.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::usb_mic\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::usb_mic, \"usb_mic.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::usb_mic\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/uvc/uvc.cpp",
    "content": "#include \"uvc.h\"\n\nnamespace cafe::uvc\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::uvc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/uvc/uvc.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::uvc\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::uvc, \"uvc.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::uvc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/uvd/uvd.cpp",
    "content": "#include \"uvd.h\"\n\nnamespace cafe::uvd\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n}\n\n} // namespace cafe::uvd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/uvd/uvd.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::uvd\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::uvd, \"uvd.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n};\n\n} // namespace cafe::uvd\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpad/vpad.cpp",
    "content": "#include \"vpad.h\"\n\nnamespace cafe::vpad\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerControllerSymbols();\n   registerGyroSymbols();\n   registerMotorSymbols();\n}\n\n} // namespace cafe::vpad\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpad/vpad.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::vpad\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::vpad, \"vpad.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerControllerSymbols();\n   void registerGyroSymbols();\n   void registerMotorSymbols();\n};\n\n} // namespace cafe::vpad\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpad/vpad_controller.cpp",
    "content": "#include \"vpad.h\"\n#include \"vpad_controller.h\"\n#include \"input/input.h\"\n\n#include <vector>\n#include <utility>\n\nnamespace cafe::vpad\n{\n\nstruct StaticControllerData\n{\n   be2_array<VPADTouchCalibrationParam, 2> calibrationParam {\n      VPADTouchCalibrationParam {\n         uint16_t { 0 }, uint16_t { 0 }, 1280.0f / 4096.0f, 720.0f / 4096.0f\n      },\n      VPADTouchCalibrationParam {\n         uint16_t { 0 }, uint16_t { 0 }, 1280.0f / 4096.0f, 720.0f / 4096.0f\n      }\n   };\n   be2_val<VPADButtons> lastButtonState = VPADButtons { 0 };\n};\n\nstatic virt_ptr<StaticControllerData> sControllerData = nullptr;\n\nvoid\nVPADInit()\n{\n}\n\nvoid\nVPADSetAccParam(VPADChan chan,\n                float unk1,\n                float unk2)\n{\n}\n\nvoid\nVPADSetBtnRepeat(VPADChan chan,\n                 float unk1,\n                 float unk2)\n{\n}\n\n\n/**\n * VPADRead\n *\n * \\return\n * Returns the number of samples read.\n */\nuint32_t\nVPADRead(VPADChan chan,\n         virt_ptr<VPADStatus> buffers,\n         uint32_t bufferCount,\n         virt_ptr<VPADReadError> outError)\n{\n   if (bufferCount < 1) {\n      if (outError) {\n         *outError = VPADReadError::NoSamples;\n      }\n\n      return 0;\n   }\n\n   if (chan >= VPADChan::Max) {\n      if (outError) {\n         *outError = VPADReadError::InvalidController;\n      }\n\n      return 0;\n   }\n\n   memset(virt_addrof(buffers[0]).get(), 0, sizeof(VPADStatus));\n\n   auto status = input::vpad::Status { };\n   input::sampleVpadController(static_cast<int>(chan), status);\n\n   if (!status.connected) {\n      if (outError) {\n         *outError = VPADReadError::InvalidController;\n      }\n\n      return 0;\n   }\n\n   auto &buffer = buffers[0];\n   auto hold = VPADButtons { 0 };\n   if (status.buttons.sync) { hold |= VPADButtons::Sync; }\n   if (status.buttons.home) { hold |= VPADButtons::Home; }\n   if (status.buttons.minus) { hold |= VPADButtons::Minus; }\n   if (status.buttons.plus) { hold |= VPADButtons::Plus; }\n   if (status.buttons.r) { hold |= VPADButtons::R; }\n   if (status.buttons.l) { hold |= VPADButtons::L; }\n   if (status.buttons.zr) { hold |= VPADButtons::ZR; }\n   if (status.buttons.zl) { hold |= VPADButtons::ZL; }\n   if (status.buttons.down) { hold |= VPADButtons::Down; }\n   if (status.buttons.up) { hold |= VPADButtons::Up; }\n   if (status.buttons.right) { hold |= VPADButtons::Right; }\n   if (status.buttons.left) { hold |= VPADButtons::Left; }\n   if (status.buttons.x) { hold |= VPADButtons::X; }\n   if (status.buttons.y) { hold |= VPADButtons::Y; }\n   if (status.buttons.b) { hold |= VPADButtons::B; }\n   if (status.buttons.a) { hold |= VPADButtons::A; }\n   if (status.buttons.stickR) { hold |= VPADButtons::StickR; }\n   if (status.buttons.stickL) { hold |= VPADButtons::StickL; }\n\n   buffer.hold = hold;\n   buffer.trigger = (~sControllerData->lastButtonState) & hold;\n   buffer.release = sControllerData->lastButtonState & (~hold);\n   sControllerData->lastButtonState = hold;\n\n   // Update axis state\n   buffer.leftStick.x = status.leftStickX;\n   buffer.leftStick.y = status.leftStickY;\n   buffer.rightStick.x = status.rightStickX;\n   buffer.rightStick.y = status.rightStickY;\n\n   // Update touchpad data\n   if (status.touch.down) {\n      buffer.tpNormal.touched = uint16_t { 1 };\n      buffer.tpNormal.x = static_cast<uint16_t>(status.touch.x * 4096.0f);\n      buffer.tpNormal.y = static_cast<uint16_t>((1.0f - status.touch.y) * 4096.0f);\n      buffer.tpNormal.validity = VPADTouchPadValidity::Valid;\n   } else {\n      buffer.tpNormal.touched = uint16_t { 0 };\n      buffer.tpNormal.validity = VPADTouchPadValidity::InvalidX | VPADTouchPadValidity::InvalidY;\n   }\n\n   // For now, lets just copy instantaneous position tpNormal to tpFiltered.\n   // My guess is that tpFiltered1/2 \"filter\" results over a period of time\n   // to allow for smoother input, due to the fact that touch screens aren't\n   // super precise and people's fingers are fat. I would guess tpFiltered1\n   // is filtered over a shorter period and tpFiltered2 over a longer period.\n   buffer.tpFiltered1 = buffer.tpNormal;\n   buffer.tpFiltered2 = buffer.tpNormal;\n\n   if (outError) {\n      *outError = VPADReadError::Success;\n   }\n\n   return 1;\n}\n\nvoid\nVPADGetTPCalibrationParam(VPADChan chan,\n                          virt_ptr<VPADTouchCalibrationParam> outParam)\n{\n   *outParam = sControllerData->calibrationParam[chan];\n}\n\nvoid\nVPADGetTPCalibratedPoint(VPADChan chan,\n                         virt_ptr<VPADTouchData> calibratedData,\n                         virt_ptr<const VPADTouchData> uncalibratedData)\n{\n   auto &calibrationParam = sControllerData->calibrationParam[chan];\n   calibratedData->touched = uncalibratedData->touched;\n   calibratedData->validity = uncalibratedData->validity;\n\n   calibratedData->x = static_cast<uint16_t>(\n      static_cast<float>(uncalibratedData->x - calibrationParam.adjustX)\n      * calibrationParam.scaleX);\n\n   calibratedData->y = static_cast<uint16_t>(\n      static_cast<float>((4096 - uncalibratedData->y) - calibrationParam.adjustY)\n      * calibrationParam.scaleY);\n}\n\nvoid\nVPADGetTPCalibratedPointEx(VPADChan chan,\n                           VPADTouchPadResolution tpReso,\n                           virt_ptr<VPADTouchData> calibratedData,\n                           virt_ptr<const VPADTouchData> uncalibratedData)\n{\n   auto &calibrationParam = sControllerData->calibrationParam[chan];\n   calibratedData->touched = uncalibratedData->touched;\n   calibratedData->validity = uncalibratedData->validity;\n\n   auto scaleX = 1.0f, scaleY = 1.0f;\n   if (tpReso == VPADTouchPadResolution::Tp_1920x1080) {\n      scaleX = 1920.0f / 1280.0f;\n      scaleY = 1080.0f / 720.0f;\n   } else if (tpReso == VPADTouchPadResolution::Tp_854x480) {\n      scaleX = 854.0f / 1280.0f;\n      scaleY = 480.0f / 720.0f;\n   }\n\n   calibratedData->x = static_cast<uint16_t>(\n      static_cast<float>(uncalibratedData->x - calibrationParam.adjustX)\n      * calibrationParam.scaleX * scaleX);\n\n   calibratedData->y = static_cast<uint16_t>(\n      static_cast<float>((4096 - uncalibratedData->y) - calibrationParam.adjustY)\n      * calibrationParam.scaleY * scaleY);\n}\n\nvoid\nVPADSetTPCalibrationParam(VPADChan chan,\n                          virt_ptr<const VPADTouchCalibrationParam> param)\n{\n   sControllerData->calibrationParam[chan] = *param;\n}\n\nbool\nVPADBASEGetHeadphoneStatus(VPADChan chan)\n{\n   return false;\n}\n\nvoid\nLibrary::registerControllerSymbols()\n{\n   RegisterFunctionExport(VPADInit);\n   RegisterFunctionExport(VPADSetAccParam);\n   RegisterFunctionExport(VPADSetBtnRepeat);\n   RegisterFunctionExport(VPADRead);\n   RegisterFunctionExport(VPADGetTPCalibrationParam);\n   RegisterFunctionExport(VPADGetTPCalibratedPoint);\n   RegisterFunctionExport(VPADGetTPCalibratedPointEx);\n   RegisterFunctionExport(VPADSetTPCalibrationParam);\n   RegisterFunctionExport(VPADBASEGetHeadphoneStatus);\n\n   RegisterDataInternal(sControllerData);\n}\n\n} // namespace cafe::vpad\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpad/vpad_controller.h",
    "content": "#pragma once\n#include \"vpad_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::vpad\n{\n\n/**\n * \\defgroup vpad_status VPAD Controller Status\n * \\ingroup vpad\n * @{\n */\n\nstruct VPADVec2D\n{\n   be2_val<float> x;\n   be2_val<float> y;\n};\nCHECK_OFFSET(VPADVec2D, 0x00, x);\nCHECK_OFFSET(VPADVec2D, 0x04, y);\nCHECK_SIZE(VPADVec2D, 0x08);\n\nstruct VPADVec3D\n{\n   be2_val<float> x;\n   be2_val<float> y;\n   be2_val<float> z;\n};\nCHECK_OFFSET(VPADVec3D, 0x00, x);\nCHECK_OFFSET(VPADVec3D, 0x04, y);\nCHECK_OFFSET(VPADVec3D, 0x08, z);\nCHECK_SIZE(VPADVec3D, 0x0C);\n\nstruct VPADAccStatus\n{\n   be2_struct<VPADVec3D> acc;\n   be2_val<float> magnitude;\n   be2_val<float> variation;\n   be2_struct<VPADVec2D> vertical;\n};\nCHECK_OFFSET(VPADAccStatus, 0x00, acc);\nCHECK_OFFSET(VPADAccStatus, 0x0C, magnitude);\nCHECK_OFFSET(VPADAccStatus, 0x10, variation);\nCHECK_OFFSET(VPADAccStatus, 0x14, vertical);\nCHECK_SIZE(VPADAccStatus, 0x1c);\n\nstruct VPADDirection\n{\n   be2_struct<VPADVec3D> x;\n   be2_struct<VPADVec3D> y;\n   be2_struct<VPADVec3D> z;\n};\nCHECK_OFFSET(VPADDirection, 0x00, x);\nCHECK_OFFSET(VPADDirection, 0x0C, y);\nCHECK_OFFSET(VPADDirection, 0x18, z);\nCHECK_SIZE(VPADDirection, 0x24);\n\nstruct VPADGyroStatus\n{\n   be2_val<float> unk1;\n   be2_val<float> unk2;\n   be2_val<float> unk3;\n   be2_val<float> unk4;\n   be2_val<float> unk5;\n   be2_val<float> unk6;\n};\nCHECK_OFFSET(VPADGyroStatus, 0x00, unk1);\nCHECK_OFFSET(VPADGyroStatus, 0x04, unk2);\nCHECK_OFFSET(VPADGyroStatus, 0x08, unk3);\nCHECK_OFFSET(VPADGyroStatus, 0x0C, unk4);\nCHECK_OFFSET(VPADGyroStatus, 0x10, unk5);\nCHECK_OFFSET(VPADGyroStatus, 0x14, unk6);\nCHECK_SIZE(VPADGyroStatus, 0x18);\n\nstruct VPADTouchCalibrationParam\n{\n   be2_val<uint16_t> adjustX;\n   be2_val<uint16_t> adjustY;\n   be2_val<float> scaleX;\n   be2_val<float> scaleY;\n};\nCHECK_OFFSET(VPADTouchCalibrationParam, 0x00, adjustX);\nCHECK_OFFSET(VPADTouchCalibrationParam, 0x02, adjustY);\nCHECK_OFFSET(VPADTouchCalibrationParam, 0x04, scaleX);\nCHECK_OFFSET(VPADTouchCalibrationParam, 0x08, scaleY);\nCHECK_SIZE(VPADTouchCalibrationParam, 0x0C);\n\nstruct VPADTouchData\n{\n   //! The x-coordinate of a touched point.\n   be2_val<uint16_t> x;\n\n   //! The y-coordinate of a touched point.\n   be2_val<uint16_t> y;\n\n   //! 0 if screen is not currently being touched\n   be2_val<uint16_t> touched;\n\n   //! Bitfield of #VPADTouchPadValidity to indicate how touch sample accuracy\n   be2_val<uint16_t> validity;\n};\nCHECK_OFFSET(VPADTouchData, 0x00, x);\nCHECK_OFFSET(VPADTouchData, 0x02, y);\nCHECK_OFFSET(VPADTouchData, 0x04, touched);\nCHECK_OFFSET(VPADTouchData, 0x06, validity);\nCHECK_SIZE(VPADTouchData, 0x08);\n\nstruct VPADStatus\n{\n   //! Indicates what buttons are held down\n   be2_val<VPADButtons> hold;\n\n   //! Indicates what buttons have been pressed since last sample\n   be2_val<VPADButtons> trigger;\n\n   //! Indicates what buttons have been released since last sample\n   be2_val<VPADButtons> release;\n\n   //! Position of left analog stick\n   be2_struct<VPADVec2D> leftStick;\n\n   //! Position of right analog stick\n   be2_struct<VPADVec2D> rightStick;\n\n   //! Status of DRC accelorometer\n   be2_struct<VPADAccStatus> accelorometer;\n\n   //! Status of DRC gyro\n   be2_struct<VPADGyroStatus> gyro;\n\n   UNKNOWN(0x02);\n\n   //! Current touch position on DRC\n   be2_struct<VPADTouchData> tpNormal;\n\n   //! Filtered touch position, first level of smoothing\n   be2_struct<VPADTouchData> tpFiltered1;\n\n   //! Filtered touch position, second level of smoothing\n   be2_struct<VPADTouchData> tpFiltered2;\n\n   UNKNOWN(0x28);\n\n   //! Status of DRC magnetometer\n   be2_struct<VPADVec3D> mag;\n\n   //! Current volume set by the slide control\n   be2_val<uint8_t> slideVolume;\n\n   //! Battery level of controller\n   be2_val<uint8_t> battery;\n\n   //! Status of DRC microphone\n   be2_val<uint8_t> micStatus;\n\n   //! Unknown volume related value\n   be2_val<uint8_t> slideVolumeEx;\n\n   UNKNOWN(0x07);\n};\nCHECK_OFFSET(VPADStatus, 0x00, hold);\nCHECK_OFFSET(VPADStatus, 0x04, trigger);\nCHECK_OFFSET(VPADStatus, 0x08, release);\nCHECK_OFFSET(VPADStatus, 0x0C, leftStick);\nCHECK_OFFSET(VPADStatus, 0x14, rightStick);\nCHECK_OFFSET(VPADStatus, 0x1C, accelorometer);\nCHECK_OFFSET(VPADStatus, 0x38, gyro);\nCHECK_OFFSET(VPADStatus, 0x52, tpNormal);\nCHECK_OFFSET(VPADStatus, 0x5A, tpFiltered1);\nCHECK_OFFSET(VPADStatus, 0x62, tpFiltered2);\nCHECK_OFFSET(VPADStatus, 0x94, mag);\nCHECK_OFFSET(VPADStatus, 0xA0, slideVolume);\nCHECK_OFFSET(VPADStatus, 0xA1, battery);\nCHECK_OFFSET(VPADStatus, 0xA2, micStatus);\nCHECK_OFFSET(VPADStatus, 0xA3, slideVolumeEx);\nCHECK_SIZE(VPADStatus, 0xAC);\n\nvoid\nVPADInit();\n\nvoid\nVPADSetAccParam(VPADChan chan,\n                float unk1,\n                float unk2);\n\nvoid\nVPADSetBtnRepeat(VPADChan chan,\n                 float unk1,\n                 float unk2);\n\nuint32_t\nVPADRead(VPADChan chan,\n         virt_ptr<VPADStatus> buffers,\n         uint32_t bufferCount,\n         virt_ptr<VPADReadError> outError);\n\nvoid\nVPADGetTPCalibrationParam(VPADChan chan,\n                          virt_ptr<VPADTouchCalibrationParam> outParam);\n\nvoid\nVPADGetTPCalibratedPoint(VPADChan chan,\n                         virt_ptr<VPADTouchData> calibratedData,\n                         virt_ptr<const VPADTouchData> uncalibratedData);\n\nvoid\nVPADGetTPCalibratedPointEx(VPADChan chan,\n                           VPADTouchPadResolution tpReso,\n                           virt_ptr<VPADTouchData> calibratedData,\n                           virt_ptr<const VPADTouchData> uncalibratedData);\n\nvoid\nVPADSetTPCalibrationParam(VPADChan chan,\n                          virt_ptr<const VPADTouchCalibrationParam> param);\n\nbool\nVPADBASEGetHeadphoneStatus(VPADChan chan);\n\n/** @} */\n\n} // namespace cafe::vpad\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpad/vpad_enum.h",
    "content": "#ifndef VPAD_ENUM_H\n#define VPAD_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(cafe)\nENUM_NAMESPACE_ENTER(vpad)\n\nFLAGS_BEG(VPADButtons, uint32_t)\n   FLAGS_VALUE(Sync,        1 << 0)\n   FLAGS_VALUE(Home,        1 << 1)\n   FLAGS_VALUE(Minus,       1 << 2)\n   FLAGS_VALUE(Plus,        1 << 3)\n   FLAGS_VALUE(R,           1 << 4)\n   FLAGS_VALUE(L,           1 << 5)\n   FLAGS_VALUE(ZR,          1 << 6)\n   FLAGS_VALUE(ZL,          1 << 7)\n   FLAGS_VALUE(Down,        1 << 8)\n   FLAGS_VALUE(Up,          1 << 9)\n   FLAGS_VALUE(Right,       1 << 10)\n   FLAGS_VALUE(Left,        1 << 11)\n   FLAGS_VALUE(Y,           1 << 12)\n   FLAGS_VALUE(X,           1 << 13)\n   FLAGS_VALUE(B,           1 << 14)\n   FLAGS_VALUE(A,           1 << 15)\n   FLAGS_VALUE(StickR,      1 << 17)\n   FLAGS_VALUE(StickL,      1 << 18)\nFLAGS_END(VPADButtons)\n\nENUM_BEG(VPADChan, int32_t)\n   ENUM_VALUE(Chan0,                   0)\n   ENUM_VALUE(Chan1,                   1)\n   ENUM_VALUE(Max,                     2)\nENUM_END(VPADChan)\n\nENUM_BEG(VPADTouchPadResolution, int32_t)\n   ENUM_VALUE(Tp_1920x1080,            0)\n   ENUM_VALUE(Tp_1280x720,             1)\n   ENUM_VALUE(Tp_854x480,              2)\nENUM_END(VPADTouchPadResolution)\n\nFLAGS_BEG(VPADTouchPadValidity, uint16_t)\n   //! Both X and Y touchpad positions are accurate\n   FLAGS_VALUE(Valid,       0)\n\n   //! X position is inaccurate\n   FLAGS_VALUE(InvalidX,    1 << 0)\n\n   //! Y position is inaccurate\n   FLAGS_VALUE(InvalidY,    1 << 1)\nFLAGS_END(VPADTouchPadValidity)\n\nENUM_BEG(VPADReadError, int32_t)\n   ENUM_VALUE(Success,            0)\n   ENUM_VALUE(NoSamples,         -1)\n   ENUM_VALUE(InvalidController, -2)\nENUM_END(VPADReadError)\n\nENUM_NAMESPACE_EXIT(vpad)\nENUM_NAMESPACE_EXIT(cafe)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef VPAD_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpad/vpad_gyro.cpp",
    "content": "#include \"vpad.h\"\n#include \"vpad_gyro.h\"\n\nnamespace cafe::vpad\n{\n\nfloat\nVPADIsEnableGyroAccRevise(VPADChan chan)\n{\n   return 0.0f;\n}\n\nfloat\nVPADIsEnableGyroZeroPlay(VPADChan chan)\n{\n   return 0.0f;\n}\n\nfloat\nVPADIsEnableGyroZeroDrift(VPADChan chan)\n{\n   return 0.0f;\n}\n\nfloat\nVPADIsEnableGyroDirRevise(VPADChan chan)\n{\n   return 0.0f;\n}\n\nvoid\nLibrary::registerGyroSymbols()\n{\n   RegisterFunctionExport(VPADIsEnableGyroAccRevise);\n   RegisterFunctionExport(VPADIsEnableGyroZeroPlay);\n   RegisterFunctionExport(VPADIsEnableGyroZeroDrift);\n   RegisterFunctionExport(VPADIsEnableGyroDirRevise);\n}\n\n} // namespace cafe::vpad\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpad/vpad_gyro.h",
    "content": "#pragma once\n#include \"vpad_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::vpad\n{\n\nfloat\nVPADIsEnableGyroAccRevise(VPADChan chan);\n\nfloat\nVPADIsEnableGyroZeroPlay(VPADChan chan);\n\nfloat\nVPADIsEnableGyroZeroDrift(VPADChan chan);\n\nfloat\nVPADIsEnableGyroDirRevise(VPADChan chan);\n\n} // namespace cafe::vpad\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpad/vpad_motor.cpp",
    "content": "#include \"vpad.h\"\n#include \"vpad_motor.h\"\n\nnamespace cafe::vpad\n{\n\nint32_t\nVPADControlMotor(VPADChan chan,\n                 virt_ptr<void> buffer,\n                 uint32_t size)\n{\n   return 0;\n}\n\nvoid\nVPADStopMotor(VPADChan chan)\n{\n}\n\nvoid\nLibrary::registerMotorSymbols()\n{\n   RegisterFunctionExport(VPADControlMotor);\n   RegisterFunctionExport(VPADStopMotor);\n}\n\n} // namespace cafe::vpad\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpad/vpad_motor.h",
    "content": "#pragma once\n#include \"vpad_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::vpad\n{\n\nint32_t\nVPADControlMotor(VPADChan chan,\n                 virt_ptr<void> buffer,\n                 uint32_t size);\n\nvoid\nVPADStopMotor(VPADChan chan);\n\n} // namespace cafe::vpad\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpadbase/vpadbase.cpp",
    "content": "#include \"vpadbase.h\"\n\nnamespace cafe::vpadbase\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerControllerSymbols();\n}\n\n} // namespace cafe::vpadbase\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpadbase/vpadbase.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::vpadbase\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::vpadbase, \"vpadbase.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerControllerSymbols();\n};\n\n} // namespace cafe::vpadbase\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpadbase/vpadbase_controller.cpp",
    "content": "#include \"vpadbase.h\"\n#include \"vpadbase_controller.h\"\n\n#include \"cafe/libraries/cafe_hle_stub.h\"\n\nnamespace cafe::vpadbase\n{\n\nBOOL\nVPADBASEGetHeadphoneStatus(int32_t chan)\n{\n   decaf_warn_stub();\n   return FALSE;\n}\n\nvoid\nLibrary::registerControllerSymbols()\n{\n   RegisterFunctionExport(VPADBASEGetHeadphoneStatus);\n}\n\n} // namespace cafe::vpadbase\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/vpadbase/vpadbase_controller.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::vpadbase\n{\n\nBOOL\nVPADBASEGetHeadphoneStatus(int32_t chan);\n\n} // namespace cafe::vpadbase\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/zlib125/zlib125.cpp",
    "content": "#include \"zlib125.h\"\n\nnamespace cafe::zlib125\n{\n\nstatic int32_t\nrpl_entry(coreinit::OSDynLoad_ModuleHandle moduleHandle,\n          coreinit::OSDynLoad_EntryReason reason)\n{\n   return 0;\n}\n\nvoid\nLibrary::registerSymbols()\n{\n   RegisterEntryPoint(rpl_entry);\n\n   registerZlibSymbols();\n}\n\n} // namespace cafe::zlib125\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/zlib125/zlib125.h",
    "content": "#pragma once\n#include \"cafe/libraries/cafe_hle_library.h\"\n\nnamespace cafe::zlib125\n{\n\nclass Library : public hle::Library\n{\npublic:\n   Library() :\n      hle::Library(hle::LibraryId::zlib125, \"zlib125.rpl\")\n   {\n   }\n\nprotected:\n   virtual void registerSymbols() override;\n\nprivate:\n   void registerZlibSymbols();\n};\n\n} // namespace cafe::zlib125\n"
  },
  {
    "path": "src/libdecaf/src/cafe/libraries/zlib125/zlib125_zlib.cpp",
    "content": "#include \"zlib125.h\"\n#include \"cafe/cafe_ppc_interface_invoke_guest.h\"\n#include \"cafe/libraries/coreinit/coreinit_memdefaultheap.h\"\n\n#include <common/decaf_assert.h>\n#include <libcpu/mmu.h>\n#include <zlib.h>\n\nnamespace cafe::zlib125\n{\n\nstatic std::map<uint32_t, z_stream>\ngStreamMap;\n\n// WiiU games will be using a 32bit zlib where stuff is in big endian order in memory\n// this means all structures like z_streamp have to be swapped endian to a temp structure\n\nusing zlib125_alloc_func = virt_func_ptr<\n   virt_ptr<void>(virt_ptr<void>, uint32_t, uint32_t)>;\n\nusing zlib125_free_func = virt_func_ptr<\n   void(virt_ptr<void>, virt_ptr<void>)>;\n\nstruct zlib125_stream\n{\n   be2_virt_ptr<Bytef> next_in;\n   be2_val<uint32_t> avail_in;\n   be2_val<uint32_t> total_in;\n\n   be2_virt_ptr<Bytef> next_out;\n   be2_val<uint32_t> avail_out;\n   be2_val<uint32_t> total_out;\n\n   be2_virt_ptr<char> msg;\n   be2_virt_ptr<struct internal_state> state;\n\n   be2_val<zlib125_alloc_func> zalloc;\n   be2_val<zlib125_free_func> zfree;\n   be2_virt_ptr<void> opaque;\n\n   be2_val<int32_t> data_type;\n   be2_val<uint32_t> adler;\n   be2_val<uint32_t> reserved;\n};\nCHECK_OFFSET(zlib125_stream, 0x00, next_in);\nCHECK_OFFSET(zlib125_stream, 0x04, avail_in);\nCHECK_OFFSET(zlib125_stream, 0x08, total_in);\nCHECK_OFFSET(zlib125_stream, 0x0C, next_out);\nCHECK_OFFSET(zlib125_stream, 0x10, avail_out);\nCHECK_OFFSET(zlib125_stream, 0x14, total_out);\nCHECK_OFFSET(zlib125_stream, 0x18, msg);\nCHECK_OFFSET(zlib125_stream, 0x1C, state);\nCHECK_OFFSET(zlib125_stream, 0x20, zalloc);\nCHECK_OFFSET(zlib125_stream, 0x24, zfree);\nCHECK_OFFSET(zlib125_stream, 0x28, opaque);\nCHECK_OFFSET(zlib125_stream, 0x2C, data_type);\nCHECK_OFFSET(zlib125_stream, 0x30, adler);\nCHECK_OFFSET(zlib125_stream, 0x34, reserved);\nCHECK_SIZE(zlib125_stream, 0x38);\n\nstruct zlib125_header\n{\n   be2_val<int32_t> text;\n   be2_val<uint32_t> time;\n   be2_val<int32_t> xflags;\n   be2_val<int32_t> os;\n   be2_virt_ptr<uint8_t> extra;\n   be2_val<uint32_t> extra_len;\n   be2_val<uint32_t> extra_max;\n   be2_virt_ptr<uint8_t> name;\n   be2_val<uint32_t> name_max;\n   be2_virt_ptr<uint8_t> comment;\n   be2_val<uint32_t> comm_max;\n   be2_val<int32_t> hcrc;\n   be2_val<int32_t> done;\n};\nCHECK_OFFSET(zlib125_header, 0x00, text);\nCHECK_OFFSET(zlib125_header, 0x04, time);\nCHECK_OFFSET(zlib125_header, 0x08, xflags);\nCHECK_OFFSET(zlib125_header, 0x0C, os);\nCHECK_OFFSET(zlib125_header, 0x10, extra);\nCHECK_OFFSET(zlib125_header, 0x14, extra_len);\nCHECK_OFFSET(zlib125_header, 0x18, extra_max);\nCHECK_OFFSET(zlib125_header, 0x1C, name);\nCHECK_OFFSET(zlib125_header, 0x20, name_max);\nCHECK_OFFSET(zlib125_header, 0x24, comment);\nCHECK_OFFSET(zlib125_header, 0x28, comm_max);\nCHECK_OFFSET(zlib125_header, 0x2C, hcrc);\nCHECK_OFFSET(zlib125_header, 0x30, done);\nCHECK_SIZE(zlib125_header, 0x34);\n\nstatic void *\nzlibAllocWrapper(void *opaque,\n                 unsigned items,\n                 unsigned size)\n{\n   auto wstrm = reinterpret_cast<zlib125_stream *>(opaque);\n\n   if (wstrm->zalloc) {\n      auto ptr = cafe::invoke(cpu::this_core::state(),\n                              wstrm->zalloc,\n                              wstrm->opaque,\n                              static_cast<uint32_t>(items),\n                              static_cast<uint32_t>(size));\n      return ptr.get();\n   } else {\n      auto ptr = coreinit::MEMAllocFromDefaultHeap(items * size);\n      return ptr.get();\n   }\n}\n\nstatic void\nzlibFreeWrapper(void *opaque,\n                void *address)\n{\n   auto wstrm = reinterpret_cast<zlib125_stream *>(opaque);\n   auto ptr = virt_cast<void *>(cpu::translate(address));\n\n   if (wstrm->zfree) {\n      cafe::invoke(cpu::this_core::state(),\n                   wstrm->zfree,\n                   wstrm->opaque,\n                   ptr);\n   } else {\n      coreinit::MEMFreeToDefaultHeap(ptr);\n   }\n}\n\nz_stream *\ngetZStream(virt_ptr<zlib125_stream> in)\n{\n   auto zstream = &gStreamMap[virt_cast<virt_addr>(in).getAddress()];\n   zstream->opaque = in.get();\n   zstream->zalloc = &zlibAllocWrapper;\n   zstream->zfree = &zlibFreeWrapper;\n   return zstream;\n}\n\nvoid\neraseZStream(virt_ptr<zlib125_stream> in)\n{\n   gStreamMap.erase(virt_cast<virt_addr>(in).getAddress());\n}\n\nstatic int\nzlib125_deflate(virt_ptr<zlib125_stream> wstrm,\n                int32_t flush)\n{\n   auto zstrm = getZStream(wstrm);\n   zstrm->next_in = wstrm->next_in.get();\n   zstrm->avail_in = wstrm->avail_in;\n   zstrm->total_in = wstrm->total_in;\n\n   zstrm->next_out = wstrm->next_out.get();\n   zstrm->avail_out = wstrm->avail_out;\n   zstrm->total_out = wstrm->total_out;\n\n   zstrm->data_type = wstrm->data_type;\n   zstrm->adler = wstrm->adler;\n\n   auto result = deflate(zstrm, flush);\n\n   wstrm->next_in = virt_cast<Bytef *>(cpu::translate(zstrm->next_in));\n   wstrm->avail_in = static_cast<uint32_t>(zstrm->avail_in);\n   wstrm->total_in = static_cast<uint32_t>(zstrm->total_in);\n\n   wstrm->next_out = virt_cast<Bytef *>(cpu::translate(zstrm->next_out));\n   wstrm->avail_out = static_cast<uint32_t>(zstrm->avail_out);\n   wstrm->total_out = static_cast<uint32_t>(zstrm->total_out);\n\n   wstrm->data_type = static_cast<int32_t>(zstrm->data_type);\n   wstrm->adler = static_cast<uint32_t>(zstrm->adler);\n\n   return result;\n}\n\nstatic int\nzlib125_deflateInit_(virt_ptr<zlib125_stream> wstrm,\n                     int32_t level,\n                     virt_ptr<const char> version,\n                     int32_t stream_size)\n{\n   decaf_check(sizeof(zlib125_stream) == stream_size);\n\n   auto zstrm = getZStream(wstrm);\n   auto result = deflateInit_(zstrm, level,\n                              version.get(), sizeof(z_stream));\n\n   wstrm->msg = nullptr;\n   return result;\n}\n\nstatic int\nzlib125_deflateInit2_(virt_ptr<zlib125_stream> wstrm,\n                      int32_t level,\n                      int32_t method,\n                      int32_t windowBits,\n                      int32_t memLevel,\n                      int32_t strategy,\n                      virt_ptr<const char> version,\n                      int32_t stream_size)\n{\n   decaf_check(sizeof(zlib125_stream) == stream_size);\n\n   auto zstrm = getZStream(wstrm);\n   auto result = deflateInit2_(zstrm, level, method, windowBits, memLevel,\n                               strategy, version.get(),\n                               sizeof(z_stream));\n\n   wstrm->msg = nullptr;\n   return result;\n}\n\nstatic uint32_t\nzlib125_deflateBound(virt_ptr<zlib125_stream> wstrm,\n                     uint32_t sourceLen)\n{\n   auto zstrm = getZStream(wstrm);\n   return deflateBound(zstrm, sourceLen);\n}\n\nstatic int\nzlib125_deflateReset(virt_ptr<zlib125_stream> wstrm)\n{\n   auto zstrm = getZStream(wstrm);\n   return deflateReset(zstrm);\n}\n\nstatic int\nzlib125_deflateEnd(virt_ptr<zlib125_stream> wstrm)\n{\n   auto zstrm = getZStream(wstrm);\n   return deflateEnd(zstrm);\n}\n\nstatic int\nzlib125_inflate(virt_ptr<zlib125_stream> wstrm,\n                int32_t flush)\n{\n   auto zstrm = getZStream(wstrm);\n   zstrm->next_in = wstrm->next_in.get();\n   zstrm->avail_in = wstrm->avail_in;\n   zstrm->total_in = wstrm->total_in;\n\n   zstrm->next_out = wstrm->next_out.get();\n   zstrm->avail_out = wstrm->avail_out;\n   zstrm->total_out = wstrm->total_out;\n\n   zstrm->data_type = wstrm->data_type;\n   zstrm->adler = wstrm->adler;\n\n   auto result = inflate(zstrm, flush);\n\n   wstrm->next_in = virt_cast<Bytef *>(cpu::translate(zstrm->next_in));\n   wstrm->avail_in = static_cast<uint32_t>(zstrm->avail_in);\n   wstrm->total_in = static_cast<uint32_t>(zstrm->total_in);\n\n   wstrm->next_out = virt_cast<Bytef *>(cpu::translate(zstrm->next_out));\n   wstrm->avail_out = static_cast<uint32_t>(zstrm->avail_out);\n   wstrm->total_out = static_cast<uint32_t>(zstrm->total_out);\n\n   wstrm->data_type = static_cast<int32_t>(zstrm->data_type);\n   wstrm->adler = static_cast<uint32_t>(zstrm->adler);\n\n   return result;\n}\n\nstatic int\nzlib125_inflateInit_(virt_ptr<zlib125_stream> wstrm,\n                     virt_ptr<const char> version,\n                     int32_t stream_size)\n{\n   decaf_check(sizeof(zlib125_stream) == stream_size);\n\n   auto zstrm = getZStream(wstrm);\n   auto result = inflateInit_(zstrm, version.get(),\n                              sizeof(z_stream));\n\n   wstrm->msg = nullptr;\n   return result;\n}\n\nstatic int\nzlib125_inflateInit2_(virt_ptr<zlib125_stream> wstrm,\n                      int32_t windowBits,\n                      virt_ptr<const char> version,\n                      int32_t stream_size)\n{\n   decaf_check(sizeof(zlib125_stream) == stream_size);\n\n   auto zstrm = getZStream(wstrm);\n   auto result = inflateInit2_(zstrm, windowBits, version.get(),\n                               sizeof(z_stream));\n\n   wstrm->msg = nullptr;\n   return result;\n}\n\nstatic int\nzlib125_inflateReset(virt_ptr<zlib125_stream> wstrm)\n{\n   auto zstrm = getZStream(wstrm);\n   return inflateReset(zstrm);\n}\n\nstatic int\nzlib125_inflateReset2(virt_ptr<zlib125_stream> wstrm,\n                      int32_t windowBits)\n{\n   auto zstrm = getZStream(wstrm);\n   return inflateReset2(zstrm, windowBits);\n}\n\nstatic int\nzlib125_inflateEnd(virt_ptr<zlib125_stream> wstrm)\n{\n   auto zstrm = getZStream(wstrm);\n   auto result = inflateEnd(zstrm);\n   eraseZStream(wstrm);\n   return result;\n}\n\nstatic uint32_t\nzlib125_adler32(uint32_t adler,\n                virt_ptr<const uint8_t> buf,\n                uint32_t len)\n{\n   return static_cast<uint32_t>(adler32(adler, buf.get(), len));\n}\n\nstatic uint32_t\nzlib125_crc32(uint32_t crc,\n              virt_ptr<const uint8_t> buf,\n              uint32_t len)\n{\n   return static_cast<uint32_t>(crc32(crc, buf.get(), len));\n}\n\nstatic int\nzlib125_compress(virt_ptr<uint8_t> dest,\n                 virt_ptr<uint32_t> destLen,\n                 virt_ptr<const uint8_t> source,\n                 uint32_t sourceLen)\n{\n   auto realDestLen = static_cast<uLong>(*destLen);\n   auto result = compress(dest.get(), &realDestLen,\n                          source.get(), sourceLen);\n   *destLen = static_cast<uint32_t>(realDestLen);\n   return result;\n}\n\nstatic uint32_t\nzlib125_compressBound(uint32_t sourceLen)\n{\n   return static_cast<uint32_t>(compressBound(sourceLen));\n}\n\nstatic int\nzlib125_uncompress(virt_ptr<uint8_t> dest,\n                   virt_ptr<uint32_t> destLen,\n                   virt_ptr<const uint8_t> source,\n                   uint32_t sourceLen)\n{\n   auto realDestLen = static_cast<uLong>(*destLen);\n   auto result = uncompress(dest.get(), &realDestLen,\n                            source.get(), sourceLen);\n   *destLen = static_cast<uint32_t>(realDestLen);\n   return result;\n}\n\nstatic uint32_t\nzlib125_zlibCompileFlags()\n{\n   return static_cast<uint32_t>(zlibCompileFlags());\n}\n\nvoid\nLibrary::registerZlibSymbols()\n{\n   RegisterFunctionExportName(\"adler32\", zlib125_adler32);\n   RegisterFunctionExportName(\"crc32\", zlib125_crc32);\n   RegisterFunctionExportName(\"deflate\", zlib125_deflate);\n   RegisterFunctionExportName(\"deflateInit_\", zlib125_deflateInit_);\n   RegisterFunctionExportName(\"deflateInit2_\", zlib125_deflateInit2_);\n   RegisterFunctionExportName(\"deflateBound\", zlib125_deflateBound);\n   RegisterFunctionExportName(\"deflateReset\", zlib125_deflateReset);\n   RegisterFunctionExportName(\"deflateEnd\", zlib125_deflateEnd);\n   RegisterFunctionExportName(\"inflate\", zlib125_inflate);\n   RegisterFunctionExportName(\"inflateInit_\", zlib125_inflateInit_);\n   RegisterFunctionExportName(\"inflateInit2_\", zlib125_inflateInit2_);\n   RegisterFunctionExportName(\"inflateReset\", zlib125_inflateReset);\n   RegisterFunctionExportName(\"inflateReset2\", zlib125_inflateReset2);\n   RegisterFunctionExportName(\"inflateEnd\", zlib125_inflateEnd);\n   RegisterFunctionExportName(\"compress\", zlib125_compress);\n   RegisterFunctionExportName(\"compressBound\", zlib125_compressBound);\n   RegisterFunctionExportName(\"uncompress\", zlib125_uncompress);\n   RegisterFunctionExportName(\"zlibCompileFlags\", zlib125_zlibCompileFlags);\n}\n\n} // namespace cafe::zlib125\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_basics.cpp",
    "content": "#include \"cafe_loader_basics.h\"\n#include \"cafe_loader_bounce.h\"\n#include \"cafe_loader_elffile.h\"\n#include \"cafe_loader_error.h\"\n#include \"cafe_loader_globals.h\"\n#include \"cafe_loader_heap.h\"\n#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_loaded_rpl.h\"\n#include \"cafe_loader_zlib.h\"\n\n#include \"cafe/cafe_tinyheap.h\"\n#include \"cafe/cafe_stackobject.h\"\n\n#include <array>\n#include <common/strutils.h>\n#include <libcpu/be2_struct.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::loader::internal\n{\n\nconstexpr auto MaxSDKVersion = 21301u;\nconstexpr auto MinSDKVersion = 20500u;\n\nstatic virt_ptr<rpl::SectionHeader>\ngetSectionHeader(virt_ptr<LOADED_RPL> rpl,\n                 virt_ptr<rpl::SectionHeader> sectionHeaderBuffer,\n                 uint32_t idx)\n{\n   auto base = virt_cast<virt_addr>(sectionHeaderBuffer);\n   auto offset = idx * rpl->elfHeader.shentsize;\n   return virt_cast<rpl::SectionHeader *>(base + offset);\n}\n\nstatic virt_ptr<rpl::SectionHeader>\ngetSectionHeader(virt_ptr<LOADED_RPL> rpl,\n                 uint32_t idx)\n{\n   return getSectionHeader(rpl,\n                           virt_cast<rpl::SectionHeader *>(rpl->sectionHeaderBuffer),\n                           idx);\n}\n\nstatic int32_t\nLiInitBufferTracking(LiBasicsLoadArgs *loadArgs)\n{\n   virt_ptr<void> allocPtr;\n   uint32_t allocSize;\n   uint32_t largestFree;\n   auto error = LiCacheLineCorrectAllocEx(loadArgs->readHeapTracking,\n                                          align_up(loadArgs->pathNameLen + 1, 4),\n                                          4,\n                                          &allocPtr,\n                                          1,\n                                          &allocSize,\n                                          &largestFree,\n                                          loadArgs->fileType);\n   if (error != 0) {\n      return error;\n   }\n\n   auto rpl = loadArgs->loadedRpl;\n   rpl->pathBuffer = allocPtr;\n   rpl->pathBufferSize = allocSize;\n   string_copy(virt_cast<char *>(rpl->pathBuffer).get(),\n               rpl->pathBufferSize,\n               loadArgs->pathName.get(),\n               loadArgs->pathNameLen + 1);\n\n   rpl->upcomingBufferNumber = 1u;\n   rpl->lastChunkBuffer = loadArgs->chunkBuffer;\n   rpl->fileOffset = loadArgs->fileOffset;\n   rpl->upcomingFileOffset = loadArgs->chunkBufferSize;\n   rpl->totalBytesRead = loadArgs->chunkBufferSize;\n   rpl->upid = loadArgs->upid;\n   rpl->fileType = loadArgs->fileType;\n   rpl->virtualFileBaseOffset = loadArgs->chunkBufferSize;\n\n   if (loadArgs->chunkBufferSize == 0x400000) {\n      error = LiRefillUpcomingBounceBuffer(rpl, 2);\n   } else {\n      LiInitBuffer(false);\n   }\n\n   if (error != 0 && rpl->pathBuffer) {\n      LiCacheLineCorrectFreeEx(loadArgs->readHeapTracking, rpl->pathBuffer, rpl->pathBufferSize);\n   }\n\n   return error;\n}\n\nstatic int32_t\nLiCheckFileBounds(virt_ptr<LOADED_RPL> rpl)\n{\n   auto shBase = virt_cast<virt_addr>(rpl->sectionHeaderBuffer);\n   auto dataMin = 0xFFFFFFFFu;\n   auto dataMax = 0u;\n\n   auto readMin = 0xFFFFFFFFu;\n   auto readMax = 0u;\n\n   auto textMin = 0xFFFFFFFFu;\n   auto textMax = 0u;\n\n   auto tempMin = 0xFFFFFFFFu;\n   auto tempMax = 0u;\n\n   for (auto i = 0u; i < rpl->elfHeader.shnum; ++i) {\n      auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize);\n      if (sectionHeader->size == 0 ||\n          sectionHeader->type == rpl::SHT_RPL_FILEINFO ||\n          sectionHeader->type == rpl::SHT_RPL_IMPORTS ||\n          sectionHeader->type == rpl::SHT_RPL_CRCS ||\n          sectionHeader->type == rpl::SHT_NOBITS) {\n         continue;\n      }\n\n      if ((sectionHeader->flags & rpl::SHF_EXECINSTR) &&\n          sectionHeader->type != rpl::SHT_RPL_EXPORTS) {\n         textMin = std::min<uint32_t>(textMin, sectionHeader->offset);\n         textMax = std::max<uint32_t>(textMax, sectionHeader->offset + sectionHeader->size);\n      } else {\n         if (sectionHeader->flags & rpl::SHF_ALLOC) {\n            if (sectionHeader->flags & rpl::SHF_WRITE) {\n               dataMin = std::min<uint32_t>(dataMin, sectionHeader->offset);\n               dataMax = std::max<uint32_t>(dataMax, sectionHeader->offset + sectionHeader->size);\n            } else {\n               readMin = std::min<uint32_t>(readMin, sectionHeader->offset);\n               readMax = std::max<uint32_t>(readMax, sectionHeader->offset + sectionHeader->size);\n            }\n         } else {\n            tempMin = std::min<uint32_t>(tempMin, sectionHeader->offset);\n            tempMax = std::max<uint32_t>(tempMax, sectionHeader->offset + sectionHeader->size);\n         }\n      }\n   }\n\n   if (dataMin == 0xFFFFFFFFu) {\n      dataMin = (rpl->elfHeader.shnum * rpl->elfHeader.shentsize) + rpl->elfHeader.shoff;\n      dataMax = dataMin;\n   }\n\n   if (readMin == 0xFFFFFFFFu) {\n      readMin = dataMax;\n      readMax = dataMax;\n   }\n\n   if (textMin == 0xFFFFFFFFu) {\n      textMin = readMax;\n      textMax = readMax;\n   }\n\n   if (tempMin == 0xFFFFFFFFu) {\n      tempMin = textMax;\n      tempMax = textMax;\n   }\n\n   if (dataMin < rpl->elfHeader.shoff) {\n      Loader_ReportError(\"*** SecHrs, FileInfo, or CRCs in bad spot in file. Return %d.\",\n                         Error::CheckFileBoundsFailed);\n      goto error;\n   }\n\n   // Data\n   if (dataMin > dataMax) {\n      Loader_ReportError(\"*** DataMin > DataMax. break.\");\n      goto error;\n   }\n\n   if (dataMin > readMin) {\n      Loader_ReportError(\"*** DataMin > ReadMin. break.\");\n      goto error;\n   }\n\n   if (dataMax > readMin) {\n      Loader_ReportError(\"*** DataMax > ReadMin. break.\");\n      goto error;\n   }\n\n   // Read\n   if (readMin > readMax) {\n      Loader_ReportError(\"*** ReadMin > ReadMax. break.\");\n      goto error;\n   }\n\n   if (readMin > textMin) {\n      Loader_ReportError(\"*** ReadMin > TextMin. break.\");\n      goto error;\n   }\n\n   if (readMax > textMin) {\n      Loader_ReportError(\"*** ReadMax > TextMin. break.\");\n      goto error;\n   }\n\n   // Text\n   if (textMin > textMax) {\n      Loader_ReportError(\"*** TextMin > TextMax. break.\");\n      goto error;\n   }\n\n   if (textMin > tempMin) {\n      Loader_ReportError(\"*** TextMin > TempMin. break.\");\n      goto error;\n   }\n\n   if (textMax > tempMin) {\n      Loader_ReportError(\"*** TextMax > TempMin. break.\");\n      goto error;\n   }\n\n   // Temp\n   if (tempMin > tempMax) {\n      Loader_ReportError(\"*** TempMin > TempMax. break.\");\n      goto error;\n   }\n\n   return 0;\n\nerror:\n   LiSetFatalError(0x18729B, rpl->fileType, 1, \"LiCheckFileBounds\", 0x247);\n   return Error::CheckFileBoundsFailed;\n}\n\nint32_t\nLiLoadRPLBasics(virt_ptr<char> moduleName,\n                uint32_t moduleNameLen,\n                virt_ptr<void> chunkBuffer,\n                virt_ptr<TinyHeap> codeHeapTracking,\n                virt_ptr<TinyHeap> dataHeapTracking,\n                bool allocModuleName,\n                uint32_t r9,\n                virt_ptr<LOADED_RPL> *outLoadedRpl,\n                LiBasicsLoadArgs *loadArgs,\n                uint32_t arg_C)\n{\n   struct LoadAttemptErrorData\n   {\n      int32_t error;\n      ios::mcp::MCPFileType fileType;\n      uint32_t fatalErr;\n      std::string fatalFunction;\n      uint32_t fatalLine;\n      uint32_t fatalMsgType;\n   };\n\n   std::array<LoadAttemptErrorData, 3> loadAttemptErrors;\n   auto globals = getGlobalStorage();\n   auto loadAttempt = int32_t { 0 };\n   auto chunkReadSize = uint32_t { 0 };\n   auto error = int32_t { 0 };\n\n   while (true) {\n      error = LiWaitOneChunk(&chunkReadSize, loadArgs->pathName.get(), loadArgs->fileType);\n      if (error == 0) {\n         break;\n      }\n\n      if (loadAttempt < 2) {\n         if (LiGetFatalError()) {\n            auto &attemptErrors = loadAttemptErrors[loadAttempt];\n            attemptErrors.error = error;\n            attemptErrors.fileType = loadArgs->fileType;\n            attemptErrors.fatalErr = LiGetFatalError();\n            attemptErrors.fatalFunction = LiGetFatalFunction();\n            attemptErrors.fatalLine = LiGetFatalLine();\n            attemptErrors.fatalMsgType = LiGetFatalMsgType();\n            LiResetFatalError();\n         } else {\n            loadAttemptErrors[loadAttempt].error = error;\n         }\n\n         if (loadAttempt == 0) {\n            if (loadArgs->fileType != ios::mcp::MCPFileType::CafeOS) {\n               loadArgs->fileType = ios::mcp::MCPFileType::CafeOS;\n            } else {\n               loadArgs->fileType = ios::mcp::MCPFileType::ProcessCode;\n            }\n         } else {\n            loadArgs->fileType = ios::mcp::MCPFileType::SharedDataCode;\n         }\n\n         LiInitBuffer(false);\n         chunkReadSize = 0;\n         loadArgs->chunkBufferSize = 0u;\n\n         auto outChunkBufferSize = uint32_t { 0 };\n         auto outChunkBuffer = virt_ptr<void> { nullptr };\n         error = LiBounceOneChunk(loadArgs->pathName.get(),\n                                  loadArgs->fileType,\n                                  loadArgs->upid,\n                                  &outChunkBufferSize,\n                                  loadArgs->fileOffset,\n                                  1,\n                                  &outChunkBuffer);\n         loadArgs->chunkBuffer = outChunkBuffer;\n         loadArgs->chunkBufferSize = outChunkBufferSize;\n         ++loadAttempt;\n      }\n\n      if (error != 0) {\n         Loader_ReportError(\"***Loader failure {} first time, {} second time and {} third time. loading \\\"{}\\\".\",\n                            loadAttemptErrors[0].error,\n                            loadAttemptErrors[1].error,\n                            error,\n                            loadArgs->pathName.get());\n         return error;\n      }\n   }\n\n   loadArgs->chunkBufferSize = chunkReadSize;\n   LiCheckAndHandleInterrupts();\n\n   // Load and validate the ELF header\n   auto filePhStride = uint32_t { 0 };\n   auto fileShStride = uint32_t { 0 };\n   auto fileElfHeader = virt_ptr<rpl::Header> { nullptr };\n   auto fileSectionHeaders = virt_ptr<rpl::SectionHeader> { nullptr };\n   error = ELFFILE_ValidateAndPrepareMinELF(chunkBuffer,\n                                            chunkReadSize,\n                                            &fileElfHeader,\n                                            &fileSectionHeaders,\n                                            &fileShStride,\n                                            &filePhStride);\n   if (error) {\n      Loader_ReportError(\"*** Failed ELF file checks (err=0x{:08X}\", error);\n      LiSetFatalError(0x18729B, loadArgs->fileType, 1, \"LiLoadRPLBasics\", 0x325);\n      return error;\n   }\n\n   // Check that this ELF looks like a Wii U Cafe RPL\n   if (fileElfHeader->fileClass != rpl::ELFCLASS32 ||\n       fileElfHeader->encoding != rpl::ELFDATA2MSB ||\n       fileElfHeader->abi != rpl::EABI_CAFE ||\n       fileElfHeader->elfVersion > rpl::EV_CURRENT ||\n       fileElfHeader->machine != rpl::EM_PPC ||\n       fileElfHeader->version != 1 ||\n       fileElfHeader->shnum < 2) {\n      return -470025;\n   }\n\n   // Initialise temporary RPL basics\n   auto tmpLoadedRpl = StackObject<LOADED_RPL> { };\n   auto rpl = virt_ptr<LOADED_RPL> { tmpLoadedRpl };\n\n   std::memset(rpl.get(), 0, sizeof(LOADED_RPL));\n   if (r9 == 0) {\n      rpl->globals = getGlobalStorage();\n   }\n\n   std::memcpy(virt_addrof(rpl->elfHeader).get(),\n               fileElfHeader.get(),\n               sizeof(rpl::Header));\n\n   // Check some offsets are valid\n   if (!rpl->elfHeader.shentsize) {\n      rpl->elfHeader.shentsize = static_cast<uint16_t>(sizeof(rpl::SectionHeader));\n   }\n\n   if (rpl->elfHeader.shoff >= chunkReadSize ||\n       ((rpl->elfHeader.shnum - 1) * rpl->elfHeader.shentsize) + rpl->elfHeader.shoff >= chunkReadSize) {\n      LiSetFatalError(0x18729B, loadArgs->fileType, 1, \"LiLoadRPLBasics\", 0x33F);\n      return -470077;\n   }\n\n   auto shRplCrcs = getSectionHeader(rpl, fileSectionHeaders, rpl->elfHeader.shnum - 2);\n   if (shRplCrcs->offset >= chunkReadSize ||\n       shRplCrcs->offset + shRplCrcs->size >= chunkReadSize) {\n      LiSetFatalError(0x18729B, loadArgs->fileType, 1, \"LiLoadRPLBasics\", 0x348);\n      return -470077;\n   }\n\n   auto shRplFileInfo = getSectionHeader(rpl, fileSectionHeaders, rpl->elfHeader.shnum - 1);\n   if (shRplFileInfo->offset + shRplFileInfo->size >= chunkReadSize) {\n      LiSetFatalError(0x18729B, loadArgs->fileType, 1, \"LiLoadRPLBasics\", 0x351);\n      return -470078;\n   }\n\n   // Check RPL file info\n   if (shRplFileInfo->type != rpl::SHT_RPL_FILEINFO ||\n       shRplFileInfo->flags & rpl::SHF_DEFLATED) {\n      Loader_ReportError(\"***shnum-1 section type = 0x{:08X}, flags=0x{:08X}\",\n                         shRplFileInfo->type, shRplFileInfo->flags);\n      LiSetFatalError(0x18729B, loadArgs->fileType, 1, \"LiLoadRPLBasics\", 0x35A);\n      return -470082;\n   }\n\n   auto fileInfo = virt_cast<rpl::RPLFileInfo_v4_2 *>(virt_cast<virt_addr>(fileElfHeader) + shRplFileInfo->offset);\n   if (fileInfo->version < rpl::RPLFileInfo_v4_2::Version) {\n      Loader_ReportError(\"*** COS requires that {} be built with at least SDK {}.{:02}.{}, it was built with an older SDK\",\n                         moduleName,\n                         2, 5, 0);\n      LiSetFatalError(0x187298, loadArgs->fileType, 1, \"LiLoadRPLBasics\", 0x38B);\n      return -470062;\n   }\n\n   if (fileInfo->minVersion < MinSDKVersion || fileInfo->minVersion > MaxSDKVersion) {\n      auto major = fileInfo->minVersion / 10000;\n      auto minor = (fileInfo->minVersion % 10000) / 100;\n      auto patch = fileInfo->minVersion % 100;\n      Loader_ReportError(\"*** COS requires that {} be built with at least SDK {}.{:02}.{}, it was built with SDK {}.{:02}.{}\",\n                         moduleName,\n                         2, 5, 0,\n                         major, minor, patch);\n      LiSetFatalError(0x187298, loadArgs->fileType, 1, \"LiLoadRPLBasics\", 0x38B);\n      return -470062;\n   }\n\n   auto allocPtr = virt_ptr<void> { nullptr };\n   auto allocSize = uint32_t { 0 };\n   auto largestFree = uint32_t { 0 };\n   auto sectionCrcs = virt_ptr<uint32_t> { nullptr };\n   auto fileInfoCrc = uint32_t { 0 };\n\n   if (fileInfo->textSize) {\n      error = LiCacheLineCorrectAllocEx(codeHeapTracking,\n                                        fileInfo->textSize,\n                                        fileInfo->textAlign,\n                                        &allocPtr,\n                                        0,\n                                        &allocSize,\n                                        &largestFree,\n                                        loadArgs->fileType);\n      if (error != 0) {\n         Loader_ReportError(\"***Could not allocate uncompressed text ({}) in {} heap \\\"{}\\\";  (needed {}, available {}).\",\n                            fileInfo->textSize,\n                            r9 ? \"shared code\" : \"code\",\n                            moduleName,\n                            fileInfo->textSize,\n                            largestFree);\n         goto lblError;\n      }\n\n      rpl->textBuffer = allocPtr;\n      rpl->textBufferSize = allocSize;\n   }\n\n   error = LiCacheLineCorrectAllocEx(dataHeapTracking,\n                                     sizeof(LOADED_RPL),\n                                     4,\n                                     &allocPtr,\n                                     0,\n                                     &allocSize,\n                                     &largestFree,\n                                     loadArgs->fileType);\n   if (error != 0) {\n      Loader_ReportError(\"*** Failed {} alloc (err=0x{:08X});  (needed {}, available {})\",\n                         r9 ? \"readheap\" : \"workarea\",\n                         error,\n                         sizeof(LOADED_RPL),\n                         largestFree);\n      goto lblError;\n   }\n\n   rpl = virt_cast<LOADED_RPL *>(allocPtr);\n   std::memcpy(rpl.get(),\n               tmpLoadedRpl.get(),\n               sizeof(LOADED_RPL));\n\n   rpl->selfBufferSize = allocSize;\n   loadArgs->loadedRpl = rpl;\n\n   error = LiInitBufferTracking(loadArgs);\n   if (error != 0) {\n      goto lblError;\n   }\n\n   error = LiCacheLineCorrectAllocEx(dataHeapTracking,\n                                     rpl->elfHeader.shnum * 8,\n                                     4,\n                                     &allocPtr,\n                                     1,\n                                     &allocSize,\n                                     &largestFree,\n                                     rpl->fileType);\n   if (error != 0) {\n      Loader_ReportError(\"***Allocate Error {}, Failed to allocate {} bytes for section addresses;  (needed {}, available {}).\",\n                         error,\n                         rpl->elfHeader.shnum * 8,\n                         rpl->sectionAddressBufferSize,\n                         largestFree);\n      goto lblError;\n   }\n\n   rpl->sectionAddressBuffer = virt_cast<virt_addr *>(allocPtr);\n   rpl->sectionAddressBufferSize = allocSize;\n\n   error = LiCacheLineCorrectAllocEx(dataHeapTracking,\n                                     rpl->elfHeader.shnum * rpl->elfHeader.shentsize,\n                                     4,\n                                     &allocPtr,\n                                     1,\n                                     &allocSize,\n                                     &largestFree,\n                                     rpl->fileType);\n   if (error != 0) {\n      Loader_ReportError(\"*** Could not allocate space for section headers in {} heap;  (needed {}, available {})\",\n                         r9 ? \"shared readonly\" : \"readonly\",\n                         rpl->sectionHeaderBufferSize,\n                         largestFree);\n      goto lblError;\n   }\n\n   rpl->sectionHeaderBuffer = allocPtr;\n   rpl->sectionHeaderBufferSize = allocSize;\n\n   std::memcpy(rpl->sectionHeaderBuffer.get(),\n               virt_cast<void *>(virt_cast<virt_addr>(fileElfHeader) + rpl->elfHeader.shoff).get(),\n               rpl->elfHeader.shnum * rpl->elfHeader.shentsize);\n\n   if (allocModuleName) {\n      error = LiCacheLineCorrectAllocEx(globals->processCodeHeap,\n                                        align_up(moduleNameLen, 4),\n                                        4,\n                                        &allocPtr,\n                                        1,\n                                        &allocSize,\n                                        &largestFree,\n                                        rpl->fileType);\n      if (error != 0) {\n         Loader_ReportError(\"*** Could not allocate space for module name;  (needed {}, available {})\\n\",\n                            rpl->moduleNameBufferSize,\n                            largestFree);\n         goto lblError;\n      }\n\n      rpl->moduleNameBuffer = virt_cast<char *>(allocPtr);\n      rpl->moduleNameBufferSize = allocSize;\n\n      string_copy(virt_cast<char *>(rpl->moduleNameBuffer).get(),\n                  moduleName.get(),\n                  rpl->moduleNameBufferSize);\n   } else {\n      rpl->moduleNameBuffer = moduleName;\n      rpl->moduleNameBufferSize = moduleNameLen;\n   }\n\n   rpl->moduleNameLen = moduleNameLen;\n   rpl->moduleNameBuffer[rpl->moduleNameLen] = char { 0 };\n\n   // Load SHT_RPL_CRCS\n   shRplCrcs = getSectionHeader(rpl, rpl->elfHeader.shnum - 2);\n   if (shRplCrcs->type != rpl::SHT_RPL_CRCS ||\n       (shRplCrcs->flags & rpl::SHF_DEFLATED)) {\n      Loader_ReportError(\"***shnum-2 section type = 0x{:08X}, flags=0x{:08X}\",\n                         shRplCrcs->type, shRplCrcs->flags);\n      LiSetFatalError(0x18729B, loadArgs->fileType, 1, \"LiLoadRPLBasics\", 0x403);\n      error = -470081;\n      goto lblError;\n   }\n\n   error = LiCacheLineCorrectAllocEx(globals->processCodeHeap,\n                                     shRplCrcs->size,\n                                     -static_cast<int32_t>(shRplCrcs->addralign),\n                                     &allocPtr,\n                                     1,\n                                     &allocSize,\n                                     &largestFree,\n                                     rpl->fileType);\n   if (error != 0) {\n      Loader_ReportError(\"*** Could not allocate space for CRCs;  (needed {}, available {})\",\n                         rpl->crcBufferSize, largestFree);\n      goto lblError;\n   }\n\n   rpl->crcBuffer = allocPtr;\n   rpl->crcBufferSize = allocSize;\n\n   sectionCrcs = virt_cast<uint32_t *>(rpl->crcBuffer);\n   std::memcpy(rpl->crcBuffer.get(),\n               virt_cast<void *>(virt_cast<virt_addr>(fileElfHeader) + shRplCrcs->offset).get(),\n               shRplCrcs->size);\n\n   rpl->sectionAddressBuffer[rpl->elfHeader.shnum - 2] =\n      virt_cast<virt_addr>(rpl->crcBuffer);\n\n   // Load SHT_RPL_FILEINFO\n   shRplFileInfo = getSectionHeader(rpl, rpl->elfHeader.shnum - 1);\n   if (shRplFileInfo->type != rpl::SHT_RPL_FILEINFO ||\n      (shRplFileInfo->flags & rpl::SHF_DEFLATED)) {\n      Loader_ReportError(\"***shnum-1 section type = 0x{:08X}, flags=0x{:08X}\",\n                         shRplFileInfo->type, shRplFileInfo->flags);\n      LiSetFatalError(0x18729B, loadArgs->fileType, 1, \"LiLoadRPLBasics\", 0x41A);\n      error = -470082;\n      goto lblError;\n   }\n\n   rpl->fileInfoSize = shRplFileInfo->size;\n   error = LiCacheLineCorrectAllocEx(dataHeapTracking,\n                                     shRplFileInfo->size,\n                                     shRplFileInfo->addralign,\n                                     &allocPtr,\n                                     1,\n                                     &allocSize,\n                                     &largestFree,\n                                     rpl->fileType);\n   if (error != 0) {\n      Loader_ReportError(\"*** Could not allocate space for file info;  (needed {}, available {})\",\n                         rpl->fileInfoBufferSize, largestFree);\n      goto lblError;\n   }\n\n   rpl->fileInfoBuffer = virt_cast<rpl::RPLFileInfo_v4_2 *>(allocPtr);\n   rpl->fileInfoBufferSize = allocSize;\n\n   Loader_ReportNotice(\"RPL_LAYOUT:{},FILE,start,=\\\"{}\\\"\",\n                       rpl->moduleNameBuffer,\n                       rpl->fileInfoBuffer);\n   Loader_ReportNotice(\"RPL_LAYOUT:{},FILE,end,=\\\"{}\\\"\",\n                       rpl->moduleNameBuffer,\n                       virt_cast<virt_addr>(rpl->fileInfoBuffer) + rpl->fileInfoBufferSize);\n\n   std::memcpy(rpl->fileInfoBuffer.get(),\n               virt_cast<void *>(virt_cast<virt_addr>(fileElfHeader) + shRplFileInfo->offset).get(),\n               shRplFileInfo->size);\n\n   rpl->sectionAddressBuffer[rpl->elfHeader.shnum - 1] =\n      virt_cast<virt_addr>(rpl->fileInfoBuffer);\n\n   fileInfoCrc = LiCalcCRC32(0, rpl->fileInfoBuffer, shRplFileInfo->size);\n   if (fileInfoCrc != sectionCrcs[rpl->elfHeader.shnum - 1]) {\n      Loader_ReportError(\"***FileInfo CRC failed check.\");\n      LiSetFatalError(0x18729B, loadArgs->fileType, 1, \"LiLoadRPLBasics\", 0x433);\n      error = -470083;\n      goto lblError;\n   }\n\n   rpl->fileInfoBuffer->tlsModuleIndex = int16_t { -1 };\n   error = LiCheckFileBounds(rpl);\n   if (error == 0) {\n      rpl->virtualFileBase = fileElfHeader;\n      *outLoadedRpl = rpl;\n      return error;\n   }\n\nlblError:\n   if (rpl) {\n      if (rpl->fileInfoBuffer) {\n         LiCacheLineCorrectFreeEx(dataHeapTracking,\n                                  rpl->fileInfoBuffer,\n                                  rpl->fileInfoBufferSize);\n      }\n\n      if (rpl->crcBuffer) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  rpl->crcBuffer,\n                                  rpl->crcBufferSize);\n      }\n\n      if (rpl->moduleNameBuffer) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  rpl->moduleNameBuffer,\n                                  rpl->moduleNameBufferSize);\n      }\n\n      if (rpl->sectionHeaderBuffer) {\n         LiCacheLineCorrectFreeEx(dataHeapTracking,\n                                  rpl->sectionHeaderBuffer,\n                                  rpl->sectionHeaderBufferSize);\n      }\n\n      if (rpl->sectionAddressBuffer) {\n         LiCacheLineCorrectFreeEx(dataHeapTracking,\n                                  rpl->sectionAddressBuffer,\n                                  rpl->sectionAddressBufferSize);\n      }\n\n      if (rpl->pathBuffer) {\n         LiCacheLineCorrectFreeEx(dataHeapTracking,\n                                  rpl->pathBuffer,\n                                  rpl->pathBufferSize);\n      }\n\n      if (rpl->textBuffer) {\n         LiCacheLineCorrectFreeEx(codeHeapTracking,\n                                  rpl->textBuffer,\n                                  rpl->textBufferSize);\n      }\n\n      LiCacheLineCorrectFreeEx(codeHeapTracking,\n                               rpl,\n                               rpl->selfBufferSize);\n   }\n\n   return error;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_basics.h",
    "content": "#pragma once\n#include \"cafe/cafe_tinyheap.h\"\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n#include \"ios/mcp/ios_mcp_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader\n{\n\nstruct LOADED_RPL;\n\nnamespace internal\n{\n\nstruct LiBasicsLoadArgs\n{\n   be2_val<cafe::kernel::UniqueProcessId> upid;\n   be2_virt_ptr<LOADED_RPL> loadedRpl;\n   be2_virt_ptr<TinyHeap> readHeapTracking;\n   be2_val<uint32_t> pathNameLen;\n   be2_virt_ptr<char> pathName;\n   UNKNOWN(0x4);\n   be2_val<ios::mcp::MCPFileType> fileType;\n   be2_virt_ptr<void> chunkBuffer;\n   be2_val<uint32_t> chunkBufferSize;\n   be2_val<uint32_t> fileOffset;\n};\nCHECK_OFFSET(LiBasicsLoadArgs, 0x00, upid);\nCHECK_OFFSET(LiBasicsLoadArgs, 0x04, loadedRpl);\nCHECK_OFFSET(LiBasicsLoadArgs, 0x08, readHeapTracking);\nCHECK_OFFSET(LiBasicsLoadArgs, 0x0C, pathNameLen);\nCHECK_OFFSET(LiBasicsLoadArgs, 0x10, pathName);\nCHECK_OFFSET(LiBasicsLoadArgs, 0x18, fileType);\nCHECK_OFFSET(LiBasicsLoadArgs, 0x1C, chunkBuffer);\nCHECK_OFFSET(LiBasicsLoadArgs, 0x20, chunkBufferSize);\nCHECK_OFFSET(LiBasicsLoadArgs, 0x24, fileOffset);\nCHECK_SIZE(LiBasicsLoadArgs, 0x28);\n\nint32_t\nLiLoadRPLBasics(virt_ptr<char> moduleName,\n                uint32_t moduleNameLen,\n                virt_ptr<void> chunkBuffer,\n                virt_ptr<TinyHeap> codeHeapTracking,\n                virt_ptr<TinyHeap> dataHeapTracking,\n                bool allocModuleName,\n                uint32_t r9,\n                virt_ptr<LOADED_RPL> *outLoadedRpl,\n                LiBasicsLoadArgs *loadArgs,\n                uint32_t arg_C);\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_bounce.cpp",
    "content": "#include \"cafe_loader_bounce.h\"\n#include \"cafe_loader_error.h\"\n#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_loaded_rpl.h\"\n#include \"cafe_loader_log.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/kernel/cafe_kernel_mmu.h\"\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n#include \"ios/ios_enum.h\"\n#include \"ios/fs/ios_fs_enum.h\"\n#include \"ios/mcp/ios_mcp_enum.h\"\n#include \"ios/mcp/ios_mcp_mcp_request.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libcpu/cpu_formatters.h>\n\nusing namespace ios::mcp;\nusing namespace cafe::kernel;\n\nnamespace cafe::loader::internal\n{\n\nconstexpr auto ChunkSize = 0x400000u;\nstatic bool sgFinishedLoadingBuffer = false;\nstatic MCPFileType sgFileType = MCPFileType::ProcessCode;\nstatic UniqueProcessId sgProcId = UniqueProcessId::Invalid;\nstatic uint32_t sgGotBytes = 0;\nstatic uint32_t sgTotalBytes = 0;\nstatic uint32_t sgFileOffset = 0;\nstatic uint32_t sgBufferNumber = 0;\nstatic ios::Error sgBounceError = ios::Error::OK;\nstatic std::string sgLoadName;\n\nvoid\nLiInitBuffer(bool unk)\n{\n   if (unk) {\n      sgFinishedLoadingBuffer = true;\n   }\n\n   sgFileType = MCPFileType::ProcessCode;\n   sgFileOffset = 0u;\n   sgBufferNumber = 0u;\n   sgProcId = UniqueProcessId::Invalid;\n   sgLoadName.clear();\n   sgTotalBytes = 0u;\n   sgBounceError = ios::Error::OK;\n   sgGotBytes = 0u;\n}\n\nios::Error\nLiBounceOneChunk(std::string_view name,\n                 ios::mcp::MCPFileType fileType,\n                 kernel::UniqueProcessId upid,\n                 uint32_t *outChunkBytes,\n                 uint32_t offset,\n                 uint32_t bufferNumber,\n                 virt_ptr<void> *outChunk)\n{\n   LiCheckAndHandleInterrupts();\n   auto bounceBufferMapping =\n      kernel::getVirtualMemoryMap(kernel::VirtualMemoryRegion::LoaderBounceBuffer);\n   auto bounceBufferAddr = bounceBufferMapping.vaddr;\n   if (bufferNumber != 1) {\n      bounceBufferAddr += ChunkSize;\n   }\n\n   sgLoadName = name;\n   sgFileOffset = offset;\n   sgBufferNumber = bufferNumber;\n   sgFileType = fileType;\n   sgProcId = upid;\n\n   auto error = LiLoadAsync(name,\n                            virt_cast<void *>(bounceBufferAddr),\n                            ChunkSize,\n                            offset,\n                            fileType,\n                            getRamPartitionIdFromUniqueProcessId(upid));\n   sgBounceError = error;\n   if (error < ios::Error::OK) {\n      LiSetFatalError(0x1872A7, fileType, 0, \"LiBounceOneChunk\", 131);\n      Loader_ReportError(\"***LiLoadAsync failed. err={}.\", error);\n      LiCheckAndHandleInterrupts();\n      return error;\n   }\n\n   if (outChunkBytes) {\n      *outChunkBytes = ChunkSize;\n   }\n\n   if (outChunk) {\n      *outChunk = virt_cast<void *>(bounceBufferAddr);\n   }\n\n   sgFinishedLoadingBuffer = (sgFinishedLoadingBuffer == 0) ? TRUE : FALSE;\n   LiCheckAndHandleInterrupts();\n   return ios::Error::OK;\n}\n\nios::Error\nLiWaitOneChunk(uint32_t *outBytesRead,\n               std::string_view name,\n               MCPFileType fileType)\n{\n   auto bytesRead = uint32_t { 0 };\n   auto error = LiWaitIopCompleteWithInterrupts(&bytesRead);\n   sgBounceError = error;\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   sgGotBytes = bytesRead;\n   sgTotalBytes += bytesRead;\n\n   if (outBytesRead) {\n      *outBytesRead = bytesRead;\n   }\n\n   sgFinishedLoadingBuffer = (sgFinishedLoadingBuffer == 0) ? TRUE : FALSE;\n   return ios::Error::OK;\n}\n\nint32_t\nLiCleanUpBufferAfterModuleLoaded()\n{\n   if (!sgLoadName[0] || sgFinishedLoadingBuffer) {\n      return 0;\n   }\n\n   if (sgTotalBytes & (ChunkSize - 1)) {\n      Loader_ReportError(\n         \"LiCleanUpBufferAfterModuleLoaded: {} finished loading but left load buffers in unusable state; error {} (0x{:08X}).\",\n         sgLoadName,\n         -470105, -470105);\n      LiSetFatalError(0x18729Bu, sgFileType, 1, \"LiCleanUpBufferAfterModuleLoaded\", 359);\n      return -470105;\n   }\n\n   auto bytesRead = uint32_t { 0 };\n   auto error = LiWaitOneChunk(&bytesRead,\n                               sgLoadName,\n                               sgFileType);\n   if (error) {\n      Loader_ReportError(\n         \"LiCleanUpBufferAfterModuleLoaded: Loader incorrectly calculated that {} finished loading but got error {} (0x{:08X}).\",\n         sgLoadName, error, error);\n      return error;\n   }\n\n   if (bytesRead) {\n      Loader_ReportError(\n         \"LiCleanUpBufferAfterModuleLoaded: Loader incorrectly calculated that {} finished loading; error {} (0x{:08X}).\",\n         sgLoadName, -470105, -470105);\n      LiSetFatalError(0x18729Bu, sgFileType, 1, \"LiCleanUpBufferAfterModuleLoaded\", 367);\n      return -470105;\n   }\n\n   return 0;\n}\n\nint32_t\nLiRefillUpcomingBounceBuffer(virt_ptr<LOADED_RPL> rpl,\n                             int32_t bufferNumber)\n{\n   auto chunkReadSize = uint32_t { 0 };\n   auto chunkBuffer = virt_ptr<void> { nullptr };\n   auto error =\n      LiBounceOneChunk(virt_cast<char *>(rpl->pathBuffer).get(),\n                       rpl->fileType,\n                       rpl->upid,\n                       &chunkReadSize,\n                       rpl->upcomingFileOffset,\n                       bufferNumber,\n                       &chunkBuffer);\n   rpl->chunkBuffer = chunkBuffer;\n\n   if (error != ios::Error::OK) {\n      Loader_ReportError(\n         \"***LiBounceOneChunk failed loading \\\"{}\\\" of type {} at offset 0x{:08X} err={}.\",\n         rpl->pathBuffer,\n         rpl->fileType,\n         rpl->upcomingFileOffset,\n         error);\n   }\n\n   return error;\n}\n\nvoid\nLiCloseBufferIfError()\n{\n   if (sgLoadName[0]) {\n      auto loadName = std::string_view { sgLoadName };\n\n      if (!sgFinishedLoadingBuffer) {\n         LiWaitOneChunk(NULL, loadName, sgFileType);\n      }\n\n      if (sgBounceError == ios::Error::OK) {\n         while(sgGotBytes == ChunkSize) {\n            Loader_ReportWarn(\"***Loader completely reading {} from offset 0x{:08X}.\",\n                              loadName, sgFileOffset + ChunkSize);\n\n            LiBounceOneChunk(loadName,\n                             sgFileType,\n                             sgProcId,\n                             nullptr,\n                             sgFileOffset + sgGotBytes,\n                             (sgBufferNumber == 1) ? 2 : 1,\n                             nullptr);\n            LiWaitOneChunk(NULL, loadName, sgFileType);\n         }\n      }\n   }\n\n   LiInitBuffer(false);\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_bounce.h",
    "content": "#pragma once\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n#include \"ios/ios_enum.h\"\n#include \"ios/mcp/ios_mcp_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader\n{\n\nstruct LOADED_RPL;\n\nnamespace internal\n{\n\nvoid\nLiInitBuffer(bool unk);\n\nios::Error\nLiBounceOneChunk(std::string_view name,\n                 ios::mcp::MCPFileType fileType,\n                 kernel::UniqueProcessId upid,\n                 uint32_t *outChunkBytes,\n                 uint32_t offset,\n                 uint32_t bufferNumber,\n                 virt_ptr<void> *outChunk);\n\nios::Error\nLiWaitOneChunk(uint32_t *outBytesRead,\n               std::string_view name,\n               ios::mcp::MCPFileType fileType);\n\nint32_t\nLiCleanUpBufferAfterModuleLoaded();\n\nint32_t\nLiRefillUpcomingBounceBuffer(virt_ptr<LOADED_RPL> rpl,\n                             int32_t bufferNumber);\n\nvoid\nLiCloseBufferIfError();\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_elffile.cpp",
    "content": "#include \"cafe_loader_elffile.h\"\n#include <common/byte_swap.h>\n\nnamespace cafe::loader::internal\n{\n\nstatic void\nELFFILE_SwapFileHeader(virt_ptr<rpl::Header> header)\n{\n   header->type = byte_swap(header->type);\n   header->machine = byte_swap(header->machine);\n   header->version = byte_swap(header->version);\n   header->entry = byte_swap(header->entry);\n   header->phoff = byte_swap(header->phoff);\n   header->shoff = byte_swap(header->shoff);\n   header->flags = byte_swap(header->flags);\n   header->ehsize = byte_swap(header->ehsize);\n   header->phentsize = byte_swap(header->phentsize);\n   header->phnum = byte_swap(header->phnum);\n   header->shentsize = byte_swap(header->shentsize);\n   header->shnum = byte_swap(header->shnum);\n   header->shstrndx = byte_swap(header->shstrndx);\n}\n\nstatic void\nELFFILE_SwapSectionHeader(virt_ptr<rpl::SectionHeader> header)\n{\n   header->name = byte_swap(header->name);\n   header->type = byte_swap(header->type);\n   header->flags = byte_swap(header->flags);\n   header->addr = byte_swap(header->addr);\n   header->offset = byte_swap(header->offset);\n   header->size = byte_swap(header->size);\n   header->link = byte_swap(header->link);\n   header->info = byte_swap(header->info);\n   header->addralign = byte_swap(header->addralign);\n   header->entsize = byte_swap(header->entsize);\n}\n\nint\nELFFILE_ValidateAndPrepareMinELF(virt_ptr<void> chunkBuffer,\n                                 size_t chunkSize,\n                                 virt_ptr<rpl::Header> *outHeader,\n                                 virt_ptr<rpl::SectionHeader> *outSectionHeaders,\n                                 uint32_t *outShEntSize,\n                                 uint32_t *outPhEntSize)\n{\n   const auto currentEncoding = rpl::ELFDATA2MSB;\n   auto header = virt_cast<rpl::Header *>(chunkBuffer);\n\n   if (chunkSize < 0x104) {\n      return 0xBAD00018;\n   }\n\n   if (header->magic[0] != 0x7F ||\n       header->magic[1] != 'E' ||\n       header->magic[2] != 'L' ||\n       header->magic[3] != 'F') {\n      return 0xBAD00019;\n   }\n\n   if (header->fileClass != rpl::ELFCLASS32) {\n      return 0xBAD0001A;\n   }\n\n   if (header->elfVersion > rpl::EV_CURRENT) {\n      return 0xBAD0001B;\n   }\n\n   if (header->encoding != currentEncoding) {\n      ELFFILE_SwapFileHeader(header);\n   }\n\n   if (!header->machine) {\n      return 0xBAD0001C;\n   }\n\n   if (header->version != 1) {\n      return 0xBAD0001D;\n   }\n\n   auto ehsize = static_cast<uint32_t>(header->ehsize);\n   if (ehsize) {\n      if (header->ehsize < sizeof(rpl::Header)) {\n         return 0xBAD0001E;\n      }\n   } else {\n      ehsize = static_cast<uint32_t>(sizeof(rpl::Header));\n   }\n\n   auto phoff = header->phoff;\n   if (phoff && (phoff < ehsize || phoff >= chunkSize)) {\n      return 0xBAD0001F;\n   }\n\n   auto shoff = header->shoff;\n   if (shoff && (shoff < ehsize || shoff >= chunkSize)) {\n      return 0xBAD00020;\n   }\n\n   if (header->shstrndx && header->shstrndx >= header->shnum) {\n      return 0xBAD00021;\n   }\n\n   auto phentsize = header->phentsize ?\n      static_cast<uint16_t>(header->phentsize) :\n      static_cast<uint16_t>(32);\n   if (header->phoff &&\n      (header->phoff + phentsize * header->phnum) > chunkSize) {\n      return 0xBAD00022;\n   }\n\n   auto shentsize = header->shentsize ?\n      static_cast<uint32_t>(header->shentsize) :\n      static_cast<uint32_t>(sizeof(rpl::SectionHeader));\n   if (header->shoff &&\n      (header->shoff + shentsize * header->shnum) > chunkSize) {\n      return 0xBAD00023;\n   }\n\n   if (header->encoding != currentEncoding) {\n      for (auto i = 1u; i < header->shnum; ++i) {\n         ELFFILE_SwapSectionHeader(\n            virt_cast<rpl::SectionHeader *>(\n               virt_cast<virt_addr>(chunkBuffer) + shoff + (i * shentsize)));\n      }\n   }\n\n   for (auto i = 1u; i < header->shnum; ++i) {\n      auto sectionHeader =\n         virt_cast<rpl::SectionHeader *>(\n            virt_cast<virt_addr>(chunkBuffer) + shoff + (i * shentsize));\n\n      if (sectionHeader->size &&\n          sectionHeader->type != rpl::SHT_NOBITS) {\n         if (sectionHeader->offset < ehsize) {\n            return 0xBAD00024;\n         }\n\n         if (sectionHeader->offset >= shoff &&\n             sectionHeader->offset < (shoff + header->shnum * shentsize)) {\n            return 0xBAD00027;\n         }\n      }\n   }\n\n   // TODO: loader.elf loads program headers too.\n\n   *outHeader = header;\n   *outShEntSize = shentsize;\n   *outPhEntSize = phentsize;\n   *outSectionHeaders =\n      virt_cast<rpl::SectionHeader *>(virt_cast<virt_addr>(chunkBuffer) + shoff);\n   return 0;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_elffile.h",
    "content": "#pragma once\n#include \"cafe_loader_rpl.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader::internal\n{\n\nint\nELFFILE_ValidateAndPrepareMinELF(virt_ptr<void> chunkBuffer,\n                                 size_t chunkSize,\n                                 virt_ptr<rpl::Header> *outHeader,\n                                 virt_ptr<rpl::SectionHeader> *outSectionHeaders,\n                                 uint32_t *outShEntSize,\n                                 uint32_t *outPhEntSize);\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_entry.cpp",
    "content": "#include \"cafe_loader_entry.h\"\n#include \"cafe_loader_error.h\"\n#include \"cafe_loader_globals.h\"\n#include \"cafe_loader_heap.h\"\n#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_ipcldriver.h\"\n#include \"cafe_loader_link.h\"\n#include \"cafe_loader_prep.h\"\n#include \"cafe_loader_setup.h\"\n#include \"cafe_loader_shared.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_query.h\"\n\n#include <common/strutils.h>\n#include <mutex>\n\nnamespace cafe::loader\n{\n\nstatic virt_ptr<kernel::Context> gpLoaderEntry_ProcContext = nullptr;\nstatic int32_t gpLoaderEntry_ProcConfig = -1;\nstatic LOADER_Code gpLoaderEntry_DispatchCode = LOADER_Code::Invalid;\nstatic bool gpLoaderEntry_LoaderIntsAllowed = false;\nstatic kernel::ProcessFlags gProcFlags = kernel::ProcessFlags::get(0);\nstatic uint32_t gProcTitleLoc = 0;\nstatic bool sLoaderInUserMode = true;\nstatic std::mutex sLoaderMutex;\n\nstatic int32_t\nLOADER_Entry(virt_ptr<LOADER_EntryParams> entryParams)\n{\n   auto error = 0;\n\n   if (!internal::IPCLDriver_IsInitialised()) {\n      internal::IPCLDriver_Init();\n      internal::IPCLDriver_Open();\n      internal::LiInitIopInterface();\n   }\n\n   switch (entryParams->dispatch.code) {\n   case LOADER_Code::Prep:\n      error = internal::LOADER_Prep(entryParams->procId,\n                                    entryParams->dispatch.minFileInfo);\n      break;\n   case LOADER_Code::Setup:\n      error = internal::LOADER_Setup(entryParams->procId,\n                                     entryParams->dispatch.handle,\n                                     FALSE,\n                                     entryParams->dispatch.minFileInfo);\n      break;\n   case LOADER_Code::Purge:\n      error = internal::LOADER_Setup(entryParams->procId,\n                                     entryParams->dispatch.handle,\n                                     TRUE,\n                                     entryParams->dispatch.minFileInfo);\n      break;\n   case LOADER_Code::Link:\n      error = internal::LOADER_Link(entryParams->procId,\n                                    entryParams->dispatch.linkInfo,\n                                    entryParams->dispatch.linkInfoSize,\n                                    entryParams->dispatch.minFileInfo);\n      break;\n   case LOADER_Code::Query:\n      error = internal::LOADER_Query(entryParams->procId,\n                                     entryParams->dispatch.handle,\n                                     entryParams->dispatch.minFileInfo);\n      break;\n   case LOADER_Code::UserGainControl:\n      getGlobalStorage()->userHasControl = TRUE;\n      error = 0;\n      break;\n   default:\n      decaf_abort(fmt::format(\"Unimplemented LOADER_ENTRY code {}\",\n                              static_cast<int>(entryParams->dispatch.code.value())));\n   }\n\n   if (error) {\n      if (auto fatalError = internal::LiGetFatalError()) {\n         auto minFileInfo = entryParams->dispatch.minFileInfo;\n         minFileInfo->error = error;\n         minFileInfo->fatalErr = internal::LiGetFatalError();\n         minFileInfo->fatalMsgType = internal::LiGetFatalMsgType();\n         minFileInfo->fatalLine = internal::LiGetFatalMsgType();\n         minFileInfo->fatalFunction = internal::LiGetFatalFunction();\n         minFileInfo->fatalFunction[63] = char { 0 };\n      }\n   }\n\n   return error;\n}\n\nint32_t\nLoaderStart(BOOL isDispatch,\n            virt_ptr<LOADER_EntryParams> entryParams)\n{\n   std::unique_lock<std::mutex> lock { sLoaderMutex };\n   sLoaderInUserMode = true;\n\n   if (isDispatch) {\n      return LOADER_Entry(entryParams);\n   }\n\n   // Initialise static data\n   internal::initialiseStaticDataHeap();\n   internal::initialiseIopStaticData();\n   internal::initialiseIpclDriverStaticData();\n\n   // Initialise globals\n   auto kernelIpcStorage = getKernelIpcStorage();\n   gpLoaderEntry_ProcContext = entryParams->procContext;\n   gpLoaderEntry_ProcConfig = entryParams->procConfig;\n   gpLoaderEntry_DispatchCode = entryParams->dispatch.code;\n   gpLoaderEntry_LoaderIntsAllowed = !!entryParams->interruptsAllowed;\n   gProcFlags = kernelIpcStorage->processFlags;\n   gProcTitleLoc = kernelIpcStorage->procTitleLoc;\n\n   // Initialise loader\n   internal::initialiseSharedHeaps();\n\n   // Clear errors\n   kernelIpcStorage->fatalErr = 0;\n   kernelIpcStorage->fatalMsgType = 0u;\n   kernelIpcStorage->fatalLine = 0u;\n   kernelIpcStorage->fatalFunction[0] = char { 0 };\n   kernelIpcStorage->loaderInitError = 0;\n   internal::LiResetFatalError();\n\n   auto error =\n      internal::LOADER_Init(kernelIpcStorage->targetProcessId,\n                            kernelIpcStorage->numCodeAreaHeapBlocks,\n                            kernelIpcStorage->maxCodeSize,\n                            kernelIpcStorage->maxDataSize,\n                            virt_addrof(kernelIpcStorage->rpxModule),\n                            virt_addrof(kernelIpcStorage->loadedModuleList),\n                            virt_addrof(kernelIpcStorage->startInfo));\n   if (error) {\n      if (!internal::LiGetFatalError()) {\n         internal::LiSetFatalError(0x1872A7u, 0, 1, \"__LoaderStart\", 227);\n      }\n\n      if (!internal::LiGetFatalError()) {\n         internal::LiPanic(\"entry.c\", 239, \"***RPX failed loader but didn't generate fatal error information; err={}.\", error);\n      }\n\n      kernelIpcStorage->loaderInitError = error;\n      kernelIpcStorage->fatalLine = internal::LiGetFatalLine();\n      kernelIpcStorage->fatalErr = internal::LiGetFatalError();\n      kernelIpcStorage->fatalMsgType = internal::LiGetFatalMsgType();\n      string_copy(virt_addrof(kernelIpcStorage->fatalFunction).get(),\n                  kernelIpcStorage->fatalFunction.size(),\n                  internal::LiGetFatalFunction().data(),\n                  kernelIpcStorage->fatalFunction.size() - 1);\n      kernelIpcStorage->fatalFunction[kernelIpcStorage->fatalFunction.size() - 1] = char { 0 };\n   }\n\n   if (kernelIpcStorage->targetProcessId == kernel::UniqueProcessId::Root) {\n      // TODO: Syscall Loader_FinishInitAndPreload\n   } else {\n      // TODO: Syscall Loader_ProfileEntry\n      // TODO: Syscall Loader_ContinueStartProcess\n   }\n\n   return 0;\n}\n\nvoid\nlockLoader()\n{\n   sLoaderMutex.lock();\n}\n\nvoid\nunlockLoader()\n{\n   sLoaderMutex.unlock();\n}\n\n\nvirt_ptr<LOADED_RPL>\ngetLoadedRpx()\n{\n   return getGlobalStorage()->loadedRpx;\n}\n\nvirt_ptr<LOADED_RPL>\ngetLoadedRplLinkedList()\n{\n   return getGlobalStorage()->firstLoadedRpl;\n}\n\nnamespace internal\n{\n\nuint32_t\ngetProcTitleLoc()\n{\n   return gProcTitleLoc;\n}\n\nkernel::ProcessFlags\ngetProcFlags()\n{\n   return gProcFlags;\n}\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_entry.h",
    "content": "#pragma once\n#include \"cafe_loader_minfileinfo.h\"\n\n#include \"cafe/kernel/cafe_kernel_context.h\"\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader\n{\n\nstruct LOADED_RPL;\n\nenum class LOADER_Code : int32_t\n{\n   Invalid                    = -1,\n   Prep                       = 1,\n   Setup                      = 2,\n   Purge                      = 3,\n   Link                       = 4,\n   Query                      = 5,\n   Tag                        = 6,\n   UserGainControl            = 7,\n   Done                       = 8,\n   GetLoaderHeapStatistics    = 9,\n};\n\nstruct LOADER_EntryDispatch\n{\n   be2_val<LOADER_Code> code;\n   be2_val<LOADER_Handle> handle;\n   be2_virt_ptr<LOADER_MinFileInfo> minFileInfo;\n   be2_virt_ptr<LOADER_LinkInfo> linkInfo;\n   be2_val<uint32_t> linkInfoSize;\n   UNKNOWN(0x20 - 0x14);\n};\nCHECK_OFFSET(LOADER_EntryDispatch, 0x00, code);\nCHECK_OFFSET(LOADER_EntryDispatch, 0x04, handle);\nCHECK_OFFSET(LOADER_EntryDispatch, 0x08, minFileInfo);\nCHECK_OFFSET(LOADER_EntryDispatch, 0x0C, linkInfo);\nCHECK_OFFSET(LOADER_EntryDispatch, 0x10, linkInfoSize);\nCHECK_SIZE(LOADER_EntryDispatch, 0x20);\n\nstruct LOADER_EntryParams\n{\n   be2_virt_ptr<kernel::Context> procContext;\n   be2_val<kernel::UniqueProcessId> procId;\n   be2_val<int32_t> procConfig;\n   be2_virt_ptr<kernel::Context> context;\n   be2_val<BOOL> interruptsAllowed;\n   be2_val<uint32_t> unk0x14;\n   be2_struct<LOADER_EntryDispatch> dispatch;\n};\nCHECK_OFFSET(LOADER_EntryParams, 0x00, procContext);\nCHECK_OFFSET(LOADER_EntryParams, 0x04, procId);\nCHECK_OFFSET(LOADER_EntryParams, 0x08, procConfig);\nCHECK_OFFSET(LOADER_EntryParams, 0x0C, context);\nCHECK_OFFSET(LOADER_EntryParams, 0x10, interruptsAllowed);\nCHECK_OFFSET(LOADER_EntryParams, 0x14, unk0x14);\nCHECK_OFFSET(LOADER_EntryParams, 0x18, dispatch);\nCHECK_SIZE(LOADER_EntryParams, 0x38);\n\nint32_t\nLoaderStart(BOOL isEntryCall,\n            virt_ptr<LOADER_EntryParams> entryParams);\n\nvoid\nlockLoader();\n\nvoid\nunlockLoader();\n\nvirt_ptr<LOADED_RPL>\ngetLoadedRpx();\n\nvirt_ptr<LOADED_RPL>\ngetLoadedRplLinkedList();\n\nnamespace internal\n{\n\nuint32_t\ngetProcTitleLoc();\n\nkernel::ProcessFlags\ngetProcFlags();\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_error.cpp",
    "content": "#include \"cafe_loader_error.h\"\n\n#include <cstdint>\n#include <string>\n\nnamespace cafe::loader::internal\n{\n\nstatic int32_t gFatalErr = 0;                // 0xEFE1AEB0\nstatic uint32_t gFatalLine = 0u;             // 0xEFE1AEB4\nstatic uint32_t gFatalMsgType = 0u;          // 0xEFE1AEB8\nstatic std::string gFatalFunction;           // 0xEFE1AEBC\n\nvoid\nLiSetFatalError(int32_t baseError,\n                uint32_t fileType,\n                uint32_t unk,\n                std::string_view function,\n                uint32_t line)\n{\n   if (gFatalErr) {\n      // Only one fatal error at a time!\n      return;\n   }\n\n   gFatalErr = baseError;\n   gFatalMsgType = fileType;\n   gFatalFunction = function;\n   gFatalLine = line;\n}\n\nint32_t\nLiGetFatalError()\n{\n   return gFatalErr;\n}\n\nconst std::string &\nLiGetFatalFunction()\n{\n   return gFatalFunction;\n}\n\nuint32_t\nLiGetFatalLine()\n{\n   return gFatalLine;\n}\n\nuint32_t\nLiGetFatalMsgType()\n{\n   return gFatalMsgType;\n}\n\nvoid\nLiResetFatalError()\n{\n   gFatalErr = 0;\n   gFatalFunction.clear();\n   gFatalLine = 0;\n   gFatalMsgType = 0;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_error.h",
    "content": "#pragma once\n#include <cstdint>\n#include <string_view>\n\nnamespace cafe::loader\n{\n\nenum Error : int32_t\n{\n   DifferentProcess                 = -470008,\n   CheckFileBoundsFailed            = -470026,\n   ZlibMemError                     = -470084,\n   ZlibVersionError                 = -470085,\n   ZlibStreamError                  = -470086,\n   ZlibDataError                    = -470087,\n   ZlibBufError                     = -470088,\n   ZlibUnknownError                 = -470100,\n};\n\nnamespace internal\n{\n\nvoid\nLiSetFatalError(int32_t baseError,\n                uint32_t fileType,\n                uint32_t unk,\n                std::string_view function,\n                uint32_t line);\n\nint32_t\nLiGetFatalError();\n\nconst std::string &\nLiGetFatalFunction();\n\nuint32_t\nLiGetFatalLine();\n\nuint32_t\nLiGetFatalMsgType();\n\nvoid\nLiResetFatalError();\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_flush.cpp",
    "content": "#include \"cafe_loader_flush.h\"\n#include \"cafe_loader_iop.h\"\n\nnamespace cafe::loader::internal\n{\n\nvoid\nLiSafeFlushCode(virt_addr base, uint32_t size)\n{\n}\n\nvoid\nLiFlushDataRangeNoSync(virt_addr base, uint32_t size)\n{\n}\n\nvoid\nLoader_FlushDataRangeNoSync(virt_addr addr, uint32_t size)\n{\n   LiCheckAndHandleInterrupts();\n   while (size > 0) {\n      auto flushSize = std::min<uint32_t>(size, 0x20000);\n      LiFlushDataRangeNoSync(addr, size);\n      LiCheckAndHandleInterrupts();\n\n      addr += flushSize;\n      size -= flushSize;\n   }\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_flush.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader::internal\n{\n\nvoid\nLiSafeFlushCode(virt_addr base,\n                uint32_t size);\n\nvoid\nLiFlushDataRangeNoSync(virt_addr base,\n                       uint32_t size);\n\nvoid\nLoader_FlushDataRangeNoSync(virt_addr addr,\n                            uint32_t size);\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_globals.cpp",
    "content": "#include \"cafe_loader_globals.h\"\n\nnamespace cafe::loader\n{\n\n// 0xEFE00000 - 0xEFE01000 = Root RPX Name\nstatic virt_ptr<char>\nsLoadRpxName = virt_cast<char *>(virt_addr { 0xEFE00000 });\n\n// 0xEFE01000 - 0xEFE02000 = Loader global variables\nstatic virt_ptr<GlobalStorage>\nsGlobalStorage = virt_cast<GlobalStorage *>(virt_addr { 0xEFE01000 });\n\n// 0xEFE02000 - 0xEFE0A400 = Loader context & stack\nstatic virt_ptr<ContextStorage>\nsContextStorage = virt_cast<ContextStorage *>(virt_addr { 0xEFE02000 });\n\n// 0xEFE0A400 - 0xEFE0A4C0 = Kernel <-> Loader data\nstatic virt_ptr<KernelIpcStorage>\nsKernelIpcStorage = virt_cast<KernelIpcStorage *>(virt_addr { 0xEFE0A400 });\n\nvoid\nsetLoadRpxName(std::string_view name)\n{\n   std::memcpy(sLoadRpxName.get(), name.data(), name.size());\n   sLoadRpxName[name.size()] = char { 0 };\n}\n\nvirt_ptr<char>\ngetLoadRpxName()\n{\n   return sLoadRpxName;\n}\n\nvirt_ptr<GlobalStorage>\ngetGlobalStorage()\n{\n   return sGlobalStorage;\n}\n\nvirt_ptr<ContextStorage>\ngetContextStorage()\n{\n   return sContextStorage;\n}\n\nvirt_ptr<KernelIpcStorage>\ngetKernelIpcStorage()\n{\n   return sKernelIpcStorage;\n}\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_globals.h",
    "content": "#pragma once\n#include \"cafe/loader/cafe_loader_basics.h\"\n#include \"cafe/kernel/cafe_kernel_context.h\"\n#include \"cafe/loader/cafe_loader_entry.h\"\n#include \"cafe/loader/cafe_loader_init.h\"\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::loader\n{\n\n// 0xEFE00000 - 0xEFE01000 = Root RPX Name\nstruct RootRPX\n{\n   be2_array<char, 0x1000> name;\n};\nCHECK_OFFSET(RootRPX, 0x00, name);\nCHECK_SIZE(RootRPX, 0x1000);\n\n// 0xEFE01000 - 0xEFE02000 = Loader global variables\nstruct GlobalStorage\n{\n   be2_val<kernel::UniqueProcessId> currentUpid;\n   be2_virt_ptr<TinyHeap> processCodeHeap;\n   be2_val<uint32_t> processCodeHeapTrackingBlockSize;\n   be2_val<uint32_t> numCodeAreaHeapBlocks;\n   be2_val<uint32_t> availableCodeSize;\n   be2_val<uint32_t> maxCodeSize;\n   be2_val<uint32_t> maxDataSize;\n   be2_val<uint32_t> sdaBase;\n   be2_val<uint32_t> sda2Base;\n   be2_val<BOOL> userHasControl;\n   be2_virt_ptr<LOADED_RPL> loadedRpx;\n   be2_virt_ptr<LOADED_RPL> firstLoadedRpl;\n   be2_virt_ptr<LOADED_RPL> lastLoadedRpl;\n   UNKNOWN(0xC);\n};\nCHECK_OFFSET(GlobalStorage, 0x00, currentUpid);\nCHECK_OFFSET(GlobalStorage, 0x04, processCodeHeap);\nCHECK_OFFSET(GlobalStorage, 0x08, processCodeHeapTrackingBlockSize);\nCHECK_OFFSET(GlobalStorage, 0x0C, numCodeAreaHeapBlocks);\nCHECK_OFFSET(GlobalStorage, 0x10, availableCodeSize);\nCHECK_OFFSET(GlobalStorage, 0x14, maxCodeSize);\nCHECK_OFFSET(GlobalStorage, 0x18, maxDataSize);\nCHECK_OFFSET(GlobalStorage, 0x1C, sdaBase);\nCHECK_OFFSET(GlobalStorage, 0x20, sda2Base);\nCHECK_OFFSET(GlobalStorage, 0x24, userHasControl);\nCHECK_OFFSET(GlobalStorage, 0x28, loadedRpx);\nCHECK_OFFSET(GlobalStorage, 0x2C, firstLoadedRpl);\nCHECK_OFFSET(GlobalStorage, 0x30, lastLoadedRpl);\nCHECK_SIZE(GlobalStorage, 0x40);\n\n// 0xEFE02000 - 0xEFE0A400 = Loader context & stack\nstruct ContextStorage\n{\n   be2_array<uint8_t, 0x2400> stack0;\n   be2_array<uint8_t, 0x2400> stack1;\n   be2_array<uint8_t, 0x2400> stack2;\n   be2_struct<kernel::Context> context0;\n   UNKNOWN(0x4E0);\n   be2_struct<kernel::Context> context1;\n   UNKNOWN(0x4E0);\n   be2_struct<kernel::Context> context2;\n   UNKNOWN(0x4E0);\n};\nCHECK_OFFSET(ContextStorage, 0x0000, stack0);\nCHECK_OFFSET(ContextStorage, 0x2400, stack1);\nCHECK_OFFSET(ContextStorage, 0x4800, stack2);\nCHECK_OFFSET(ContextStorage, 0x6C00, context0);\nCHECK_OFFSET(ContextStorage, 0x7400, context1);\nCHECK_OFFSET(ContextStorage, 0x7C00, context2);\nCHECK_SIZE(ContextStorage, 0x8400);\n\n// 0xEFE0A400 - 0xEFE0A4C0 = SharedData\nstruct KernelIpcStorage\n{\n   //! Set to 0 and never read?\n   be2_val<uint32_t> unk0x00;\n   be2_val<kernel::ProcessFlags> processFlags;\n   be2_val<kernel::UniqueProcessId> callerProcessId;\n   be2_val<kernel::UniqueProcessId> targetProcessId;\n   be2_val<uint32_t> numCodeAreaHeapBlocks;\n   be2_val<uint32_t> maxCodeSize;\n   be2_val<uint32_t> maxDataSize;\n   be2_val<uint32_t> procTitleLoc;\n   be2_virt_ptr<loader::LOADED_RPL> rpxModule;\n   be2_virt_ptr<loader::LOADED_RPL> loadedModuleList;\n   be2_val<uint32_t> unk0x28;\n   be2_val<uint32_t> fatalMsgType;\n   be2_val<int32_t> fatalErr;\n   //! Error returned from LOADER_Init called from loader start\n   be2_val<int32_t> loaderInitError;\n   be2_val<uint32_t> fatalLine;\n   be2_array<char, 0x40> fatalFunction;\n   be2_struct<loader::RPL_STARTINFO> startInfo;\n   be2_struct<loader::LOADER_EntryParams> entryParams;\n};\nCHECK_OFFSET(KernelIpcStorage, 0x00, unk0x00);\nCHECK_OFFSET(KernelIpcStorage, 0x04, processFlags);\nCHECK_OFFSET(KernelIpcStorage, 0x08, callerProcessId);\nCHECK_OFFSET(KernelIpcStorage, 0x0C, targetProcessId);\nCHECK_OFFSET(KernelIpcStorage, 0x10, numCodeAreaHeapBlocks);\nCHECK_OFFSET(KernelIpcStorage, 0x14, maxCodeSize);\nCHECK_OFFSET(KernelIpcStorage, 0x18, maxDataSize);\nCHECK_OFFSET(KernelIpcStorage, 0x1C, procTitleLoc);\nCHECK_OFFSET(KernelIpcStorage, 0x20, rpxModule);\nCHECK_OFFSET(KernelIpcStorage, 0x24, loadedModuleList);\nCHECK_OFFSET(KernelIpcStorage, 0x28, unk0x28);\nCHECK_OFFSET(KernelIpcStorage, 0x2C, fatalMsgType);\nCHECK_OFFSET(KernelIpcStorage, 0x30, fatalErr);\nCHECK_OFFSET(KernelIpcStorage, 0x34, loaderInitError);\nCHECK_OFFSET(KernelIpcStorage, 0x38, fatalLine);\nCHECK_OFFSET(KernelIpcStorage, 0x3C, fatalFunction);\nCHECK_OFFSET(KernelIpcStorage, 0x7C, startInfo);\nCHECK_OFFSET(KernelIpcStorage, 0xA4, entryParams);\nCHECK_SIZE(KernelIpcStorage, 0xDC);\n\n// 0xEFE0B000 - 0xEFE80000 = loader .rodata & .data & .bss\n\nvoid\nsetLoadRpxName(std::string_view name);\n\nvirt_ptr<char>\ngetLoadRpxName();\n\nvirt_ptr<GlobalStorage>\ngetGlobalStorage();\n\nvirt_ptr<ContextStorage>\ngetContextStorage();\n\nvirt_ptr<KernelIpcStorage>\ngetKernelIpcStorage();\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_heap.cpp",
    "content": "#include \"cafe_loader_error.h\"\n#include \"cafe_loader_flush.h\"\n#include \"cafe_loader_heap.h\"\n\n#include <common/frameallocator.h>\n#include <common/log.h>\n\nnamespace cafe::loader::internal\n{\n\nstatic FrameAllocator\nsStaticDataHeap;\n\nint32_t\nLiCacheLineCorrectAllocEx(virt_ptr<TinyHeap> heap,\n                          uint32_t textSize,\n                          int32_t textAlign,\n                          virt_ptr<void> *outPtr,\n                          uint32_t /*unused*/,\n                          uint32_t *outAllocSize,\n                          uint32_t *outLargestFree,\n                          ios::mcp::MCPFileType fileType)\n{\n   auto fromEnd = false;\n   textSize = align_up(textSize, 128);\n   *outAllocSize = textSize;\n\n   if (textAlign < 0) {\n      textAlign = -textAlign;\n      fromEnd = true;\n   }\n\n   if (textAlign == 0 && textAlign < 64) {\n      textAlign = 64;\n   }\n\n   if (fromEnd) {\n      textAlign = -textAlign;\n   }\n\n   auto tinyHeapError = TinyHeap_Alloc(heap, textSize, textAlign, outPtr);\n   if (tinyHeapError < TinyHeapError::OK) {\n      LiSetFatalError(0x187298, fileType, 0, \"LiCacheLineCorrectAllocEx\", 0x88);\n      *outLargestFree = TinyHeap_GetLargestFree(heap);\n      return static_cast<int32_t>(tinyHeapError);\n   }\n\n   std::memset(outPtr->get(), 0, textSize);\n   return 0;\n}\n\nvoid\nLiCacheLineCorrectFreeEx(virt_ptr<TinyHeap> heap,\n                         virt_ptr<void> ptr,\n                         uint32_t size)\n{\n   TinyHeap_Free(heap, ptr);\n   LiSafeFlushCode(virt_cast<virt_addr>(ptr), size);\n}\n\nvoid\ninitialiseStaticDataHeap()\n{\n   sStaticDataHeap = FrameAllocator {\n      virt_cast<void *>(virt_addr { 0xEFE0B000 }).get(),\n      0xEFE80000 - 0xEFE0B000,\n   };\n}\n\nvirt_ptr<void>\nallocStaticData(size_t size,\n                size_t align)\n{\n   return virt_cast<void *>(cpu::translate(sStaticDataHeap.allocate(size, align)));\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_heap.h",
    "content": "#pragma once\n#include \"cafe/cafe_tinyheap.h\"\n#include \"ios/mcp/ios_mcp_enum.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader::internal\n{\n\nint32_t\nLiCacheLineCorrectAllocEx(virt_ptr<TinyHeap> heap,\n                          uint32_t textSize,\n                          int32_t textAlign,\n                          virt_ptr<void> *outPtr,\n                          uint32_t unk,\n                          uint32_t *outAllocSize,\n                          uint32_t *outLargestFree,\n                          ios::mcp::MCPFileType fileType);\n\nvoid\nLiCacheLineCorrectFreeEx(virt_ptr<TinyHeap> heap,\n                         virt_ptr<void> ptr,\n                         uint32_t size);\n\nvoid\ninitialiseStaticDataHeap();\n\nvirt_ptr<void>\nallocStaticData(size_t size,\n                size_t align = 4u);\n\ntemplate<typename Type>\nvirt_ptr<Type>\nallocStaticData()\n{\n   return virt_cast<Type *>(allocStaticData(sizeof(Type), alignof(Type)));\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_init.cpp",
    "content": "#include \"cafe_loader_basics.h\"\n#include \"cafe_loader_bounce.h\"\n#include \"cafe_loader_entry.h\"\n#include \"cafe_loader_error.h\"\n#include \"cafe_loader_globals.h\"\n#include \"cafe_loader_heap.h\"\n#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_ipcldriver.h\"\n#include \"cafe_loader_prep.h\"\n#include \"cafe_loader_setup.h\"\n#include \"cafe_loader_shared.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_minfileinfo.h\"\n#include \"cafe_loader_utils.h\"\n#include \"cafe/cafe_stackobject.h\"\n\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::loader::internal\n{\n\nstatic uint32_t sDataAreaSize = 0;\nstatic uint32_t sNumDataAreaAllocations = 0;\n\nstatic int32_t\nLiLoadCoreIntoProcess(virt_ptr<RPL_STARTINFO> startInfo)\n{\n   auto error = 0;\n   auto globals = getGlobalStorage();\n   Loader_LogEntry(2, 1, 512, \"LiLoadCoreIntoProcess\");\n\n   if (!getProcFlags().disableSharedLibraries()) {\n      auto sharedModule = findLoadedSharedModule(\"coreinit\");\n      if (!sharedModule) {\n         Loader_ReportError(\"***Failed to find shared coreinit on module list.\");\n         LiSetFatalError(0x18729Bu, 0, 1, \"LiLoadCoreIntoProcess\", 161);\n         Loader_LogEntry(2, 1, 1024, \"LiLoadCoreIntoProcess\");\n         return -470010;\n      }\n\n      auto allocPtr = virt_ptr<void> { nullptr };\n      auto allocSize = uint32_t { 0 };\n      auto largestFree = uint32_t { 0 };\n      error = LiCacheLineCorrectAllocEx(globals->processCodeHeap,\n                                        sizeof(LOADED_RPL),\n                                        4,\n                                        &allocPtr,\n                                        1,\n                                        &allocSize,\n                                        &largestFree,\n                                        sharedModule->fileType);\n      if (error) {\n         Loader_ReportError(\n            \"***Failed to allocate system coreinit RPL tracking for process;  (needed {}, available {}).\\n\",\n            allocSize, largestFree);\n         Loader_LogEntry(2, 1, 1024, \"LiLoadCoreIntoProcess\");\n         return error;\n      }\n\n      auto trackingModule = virt_cast<LOADED_RPL *>(allocPtr);\n      *trackingModule = *sharedModule;\n      trackingModule->selfBufferSize = allocSize;\n      trackingModule->globals = globals;\n\n      // Add to global loaded module linked list\n      trackingModule->nextLoadedRpl = nullptr;\n      if (globals->lastLoadedRpl) {\n         globals->lastLoadedRpl->nextLoadedRpl = trackingModule;\n      } else {\n         globals->firstLoadedRpl = trackingModule;\n      }\n      globals->lastLoadedRpl = trackingModule;\n\n      startInfo->coreinit = trackingModule;\n   } else {\n      decaf_abort(\"Unimplemented LiLoadCoreIntoProcess for disableSharedLibraries\");\n   }\n\n   Loader_LogEntry(2, 1, 1024, \"LiLoadCoreIntoProcess\");\n   return error;\n}\n\nint32_t\nLOADER_Init(kernel::UniqueProcessId upid,\n            uint32_t numCodeAreaHeapBlocks,\n            uint32_t maxCodeSize,\n            uint32_t maxDataSize,\n            virt_ptr<virt_ptr<LOADED_RPL>> outLoadedRpx,\n            virt_ptr<virt_ptr<LOADED_RPL>> outModuleList,\n            virt_ptr<RPL_STARTINFO> startInfo)\n{\n   auto trackingBlockSize = align_up(TinyHeapBlockSize * numCodeAreaHeapBlocks + TinyHeapHeaderSize, 0x40);\n   auto globals = getGlobalStorage();\n   auto error = 0;\n   auto chunkBufferSize = uint32_t { 0 };\n   auto chunkBuffer = virt_ptr<void> { nullptr };\n   auto loadRpxName = std::string_view { };\n   auto loadFileType = ios::mcp::MCPFileType::CafeOS;\n   auto fileName = StackArray<char, 64> { };\n   auto moduleName = StackArray<char, 64> { };\n   auto fileNameLen = uint32_t { 0 };\n   auto loadArgs = LiBasicsLoadArgs { };\n   auto rpx = virt_ptr<LOADED_RPL> { nullptr };\n   auto fileInfo = virt_ptr<rpl::RPLFileInfo_v4_2> { nullptr };\n   auto dataArea = virt_addr { 0 };\n\n   sDataAreaSize = 0;\n   sNumDataAreaAllocations = 0;\n   maxCodeSize = std::min(maxCodeSize, 0x0E000000u);\n\n   globals->numCodeAreaHeapBlocks = numCodeAreaHeapBlocks;\n   globals->currentUpid = upid;\n   globals->processCodeHeapTrackingBlockSize = trackingBlockSize;\n   globals->maxCodeSize = maxCodeSize;\n   globals->maxDataSize = maxDataSize;\n   globals->availableCodeSize = maxCodeSize - trackingBlockSize;\n   globals->userHasControl = FALSE;\n   globals->firstLoadedRpl = nullptr;\n   globals->lastLoadedRpl = nullptr;\n   globals->loadedRpx = nullptr;\n\n   std::memset(startInfo.get(), 0, sizeof(RPL_STARTINFO));\n   startInfo->dataAreaEnd = virt_addr { 0x10000000u } + maxDataSize;\n\n   // Setup code heap\n   globals->processCodeHeap = virt_cast<TinyHeap *>(virt_addr { 0x10000000 - trackingBlockSize });\n\n   auto heapError =\n      TinyHeap_Setup(globals->processCodeHeap,\n                     globals->processCodeHeapTrackingBlockSize,\n                     virt_cast<void *>(virt_addr { 0x10000000 - globals->availableCodeSize - trackingBlockSize }),\n                     globals->availableCodeSize);\n   if (heapError != TinyHeapError::OK) {\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_Init\", 204);\n      Loader_ReportError(\"*** Process code heap setup failed.\\n\");\n      goto error;\n   }\n\n   if (!IPCLDriver_IsInitialised()) {\n      IPCLDriver_Init();\n      IPCLDriver_Open();\n      LiInitIopInterface();\n   }\n\n   error = LiInitSharedForProcess(startInfo);\n   Loader_LogEntry(2, 1, 0, \"LOADER_Init  LoadINIT ShrForPro ret={}.\", error);\n   if (error) {\n      Loader_ReportError(\"*** Process shared resource init failure.\");\n      goto error;\n   }\n\n   LiInitBuffer(true);\n\n   if (upid == kernel::UniqueProcessId::Root) {\n      loadRpxName = std::string_view { \"root.rpx\" };\n      loadFileType = ios::mcp::MCPFileType::CafeOS;\n   } else {\n      loadRpxName = getLoadRpxName().get();\n      loadFileType = ios::mcp::MCPFileType::ProcessCode;\n   }\n\n   error = LiBounceOneChunk(loadRpxName, loadFileType, upid, &chunkBufferSize, 0, 1, &chunkBuffer);\n   Loader_LogEntry(2, 1, 0,\n      \"LOADER_Init  LoadINIT BncLoad ret, pName={}, mProcId={}, &pLoaded={}, err={}.\",\n         loadRpxName, static_cast<int>(upid), chunkBuffer, error);\n\n   if (error) {\n      Loader_ReportError(\n         \"***LiBounceOneChunk failed loading \\\"{}\\\" of type {} at offset 0x{:08X} err={}.\",\n         loadRpxName,\n         loadFileType,\n         0,\n         error);\n      goto error;\n   }\n\n   fileNameLen = std::min<uint32_t>(static_cast<uint32_t>(loadRpxName.size()), 59);\n   std::memcpy(fileName.get(), loadRpxName.data(), fileNameLen);\n\n   // Resolve module name and copy to guest stack buffer\n   loadRpxName = LiResolveModuleName(loadRpxName);\n   std::memcpy(moduleName.get(), loadRpxName.data(), loadRpxName.size());\n   moduleName[loadRpxName.size()] = char { 0 };\n\n   loadArgs.upid = upid;\n   loadArgs.loadedRpl = nullptr;\n   loadArgs.readHeapTracking = globals->processCodeHeap;\n   loadArgs.pathNameLen = fileNameLen;\n   loadArgs.pathName = fileName;\n   loadArgs.fileType = loadFileType;\n   loadArgs.chunkBuffer = chunkBuffer;\n   loadArgs.chunkBufferSize = chunkBufferSize;\n   loadArgs.fileOffset = 0u;\n\n   error = LiLoadForPrep(moduleName,\n                         static_cast<uint32_t>(loadRpxName.size()),\n                         chunkBuffer,\n                         &rpx,\n                         &loadArgs,\n                         0);\n   if (error) {\n      Loader_ReportError(\"***LiLoadForPrep() failed with err={}.\", error);\n      goto error;\n   }\n\n   globals->loadedRpx = rpx;\n   rpx->loadStateFlags |= LoadStateFlags::LoaderStateFlag4;\n\n   // Allocate data buffer\n   dataArea = virt_addr { startInfo->dataAreaStart };\n   fileInfo = rpx->fileInfoBuffer;\n   if (fileInfo->dataSize) {\n      dataArea = align_up(dataArea, fileInfo->dataAlign);\n      rpx->dataBuffer = virt_cast<void *>(dataArea);\n      dataArea = align_up(dataArea + fileInfo->dataSize, 64);\n      ++sNumDataAreaAllocations;\n   }\n\n   // Allocate load buffer\n   if (fileInfo->loadSize != fileInfo->fileInfoPad) {\n      dataArea = align_up(dataArea, fileInfo->loadAlign);\n      rpx->loadBuffer = virt_cast<void *>(dataArea);\n      dataArea = align_up(dataArea + fileInfo->loadSize - fileInfo->fileInfoPad, 64);\n      ++sNumDataAreaAllocations;\n   }\n\n   sDataAreaSize += static_cast<uint32_t>(dataArea - startInfo->dataAreaStart);\n   if (dataArea < virt_addr { 0x10000000 } || dataArea >= startInfo->dataAreaEnd) {\n      Loader_ReportError(\n         \"*** Insufficient data area space in process. end @ {}, areaend @ {}\",\n         dataArea, startInfo->dataAreaEnd);\n      LiSetFatalError(0x18729Bu, rpx->fileType, 1, \"LOADER_Init\", 338);\n      error = -470019;\n      goto error;\n   }\n\n   startInfo->dataAreaStart = dataArea;\n   if (upid == kernel::UniqueProcessId::Root) {\n      startInfo->systemHeapSize = 0x4000u;\n      startInfo->stackSize = 0x4000u;\n   } else {\n      if (fileInfo->stackSize) {\n         startInfo->stackSize = fileInfo->stackSize;\n      } else {\n         startInfo->stackSize = 0x10000u;\n      }\n\n      startInfo->systemHeapSize = std::max<uint32_t>(fileInfo->heapSize, 0x8000u);\n   }\n\n   error = LiSetupOneRPL(upid, rpx, globals->processCodeHeap, globals->processCodeHeap);\n   if (error) {\n      goto error;\n   }\n\n   Loader_LogEntry(2, 1, 0,\n      \"LOADER_Init  LoadINIT Setup1RPL ret, mpName={}, aProcId={}, mpCodeHeapTracking={}, err={}.\",\n      rpx->moduleNameBuffer,\n      static_cast<int>(upid),\n      globals->processCodeHeap,\n      0);\n\n   if ((startInfo->dataAreaEnd - startInfo->dataAreaStart) < startInfo->systemHeapSize) {\n      Loader_ReportError(\"***Insufficient space for stacks and system heap in process to start it.\\n\");\n      LiSetFatalError(0x18729Bu, rpx->fileType, 1, \"LOADER_Init\", 375);\n      error = -470020;\n      goto error;\n   }\n\n   // Relocate sda base\n   globals->sdaBase = 0u;\n   globals->sda2Base = 0u;\n\n   for (auto i = 1u; i < rpx->elfHeader.shnum; ++i) {\n      auto sectionHeader = getSectionHeader(rpx, i);\n      auto sectionAddress = rpx->sectionAddressBuffer[i];\n      if (!sectionHeader->size ||\n          !sectionAddress ||\n          (sectionHeader->flags & rpl::SHF_EXECINSTR)) {\n         continue;\n      }\n\n      if ((fileInfo->sdaBase - 0x8000) >= sectionHeader->addr &&\n         (fileInfo->sdaBase - 0x8000) < (sectionHeader->addr + sectionHeader->size)) {\n         globals->sdaBase = sectionAddress + fileInfo->sdaBase - sectionHeader->addr;\n      }\n\n      if ((fileInfo->sda2Base - 0x8000) >= sectionHeader->addr &&\n         (fileInfo->sda2Base - 0x8000) < (sectionHeader->addr + sectionHeader->size)) {\n         globals->sda2Base = sectionAddress + fileInfo->sda2Base - sectionHeader->addr;\n      }\n   }\n\n   if (!globals->sdaBase) {\n      globals->sdaBase = virt_cast<virt_addr>(rpx->dataBuffer) + 0x8000u;\n   }\n\n   if (!globals->sda2Base) {\n      globals->sda2Base = virt_cast<virt_addr>(rpx->loadBuffer) + 0x8000u;\n   }\n\n   startInfo->sdaBase = globals->sdaBase;\n   startInfo->sda2Base = globals->sda2Base;\n\n   error = LiLoadCoreIntoProcess(startInfo);\n   Loader_LogEntry(2, 1, 0,\n      \"LOADER_Init  LoadINIT LdCrIntoPc ret, mpName={}, aProcId={}, mpCodeHeapTracking={}, err={}.\",\n      rpx->moduleNameBuffer, static_cast<int>(upid), globals->processCodeHeap, error);\n   if (error) {\n      goto error;\n   }\n\n   if (!startInfo->coreinit->entryPoint) {\n      Loader_ReportError(\"***Error: No main program entrypoint found.\");\n      LiSetFatalError(0x18729Bu, rpx->fileType, 1, \"LOADER_Init\", 432);\n      error = -470080;\n      goto error;\n   }\n\n   startInfo->entryPoint = startInfo->coreinit->entryPoint;\n   startInfo->unk0x10 = 0u;\n   *outLoadedRpx = rpx;\n   *outModuleList = globals->firstLoadedRpl;\n   Loader_LogEntry(2, 1, 1024, \"LOADER_Init\");\n\n   for (auto module = globals->firstLoadedRpl; module; module = module->nextLoadedRpl) {\n      gLog->debug(\"Loaded module {}\", module->moduleNameBuffer);\n      for (auto i = 0u; i < module->elfHeader.shnum; ++i) {\n         auto size = 0u;\n         if (module->sectionHeaderBuffer) {\n            size = getSectionHeader(module, i)->size;\n         }\n         gLog->debug(\"  Section {}, {} - {}\",\n                     i,\n                     module->sectionAddressBuffer[i],\n                     module->sectionAddressBuffer[i] + size);\n      }\n   }\n   return 0;\n\nerror:\n   globals->currentUpid = kernel::UniqueProcessId::Kernel;\n   LiCloseBufferIfError();\n   Loader_LogEntry(2, 1, 1024, \"LOADER_Init\");\n   return error;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_init.h",
    "content": "#pragma once\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader\n{\n\nstruct LOADED_RPL;\n\nstruct RPL_STARTINFO\n{\n   be2_val<virt_addr> entryPoint;\n   be2_val<virt_addr> dataAreaStart;\n   be2_val<virt_addr> sdaBase;\n   be2_val<virt_addr> sda2Base;\n   be2_val<uint32_t> unk0x10;\n   be2_val<uint32_t> stackSize;\n   be2_val<uint32_t> systemHeapSize;\n   be2_val<uint32_t> appFlags;\n   be2_virt_ptr<LOADED_RPL> coreinit;\n   be2_val<virt_addr> dataAreaEnd;\n};\nCHECK_OFFSET(RPL_STARTINFO, 0x00, entryPoint);\nCHECK_OFFSET(RPL_STARTINFO, 0x04, dataAreaStart);\nCHECK_OFFSET(RPL_STARTINFO, 0x08, sdaBase);\nCHECK_OFFSET(RPL_STARTINFO, 0x0C, sda2Base);\nCHECK_OFFSET(RPL_STARTINFO, 0x10, unk0x10);\nCHECK_OFFSET(RPL_STARTINFO, 0x14, stackSize);\nCHECK_OFFSET(RPL_STARTINFO, 0x18, systemHeapSize);\nCHECK_OFFSET(RPL_STARTINFO, 0x1C, appFlags);\nCHECK_OFFSET(RPL_STARTINFO, 0x20, coreinit);\nCHECK_OFFSET(RPL_STARTINFO, 0x24, dataAreaEnd);\nCHECK_SIZE(RPL_STARTINFO, 0x28);\n\nnamespace internal\n{\n\nint32_t\nLOADER_Init(kernel::UniqueProcessId upid,\n            uint32_t numCodeAreaHeapBlocks,\n            uint32_t maxCodeSize,\n            uint32_t maxDataSize,\n            virt_ptr<virt_ptr<LOADED_RPL>> outLoadedRpx,\n            virt_ptr<virt_ptr<LOADED_RPL>> outModuleList,\n            virt_ptr<RPL_STARTINFO> startInfo);\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_iop.cpp",
    "content": "#include \"cafe_loader_heap.h\"\n#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_ipcldriver.h\"\n\n#include \"cafe/cafe_tinyheap.h\"\n#include \"cafe/kernel/cafe_kernel_ipckdriver.h\"\n#include \"cafe/kernel/cafe_kernel_ipc.h\"\n#include \"cafe/kernel/cafe_kernel_process.h\"\n#include \"ios/mcp/ios_mcp_mcp.h\"\n\n#include <array>\n#include <cstdint>\n#include <common/cbool.h>\n#include <libcpu/be2_struct.h>\n#include <libcpu/cpu_control.h>\n\nusing namespace ios::mcp;\nusing namespace cafe::kernel;\n\nnamespace cafe::loader::internal\n{\n\nconstexpr auto HeapSizePerCore = 0x2000u;\nconstexpr auto HeapAllocAlign = 0x40u;\n\nstruct LiLoadReply\n{\n   be2_val<BOOL> done;\n   be2_virt_ptr<void> requestBuffer;\n   be2_val<ios::Error> error;\n   be2_val<BOOL> pending;\n};\n\nstruct StaticIopData\n{\n   be2_val<ios::Handle> mcpHandle;\n   be2_struct<LiLoadReply> loadReply;\n   alignas(0x40) be2_array<uint8_t, HeapSizePerCore * 3> heapBufs;\n};\n\nstatic virt_ptr<StaticIopData>\nsIopData = nullptr;\n\nstatic virt_ptr<TinyHeap>\niop_percore_getheap()\n{\n   return virt_cast<TinyHeap *>(\n      virt_addrof(sIopData->heapBufs) + HeapSizePerCore * cpu::this_core::id());\n}\n\nstatic void\niop_percore_initheap()\n{\n   auto heap = iop_percore_getheap();\n   auto data = virt_cast<void *>(virt_cast<virt_addr>(heap) + 0x430);\n   auto alignedData = align_up(data, HeapAllocAlign);\n   auto alignedOffset = virt_cast<virt_addr>(alignedData) - virt_cast<virt_addr>(data);\n   TinyHeap_Setup(heap, 0x430, alignedData,\n                  static_cast<uint32_t>(HeapSizePerCore - 0x430 - alignedOffset));\n}\n\nstatic virt_ptr<void>\niop_percore_malloc(uint32_t size)\n{\n   auto heap = iop_percore_getheap();\n   size = align_up(size, HeapAllocAlign);\n\n   auto allocPtr = virt_ptr<void> { nullptr };\n   TinyHeap_Alloc(heap, size, HeapAllocAlign, &allocPtr);\n   return allocPtr;\n}\n\nstatic void\niop_percore_free(virt_ptr<void> buffer)\n{\n   auto heap = iop_percore_getheap();\n   TinyHeap_Free(heap, buffer);\n}\n\nvoid\nLiInitIopInterface()\n{\n   iop_percore_initheap();\n\n   if (sIopData->mcpHandle <= 0) {\n      sIopData->mcpHandle = kernel::getMcpHandle();\n   }\n}\n\nstatic void\nLoader_AsyncCallback(ios::Error error,\n                     virt_ptr<void> context)\n{\n   auto reply = virt_cast<LiLoadReply *>(context);\n   if (!reply) {\n      return;\n   }\n\n   if (reply->requestBuffer) {\n      iop_percore_free(reply->requestBuffer);\n      reply->requestBuffer = nullptr;\n   }\n\n   reply->error = error;\n   reply->done = TRUE;\n}\n\nstatic ios::Error\nLiPollForCompletion()\n{\n   virt_ptr<IPCLDriver> driver;\n   auto error = IPCLDriver_GetInstance(&driver);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   auto request = ipckDriverLoaderPollCompletion();\n   if (!request) {\n      return ios::Error::QEmpty;\n   }\n\n   return IPCLDriver_ProcessReply(driver, request);\n}\n\nint32_t\nLiCheckInterrupts()\n{\n   cpu::this_core::checkInterrupts();\n   return 0;\n}\n\nvoid\nLiCheckAndHandleInterrupts()\n{\n   LiCheckInterrupts();\n}\n\nios::Error\nLiLoadAsync(std::string_view name,\n            virt_ptr<void> outBuffer,\n            uint32_t outBufferSize,\n            uint32_t pos,\n            MCPFileType fileType,\n            RamPartitionId rampid)\n{\n   auto request = virt_cast<MCPRequestLoadFile *>(iop_percore_malloc(sizeof(MCPRequestLoadFile)));\n   request->pos = pos;\n   request->fileType = fileType;\n   request->cafeProcessId = static_cast<uint32_t>(rampid);\n   request->name = name;\n\n   sIopData->loadReply.done = FALSE;\n   sIopData->loadReply.pending = TRUE;\n   sIopData->loadReply.requestBuffer = request;\n   sIopData->loadReply.error = ios::Error::InvalidArg;\n\n   auto error = IPCLDriver_IoctlAsync(sIopData->mcpHandle,\n                                      MCPCommand::LoadFile,\n                                      request, sizeof(MCPRequestLoadFile),\n                                      outBuffer, outBufferSize,\n                                      &Loader_AsyncCallback,\n                                      virt_addrof(sIopData->loadReply));\n   if (error < ios::Error::OK) {\n      iop_percore_free(request);\n   }\n\n   return error;\n}\n\nstatic ios::Error\nLiWaitAsyncReply(virt_ptr<LiLoadReply> reply)\n{\n   while (!reply->done) {\n      LiPollForCompletion();\n   }\n\n   reply->done = FALSE;\n   reply->pending = FALSE;\n   return reply->error;\n}\n\nstatic ios::Error\nLiWaitAsyncReplyWithInterrupts(virt_ptr<LiLoadReply> reply)\n{\n   while (!reply->done) {\n      LiPollForCompletion();\n\n      if (!reply->done) {\n         cpu::this_core::waitNextInterrupt();\n      }\n   }\n\n   reply->done = FALSE;\n   reply->pending = FALSE;\n   return reply->error;\n}\n\nios::Error\nLiWaitIopComplete(uint32_t *outBytesRead)\n{\n   auto error = LiWaitAsyncReply(virt_addrof(sIopData->loadReply));\n   if (error < 0) {\n      *outBytesRead = 0;\n   } else {\n      *outBytesRead = static_cast<uint32_t>(error);\n      error = ios::Error::OK;\n   }\n\n   return error;\n}\n\nios::Error\nLiWaitIopCompleteWithInterrupts(uint32_t *outBytesRead)\n{\n   auto error = LiWaitAsyncReplyWithInterrupts(virt_addrof(sIopData->loadReply));\n   if (error < 0) {\n      *outBytesRead = 0;\n   } else {\n      *outBytesRead = static_cast<uint32_t>(error);\n      error = ios::Error::OK;\n   }\n\n   return error;\n}\n\nvoid\ninitialiseIopStaticData()\n{\n   sIopData = allocStaticData<StaticIopData>();\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_iop.h",
    "content": "#pragma once\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n\n#include \"ios/ios_error.h\"\n#include \"ios/ios_ipc.h\"\n#include \"ios/mcp/ios_mcp_enum.h\"\n\nnamespace cafe::loader::internal\n{\n\nvoid\nLiInitIopInterface();\n\nvoid\nLiCheckAndHandleInterrupts();\n\nios::Error\nLiLoadAsync(std::string_view name,\n            virt_ptr<void> outBuffer,\n            uint32_t outBufferSize,\n            uint32_t pos,\n            ios::mcp::MCPFileType fileType,\n            kernel::RamPartitionId rampid);\n\nios::Error\nLiWaitIopComplete(uint32_t *outBytesRead);\n\nios::Error\nLiWaitIopCompleteWithInterrupts(uint32_t *outBytesRead);\n\nvoid\ninitialiseIopStaticData();\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_ipcldriver.cpp",
    "content": "#include \"cafe_loader_heap.h\"\n#include \"cafe_loader_ipcldriver.h\"\n\n#include \"cafe/kernel/cafe_kernel_ipckdriver.h\"\n#include \"cafe/cafe_stackobject.h\"\n\n#include <libcpu/state.h>\n\nusing namespace cafe::kernel;\n\nnamespace cafe::loader::internal\n{\n\nstruct StaticIpclData\n{\n   be2_array<IPCLDriver, 3> drivers;\n   be2_array<IPCKDriverRequest, IPCLBufferCount * 3> ipclResourceRequestBuffer;\n};\n\nstatic virt_ptr<StaticIpclData>\nsIpclData;\n\nbool\nIPCLDriver_IsInitialised()\n{\n   return sIpclData->drivers[cpu::this_core::id()].status != IPCLDriverStatus::Invalid;\n}\n\nios::Error\nIPCLDriver_Init()\n{\n   virt_ptr<IPCLDriver> driver;\n   IPCLDriver_GetInstance(&driver);\n\n   std::memset(driver.get(), 0, sizeof(IPCLDriver));\n   driver->coreId = cpu::this_core::id();\n   driver->ipckRequestBuffer = virt_addrof(sIpclData->ipclResourceRequestBuffer[driver->coreId]);\n   driver->status = IPCLDriverStatus::Initialised;\n\n   std::memset(driver->ipckRequestBuffer.get(),\n               0,\n               sizeof(IPCKDriverRequest) * IPCLBufferCount);\n\n   return ios::Error::OK;\n}\n\nios::Error\nIPCLDriver_InitRequestParameterBlocks(virt_ptr<IPCLDriver> driver)\n{\n   for (auto i = 0u; i < IPCLBufferCount; ++i) {\n      auto ipckRequestBuffer = virt_addrof(driver->ipckRequestBuffer[i]);\n      auto &request = driver->requests[i];\n      request.ipckRequestBuffer = ipckRequestBuffer;\n      request.asyncCallback = nullptr;\n      request.asyncCallbackData = nullptr;\n   }\n\n   return ios::Error::OK;\n}\n\nios::Error\nIPCLDriver_Open()\n{\n   virt_ptr<IPCLDriver> driver;\n   IPCLDriver_GetInstance(&driver);\n\n   if (driver->status != IPCLDriverStatus::Closed && driver->status != IPCLDriverStatus::Initialised) {\n      return ios::Error::NotReady;\n   }\n\n   IPCLDriver_InitRequestParameterBlocks(driver);\n\n   IPCLDriver_FIFOInit(virt_addrof(driver->freeFifo));\n   IPCLDriver_FIFOInit(virt_addrof(driver->outboundFifo));\n\n   for (auto i = 0u; i < IPCLBufferCount; ++i) {\n      IPCLDriver_FIFOPush(virt_addrof(driver->freeFifo),\n                          virt_addrof(driver->requests[i]));\n   }\n\n   auto error = kernel::ipckDriverLoaderOpen();\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   driver->status = IPCLDriverStatus::Open;\n   return ios::Error::OK;\n}\n\nios::Error\nIPCLDriver_GetInstance(virt_ptr<IPCLDriver> *outDriver)\n{\n   auto driver = virt_addrof(sIpclData->drivers[cpu::this_core::id()]);\n   *outDriver = driver;\n\n   if (driver->status < IPCLDriverStatus::Open) {\n      return ios::Error::NotReady;\n   } else {\n      return ios::Error::OK;\n   }\n}\n\nios::Error\nIPCLDriver_AllocateRequestBlock(virt_ptr<IPCLDriver> driver,\n                                virt_ptr<IPCLDriverRequest> *outRequest,\n                                ios::Handle handle,\n                                ios::Command command,\n                                IPCLAsyncCallbackFn callback,\n                                virt_ptr<void> callbackContext)\n{\n   virt_ptr<IPCLDriverRequest> request;\n   auto error = IPCLDriver_FIFOPop(virt_addrof(driver->freeFifo), &request);\n   if (error < ios::Error::OK) {\n      driver->failedAllocateRequestBlock++;\n      return error;\n   }\n\n   // Initialise IPCLDriverRequest\n   request->allocated = TRUE;\n   request->asyncCallback = callback;\n   request->asyncCallbackData = callbackContext;\n\n   // Initialise IPCKDriverRequest\n   auto ipckRequest = request->ipckRequestBuffer;\n   std::memset(virt_addrof(ipckRequest->request.args).get(), 0, sizeof(ipckRequest->request.args));\n   ipckRequest->request.command = command;\n   ipckRequest->request.handle = handle;\n   ipckRequest->request.flags = 0u;\n   ipckRequest->request.clientPid = 0;\n   ipckRequest->request.reply = ios::Error::OK;\n\n   *outRequest = request;\n   return ios::Error::OK;\n}\n\nios::Error\nIPCLDriver_FreeRequestBlock(virt_ptr<IPCLDriver> driver,\n                            virt_ptr<IPCLDriverRequest> request)\n{\n   auto error = IPCLDriver_FIFOPush(virt_addrof(driver->freeFifo), request);\n   request->allocated = FALSE;\n\n   if (error < ios::Error::OK) {\n      driver->failedFreeRequestBlock++;\n   }\n\n   return error;\n}\n\nstatic ios::Error\ndefensiveProcessIncomingMessagePointer(virt_ptr<IPCLDriver> driver,\n                                       virt_ptr<cafe::kernel::IPCKDriverRequest> ipckRequest,\n                                       virt_ptr<IPCLDriverRequest> *outIpclRequest)\n{\n   if (ipckRequest < driver->ipckRequestBuffer) {\n      return ios::Error::Invalid;\n   }\n\n   auto index = ipckRequest - driver->ipckRequestBuffer;\n   if (index >= IPCLBufferCount) {\n      return ios::Error::Invalid;\n   }\n\n   if (!driver->requests[index].allocated) {\n      driver->invalidReplyMessagePointerNotAlloc++;\n      return ios::Error::Invalid;\n   }\n\n   *outIpclRequest = virt_addrof(driver->requests[index]);\n   return ios::Error::OK;\n}\n\nstatic void\nipclProcessReply(virt_ptr<IPCLDriver> driver,\n                 virt_ptr<IPCLDriverRequest> request)\n{\n   driver->repliesReceived++;\n\n   auto ipckRequest = request->ipckRequestBuffer;\n   switch (ipckRequest->request.command) {\n   case ios::Command::Open:\n      if (request->asyncCallback) {\n         if (ipckRequest->request.reply < ios::Error::OK) {\n            driver->iosOpenAsyncRequestFail++;\n         } else {\n            driver->iosOpenAsyncRequestSuccess++;\n         }\n      }\n      break;\n   case ios::Command::Close:\n      if (request->asyncCallback) {\n         if (ipckRequest->request.reply < ios::Error::OK) {\n            driver->iosCloseAsyncRequestFail++;\n         } else {\n            driver->iosCloseAsyncRequestSuccess++;\n         }\n      }\n      break;\n   case ios::Command::Read:\n      decaf_check(ipckRequest->buffer1 || !ipckRequest->request.args.read.length);\n\n      if (request->asyncCallback) {\n         if (ipckRequest->request.reply < ios::Error::OK) {\n            driver->iosReadAsyncRequestFail++;\n         } else {\n            driver->iosReadAsyncRequestSuccess++;\n         }\n      }\n      break;\n   case ios::Command::Write:\n      decaf_check(ipckRequest->buffer1 || !ipckRequest->request.args.write.length);\n\n      if (request->asyncCallback) {\n         if (ipckRequest->request.reply < ios::Error::OK) {\n            driver->iosWriteAsyncRequestFail++;\n         } else {\n            driver->iosWriteAsyncRequestSuccess++;\n         }\n      }\n      break;\n   case ios::Command::Seek:\n      if (request->asyncCallback) {\n         if (ipckRequest->request.reply < ios::Error::OK) {\n            driver->iosSeekAsyncRequestFail++;\n         } else {\n            driver->iosSeekAsyncRequestSuccess++;\n         }\n      }\n      break;\n   case ios::Command::Ioctl:\n      decaf_check(ipckRequest->buffer1 || !ipckRequest->request.args.ioctl.inputLength);\n      decaf_check(ipckRequest->buffer2 || !ipckRequest->request.args.ioctl.outputLength);\n\n      if (request->asyncCallback) {\n         if (ipckRequest->request.reply < ios::Error::OK) {\n            driver->iosIoctlAsyncRequestFail++;\n         } else {\n            driver->iosIoctlAsyncRequestSuccess++;\n         }\n      }\n      break;\n   case ios::Command::Ioctlv:\n      decaf_check(ipckRequest->buffer1 ||\n                  (ipckRequest->request.args.ioctlv.numVecIn + ipckRequest->request.args.ioctlv.numVecOut) == 0);\n\n      if (request->asyncCallback) {\n         if (ipckRequest->request.reply < ios::Error::OK) {\n            driver->iosIoctlvAsyncRequestFail++;\n         } else {\n            driver->iosIoctlvAsyncRequestSuccess++;\n         }\n      }\n      break;\n   default:\n      driver->invalidReplyCommand++;\n   }\n}\n\nios::Error\nIPCLDriver_ProcessReply(virt_ptr<IPCLDriver> driver,\n                        virt_ptr<cafe::kernel::IPCKDriverRequest> ipckRequest)\n{\n   auto request = virt_ptr<IPCLDriverRequest> { nullptr };\n   if (driver->status < IPCLDriverStatus::Open) {\n      driver->unexpectedReplyInterrupt++;\n      return ios::Error::Invalid;\n   }\n\n   auto error = defensiveProcessIncomingMessagePointer(driver, ipckRequest, &request);\n   if (error < ios::Error::OK) {\n      driver->invalidReplyMessagePointer++;\n      return error;\n   }\n\n   ipclProcessReply(driver, request);\n\n   if (request->asyncCallback) {\n      request->asyncCallback(ipckRequest->request.reply, request->asyncCallbackData);\n      IPCLDriver_FreeRequestBlock(driver, request);\n      driver->asyncTransactionsCompleted++;\n   }\n\n   return error;\n}\n\nios::Error\nIPCLDriver_ProcessIOSIoctlRequest(virt_ptr<IPCLDriver> driver,\n                                  virt_ptr<IPCLDriverRequest> request,\n                                  uint32_t command,\n                                  virt_ptr<const void> inputBuffer,\n                                  uint32_t inputLength,\n                                  virt_ptr<void> outputBuffer,\n                                  uint32_t outputLength)\n{\n   auto ipckRequest = request->ipckRequestBuffer;\n   ipckRequest->request.args.ioctl.request = command;\n   ipckRequest->request.args.ioctl.inputBuffer = nullptr;\n   ipckRequest->request.args.ioctl.inputLength = inputLength;\n   ipckRequest->request.args.ioctl.outputBuffer = nullptr;\n   ipckRequest->request.args.ioctl.outputLength = outputLength;\n\n   ipckRequest->buffer1 = inputBuffer;\n   ipckRequest->buffer2 = outputBuffer;\n   return ios::Error::OK;\n}\n\nstatic ios::Error\nsendFIFOToKernel(virt_ptr<IPCLDriver> driver)\n{\n   auto error = ios::Error::OK;\n   auto poppedRequest = virt_ptr<IPCLDriverRequest> { nullptr };\n\n   if (driver->status != IPCLDriverStatus::Open) {\n      return error;\n   }\n\n   while (error == ios::Error::OK) {\n      error = IPCLDriver_PeekFIFO(virt_addrof(driver->outboundFifo),\n                                    virt_addrof(driver->currentSendTransaction));\n      if (error < ios::Error::OK) {\n         break;\n      }\n\n      driver->status = IPCLDriverStatus::Submitting;\n      error = ipckDriverLoaderSubmitRequest(driver->currentSendTransaction->ipckRequestBuffer);\n      if (error == ios::Error::OK) {\n         IPCLDriver_FIFOPop(virt_addrof(driver->outboundFifo), &poppedRequest);\n         decaf_check(poppedRequest == driver->currentSendTransaction);\n      }\n      driver->status = IPCLDriverStatus::Open;\n   }\n\n   return error;\n}\n\nios::Error\nIPCLDriver_SubmitRequestBlock(virt_ptr<IPCLDriver> driver,\n                              virt_ptr<IPCLDriverRequest> request)\n{\n   // Flush out any pending requests\n   sendFIFOToKernel(driver);\n\n   auto error = IPCLDriver_FIFOPush(virt_addrof(driver->outboundFifo), request);\n   if (error < ios::Error::OK) {\n      driver->failedRequestSubmitOutboundFIFOFull++;\n\n      // Try flush again\n      sendFIFOToKernel(driver);\n   } else {\n      driver->requestsSubmitted++;\n      sendFIFOToKernel(driver);\n      error = ios::Error::OK;\n   }\n\n   return error;\n}\n\nios::Error\nIPCLDriver_IoctlAsync(ios::Handle handle,\n                      uint32_t command,\n                      virt_ptr<const void> inputBuffer,\n                      uint32_t inputLength,\n                      virt_ptr<void> outputBuffer,\n                      uint32_t outputLength,\n                      IPCLAsyncCallbackFn callback,\n                      virt_ptr<void> callbackContext)\n{\n   if (!inputBuffer && inputLength > 0) {\n      return ios::Error::InvalidArg;\n   }\n\n   if (!outputBuffer && outputLength > 0) {\n      return ios::Error::InvalidArg;\n   }\n\n   auto driver = virt_ptr<IPCLDriver> { nullptr };\n   auto error = IPCLDriver_GetInstance(&driver);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   auto request = virt_ptr<IPCLDriverRequest> { nullptr };\n   error = IPCLDriver_AllocateRequestBlock(driver,\n                                           &request,\n                                           handle,\n                                           ios::Command::Ioctl,\n                                           callback,\n                                           callbackContext);\n   if (error < ios::Error::OK) {\n      return error;\n   }\n\n   error = IPCLDriver_ProcessIOSIoctlRequest(driver,\n                                             request,\n                                             command,\n                                             inputBuffer,\n                                             inputLength,\n                                             outputBuffer,\n                                             outputLength);\n\n   if (error >= ios::Error::OK) {\n      error = IPCLDriver_SubmitRequestBlock(driver, request);\n   }\n\n   if (error < ios::Error::OK) {\n      IPCLDriver_FreeRequestBlock(driver, request);\n      driver->iosIoctlAsyncRequestSubmitFail++;\n   } else {\n      driver->iosIoctlAsyncRequestSubmitSuccess++;\n   }\n\n   return error;\n}\n\nvoid\ninitialiseIpclDriverStaticData()\n{\n   sIpclData = allocStaticData<StaticIpclData>();\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_ipcldriver.h",
    "content": "#pragma once\n#include \"cafe_loader_ipcldriverfifo.h\"\n\n#include \"cafe/kernel/cafe_kernel_ipckdriver.h\"\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_ipc.h\"\n\n#include <cstdint>\n#include <common/cbool.h>\n#include <functional>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader::internal\n{\n\n#ifndef DECAF_LOADER_LLE\nusing IPCLAsyncCallbackFn = void(*)(ios::Error, virt_ptr<void>);\n#endif\n\n#pragma pack(push, 1)\n\nconstexpr auto IPCLBufferCount = 0x4u;\n\nenum class IPCLDriverStatus : uint32_t\n{\n   Invalid = 0,\n   Closed = 1,\n   Initialised = 2,\n   Open = 3,\n   Submitting = 4,\n};\n\nstruct IPCLDriverRequest\n{\n   be2_val<BOOL> allocated;\n#ifdef DECAF_LOADER_LLE\n   be2_val<uint32_t> asyncCallback;\n   be2_virt_ptr<void> asyncCallbackData;\n   UNKNOWN(4);\n#else\n   IPCLAsyncCallbackFn asyncCallback;\n   be2_virt_ptr<void> asyncCallbackData;\n#endif\n   be2_virt_ptr<cafe::kernel::IPCKDriverRequest> ipckRequestBuffer;\n};\nCHECK_OFFSET(IPCLDriverRequest, 0x00, allocated);\nCHECK_OFFSET(IPCLDriverRequest, 0x04, asyncCallback);\n#ifdef DECAF_LOADER_LLE\nCHECK_OFFSET(IPCLDriverRequest, 0x08, asyncCallbackData);\n#endif\nCHECK_OFFSET(IPCLDriverRequest, 0x10, ipckRequestBuffer);\nCHECK_SIZE(IPCLDriverRequest, 0x14);\n\nstruct IPCLDriver\n{\n   be2_val<IPCLDriverStatus> status;\n   UNKNOWN(0x4);\n   be2_val<uint32_t> coreId;\n   be2_virt_ptr<cafe::kernel::IPCKDriverRequest> ipckRequestBuffer;\n   be2_virt_ptr<IPCLDriverRequest> currentSendTransaction;\n   be2_val<uint32_t> iosOpenRequestFail;\n   be2_val<uint32_t> iosOpenRequestSuccess;\n   be2_val<uint32_t> iosOpenAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosOpenAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosOpenAsyncRequestFail;\n   be2_val<uint32_t> iosOpenAsyncRequestSuccess;\n   be2_val<uint32_t> iosCloseRequestFail;\n   be2_val<uint32_t> iosCloseRequestSuccess;\n   be2_val<uint32_t> iosCloseAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosCloseAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosCloseAsyncRequestFail;\n   be2_val<uint32_t> iosCloseAsyncRequestSuccess;\n   be2_val<uint32_t> iosReadRequestFail;\n   be2_val<uint32_t> iosReadRequestSuccess;\n   be2_val<uint32_t> iosReadAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosReadAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosReadAsyncRequestFail;\n   be2_val<uint32_t> iosReadAsyncRequestSuccess;\n   be2_val<uint32_t> iosWriteRequestFail;\n   be2_val<uint32_t> iosWriteRequestSuccess;\n   be2_val<uint32_t> iosWriteAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosWriteAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosWriteAsyncRequestFail;\n   be2_val<uint32_t> iosWriteAsyncRequestSuccess;\n   be2_val<uint32_t> iosSeekRequestFail;\n   be2_val<uint32_t> iosSeekRequestSuccess;\n   be2_val<uint32_t> iosSeekAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosSeekAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosSeekAsyncRequestFail;\n   be2_val<uint32_t> iosSeekAsyncRequestSuccess;\n   be2_val<uint32_t> iosIoctlRequestFail;\n   be2_val<uint32_t> iosIoctlRequestSuccess;\n   be2_val<uint32_t> iosIoctlAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosIoctlAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosIoctlAsyncRequestFail;\n   be2_val<uint32_t> iosIoctlAsyncRequestSuccess;\n   be2_val<uint32_t> iosIoctlvRequestFail;\n   be2_val<uint32_t> iosIoctlvRequestSuccess;\n   be2_val<uint32_t> iosIoctlvAsyncRequestSubmitFail;\n   be2_val<uint32_t> iosIoctlvAsyncRequestSubmitSuccess;\n   be2_val<uint32_t> iosIoctlvAsyncRequestFail;\n   be2_val<uint32_t> iosIoctlvAsyncRequestSuccess;\n   be2_val<uint32_t> requestsProcessed;\n   be2_val<uint32_t> requestsSubmitted;\n   be2_val<uint32_t> repliesReceived;\n   be2_val<uint32_t> asyncTransactionsCompleted;\n   UNKNOWN(4);\n   be2_val<uint32_t> syncTransactionsCompleted;\n   be2_val<uint32_t> invalidReplyAddress;\n   be2_val<uint32_t> unexpectedReplyInterrupt;\n   be2_val<uint32_t> unexpectedAckInterrupt;\n   be2_val<uint32_t> invalidReplyMessagePointer;\n   be2_val<uint32_t> invalidReplyMessagePointerNotAlloc;\n   be2_val<uint32_t> invalidReplyCommand;\n   be2_val<uint32_t> failedAllocateRequestBlock;\n   be2_val<uint32_t> failedFreeRequestBlock;\n   be2_val<uint32_t> failedRequestSubmitOutboundFIFOFull;\n   be2_struct<IPCLDriverFIFO<IPCLBufferCount>> freeFifo;\n   be2_struct<IPCLDriverFIFO<IPCLBufferCount>> outboundFifo;\n   be2_array<IPCLDriverRequest, IPCLBufferCount> requests;\n};\nCHECK_OFFSET(IPCLDriver, 0x00, status);\nCHECK_OFFSET(IPCLDriver, 0x08, coreId);\nCHECK_OFFSET(IPCLDriver, 0x0C, ipckRequestBuffer);\nCHECK_OFFSET(IPCLDriver, 0x10, currentSendTransaction);\nCHECK_OFFSET(IPCLDriver, 0x14, iosOpenRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x18, iosOpenRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x1C, iosOpenAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCLDriver, 0x20, iosOpenAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCLDriver, 0x24, iosOpenAsyncRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x28, iosOpenAsyncRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x2C, iosCloseRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x30, iosCloseRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x34, iosCloseAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCLDriver, 0x38, iosCloseAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCLDriver, 0x3C, iosCloseAsyncRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x40, iosCloseAsyncRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x44, iosReadRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x48, iosReadRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x4C, iosReadAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCLDriver, 0x50, iosReadAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCLDriver, 0x54, iosReadAsyncRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x58, iosReadAsyncRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x5C, iosWriteRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x60, iosWriteRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x64, iosWriteAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCLDriver, 0x68, iosWriteAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCLDriver, 0x6C, iosWriteAsyncRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x70, iosWriteAsyncRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x74, iosSeekRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x78, iosSeekRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x7C, iosSeekAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCLDriver, 0x80, iosSeekAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCLDriver, 0x84, iosSeekAsyncRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x88, iosSeekAsyncRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x8C, iosIoctlRequestFail);\nCHECK_OFFSET(IPCLDriver, 0x90, iosIoctlRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0x94, iosIoctlAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCLDriver, 0x98, iosIoctlAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCLDriver, 0x9C, iosIoctlAsyncRequestFail);\nCHECK_OFFSET(IPCLDriver, 0xA0, iosIoctlAsyncRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0xA4, iosIoctlvRequestFail);\nCHECK_OFFSET(IPCLDriver, 0xA8, iosIoctlvRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0xAC, iosIoctlvAsyncRequestSubmitFail);\nCHECK_OFFSET(IPCLDriver, 0xB0, iosIoctlvAsyncRequestSubmitSuccess);\nCHECK_OFFSET(IPCLDriver, 0xB4, iosIoctlvAsyncRequestFail);\nCHECK_OFFSET(IPCLDriver, 0xB8, iosIoctlvAsyncRequestSuccess);\nCHECK_OFFSET(IPCLDriver, 0xBC, requestsProcessed);\nCHECK_OFFSET(IPCLDriver, 0xC0, requestsSubmitted);\nCHECK_OFFSET(IPCLDriver, 0xC4, repliesReceived);\nCHECK_OFFSET(IPCLDriver, 0xC8, asyncTransactionsCompleted);\nCHECK_OFFSET(IPCLDriver, 0xD0, syncTransactionsCompleted);\nCHECK_OFFSET(IPCLDriver, 0xD4, invalidReplyAddress);\nCHECK_OFFSET(IPCLDriver, 0xD8, unexpectedReplyInterrupt);\nCHECK_OFFSET(IPCLDriver, 0xDC, unexpectedAckInterrupt);\nCHECK_OFFSET(IPCLDriver, 0xE0, invalidReplyMessagePointer);\nCHECK_OFFSET(IPCLDriver, 0xE4, invalidReplyMessagePointerNotAlloc);\nCHECK_OFFSET(IPCLDriver, 0xE8, invalidReplyCommand);\nCHECK_OFFSET(IPCLDriver, 0xEC, failedAllocateRequestBlock);\nCHECK_OFFSET(IPCLDriver, 0xF0, failedFreeRequestBlock);\nCHECK_OFFSET(IPCLDriver, 0xF4, failedRequestSubmitOutboundFIFOFull);\nCHECK_OFFSET(IPCLDriver, 0xF8, freeFifo);\nCHECK_OFFSET(IPCLDriver, 0x118, outboundFifo);\nCHECK_OFFSET(IPCLDriver, 0x138, requests);\nCHECK_SIZE(IPCLDriver, 0x188);\n\n#pragma pack(pop)\n\nbool\nIPCLDriver_IsInitialised();\n\nios::Error\nIPCLDriver_Init();\n\nios::Error\nIPCLDriver_Open();\n\nios::Error\nIPCLDriver_GetInstance(virt_ptr<IPCLDriver> *outDriver);\n\nios::Error\nIPCLDriver_ProcessReply(virt_ptr<IPCLDriver> driver,\n                        virt_ptr<cafe::kernel::IPCKDriverRequest> ipckRequest);\n\nios::Error\nIPCLDriver_IoctlAsync(ios::Handle handle,\n                      uint32_t command,\n                      virt_ptr<const void> inputBuffer,\n                      uint32_t inputLength,\n                      virt_ptr<void> outputBuffer,\n                      uint32_t outputLength,\n                      IPCLAsyncCallbackFn callback,\n                      virt_ptr<void> callbackContext);\n\nvoid\ninitialiseIpclDriverStaticData();\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_ipcldriverfifo.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_ipc.h\"\n#include \"cafe/kernel/cafe_kernel_ipckdriver.h\"\n\n#include <cstdint>\n#include <common/cbool.h>\n#include <functional>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader::internal\n{\n\n#pragma pack(push, 1)\n\nstruct IPCLDriverRequest;\n\n/**\n * FIFO queue for IPCLDriverRequests.\n *\n * Functions similar to a ring buffer.\n */\ntemplate<size_t MaxSize>\nstruct IPCLDriverFIFO\n{\n   //! The current item index to push to\n   be2_val<int32_t> pushIndex;\n\n   //! The current item index to pop from\n   be2_val<int32_t> popIndex;\n\n   //! The number of items in the queue\n   be2_val<int32_t> count;\n\n   //! Tracks the highest amount of items there has been in the queue\n   be2_val<int32_t> maxCount;\n\n   //! Items in the queue\n   be2_array<virt_ptr<IPCLDriverRequest>, MaxSize> requests;\n};\nCHECK_OFFSET(IPCLDriverFIFO<4>, 0x00, pushIndex);\nCHECK_OFFSET(IPCLDriverFIFO<4>, 0x04, popIndex);\nCHECK_OFFSET(IPCLDriverFIFO<4>, 0x08, count);\nCHECK_OFFSET(IPCLDriverFIFO<4>, 0x0C, maxCount);\nCHECK_OFFSET(IPCLDriverFIFO<4>, 0x10, requests);\nCHECK_SIZE(IPCLDriverFIFO<4>, 0x20);\n\n#pragma pack(pop)\n\n\n/**\n * Initialise a IPCLDriverFIFO structure.\n */\ntemplate<size_t MaxSize>\ninline void\nIPCLDriver_FIFOInit(virt_ptr<IPCLDriverFIFO<MaxSize>> fifo)\n{\n   fifo->pushIndex = 0;\n   fifo->popIndex = -1;\n   fifo->count = 0;\n   fifo->requests.fill(nullptr);\n}\n\n\n/**\n * Push a request into an IPCLDriverFIFO structure\n *\n * \\retval ios::Error::OK\n * Success.\n *\n * \\retval ios::Error::QFull\n * There was no free space in the queue to push the request.\n */\ntemplate<size_t MaxSize>\ninline ios::Error\nIPCLDriver_FIFOPush(virt_ptr<IPCLDriverFIFO<MaxSize>> fifo,\n                    virt_ptr<IPCLDriverRequest> request)\n{\n   if (fifo->pushIndex == fifo->popIndex) {\n      return ios::Error::QFull;\n   }\n\n   fifo->requests[fifo->pushIndex] = request;\n\n   if (fifo->popIndex == -1) {\n      fifo->popIndex = fifo->pushIndex;\n   }\n\n   fifo->count += 1;\n   fifo->pushIndex = static_cast<int32_t>((fifo->pushIndex + 1) % MaxSize);\n\n   if (fifo->count > fifo->maxCount) {\n      fifo->maxCount = fifo->count;\n   }\n\n   return ios::Error::OK;\n}\n\n\n/**\n * Pop a request into an IPCLDriverFIFO structure.\n *\n * \\retval ios::Error::OK\n * Success.\n *\n * \\retval ios::Error::QEmpty\n * There was no requests to pop from the queue.\n */\ntemplate<size_t MaxSize>\ninline ios::Error\nIPCLDriver_FIFOPop(virt_ptr<IPCLDriverFIFO<MaxSize>> fifo,\n                   virt_ptr<IPCLDriverRequest> *outRequest)\n{\n   if (fifo->popIndex == -1) {\n      return ios::Error::QEmpty;\n   }\n\n   auto request = fifo->requests[fifo->popIndex];\n   fifo->count -= 1;\n\n   if (fifo->count == 0) {\n      fifo->popIndex = -1;\n   } else {\n      fifo->popIndex = static_cast<int32_t>((fifo->popIndex + 1) % MaxSize);\n   }\n\n   *outRequest = request;\n   return ios::Error::OK;\n}\n\n\n/**\n * Peek the next request which would be popped from a IPCLDriverFIFO structure.\n *\n * \\retval ios::Error::OK\n * Success.\n *\n * \\retval ios::Error::QEmpty\n * There was no requests to pop from the queue.\n */\ntemplate<size_t MaxSize>\nios::Error\nIPCLDriver_PeekFIFO(virt_ptr<IPCLDriverFIFO<MaxSize>> fifo,\n                    virt_ptr<virt_ptr<IPCLDriverRequest>> outRequest)\n{\n   if (fifo->popIndex == -1) {\n      return ios::Error::QEmpty;\n   }\n\n   *outRequest = fifo->requests[fifo->popIndex];\n   return ios::Error::OK;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_link.cpp",
    "content": "#include \"cafe_loader_error.h\"\n#include \"cafe_loader_globals.h\"\n#include \"cafe_loader_heap.h\"\n#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_loaded_rpl.h\"\n#include \"cafe_loader_link.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_purge.h\"\n#include \"cafe_loader_reloc.h\"\n#include \"cafe_loader_query.h\"\n#include \"cafe_loader_utils.h\"\n\n#include <algorithm>\n#include <cctype>\n#include <libcpu/cpu_formatters.h>\n\nnamespace cafe::loader::internal\n{\n\nstatic virt_ptr<LOADED_RPL>\nLiFindRPLByName(virt_ptr<char> name)\n{\n   char buffer[64];\n   auto resolvedName = LiResolveModuleName(name.get());\n   if (resolvedName.size() >= 64) {\n      resolvedName = resolvedName.substr(0, 63);\n   }\n\n   std::transform(resolvedName.begin(), resolvedName.end(),\n                  buffer,\n                  [](char x) { return static_cast<char>(::tolower(x)); });\n   buffer[resolvedName.size()] = 0;\n   resolvedName = buffer;\n\n   auto globals = getGlobalStorage();\n   for (auto module = globals->firstLoadedRpl; module; module = module->nextLoadedRpl) {\n      if (module->moduleNameLen != resolvedName.size()) {\n         continue;\n      }\n\n      if (resolvedName.compare(module->moduleNameBuffer.get()) == 0) {\n         return module;\n      }\n   }\n\n   return nullptr;\n}\n\nstatic void\nsReportCodeHeap(virt_ptr<GlobalStorage> globals,\n                const char *msg)\n{\n}\n\nstatic int32_t\nsCheckOne(virt_ptr<LOADED_RPL> module)\n{\n   module->loadStateFlags |= LoaderStateFlag2;\n\n   // Get the section header string table\n   if (!module->elfHeader.shstrndx) {\n      Loader_ReportError(\n         \"*** Error: Could not get section string table index for \\\"{}\\\".\",\n         module->moduleNameBuffer);\n      LiSetFatalError(0x18729Bu, module->fileType, 1, \"sCheckOne\", 73);\n      return -470071;\n   }\n\n   auto shStrAddr = module->sectionAddressBuffer[module->elfHeader.shstrndx];\n   if (!shStrAddr) {\n      Loader_ReportError(\n         \"*** Error: Could not get section string table for \\\"{}\\\".\",\n         module->moduleNameBuffer);\n      LiSetFatalError(0x18729Bu, module->fileType, 1, \"sCheckOne\", 65);\n      return -470072;\n   }\n\n   for (auto i = 0u; i < module->elfHeader.shnum; ++i) {\n      auto sectionHeader = getSectionHeader(module, i);\n      if (sectionHeader->type != rpl::SHT_RPL_IMPORTS) {\n         continue;\n      }\n\n      auto name = virt_cast<char *>(shStrAddr + sectionHeader->name) + 9;\n      auto rpl = LiFindRPLByName(name);\n      if (!rpl) {\n         Loader_ReportError(\"*** Imp Sec num {} @ 0x{}\", i, sectionHeader);\n         Loader_ReportError(\"*** Imp Name 0x{:02X} 0x{:02X} 0x{:02X} 0x{:02X}\",\n                            name[0], name[1], name[2], name[3]);\n         Loader_ReportError(\n            \"*** Error. Could not find module \\\"{}\\\" during linking of \\\"{}\\\"!\",\n            name, module->moduleNameBuffer);\n         LiSetFatalError(0x18729Bu, module->fileType, 1, \"sCheckOne\", 99);\n         return -470021;\n      }\n\n      if (rpl->loadStateFlags & LoaderStateFlag2) {\n         rpl->loadStateFlags |= LoaderStateFlag8;\n         module->loadStateFlags |= LoaderStateFlag8;\n      }\n\n      if (!rpl->entryPoint && !(rpl->loadStateFlags & LoaderStateFlag8)) {\n         auto error = sCheckOne(rpl);\n         if (error) {\n            return error;\n         }\n      }\n   }\n\n   module->loadStateFlags &= ~LoaderStateFlag2;\n   return 0;\n}\n\nstatic int32_t\nsCheckCircular(uint32_t numModules,\n               virt_ptr<virt_ptr<LOADED_RPL>> modules,\n               uint32_t checkIndex)\n{\n   for (auto i = 0u; i < numModules; ++i) {\n      modules[i]->loadStateFlags &= ~LoaderStateFlag2;\n   }\n\n   auto result = sCheckOne(modules[checkIndex]);\n\n   for (auto i = 0u; i < numModules; ++i) {\n      modules[i]->loadStateFlags &= ~LoaderStateFlag2;\n   }\n\n   return result;\n}\n\nstatic int32_t\nsValidateLinkData(virt_ptr<GlobalStorage> globals,\n                  virt_ptr<LOADER_LinkInfo> linkInfo,\n                  uint32_t linkInfoSize,\n                  virt_ptr<virt_ptr<LOADED_RPL>> *outRplPointers,\n                  uint32_t *outRplPointersAllocSize)\n{\n   if (auto error = LiValidateAddress(linkInfo,\n                                      linkInfoSize, 3,\n                                      -470022,\n                                      virt_addr { 0x10000000 },\n                                      virt_addr { 0xC0000000 },\n                                      \"link data\")) {\n      LiSetFatalError(0x18729Bu, 0, 1, \"sValidateLinkData\", 165);\n      return error;\n   }\n\n   if (linkInfoSize < sizeof(LOADER_LinkInfo)) {\n      Loader_ReportError(\"*** invalid link data size.\");\n      LiSetFatalError(0x18729Bu, 0, 1, \"sValidateLinkData\", 174);\n      return -470020;\n   }\n\n   if (linkInfo->size != linkInfoSize) {\n      Loader_ReportError(\"*** incorrect link data size.\");\n      LiSetFatalError(0x18729Bu, 0, 1, \"sValidateLinkData\", 183);\n      return -470020;\n   }\n\n   if (!linkInfo->numModules) {\n      Loader_ReportError(\"*** incorrect # of modules being linked.\");\n      LiSetFatalError(0x18729Bu, 0, 1, \"sValidateLinkData\", 191);\n      return -470022;\n   }\n\n   if (sizeof(LOADER_LinkModule) * linkInfo->numModules + 8 != linkInfo->size) {\n      Loader_ReportError(\"*** link data size does not match calculation.\");\n      LiSetFatalError(0x18729Bu, 0, 1, \"sValidateLinkData\", 203);\n      return -470023;\n   }\n\n   auto allocPtr = virt_ptr<void> { nullptr };\n   auto allocSize = uint32_t { 0 };\n   auto largestFree = uint32_t { 0 };\n   if (auto error = LiCacheLineCorrectAllocEx(globals->processCodeHeap,\n                                              4 * linkInfo->numModules,\n                                              4,\n                                              &allocPtr, 1, &allocSize,\n                                              &largestFree,\n                                              ios::mcp::MCPFileType::ProcessCode)) {\n      Loader_ReportError(\n         \"*** memory allocation failed {} bytes, for list of LOADED_RPL pointers mNumModules = {};  (needed {}, available {}).\",\n         4 * linkInfo->numModules, linkInfo->numModules, allocSize, largestFree);\n      LiSetFatalError(0x187298u, 0, 0, \"sValidateLinkData\", 214);\n      return -470021;\n   }\n\n   // Generate the rpl pointer list\n   auto rplPointers = virt_cast<virt_ptr<LOADED_RPL> *>(allocPtr);\n   for (auto i = 0u; i < linkInfo->numModules; ++i) {\n      auto rpl = virt_ptr<LOADED_RPL> { nullptr };\n      if (!linkInfo->modules[i].loaderHandle) {\n         rpl = globals->loadedRpx;\n      } else {\n         rpl = getModule(linkInfo->modules[i].loaderHandle);\n         if (!rpl) {\n            Loader_ReportError(\"*** Module with base {} not found in attempt to link.\",\n                               linkInfo->modules[i].loaderHandle);\n            LiSetFatalError(0x18729Bu, 0, 1, \"sValidateLinkData\", 239);\n            LiCacheLineCorrectFreeEx(globals->processCodeHeap, allocPtr, allocSize);\n            return -470022;\n         }\n      }\n\n      rplPointers[i] = rpl;\n\n      if (rpl->loadStateFlags & LoaderStateFlags_Unk0x20000000) {\n         continue;\n      }\n\n      if (!(rpl->loadStateFlags & LoaderSetup)) {\n         Loader_ReportError(\"*** Module with base {} has not been set up.\",\n                            linkInfo->modules[i].loaderHandle);\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sValidateLinkData\", 251);\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap, allocPtr, allocSize);\n         return -470023;\n      }\n\n      if (rpl->entryPoint) {\n         Loader_ReportError(\"*** Module with base 0x{:08X} has already been linked.\",\n                            linkInfo->modules[i].loaderHandle);\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sValidateLinkData\", 259);\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap, allocPtr, allocSize);\n         return - 470024;\n      }\n   }\n\n   for (auto i = 0u; i < linkInfo->numModules; ++i) {\n      if (rplPointers[i]->loadStateFlags & LoaderStateFlags_Unk0x20000000) {\n         continue;\n      }\n\n      if (auto error = sCheckCircular(linkInfo->numModules, rplPointers, i)) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap, allocPtr, allocSize);\n         return error;\n      }\n   }\n\n   *outRplPointers = rplPointers;\n   *outRplPointersAllocSize = allocSize;\n   return 0;\n}\n\nstatic void\nsSetLinkOutput(virt_ptr<LOADER_LinkInfo> linkInfo,\n               virt_ptr<virt_ptr<LOADED_RPL>> linkModules)\n{\n   for (auto i = 0u; i < linkInfo->numModules; ++i) {\n      auto &linkOutput = linkInfo->modules[i];\n      auto &linkModule = linkModules[i];\n\n      if (!linkModule->entryPoint) {\n         Loader_Panic(0x130008,\n                      \"*** Linker trying to return successful output when a module ({}) is not linked!\",\n                      linkModule->moduleNameBuffer);\n      }\n\n      linkOutput.entryPoint = linkModule->entryPoint;\n      linkOutput.textAddr = linkModule->textAddr;\n      linkOutput.textOffset = linkModule->textOffset;\n      linkOutput.textSize = linkModule->textSize;\n      linkOutput.dataAddr = linkModule->dataAddr;\n      linkOutput.dataOffset = linkModule->dataOffset;\n      linkOutput.dataSize = linkModule->dataSize;\n      linkOutput.loadAddr = linkModule->loadAddr;\n      linkOutput.loadOffset = linkModule->loadOffset;\n      linkOutput.loadSize = linkModule->loadSize;\n   }\n}\n\nint32_t\nLOADER_Link(kernel::UniqueProcessId upid,\n            virt_ptr<LOADER_LinkInfo> linkInfo,\n            uint32_t linkInfoSize,\n            virt_ptr<LOADER_MinFileInfo> minFileInfo)\n{\n   auto error = int32_t { 0 };\n   auto globals = getGlobalStorage();\n   if (globals->currentUpid != upid) {\n      Loader_ReportError(\"*** Loader address space not set for process {} but called for {}.\",\n                         static_cast<int>(globals->currentUpid.value()),\n                         static_cast<int>(upid));\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_Link\", 731);\n      return Error::DifferentProcess;\n   }\n\n   auto linkModules = virt_ptr<virt_ptr<LOADED_RPL>> { nullptr };\n   auto linkModulesAllocSize = uint32_t { 0 };\n   error = sValidateLinkData(globals, linkInfo, linkInfoSize, &linkModules, &linkModulesAllocSize);\n   if (error) {\n      Loader_ReportError(\"*** Link Data not valid.\");\n      sReportCodeHeap(globals, \"link done\");\n      return error;\n   }\n\n   error = LiValidateMinFileInfo(minFileInfo, \"LOADER_Link\");\n   if (error) {\n      sReportCodeHeap(globals, \"link done\");\n      return error;\n   }\n\n   auto numUnlinkedModules = 0u;\n   for (auto i = 0u; i < linkInfo->numModules; ++i) {\n      if (!(linkModules[i]->loadStateFlags & LoaderStateFlags_Unk0x20000000)) {\n         ++numUnlinkedModules;\n      }\n   }\n\n   if (!numUnlinkedModules) {\n      sSetLinkOutput(linkInfo, linkModules);\n      LiCacheLineCorrectFreeEx(globals->processCodeHeap, linkModules, linkModulesAllocSize);\n      sReportCodeHeap(globals, \"link done\");\n      return 0;\n   }\n\n   auto allocSize = uint32_t { 0 };\n   auto largestFree = uint32_t { 0 };\n   auto allocPtr = virt_ptr<void> { nullptr };\n   error = LiCacheLineCorrectAllocEx(globals->processCodeHeap,\n                                     4 * numUnlinkedModules,\n                                     4,\n                                     &allocPtr, 1,\n                                     &allocSize,\n                                     &largestFree,\n                                     ios::mcp::MCPFileType::ProcessCode);\n   if (error) {\n      Loader_ReportError(\n         \"*** memory allocation failed {} bytes, for list of LOADED_RPL pointers actNumLink = {};  (needed {}, available {}).\",\n         4 * numUnlinkedModules,\n         numUnlinkedModules,\n         allocSize,\n         largestFree);\n      LiCacheLineCorrectFreeEx(globals->processCodeHeap, linkModules, linkModulesAllocSize);\n      LiSetFatalError(0x187298u, 0, 0, \"LOADER_Link\", 773);\n      return -470021;\n   }\n\n   auto unlinkedModules = virt_cast<virt_ptr<LOADED_RPL> *>(allocPtr);\n   auto unlinkedModulesSize = allocSize;\n   numUnlinkedModules = 0u;\n   for (auto i = 0u; i < linkInfo->numModules; ++i) {\n      if (!(linkModules[i]->loadStateFlags & LoaderStateFlags_Unk0x20000000)) {\n         unlinkedModules[numUnlinkedModules] = linkModules[i];\n         unlinkedModules[numUnlinkedModules]->loadStateFlags |= LoaderStateFlag2;\n         ++numUnlinkedModules;\n      }\n   }\n\n   sReportCodeHeap(globals, \"fixup start\");\n\n   auto maxShnum = 0u;\n   auto numLoadStateFlag8 = 0u;\n   auto importTracking = virt_ptr<LiImportTracking> { nullptr };\n   auto importTrackingSize = uint32_t { 0 };\n   auto remainingUnlinkedModules = 0u;\n   auto unlinkedModuleIndex = 0u;\n\n   for (auto i = 0u; i < numUnlinkedModules; ++i) {\n      if (unlinkedModules[i]->elfHeader.shnum > maxShnum) {\n         maxShnum = unlinkedModules[i]->elfHeader.shnum;\n      }\n\n      if (unlinkedModules[i]->loadStateFlags & LoaderStateFlag8) {\n         numLoadStateFlag8++;\n      }\n   }\n\n   // Allocate import tracking\n   error = LiCacheLineCorrectAllocEx(globals->processCodeHeap,\n                                     sizeof(LiImportTracking) * maxShnum,\n                                     4,\n                                     &allocPtr,\n                                     1,\n                                     &allocSize,\n                                     &largestFree,\n                                     ios::mcp::MCPFileType::ProcessCode);\n   if (error) {\n      Loader_ReportError(\n         \"*** Could not allocate space for largest # of sections ({});  (needed {}, available {}).\",\n         maxShnum, allocSize, largestFree);\n      goto out;\n   }\n\n   importTracking = virt_cast<LiImportTracking *>(allocPtr);\n   importTrackingSize = allocSize;\n\n   if (numLoadStateFlag8 != 0) {\n      for (auto i = 0u; i < numUnlinkedModules; ++i) {\n         LiCheckAndHandleInterrupts();\n         auto module = unlinkedModules[i];\n         if (module->loadStateFlags & LoaderStateFlag8) {\n            if (!module->elfHeader.shstrndx) {\n               Loader_ReportError(\n                  \"*** Error: Could not get section string table index for \\\"{}\\\".\",\n                  module->moduleNameBuffer);\n               LiSetFatalError(0x18729Bu, module->fileType, 1, \"LOADER_Link\", 839);\n               error = -470071;\n               goto out;\n            }\n\n            auto shStrAddr = module->sectionAddressBuffer[module->elfHeader.shstrndx];\n            if (!shStrAddr) {\n               Loader_ReportError(\n                  \"*** Error: Could not get section string table for \\\"{}\\\".\",\n                  module->moduleNameBuffer);\n               LiSetFatalError(0x18729Bu, module->fileType, 1, \"LOADER_Link\", 831);\n               error = -470072;\n               goto out;\n            }\n\n            for (auto j = 1u; j < module->elfHeader.shnum; ++j) {\n               auto sectionHeader = getSectionHeader(module, j);\n               if (sectionHeader->type == rpl::SHT_RPL_IMPORTS) {\n                  auto name = virt_cast<char *>(shStrAddr + sectionHeader->name) + 9;\n                  auto rpl = LiFindRPLByName(name);\n                  if (!rpl) {\n                     Loader_ReportError(\"*** Error: Could not get imported RPL name.\");\n                     LiSetFatalError(0x18729Bu, module->fileType, 1, \"LOADER_Link\", 854);\n                     error = -470073;\n                     goto out;\n                  }\n\n                  if ((rpl->loadStateFlags & LoaderStateFlag2) &&\n                      (rpl->loadStateFlags & LoaderStateFlag8)) {\n                     error = LiFixupRelocOneRPL(rpl, nullptr, 1);\n                     if (error) {\n                        Loader_ReportError(\n                           \"*** Error. Could not find module \\\"{}\\\" during linking!\",\n                           name);\n                        LiSetFatalError(0x18729Bu, module->fileType, 1, \"LOADER_Link\", 863);\n                        error = -470010;\n                        goto out;\n                     }\n                  }\n               }\n            }\n         }\n      }\n   }\n\n   // PLEASE SOMEONE TELL ME WHAT THIS LOAD STATE FLAGS MEANS!!\n   for (auto i = 0u; i < numUnlinkedModules; ++i) {\n      unlinkedModules[i]->loadStateFlags |= LoaderStateFlag2;\n   }\n\n   // Loop through trying to link everything\n   remainingUnlinkedModules = numUnlinkedModules;\n   unlinkedModuleIndex = 0u;\n\n   while (remainingUnlinkedModules > 0) {\n      LiCheckAndHandleInterrupts();\n      auto module = unlinkedModules[unlinkedModuleIndex];\n\n      if (module->loadStateFlags & LoaderStateFlag2) {\n         std::memset(importTracking.get(), 0,\n                     sizeof(LiImportTracking) * module->elfHeader.shnum);\n\n         if (!module->elfHeader.shstrndx) {\n            Loader_ReportError(\n               \"*** Error: Could not get section string table index for \\\"{}\\\".\",\n               module->moduleNameBuffer);\n            LiSetFatalError(0x18729Bu, module->fileType, 1, \"LOADER_Link\", 927);\n            error = -470071;\n            goto out;\n         }\n\n         auto shStrAddr = module->sectionAddressBuffer[module->elfHeader.shstrndx];\n         if (!shStrAddr) {\n            Loader_ReportError(\n               \"*** Error: Could not get section string table for \\\"{}\\\".\",\n               module->moduleNameBuffer);\n            LiSetFatalError(0x18729Bu, module->fileType, 1, \"LOADER_Link\", 919);\n            error = -470072;\n            goto out;\n         }\n\n         auto j = 1u;\n         for (; j < module->elfHeader.shnum; ++j) {\n            auto sectionHeader = getSectionHeader(module, j);\n            if (sectionHeader->type == rpl::SHT_RPL_IMPORTS) {\n               auto name = virt_cast<char *>(shStrAddr + sectionHeader->name) + 9;\n               auto rpl = LiFindRPLByName(name);\n               if (!rpl) {\n                  Loader_ReportError(\n                     \"*** Error. Could not find module \\\"{}\\\" during linking!\",\n                     name);\n                  LiSetFatalError(0x18729Bu, module->fileType, 1, \"LOADER_Link\", 950);\n                  error = -470010;\n                  goto out;\n               }\n\n               if ((rpl->loadStateFlags & LoaderStateFlag2) &&\n                   !(rpl->loadStateFlags & LoaderStateFlag8)) {\n                  break;\n               }\n\n               if (sectionHeader->flags & rpl::SHF_EXECINSTR) {\n                  importTracking[j].numExports = rpl->numFuncExports;\n                  importTracking[j].exports = virt_cast<rpl::Export *>(rpl->funcExports);\n               } else {\n                  importTracking[j].numExports = rpl->numDataExports;\n                  importTracking[j].exports = virt_cast<rpl::Export *>(rpl->dataExports);\n               }\n\n               importTracking[j].tlsModuleIndex = rpl->fileInfoBuffer->tlsModuleIndex;\n               importTracking[j].rpl = rpl;\n            }\n         }\n\n         if (j == module->elfHeader.shnum) {\n            auto unk = (module->loadStateFlags & LoaderStateFlag8) ? 2 : 0;\n            error = LiFixupRelocOneRPL(module, importTracking, unk);\n            if (error) {\n               break;\n            }\n         }\n\n         --remainingUnlinkedModules;\n      }\n\n      unlinkedModuleIndex++;\n      if (unlinkedModuleIndex >= numUnlinkedModules) {\n         unlinkedModuleIndex = 0;\n      }\n   }\n\n   sReportCodeHeap(globals, \"fixup done\");\n   LiCacheLineCorrectFreeEx(globals->processCodeHeap, importTracking,\n                            importTrackingSize);\n\nout:\n   // PLEASE SOMEONE TELL ME WHAT THIS LOAD STATE FLAGS MEANS!!\n   for (auto i = 0u; i < numUnlinkedModules; ++i) {\n      unlinkedModules[i]->loadStateFlags &= ~LoaderStateFlag8;\n   }\n\n   if (error) {\n      // Purge all unlinked modules\n      for (auto i = 0u; i < numUnlinkedModules; ++i) {\n         auto module = virt_ptr<LOADED_RPL> { nullptr };\n         auto prev = virt_ptr<LOADED_RPL> { nullptr };\n\n         for (module = globals->firstLoadedRpl; module; module = module->nextLoadedRpl) {\n            if (module == unlinkedModules[i]) {\n               break;\n            }\n\n            prev = module;\n         }\n\n         if (!module) {\n            Loader_ReportError(\"**** Module disappeared while being linked!\");\n         } else if (!(module->loadStateFlags & LoaderStateFlag4)) {\n            if (!module->nextLoadedRpl) {\n               globals->lastLoadedRpl = prev;\n            }\n\n            if (prev) {\n               prev->nextLoadedRpl = module->nextLoadedRpl;\n            }\n\n            LiPurgeOneUnlinkedModule(module);\n         }\n      }\n   } else {\n      sSetLinkOutput(linkInfo, linkModules);\n   }\n\n   for (auto i = 0u; i < numUnlinkedModules; ++i) {\n      auto module = unlinkedModules[unlinkedModuleIndex];\n      if (!module) {\n         continue;\n      }\n\n      if (module->compressedRelocationsBuffer) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  module->compressedRelocationsBuffer,\n                                  module->compressedRelocationsBufferSize);\n         module->compressedRelocationsBuffer = nullptr;\n      }\n\n      if (module->crcBuffer && module->crcBufferSize) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  module->crcBuffer,\n                                  module->crcBufferSize);\n         module->crcBuffer = nullptr;\n         module->crcBufferSize = 0u;\n         module->sectionAddressBuffer[module->elfHeader.shnum - 2] = virt_addr { 0 };\n      }\n   }\n\n   LiCacheLineCorrectFreeEx(globals->processCodeHeap, linkModules, linkModulesAllocSize);\n   LiCacheLineCorrectFreeEx(globals->processCodeHeap, unlinkedModules, unlinkedModulesSize);\n   sReportCodeHeap(globals, \"link done\");\n   return 0;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_link.h",
    "content": "#pragma once\n#include \"cafe_loader_minfileinfo.h\"\n\nnamespace cafe::loader::internal\n{\n\nint32_t\nLOADER_Link(kernel::UniqueProcessId upid,\n            virt_ptr<LOADER_LinkInfo> linkInfo,\n            uint32_t linkInfoSize,\n            virt_ptr<LOADER_MinFileInfo> minFileInfo);\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_loaded_rpl.h",
    "content": "#pragma once\n#include \"cafe_loader_rpl.h\"\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n#include \"ios/mcp/ios_mcp_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader\n{\n\nenum LoadStateFlags : uint32_t\n{\n   LoaderPrep = 0,\n   LoaderSetup = 1 << 0,\n   LoaderStateFlag2 = 1 << 1,\n   LoaderStateFlag4 = 1 << 2,\n\n   //! Likely means this is a circular dependency\n   LoaderStateFlag8 = 1 << 3,\n\n   //! Likely means linked\n   LoaderStateFlags_Unk0x20000000 = 0x20000000,\n};\n\nstruct LOADER_UserFileInfo;\n\nstruct LOADED_RPL\n{\n   be2_virt_ptr<void> globals;\n   be2_val<uint32_t> selfBufferSize;\n   be2_virt_ptr<char> moduleNameBuffer;\n   be2_val<uint32_t> moduleNameLen;\n   be2_val<uint32_t> moduleNameBufferSize;\n   be2_virt_ptr<void> pathBuffer;\n   be2_val<uint32_t> pathBufferSize;\n   be2_struct<rpl::Header> elfHeader;\n   be2_virt_ptr<void> sectionHeaderBuffer;\n   be2_val<uint32_t> sectionHeaderBufferSize;\n   be2_virt_ptr<rpl::RPLFileInfo_v4_2> fileInfoBuffer;\n   be2_val<uint32_t> fileInfoSize;\n   be2_val<uint32_t> fileInfoBufferSize;\n   be2_virt_ptr<void> crcBuffer;\n   be2_val<uint32_t> crcBufferSize;\n   be2_val<uint32_t> lastSectionCrc;\n   be2_val<LoadStateFlags> loadStateFlags;\n   be2_val<virt_addr> entryPoint;\n   be2_val<uint32_t> upcomingBufferNumber;\n   be2_virt_ptr<void> lastChunkBuffer;\n   be2_val<uint32_t> virtualFileBaseOffset;\n   be2_val<uint32_t> fileOffset;\n   be2_val<uint32_t> upcomingFileOffset;\n   be2_val<ios::mcp::MCPFileType> fileType;\n   be2_val<uint32_t> totalBytesRead;\n   be2_virt_ptr<void> chunkBuffer;\n   UNKNOWN(0xAC - 0x98);\n   be2_val<cafe::kernel::UniqueProcessId> upid;\n   be2_virt_ptr<void> virtualFileBase;\n   be2_virt_ptr<void> textBuffer;\n   be2_val<uint32_t> textBufferSize;\n   be2_virt_ptr<void> dataBuffer;\n   be2_virt_ptr<void> loadBuffer;\n   be2_virt_ptr<void> compressedRelocationsBuffer;\n   be2_val<uint32_t> compressedRelocationsBufferSize;\n   be2_val<virt_addr> postTrampBuffer;\n   be2_val<virt_addr> textAddr;\n   be2_val<uint32_t> textOffset;\n   be2_val<uint32_t> textSize;\n   be2_val<virt_addr> dataAddr;\n   be2_val<uint32_t> dataOffset;\n   be2_val<uint32_t> dataSize;\n   be2_val<virt_addr> loadAddr;\n   be2_val<uint32_t> loadOffset;\n   be2_val<uint32_t> loadSize;\n   be2_virt_ptr<virt_addr> sectionAddressBuffer;\n   be2_val<uint32_t> sectionAddressBufferSize;\n   be2_val<uint32_t> numFuncExports;\n   be2_virt_ptr<void> funcExports;\n   be2_val<uint32_t> numDataExports;\n   be2_virt_ptr<void> dataExports;\n\n   //! Pointer to last struct returned from LOADER_GetFileInfo.\n   be2_virt_ptr<LOADER_UserFileInfo> userFileInfo;\n\n   //! Size of the file info structure returned from LOADER_GetFileInfo.\n   be2_val<uint32_t> userFileInfoSize;\n\n   be2_virt_ptr<LOADED_RPL> nextLoadedRpl;\n};\nCHECK_OFFSET(LOADED_RPL, 0x00, globals);\nCHECK_OFFSET(LOADED_RPL, 0x04, selfBufferSize);\nCHECK_OFFSET(LOADED_RPL, 0x08, moduleNameBuffer);\nCHECK_OFFSET(LOADED_RPL, 0x0C, moduleNameLen);\nCHECK_OFFSET(LOADED_RPL, 0x10, moduleNameBufferSize);\nCHECK_OFFSET(LOADED_RPL, 0x14, pathBuffer);\nCHECK_OFFSET(LOADED_RPL, 0x18, pathBufferSize);\nCHECK_OFFSET(LOADED_RPL, 0x1C, elfHeader);\nCHECK_OFFSET(LOADED_RPL, 0x50, sectionHeaderBuffer);\nCHECK_OFFSET(LOADED_RPL, 0x54, sectionHeaderBufferSize);\nCHECK_OFFSET(LOADED_RPL, 0x58, fileInfoBuffer);\nCHECK_OFFSET(LOADED_RPL, 0x5C, fileInfoSize);\nCHECK_OFFSET(LOADED_RPL, 0x60, fileInfoBufferSize);\nCHECK_OFFSET(LOADED_RPL, 0x64, crcBuffer);\nCHECK_OFFSET(LOADED_RPL, 0x68, crcBufferSize);\nCHECK_OFFSET(LOADED_RPL, 0x6C, lastSectionCrc);\nCHECK_OFFSET(LOADED_RPL, 0x70, loadStateFlags);\nCHECK_OFFSET(LOADED_RPL, 0x74, entryPoint);\nCHECK_OFFSET(LOADED_RPL, 0x78, upcomingBufferNumber);\nCHECK_OFFSET(LOADED_RPL, 0x7C, lastChunkBuffer);\nCHECK_OFFSET(LOADED_RPL, 0x80, virtualFileBaseOffset);\nCHECK_OFFSET(LOADED_RPL, 0x84, fileOffset);\nCHECK_OFFSET(LOADED_RPL, 0x88, upcomingFileOffset);\nCHECK_OFFSET(LOADED_RPL, 0x8C, fileType);\nCHECK_OFFSET(LOADED_RPL, 0x90, totalBytesRead);\nCHECK_OFFSET(LOADED_RPL, 0x94, chunkBuffer);\nCHECK_OFFSET(LOADED_RPL, 0xAC, upid);\nCHECK_OFFSET(LOADED_RPL, 0xB0, virtualFileBase);\nCHECK_OFFSET(LOADED_RPL, 0xB4, textBuffer);\nCHECK_OFFSET(LOADED_RPL, 0xB8, textBufferSize);\nCHECK_OFFSET(LOADED_RPL, 0xBC, dataBuffer);\nCHECK_OFFSET(LOADED_RPL, 0xC0, loadBuffer);\nCHECK_OFFSET(LOADED_RPL, 0xC4, compressedRelocationsBuffer);\nCHECK_OFFSET(LOADED_RPL, 0xC8, compressedRelocationsBufferSize);\nCHECK_OFFSET(LOADED_RPL, 0xCC, postTrampBuffer);\nCHECK_OFFSET(LOADED_RPL, 0xD0, textAddr);\nCHECK_OFFSET(LOADED_RPL, 0xD4, textOffset);\nCHECK_OFFSET(LOADED_RPL, 0xD8, textSize);\nCHECK_OFFSET(LOADED_RPL, 0xDC, dataAddr);\nCHECK_OFFSET(LOADED_RPL, 0xE0, dataOffset);\nCHECK_OFFSET(LOADED_RPL, 0xE4, dataSize);\nCHECK_OFFSET(LOADED_RPL, 0xE8, loadAddr);\nCHECK_OFFSET(LOADED_RPL, 0xEC, loadOffset);\nCHECK_OFFSET(LOADED_RPL, 0xF0, loadSize);\nCHECK_OFFSET(LOADED_RPL, 0xF4, sectionAddressBuffer);\nCHECK_OFFSET(LOADED_RPL, 0xF8, sectionAddressBufferSize);\nCHECK_OFFSET(LOADED_RPL, 0xFC, numFuncExports);\nCHECK_OFFSET(LOADED_RPL, 0x100, funcExports);\nCHECK_OFFSET(LOADED_RPL, 0x104, numDataExports);\nCHECK_OFFSET(LOADED_RPL, 0x108, dataExports);\nCHECK_OFFSET(LOADED_RPL, 0x10C, userFileInfo);\nCHECK_OFFSET(LOADED_RPL, 0x110, userFileInfoSize);\nCHECK_OFFSET(LOADED_RPL, 0x114, nextLoadedRpl);\nCHECK_SIZE(LOADED_RPL, 0x118);\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_log.h",
    "content": "#pragma once\n#include \"cafe_loader_entry.h\"\n#include <common/log.h>\n\nnamespace cafe::loader::internal\n{\n\ntemplate<typename... Args>\nvoid\nLoader_ReportError(const char *fmt, Args... args)\n{\n   gLog->error(fmt, args...);\n}\n\ntemplate<typename... Args>\nvoid\nLoader_ReportWarn(const char *fmt, Args... args)\n{\n   if (getProcFlags().debugLevel() >= kernel::DebugLevel::Warn) {\n      gLog->warn(fmt, args...);\n   }\n}\n\ntemplate<typename... Args>\nvoid\nLoader_ReportInfo(const char *fmt, Args... args)\n{\n   if (getProcFlags().debugLevel() >= kernel::DebugLevel::Info) {\n      gLog->info(fmt, args...);\n   }\n}\n\ntemplate<typename... Args>\nvoid\nLoader_ReportNotice(const char *fmt, Args... args)\n{\n   if (getProcFlags().debugLevel() >= kernel::DebugLevel::Notice) {\n      gLog->debug(fmt, args...);\n   }\n}\n\ntemplate<typename... Args>\nvoid\nLoader_ReportVerbose(const char *fmt, Args... args)\n{\n   if (getProcFlags().debugLevel() >= kernel::DebugLevel::Verbose) {\n      gLog->trace(fmt, args...);\n   }\n}\n\ntemplate<typename... Args>\nvoid\nLoader_LogEntry(uint32_t unk1, uint32_t unk2, uint32_t unk3, const char *fmt, Args... args)\n{\n   gLog->debug(fmt, args...);\n}\n\ntemplate<typename... Args>\nvoid\nLoader_Panic(uint32_t unk1, const char *fmt, Args... args)\n{\n   gLog->error(fmt, args...);\n   decaf_abort(\"Loader_Panic\");\n}\n\ntemplate<typename... Args>\nvoid\nLiPanic(const char *file, int line, const char *fmt, Args... args)\n{\n   gLog->error(fmt, args...);\n   gLog->error(\"Loader_Panic: {}, line {}\", file, line);\n   Loader_Panic(0x130016, \"in \\\"{}\\\" on line {}.\", file, line);\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_minfileinfo.cpp",
    "content": "#include \"cafe_loader_error.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_loaded_rpl.h\"\n#include \"cafe_loader_minfileinfo.h\"\n\nnamespace cafe::loader::internal\n{\n\nbool\nLoader_ValidateAddrRange(virt_addr addr,\n                         uint32_t size)\n{\n   // TODO: Syscall to kernel validate addr range\n   return TRUE;\n}\n\nint32_t\nLiValidateAddress(virt_ptr<void> ptr,\n                  uint32_t size,\n                  uint32_t alignMask,\n                  int32_t errorCode,\n                  virt_addr minAddr,\n                  virt_addr maxAddr,\n                  std::string_view name)\n{\n   auto addr = virt_cast<virt_addr>(ptr);\n   if (addr < minAddr || addr >= maxAddr) {\n      if (!name.empty()) {\n         Loader_ReportError(\"***bad {} address.\", name);\n      }\n      return errorCode;\n   }\n\n   if (alignMask && (addr & alignMask)) {\n      if (!name.empty()) {\n         Loader_ReportError(\"***bad {} address alignment.\", name);\n      }\n\n      return errorCode;\n   }\n\n   if (size && !Loader_ValidateAddrRange(addr, size)) {\n      if (!name.empty()) {\n         Loader_ReportError(\"***bad {} address buffer.\", name);\n      }\n\n      return errorCode;\n   }\n\n   return 0;\n}\n\n\n/**\n * sUpdateFileInfoForUser\n */\nvoid\nupdateFileInfoForUser(virt_ptr<LOADED_RPL> rpl,\n                      virt_ptr<LOADER_UserFileInfo> userFileInfo,\n                      virt_ptr<LOADER_MinFileInfo> minFileInfo)\n{\n   if (rpl->loadStateFlags & LoaderStateFlags_Unk0x20000000) {\n      if (userFileInfo) {\n         userFileInfo->fileInfoFlags |= rpl::RPL_FLAG_4;\n      } else if (minFileInfo) {\n         minFileInfo->fileInfoFlags |= rpl::RPL_FLAG_4;\n      }\n   }\n}\n\nint32_t\nLiGetMinFileInfo(virt_ptr<LOADED_RPL> rpl,\n                 virt_ptr<LOADER_MinFileInfo> info)\n{\n   auto fileInfo = rpl->fileInfoBuffer;\n   *info->outSizeOfFileInfo = rpl->fileInfoSize;\n\n   if (fileInfo->runtimeFileInfoSize) {\n      *info->outSizeOfFileInfo = fileInfo->runtimeFileInfoSize;\n   }\n\n   info->dataSize = fileInfo->dataSize;\n   info->dataAlign = fileInfo->dataAlign;\n   info->loadSize = fileInfo->loadSize;\n   info->loadAlign = fileInfo->loadAlign;\n   info->fileInfoFlags = fileInfo->flags;\n\n   if (rpl->fileType == ios::mcp::MCPFileType::ProcessCode) {\n      info->fileLocation = getProcTitleLoc();\n   }\n\n   if (info->inoutNextTlsModuleNumber && (fileInfo->flags & 8)) {\n      if (fileInfo->tlsModuleIndex != -1) {\n         Loader_ReportError(\"*** unexpected module index.\\n\");\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LiGetMinFileInfo\", 261);\n         return -470064;\n      }\n\n      fileInfo->tlsModuleIndex = *info->inoutNextTlsModuleNumber;\n      *info->inoutNextTlsModuleNumber += 1;\n   }\n\n   if (fileInfo->filename) {\n      auto path = virt_cast<char *>(rpl->fileInfoBuffer) + fileInfo->filename;\n      *info->outPathStringSize =\n         static_cast<uint32_t>(strnlen(path.get(),\n                                       rpl->fileInfoSize - fileInfo->filename)\n                               + 1);\n   } else {\n      *info->outPathStringSize = 0u;\n   }\n\n   updateFileInfoForUser(rpl, nullptr, info);\n   return 0;\n}\n\nint32_t\nLiValidateMinFileInfo(virt_ptr<LOADER_MinFileInfo> minFileInfo,\n                      std::string_view funcName)\n{\n   if (!minFileInfo) {\n      Loader_ReportError(\"*** Null minimum file info pointer.\");\n      LiSetFatalError(0x18729Bu, 0, 1, \"LiValidateMinFileInfo\", 316);\n      return -470058;\n   }\n\n   auto error = LiValidateAddress(minFileInfo,\n                                  sizeof(LOADER_MinFileInfo),\n                                  0,\n                                  -470058,\n                                  virt_addr { 0x10000000 },\n                                  virt_addr { 0xC0000000 },\n                                  \"minimum file info\");\n   if (error) {\n      LiSetFatalError(0x18729Bu, 0, 1, \"LiValidateMinFileInfo\", 325);\n      return error;\n   }\n\n   if (minFileInfo->size != sizeof(LOADER_MinFileInfo)) {\n      Loader_ReportError(\"***{} received invalid minimum file control block size.\", funcName);\n      LiSetFatalError(0x18729Bu, 0, 1, \"LiValidateMinFileInfo\", 334);\n      return -470059;\n   }\n\n   if (minFileInfo->version != 4) {\n      Loader_ReportError(\"***{} received invalid minimum file control block version.\", funcName);\n      LiSetFatalError(0x18729Bu, 0, 1, \"LiValidateMinFileInfo\", 342);\n      return -470059;\n   }\n\n   if (minFileInfo->outKernelHandle) {\n      error = LiValidateAddress(minFileInfo->outKernelHandle,\n                                4,\n                                0,\n                                -470027,\n                                virt_addr { 0x10000000 },\n                                virt_addr { 0xC0000000 },\n                                \"kernel handle for module (out of valid range)\");\n      if (error) {\n         LiSetFatalError(0x18729Bu, 0, 1, \"LiValidateMinFileInfo\", 353);\n         return error;\n      }\n   }\n\n   if (minFileInfo->moduleNameBuffer) {\n      error = LiValidateAddress(minFileInfo->moduleNameBuffer,\n                                minFileInfo->moduleNameBufferLen,\n                                0,\n                                -470027,\n                                virt_addr { 0x10000000 },\n                                virt_addr { 0xC0000000 },\n                                \"module name\");\n      if (error) {\n         LiSetFatalError(0x18729Bu, 0, 1, \"LiValidateMinFileInfo\", 364);\n         return error;\n      }\n   }\n\n   if (minFileInfo->outNumberOfSections) {\n      error = LiValidateAddress(minFileInfo->outNumberOfSections,\n                                4,\n                                0,\n                                -470027,\n                                virt_addr { 0x10000000 },\n                                virt_addr { 0xC0000000 },\n                                \"number of sections\");\n      if (error) {\n         LiSetFatalError(0x18729Bu, 0, 1, \"LiValidateMinFileInfo\", 375);\n         return error;\n      }\n   }\n\n   if (minFileInfo->outSizeOfFileInfo) {\n      error = LiValidateAddress(minFileInfo->outSizeOfFileInfo,\n                                4,\n                                0,\n                                -470027,\n                                virt_addr { 0x10000000 },\n                                virt_addr { 0xC0000000 },\n                                \"size of file info\");\n      if (error) {\n         LiSetFatalError(0x18729Bu, 0, 1, \"LiValidateMinFileInfo\", 386);\n         return error;\n      }\n   }\n\n   if (minFileInfo->outPathStringSize) {\n      error = LiValidateAddress(minFileInfo->outPathStringSize,\n                                4,\n                                0,\n                                -470027,\n                                virt_addr { 0x10000000 },\n                                virt_addr { 0xC0000000 },\n                                \"size of path string\");\n      if (error) {\n         LiSetFatalError(0x18729Bu, 0, 1, \"LiValidateMinFileInfo\", 397);\n         return error;\n      }\n   }\n\n   if (minFileInfo->inoutNextTlsModuleNumber) {\n      error = LiValidateAddress(minFileInfo->inoutNextTlsModuleNumber,\n                                4,\n                                0,\n                                -470027,\n                                virt_addr { 0x10000000 },\n                                virt_addr { 0xC0000000 },\n                                \"next TLS module number\");\n      if (error) {\n         LiSetFatalError(0x18729Bu, 0, 1, \"LiValidateMinFileInfo\", 408);\n         return error;\n      }\n   }\n\n   return 0;\n}\n\n} // namespace cafe::loader::internal\n\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_minfileinfo.h",
    "content": "#pragma once\n#include \"cafe_loader_basics.h\"\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::loader\n{\n\n//! Unique pointer representing a module handle, we actually use the pointer\n//! value of the LOADED_RPL.moduleNameBuffer\nusing LOADER_Handle = virt_ptr<void>;\n\nstruct LOADER_LinkModule\n{\n   be2_val<LOADER_Handle> loaderHandle;\n   be2_val<virt_addr> entryPoint;\n   be2_val<virt_addr> textAddr;\n   be2_val<uint32_t> textOffset;\n   be2_val<uint32_t> textSize;\n   be2_val<virt_addr> dataAddr;\n   be2_val<uint32_t> dataOffset;\n   be2_val<uint32_t> dataSize;\n   be2_val<virt_addr> loadAddr;\n   be2_val<uint32_t> loadOffset;\n   be2_val<uint32_t> loadSize;\n};\nCHECK_OFFSET(LOADER_LinkModule, 0x00, loaderHandle);\nCHECK_OFFSET(LOADER_LinkModule, 0x04, entryPoint);\nCHECK_OFFSET(LOADER_LinkModule, 0x08, textAddr);\nCHECK_OFFSET(LOADER_LinkModule, 0x0C, textOffset);\nCHECK_OFFSET(LOADER_LinkModule, 0x10, textSize);\nCHECK_OFFSET(LOADER_LinkModule, 0x14, dataAddr);\nCHECK_OFFSET(LOADER_LinkModule, 0x18, dataOffset);\nCHECK_OFFSET(LOADER_LinkModule, 0x1C, dataSize);\nCHECK_OFFSET(LOADER_LinkModule, 0x20, loadAddr);\nCHECK_OFFSET(LOADER_LinkModule, 0x24, loadOffset);\nCHECK_OFFSET(LOADER_LinkModule, 0x28, loadSize);\nCHECK_SIZE(LOADER_LinkModule, 0x2C);\n\nstruct LOADER_LinkInfo\n{\n   be2_val<uint32_t> size;\n   be2_val<uint32_t> numModules;\n   be2_array<LOADER_LinkModule, 1> modules;\n};\nCHECK_OFFSET(LOADER_LinkInfo, 0x00, size);\nCHECK_OFFSET(LOADER_LinkInfo, 0x04, numModules);\nCHECK_OFFSET(LOADER_LinkInfo, 0x08, modules);\nCHECK_SIZE(LOADER_LinkInfo, 0x34);\n\nstruct LOADER_SectionInfo\n{\n   be2_val<uint32_t> type;\n   be2_val<uint32_t> flags;\n   be2_val<virt_addr> address;\n\n   union\n   {\n      //! Size of the section, set when type != SHT_RPL_IMPORTS\n      be2_val<uint32_t> size;\n\n      //! Name offset of the section, set when type == SHT_RPL_IMPORTS\n      be2_val<uint32_t> name;\n   };\n};\nCHECK_OFFSET(LOADER_SectionInfo, 0x00, type);\nCHECK_OFFSET(LOADER_SectionInfo, 0x04, flags);\nCHECK_OFFSET(LOADER_SectionInfo, 0x08, address);\nCHECK_OFFSET(LOADER_SectionInfo, 0x0C, size);\nCHECK_OFFSET(LOADER_SectionInfo, 0x0C, name);\nCHECK_SIZE(LOADER_SectionInfo, 0x10);\n\nstruct LOADER_UserFileInfo\n{\n   be2_val<uint32_t> size;\n   be2_val<uint32_t> magic;\n   be2_val<uint32_t> pathStringLength;\n   be2_virt_ptr<char> pathString;\n   be2_val<uint32_t> fileInfoFlags;\n   be2_val<int16_t> tlsModuleIndex;\n   be2_val<int16_t> tlsAlignShift;\n   be2_val<virt_addr> tlsAddressStart;\n   be2_val<uint32_t> tlsSectionSize;\n   be2_val<uint32_t> shstrndx;\n   be2_val<uint32_t> titleLocation;\n   UNKNOWN(0x60 - 0x28);\n};\nCHECK_OFFSET(LOADER_UserFileInfo, 0x00, size);\nCHECK_OFFSET(LOADER_UserFileInfo, 0x04, magic);\nCHECK_OFFSET(LOADER_UserFileInfo, 0x08, pathStringLength);\nCHECK_OFFSET(LOADER_UserFileInfo, 0x0C, pathString);\nCHECK_OFFSET(LOADER_UserFileInfo, 0x10, fileInfoFlags);\nCHECK_OFFSET(LOADER_UserFileInfo, 0x14, tlsModuleIndex);\nCHECK_OFFSET(LOADER_UserFileInfo, 0x16, tlsAlignShift);\nCHECK_OFFSET(LOADER_UserFileInfo, 0x18, tlsAddressStart);\nCHECK_OFFSET(LOADER_UserFileInfo, 0x1C, tlsSectionSize);\nCHECK_OFFSET(LOADER_UserFileInfo, 0x20, shstrndx);\nCHECK_OFFSET(LOADER_UserFileInfo, 0x24, titleLocation);\nCHECK_SIZE(LOADER_UserFileInfo, 0x60);\n\nstruct LOADER_MinFileInfo\n{\n   be2_val<uint32_t> size;\n   be2_val<uint32_t> version;\n   be2_virt_ptr<char> moduleNameBuffer;\n   be2_val<uint32_t> moduleNameBufferLen;\n   be2_virt_ptr<LOADER_Handle> outKernelHandle;\n   be2_virt_ptr<uint32_t> outNumberOfSections;\n   be2_virt_ptr<LOADER_SectionInfo> outSectionInfo;\n   be2_virt_ptr<uint32_t> outSizeOfFileInfo;\n   be2_virt_ptr<LOADER_UserFileInfo> outFileInfo;\n   be2_val<uint32_t> dataSize;\n   be2_val<uint32_t> dataAlign;\n   be2_virt_ptr<void> dataBuffer;\n   be2_val<uint32_t> loadSize;\n   be2_val<uint32_t> loadAlign;\n   be2_virt_ptr<void> loadBuffer;\n   be2_val<uint32_t> fileInfoFlags;\n   be2_virt_ptr<uint32_t> inoutNextTlsModuleNumber;\n   be2_val<uint32_t> pathStringSize;\n   be2_virt_ptr<uint32_t> outPathStringSize;\n   be2_virt_ptr<char> pathStringBuffer;\n   be2_val<uint32_t> fileLocation;\n   be2_val<uint32_t> fatalMsgType;\n   be2_val<int32_t> fatalErr;\n   be2_val<int32_t> error;\n   be2_val<uint32_t> fatalLine;\n   be2_array<char, 64> fatalFunction;\n};\nCHECK_OFFSET(LOADER_MinFileInfo, 0x00, size);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x04, version);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x08, moduleNameBuffer);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x0C, moduleNameBufferLen);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x10, outKernelHandle);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x14, outNumberOfSections);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x18, outSectionInfo);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x1C, outSizeOfFileInfo);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x20, outFileInfo);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x24, dataSize);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x28, dataAlign);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x2C, dataBuffer);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x30, loadSize);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x34, loadAlign);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x38, loadBuffer);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x3C, fileInfoFlags);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x40, inoutNextTlsModuleNumber);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x44, pathStringSize);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x48, outPathStringSize);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x4C, pathStringBuffer);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x50, fileLocation);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x54, fatalMsgType);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x58, fatalErr);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x5C, error);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x60, fatalLine);\nCHECK_OFFSET(LOADER_MinFileInfo, 0x64, fatalFunction);\nCHECK_SIZE(LOADER_MinFileInfo, 0xA4);\n\nnamespace internal\n{\n\nbool\nLoader_ValidateAddrRange(virt_addr addr,\n                         uint32_t size);\n\nint32_t\nLiValidateAddress(virt_ptr<void> ptr,\n                  uint32_t size,\n                  uint32_t alignMask,\n                  int32_t errorCode,\n                  virt_addr minAddr,\n                  virt_addr maxAddr,\n                  std::string_view name);\n\nint32_t\nLiGetMinFileInfo(virt_ptr<LOADED_RPL> rpl,\n                 virt_ptr<LOADER_MinFileInfo> info);\n\nint32_t\nLiValidateMinFileInfo(virt_ptr<LOADER_MinFileInfo> minFileInfo,\n                      std::string_view funcName);\n\nvoid\nupdateFileInfoForUser(virt_ptr<LOADED_RPL> rpl,\n                      virt_ptr<LOADER_UserFileInfo> userFileInfo,\n                      virt_ptr<LOADER_MinFileInfo> minFileInfo);\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_prep.cpp",
    "content": "#include \"cafe_loader_bounce.h\"\n#include \"cafe_loader_entry.h\"\n#include \"cafe_loader_error.h\"\n#include \"cafe_loader_globals.h\"\n#include \"cafe_loader_heap.h\"\n#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_loaded_rpl.h\"\n#include \"cafe_loader_basics.h\"\n#include \"cafe_loader_purge.h\"\n#include \"cafe_loader_prep.h\"\n#include \"cafe_loader_shared.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_minfileinfo.h\"\n#include \"cafe_loader_utils.h\"\n\n#include \"cafe/cafe_stackobject.h\"\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n\n#include <common/strutils.h>\n\nnamespace cafe::loader::internal\n{\n\nint32_t\nLiLoadForPrep(virt_ptr<char> moduleName,\n              uint32_t moduleNameLen,\n              virt_ptr<void> chunkBuffer,\n              virt_ptr<LOADED_RPL> *outLoadedRpl,\n              LiBasicsLoadArgs *loadArgs,\n              uint32_t unk)\n{\n   auto globals = getGlobalStorage();\n   auto rpl = virt_ptr<LOADED_RPL> { nullptr };\n   auto error = LiLoadRPLBasics(moduleName,\n                                moduleNameLen,\n                                chunkBuffer,\n                                globals->processCodeHeap,\n                                globals->processCodeHeap,\n                                true,\n                                0,\n                                &rpl,\n                                loadArgs,\n                                unk);\n   if (error) {\n      return error;\n   }\n\n   auto fileInfo = rpl->fileInfoBuffer;\n   if (globals->loadedRpx && (fileInfo->flags & rpl::RPL_IS_RPX)) {\n      Loader_ReportError(\"***Attempt to load RPX when main program already loaded.\\n\");\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LiLoadForPrep\", 1175);\n      error = -470093;\n   } else if (!globals->loadedRpx && !(fileInfo->flags & rpl::RPL_IS_RPX)) {\n      Loader_ReportError(\"***Attempt to load non-RPX as main program.\\n\");\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LiLoadForPrep\", 1183);\n      error = -470094;\n   } else {\n      rpl->nextLoadedRpl = nullptr;\n      if (globals->lastLoadedRpl) {\n         globals->lastLoadedRpl->nextLoadedRpl = rpl;\n         globals->lastLoadedRpl = rpl;\n      } else {\n         globals->firstLoadedRpl = rpl;\n         globals->lastLoadedRpl = rpl;\n      }\n\n      *outLoadedRpl = rpl;\n      return 0;\n   }\n\n   if (rpl) {\n      LiPurgeOneUnlinkedModule(rpl);\n   }\n\n   return error;\n}\n\nint32_t\nLOADER_Prep(kernel::UniqueProcessId upid,\n            virt_ptr<LOADER_MinFileInfo> minFileInfo)\n{\n   auto globals = getGlobalStorage();\n   auto error = 0;\n\n   LiCheckAndHandleInterrupts();\n\n   if (globals->currentUpid != upid) {\n      Loader_ReportError(\"*** Loader address space not set for process {} but called for {}.\",\n                         static_cast<int>(globals->currentUpid.value()),\n                         static_cast<int>(upid));\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_Prep\", 1262);\n      LiCloseBufferIfError();\n      return Error::DifferentProcess;\n   }\n\n   if (minFileInfo) {\n      error = LiValidateMinFileInfo(minFileInfo, \"LOADER_Prep\");\n      if (error) {\n         LiCloseBufferIfError();\n         return error;\n      }\n   }\n\n   *minFileInfo->outKernelHandle = nullptr;\n\n   LiResolveModuleName(virt_addrof(minFileInfo->moduleNameBuffer),\n                       virt_addrof(minFileInfo->moduleNameBufferLen));\n   auto moduleName =\n      std::string_view {\n         minFileInfo->moduleNameBuffer.get(),\n         minFileInfo->moduleNameBufferLen\n      };\n\n   if (minFileInfo->moduleNameBufferLen == 8 &&\n       strncmp(minFileInfo->moduleNameBuffer.get(), \"coreinit\", 8) == 0) {\n      Loader_ReportError(\"*** Loader Failure (system module re-load).\");\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_Prep\", 1305);\n      LiCloseBufferIfError();\n      return -470029;\n   }\n\n   for (auto itr = globals->firstLoadedRpl; itr; itr = itr->nextLoadedRpl) {\n      if (itr->moduleNameLen == minFileInfo->moduleNameBufferLen &&\n          strncmp(itr->moduleNameBuffer.get(),\n                  minFileInfo->moduleNameBuffer.get(),\n                  minFileInfo->moduleNameBufferLen) == 0) {\n         Loader_ReportError(\"*** module \\\"{}\\\" already loaded.\\n\",\n            std::string_view { minFileInfo->moduleNameBuffer.get(),\n                               minFileInfo->moduleNameBufferLen });\n         LiSetFatalError(0x18729Bu, itr->fileType, 1, \"LOADER_Prep\", 1292);\n         LiCloseBufferIfError();\n         return -470028;\n      }\n   }\n\n   // Check if module already loaded as a shared library\n   if (!getProcFlags().disableSharedLibraries()) {\n      auto sharedModule = findLoadedSharedModule(moduleName);\n      if (sharedModule) {\n         auto allocPtr = virt_ptr<void> { nullptr };\n         auto allocSize = uint32_t { 0 };\n         auto largestFree = uint32_t { 0 };\n         error = LiCacheLineCorrectAllocEx(getGlobalStorage()->processCodeHeap,\n                                           sizeof(LOADED_RPL),\n                                           4,\n                                           &allocPtr,\n                                           1,\n                                           &allocSize,\n                                           &largestFree,\n                                           sharedModule->fileType);\n         if (error) {\n            Loader_ReportError(\n               \"***Allocate Error {}, Failed to allocate {} bytes for system shared RPL tracking for {} in current process;  (needed {}, available {}).\",\n               error, allocSize, moduleName, allocSize, largestFree);\n            LiCloseBufferIfError();\n            return error;\n         }\n\n         auto trackingModule = virt_cast<LOADED_RPL *>(allocPtr);\n         *trackingModule = *sharedModule;\n         trackingModule->selfBufferSize = allocSize;\n         trackingModule->globals = globals;\n         trackingModule->loadStateFlags &= ~LoaderStateFlag4;\n\n         // Add to global loaded module linked list\n         trackingModule->nextLoadedRpl = nullptr;\n         if (globals->lastLoadedRpl) {\n            globals->lastLoadedRpl->nextLoadedRpl = trackingModule;\n         } else {\n            globals->firstLoadedRpl = trackingModule;\n         }\n         globals->lastLoadedRpl = trackingModule;\n\n         *minFileInfo->outKernelHandle = trackingModule->moduleNameBuffer;\n         *minFileInfo->outNumberOfSections = trackingModule->elfHeader.shnum;\n\n         error = LiGetMinFileInfo(trackingModule, minFileInfo);\n         if (error) {\n            LiCloseBufferIfError();\n         }\n\n         return error;\n      }\n   }\n\n   auto filename = StackArray<char, 64> { };\n   auto filenameLen = std::min<uint32_t>(minFileInfo->moduleNameBufferLen, 59);\n   std::memcpy(filename.get(),\n               minFileInfo->moduleNameBuffer.get(),\n               filenameLen);\n   string_copy(filename.get() + filenameLen,\n               \".rpl\",\n               filename.size() - filenameLen);\n\n   LiCheckAndHandleInterrupts();\n   LiInitBuffer(false);\n\n   auto chunkBuffer = virt_ptr<void> { nullptr };\n   auto chunkBufferSize = uint32_t { 0 };\n   error = LiBounceOneChunk(filename.get(),\n                            ios::mcp::MCPFileType::CafeOS,\n                            globals->currentUpid,\n                            &chunkBufferSize,\n                            0, 1,\n                            &chunkBuffer);\n   LiCheckAndHandleInterrupts();\n   if (error) {\n      Loader_ReportError(\n         \"***LiBounceOneChunk failed loading \\\"{}\\\" of type {} at offset 0x{:08X} err={}.\",\n         filename.get(), 1, 0, error);\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   auto loadArgs = LiBasicsLoadArgs { };\n   auto rpl = virt_ptr<LOADED_RPL> { nullptr };\n   loadArgs.upid = upid;\n   loadArgs.loadedRpl = nullptr;\n   loadArgs.readHeapTracking = globals->processCodeHeap;\n   loadArgs.pathNameLen = filenameLen + 5;\n   loadArgs.pathName = filename;\n   loadArgs.fileType = ios::mcp::MCPFileType::CafeOS;\n   loadArgs.chunkBuffer = chunkBuffer;\n   loadArgs.chunkBufferSize = chunkBufferSize;\n   loadArgs.fileOffset = 0u;\n\n   error = LiLoadForPrep(filename,\n                         filenameLen,\n                         chunkBuffer,\n                         &rpl,\n                         &loadArgs,\n                         0);\n   if (error) {\n      Loader_ReportError(\"***LiLoadForPrep failure {}. loading \\\"{}\\\".\",\n                         error, filename.get());\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   *minFileInfo->outKernelHandle = rpl->moduleNameBuffer;\n   *minFileInfo->outNumberOfSections = rpl->elfHeader.shnum;\n   error = LiGetMinFileInfo(rpl, minFileInfo);\n   if (error) {\n      LiCloseBufferIfError();\n   }\n\n   return error;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_prep.h",
    "content": "#pragma once\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader\n{\n\nstruct LOADER_MinFileInfo;\nstruct LOADED_RPL;\n\nnamespace internal\n{\n\nstruct LiBasicsLoadArgs;\n\nint32_t\nLiLoadForPrep(virt_ptr<char> moduleName,\n              uint32_t moduleNameLen,\n              virt_ptr<void> chunkBuffer,\n              virt_ptr<LOADED_RPL> *outLoadedRpl,\n              LiBasicsLoadArgs *loadArgs,\n              uint32_t unk);\n\nint32_t\nLOADER_Prep(kernel::UniqueProcessId upid,\n            virt_ptr<LOADER_MinFileInfo> minFileInfo);\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_purge.cpp",
    "content": "#include \"cafe_loader_globals.h\"\n#include \"cafe_loader_heap.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_loaded_rpl.h\"\n#include \"cafe_loader_purge.h\"\n\nnamespace cafe::loader::internal\n{\n\nvoid\nLiPurgeOneUnlinkedModule(virt_ptr<LOADED_RPL> rpl)\n{\n   auto globals = getGlobalStorage();\n   if (rpl->globals != globals) {\n      Loader_ReportWarn(\"*** Purge of module in foreign process!\");\n      return;\n   }\n\n   if (!(rpl->loadStateFlags & LoaderStateFlags_Unk0x20000000)) {\n      if (rpl->textBuffer) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  rpl->textBuffer,\n                                  rpl->textBufferSize);\n      }\n\n      if (rpl->compressedRelocationsBuffer) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  rpl->compressedRelocationsBuffer,\n                                  rpl->compressedRelocationsBufferSize);\n      }\n\n      if (rpl->moduleNameBuffer && rpl->moduleNameBufferSize) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  rpl->moduleNameBuffer,\n                                  rpl->moduleNameBufferSize);\n      }\n\n      if (rpl->pathBuffer) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  rpl->pathBuffer,\n                                  rpl->pathBufferSize);\n      }\n\n      if (rpl->sectionHeaderBuffer) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  rpl->sectionHeaderBuffer,\n                                  rpl->sectionHeaderBufferSize);\n      }\n\n      if (rpl->fileInfoBuffer) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  rpl->fileInfoBuffer,\n                                  rpl->fileInfoBufferSize);\n      }\n\n      if (rpl->crcBuffer) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  rpl->crcBuffer,\n                                  rpl->crcBufferSize);\n      }\n\n      if (rpl->sectionAddressBuffer) {\n         LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                                  rpl->sectionAddressBuffer,\n                                  rpl->sectionAddressBufferSize);\n      }\n   }\n\n   std::memset(rpl.get(), 0, sizeof(LOADED_RPL));\n   LiCacheLineCorrectFreeEx(globals->processCodeHeap,\n                            rpl,\n                            rpl->selfBufferSize);\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_purge.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader\n{\n\nstruct LOADED_RPL;\n\nnamespace internal\n{\n\nvoid\nLiPurgeOneUnlinkedModule(virt_ptr<LOADED_RPL> rpl);\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_query.cpp",
    "content": "#include \"cafe_loader_bounce.h\"\n#include \"cafe_loader_error.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_loaded_rpl.h\"\n#include \"cafe_loader_globals.h\"\n#include \"cafe_loader_minfileinfo.h\"\n#include \"cafe_loader_utils.h\"\n#include \"cafe_loader_query.h\"\n\n#include <common/strutils.h>\n#include <cstring>\n\nnamespace cafe::loader::internal\n{\n\nvirt_ptr<LOADED_RPL>\ngetModule(LOADER_Handle handle)\n{\n   auto globals = getGlobalStorage();\n   for (auto rpl = globals->firstLoadedRpl; rpl; rpl = rpl->nextLoadedRpl) {\n      if (rpl->moduleNameBuffer == handle) {\n         return rpl;\n      }\n   }\n\n   return nullptr;\n}\n\nint32_t\nLOADER_GetSecInfo(kernel::UniqueProcessId upid,\n                  LOADER_Handle handle,\n                  virt_ptr<uint32_t> outNumberOfSections,\n                  virt_ptr<LOADER_SectionInfo> outSectionInfo)\n{\n   auto globals = getGlobalStorage();\n   if (globals->currentUpid != upid) {\n      Loader_ReportError(\"*** Loader address space not set for process {} but called for {}.\",\n                         static_cast<int>(globals->currentUpid.value()),\n                         static_cast<int>(upid));\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetSecInfo\", 45);\n      LiCloseBufferIfError();\n      return Error::DifferentProcess;\n   }\n\n   // Verify outNumberOfSections pointer\n   if (auto error = LiValidateAddress(outNumberOfSections,\n                                      sizeof(uint32_t),\n                                      0, -470009,\n                                      virt_addr { 0x10000000 },\n                                      virt_addr { 0xC0000000 },\n                                      \"number of sections\")) {\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetSecInfo\", 56);\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   // Find the module\n   if (!handle) {\n      handle = globals->loadedRpx->moduleNameBuffer;\n   }\n\n   auto rpl = getModule(handle);\n   if (!rpl) {\n      Loader_ReportError(\"*** module not found.\");\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetSecInfo\", 75);\n      LiCloseBufferIfError();\n      return -470010;\n   }\n\n   // Check section count\n   if (!outSectionInfo || !*outNumberOfSections) {\n      *outNumberOfSections = rpl->elfHeader.shnum;\n      return 0;\n   }\n\n   if (*outNumberOfSections < rpl->elfHeader.shnum) {\n      Loader_ReportError(\"*** num sections is too small.\\n\");\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LOADER_GetSecInfo\", 91);\n      LiCloseBufferIfError();\n      return -470011;\n   }\n\n   *outNumberOfSections = rpl->elfHeader.shnum;\n\n   // Verify the outSectionInfo pointer\n   if (auto error = LiValidateAddress(outSectionInfo,\n                                      sizeof(LOADER_SectionInfo) * rpl->elfHeader.shnum,\n                                      0, -470012,\n                                      virt_addr { 0x10000000 },\n                                      virt_addr { 0xC0000000 },\n                                      \"section return\")) {\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetSecInfo\", 56);\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   // Fill out the section info data\n   std::memset(outSectionInfo.get(),\n               0,\n               sizeof(LOADER_SectionInfo) * rpl->elfHeader.shnum);\n\n   for (auto i = 1; i < rpl->elfHeader.shnum; ++i) {\n      auto sectionHeader = getSectionHeader(rpl, i);\n      if (sectionHeader->type == rpl::SHT_RELA ||\n          sectionHeader->type == rpl::SHT_RPL_FILEINFO ||\n          sectionHeader->type == rpl::SHT_RPL_CRCS) {\n         continue;\n      }\n\n      auto &info = outSectionInfo[i];\n      info.type = sectionHeader->type;\n      info.flags = sectionHeader->flags;\n      info.address = rpl->sectionAddressBuffer[i];\n      if (sectionHeader->type == rpl::SHT_RPL_IMPORTS) {\n         info.name = sectionHeader->name;\n      } else {\n         info.size = sectionHeader->size;\n      }\n   }\n\n   return 0;\n}\n\nint32_t\nLOADER_GetFileInfo(kernel::UniqueProcessId upid,\n                   LOADER_Handle handle,\n                   virt_ptr<uint32_t> outSizeOfFileInfo,\n                   virt_ptr<LOADER_UserFileInfo> outFileInfo,\n                   virt_ptr<uint32_t> nextTlsModuleNumber,\n                   virt_ptr<uint32_t> outFileLocation)\n{\n   auto globals = getGlobalStorage();\n   if (globals->currentUpid != upid) {\n      Loader_ReportError(\"*** Loader address space not set for process {} but called for {}.\",\n                         static_cast<int>(globals->currentUpid.value()),\n                         static_cast<int>(upid));\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetFileInfo\", 59);\n      LiCloseBufferIfError();\n      return Error::DifferentProcess;\n   }\n\n   // Verify outSizeOfFileInfo pointer\n   if (auto error = LiValidateAddress(outSizeOfFileInfo,\n                                      sizeof(uint32_t),\n                                      0, -470060,\n                                      virt_addr { 0x10000000 },\n                                      virt_addr { 0xC0000000 },\n                                      \"file info bytes\")) {\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetFileInfo\", 70);\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   // Find the module\n   if (!handle) {\n      handle = globals->loadedRpx->moduleNameBuffer;\n   }\n\n   auto rpl = getModule(handle);\n   if (!rpl) {\n      Loader_ReportError(\"*** module not found.\");\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetFileInfo\", 89);\n      LiCloseBufferIfError();\n      return -470010;\n   }\n\n   // Check file info size\n   auto fileInfoSize = rpl->fileInfoSize;\n   auto fileInfo = rpl->fileInfoBuffer;\n   if (fileInfo->runtimeFileInfoSize) {\n      fileInfoSize = fileInfo->runtimeFileInfoSize;\n   }\n\n   rpl->userFileInfoSize = fileInfoSize;\n\n   if (!outFileInfo || !*outSizeOfFileInfo) {\n      *outSizeOfFileInfo = fileInfoSize;\n\n      if (outFileLocation) {\n         if (rpl->fileType == ios::mcp::MCPFileType::ProcessCode) {\n            *outFileLocation = getProcTitleLoc();\n         } else {\n            *outFileLocation = 0u;\n         }\n      }\n\n      return 0;\n   }\n\n   if (*outSizeOfFileInfo < fileInfoSize) {\n      Loader_ReportError(\"*** file info size is too small.\\n\");\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LOADER_GetFileInfo\", 124);\n      LiCloseBufferIfError();\n      return -470066;\n   }\n\n   *outSizeOfFileInfo = fileInfoSize;\n\n   // Update the TLS module number if necessary\n   if (nextTlsModuleNumber) {\n      if (auto error = LiValidateAddress(nextTlsModuleNumber,\n                                         sizeof(uint32_t),\n                                         0, -470097,\n                                         virt_addr { 0x10000000 },\n                                         virt_addr { 0xC0000000 },\n                                         \"next TLS number\")) {\n         LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetFileInfo\", 137);\n         LiCloseBufferIfError();\n         return error;\n      }\n\n      if (fileInfo->flags & rpl::RPL_HAS_TLS) {\n         if (fileInfo->tlsModuleIndex != -1) {\n            Loader_ReportError(\"*** unexpected module index.\\n\");\n            LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LOADER_GetFileInfo\", 152);\n            LiCloseBufferIfError();\n            return -470064;\n         }\n\n         fileInfo->tlsModuleIndex = static_cast<int16_t>((*nextTlsModuleNumber)++);\n      }\n   }\n\n   // Verify the outFileInfo pointer\n   if (auto error = LiValidateAddress(outFileInfo,\n                                      sizeof(LOADER_UserFileInfo),\n                                      0, -470062,\n                                      virt_addr { 0x10000000 },\n                                      virt_addr { 0xC0000000 },\n                                      \"file info return\")) {\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetFileInfo\", 165);\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   // Set the file info\n   rpl->userFileInfo = outFileInfo;\n   outFileInfo->size = static_cast<uint32_t>(sizeof(LOADER_UserFileInfo));\n   outFileInfo->magic = 0xCABE0402u;\n   outFileInfo->pathStringLength = 0u;\n   outFileInfo->pathString = nullptr;\n   outFileInfo->fileInfoFlags = fileInfo->flags;\n   outFileInfo->tlsAlignShift = fileInfo->tlsAlignShift;\n   outFileInfo->tlsModuleIndex = fileInfo->tlsModuleIndex;\n   outFileInfo->shstrndx = rpl->elfHeader.shstrndx;\n\n   if (rpl->fileType == ios::mcp::MCPFileType::ProcessCode) {\n      outFileInfo->titleLocation = getProcTitleLoc();\n   } else {\n      outFileInfo->titleLocation = 0u;\n   }\n\n   updateFileInfoForUser(rpl, outFileInfo, nullptr);\n   return 0;\n}\n\nint32_t\nLOADER_GetPathString(kernel::UniqueProcessId upid,\n                     LOADER_Handle handle,\n                     virt_ptr<uint32_t> outPathStringSize,\n                     virt_ptr<char> pathStringBuffer,\n                     virt_ptr<LOADER_UserFileInfo> outFileInfo)\n{\n   auto globals = getGlobalStorage();\n   if (globals->currentUpid != upid) {\n      Loader_ReportError(\"*** Loader address space not set for process {} but called for {}.\",\n                         static_cast<int>(globals->currentUpid.value()),\n                         static_cast<int>(upid));\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetPathString\", 304);\n      LiCloseBufferIfError();\n      return Error::DifferentProcess;\n   }\n\n   // Verify outPathStringSize pointer\n   if (auto error = LiValidateAddress(outPathStringSize,\n                                      sizeof(uint32_t),\n                                      0, -470098,\n                                      virt_addr { 0x10000000 },\n                                      virt_addr { 0xC0000000 },\n                                      \"file info bytes\")) { // Yes they copy and pasted this in loader.elf\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetPathString\", 315);\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   // Find the module\n   if (!handle) {\n      handle = globals->loadedRpx->moduleNameBuffer;\n   }\n\n   auto rpl = getModule(handle);\n   if (!rpl) {\n      Loader_ReportError(\"*** module not found.\");\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetPathString\", 334);\n      LiCloseBufferIfError();\n      return -470010;\n   }\n\n   // Check path string size\n   if (!rpl->fileInfoBuffer || !rpl->fileInfoBuffer->filename) {\n      *outPathStringSize = 0u;\n      return 0;\n   }\n\n   auto path = virt_cast<char *>(rpl->fileInfoBuffer) + rpl->fileInfoBuffer->filename;\n   auto pathLength = static_cast<uint32_t>(\n      strnlen(path.get(),\n              rpl->fileInfoBufferSize - rpl->fileInfoBuffer->filename) + 1);\n\n   if (!pathStringBuffer ||\n       *outPathStringSize == 0 ||\n       pathLength == 0) {\n      *outPathStringSize = pathLength;\n      return 0;\n   }\n\n   if (*outPathStringSize < pathLength) {\n      Loader_ReportError(\"*** notify path size is too small.\\n\");\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LOADER_GetPathString\", 354);\n      return -470068;\n   }\n\n   *outPathStringSize = pathLength;\n\n   // Verify the pathStringBuffer pointer\n   if (auto error = LiValidateAddress(pathStringBuffer,\n                                      pathLength,\n                                      0, -470099,\n                                      virt_addr { 0x10000000 },\n                                      virt_addr { 0xC0000000 },\n                                      \"path string return\")) {\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_GetPathString\", 366);\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   // Copy the path string\n   string_copy(pathStringBuffer.get(),\n               path.get(),\n               pathLength);\n   pathStringBuffer[pathLength - 1] = char { 0 };\n\n   if (outFileInfo) {\n      outFileInfo->pathString = pathStringBuffer;\n      outFileInfo->pathStringLength = pathLength;\n   }\n\n   return 0;\n}\n\nint32_t\nLOADER_Query(kernel::UniqueProcessId upid,\n             LOADER_Handle handle,\n             virt_ptr<LOADER_MinFileInfo> minFileInfo)\n{\n   auto globals = getGlobalStorage();\n   if (globals->currentUpid != upid) {\n      Loader_ReportError(\"*** Loader address space not set for process {} but called for {}.\",\n                         static_cast<int>(globals->currentUpid.value()),\n                         static_cast<int>(upid));\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_Query\", 40);\n      return Error::DifferentProcess;\n   }\n\n   if (auto error = LiValidateMinFileInfo(minFileInfo, \"LOADER_Query\")) {\n      return error;\n   }\n\n   if (minFileInfo->outNumberOfSections) {\n      if (auto error = LOADER_GetSecInfo(upid, handle,\n                                         minFileInfo->outNumberOfSections,\n                                         minFileInfo->outSectionInfo)) {\n         return error;\n      }\n   }\n\n   if (minFileInfo->outSizeOfFileInfo) {\n      if (auto error = LOADER_GetFileInfo(upid, handle,\n                                          minFileInfo->outSizeOfFileInfo,\n                                          minFileInfo->outFileInfo,\n                                          minFileInfo->inoutNextTlsModuleNumber,\n                                          virt_addrof(minFileInfo->fileLocation))) {\n         return error;\n      }\n   }\n\n   if (minFileInfo->outPathStringSize) {\n      if (auto error = LOADER_GetPathString(upid, handle,\n                                            minFileInfo->outPathStringSize,\n                                            minFileInfo->pathStringBuffer,\n                                            minFileInfo->outFileInfo)) {\n         return error;\n      }\n   }\n\n   return 0;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_query.h",
    "content": "#pragma once\n#include \"cafe_loader_minfileinfo.h\"\n\nnamespace cafe::loader::internal\n{\n\nvirt_ptr<LOADED_RPL>\ngetModule(LOADER_Handle handle);\n\nint32_t\nLOADER_GetSecInfo(kernel::UniqueProcessId upid,\n                  LOADER_Handle handle,\n                  virt_ptr<uint32_t> outNumberOfSections,\n                  virt_ptr<LOADER_SectionInfo> outSectionInfo);\n\nint32_t\nLOADER_GetFileInfo(kernel::UniqueProcessId upid,\n                   LOADER_Handle handle,\n                   virt_ptr<uint32_t> outSizeOfFileInfo,\n                   virt_ptr<LOADER_UserFileInfo> outFileInfo,\n                   virt_ptr<uint32_t> nextTlsModuleNumber,\n                   virt_ptr<uint32_t> outFileLocation);\n\nint32_t\nLOADER_GetPathString(kernel::UniqueProcessId upid,\n                     LOADER_Handle handle,\n                     virt_ptr<uint32_t> outPathStringSize,\n                     virt_ptr<char> pathStringBuffer,\n                     virt_ptr<LOADER_UserFileInfo> outFileInfo);\n\nint32_t\nLOADER_Query(kernel::UniqueProcessId upid,\n             LOADER_Handle handle,\n             virt_ptr<LOADER_MinFileInfo> minFileInfo);\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_reloc.cpp",
    "content": "#include \"cafe_loader_basics.h\"\n#include \"cafe_loader_error.h\"\n#include \"cafe_loader_flush.h\"\n#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_loaded_rpl.h\"\n#include \"cafe_loader_reloc.h\"\n#include \"cafe_loader_rpl.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_globals.h\"\n#include \"cafe_loader_utils.h\"\n\n#include \"cafe/libraries/cafe_hle.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libcpu/cpu_formatters.h>\n#include <libcpu/espresso/espresso_instructionset.h>\n#include <libcpu/espresso/espresso_spr.h>\n#include <zlib.h>\n\nnamespace cafe::loader::internal\n{\n\nconstexpr auto TrampSize = uint32_t { 16 };\nstatic std::array<uint8_t, 0x1FF8> sRelocBuffer;\n\nstatic virt_ptr<rpl::Export>\nLiBinSearchExport(virt_ptr<rpl::Export> exports,\n                  uint32_t numExports,\n                  virt_ptr<char> name)\n{\n   if (!exports || !numExports || !name || !name[0]) {\n      return nullptr;\n   }\n\n   auto strTable = virt_cast<char *>(virt_cast<virt_addr>(exports) - 8);\n   auto left = 0u;\n   auto right = numExports;\n\n   while (left < right) {\n      auto index = left + (right - left) / 2;\n      auto exportName = strTable + (exports[index].name & 0x7FFFFFFF);\n      auto cmpValue = strcmp(name.get(), exportName.get());\n      if (cmpValue == 0) {\n         return exports + index;\n      } else if (cmpValue < 0) {\n         right = index;\n      } else {\n         left = index + 1;\n      }\n   }\n\n   return nullptr;\n}\n\nstatic int32_t\nsFixupOneSymbolTable(virt_ptr<LOADED_RPL> rpl,\n                     uint32_t sectionIndex,\n                     virt_ptr<rpl::SectionHeader> sectionHeader,\n                     virt_ptr<LiImportTracking> imports,\n                     uint32_t unk)\n{\n   auto strTable = virt_ptr<char> { nullptr };\n   LiCheckAndHandleInterrupts();\n\n   if (sectionHeader->link < rpl->elfHeader.shnum) {\n      strTable = virt_cast<char *>(rpl->sectionAddressBuffer[sectionHeader->link]);\n   }\n\n   auto symbolEntrySize = sectionHeader->entsize;\n   if (!symbolEntrySize) {\n      symbolEntrySize = static_cast<uint32_t>(sizeof(rpl::Symbol));\n   }\n\n   auto symbolBuffer = rpl->sectionAddressBuffer[sectionIndex];\n   auto numSymbols = sectionHeader->size / symbolEntrySize;\n   for (auto i = 1u; i < numSymbols; ++i) {\n      auto symbol = virt_cast<rpl::Symbol *>(symbolBuffer + (i * symbolEntrySize));\n      if (symbol->shndx == 0 || symbol->shndx >= rpl->elfHeader.shnum) {\n         continue;\n      }\n\n      auto targetSectionAddress = rpl->sectionAddressBuffer[symbol->shndx];\n      if (!targetSectionAddress) {\n         symbol->value = 0xCD000000u | i;\n         continue;\n      }\n\n      LiCheckAndHandleInterrupts();\n\n      auto targetSectionHeader = getSectionHeader(rpl, symbol->shndx);\n      auto targetSectionOffset = symbol->value - targetSectionHeader->addr;\n      if (!imports || imports[symbol->shndx].numExports == 0 || targetSectionOffset < 8) {\n         // auto binding = symbol->info >> 4;\n         auto type = symbol->info & 0xf;\n\n         // Not really sure what this is doing tbh\n         if (type != rpl::STT_TLS &&\n             (targetSectionHeader->type != rpl::SHT_RPL_IMPORTS || unk != 1) &&\n             unk != 2) {\n            symbol->value = static_cast<uint32_t>(targetSectionAddress + targetSectionOffset);\n         }\n\n         continue;\n      }\n\n      if (!strTable) {\n         Loader_ReportError(\"*** imports require a string table but there isn't one - can't link blind symbol.\");\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sFixupOneSymbolTable\", 529);\n         return -470007;\n      }\n\n      auto symbolName = strTable + symbol->name;\n      if (!symbolName[0]) {\n         symbol->value = 0xCD000000u | i;\n         continue;\n      }\n\n      auto &import = imports[symbol->shndx];\n      auto symbolExport = LiBinSearchExport(import.exports,\n                                            import.numExports,\n                                            symbolName);\n      if (symbolExport) {\n         symbol->value = symbolExport->value;\n      } else {\n         symbol->value =\n            cafe::hle::registerUnimplementedSymbol(\n               std::string_view {\n                  import.rpl->moduleNameBuffer.get(),\n                  import.rpl->moduleNameLen\n               },\n               symbolName.get());\n         if (!symbol->value) {\n            // Must not be from a HLE library, so let's error out like normal\n            Loader_ReportError(\"*** could not find imported symbol \\\"{}\\\".\", symbolName);\n            LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sFixupOneSymbolTable\", 551);\n            return -470005;\n         }\n      }\n   }\n\n   return 0;\n}\n\nstatic int32_t\nLiCpu_RelocAdd(bool isRpx,\n               virt_addr targetSectionBuffer,\n               rpl::RelocationType relaType,\n               bool isWeakSymbol,\n               uint32_t offset,\n               uint32_t addend,\n               uint32_t symbolValue,\n               virt_addr *preTrampBuffer,\n               uint32_t *preTrampAvailableEntries,\n               virt_addr *postTrampBuffer,\n               uint32_t *postTrampAvailableEntries,\n               int32_t tlsModuleIndex,\n               uint32_t fileType)\n{\n   auto target = targetSectionBuffer + offset;\n   auto value = symbolValue + addend;\n   auto relValue = value - static_cast<uint32_t>(target);\n\n   if (isRpx) {\n      // SDA based relocations are only valid for a .rpx\n      switch (relaType) {\n      case rpl::R_PPC_EMB_SDA21:\n      {\n         auto ins = espresso::Instruction { +*virt_cast<uint32_t *>(target) };\n         if (ins.rA == 2) {\n            value -= getGlobalStorage()->sda2Base;\n         } else if (ins.rA == 13) {\n            value -= getGlobalStorage()->sdaBase;\n         } else if (ins.rA != 0) {\n            Loader_ReportError(\"***ERROR: SDA21_HI malformed.\\n\");\n            LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 166);\n            return -470039;\n         }\n\n         ins.simm = static_cast<uint16_t>(value);\n         *virt_cast<uint32_t *>(target) = ins.value;\n         return 0;\n      }\n      case rpl::R_PPC_EMB_RELSDA:\n      case rpl::R_PPC_DIAB_SDA21_LO:\n      case rpl::R_PPC_DIAB_SDA21_HI:\n      case rpl::R_PPC_DIAB_SDA21_HA:\n      case rpl::R_PPC_DIAB_RELSDA_LO:\n      case rpl::R_PPC_DIAB_RELSDA_HI:\n      case rpl::R_PPC_DIAB_RELSDA_HA:\n         // TODO: Implement these relocation types\n         decaf_abort(fmt::format(\"Unimplemented relocation type {}\", relaType));\n         return -470038;\n      }\n   }\n\n   switch (relaType) {\n   case rpl::R_PPC_NONE:\n      break;\n   case rpl::R_PPC_ADDR32:\n      *virt_cast<uint32_t *>(target) = value;\n      break;\n   case rpl::R_PPC_ADDR16_LO:\n      *virt_cast<uint16_t *>(target) = static_cast<uint16_t>(value & 0xFFFF);\n      break;\n   case rpl::R_PPC_ADDR16_HI:\n      *virt_cast<uint16_t *>(target) = static_cast<uint16_t>(value >> 16);\n      break;\n   case rpl::R_PPC_ADDR16_HA:\n      *virt_cast<uint16_t *>(target) = static_cast<uint16_t>((value + 0x8000) >> 16);\n      break;\n   case rpl::R_PPC_DTPMOD32:\n      *virt_cast<int32_t *>(target) = tlsModuleIndex;\n      break;\n   case rpl::R_PPC_DTPREL32:\n      *virt_cast<uint32_t *>(target) = value;\n      break;\n   case rpl::R_PPC_GHS_REL16_HA:\n      *virt_cast<uint16_t *>(target) = static_cast<uint16_t>((relValue + 0x8000) >> 16);\n      break;\n   case rpl::R_PPC_GHS_REL16_HI:\n      *virt_cast<uint16_t *>(target) = static_cast<uint16_t>(relValue >> 16);\n      break;\n   case rpl::R_PPC_GHS_REL16_LO:\n      *virt_cast<uint16_t *>(target) = static_cast<uint16_t>(relValue & 0xFFFF);\n      break;\n   case rpl::R_PPC_REL14:\n   {\n      if (isWeakSymbol && !symbolValue) {\n         symbolValue = static_cast<uint32_t>(target);\n         value = symbolValue + addend;\n      }\n\n      auto distance = static_cast<int32_t>(value) - static_cast<int32_t>(target);\n      if (distance > 0x7FFC || distance < -0x7FFC) {\n         Loader_ReportError(\"***14-bit relative branch cannot hit target.\");\n         LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 398);\n         return -470039;\n      }\n\n      if (distance & 3) {\n         Loader_ReportError(\"***RELOC ERROR {}: lower 2 bits must be zero before shifting.\",\n                            -470040);\n         LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 408);\n         return -470040;\n      }\n\n      if ((distance >= 0 && (distance & 0xFFFF8000)) ||\n          (distance < 0 && ((distance & 0xFFFF8000) != 0xFFFF8000))) {\n         Loader_ReportError(\n            \"***RELOC ERROR {}: upper 17 bits before shift must all be the same.\",\n            -470040);\n         LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 429);\n         return -470040;\n      }\n\n      *virt_cast<uint32_t *>(target) =\n         (*virt_cast<uint32_t *>(target) & 0xFFBF0003) |\n         static_cast<uint16_t>(distance & 0xFFFC);\n      break;\n   }\n   case rpl::R_PPC_REL24:\n   {\n      if (isWeakSymbol && !symbolValue) {\n         symbolValue = static_cast<uint32_t>(target);\n         value = symbolValue + addend;\n      }\n\n      // Check if we need to generate a trampoline\n      auto distance = static_cast<int32_t>(value) - static_cast<int32_t>(target);\n      if (distance > 0x1FFFFFC || distance < -0x1FFFFFC) {\n         auto tramp = virt_ptr<uint32_t> { nullptr };\n\n         if (*postTrampBuffer - target <= 0x1FFFFFC) {\n            if (*postTrampAvailableEntries == 0) {\n               Loader_ReportError(\"Post tramp buffer exhausted.\");\n               LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 323);\n               return -470037;\n            }\n\n            tramp = virt_cast<uint32_t *>(*postTrampBuffer);\n            *postTrampBuffer += 16;\n            *postTrampAvailableEntries -= 1;\n         } else if (target - *preTrampBuffer <= 0x1FFFFFC) {\n            if (*preTrampAvailableEntries == 0) {\n               Loader_ReportError(\"Pre tramp buffer exhausted.\");\n               LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 310);\n               return -470037;\n            }\n\n            tramp = virt_cast<uint32_t *>(*preTrampBuffer);\n            *preTrampBuffer -= 16;\n            *preTrampAvailableEntries -= 1;\n         } else {\n            Loader_ReportError(\"**Cannot link 24-bit jump (too far to tramp buffer).\");\n            LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 303);\n            return -470037;\n         }\n\n         /* Write the trampoline:\n          * lis r11, 0\n          * ori r11, r11, 0\n          * mtctr r11\n          * bctr\n          */\n         auto lis = espresso::encodeInstruction(espresso::InstructionID::addis);\n         lis.rD = 11;\n         lis.rA = 0;\n         lis.simm = (value >> 16) & 0xFFFF;\n\n         auto ori = espresso::encodeInstruction(espresso::InstructionID::ori);\n         ori.rA = 11;\n         ori.rS = 11;\n         ori.uimm = value & 0xFFFF;\n\n         auto mtctr = espresso::encodeInstruction(espresso::InstructionID::mtspr);\n         mtctr.rS = 11;\n         espresso::encodeSPR(mtctr, espresso::SPR::CTR);\n\n         auto bctr = espresso::encodeInstruction(espresso::InstructionID::bcctr);\n         bctr.bo = 0b10100;\n\n         tramp[0] = lis.value;\n         tramp[1] = ori.value;\n         tramp[2] = mtctr.value;\n         tramp[3] = bctr.value;\n\n         symbolValue = static_cast<uint32_t>(virt_cast<virt_addr>(tramp));\n         value = symbolValue + addend;\n         distance = static_cast<int32_t>(value) - static_cast<int32_t>(target);\n      }\n\n      if (distance & 3) {\n         Loader_ReportError(\n            \"***RELOC ERROR {}: lower 2 bits must be zero before shifting.\",\n            -470022);\n         LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 348);\n         return -470037;\n      }\n\n      if (distance < 0 && (distance & 0xFE000000) != 0xFE000000) {\n         Loader_ReportError(\n            \"***RELOC ERROR {}: upper 7 bits before shift must all be the same (1).\",\n            -470040);\n         LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 359);\n         return -470038;\n      }\n\n      if (distance >= 0 && (distance & 0xFE000000)) {\n         Loader_ReportError(\n            \"***RELOC ERROR {}: upper 7 bits before shift must all be the same (0).\",\n            -470040);\n         LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 371);\n         return -470038;\n      }\n\n      *virt_cast<uint32_t *>(target) =\n         (*virt_cast<uint32_t *>(target) & 0xFC000003) |\n         (static_cast<uint32_t>(distance) & 0x3FFFFFC);\n      break;\n   }\n   default:\n      Loader_ReportError(\"***ERROR: Unsupported Relocation_Add Type ({}):\", relaType);\n      LiSetFatalError(0x18729Bu, fileType, 1, \"LiCpu_RelocAdd\", 462);\n      return -470038;\n   }\n\n   return 0;\n}\n\nstatic int32_t\nsExecReloc(virt_ptr<LOADED_RPL> rpl,\n           bool isRpx,\n           uint32_t sectionIndex,\n           virt_ptr<rpl::SectionHeader> sectionHeader,\n           virt_addr *preTrampBuffer,\n           uint32_t *preTrampBufferSize,\n           virt_addr *postTrampBuffer,\n           uint32_t *postTrampBufferSize,\n           virt_ptr<LiImportTracking> imports)\n{\n   if (sectionHeader->info >= rpl->elfHeader.shnum ||\n       !rpl->sectionAddressBuffer[sectionHeader->info]) {\n      return 0;\n   }\n\n   auto targetSectionBuffer = rpl->sectionAddressBuffer[sectionHeader->info];\n   auto targetSectionVirtualAddress = getSectionHeader(rpl, sectionHeader->info)->addr;\n\n   if (sectionHeader->link >= rpl->elfHeader.shnum ||\n       !rpl->sectionAddressBuffer[sectionHeader->link]) {\n      Loader_ReportError(\"*** relocations symbol table missing.\");\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 93);\n      return -470003;\n   }\n\n   auto symbolSectionHeader = getSectionHeader(rpl, sectionHeader->link);\n   auto symbolSectionEntSize = symbolSectionHeader->entsize;\n   auto symbolSection = rpl->sectionAddressBuffer[sectionHeader->link];\n   if (!symbolSectionEntSize) {\n      symbolSectionEntSize = static_cast<uint32_t>(sizeof(rpl::Symbol));\n   }\n\n   auto symbolCount = symbolSectionHeader->size / symbolSectionEntSize;\n   if (symbolCount == 0) {\n      Loader_ReportError(\"*** relocations symbol table empty.\");\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 107);\n      return -470004;\n   }\n\n   auto symbolStringTable = virt_ptr<char> { nullptr };\n   if (symbolSectionHeader->link < rpl->elfHeader.shnum) {\n      symbolStringTable = virt_cast<char *>(rpl->sectionAddressBuffer[symbolSectionHeader->link]);\n   }\n\n   auto relaEntSize = sectionHeader->entsize;\n   auto relaSectionSize = sectionHeader->size;\n   auto relaSectionAddress = rpl->sectionAddressBuffer[sectionIndex];\n   if (sectionHeader->flags & rpl::SHF_DEFLATED) {\n      relaSectionSize = *virt_cast<uint32_t *>(relaSectionAddress);\n   }\n\n   if (!relaEntSize) {\n      relaEntSize = static_cast<uint32_t>(sizeof(rpl::Rela));\n   } else if (relaEntSize != sizeof(rpl::Rela)) {\n      Loader_ReportError(\n         \"*** sExecReloc: relocation section sh_entsize is not {}; err = {}\",\n         sizeof(rpl::Rela), -470052);\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 144);\n      return -470052;\n   }\n\n   auto relaCount = relaSectionSize / relaEntSize;\n   if (relaSectionSize != relaCount * relaEntSize) {\n      Loader_ReportError(\n         \"*** sExecReloc: relocation section size not a multiple of %d; err = %d.\\n\",\n         sizeof(rpl::Rela), -470053);\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 154);\n      return -470053;\n   }\n\n   if (relaCount <= 0) {\n      return 0;\n   }\n\n   auto stream = z_stream { };\n   std::memset(&stream, 0, sizeof(stream));\n\n   if (sectionHeader->flags & rpl::SHF_DEFLATED) {\n      auto zlibError = inflateInit(&stream);\n      if (zlibError != Z_OK) {\n         switch (zlibError) {\n         case Z_STREAM_ERROR:\n            LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 186);\n            Loader_ReportError(\"*** sExecReloc: could not initialize ZLIB uncompressor; ZLIB err = {}.\",\n                               Error::ZlibStreamError);\n            return Error::ZlibStreamError;\n         case Z_MEM_ERROR:\n            LiSetFatalError(0x187298u, rpl->fileType, 0, \"sExecReloc\", 178);\n            Loader_ReportError(\"*** sExecReloc: could not initialize ZLIB uncompressor; ZLIB err = {}.\",\n                               Error::ZlibMemError);\n            return Error::ZlibMemError;\n         case Z_VERSION_ERROR:\n            LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 182);\n            Loader_ReportError(\"*** sExecReloc: could not initialize ZLIB uncompressor; ZLIB err = {}.\",\n                               Error::ZlibVersionError);\n            return Error::ZlibVersionError;\n         default:\n            LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 191);\n            Loader_ReportError(\"***Unknown ZLIB error {} (0x{:08X}).\", zlibError, zlibError);\n            return Error::ZlibUnknownError;\n         }\n      }\n\n      stream.avail_in = sectionHeader->size;\n      stream.next_in = virt_cast<Bytef *>(relaSectionAddress + 4).get();\n   }\n\n   auto remainingBytes = relaSectionSize;\n   auto remainingRela = relaCount;\n   auto zlibError = Z_STREAM_END;\n\n   while (remainingBytes > 0) {\n      // Read whole shit\n      auto availableBytes = remainingBytes;\n      auto relas = virt_cast<rpl::Rela *>(relaSectionAddress).get();\n      auto error = 0;\n\n      if (sectionHeader->flags & rpl::SHF_DEFLATED) {\n         availableBytes = std::min<uInt>(remainingBytes, static_cast<uInt>(sRelocBuffer.size()));\n         stream.avail_out = availableBytes;\n         stream.next_out = reinterpret_cast<Bytef *>(sRelocBuffer.data());\n\n         LiCheckAndHandleInterrupts();\n         zlibError = inflate(&stream, Z_NO_FLUSH);\n         if (zlibError != Z_BUF_ERROR &&\n            (zlibError < Z_OK || zlibError == Z_NEED_DICT)) {\n            switch (zlibError) {\n            case Z_MEM_ERROR:\n               LiSetFatalError(0x187298u, rpl->fileType, 0, \"sExecReloc\", 236);\n               error = Error::ZlibMemError;\n               break;\n            case Z_NEED_DICT:\n               zlibError = Z_DATA_ERROR;\n               // fallthrough\n            case Z_DATA_ERROR:\n               LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 240);\n               error = Error::ZlibDataError;\n               break;\n            case Z_STREAM_ERROR:\n               LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 244);\n               error = Error::ZlibStreamError;\n               break;\n            default:\n               LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 191);\n               error = Error::ZlibUnknownError;\n               break;\n            }\n\n            Loader_ReportError(\"*** sExecReloc: unable to uncompress relocation section; ZLIB err = {}.\",\n                               zlibError);\n            inflateEnd(&stream);\n            return error;\n         }\n\n         if (stream.avail_out) {\n            Loader_ReportError(\"*** sExecReloc: ZLIB inflate's avail_out is {}, expected 0; err = {}.\",\n                               stream.avail_out, -470055);\n            LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 254);\n            inflateEnd(&stream);\n            return -470055;\n         }\n\n         if (stream.total_out != sizeof(rpl::Rela) * (stream.total_out / sizeof(rpl::Rela))) {\n            Loader_ReportError(\n               \"*** sExecReloc: inflate did not read a multiple of sizeof(ELF32_RELOC_ADD); err = {}.\",\n               -470055);\n            LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 261);\n            inflateEnd(&stream);\n            return -470055;\n         }\n\n         relas = reinterpret_cast<rpl::Rela *>(sRelocBuffer.data());\n      }\n\n      auto availableRela = availableBytes / sizeof(rpl::Rela);\n      for (auto relaIndex = 0u; relaIndex < availableRela; ++relaIndex) {\n         auto &rela = relas[relaIndex];\n         auto symbolIndex = rela.info >> 8;\n         auto relaType = static_cast<rpl::RelocationType>(rela.info & 0xFF);\n\n         LiCheckAndHandleInterrupts();\n\n         if (symbolIndex > symbolCount) {\n            Loader_ReportError(\n               \"***{} Rel invalid: rel ix {}, ref sym ix {}, max is {}. type {}\",\n               sectionIndex,\n               relaIndex,\n               symbolIndex,\n               symbolCount,\n               static_cast<int>(relaType));\n            Loader_ReportError(\"*** r_info 0x{:08X}\\n\", rela.offset);\n            Loader_ReportError(\"*** r_addend 0x{:08X}\\n\", rela.addend);\n            Loader_ReportError(\"*** r_offset 0x{:08X}\\n\", rela.offset);\n            LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 286);\n            error = -470005;\n            break;\n         }\n\n         auto symbol = virt_cast<rpl::Symbol *>(symbolSection + symbolIndex * symbolSectionEntSize);\n         auto symbolValue = symbol->value;\n         auto symbolBinding = symbol->info >> 4;\n         auto symbolType = symbol->info & 0xf;\n\n         if ((symbolValue & 0xFF000000) == 0xCD000000) {\n            if (symbolBinding != rpl::STB_WEAK) {\n               if (symbolStringTable) {\n                  Loader_ReportError(\n                     \"***Rel invalid: sym ix {} val 0x{:08X} \\\"{}\\\"\",\n                     symbolIndex, symbolValue, symbolStringTable + symbol->name);\n               } else {\n                  Loader_ReportError(\n                     \"***Rel invalid: sym ix {} val 0x{:08X}\",\n                     symbolIndex, symbolValue);\n               }\n\n               LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 308);\n               error = -470006;\n               break;\n            } else {\n               symbolValue = 0u;\n            }\n         }\n\n         auto tlsModuleIndex = -1;\n         if (relaType == rpl::R_PPC_DTPMOD32 || relaType == rpl::R_PPC_DTPREL32) {\n            if (symbolType != rpl::STT_TLS) {\n               Loader_ReportError(\"***TLS relocation used with non-TLS symbol 0x{:08X}\", symbolValue);\n               LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 349);\n               error = -470065;\n               break;\n            }\n\n            if (imports && imports[symbol->shndx].numExports) {\n               tlsModuleIndex = imports[symbol->shndx].tlsModuleIndex;\n               if (tlsModuleIndex == -1) {\n                  Loader_ReportError(\"***TLS relocation used with non-TLS import module 0x{:08X}.\", symbolValue);\n                  LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 327);\n                  error = -470101;\n                  break;\n               }\n            } else {\n               tlsModuleIndex = rpl->fileInfoBuffer->tlsModuleIndex;\n               if (tlsModuleIndex == -1) {\n                  Loader_ReportError(\"***TLS relocation used with non-TLS module 0x{:08X}.\", symbolValue);\n                  LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 339);\n                  error = -470102;\n                  break;\n               }\n            }\n         }\n\n         error = LiCpu_RelocAdd(isRpx,\n                                targetSectionBuffer,\n                                relaType,\n                                symbolBinding == rpl::STB_WEAK,\n                                rela.offset - targetSectionVirtualAddress,\n                                rela.addend,\n                                symbolValue,\n                                preTrampBuffer,\n                                preTrampBufferSize,\n                                postTrampBuffer,\n                                postTrampBufferSize,\n                                tlsModuleIndex,\n                                rpl->fileType);\n         if (error) {\n            break;\n         }\n\n         --remainingRela;\n      }\n\n      if (error) {\n         if (sectionHeader->flags & rpl::SHF_DEFLATED) {\n            inflateEnd(&stream);\n         }\n\n         return error;\n      }\n\n      remainingBytes -= availableBytes;\n   }\n\n   if (sectionHeader->flags & rpl::SHF_DEFLATED) {\n      inflateEnd(&stream);\n   }\n\n   if (remainingRela) {\n      Loader_ReportError(\n         \"*** sExecReloc: not all relocations were uncompressed; err = {}.\",\n         -470053);\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 379);\n      return -470053;\n   }\n\n   if (zlibError != Z_STREAM_END) {\n      Loader_ReportError(\n         \"*** sExecReloc: streaming failure err (zlib_ret != Z_STREAM_END) = {}.\",\n         -470055);\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sExecReloc\", 404);\n      return -470055;\n   }\n\n   LiCheckAndHandleInterrupts();\n   return 0;\n}\n\nint32_t\nLiFixupRelocOneRPL(virt_ptr<LOADED_RPL> rpl,\n                   virt_ptr<LiImportTracking> imports,\n                   uint32_t unk)\n{\n   LiCheckAndHandleInterrupts();\n\n   // Apply imports to symbol table\n   for (auto i = 1u; i < static_cast<unsigned int>(rpl->elfHeader.shnum - 2); ++i) {\n      auto sectionHeader = getSectionHeader(rpl, i);\n      if (sectionHeader->type == rpl::SHT_SYMTAB) {\n         if (auto error = sFixupOneSymbolTable(rpl, i, sectionHeader, imports, unk)) {\n            return error;\n         }\n      }\n   }\n\n   auto isRpx = rpl == getGlobalStorage()->loadedRpx;\n   auto textAddress = virt_cast<virt_addr>(rpl->textBuffer);\n\n   // Pre tramp buffer grows downawrds\n   auto preTrampBufferEnd = (textAddress + rpl->fileInfoBuffer->trampAdjust) & 0xFFFFFFF0u;\n   auto preTrampNext = preTrampBufferEnd - TrampSize;\n   auto preTrampAvailable = static_cast<uint32_t>((preTrampBufferEnd - textAddress) / TrampSize);\n\n   // Post tramp buffer grows upwards\n   auto postTrampBufferSize = (textAddress + rpl->fileInfoBuffer->textSize) - rpl->postTrampBuffer;\n   auto postTrampNext = virt_addr { rpl->postTrampBuffer };\n   auto postTrampAvailable = static_cast<uint32_t>(postTrampBufferSize / TrampSize);\n\n   auto textMax = rpl->postTrampBuffer + (postTrampAvailable * TrampSize);\n\n   // Apply relocations\n   for (auto i = 1u; i < static_cast<unsigned int>(rpl->elfHeader.shnum - 2); ++i) {\n      auto sectionHeader = getSectionHeader(rpl, i);\n      if (sectionHeader->type == rpl::SHT_RELA) {\n         if (auto error = sExecReloc(rpl,\n                                     isRpx,\n                                     i,\n                                     sectionHeader,\n                                     &preTrampNext,\n                                     &preTrampAvailable,\n                                     &postTrampNext,\n                                     &postTrampAvailable,\n                                     imports)) {\n            return error;\n         }\n      }\n   }\n\n   // Flush the code cache\n   if (textAddress) {\n      if (textMax - textAddress > rpl->textBufferSize) {\n         Loader_ReportError(\"*** Fatal error: code overflowed its allocation.\");\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LiFixupRelocOneRPL\", 695);\n         return -470069;\n      }\n\n      LiSafeFlushCode(textAddress, rpl->textBufferSize);\n   }\n\n   // Relocate entry point\n   rpl->entryPoint = virt_addr { 0 };\n\n   for (auto i = 1u; i < static_cast<unsigned int>(rpl->elfHeader.shnum - 2); ++i) {\n      auto sectionHeader = getSectionHeader(rpl, i);\n      auto sectionAddress = rpl->sectionAddressBuffer[i];\n      LiCheckAndHandleInterrupts();\n\n      if (!sectionHeader->size || !sectionAddress) {\n         continue;\n      }\n\n      if (sectionHeader->type != rpl::SHT_PROGBITS ||\n          !(sectionHeader->flags & rpl::SHF_EXECINSTR)) {\n         Loader_FlushDataRangeNoSync(sectionAddress, sectionHeader->size);\n      }\n\n      if (rpl->elfHeader.entry >= sectionHeader->addr &&\n          rpl->elfHeader.entry < sectionHeader->addr + sectionHeader->size) {\n         rpl->entryPoint = sectionAddress + (rpl->elfHeader.entry - sectionHeader->addr);\n         break;\n      }\n   }\n\n   if (!rpl->entryPoint) {\n      Loader_ReportError(\"*** Could not find/relocate module entry point. this is required.\\n\");\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LiFixupRelocOneRPL\", 740);\n      return -470092;\n   }\n\n   rpl->loadStateFlags &= ~LoaderStateFlag2;\n   return 0;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_reloc.h",
    "content": "#pragma once\n#include \"cafe_loader_basics.h\"\n#include \"cafe_loader_rpl.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader::internal\n{\n\nstruct LiImportTracking\n{\n   be2_val<uint32_t> numExports;\n   be2_virt_ptr<rpl::Export> exports;\n   be2_val<uint32_t> tlsModuleIndex;\n   be2_virt_ptr<LOADED_RPL> rpl;\n};\nCHECK_OFFSET(LiImportTracking, 0x00, numExports);\nCHECK_OFFSET(LiImportTracking, 0x04, exports);\nCHECK_OFFSET(LiImportTracking, 0x08, tlsModuleIndex);\nCHECK_OFFSET(LiImportTracking, 0x0C, rpl);\nCHECK_SIZE(LiImportTracking, 0x10);\n\nint32_t\nLiFixupRelocOneRPL(virt_ptr<LOADED_RPL> rpl,\n                   virt_ptr<LiImportTracking> imports,\n                   uint32_t unk);\n\n} // namespace cafe::loader::internal;\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_rpl.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\n#pragma pack(push, 1)\n\nnamespace cafe::loader::rpl\n{\n\nenum Machine : uint16_t\n{\n   EM_PPC = 20,\n};\n\nenum Encoding : uint8_t\n{\n   ELFDATANONE = 0,\n   ELFDATA2LSB = 1,\n   ELFDATA2MSB = 2,\n};\n\nenum Class : uint8_t\n{\n   ELFCLASS32 = 1,\n};\n\nenum : uint8_t\n{\n   EABI_CAFE = 0xCA,\n};\n\nenum : uint8_t\n{\n   EABI_VERSION_CAFE = 0xFE,\n};\n\nenum Version : uint8_t\n{\n   EV_NONE = 0,\n   EV_CURRENT = 1,\n};\n\nenum SectionFlags : uint32_t\n{\n   SHF_WRITE      = 0x1,\n   SHF_ALLOC      = 0x2,\n   SHF_EXECINSTR  = 0x4,\n   SHF_TLS        = 0x04000000,\n   SHF_DEFLATED   = 0x08000000,\n   SHF_MASKPROC   = 0xF0000000,\n};\n\nenum SectionType : uint32_t\n{\n   SHT_NULL = 0,\n   SHT_PROGBITS = 1,\n   SHT_SYMTAB = 2,\n   SHT_STRTAB = 3,\n   SHT_RELA = 4,\n   SHT_HASH = 5,\n   SHT_DYNAMIC = 6,\n   SHT_NOTE = 7,\n   SHT_NOBITS = 8,\n   SHT_REL = 9,\n   SHT_SHLIB = 10,\n   SHT_DYNSYM = 11,\n   SHT_INIT_ARRAY = 14,\n   SHT_FINI_ARRAY = 15,\n   SHT_PREINIT_ARRAY = 16,\n   SHT_GROUP = 17,\n   SHT_SYMTAB_SHNDX = 18,\n   SHT_LOPROC = 0x70000000u,\n   SHT_HIPROC = 0x7fffffffu,\n   SHT_LOUSER = 0x80000000u,\n   SHT_RPL_EXPORTS = 0x80000001u,\n   SHT_RPL_IMPORTS = 0x80000002u,\n   SHT_RPL_CRCS = 0x80000003u,\n   SHT_RPL_FILEINFO = 0x80000004u,\n   SHT_HIUSER = 0xffffffffu,\n};\n\nenum SymbolBinding : uint32_t\n{\n   STB_LOCAL = 0,\n   STB_GLOBAL = 1,\n   STB_WEAK = 2,\n   STB_GNU_UNIQUE = 10,\n   STB_LOOS = 10,\n   STB_HIOS = 12,\n   STB_LOPROC = 13,\n   STB_HIPROC = 15,\n};\n\nenum SymbolType : uint32_t\n{\n   STT_NOTYPE = 0,\n   STT_OBJECT = 1,\n   STT_FUNC = 2,\n   STT_SECTION = 3,\n   STT_FILE = 4,\n   STT_COMMON = 5,\n   STT_TLS = 6,\n   STT_LOOS = 7,\n   STT_HIOS = 8,\n   STT_GNU_IFUNC = 10,\n   STT_LOPROC = 13,\n   STT_HIPROC = 15,\n};\n\nenum SectionIndex : uint16_t\n{\n   SHN_UNDEF = 0,\n   SHN_LORESERVE = 0xff00,\n   SHN_ABS = 0xfff1,\n   SHN_COMMON = 0xfff2,\n   SHN_XINDEX = 0xffff,\n   SHN_HIRESERVE = 0xffff\n};\n\nenum RelocationType : uint32_t\n{\n   R_PPC_NONE = 0,\n   R_PPC_ADDR32 = 1,\n   R_PPC_ADDR16_LO = 4,\n   R_PPC_ADDR16_HI = 5,\n   R_PPC_ADDR16_HA = 6,\n   R_PPC_REL24 = 10,\n   R_PPC_REL14 = 11,\n   R_PPC_DTPMOD32 = 68,\n   R_PPC_DTPREL32 = 78,\n   R_PPC_EMB_SDA21 = 109,\n   R_PPC_EMB_RELSDA = 116,\n   R_PPC_DIAB_SDA21_LO = 180,\n   R_PPC_DIAB_SDA21_HI = 181,\n   R_PPC_DIAB_SDA21_HA = 182,\n   R_PPC_DIAB_RELSDA_LO = 183,\n   R_PPC_DIAB_RELSDA_HI = 184,\n   R_PPC_DIAB_RELSDA_HA = 185,\n   R_PPC_GHS_REL16_HA = 251,\n   R_PPC_GHS_REL16_HI = 252,\n   R_PPC_GHS_REL16_LO = 253,\n};\n\nenum FileInfoFlags : uint32_t\n{\n   RPL_IS_RPX     = 1 << 1,\n   RPL_FLAG_4     = 1 << 2,\n   RPL_HAS_TLS    = 1 << 3,\n};\n\nstruct Header\n{\n   be2_array<uint8_t, 4> magic;   // File identification.\n   be2_val<uint8_t> fileClass;    // File class.\n   be2_val<uint8_t> encoding;     // Data encoding.\n   be2_val<uint8_t> elfVersion;   // File version.\n   be2_val<uint8_t> abi;          // OS/ABI identification.\n   be2_val<uint8_t> abiVersion;   // OS/ABI version.\n   be2_array<uint8_t, 7> pad;\n\n   be2_val<uint16_t> type;        // Type of file (ET_*)\n   be2_val<uint16_t> machine;     // Required architecture for this file (EM_*)\n   be2_val<uint32_t> version;     // Must be equal to 1\n   be2_val<uint32_t> entry;       // Address to jump to in order to start program\n   be2_val<uint32_t> phoff;       // Program header table's file offset, in bytes\n   be2_val<uint32_t> shoff;       // Section header table's file offset, in bytes\n   be2_val<uint32_t> flags;       // Processor-specific flags\n   be2_val<uint16_t> ehsize;      // Size of ELF header, in bytes\n   be2_val<uint16_t> phentsize;   // Size of an entry in the program header table\n   be2_val<uint16_t> phnum;       // Number of entries in the program header table\n   be2_val<uint16_t> shentsize;   // Size of an entry in the section header table\n   be2_val<uint16_t> shnum;       // Number of entries in the section header table\n   be2_val<uint16_t> shstrndx;    // Sect hdr table index of sect name string table\n};\nCHECK_OFFSET(Header, 0x00, magic);\nCHECK_OFFSET(Header, 0x04, fileClass);\nCHECK_OFFSET(Header, 0x05, encoding);\nCHECK_OFFSET(Header, 0x06, elfVersion);\nCHECK_OFFSET(Header, 0x07, abi);\nCHECK_OFFSET(Header, 0x08, abiVersion);\nCHECK_OFFSET(Header, 0x10, type);\nCHECK_OFFSET(Header, 0x12, machine);\nCHECK_OFFSET(Header, 0x14, version);\nCHECK_OFFSET(Header, 0x18, entry);\nCHECK_OFFSET(Header, 0x1C, phoff);\nCHECK_OFFSET(Header, 0x20, shoff);\nCHECK_OFFSET(Header, 0x24, flags);\nCHECK_OFFSET(Header, 0x28, ehsize);\nCHECK_OFFSET(Header, 0x2A, phentsize);\nCHECK_OFFSET(Header, 0x2C, phnum);\nCHECK_OFFSET(Header, 0x2E, shentsize);\nCHECK_OFFSET(Header, 0x30, shnum);\nCHECK_OFFSET(Header, 0x32, shstrndx);\nCHECK_SIZE(Header, 0x34);\n\nstruct ProgramHeader\n{\n   be2_val<uint32_t> type;\n   be2_val<uint32_t> offset;\n   be2_val<uint32_t> vaddr;\n   be2_val<uint32_t> paddr;\n   be2_val<uint32_t> filesz;\n   be2_val<uint32_t> memsz;\n   be2_val<uint32_t> flags;\n   be2_val<uint32_t> align;\n};\nCHECK_OFFSET(ProgramHeader, 0x00, type);\nCHECK_OFFSET(ProgramHeader, 0x04, offset);\nCHECK_OFFSET(ProgramHeader, 0x08, vaddr);\nCHECK_OFFSET(ProgramHeader, 0x0C, paddr);\nCHECK_OFFSET(ProgramHeader, 0x10, filesz);\nCHECK_OFFSET(ProgramHeader, 0x14, memsz);\nCHECK_OFFSET(ProgramHeader, 0x18, flags);\nCHECK_OFFSET(ProgramHeader, 0x1C, align);\nCHECK_SIZE(ProgramHeader, 0x20);\n\nstruct SectionHeader\n{\n   //! Section name (index into string table)\n   be2_val<uint32_t> name;\n\n   //! Section type (SHT_*)\n   be2_val<uint32_t> type;\n\n   //! Section flags (SHF_*)\n   be2_val<uint32_t> flags;\n\n   //! Address where section is to be loaded\n   be2_val<uint32_t> addr;\n\n   //! File offset of section data, in bytes\n   be2_val<uint32_t> offset;\n\n   //! Size of section, in bytes\n   be2_val<uint32_t> size;\n\n   //! Section type-specific header table index link\n   be2_val<uint32_t> link;\n\n   //! Section type-specific extra information\n   be2_val<uint32_t> info;\n\n   //! Section address alignment\n   be2_val<uint32_t> addralign;\n\n   //! Size of records contained within the section\n   be2_val<uint32_t> entsize;\n};\nCHECK_OFFSET(SectionHeader, 0x00, name);\nCHECK_OFFSET(SectionHeader, 0x04, type);\nCHECK_OFFSET(SectionHeader, 0x08, flags);\nCHECK_OFFSET(SectionHeader, 0x0C, addr);\nCHECK_OFFSET(SectionHeader, 0x10, offset);\nCHECK_OFFSET(SectionHeader, 0x14, size);\nCHECK_OFFSET(SectionHeader, 0x18, link);\nCHECK_OFFSET(SectionHeader, 0x1C, info);\nCHECK_OFFSET(SectionHeader, 0x20, addralign);\nCHECK_OFFSET(SectionHeader, 0x24, entsize);\nCHECK_SIZE(SectionHeader, 0x28);\n\nstruct Symbol\n{\n   //! Symbol name (index into string table)\n   be2_val<uint32_t> name;\n\n   //! Value or address associated with the symbol\n   be2_val<uint32_t> value;\n\n   //! Size of the symbol\n   be2_val<uint32_t> size;\n\n   //! Symbol's type and binding attributes\n   be2_val<uint8_t> info;\n\n   //! Must be zero; reserved\n   be2_val<uint8_t> other;\n\n   //! Which section (header table index) it's defined in (SHN_*)\n   be2_val<uint16_t> shndx;\n};\nCHECK_OFFSET(Symbol, 0x00, name);\nCHECK_OFFSET(Symbol, 0x04, value);\nCHECK_OFFSET(Symbol, 0x08, size);\nCHECK_OFFSET(Symbol, 0x0C, info);\nCHECK_OFFSET(Symbol, 0x0D, other);\nCHECK_OFFSET(Symbol, 0x0E, shndx);\nCHECK_SIZE(Symbol, 0x10);\n\nstruct Rela\n{\n   be2_val<uint32_t> offset;\n   be2_val<uint32_t> info;\n   be2_val<int32_t> addend;\n};\nCHECK_OFFSET(Rela, 0x00, offset);\nCHECK_OFFSET(Rela, 0x04, info);\nCHECK_OFFSET(Rela, 0x08, addend);\nCHECK_SIZE(Rela, 0x0C);\n\nstruct Imports\n{\n   be2_val<uint32_t> count;\n   be2_val<uint32_t> signature;\n};\nCHECK_OFFSET(Imports, 0x00, count);\nCHECK_OFFSET(Imports, 0x04, signature);\nCHECK_SIZE(Imports, 0x08);\n\nstruct Exports\n{\n   be2_val<uint32_t> count;\n   be2_val<uint32_t> signature;\n};\nCHECK_OFFSET(Exports, 0x00, count);\nCHECK_OFFSET(Exports, 0x04, signature);\nCHECK_SIZE(Exports, 0x08);\n\nstruct Export\n{\n   be2_val<uint32_t> value;\n   be2_val<uint32_t> name;\n};\nCHECK_OFFSET(Export, 0x00, value);\nCHECK_OFFSET(Export, 0x04, name);\nCHECK_SIZE(Export, 0x08);\n\nstruct DeflatedHeader\n{\n   be2_val<uint32_t> inflatedSize;\n};\nCHECK_OFFSET(DeflatedHeader, 0x00, inflatedSize);\nCHECK_SIZE(DeflatedHeader, 0x04);\n\nstruct RplCrc\n{\n   be2_val<uint32_t> crc;\n};\nCHECK_OFFSET(RplCrc, 0x00, crc);\nCHECK_SIZE(RplCrc, 0x04);\n\nstruct RPLFileInfo_v3_0\n{\n   static constexpr auto Version = 0xCAFE0300u;\n   be2_val<uint32_t> version;\n   be2_val<uint32_t> textSize;\n   be2_val<uint32_t> textAlign;\n   be2_val<uint32_t> dataSize;\n   be2_val<uint32_t> dataAlign;\n   be2_val<uint32_t> loadSize;\n   be2_val<uint32_t> loadAlign;\n   be2_val<uint32_t> tempSize;\n   be2_val<uint32_t> trampAdjust;\n   be2_val<uint32_t> sdaBase;\n   be2_val<uint32_t> sda2Base;\n   be2_val<uint32_t> stackSize;\n   be2_val<uint32_t> filename;\n   UNKNOWN(0x0C);\n};\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x00, version);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x04, textSize);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x08, textAlign);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x0C, dataSize);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x10, dataAlign);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x14, loadSize);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x18, loadAlign);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x1C, tempSize);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x20, trampAdjust);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x24, sdaBase);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x28, sda2Base);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x2C, stackSize);\nCHECK_OFFSET(RPLFileInfo_v3_0, 0x30, filename);\nCHECK_SIZE(RPLFileInfo_v3_0, 0x40);\n\nstruct RPLFileInfo_v4_1\n{\n   static constexpr auto Version = 0xCAFE0401u;\n   be2_val<uint32_t> version;\n   be2_val<uint32_t> textSize;\n   be2_val<uint32_t> textAlign;\n   be2_val<uint32_t> dataSize;\n   be2_val<uint32_t> dataAlign;\n   be2_val<uint32_t> loadSize;\n   be2_val<uint32_t> loadAlign;\n   be2_val<uint32_t> tempSize;\n   be2_val<uint32_t> trampAdjust;\n   be2_val<uint32_t> sdaBase;\n   be2_val<uint32_t> sda2Base;\n   be2_val<uint32_t> stackSize;\n   be2_val<uint32_t> filename;\n   be2_val<uint32_t> flags;\n   be2_val<uint32_t> heapSize;\n   be2_val<uint32_t> tagOffset;\n};\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x00, version);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x04, textSize);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x08, textAlign);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x0C, dataSize);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x10, dataAlign);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x14, loadSize);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x18, loadAlign);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x1C, tempSize);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x20, trampAdjust);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x24, sdaBase);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x28, sda2Base);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x2C, stackSize);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x30, filename);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x34, flags);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x38, heapSize);\nCHECK_OFFSET(RPLFileInfo_v4_1, 0x3C, tagOffset);\nCHECK_SIZE(RPLFileInfo_v4_1, 0x40);\n\nstruct RPLFileInfo_v4_2\n{\n   static constexpr auto Version = 0xCAFE0402u;\n   be2_val<uint32_t> version;\n   be2_val<uint32_t> textSize;\n   be2_val<uint32_t> textAlign;\n   be2_val<uint32_t> dataSize;\n   be2_val<uint32_t> dataAlign;\n   be2_val<uint32_t> loadSize;\n   be2_val<uint32_t> loadAlign;\n   be2_val<uint32_t> tempSize;\n   be2_val<uint32_t> trampAdjust;\n   be2_val<uint32_t> sdaBase;\n   be2_val<uint32_t> sda2Base;\n   be2_val<uint32_t> stackSize;\n   be2_val<uint32_t> filename;\n   be2_val<uint32_t> flags;\n   be2_val<uint32_t> heapSize;\n   be2_val<uint32_t> tagOffset;\n   be2_val<uint32_t> minVersion;\n   be2_val<int32_t> compressionLevel;\n   be2_val<uint32_t> trampAddition;\n   be2_val<uint32_t> fileInfoPad;\n   be2_val<uint32_t> cafeSdkVersion;\n   be2_val<uint32_t> cafeSdkRevision;\n   be2_val<int16_t> tlsModuleIndex;\n   be2_val<uint16_t> tlsAlignShift;\n   be2_val<uint32_t> runtimeFileInfoSize;\n};\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x00, version);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x04, textSize);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x08, textAlign);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x0C, dataSize);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x10, dataAlign);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x14, loadSize);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x18, loadAlign);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x1C, tempSize);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x20, trampAdjust);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x24, sdaBase);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x28, sda2Base);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x2C, stackSize);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x30, filename);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x34, flags);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x38, heapSize);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x3C, tagOffset);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x40, minVersion);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x44, compressionLevel);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x48, trampAddition);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x4C, fileInfoPad);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x50, cafeSdkVersion);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x54, cafeSdkRevision);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x58, tlsModuleIndex);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x5A, tlsAlignShift);\nCHECK_OFFSET(RPLFileInfo_v4_2, 0x5C, runtimeFileInfoSize);\nCHECK_SIZE(RPLFileInfo_v4_2, 0x60);\n\n} // namespace cafe::loader::rpl\n\n#pragma pack(pop)\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_setup.cpp",
    "content": "#include \"cafe_loader_bounce.h\"\n#include \"cafe_loader_entry.h\"\n#include \"cafe_loader_error.h\"\n#include \"cafe_loader_globals.h\"\n#include \"cafe_loader_heap.h\"\n#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_purge.h\"\n#include \"cafe_loader_setup.h\"\n#include \"cafe_loader_query.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_minfileinfo.h\"\n#include \"cafe/cafe_stackobject.h\"\n\n#include <array>\n#include <common/align.h>\n#include <libcpu/cpu_formatters.h>\n#include <zlib.h>\n\nnamespace cafe::loader::internal\n{\n\nstruct SegmentBounds\n{\n   uint32_t min = 0xffffffffu;\n   uint32_t max = 0;\n   uint32_t allocMax = 0;\n   const char *name = nullptr;\n};\n\nstruct RplSegmentBounds\n{\n   SegmentBounds data;\n   SegmentBounds load;\n   SegmentBounds text;\n   SegmentBounds temp;\n\n   SegmentBounds &operator[](size_t idx)\n   {\n      if (idx == 0) {\n         return data;\n      } else if (idx == 1) {\n         return load;\n      } else if (idx == 2) {\n         return text;\n      } else {\n         return temp;\n      }\n   }\n};\n\nstatic void\nLiClearUserBss(bool userHasControl,\n               kernel::UniqueProcessId upid,\n               virt_ptr<void> base,\n               uint32_t size)\n{\n   if (!userHasControl) {\n      // Check upid\n      if (upid == kernel::UniqueProcessId::HomeMenu ||\n          upid == kernel::UniqueProcessId::OverlayMenu ||\n          upid == kernel::UniqueProcessId::ErrorDisplay ||\n          upid == kernel::UniqueProcessId::Game) {\n         return;\n      }\n   }\n\n   std::memset(base.get(), 0, size);\n}\n\nstatic int32_t\nGetNextBounce(virt_ptr<LOADED_RPL> rpl)\n{\n   uint32_t chunkBytesRead = 0;\n   auto error = LiWaitOneChunk(&chunkBytesRead,\n                               virt_cast<char *>(rpl->pathBuffer).get(),\n                               rpl->fileType);\n   if (error != 0) {\n      return error;\n   }\n\n   rpl->fileOffset = rpl->upcomingFileOffset;\n   rpl->upcomingFileOffset += chunkBytesRead;\n   rpl->totalBytesRead += chunkBytesRead;\n   rpl->lastChunkBuffer = rpl->chunkBuffer;\n\n   auto readBufferNumber = rpl->upcomingBufferNumber;\n   if (readBufferNumber == 1) {\n      rpl->upcomingBufferNumber = 2u;\n\n      rpl->virtualFileBaseOffset += chunkBytesRead;\n   } else {\n      rpl->upcomingBufferNumber = 1u;\n\n      rpl->virtualFileBase = virt_cast<void *>(virt_cast<virt_addr>(rpl->virtualFileBase) - rpl->virtualFileBaseOffset);\n      rpl->virtualFileBaseOffset = chunkBytesRead;\n   }\n\n   if (chunkBytesRead == 0x400000) {\n      return LiRefillUpcomingBounceBuffer(rpl, readBufferNumber);\n   }\n\n   LiInitBuffer(false);\n   return 0;\n}\n\nstatic int32_t\nsLiPrepareBounceBufferForReading(virt_ptr<LOADED_RPL> rpl,\n                                 uint32_t sectionIndex,\n                                 std::string_view name,\n                                 uint32_t sectionOffset,\n                                 uint32_t *outBytesRead,\n                                 uint32_t readSize,\n                                 virt_ptr<void> *outBuffer)\n{\n   LiCheckAndHandleInterrupts();\n\n   if (rpl->upcomingFileOffset <= rpl->fileOffset) {\n      if (sectionIndex == 0) {\n         Loader_ReportError(\"*** {} Segment {}: bounce buffer has no size or is corrupted.\",\n                            rpl->moduleNameBuffer, name);\n      } else {\n         Loader_ReportError(\"*** {} Segment {}'s segment {}: bounce buffer has no size or is corrupted.\",\n                            rpl->moduleNameBuffer, name, sectionIndex);\n      }\n\n      LiSetFatalError(0x18729B, rpl->fileType, 1, \"sLiPrepareBounceBufferForReading\", 0xAD);\n      return -470074;\n   }\n\n   while (sectionOffset >= rpl->upcomingFileOffset) {\n      auto error = GetNextBounce(rpl);\n      if (error != 0) {\n         break;\n      }\n   }\n\n   if (sectionOffset < rpl->fileOffset) {\n      if (sectionIndex <= 0) {\n         Loader_ReportError(\n            \"*** {} Segment {}'s segment {} offset is before the current read position.\",\n            rpl->moduleNameBuffer, name, sectionIndex);\n      } else {\n         Loader_ReportError(\"*** {} Segment {}'s base is before the current read position.\",\n                            rpl->moduleNameBuffer, name);\n      }\n\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sLiPrepareBounceBufferForReading\", 193);\n      return -470075;\n   }\n\n   if (sectionOffset == rpl->upcomingFileOffset) {\n      if (sectionIndex <= 0) {\n         Loader_ReportError(\n            \"*** {} Segment {}'s segment {}: file has nothing left to read or bounce buffer is corrupted.\",\n            rpl->moduleNameBuffer, name, sectionIndex);\n      } else {\n         Loader_ReportError(\n            \"*** {} Segment {}: file has nothing left to read or bounce buffer is corrupted.\",\n            rpl->moduleNameBuffer, name);\n      }\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sLiPrepareBounceBufferForReading\", 210);\n      return -470076;\n   }\n\n   auto bytesRead = rpl->upcomingFileOffset - sectionOffset;\n   if (readSize < bytesRead) {\n      bytesRead = readSize;\n   }\n\n   *outBytesRead = bytesRead;\n   *outBuffer = virt_cast<void *>(virt_cast<virt_addr>(rpl->virtualFileBase) + sectionOffset);\n   return 0;\n}\n\nint32_t\nsLiRefillBounceBufferForReading(virt_ptr<LOADED_RPL> rpl,\n                                uint32_t *outBytesRead,\n                                uint32_t size,\n                                virt_ptr<void> *outBuffer)\n{\n   LiCheckAndHandleInterrupts();\n   *outBytesRead = 0;\n   auto result = GetNextBounce(rpl);\n   if (!result) {\n      auto bytesRead = rpl->upcomingFileOffset - rpl->fileOffset;\n      if (size < bytesRead) {\n         bytesRead = size;\n      }\n\n      *outBytesRead = bytesRead;\n      *outBuffer = rpl->lastChunkBuffer;\n   }\n\n   return result;\n}\n\nint32_t\nZLIB_UncompressFromStream(virt_ptr<LOADED_RPL> rpl,\n                          uint32_t sectionIndex,\n                          std::string_view boundsName,\n                          uint32_t fileOffset,\n                          uint32_t deflatedSize,\n                          virt_ptr<void> inflatedBuffer,\n                          uint32_t *inflatedSize)\n{\n   auto inflatedBytesMax = *inflatedSize;\n   auto deflatedBytesRemaining = deflatedSize;\n   auto bounceBuffer = virt_ptr<void> { nullptr };\n   auto bounceBufferSize = uint32_t { 0 };\n\n   LiCheckAndHandleInterrupts();\n\n   auto error =\n      sLiPrepareBounceBufferForReading(rpl, sectionIndex, boundsName,\n                                       fileOffset, &bounceBufferSize,\n                                       deflatedSize, &bounceBuffer);\n   if (error) {\n      return error;\n   }\n\n   auto stream = z_stream { };\n   std::memset(&stream, 0, sizeof(stream));\n\n   auto zlibError = inflateInit(&stream);\n   if (zlibError != Z_OK) {\n      switch (zlibError) {\n      case Z_STREAM_ERROR:\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"ZLIB_UncompressFromStream\", 332);\n         return Error::ZlibStreamError;\n      case Z_MEM_ERROR:\n         LiSetFatalError(0x187298u, rpl->fileType, 0, \"ZLIB_UncompressFromStream\", 319);\n         return Error::ZlibMemError;\n      case Z_VERSION_ERROR:\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"ZLIB_UncompressFromStream\", 332);\n         return Error::ZlibVersionError;\n      default:\n         Loader_ReportError(\"***Unknown ZLIB error {} (0x{}).\", zlibError, zlibError);\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"ZLIB_UncompressFromStream\", 332);\n         return Error::ZlibUnknownError;\n      }\n   }\n\n   constexpr auto InflateChunkSize = 0x3000u;\n   rpl->lastSectionCrc = 0u;\n\n   stream.next_out = reinterpret_cast<Bytef *>(inflatedBuffer.get());\n\n   while (true) {\n      // TODO: Loader_UpdateHeartBeat();\n      LiCheckAndHandleInterrupts();\n      stream.avail_in = bounceBufferSize;\n      stream.next_in = reinterpret_cast<Bytef *>(bounceBuffer.get());\n\n      while (stream.avail_in) {\n         LiCheckAndHandleInterrupts();\n         stream.avail_out = std::min<uInt>(InflateChunkSize, inflatedBytesMax - stream.total_out);\n\n         zlibError = inflate(&stream, 0);\n         if (zlibError != Z_OK && zlibError != Z_STREAM_END) {\n            switch (zlibError) {\n            case Z_STREAM_ERROR:\n               LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"ZLIB_UncompressFromStream\", 405);\n               return -470086;\n            case Z_MEM_ERROR:\n               LiSetFatalError(0x187298u, rpl->fileType, 0, \"ZLIB_UncompressFromStream\", 415);\n               error = -470084;\n               break;\n            case Z_DATA_ERROR:\n               LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"ZLIB_UncompressFromStream\", 419);\n               error = -470087;\n               break;\n            default:\n               Loader_ReportError(\"***Unknown ZLIB error {} (0x{}).\", zlibError, zlibError);\n               LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"ZLIB_UncompressFromStream\", 424);\n               error = -470100;\n            }\n\n            inflateEnd(&stream);\n            return error;\n         }\n\n         decaf_check(stream.total_out <= inflatedBytesMax);\n      }\n\n      deflatedBytesRemaining -= bounceBufferSize;\n      if (!deflatedBytesRemaining) {\n         break;\n      }\n\n      error = sLiRefillBounceBufferForReading(rpl, &bounceBufferSize, deflatedBytesRemaining, &bounceBuffer);\n      if (error) {\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"ZLIB_UncompressFromStream\", 520);\n         inflateEnd(&stream);\n         *inflatedSize = stream.total_out;\n         return -470087;\n      }\n   }\n\n   inflateEnd(&stream);\n   *inflatedSize = stream.total_out;\n   return 0;\n}\n\nstatic int32_t\nLiSetupOneAllocSection(kernel::UniqueProcessId upid,\n                       virt_ptr<LOADED_RPL> rpl,\n                       int32_t sectionIndex,\n                       virt_ptr<rpl::SectionHeader> sectionHeader,\n                       int32_t unk_a5,\n                       SegmentBounds *bounds,\n                       virt_ptr<void> base,\n                       uint32_t baseAlign,\n                       uint32_t unk_a9)\n{\n   auto globals = getGlobalStorage();\n   LiCheckAndHandleInterrupts();\n\n   auto sectionAddress = virt_cast<virt_addr>(base) + (sectionHeader->addr - bounds->min);\n   rpl->sectionAddressBuffer[sectionIndex] = sectionAddress;\n\n   if (!align_check(sectionAddress, sectionHeader->addralign)) {\n      Loader_ReportError(\"***{} section {} alignment failure.\", bounds->name, sectionIndex);\n      Loader_ReportError(\"Ptr              = {}\", sectionAddress);\n      Loader_ReportError(\"{} base        = {}\", bounds->name, base);\n      Loader_ReportError(\"{} base align  = {}\", bounds->name, baseAlign);\n      Loader_ReportError(\"SecHdr->addr     = 0x{:08X}\", sectionHeader->addr);\n      Loader_ReportError(\"bound[{}].base = 0x{:08X}\", bounds->name, bounds->min);\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sLiSetupOneAllocSection\", 1510);\n      return -470043;\n   }\n\n   if (!unk_a9 && sectionHeader->type == rpl::SHT_NOBITS && unk_a5) {\n      auto userHasControl =\n         (upid == kernel::UniqueProcessId::Invalid) ? true : !!globals->userHasControl;\n      LiClearUserBss(userHasControl, upid, virt_cast<void *>(sectionAddress), sectionHeader->size);\n   } else {\n      auto bytesAvailable = uint32_t { 0 };\n      auto sectionData = virt_ptr<void> { nullptr };\n      auto error = sLiPrepareBounceBufferForReading(rpl,\n                                                    sectionIndex,\n                                                    bounds->name,\n                                                    sectionHeader->offset,\n                                                    &bytesAvailable,\n                                                    sectionHeader->size,\n                                                    &sectionData);\n      if (error) {\n         return error;\n      }\n\n      if (!unk_a9 && (sectionHeader->flags & rpl::SHF_DEFLATED)) {\n         auto inflatedExpectedSizeBuffer = std::array<uint8_t, sizeof(uint32_t)> { };\n         auto readBytes = uint32_t { 0 };\n\n         while (readBytes < inflatedExpectedSizeBuffer.size()) {\n            std::memcpy(\n               inflatedExpectedSizeBuffer.data() + readBytes,\n               sectionData.get(),\n               std::min<size_t>(bytesAvailable, inflatedExpectedSizeBuffer.size() - readBytes));\n\n            readBytes += bytesAvailable;\n            if (readBytes >= inflatedExpectedSizeBuffer.size()) {\n               break;\n            }\n\n            error =\n               sLiRefillBounceBufferForReading(\n                  rpl,\n                  &bytesAvailable,\n                  static_cast<uint32_t>(inflatedExpectedSizeBuffer.size() - readBytes),\n                  &sectionData);\n            if (error) {\n               return error;\n            }\n         }\n\n         auto inflatedExpectedSize =\n            *reinterpret_cast<be2_val<uint32_t> *>(\n               inflatedExpectedSizeBuffer.data());\n         if (inflatedExpectedSize) {\n            auto inflatedBytes = static_cast<uint32_t>(inflatedExpectedSize);\n            error = ZLIB_UncompressFromStream(rpl,\n                                              sectionIndex,\n                                              bounds->name,\n                                              sectionHeader->offset + 4,\n                                              sectionHeader->size - 4,\n                                              virt_cast<void *>(sectionAddress),\n                                              &inflatedBytes);\n            if (error) {\n               Loader_ReportError(\n                  \"***{} {} {} Decompression ({}->{}) failure.\",\n                  rpl->moduleNameBuffer,\n                  bounds->name,\n                  sectionIndex,\n                  sectionHeader->size - 4,\n                  inflatedExpectedSize);\n               return error;\n            }\n\n            if (inflatedBytes != inflatedExpectedSize) {\n               Loader_ReportError(\n                  \"***{} {} {} Decompression ({}->{}) failure. Anticipated uncompressed size would be {}; got {}\",\n                  rpl->moduleNameBuffer,\n                  bounds->name,\n                  sectionIndex,\n                  sectionHeader->size - 4,\n                  inflatedExpectedSize,\n                  inflatedExpectedSize,\n                  inflatedBytes);\n               LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sLiSetupOneAllocSection\", 1604);\n               return -470090;\n            }\n\n            sectionHeader->size = inflatedBytes;\n         }\n      } else {\n         auto bytesRead = 0u;\n         while (true) {\n            // TODO: Loader_UpdateHeartBeat();\n            LiCheckAndHandleInterrupts();\n            std::memcpy(virt_cast<void *>(sectionAddress + bytesRead).get(),\n                        sectionData.get(),\n                        bytesAvailable);\n            bytesRead += bytesAvailable;\n\n            if (bytesRead >= sectionHeader->size) {\n               break;\n            }\n\n            error = sLiRefillBounceBufferForReading(rpl,\n                                                    &bytesAvailable,\n                                                    sectionHeader->size - bytesRead,\n                                                    &sectionData);\n            if (error) {\n               return error;\n            }\n         }\n      }\n   }\n\n   bounds->allocMax = std::max<uint32_t>(bounds->allocMax,\n                                         sectionHeader->addr + sectionHeader->size);\n   if (bounds->allocMax > bounds->max) {\n      Loader_ReportError(\n         \"*** {} section {} segment {} makerpl's segment size was wrong: real time calculated size =0x{:08X} makerpl's size=0x{:08X}.\",\n         rpl->moduleNameBuffer,\n         sectionIndex,\n         bounds->name,\n         bounds->allocMax,\n         bounds->max);\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sLiSetupOneAllocSection\", 1676);\n      return -470091;\n   }\n\n   return 0;\n}\n\nint32_t\nLiSetupOneRPL(kernel::UniqueProcessId upid,\n              virt_ptr<LOADED_RPL> rpl,\n              virt_ptr<TinyHeap> codeHeapTracking,\n              virt_ptr<TinyHeap> dataHeapTracking)\n{\n   int32_t result = 0;\n\n   // Calculate segment bounds\n   RplSegmentBounds bounds;\n   bounds.data.name = \"DATA\";\n   bounds.load.name = \"LOADERINFO\";\n   bounds.text.name = \"TEXT\";\n   bounds.temp.name = \"TEMP\";\n\n   auto shBase = virt_cast<virt_addr>(rpl->sectionHeaderBuffer);\n   for (auto i = 1u; i < rpl->elfHeader.shnum; ++i) {\n      auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize);\n      if (sectionHeader->size == 0 ||\n          sectionHeader->type == rpl::SHT_RPL_FILEINFO ||\n          sectionHeader->type == rpl::SHT_RPL_CRCS ||\n          sectionHeader->type == rpl::SHT_RPL_IMPORTS) {\n         continue;\n      }\n\n      if (sectionHeader->flags & rpl::SHF_ALLOC) {\n         if ((sectionHeader->flags & rpl::SHF_EXECINSTR) &&\n             sectionHeader->type != rpl::SHT_RPL_EXPORTS) {\n            bounds.text.min = std::min<uint32_t>(bounds.text.min, sectionHeader->addr);\n         } else {\n            if (sectionHeader->flags & rpl::SHF_WRITE) {\n               bounds.data.min = std::min<uint32_t>(bounds.data.min, sectionHeader->addr);\n            } else {\n               bounds.load.min = std::min<uint32_t>(bounds.load.min, sectionHeader->addr);\n            }\n         }\n      } else {\n         bounds.temp.min = std::min<uint32_t>(bounds.temp.min, sectionHeader->offset);\n         bounds.temp.max = std::max<uint32_t>(bounds.temp.max, sectionHeader->offset + sectionHeader->size);\n      }\n   }\n\n   if (bounds.data.min == -1) {\n      bounds.data.min = 0;\n   }\n\n   if (bounds.load.min == -1) {\n      bounds.load.min = 0;\n   }\n\n   if (bounds.text.min == -1) {\n      bounds.text.min = 0;\n   }\n\n   if (bounds.temp.min == -1) {\n      bounds.temp.min = 0;\n   }\n\n   auto fileInfo = rpl->fileInfoBuffer;\n   bounds.text.max = (bounds.text.min + fileInfo->textSize) - fileInfo->trampAdjust;\n   bounds.data.max = bounds.data.min + fileInfo->dataSize;\n   bounds.load.max = (bounds.load.min + fileInfo->loadSize) - fileInfo->fileInfoPad;\n\n   auto textSize = static_cast<uint32_t>(bounds.text.max - bounds.text.min);\n   auto dataSize = static_cast<uint32_t>(bounds.data.max - bounds.data.min);\n   auto loadSize = static_cast<uint32_t>(bounds.load.max - bounds.load.min);\n   auto tempSize = static_cast<uint32_t>(bounds.temp.max - bounds.temp.min);\n\n   if (fileInfo->trampAdjust >= textSize ||\n       fileInfo->textSize - fileInfo->trampAdjust < textSize ||\n       fileInfo->dataSize < dataSize ||\n       fileInfo->loadSize - fileInfo->fileInfoPad < loadSize ||\n       fileInfo->tempSize < tempSize) {\n      Loader_ReportError(\"***Bounds check failure.\");\n      Loader_ReportError(\"b%d: %08X %08x\", 0, bounds.data.min, bounds.data.max);\n      Loader_ReportError(\"b%d: %08X %08x\", 1, bounds.load.min, bounds.load.max);\n      Loader_ReportError(\"b%d: %08X %08x\", 2, bounds.text.min, bounds.text.max);\n      Loader_ReportError(\"b%d: %08X %08x\", 3, bounds.temp.min, bounds.temp.max);\n      Loader_ReportError(\"TrampAdj = %08X\", fileInfo->trampAdjust);\n      Loader_ReportError(\"Text = %08X\", fileInfo->textSize);\n      Loader_ReportError(\"Data = %08X\", fileInfo->dataSize);\n      Loader_ReportError(\"Read = %08X\", fileInfo->loadSize - fileInfo->fileInfoPad);\n      Loader_ReportError(\"Temp = %08X\", fileInfo->tempSize);\n      LiSetFatalError(0x18729B, rpl->fileType, 1, \"LiSetupOneRPL\", 0x715);\n      result = -470042;\n      goto error;\n   }\n\n   if (rpl->dataBuffer) {\n      for (auto i = 1u; i < rpl->elfHeader.shnum; ++i) {\n         auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize);\n         LiCheckAndHandleInterrupts();\n\n         if (sectionHeader->size &&\n             !rpl->sectionAddressBuffer[i] &&\n             (sectionHeader->flags & rpl::SHF_ALLOC) &&\n             (sectionHeader->flags & rpl::SHF_WRITE)) {\n            result = LiSetupOneAllocSection(upid,\n                                            rpl,\n                                            i,\n                                            sectionHeader,\n                                            1,\n                                            &bounds.data,\n                                            rpl->dataBuffer,\n                                            fileInfo->dataAlign,\n                                            0);\n            if (result) {\n               goto error;\n            }\n         }\n      }\n   }\n\n   if (rpl->loadBuffer) {\n      for (auto i = 1u; i < rpl->elfHeader.shnum; ++i) {\n         auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize);\n         LiCheckAndHandleInterrupts();\n\n         if (sectionHeader->size &&\n             !rpl->sectionAddressBuffer[i] &&\n             (sectionHeader->flags & rpl::SHF_ALLOC)) {\n            if (sectionHeader->type == rpl::SHT_RPL_EXPORTS ||\n                sectionHeader->type == rpl::SHT_RPL_IMPORTS ||\n                !(sectionHeader->flags & (rpl::SHF_EXECINSTR | rpl::SHF_WRITE))) {\n               result = LiSetupOneAllocSection(upid,\n                                               rpl,\n                                               i,\n                                               sectionHeader,\n                                               0,\n                                               &bounds.load,\n                                               rpl->loadBuffer,\n                                               fileInfo->loadAlign,\n                                               (sectionHeader->type == rpl::SHT_RPL_IMPORTS) ? 1 : 0);\n               if (result) {\n                  goto error;\n               }\n\n               if (sectionHeader->type == rpl::SHT_RPL_EXPORTS) {\n                  if (sectionHeader->flags & rpl::SHF_EXECINSTR) {\n                     rpl->numFuncExports = *virt_cast<uint32_t *>(rpl->sectionAddressBuffer[i]);\n                     rpl->funcExports = virt_cast<void *>(rpl->sectionAddressBuffer[i] + 8);\n                  } else {\n                     rpl->numDataExports = *virt_cast<uint32_t *>(rpl->sectionAddressBuffer[i]);\n                     rpl->dataExports = virt_cast<void *>(rpl->sectionAddressBuffer[i] + 8);\n                  }\n               }\n            }\n         }\n      }\n   }\n\n   if (fileInfo->textSize) {\n      if (!rpl->textBuffer) {\n         Loader_ReportError(\"Missing TEXT allocation.\");\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LiSetupOneRPL\", 1918);\n         result = -470057;\n         goto error;\n      }\n\n      for (auto i = 1u; i < rpl->elfHeader.shnum; ++i) {\n         auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize);\n         LiCheckAndHandleInterrupts();\n\n         if (sectionHeader->size &&\n             !rpl->sectionAddressBuffer[i] &&\n             (sectionHeader->flags & rpl::SHF_ALLOC) &&\n             (sectionHeader->flags & rpl::SHF_EXECINSTR) &&\n             sectionHeader->type != rpl::SHT_RPL_EXPORTS) {\n            result = LiSetupOneAllocSection(upid,\n                                            rpl,\n                                            i,\n                                            sectionHeader,\n                                            0,\n                                            &bounds.text,\n                                            virt_cast<void *>(virt_cast<virt_addr>(rpl->textBuffer) + fileInfo->trampAdjust),\n                                            fileInfo->textAlign,\n                                            0);\n            if (result) {\n               goto error;\n            }\n         }\n      }\n   }\n\n   if (bounds.temp.min != bounds.temp.max) {\n      auto compressedRelocationsBuffer = virt_ptr<void> { nullptr };\n      auto compressedRelocationsBufferSize = uint32_t { 0 };\n      auto memoryAvailable = uint32_t { 0 };\n      auto data = virt_ptr<void> { nullptr };\n      auto readBytes = 0u;\n      tempSize = bounds.temp.max - bounds.temp.min;\n      dataSize = uint32_t { 0 };\n\n      result = LiCacheLineCorrectAllocEx(codeHeapTracking,\n                                         tempSize,\n                                         -32,\n                                         &compressedRelocationsBuffer,\n                                         1,\n                                         &compressedRelocationsBufferSize,\n                                         &memoryAvailable,\n                                         rpl->fileType);\n      if (result) {\n         Loader_ReportError(\n            \"*** allocation failed for {} size = {}, align = {} from {} heap;  (needed {}, available {}).\",\n            \"compressed relocations\",\n            tempSize,\n            -32,\n            \"RPL Code\",\n            compressedRelocationsBufferSize,\n            memoryAvailable);\n         goto error;\n      }\n\n      rpl->compressedRelocationsBuffer = compressedRelocationsBuffer;\n      rpl->compressedRelocationsBufferSize = compressedRelocationsBufferSize;\n\n      result = sLiPrepareBounceBufferForReading(rpl, 0, bounds.temp.name, bounds.temp.min, &dataSize, tempSize, &data);\n      if (result) {\n         goto error;\n      }\n\n      while (tempSize > 0) {\n         // TODO: Loader_UpdateHeartBeat\n         LiCheckAndHandleInterrupts();\n         std::memcpy(virt_cast<void *>(virt_cast<virt_addr>(compressedRelocationsBuffer) + readBytes).get(),\n                     data.get(),\n                     dataSize);\n         readBytes += dataSize;\n         tempSize -= dataSize;\n\n         if (!tempSize) {\n            break;\n         }\n\n         result = sLiRefillBounceBufferForReading(rpl, &dataSize, tempSize, &data);\n         if (result) {\n            goto error;\n         }\n      }\n\n      for (auto i = 1u; i < rpl->elfHeader.shnum; ++i) {\n         auto sectionHeader = virt_cast<rpl::SectionHeader *>(shBase + i * rpl->elfHeader.shentsize);\n         LiCheckAndHandleInterrupts();\n\n         if (sectionHeader->size && !rpl->sectionAddressBuffer[i]) {\n            rpl->sectionAddressBuffer[i] = virt_cast<virt_addr>(compressedRelocationsBuffer) + (sectionHeader->offset - bounds.temp.min);\n            bounds.temp.allocMax = std::max<uint32_t>(bounds.temp.allocMax,\n                                                      sectionHeader->addr + sectionHeader->size);\n\n            if (bounds.temp.allocMax > bounds.temp.max) {\n               Loader_ReportError(\n                  \"***Section {} segment {} makerpl's section size was wrong: mRealTimeLimit=0x%08X, mLimit=0x%08X. Error is Loader's fault.\",\n                  i,\n                  bounds.temp.name,\n                  bounds.temp.allocMax,\n                  bounds.temp.max);\n               LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LiSetupOneRPL\", 2034);\n               result = -470091;\n               goto error;\n            }\n         }\n      }\n   }\n\n   for (auto i = 0u; i < 4; ++i) {\n      if (bounds[i].allocMax > bounds[i].max) {\n         Loader_ReportError(\n            \"***Segment %s makerpl's segment size was wrong: mRealTimeLimit=0x%08X, mLimit=0x%08X. Error is Loader's fault.\\n\",\n            bounds[i].name,\n            bounds[i].allocMax,\n            bounds[i].max);\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LiSetupOneRPL\", 2052);\n         result = -470091;\n         goto error;\n      }\n   }\n\n   rpl->postTrampBuffer = align_up(virt_cast<virt_addr>(rpl->textBuffer) + fileInfo->trampAdjust + (bounds.text.allocMax - bounds.text.min), 16);\n\n   rpl->textAddr = virt_cast<virt_addr>(rpl->textBuffer) + fileInfo->trampAdjust;\n   rpl->textOffset = rpl->textAddr - bounds.text.min;\n   rpl->textSize = static_cast<uint32_t>(bounds.text.max - bounds.text.min);\n\n   rpl->dataAddr = virt_cast<virt_addr>(rpl->dataBuffer);\n   rpl->dataOffset = rpl->dataAddr - bounds.data.min;\n   rpl->dataSize = static_cast<uint32_t>(bounds.data.max - bounds.data.min);\n\n   rpl->loadAddr = virt_cast<virt_addr>(rpl->loadBuffer);\n   rpl->loadOffset = rpl->loadAddr - bounds.load.min;\n   rpl->loadSize = static_cast<uint32_t>(bounds.load.max - bounds.load.min);\n\n   rpl->loadStateFlags |= LoadStateFlags::LoaderSetup;\n\n   result = LiCleanUpBufferAfterModuleLoaded();\n   if (result) {\n      goto error;\n   }\n\n   return 0;\n\nerror:\n   if (rpl->compressedRelocationsBuffer) {\n      LiCacheLineCorrectFreeEx(codeHeapTracking,\n                                 rpl->compressedRelocationsBuffer,\n                                 rpl->compressedRelocationsBufferSize);\n      rpl->compressedRelocationsBuffer = nullptr;\n   }\n\n   LiCloseBufferIfError();\n   Loader_ReportError(\"***LiSetupOneRPL({}) failed with err={}.\", rpl->moduleNameBuffer, result);\n   return result;\n}\n\nstatic bool\nsCheckDataRange(virt_addr address,\n                uint32_t maxDataSize)\n{\n\n   return address >= virt_addr { 0x10000000 } &&\n          address < (virt_addr { 0x10000000 } + maxDataSize);\n}\n\nstatic int32_t\nsValidateSetupParams(virt_addr address,\n                     uint32_t size,\n                     uint32_t align,\n                     uint32_t maxSize,\n                     int32_t error,\n                     const char *areaName)\n{\n   if (!sCheckDataRange(address, maxSize)) {\n      Loader_ReportError(\n         \"*** invalid {} area address. apArea={}, lo addr={}, hi addr={}\",\n         areaName,\n         address,\n         virt_addr { 0x10000000 },\n         virt_addr { maxSize + 0x10000000 });\n      return error;\n   }\n\n   if (!sCheckDataRange(address + size, maxSize)) {\n      Loader_ReportError(\n         \"*** invalid {} area buffer. apArea+aAreaBytes={}, lo addr={}, hi addr={}\",\n         areaName,\n         address + size,\n         virt_addr { 0x10000000 },\n         virt_addr { maxSize + 0x10000000 });\n      return error;\n   }\n\n   if (!align_check(address, align)) {\n      Loader_ReportError(\"*** invalid {} area buffer alignment.\", areaName);\n      return error;\n   }\n\n   if (size && !Loader_ValidateAddrRange(address, size)) {\n      Loader_ReportError(\"*** invalid {} area buffer range {}..{}.\",\n                         areaName, address, address + size);\n      return error;\n   }\n\n   return 0;\n}\n\nint32_t\nLOADER_Setup(kernel::UniqueProcessId upid,\n             LOADER_Handle handle,\n             BOOL isPurge,\n             virt_ptr<LOADER_MinFileInfo> minFileInfo)\n{\n   auto globals = getGlobalStorage();\n   if (globals->currentUpid != upid) {\n      Loader_ReportError(\"*** Loader address space not set for process {} but called for {}.\",\n                         static_cast<int>(globals->currentUpid.value()),\n                         static_cast<int>(upid));\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_Setup\", 2184);\n      LiCloseBufferIfError();\n      return Error::DifferentProcess;\n   }\n\n   if (minFileInfo) {\n      if (auto error = LiValidateMinFileInfo(minFileInfo, \"LOADER_Setup\")) {\n         LiCloseBufferIfError();\n         return error;\n      }\n   }\n\n   auto error = LiValidateAddress(handle, 0, 0, -470046,\n                                  virt_addr { 0x02000000 },\n                                  virt_addr { 0x10000000 },\n                                  \"kernel handle for module (out of valid range-read)\");\n   if (error) {\n      if (getProcFlags().disableSharedLibraries()) {\n         Loader_ReportError(\"*** bad kernel handle for module (out of valid range-read)\");\n         LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_Setup\", 2215);\n         LiCloseBufferIfError();\n         return error;\n      }\n\n      error = LiValidateAddress(handle, 0, 0, -470046,\n                                virt_addr { 0xEFE0B000 },\n                                virt_addr { 0xEFE80000 },\n                                \"kernel handle for module (out of valid range-read)\");\n      if (error) {\n         LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_Setup\", 2215);\n         LiCloseBufferIfError();\n         return error;\n      }\n   }\n\n   // Find module\n   auto prev = virt_ptr<LOADED_RPL> { nullptr };\n   auto rpl = virt_ptr<LOADED_RPL> { nullptr };\n   for (rpl = globals->firstLoadedRpl; rpl; rpl = rpl->nextLoadedRpl) {\n      if (rpl->moduleNameBuffer == handle) {\n         break;\n      }\n\n      prev = rpl;\n   }\n\n   if (!rpl) {\n      LiSetFatalError(0x18729Bu, 0, 1, \"LOADER_Setup\", 2240);\n      LiCloseBufferIfError();\n      return -470010;\n   }\n\n   // If this is a purge command, perform the purge\n   if (isPurge) {\n      if (rpl->loadStateFlags & LoaderStateFlag4) {\n         return 0;\n      }\n\n      // Remove from linked list\n      if (prev) {\n         prev->nextLoadedRpl = rpl->nextLoadedRpl;\n      }\n\n      if (!rpl->nextLoadedRpl) {\n         globals->lastLoadedRpl = prev;\n      }\n\n      LiPurgeOneUnlinkedModule(rpl);\n      return 0;\n   }\n\n   // Check if we have already setup\n   if (rpl->loadStateFlags & LoaderSetup) {\n      Loader_ReportError(\"*** module already set up.\");\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LOADER_Setup\", 2267);\n      LiCloseBufferIfError();\n      return -470047;\n   }\n\n   // Check input\n   if (minFileInfo->dataSize) {\n      error = sValidateSetupParams(virt_cast<virt_addr>(minFileInfo->dataBuffer),\n                                   minFileInfo->dataSize,\n                                   minFileInfo->dataAlign,\n                                   globals->maxDataSize,\n                                   -470021,\n                                   \"data\");\n      if (error) {\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LOADER_Setup\", 2283);\n         LiCloseBufferIfError();\n         return error;\n      }\n   }\n\n   if (minFileInfo->loadSize) {\n      error = sValidateSetupParams(virt_cast<virt_addr>(minFileInfo->loadBuffer),\n                                   minFileInfo->loadSize,\n                                   minFileInfo->loadAlign,\n                                   globals->maxDataSize,\n                                   -470048,\n                                   \"loader_info\");\n      if (error) {\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"LOADER_Setup\", 2300);\n         LiCloseBufferIfError();\n         return error;\n      }\n   }\n\n   // Perform setup\n   rpl->dataBuffer = minFileInfo->dataBuffer;\n   rpl->loadBuffer = minFileInfo->loadBuffer;\n   error = LiSetupOneRPL(upid,\n                         rpl,\n                         globals->processCodeHeap,\n                         globals->processCodeHeap);\n   if (error) {\n      rpl->dataBuffer = nullptr;\n      rpl->loadBuffer = nullptr;\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   // Do queries\n   error = LOADER_GetSecInfo(upid, handle,\n                             minFileInfo->outNumberOfSections,\n                             minFileInfo->outSectionInfo);\n   if (error) {\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   error = LOADER_GetFileInfo(upid, handle,\n                              minFileInfo->outSizeOfFileInfo,\n                              minFileInfo->outFileInfo,\n                              nullptr, nullptr);\n   if (error) {\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   error = LOADER_GetPathString(upid, handle,\n                                minFileInfo->outPathStringSize,\n                                minFileInfo->pathStringBuffer,\n                                minFileInfo->outFileInfo);\n   if (error) {\n      LiCloseBufferIfError();\n      return error;\n   }\n\n   return 0;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_setup.h",
    "content": "#pragma once\n#include \"cafe_loader_loaded_rpl.h\"\n#include \"cafe_loader_minfileinfo.h\"\n#include \"cafe/kernel/cafe_kernel_processid.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n\nnamespace cafe::loader::internal\n{\n\nint32_t\nLiSetupOneRPL(kernel::UniqueProcessId upid,\n              virt_ptr<LOADED_RPL> rpl,\n              virt_ptr<TinyHeap> codeHeapTracking,\n              virt_ptr<TinyHeap> dataHeapTracking);\n\nint32_t\nLOADER_Setup(kernel::UniqueProcessId upid,\n             LOADER_Handle handle,\n             BOOL isPurge,\n             virt_ptr<LOADER_MinFileInfo> minFileInfo);\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_shared.cpp",
    "content": "#include \"cafe_loader_bounce.h\"\n#include \"cafe_loader_entry.h\"\n#include \"cafe_loader_error.h\"\n#include \"cafe_loader_flush.h\"\n#include \"cafe_loader_globals.h\"\n#include \"cafe_loader_heap.h\"\n#include \"cafe_loader_init.h\"\n#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_basics.h\"\n#include \"cafe_loader_log.h\"\n#include \"cafe_loader_minfileinfo.h\"\n#include \"cafe_loader_prep.h\"\n#include \"cafe_loader_reloc.h\"\n#include \"cafe_loader_setup.h\"\n#include \"cafe_loader_shared.h\"\n#include \"cafe_loader_utils.h\"\n#include \"cafe/cafe_stackobject.h\"\n\n#include \"cafe/libraries/cafe_hle_library.h\"\n\n#include <libcpu/cpu_formatters.h>\n#include <zlib.h>\n\nnamespace cafe::loader::internal\n{\n\nconstexpr const char *\nSharedLibraryList[] {\n   \"tve.rpl\",\n   \"nsysccr.rpl\",\n   \"nsysnet.rpl\",\n   \"uvc.rpl\",\n   \"tcl.rpl\",\n   \"nn_pdm.rpl\",\n   \"dmae.rpl\",\n   \"dc.rpl\",\n   \"vpadbase.rpl\",\n   \"vpad.rpl\",\n   \"avm.rpl\",\n   \"gx2.rpl\",\n   \"snd_core.rpl\",\n};\n\nstruct LiCompressedSharedDataTracking\n{\n   be2_virt_ptr<void> data;\n   be2_virt_ptr<void> intialisationData;\n   be2_val<uint32_t> compressedSize;\n   be2_val<uint32_t> size;\n};\nCHECK_OFFSET(LiCompressedSharedDataTracking, 0x00, data);\nCHECK_OFFSET(LiCompressedSharedDataTracking, 0x04, intialisationData);\nCHECK_OFFSET(LiCompressedSharedDataTracking, 0x08, compressedSize);\nCHECK_OFFSET(LiCompressedSharedDataTracking, 0x0C, size);\nCHECK_SIZE(LiCompressedSharedDataTracking, 0x10);\n\nstruct LoaderShared\n{\n   be2_virt_ptr<LOADED_RPL> loadedModules;\n   be2_virt_ptr<void> dataBufferHead;\n   be2_val<uint32_t> numFreeTrackCompBlocks;\n   be2_virt_ptr<LiCompressedSharedDataTracking> freeTrackCompBlocks;\n   be2_val<uint32_t> numUsedTrackCompBlocks;\n   be2_virt_ptr<LiCompressedSharedDataTracking> usedTrackCompBlocks;\n};\nCHECK_OFFSET(LoaderShared, 0x00, loadedModules);\nCHECK_OFFSET(LoaderShared, 0x04, dataBufferHead);\nCHECK_OFFSET(LoaderShared, 0x08, numFreeTrackCompBlocks);\nCHECK_OFFSET(LoaderShared, 0x0C, freeTrackCompBlocks);\nCHECK_OFFSET(LoaderShared, 0x10, numUsedTrackCompBlocks);\nCHECK_OFFSET(LoaderShared, 0x14, usedTrackCompBlocks);\nCHECK_SIZE(LoaderShared, 0x18);\n\nstatic virt_ptr<LoaderShared> gpLoaderShared = nullptr;\nstatic virt_ptr<TinyHeap> gpSharedCodeHeapTracking = nullptr;\nstatic virt_ptr<TinyHeap> gpSharedReadHeapTracking = nullptr;\nstatic virt_ptr<TinyHeap> sgpTrackComp = nullptr;\nstatic virt_ptr<void> sHleUnimplementedStubMemory = nullptr;\nstatic uint32_t sHleUnimplementedStubMemorySize = 0;\n\nstatic int32_t\nsLoadOneShared(std::string_view filename)\n{\n   auto moduleName = StackObject<virt_ptr<char>> { };\n   auto moduleNameLen = StackObject<uint32_t> { };\n   auto fileNameBuffer = StackArray<char, 32> { };\n   auto chunkSize = uint32_t { 0 };\n   auto chunkBuffer = virt_ptr<void> { nullptr };\n   auto rpl = virt_ptr<LOADED_RPL> { nullptr };\n   auto rplBasicLoadArgs = LiBasicsLoadArgs { };\n   auto error = int32_t { 0 };\n\n   Loader_LogEntry(2, 0, 0, \"sLoadOneShared  Loading Shared RPL {}\", filename);\n   Loader_LogEntry(2, 0, 0, \"sLoadOneShared  LiSyncBnce start {}\", filename);\n   LiInitBuffer(false);\n\n   error = LiBounceOneChunk(filename,\n                            ios::mcp::MCPFileType::CafeOS,\n                            kernel::UniqueProcessId::Kernel,\n                            &chunkSize,\n                            0,\n                            1,\n                            &chunkBuffer);\n   if (error != 0) {\n      Loader_ReportError(\"***LiBounceOneChunk failed loading \\\"{}\\\" of type {} at offset 0x{:08X} err={}\",\n                         filename, 1, 0, error);\n      return error;\n   }\n\n   Loader_LogEntry(2, 0, 0, \"sLoadOneShared  LiSyncBnce end {}\", filename);\n\n   std::memcpy(fileNameBuffer.get(),\n               filename.data(),\n               filename.size());\n   fileNameBuffer[filename.size()] = 0;\n\n   *moduleName = fileNameBuffer;\n   *moduleNameLen = static_cast<uint32_t>(filename.size());\n   LiResolveModuleName(moduleName, moduleNameLen);\n\n   rplBasicLoadArgs.fileOffset = 0u;\n   rplBasicLoadArgs.pathNameLen = static_cast<uint32_t>(filename.size());\n   rplBasicLoadArgs.pathName = fileNameBuffer;\n   rplBasicLoadArgs.chunkBuffer = chunkBuffer;\n   rplBasicLoadArgs.chunkBufferSize = chunkSize;\n   rplBasicLoadArgs.readHeapTracking = gpSharedReadHeapTracking;\n   rplBasicLoadArgs.upid = kernel::UniqueProcessId::Kernel;\n   rplBasicLoadArgs.fileType = ios::mcp::MCPFileType::CafeOS;\n\n   error = LiLoadRPLBasics(*moduleName,\n                           *moduleNameLen,\n                           chunkBuffer,\n                           gpSharedCodeHeapTracking,\n                           gpSharedReadHeapTracking,\n                           true, // TODO: Change to false and keep module name in loader .data memory\n                           1,\n                           &rpl,\n                           &rplBasicLoadArgs,\n                           0);\n   if (error != 0) {\n      return error;\n   }\n\n   rpl->loadStateFlags |= LoaderStateFlags_Unk0x20000000 | LoaderStateFlag4;\n\n   auto fileInfo = rpl->fileInfoBuffer;\n   if (fileInfo->dataSize) {\n      auto dataBufferHeadAddr = virt_cast<virt_addr>(gpLoaderShared->dataBufferHead);\n\n      // Align data buffer start\n      dataBufferHeadAddr = align_up(dataBufferHeadAddr, fileInfo->dataAlign);\n      rpl->dataBuffer = virt_cast<void *>(dataBufferHeadAddr);\n\n      // Align data buffer end to 64 bytes\n      dataBufferHeadAddr = align_up(dataBufferHeadAddr + fileInfo->dataSize, 64);\n      gpLoaderShared->dataBufferHead = virt_cast<void *>(dataBufferHeadAddr);\n   }\n\n   if (fileInfo->loadSize != fileInfo->fileInfoPad) {\n      auto allocPtr = virt_ptr<void> { nullptr };\n      auto tinyHeapError = TinyHeap_Alloc(gpSharedReadHeapTracking,\n                                          fileInfo->loadSize - fileInfo->fileInfoPad,\n                                          -static_cast<int32_t>(fileInfo->loadAlign),\n                                          &allocPtr);\n      if (tinyHeapError != TinyHeapError::OK) {\n         Loader_ReportError(\"Could not allocate read-only space for shared library \\\"{}\\\"\",\n                            rpl->moduleNameBuffer);\n         return static_cast<int32_t>(tinyHeapError);\n      }\n\n      rpl->loadBuffer = allocPtr;\n   }\n\n   Loader_LogEntry(2, 0, 0, \"sLoadOneShared  LiSetupOneRPL start.\");\n   error = LiSetupOneRPL(kernel::UniqueProcessId::Invalid,\n                         rpl,\n                         gpSharedCodeHeapTracking,\n                         gpSharedReadHeapTracking);\n   if (error) {\n      Loader_ReportError(\"LiSetupOneRPL failed for shared library \\\"{}\\\".\", filename);\n      return error;\n   }\n\n   Loader_LogEntry(2, 0, 0, \"sLoadOneShared  LiSetupOneRPL end.\");\n\n   if (!rpl->elfHeader.shstrndx) {\n      Loader_ReportError(\n         \"*** Error: Could not get section string table index for \\\"{}\\\".\",\n         filename);\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sLoadOneShared\", 213);\n      return -470071;\n   }\n\n   if (!rpl->sectionAddressBuffer[rpl->elfHeader.shstrndx]) {\n      Loader_ReportError(\"*** Error: Could not get section string table for \\\"%s\\\".\",\n                         filename);\n      LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sLoadOneShared\", 204);\n      return -470072;\n   }\n\n   auto shStr = virt_cast<char *>(rpl->sectionAddressBuffer[rpl->elfHeader.shstrndx]);\n   auto importTracking = virt_ptr<LiImportTracking> { nullptr };\n   auto importTrackingSize = uint32_t { 0 };\n\n   // Setup import tracking\n   for (auto i = 1; i < rpl->elfHeader.shnum - 2; ++i) {\n      auto sectionHeader = virt_cast<rpl::SectionHeader *>(\n         virt_cast<virt_addr>(rpl->sectionHeaderBuffer) +\n         i * rpl->elfHeader.shentsize);\n      if (!sectionHeader->size || sectionHeader->type != rpl::SHT_RPL_IMPORTS) {\n         continue;\n      }\n\n      // Make sure we have memory for import tracking\n      if (!importTracking) {\n         auto largestFree = uint32_t { 0 };\n         error = LiCacheLineCorrectAllocEx(getGlobalStorage()->processCodeHeap,\n                                           sizeof(LiImportTracking) * (rpl->elfHeader.shnum - 2),\n                                           4,\n                                           reinterpret_cast<virt_ptr<void> *>(&importTracking),\n                                           1,\n                                           &importTrackingSize,\n                                           &largestFree,\n                                           rpl->fileType);\n         if (error) {\n            Loader_ReportError(\n               \"***Could not allocate space for shared import tracking in local heap;  (needed {}, available {}).\",\n               importTrackingSize, largestFree);\n            return error;\n         }\n      }\n\n      // Load imported rpl\n      auto name = shStr + sectionHeader->name + strlen(\".fimport_\");\n      auto nameLen = strlen(name.get());\n      auto importModule = virt_ptr<LOADED_RPL> { nullptr };\n\n      for (auto module = gpLoaderShared->loadedModules; module; module = module->nextLoadedRpl) {\n         if (module->moduleNameLen == nameLen &&\n             strncmp(name.get(),\n                     module->moduleNameBuffer.get(),\n                     nameLen) == 0) {\n            importModule = module;\n            break;\n         }\n      }\n\n      if (!importModule) {\n         Loader_ReportError(\n            \"*** \\\"{}\\\" imports from \\\"{}\\\" which is not loaded as a shared library.\",\n            filename, name);\n         LiSetFatalError(0x18729Bu, rpl->fileType, 1, \"sLoadOneShared\", 253);\n         LiCacheLineCorrectFreeEx(getGlobalStorage()->processCodeHeap, importTracking, importTrackingSize);\n         return -470010;\n      }\n\n      if (sectionHeader->flags & rpl::SHF_EXECINSTR) {\n         importTracking[i].numExports = importModule->numFuncExports;\n         importTracking[i].exports = virt_cast<rpl::Export *>(importModule->funcExports);\n      } else {\n         importTracking[i].numExports = importModule->numDataExports;\n         importTracking[i].exports = virt_cast<rpl::Export *>(importModule->dataExports);\n      }\n\n      importTracking[i].tlsModuleIndex = importModule->fileInfoBuffer->tlsModuleIndex;\n      importTracking[i].rpl = rpl;\n   }\n\n   // Process relocations and imports\n   Loader_LogEntry(2, 0, 0, \"sLoadOneShared  LiFixupRelocOneRPL start.\");\n   error = LiFixupRelocOneRPL(rpl, importTracking, 0);\n   Loader_LogEntry(2, 0, 0, \"sLoadOneShared  LiFixupRelocOneRPL end.\");\n   if (error) {\n      Loader_ReportError(\"LiFixupRelocOneRPL failed for shared library \\\"{}\\\".\", filename);\n      return error;\n   }\n\n   for (auto i = 1; i < rpl->elfHeader.shnum - 2; ++i) {\n      auto sectionHeader = getSectionHeader(rpl, i);\n      if (sectionHeader->size && sectionHeader->type == rpl::SHT_NOBITS) {\n         auto tinyHeapError =\n            TinyHeap_AllocAt(sgpTrackComp,\n                             virt_cast<void *>(rpl->sectionAddressBuffer[i]),\n                             sectionHeader->size);\n         if (tinyHeapError != TinyHeapError::OK) {\n            Loader_Panic(0x13000B, \"*** Critical error in tracking shared bss.\");\n         }\n      }\n   }\n\n   if (rpl->compressedRelocationsBuffer) {\n      LiCacheLineCorrectFreeEx(gpSharedCodeHeapTracking,\n                               rpl->compressedRelocationsBuffer,\n                               rpl->compressedRelocationsBufferSize);\n      rpl->compressedRelocationsBuffer = nullptr;\n   }\n\n   if (importTracking) {\n      LiCacheLineCorrectFreeEx(getGlobalStorage()->processCodeHeap, importTracking, importTrackingSize);\n      importTracking = nullptr;\n   }\n\n   LiCacheLineCorrectFreeEx(getGlobalStorage()->processCodeHeap, rpl->crcBuffer, rpl->crcBufferSize);\n   rpl->crcBuffer = nullptr;\n   rpl->crcBufferSize = 0u;\n   rpl->sectionAddressBuffer[rpl->elfHeader.shnum - 2] = virt_addr { 0 };\n   rpl->nextLoadedRpl = gpLoaderShared->loadedModules;\n   gpLoaderShared->loadedModules = rpl;\n   return 0;\n}\n\nstatic int32_t\nLiInitSharedForAll()\n{\n   Loader_LogEntry(2, 1, 512, \"LiInitSharedForAll\");\n\n   // Setup tracking compression block heap\n   auto outAllocPtr = virt_ptr<void> { nullptr };\n   auto tinyHeapError = TinyHeap_Alloc(gpSharedCodeHeapTracking,\n                                       0x430, -4,\n                                       &outAllocPtr);\n   if (tinyHeapError < TinyHeapError::OK) {\n      Loader_Panic(0x13000C, \"***Could not allocate memory for tracking compression blocks for shared data.\");\n   }\n\n   sgpTrackComp = virt_cast<TinyHeap *>(outAllocPtr);\n   tinyHeapError = TinyHeap_Setup(sgpTrackComp, 0x430, virt_cast<void *>(virt_addr { 0x10000000 }), 0x60000000); // 1536 mb??\n   if (tinyHeapError < TinyHeapError::OK) {\n      Loader_Panic(0x13000E, \"***Could not setup heap for tracking compression blocks for shared data.\");\n   }\n\n   // First load coreinit.rpl\n   auto error = sLoadOneShared(\"coreinit.rpl\");\n   if (error) {\n      Loader_Panic(0x13000F, \"***Could not bounceload coreinit.rpl to shared code area.\");\n   }\n\n   // Load remaining shared libraries\n   Loader_LogEntry(2, 1, 0, \"LiInitSharedForAll  load GRP start\");\n   for (auto &name : SharedLibraryList) {\n      error = sLoadOneShared(name);\n      if (error) {\n         Loader_Panic(0x130010, \"*** Could not bounceload shared library to shared code area.\");\n      }\n   }\n   Loader_LogEntry(2, 1, 0, \"LiInitSharedForAll  load GRP end\");\n\n   gpLoaderShared->dataBufferHead = align_up(gpLoaderShared->dataBufferHead, 0x100);\n\n   // Count number of track comp blocks\n   for (auto block = TinyHeap_Enum(sgpTrackComp, nullptr, nullptr, nullptr);\n        block;\n        block = TinyHeap_Enum(sgpTrackComp, block, nullptr, nullptr)) {\n      ++gpLoaderShared->numUsedTrackCompBlocks;\n   }\n\n   for (auto block = TinyHeap_EnumFree(sgpTrackComp, nullptr, nullptr, nullptr);\n        block;\n        block = TinyHeap_EnumFree(sgpTrackComp, block, nullptr, nullptr)) {\n      ++gpLoaderShared->numFreeTrackCompBlocks;\n   }\n\n   tinyHeapError =\n      TinyHeap_Alloc(gpSharedReadHeapTracking,\n                     static_cast<int32_t>(sizeof(LiCompressedSharedDataTracking) * (gpLoaderShared->numFreeTrackCompBlocks + gpLoaderShared->numUsedTrackCompBlocks)),\n                     -4,\n                     &outAllocPtr);\n   if (tinyHeapError != TinyHeapError::OK) {\n      Loader_Panic(0x130011, \"***Coult not allocate enough space for compressed shared data tracking.\");\n   }\n\n   gpLoaderShared->freeTrackCompBlocks =\n      virt_cast<LiCompressedSharedDataTracking *>(outAllocPtr);\n   gpLoaderShared->usedTrackCompBlocks =\n      virt_cast<LiCompressedSharedDataTracking *>(\n         virt_cast<virt_addr>(outAllocPtr)\n         + (sizeof(LiCompressedSharedDataTracking) * gpLoaderShared->numFreeTrackCompBlocks));\n\n   auto blockIndex = 0u;\n   auto blockPointer = virt_ptr<void> { 0 };\n   auto blockSize = uint32_t { 0 };\n   for (auto block = TinyHeap_Enum(sgpTrackComp, nullptr, &blockPointer, &blockSize);\n        block;\n        block = TinyHeap_Enum(sgpTrackComp, block, &blockPointer, &blockSize)) {\n      gpLoaderShared->usedTrackCompBlocks[blockIndex].data = blockPointer;\n      gpLoaderShared->usedTrackCompBlocks[blockIndex].intialisationData = nullptr;\n      gpLoaderShared->usedTrackCompBlocks[blockIndex].compressedSize = 0u;\n      gpLoaderShared->usedTrackCompBlocks[blockIndex].size = blockSize;\n      ++blockIndex;\n   }\n\n   blockIndex = 0u;\n   for (auto block = TinyHeap_EnumFree(sgpTrackComp, nullptr, &blockPointer, &blockSize);\n        block;\n        block = TinyHeap_EnumFree(sgpTrackComp, block, &blockPointer, &blockSize)) {\n      gpLoaderShared->freeTrackCompBlocks[blockIndex].data = blockPointer;\n      gpLoaderShared->freeTrackCompBlocks[blockIndex].intialisationData = nullptr;\n      gpLoaderShared->freeTrackCompBlocks[blockIndex].compressedSize = 0u;\n      gpLoaderShared->freeTrackCompBlocks[blockIndex].size = blockSize;\n      ++blockIndex;\n   }\n\n   --gpLoaderShared->numFreeTrackCompBlocks; // Why? i do not know.\n\n   // Allocate temporary buffer to use for compressing free blocks\n   auto compressedInitialisationData = virt_ptr<void> { nullptr };\n   tinyHeapError =  TinyHeap_Alloc(gpSharedCodeHeapTracking,\n                                   TinyHeap_GetLargestFree(gpSharedCodeHeapTracking) - 4,\n                                   4,\n                                   &compressedInitialisationData);\n   if (tinyHeapError != TinyHeapError::OK) {\n      Loader_Panic(0x130012, \"***Could not allocate space for compressed initialization data.\");\n   }\n\n   for (auto i = 0u; i < gpLoaderShared->numFreeTrackCompBlocks; ++i) {\n      auto &block = gpLoaderShared->freeTrackCompBlocks[i];\n      auto compressedBlockSize = uLongf { block.size };\n      if (block.size > 0x200) {\n         error = compress(reinterpret_cast<Bytef *>(compressedInitialisationData.get()),\n                          &compressedBlockSize,\n                          reinterpret_cast<Bytef *>(block.data.get()),\n                          block.size);\n         if (error != Z_OK) {\n            if (error == Z_MEM_ERROR) {\n               error = Error::ZlibMemError;\n               LiSetFatalError(0x187298u, 1u, 0, \"LiInitSharedForAll\", 487);\n            } else if (error == Z_BUF_ERROR) {\n               error = Error::ZlibBufError;\n               LiSetFatalError(0x18729Bu, 1u, 1, \"LiInitSharedForAll\", 483);\n            }\n\n            Loader_Panic(0x130013, \"***Could not compress initialization data for processes shared libraries.\");\n         }\n      }\n\n      if (((100 * compressedBlockSize) / block.size) <= 90) {\n         // Stored the compressed initialisation data when compressed size < 90%\n         tinyHeapError = TinyHeap_Alloc(gpSharedReadHeapTracking,\n                                        static_cast<int32_t>(compressedBlockSize),\n                                        -4,\n                                        &outAllocPtr);\n         if (tinyHeapError != TinyHeapError::OK) {\n            Loader_Panic(0x130015, \"***Could not allocate space for compressed shared initialization data.\");\n            error = static_cast<int32_t>(tinyHeapError);\n            break;\n         }\n\n         block.intialisationData = outAllocPtr;\n         block.compressedSize = static_cast<uint32_t>(compressedBlockSize);\n\n         std::memcpy(block.intialisationData.get(),\n                     compressedInitialisationData.get(),\n                     compressedBlockSize);\n         Loader_FlushDataRangeNoSync(virt_cast<virt_addr>(block.intialisationData),\n                                     block.compressedSize);\n      } else {\n         // Store uncompressed\n         tinyHeapError = TinyHeap_Alloc(gpSharedReadHeapTracking,\n                                        block.size,\n                                        -4,\n                                        &outAllocPtr);\n         if (tinyHeapError != TinyHeapError::OK) {\n            Loader_Panic(0x130015, \"***Could not allocate space for compressed shared initialization data.\");\n            error = static_cast<int32_t>(tinyHeapError);\n            break;\n         }\n\n         block.intialisationData = outAllocPtr;\n         std::memcpy(block.intialisationData.get(),\n                     block.data.get(),\n                     block.size);\n         Loader_FlushDataRangeNoSync(virt_cast<virt_addr>(block.intialisationData),\n                                     block.size);\n      }\n   }\n\n   TinyHeap_Free(gpSharedCodeHeapTracking, compressedInitialisationData);\n   TinyHeap_Free(gpSharedCodeHeapTracking, sgpTrackComp);\n   Loader_LogEntry(2, 1, 1024, \"LiInitSharedForAll\");\n   return error;\n}\n\nint32_t\ninitialiseSharedHeaps()\n{\n   constexpr auto SharedCodeTrackingSize = 0x830u;\n   constexpr auto SharedReadTrackingSize = 0x1030u;\n   constexpr auto LoaderSharedAddr = virt_addr { 0xFA000000 };\n   constexpr auto SharedCodeHeapTrackingAddr = LoaderSharedAddr + sizeof(LoaderShared);\n   constexpr auto SharedReadHeapTrackingAddr = SharedCodeHeapTrackingAddr + SharedCodeTrackingSize;\n\n   constexpr auto SharedCodeHeapAddr = virt_addr { 0x01000000 };\n   constexpr auto SharedCodeHeapSize = uint32_t { 0x007E0000 };\n\n   constexpr auto SharedReadHeapAddr = virt_addr { 0xF8000000 };\n   constexpr auto SharedReadHeapSize = uint32_t { 0x03000000 };\n   constexpr auto SharedReadHeapReserveSize = uint32_t { 0x02000000 }; // Unknown\n\n   gpLoaderShared = virt_cast<LoaderShared *>(LoaderSharedAddr);\n   gpSharedCodeHeapTracking = virt_cast<TinyHeap *>(SharedCodeHeapTrackingAddr);\n   gpSharedReadHeapTracking = virt_cast<TinyHeap *>(SharedReadHeapTrackingAddr);\n\n   if (getProcFlags().isFirstProcess()) {\n      if (TinyHeap_Setup(gpSharedCodeHeapTracking,\n                         SharedCodeTrackingSize,\n                         virt_cast<void *>(SharedCodeHeapAddr),\n                         SharedCodeHeapSize) != TinyHeapError::OK) {\n         Loader_Panic(0x130002, \"***Could not initialize shared code heap tracking.\");\n      }\n\n      if (TinyHeap_Setup(gpSharedReadHeapTracking,\n                         SharedReadTrackingSize,\n                         virt_cast<void *>(SharedReadHeapAddr),\n                         SharedReadHeapSize) != TinyHeapError::OK) {\n         Loader_Panic(0x130003, \"***Could not initialize data heap tracking.\");\n      }\n\n      // Reserve space for loader .text section\n      if (TinyHeap_AllocAt(gpSharedCodeHeapTracking,\n                           virt_cast<void *>(SharedCodeHeapAddr),\n                           align_up(0x1C758, 1024)) != TinyHeapError::OK) {\n         Loader_Panic(0x130004, \"***Could not reserve shared code space for loader.\");\n      }\n\n      // Reserve unknown chunk in shared read heap\n      if (TinyHeap_AllocAt(gpSharedReadHeapTracking,\n                           virt_cast<void *>(SharedReadHeapAddr),\n                           SharedReadHeapReserveSize) != TinyHeapError::OK) {\n         Loader_Panic(0x130005, \"***Could not reserve read/only space for heap tracking.\");\n      }\n\n      // Reserve gpLoaderShared\n      if (TinyHeap_AllocAt(gpSharedReadHeapTracking,\n                           gpLoaderShared,\n                           sizeof(LoaderShared)) != TinyHeapError::OK) {\n         Loader_Panic(0x130006, \"***Could not reserve read/only space for heap tracking.\");\n      }\n\n      // Reserve gpSharedCodeHeapTracking and gpSharedReadHeapTracking\n      if (TinyHeap_AllocAt(gpSharedReadHeapTracking,\n                           virt_cast<void *>(SharedCodeHeapTrackingAddr),\n                           SharedCodeTrackingSize + SharedReadTrackingSize) != TinyHeapError::OK) {\n         Loader_Panic(0x130007, \"***Could not reserve read/only space for heap tracking.\");\n      }\n\n      // Clear gpLoaderShared\n      std::memset(gpLoaderShared.get(), 0, sizeof(LoaderShared));\n      gpLoaderShared->dataBufferHead = virt_cast<void *>(virt_addr { 0x10000000 });\n      Loader_ReportWarn(\"Title Loc is {}\", getProcTitleLoc());\n   }\n\n   return 0;\n}\n\nvirt_ptr<LOADED_RPL>\nfindLoadedSharedModule(std::string_view name)\n{\n   for (auto module = gpLoaderShared->loadedModules; module; module = module->nextLoadedRpl) {\n      auto moduleName =\n         std::string_view {\n            module->moduleNameBuffer.get(),\n            module->moduleNameLen\n         };\n      if (moduleName == name) {\n         return module;\n      }\n   }\n\n   return nullptr;\n}\n\nint32_t\nLiInitSharedForProcess(virt_ptr<RPL_STARTINFO> initData)\n{\n   auto globals = getGlobalStorage();\n\n   Loader_LogEntry(2, 1, 512, \"LiInitSharedForProcess\");\n   if (globals->currentUpid != kernel::UniqueProcessId::Root) {\n      if (getProcFlags().disableSharedLibraries()) {\n         initData->dataAreaStart = 0x10000000u;\n         Loader_ReportWarn(\"Shared Libraries disabled in process {}\",\n                           static_cast<int>(globals->currentUpid.value()));\n         Loader_LogEntry(2, 1, 1024, \"LiInitSharedForProcess\");\n         return 0;\n      }\n   }\n\n   if (getProcFlags().isFirstProcess()) {\n      auto error = LiInitSharedForAll();\n      if (!error) {\n         initData->dataAreaStart = virt_cast<virt_addr>(gpLoaderShared->dataBufferHead);\n\n         // Allocate some memory to place HLE unimplemented function call stubs\n         sHleUnimplementedStubMemorySize =\n            TinyHeap_GetLargestFree(gpSharedCodeHeapTracking) - 4;\n         TinyHeap_Alloc(gpSharedCodeHeapTracking,\n                        sHleUnimplementedStubMemorySize,\n                        4,\n                        &sHleUnimplementedStubMemory);\n\n         hle::setUnimplementedFunctionStubMemory(sHleUnimplementedStubMemory,\n                                                 sHleUnimplementedStubMemorySize);\n      }\n\n      Loader_LogEntry(2, 1, 1024, \"LiInitSharedForProcess\");\n      return error;\n   }\n\n   // TODO: Finish LiInitSharedForProcess for non-first processes\n   decaf_abort(\"LiInitSharedForProcess not implemented for not first process\");\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_shared.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::loader\n{\n\nstruct RPL_STARTINFO;\nstruct LOADED_RPL;\n\nnamespace internal\n{\n\nint32_t\ninitialiseSharedHeaps();\n\nvirt_ptr<LOADED_RPL>\nfindLoadedSharedModule(std::string_view moduleName);\n\nint32_t\nLiInitSharedForProcess(virt_ptr<RPL_STARTINFO> initData);\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_utils.h",
    "content": "#pragma once\n#include \"cafe_loader_basics.h\"\n#include \"cafe_loader_rpl.h\"\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::loader::internal\n{\n\ninline  std::string_view\nLiResolveModuleName(std::string_view name)\n{\n   auto pos = name.find_last_of(\"\\\\/\");\n   if (pos != std::string_view::npos) {\n      name = name.substr(pos + 1);\n   }\n\n   pos = name.find_first_of(\".\");\n   if (pos != std::string_view::npos) {\n      name = name.substr(0, pos);\n   }\n\n   return name;\n}\n\ninline void\nLiResolveModuleName(virt_ptr<virt_ptr<char>> moduleName,\n                    virt_ptr<uint32_t> moduleNameLen)\n{\n   auto name = std::string_view { moduleName->get(), *moduleNameLen };\n   auto pos = name.find_last_of(\"\\\\/\");\n   if (pos != std::string_view::npos) {\n      auto diff = static_cast<uint32_t>(pos + 1);\n      name = name.substr(diff);\n      *moduleName += diff;\n      *moduleNameLen -= diff;\n   }\n\n   pos = name.find_first_of(\".\");\n   if (pos != std::string_view::npos) {\n      auto diff = static_cast<uint32_t>(name.size() - pos);\n      *moduleNameLen -= diff;\n   }\n\n   (*moduleName)[*moduleNameLen] = char { 0 };\n}\n\ninline virt_ptr<rpl::SectionHeader>\ngetSectionHeader(virt_ptr<LOADED_RPL> rpl, uint32_t index)\n{\n   return virt_cast<rpl::SectionHeader *>(\n      virt_cast<virt_addr>(rpl->sectionHeaderBuffer) +\n      (index * rpl->elfHeader.shentsize));\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_zlib.cpp",
    "content": "#include \"cafe_loader_iop.h\"\n#include \"cafe_loader_zlib.h\"\n#include <zlib.h>\n\nnamespace cafe::loader::internal\n{\n\nuint32_t\nLiCalcCRC32(uint32_t crc,\n            virt_ptr<const void> data,\n            uint32_t size)\n{\n   LiCheckAndHandleInterrupts();\n   if (!data || !size) {\n      return crc;\n   }\n\n   crc = crc32(crc, reinterpret_cast<const Bytef *>(data.get()), size);\n   LiCheckAndHandleInterrupts();\n   return crc;\n}\n\n} // namespace cafe::loader::internal\n"
  },
  {
    "path": "src/libdecaf/src/cafe/loader/cafe_loader_zlib.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace cafe::loader\n{\n\nstruct LOADED_RPL;\n\nnamespace internal\n{\n\nuint32_t\nLiCalcCRC32(uint32_t crc,\n            virt_ptr<const void> data,\n            uint32_t size);\n\nint32_t\nZLIB_UncompressFromStream(virt_ptr<LOADED_RPL> basics,\n                          uint32_t sectionIndex,\n                          std::string_view boundsName,\n                          uint32_t fileOffset,\n                          uint32_t deflatedSize,\n                          virt_ptr<void> dst,\n                          uint32_t *inflatedSize);\n\n} // namespace internal\n\n} // namespace cafe::loader\n"
  },
  {
    "path": "src/libdecaf/src/cafe/nn/cafe_nn_ipc_bufferallocator.cpp",
    "content": "#include \"cafe_nn_ipc_bufferallocator.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_ios.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n\nusing namespace cafe::coreinit;\n\nnamespace nn::ipc\n{\n\nBufferAllocator::BufferAllocator()\n{\n   OSInitMutex(virt_addrof(mMutex));\n}\n\nvoid\nBufferAllocator::initialise(virt_ptr<void> buffer,\n                            uint32_t size)\n{\n   auto numBuffers = size / BufferSize;\n\n   // Set up a linked list of free buffers\n   auto bufferAddr = virt_cast<virt_addr>(buffer);\n   mFreeBuffers = virt_cast<FreeBuffer *>(bufferAddr);\n\n   for (auto i = 0u; i < numBuffers; ++i) {\n      auto curBuffer = virt_cast<FreeBuffer *>(bufferAddr);\n      auto nextBuffer = virt_cast<FreeBuffer *>(bufferAddr + BufferSize);\n\n      if (i < numBuffers - 1) {\n         curBuffer->next = nextBuffer;\n      } else {\n         curBuffer->next = nullptr;\n      }\n\n      bufferAddr += BufferSize;\n   }\n}\n\nvirt_ptr<void>\nBufferAllocator::allocate(uint32_t size)\n{\n   auto result = virt_ptr<void> { nullptr };\n   decaf_check(size <= BufferSize);\n\n   OSLockMutex(virt_addrof(mMutex));\n   result = mFreeBuffers;\n   mFreeBuffers = mFreeBuffers->next;\n   OSUnlockMutex(virt_addrof(mMutex));\n   return result;\n}\n\nvoid\nBufferAllocator::deallocate(virt_ptr<void> ptr)\n{\n   auto buffer = virt_cast<FreeBuffer *>(ptr);\n   OSLockMutex(virt_addrof(mMutex));\n   buffer->next = mFreeBuffers;\n   mFreeBuffers = buffer;\n   OSUnlockMutex(virt_addrof(mMutex));\n}\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/nn/cafe_nn_ipc_bufferallocator.h",
    "content": "#pragma once\n#include \"cafe/libraries/coreinit/coreinit_ios.h\"\n#include \"cafe/libraries/coreinit/coreinit_mutex.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace nn::ipc\n{\n\nclass BufferAllocator\n{\n   static constexpr auto BufferSize = 256u;\n\n   struct FreeBuffer\n   {\n      be2_virt_ptr<FreeBuffer> next;\n   };\n\npublic:\n   BufferAllocator();\n\n   void\n   initialise(virt_ptr<void> buffer,\n              uint32_t size);\n\n   virt_ptr<void>\n   allocate(uint32_t size);\n\n   void\n   deallocate(virt_ptr<void> ptr);\n\nprivate:\n   be2_struct<cafe::coreinit::OSMutex> mMutex;\n   be2_virt_ptr<FreeBuffer> mFreeBuffers;\n};\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/nn/cafe_nn_ipc_client.cpp",
    "content": "#include \"cafe_nn_ipc_client.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_ios.h\"\n#include \"nn/nn_result.h\"\n\nusing namespace cafe::coreinit;\n\nnamespace nn::ipc\n{\n\nResult\nClient::initialise(virt_ptr<const char> device)\n{\n   auto error = IOS_Open(device, IOSOpenMode::None);\n   if (error < 0) {\n      return ios::convertError(error);\n   }\n\n   mHandle = static_cast<IOSHandle>(error);\n   return ios::ResultOK;\n}\n\nResult\nClient::close()\n{\n   return ios::convertError(IOS_Close(mHandle));\n}\n\nbool\nClient::isInitialised() const\n{\n   return mHandle >= 0;\n}\n\nResult\nClient::sendSyncRequest(const detail::ClientCommandData &command)\n{\n   auto error = IOS_Ioctlv(mHandle, 0,\n                           command.numVecIn,\n                           command.numVecOut,\n                           command.vecsBuffer);\n\n   if (error < 0) {\n      return ios::convertError(error);\n   }\n\n   return ResultSuccess;\n}\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/nn/cafe_nn_ipc_client.h",
    "content": "#pragma once\n#include \"cafe_nn_ipc_client_command.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_ios.h\"\n#include \"nn/ios/nn_ios_error.h\"\n#include \"nn/nn_result.h\"\n\nnamespace nn::ipc\n{\n\nclass Client\n{\npublic:\n   Result initialise(virt_ptr<const char> device);\n   Result close();\n   bool isInitialised() const;\n\n   template<typename CommandType>\n   Result sendSyncRequest(const ClientCommand<CommandType> &command)\n   {\n      return sendSyncRequest(command.getCommandData());\n   }\n\nprivate:\n   Result sendSyncRequest(const detail::ClientCommandData &command);\n\nprivate:\n   cafe::coreinit::IOSHandle mHandle { -1 };\n};\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/nn/cafe_nn_ipc_client_command.h",
    "content": "#pragma once\n#include \"cafe_nn_ipc_bufferallocator.h\"\n\n#include \"nn/nn_result.h\"\n#include \"nn/ios/nn_ios_error.h\"\n#include \"nn/ipc/nn_ipc_format.h\"\n#include \"nn/ipc/nn_ipc_managedbuffer.h\"\n\n#include <array>\n#include <libcpu/be2_struct.h>\n\nnamespace nn::ipc\n{\n\nnamespace detail\n{\n\ntemplate<typename... Ts>\nstruct ManagedBufferCount;\n\ntemplate<>\nstruct ManagedBufferCount<>\n{\n   static constexpr auto Input = 0;\n   static constexpr auto Output = 0;\n};\n\ntemplate<typename T, typename... Ts>\nstruct ManagedBufferCount<T, Ts...>\n{\n   static constexpr auto Input = 0 + ManagedBufferCount<Ts...>::Input;\n   static constexpr auto Output = 0 + ManagedBufferCount<Ts...>::Output;\n};\n\ntemplate<typename T, typename... Ts>\nstruct ManagedBufferCount<InBuffer<T>, Ts...>\n{\n   static constexpr auto Input = 1 + ManagedBufferCount<Ts...>::Input;\n   static constexpr auto Output = 0 + ManagedBufferCount<Ts...>::Output;\n};\n\ntemplate<typename T, typename... Ts>\nstruct ManagedBufferCount<InOutBuffer<T>, Ts...>\n{\n   static constexpr auto Input = 0 + ManagedBufferCount<Ts...>::Input;\n   static constexpr auto Output = 1 + ManagedBufferCount<Ts...>::Output;\n};\n\ntemplate<typename T, typename... Ts>\nstruct ManagedBufferCount<OutBuffer<T>, Ts...>\n{\n   static constexpr auto Input = 0 + ManagedBufferCount<Ts...>::Input;\n   static constexpr auto Output = 1 + ManagedBufferCount<Ts...>::Output;\n};\n\nstruct ManagedBufferInfo\n{\n   virt_ptr<void> ipcBuffer = nullptr;\n\n   virt_ptr<void> userBuffer;\n   uint32_t userBufferSize;\n\n   virt_ptr<void> unalignedBeforeBuffer;\n   uint32_t unalignedBeforeBufferSize;\n\n   virt_ptr<void> alignedBuffer;\n   uint32_t alignedBufferSize;\n\n   virt_ptr<void> unalignedAfterBuffer;\n   uint32_t unalignedAfterBufferSize;\n\n   bool output;\n};\n\nstruct ClientCommandData\n{\n   virt_ptr<BufferAllocator> allocator;\n   virt_ptr<void> requestBuffer;\n   virt_ptr<void> responseBuffer;\n   virt_ptr<::ios::IoctlVec> vecsBuffer;\n   ManagedBufferInfo *ioBuffers;\n   int numVecIn;\n   int numVecOut;\n};\n\ntemplate<typename Type>\nstruct IpcSerialiser\n{\n   static void\n   write(ClientCommandData &data,\n         size_t &offset,\n         int &inputVecIdx,\n         int &outputVecIdx,\n         const Type &value)\n   {\n      auto ptr = virt_cast<Type *>(virt_cast<virt_addr>(data.requestBuffer) + offset);\n      *ptr = value;\n      offset += sizeof(Type);\n   }\n};\n\ntemplate<>\nstruct IpcSerialiser<ManagedBuffer>\n{\n   static void\n   write(ClientCommandData &data,\n         size_t &offset,\n         int &inputVecIdx,\n         int &outputVecIdx,\n         const ManagedBuffer &userBuffer)\n   {\n      // The user buffer pointer is not guaranteed to be aligned so we must\n      // split the buffer by separately reading / writing the unaligned data at\n      // the start and end of the user buffer.\n      auto &ioBuffer = data.ioBuffers[(inputVecIdx + outputVecIdx) / 2];\n      ioBuffer.ipcBuffer = data.allocator->allocate(256);\n      ioBuffer.userBuffer = userBuffer.ptr;\n      ioBuffer.userBufferSize = userBuffer.size;\n      ioBuffer.output = userBuffer.output;\n\n      auto midPoint = virt_cast<virt_addr>(ioBuffer.ipcBuffer) + 64;\n\n      auto unalignedStart = virt_cast<virt_addr>(userBuffer.ptr);\n      auto unalignedEnd = unalignedStart + userBuffer.size;\n\n      auto alignedStart = align_up(unalignedStart, 64);\n      auto alignedEnd = align_down(unalignedEnd, 64);\n\n      if (unalignedEnd <= alignedStart) {\n         // Whole buffer is before alignment\n         ioBuffer.unalignedBeforeBufferSize = ioBuffer.userBufferSize;\n         ioBuffer.unalignedBeforeBuffer =\n            virt_cast<void *>(midPoint - ioBuffer.unalignedBeforeBufferSize);\n\n         ioBuffer.alignedBuffer = nullptr;\n         ioBuffer.alignedBufferSize = 0;\n\n         ioBuffer.unalignedAfterBuffer = nullptr;\n         ioBuffer.unalignedAfterBufferSize = 0;\n      } else {\n         // Split over unaligned before / aligned / unaligned after\n         ioBuffer.unalignedBeforeBufferSize =\n            static_cast<uint32_t>(alignedStart - unalignedStart);\n         ioBuffer.unalignedBeforeBuffer =\n            virt_cast<void *>(midPoint - ioBuffer.unalignedBeforeBufferSize);\n\n         ioBuffer.alignedBuffer = virt_cast<void *>(alignedStart);\n         ioBuffer.alignedBufferSize =\n            static_cast<uint32_t>(alignedEnd - alignedStart);\n\n         ioBuffer.unalignedAfterBufferSize =\n            static_cast<uint32_t>(unalignedEnd - alignedEnd);\n         ioBuffer.unalignedAfterBuffer =\n            virt_cast<void *>(midPoint);\n      }\n\n      if (userBuffer.input) {\n         // Copy the unaligned buffer input\n         std::memcpy(ioBuffer.unalignedBeforeBuffer.get(),\n                     virt_cast<void *>(unalignedStart).get(),\n                     ioBuffer.unalignedBeforeBufferSize);\n\n         std::memcpy(\n            ioBuffer.unalignedAfterBuffer.get(),\n            virt_cast<void *>(unalignedEnd\n                              - ioBuffer.unalignedAfterBufferSize).get(),\n            ioBuffer.unalignedAfterBufferSize);\n      }\n\n      // Calculate our ioctlv vecs indices\n      auto alignedBufferIndex = uint8_t { 0 };\n      auto unalignedBufferIndex = uint8_t { 0 };\n      auto bufferIndexOffset = uint8_t { 0 };\n\n      if (userBuffer.output) {\n         alignedBufferIndex = static_cast<uint8_t>(outputVecIdx++);\n         unalignedBufferIndex = static_cast<uint8_t>(outputVecIdx++);\n         bufferIndexOffset = 1u;\n      } else {\n         alignedBufferIndex = static_cast<uint8_t>(inputVecIdx++);\n         unalignedBufferIndex = static_cast<uint8_t>(inputVecIdx++);\n         bufferIndexOffset = static_cast<uint8_t>(1 + data.numVecOut);\n      }\n\n      // Update our ioctlv vecs buffer\n      auto &alignedBufferVec =\n         data.vecsBuffer[bufferIndexOffset + alignedBufferIndex];\n      auto &unalignedBufferVec =\n         data.vecsBuffer[bufferIndexOffset + unalignedBufferIndex];\n\n      alignedBufferVec.vaddr = virt_cast<virt_addr>(ioBuffer.alignedBuffer);\n      alignedBufferVec.len = ioBuffer.alignedBufferSize;\n\n      if (ioBuffer.unalignedBeforeBufferSize + ioBuffer.unalignedAfterBufferSize) {\n         unalignedBufferVec.vaddr =\n            virt_cast<virt_addr>(ioBuffer.unalignedBeforeBuffer);\n         unalignedBufferVec.len =\n            ioBuffer.unalignedBeforeBufferSize\n            + ioBuffer.unalignedAfterBufferSize;\n      } else {\n         unalignedBufferVec.vaddr = virt_addr { 0u };\n         unalignedBufferVec.len = 0u;\n      }\n\n      // Serialise the buffer info to the request\n      auto managedBuffer =\n         virt_cast<ManagedBufferParameter *>(\n            virt_cast<virt_addr>(data.requestBuffer) + offset);\n      managedBuffer->alignedBufferSize = ioBuffer.alignedBufferSize;\n      managedBuffer->unalignedBeforeBufferSize =\n         static_cast<uint8_t>(ioBuffer.unalignedBeforeBufferSize);\n      managedBuffer->unalignedAfterBufferSize =\n         static_cast<uint8_t>(ioBuffer.unalignedAfterBufferSize);\n      managedBuffer->alignedBufferIndex = alignedBufferIndex;\n      managedBuffer->unalignedBufferIndex = unalignedBufferIndex;\n      offset += 8;\n   }\n};\n\ntemplate<typename Type>\nstruct IpcSerialiser<::nn::ipc::InBuffer<Type>>\n{\n   static void write(ClientCommandData &data, size_t &offset, int &inputVecIdx,\n                     int &outputVecIdx, const ManagedBuffer &userBuffer)\n   {\n      IpcSerialiser<ManagedBuffer>::write(data, offset, inputVecIdx,\n                                          outputVecIdx, userBuffer);\n   }\n};\n\ntemplate<typename Type>\nstruct IpcSerialiser<::nn::ipc::InOutBuffer<Type>>\n{\n   static void write(ClientCommandData &data, size_t &offset, int &inputVecIdx,\n                     int &outputVecIdx, const ManagedBuffer &userBuffer)\n   {\n      IpcSerialiser<ManagedBuffer>::write(data, offset, inputVecIdx,\n                                          outputVecIdx, userBuffer);\n   }\n};\n\ntemplate<typename Type>\nstruct IpcSerialiser<::nn::ipc::OutBuffer<Type>>\n{\n   static void write(ClientCommandData &data, size_t &offset, int &inputVecIdx,\n                     int &outputVecIdx, const ManagedBuffer &userBuffer)\n   {\n      IpcSerialiser<ManagedBuffer>::write(data, offset, inputVecIdx,\n                                          outputVecIdx, userBuffer);\n   }\n};\n\ntemplate<typename Type>\nstruct IpcDeserialiser\n{\n   static void read(ClientCommandData &data, size_t &offset, Type &value)\n   {\n      auto ptr =\n         virt_cast<Type *>(\n            virt_cast<virt_addr>(data.responseBuffer) + offset);\n      value = *ptr;\n      offset += sizeof(Type);\n   }\n};\n\ntemplate<int, int, typename... Types>\nstruct ClientCommandHelper;\n\ntemplate<int ServiceId, int CommandId,\n         typename... ParameterTypes,\n         typename... ResponseTypes>\nstruct ClientCommandHelper<ServiceId, CommandId,\n                           std::tuple<ParameterTypes...>,\n                           std::tuple<ResponseTypes...>>\n{\n   static constexpr auto NumInputBuffers =\n      ManagedBufferCount<ParameterTypes...>::Input;\n\n   static constexpr auto NumOutputBuffers =\n      ManagedBufferCount<ParameterTypes...>::Output;\n\n   static constexpr auto NumManagedBuffers = NumInputBuffers + NumOutputBuffers;\n\npublic:\n   ClientCommandHelper(virt_ptr<BufferAllocator> allocator)\n   {\n      // Allocate buffers\n      mData.allocator = allocator;\n      mData.vecsBuffer = virt_cast<::ios::IoctlVec *>(allocator->allocate(128));\n      mData.requestBuffer = allocator->allocate(128);\n      mData.responseBuffer = allocator->allocate(128);\n      mData.ioBuffers = mManagedBufferInfo.data();\n      mData.numVecIn = 1 + 2 * NumOutputBuffers;\n      mData.numVecOut = 1 + 2 * NumInputBuffers;\n\n      std::memset(mData.vecsBuffer.get(), 0, 128);\n      std::memset(mData.requestBuffer.get(), 0, 128);\n      std::memset(mData.responseBuffer.get(), 0, 128);\n\n      // Setup request header\n      auto request = virt_cast<RequestHeader *>(mData.requestBuffer);\n      request->unk0x00 = 1u;\n      request->command = static_cast<uint32_t>(CommandId);\n      request->unk0x08 = 0u;\n      request->service = static_cast<uint32_t>(ServiceId);\n\n      // Setup vecs buffer\n      mData.vecsBuffer[0].vaddr =\n         virt_cast<virt_addr>(mData.responseBuffer);\n      mData.vecsBuffer[0].len = 128u;\n\n      mData.vecsBuffer[mData.numVecIn].vaddr =\n         virt_cast<virt_addr>(mData.requestBuffer);\n      mData.vecsBuffer[mData.numVecIn].len = 128u;\n   }\n\n   ~ClientCommandHelper()\n   {\n      if (mData.vecsBuffer) {\n         mData.allocator->deallocate(mData.vecsBuffer);\n      }\n\n      if (mData.requestBuffer) {\n         mData.allocator->deallocate(mData.requestBuffer);\n      }\n\n      if (mData.responseBuffer) {\n         mData.allocator->deallocate(mData.responseBuffer);\n      }\n\n      for (auto &ioBuffer : mManagedBufferInfo) {\n         if (ioBuffer.ipcBuffer) {\n            mData.allocator->deallocate(ioBuffer.ipcBuffer);\n         }\n      }\n   }\n\npublic:\n   const ClientCommandData &getCommandData() const\n   {\n      return mData;\n   }\n\n   void setParameters(ParameterTypes... params)\n   {\n      auto offset = sizeof(RequestHeader);\n      auto inputVecIdx = 0;\n      auto outputVecIdx = 0;\n      (IpcSerialiser<ParameterTypes>::write(mData, offset, inputVecIdx,\n                                            outputVecIdx, params), ...);\n   }\n\n   Result readResponse(ResponseTypes &... responses)\n   {\n      auto header = virt_cast<ResponseHeader *>(mData.responseBuffer);\n      auto result = Result { static_cast<uint32_t>(static_cast<int32_t>(header->result)) };\n\n      // Read unaligned output buffer data\n      for (auto &ioBuffer : mManagedBufferInfo) {\n         if (!ioBuffer.output) {\n            continue;\n         }\n\n         std::memcpy(ioBuffer.userBuffer.get(),\n                     ioBuffer.unalignedBeforeBuffer.get(),\n                     ioBuffer.unalignedBeforeBufferSize);\n\n         auto userAfterBufferAddr =\n            virt_cast<virt_addr>(ioBuffer.userBuffer)\n            + ioBuffer.userBufferSize - ioBuffer.unalignedAfterBufferSize;\n\n         std::memcpy(virt_cast<void *>(userAfterBufferAddr).get(),\n                     ioBuffer.unalignedAfterBuffer.get(),\n                     ioBuffer.unalignedAfterBufferSize);\n      }\n\n      // Read response values\n      auto offset = sizeof(ResponseHeader);\n      (IpcDeserialiser<ResponseTypes>::read(mData, offset, responses),  ...);\n\n      return result;\n   }\n\nprivate:\n   ClientCommandData mData;\n   std::array<ManagedBufferInfo, NumManagedBuffers> mManagedBufferInfo;\n};\n\n} // namespace detail\n\ntemplate<typename CommandType>\nclass ClientCommand;\n\ntemplate<typename CommandType>\nclass ClientCommand :\n   public detail::ClientCommandHelper<CommandType::service,\n                                      CommandType::command,\n                                      typename CommandType::parameters,\n                                      typename CommandType::response>\n{\npublic:\n   ClientCommand(virt_ptr<BufferAllocator> allocator) :\n      detail::ClientCommandHelper<CommandType::service,\n                                  CommandType::command,\n                                  typename CommandType::parameters,\n                                  typename CommandType::response>(allocator)\n   {\n   }\n};\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/cafe/nn/cafe_nn_os_criticalsection.h",
    "content": "#pragma once\n#include \"cafe/libraries/coreinit/coreinit_fastmutex.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace nn::os\n{\n\nstruct CriticalSection\n{\n   CriticalSection()\n   {\n      cafe::coreinit::OSFastMutex_Init(virt_addrof(_mutex), nullptr);\n   }\n\n   void lock()\n   {\n      cafe::coreinit::OSFastMutex_Lock(virt_addrof(_mutex));\n   }\n\n   bool try_lock()\n   {\n      return !!cafe::coreinit::OSFastMutex_TryLock(virt_addrof(_mutex));\n   }\n\n   void unlock()\n   {\n      cafe::coreinit::OSFastMutex_Unlock(virt_addrof(_mutex));\n   }\n\n   be2_struct<cafe::coreinit::OSFastMutex> _mutex;\n};\nCHECK_OFFSET(CriticalSection, 0x00, _mutex);\nCHECK_SIZE(CriticalSection, 0x2C);\n\n} // namespace nn::os\n"
  },
  {
    "path": "src/libdecaf/src/debug_api/debug_api_analyse.cpp",
    "content": "#include \"decaf_debug_api.h\"\n\n#include \"cafe/loader/cafe_loader_entry.h\"\n#include \"cafe/loader/cafe_loader_loaded_rpl.h\"\n\n#include <fmt/core.h>\n#include <libcpu/espresso/espresso_disassembler.h>\n#include <libcpu/espresso/espresso_instructionset.h>\n#include <libcpu/mem.h>\n\nnamespace decaf::debug\n{\n\nstruct FunctionListPredicate\n{\n   bool operator () (const AnalyseDatabase::Function &func, VirtualAddress addr) {\n      return func.start < addr;\n   }\n   bool operator () (VirtualAddress addr, const AnalyseDatabase::Function &func) {\n      return addr < func.start;\n   }\n};\n\nstruct RFunctionListPredicate\n{\n   bool operator () (const AnalyseDatabase::Function &func, VirtualAddress addr) {\n      return func.start > addr;\n   }\n};\n\nconst AnalyseDatabase::Function *\nanalyseLookupFunction(const AnalyseDatabase &db,\n                      VirtualAddress address)\n{\n   auto itr = std::lower_bound(db.functions.begin(), db.functions.end(),\n                               address, FunctionListPredicate { });\n   if (itr == db.functions.end() || itr->start != address) {\n      return nullptr;\n   }\n\n   return &*itr;\n}\n\ntemplate<typename ConstOptionalDatabase>\nstatic auto\nfindFunctionContainingAddress(ConstOptionalDatabase &db,\n                              VirtualAddress address)\n{\n   auto itr = std::lower_bound(db.functions.rbegin(), db.functions.rend(),\n                               address, RFunctionListPredicate{ });\n   if (itr != db.functions.rend()) {\n      auto &func = *itr;\n\n      if (address >= func.start && address < func.end) {\n         // The function needs to have an end, or be the first two instructions\n         //  since we apply some special display logic to the first two instructions\n         //  in a never-ending function...\n         if (func.end != 0xFFFFFFFF || (address == func.start || address == func.start + 4)) {\n            return &func;\n         }\n      }\n   }\n\n   return static_cast<decltype(&*itr)>(nullptr);\n}\n\nAnalyseDatabase::Lookup\nanalyseLookupAddress(const AnalyseDatabase &db,\n                     VirtualAddress address)\n{\n   auto info = AnalyseDatabase::Lookup { };\n   if (auto itr = db.instructions.find(address); itr != db.instructions.end()) {\n      info.instruction = &itr->second;\n   }\n\n   info.function = findFunctionContainingAddress(db, address);\n   return info;\n}\n\nuint32_t\nanalyseScanFunctionEnd(VirtualAddress start)\n{\n   static const uint32_t MaxScannedBytes = 0x1000u;\n   auto fnStart = start;\n   auto fnMax = start;\n   auto fnEnd = uint32_t { 0xFFFFFFFFu };\n\n   for (auto addr = start; addr < start + MaxScannedBytes; addr += 4) {\n      if (!cpu::isValidAddress(cpu::VirtualAddress { addr })) {\n         break;\n      }\n\n      auto instr = mem::read<espresso::Instruction>(addr);\n      auto data = espresso::decodeInstruction(instr);\n\n      if (!data) {\n         // If we can't decode this instruction, then we gone done fucked up\n         break;\n      }\n\n      if (addr > fnMax) {\n         fnMax = addr;\n      }\n\n      if (espresso::isBranchInstruction(data->id)) {\n         auto branchInfo =\n            espresso::disassembleBranchInfo(data->id, instr, addr, 0, 0, 0);\n\n         // Ignore call instructions\n         if (!branchInfo.isCall) {\n            if (branchInfo.isVariable) {\n               // We hit a variable non-call instruction, we can't scan\n               //  any further than this.  If we don't have any instructions\n               //  further down, this is the final.\n               if (fnMax > addr) {\n                  addr = fnMax;\n                  continue;\n               } else {\n                  fnEnd = fnMax + 4;\n                  break;\n               }\n            } else {\n               if (addr == fnMax && !branchInfo.isConditional) {\n                  if (branchInfo.target >= fnStart && branchInfo.target < addr) {\n                     // If we are the last instruction, and this instruction unconditionally\n                     //   branches backwards, that means that we must be at the end of the func.\n                     fnEnd = fnMax + 4;\n                     break;\n                  }\n               }\n\n               // We cannot follow unconditional branches outside of the function body\n               //  that we have already determined, this is because we don't want to follow\n               //  tail calls!\n               if (branchInfo.target > fnMax && branchInfo.isConditional) {\n                  fnMax = branchInfo.target;\n               }\n            }\n         }\n      }\n   }\n\n   return fnEnd;\n}\n\nstatic std::string\ndefaultFunctionName(VirtualAddress address)\n{\n   return fmt::format(\"sub_{:08x}\", address);\n}\n\nstatic void\nmarkAsFunction(AnalyseDatabase &db,\n               VirtualAddress address,\n               std::string_view name = {})\n{\n   // Check if the address is already marked as a function\n   auto function = findFunctionContainingAddress(db, address);\n   if (function) {\n      if (!name.empty()) {\n         // Update the name if a name was passed in\n         function->name = name;\n      }\n\n      return;\n   }\n\n   auto func = AnalyseDatabase::Function { };\n   func.start = address;\n   func.end = analyseScanFunctionEnd(address);\n\n   if (name.empty()) {\n      func.name = defaultFunctionName(address);\n   } else {\n      func.name = name;\n   }\n\n   db.functions.insert(\n      std::upper_bound(db.functions.begin(), db.functions.end(),\n                       func.start, FunctionListPredicate { }),\n      func);\n}\n\nvoid\nanalyseToggleAsFunction(AnalyseDatabase &db,\n                        VirtualAddress address)\n{\n   auto itr = std::lower_bound(db.functions.begin(), db.functions.end(),\n                               address, FunctionListPredicate{ });\n   if (itr == db.functions.end() || itr->start != address) {\n      markAsFunction(db, address);\n   } else {\n      db.functions.erase(itr);\n   }\n}\n\nvoid\nanalyseLoadedModules(AnalyseDatabase &db)\n{\n   cafe::loader::lockLoader();\n   for (auto rpl = cafe::loader::getLoadedRplLinkedList(); rpl; rpl = rpl->nextLoadedRpl) {\n      auto symTabHdr = virt_ptr<cafe::loader::rpl::SectionHeader> { nullptr };\n      auto symTabAddr = virt_addr { 0 };\n      auto strTabAddr = virt_addr { 0 };\n      auto textStartAddr = rpl->textAddr;\n      auto textEndAddr = rpl->textAddr + rpl->textSize;\n\n      // Find symbol section\n      if (rpl->sectionHeaderBuffer) {\n         for (auto i = 0u; i < rpl->elfHeader.shnum; ++i) {\n            auto sectionHeader =\n               virt_cast<cafe::loader::rpl::SectionHeader *>(\n                  virt_cast<virt_addr>(rpl->sectionHeaderBuffer) +\n                  (i * rpl->elfHeader.shentsize));\n\n            if (sectionHeader->type == cafe::loader::rpl::SHT_SYMTAB) {\n               symTabHdr = sectionHeader;\n               symTabAddr = rpl->sectionAddressBuffer[i];\n               strTabAddr = rpl->sectionAddressBuffer[symTabHdr->link];\n               break;\n            }\n         }\n      }\n\n      if (symTabHdr && symTabAddr && strTabAddr) {\n         auto symTabEntSize =\n            symTabHdr->entsize ?\n            static_cast<size_t>(symTabHdr->entsize) :\n            sizeof(cafe::loader::rpl::Symbol);\n         auto symTabEntries = symTabHdr->size / symTabEntSize;\n\n         for (auto i = 0u; i < symTabEntries; ++i) {\n            auto symbol =\n               virt_cast<cafe::loader::rpl::Symbol *>(\n                  symTabAddr + (i * symTabEntSize));\n            auto symbolAddress = virt_addr { static_cast<uint32_t>(symbol->value) };\n            if ((symbol->info & 0xf) == cafe::loader::rpl::STT_FUNC &&\n                symbolAddress >= textStartAddr && symbolAddress < textEndAddr) {\n               auto name = virt_cast<const char *>(strTabAddr + symbol->name);\n               markAsFunction(db, symbol->value, name.get());\n            }\n         }\n      }\n   }\n   cafe::loader::unlockLoader();\n}\n\nvoid\nanalyseCode(AnalyseDatabase &db,\n            VirtualAddress start,\n            VirtualAddress end)\n{\n   for (auto addr = start; addr < end; addr += 4) {\n      auto instr = mem::read<espresso::Instruction>(addr);\n      auto data = espresso::decodeInstruction(instr);\n\n      if (!data) {\n         continue;\n      }\n\n      if (espresso::isBranchInstruction(data->id)) {\n         auto branchInfo =\n            espresso::disassembleBranchInfo(data->id, instr, addr, 0, 0, 0);\n\n         if (!branchInfo.isCall && !branchInfo.isVariable) {\n            db.instructions[branchInfo.target].sourceBranches.push_back(addr);\n         }\n\n         // If this is a call, and its not variable, we should mark\n         //  the target as a function, since it likely is...\n         if (branchInfo.isCall && !branchInfo.isVariable) {\n            markAsFunction(db, branchInfo.target);\n         }\n      }\n   }\n}\n\n} // namespace decaf::debug\n"
  },
  {
    "path": "src/libdecaf/src/debug_api/debug_api_cafe.cpp",
    "content": "#include \"decaf_debug_api.h\"\n\n#include \"cafe/loader/cafe_loader_entry.h\"\n#include \"cafe/loader/cafe_loader_loaded_rpl.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_enum_string.h\"\n#include \"cafe/libraries/coreinit/coreinit_scheduler.h\"\n#include \"cafe/libraries/coreinit/coreinit_thread.h\"\n#include \"cafe/libraries/sndcore2/sndcore2_enum.h\"\n#include \"cafe/libraries/sndcore2/sndcore2_voice.h\"\n#include \"cafe/loader/cafe_loader_entry.h\"\n#include \"cafe/kernel/cafe_kernel_loader.h\"\n\n#include \"debugger/debugger.h\"\n\nnamespace decaf::debug\n{\n\nbool\nfindClosestSymbol(VirtualAddress addr,\n                  uint32_t *outSymbolDistance,\n                  char *symbolNameBuffer,\n                  uint32_t symbolNameBufferLength,\n                  char *moduleNameBuffer,\n                  uint32_t moduleNameBufferLength)\n{\n   return cafe::kernel::internal::findClosestSymbol(virt_addr { addr },\n                                                    outSymbolDistance,\n                                                    symbolNameBuffer,\n                                                    symbolNameBufferLength,\n                                                    moduleNameBuffer,\n                                                    moduleNameBufferLength) == 0;\n}\n\nbool\ngetLoadedModuleInfo(CafeModuleInfo &info)\n{\n   auto rpx = cafe::loader::getLoadedRpx();\n   if (!rpx) {\n      return false;\n   }\n\n   info.textAddr = static_cast<uint32_t>(rpx->textAddr);\n   info.textSize = rpx->textSize;\n\n   info.dataAddr = static_cast<uint32_t>(rpx->dataAddr);\n   info.dataSize = rpx->textSize;\n   return true;\n}\n\nbool\nsampleCafeMemorySegments(std::vector<CafeMemorySegment> &segments)\n{\n   cafe::loader::lockLoader();\n   for (auto rpl = cafe::loader::getLoadedRplLinkedList(); rpl; rpl = rpl->nextLoadedRpl) {\n      if (!rpl->sectionAddressBuffer ||\n          !rpl->sectionAddressBuffer ||\n          !rpl->moduleNameBuffer ||\n          !rpl->moduleNameLen ||\n          !rpl->sectionAddressBuffer[rpl->elfHeader.shstrndx]) {\n         continue;\n      }\n\n      auto rplName = std::string_view { rpl->moduleNameBuffer.get(),\n                                        rpl->moduleNameLen };\n      auto shStrTab =\n         virt_cast<const char *>(rpl->sectionAddressBuffer[rpl->elfHeader.shstrndx])\n         .get();\n\n      for (auto i = 0u; i < rpl->elfHeader.shnum; ++i) {\n         auto sectionHeader =\n            virt_cast<cafe::loader::rpl::SectionHeader *>(\n               virt_cast<virt_addr>(rpl->sectionHeaderBuffer) +\n               (i * rpl->elfHeader.shentsize));\n\n         if (rpl->sectionAddressBuffer[i] &&\n             sectionHeader->size != 0 &&\n             (sectionHeader->flags & cafe::loader::rpl::SHF_ALLOC)) {\n            auto &segment = segments.emplace_back();\n            segment.name = fmt::format(\"{}:{}\", rplName, shStrTab + sectionHeader->name);\n            segment.address = static_cast<uint32_t>(rpl->sectionAddressBuffer[i]);\n            segment.size = sectionHeader->size;\n            segment.align = sectionHeader->addralign;\n            segment.read = sectionHeader->flags & cafe::loader::rpl::SHF_ALLOC;\n            segment.write = sectionHeader->flags & cafe::loader::rpl::SHF_WRITE;\n            segment.execute = sectionHeader->flags & cafe::loader::rpl::SHF_EXECINSTR;\n         }\n      }\n   }\n   cafe::loader::unlockLoader();\n   return true;\n}\n\nstatic void\nsampleThreadInfo(CafeThread &info, virt_ptr<cafe::coreinit::OSThread> thread)\n{\n   info.handle = static_cast<uint32_t>(virt_cast<virt_addr>(thread));\n   info.id = thread->id;\n   info.name = thread->name ? thread->name.get() : \"\";\n   info.priority = thread->priority;\n   info.basePriority = thread->basePriority;\n   info.state = static_cast<CafeThread::ThreadState>(thread->state.value());\n   info.affinity = static_cast<CafeThread::ThreadAffinity>(thread->attr & cafe::coreinit::OSThreadAttributes::AffinityAny);\n   info.stackStart = static_cast<uint32_t>(virt_cast<virt_addr>(thread->stackStart));\n   info.stackEnd = static_cast<uint32_t>(virt_cast<virt_addr>(thread->stackEnd));\n   info.executionTime = std::chrono::nanoseconds { thread->coreTimeConsumedNs.value() };\n}\n\nstatic void\ncopyThreadRegisters(CafeThread &info, virt_ptr<cafe::coreinit::OSThread> thread)\n{\n   info.cia = thread->context.cia;\n   info.nia = thread->context.nia;\n   info.gpr = thread->context.gpr;\n   info.fpr = thread->context.fpr;\n   info.ps1 = thread->context.psf;\n   info.cr = thread->context.cr;\n   info.xer = thread->context.xer;\n   info.lr = thread->context.lr;\n   info.ctr = thread->context.ctr;\n   info.msr = 0u;\n}\n\nstatic void\ncopyContextRegisters(CafeThread &info, const decaf::debug::CpuContext *context)\n{\n   info.cia = context->cia;\n   info.nia = context->nia;\n   info.gpr = context->gpr;\n   info.fpr = context->fpr;\n   info.ps1 = context->ps1;\n   info.cr = context->cr;\n   info.xer = context->xer;\n   info.lr = context->lr;\n   info.ctr = context->ctr;\n   info.msr = context->msr;\n}\n\nbool\nsampleCafeRunningThread(int coreId, CafeThread &info)\n{\n   if (!decaf::debug::isPaused()) {\n      return false;\n   }\n\n   cafe::coreinit::internal::lockScheduler();\n   auto thread = cafe::coreinit::internal::getCoreRunningThread(coreId);\n   if (!thread) {\n      cafe::coreinit::internal::unlockScheduler();\n      return false;\n   }\n\n   sampleThreadInfo(info, thread);\n   cafe::coreinit::internal::unlockScheduler();\n\n   // Copy registers from paused context\n   info.coreId = coreId;\n   copyContextRegisters(info, decaf::debug::getPausedContext(coreId));\n   info.executionTime += std::chrono::nanoseconds { cafe::coreinit::internal::getCoreThreadRunningTime(coreId) };\n   return true;\n}\n\nbool\nsampleCafeThreads(std::vector<CafeThread> &threads)\n{\n   auto paused = decaf::debug::isPaused();\n\n   cafe::coreinit::internal::lockScheduler();\n   virt_ptr<cafe::coreinit::OSThread> runningThreads[] = {\n      cafe::coreinit::internal::getCoreRunningThread(0),\n      cafe::coreinit::internal::getCoreRunningThread(1),\n      cafe::coreinit::internal::getCoreRunningThread(2),\n   };\n\n   for (auto thread = cafe::coreinit::internal::getFirstActiveThread(); thread; thread = thread->activeLink.next) {\n      auto &info = threads.emplace_back();\n      sampleThreadInfo(info, thread);\n      info.coreId = -1;\n\n      if (paused) {\n         for (auto i = 0u; i < 3; ++i) {\n            if (thread == runningThreads[i]) {\n               info.coreId = i;\n               copyContextRegisters(info, decaf::debug::getPausedContext(i));\n               info.executionTime += std::chrono::nanoseconds { cafe::coreinit::internal::getCoreThreadRunningTime(i) };\n               break;\n            }\n         }\n      }\n\n      if (info.coreId == -1) {\n         copyThreadRegisters(info, thread);\n      }\n   }\n\n   cafe::coreinit::internal::unlockScheduler();\n   return true;\n}\n\nbool\nsampleCafeVoices(std::vector<CafeVoice> &voiceInfos)\n{\n   auto voices = cafe::sndcore2::internal::getAcquiredVoices();\n   voiceInfos.resize(voices.size());\n\n   for (auto i = 0u; i < voices.size(); ++i) {\n      auto voice = voices[i];\n      auto extras = cafe::sndcore2::internal::getVoiceExtras(voice->index);\n\n      auto &voiceInfo = voiceInfos[i];\n      voiceInfo.index = voice->index;\n      voiceInfo.state = static_cast<CafeVoice::State>(voice->state);\n      voiceInfo.format = static_cast<CafeVoice::Format>(voice->offsets.dataType);\n      voiceInfo.type = static_cast<CafeVoice::VoiceType>(extras->type);\n      voiceInfo.data = static_cast<VirtualAddress>(virt_cast<virt_addr>(voice->offsets.data));\n      voiceInfo.currentOffset = static_cast<int>(voice->offsets.currentOffset);\n      voiceInfo.loopOffset = static_cast<int>(voice->offsets.loopOffset);\n      voiceInfo.endOffset = static_cast<int>(voice->offsets.endOffset);\n      voiceInfo.loopingEnabled = (voice->offsets.loopingEnabled != cafe::sndcore2::AXVoiceLoop::Disabled);\n   }\n\n   return true;\n}\n\n} // namespace decaf::debug\n"
  },
  {
    "path": "src/libdecaf/src/debug_api/debug_api_controller.cpp",
    "content": "#include \"decaf_debug_api.h\"\n\n#include \"debug_api_controller.h\"\n#include \"debugger/debugger.h\"\n#include \"decaf_config.h\"\n\n#include <array>\n#include <atomic>\n#include <condition_variable>\n#include <libcpu/state.h>\n#include <libcpu/mem.h>\n#include <libcpu/cpu_control.h>\n#include <libcpu/cpu_breakpoints.h>\n#include <libcpu/espresso/espresso_disassembler.h>\n#include <libcpu/espresso/espresso_instructionset.h>\n#include <mutex>\n\nnamespace decaf::debug\n{\n\nstruct Controller\n{\n   bool enabled = false;\n\n   //! Whether we are currently paused.\n   std::atomic_bool isPaused;\n\n   //! Used to synchronise cores across a pause.\n   std::mutex pauseMutex;\n\n   //! Used to synchronise cores across a pause.\n   std::condition_variable pauseReleaseCond;\n\n   //! The context running on each core at the time of a pause.\n   std::array<cpu::Core *, 3> pausedContexts;\n\n   //! Which core initiated the pause by sending a DbgBreak interrupt.\n   int pauseInitiator = -1;\n\n   //! Which cores are trying to pause.\n   std::atomic<unsigned> coresPausing;\n\n   //! Which cores are trying to resume.\n   std::atomic<unsigned> coresResuming;\n\n   //! Public copy of pausedContexts\n   std::array<CpuContext, 3> contexts;\n\n   bool entryPointFound = false;\n\n   //! Callback to call on debug interrupt\n   PauseCallback callback;\n} sController;\n\nstatic bool\ncopyPauseContext(int core)\n{\n   auto pauseContext = sController.pausedContexts[core];\n   if (!pauseContext) {\n      return false;\n   }\n\n   auto &context = sController.contexts[core];\n   context.cia = pauseContext->cia;\n   context.nia = pauseContext->nia;\n\n   for (auto i = 0u; i < 32; ++i) {\n      context.gpr[i] = pauseContext->gpr[i];\n      context.fpr[i] = pauseContext->fpr[i].paired0;\n      context.ps1[i] = pauseContext->fpr[i].paired1;\n   }\n\n   context.cr = pauseContext->cr.value;\n   context.xer = pauseContext->xer.value;\n   context.lr = pauseContext->lr;\n   context.ctr = pauseContext->ctr;\n   context.fpscr = pauseContext->fpscr.value;\n   context.pvr = pauseContext->pvr.value;\n   context.msr = pauseContext->msr.value;\n\n   for (auto i = 0u; i < 16; ++i) {\n      context.sr[i] = pauseContext->sr[i];\n   }\n\n   for (auto i = 0u; i < 8; ++i) {\n      context.gqr[i] = pauseContext->gqr[i].value;\n   }\n\n   context.dar = pauseContext->dar;\n   context.dsisr = pauseContext->dsisr;\n   context.srr0 = pauseContext->srr0;\n\n   // If we are inside a kernel call we need to adjust gpr[1] due to how we\n   // use lazy stack frame creation\n   auto instr = mem::read<espresso::Instruction>(pauseContext->nia - 4);\n   auto data = espresso::decodeInstruction(instr);\n   if (data && data->id == espresso::InstructionID::kc) {\n      context.gpr[1] = pauseContext->systemCallStackHead;\n   }\n\n   return true;\n}\n\nbool\nready()\n{\n   return sController.entryPointFound;\n}\n\nvoid\nsetPauseCallback(PauseCallback callback)\n{\n   sController.callback = callback;\n}\n\nbool\npause()\n{\n   for (auto i = 0; i < 3; ++i) {\n      cpu::interrupt(i, cpu::DBGBREAK_INTERRUPT);\n   }\n\n   return true;\n}\n\nbool\nresume()\n{\n   if (sController.isPaused.exchange(false)) {\n      sController.pausedContexts.fill(nullptr);\n      sController.pauseReleaseCond.notify_all();\n   }\n\n   return true;\n}\n\nbool\nisPaused()\n{\n   return sController.isPaused.load();\n}\n\nint\ngetPauseInitiatorCoreId()\n{\n   return sController.pauseInitiator;\n}\n\nconst CpuContext *\ngetPausedContext(int core)\n{\n   if (!isPaused() || core < 0 || core > 2) {\n      return nullptr;\n   }\n\n   return &sController.contexts[core];\n}\n\nstatic uint32_t\ncalculateNextInstr(const cpu::CoreRegs *state, bool stepOver)\n{\n   auto instr = mem::read<espresso::Instruction>(state->nia);\n   auto data = espresso::decodeInstruction(instr);\n\n   if (data && espresso::isBranchInstruction(data->id)) {\n      auto branchInfo =\n         espresso::disassembleBranchInfo(data->id, instr, state->nia,\n                                         state->ctr, state->cr.value,\n                                         state->lr);\n\n      if (branchInfo.isCall && stepOver) {\n         // This is a call and we are stepping over...\n         return state->nia + 4;\n      }\n\n      if (branchInfo.conditionSatisfied) {\n         return branchInfo.target;\n      } else {\n         return state->nia + 4;\n      }\n   } else {\n      // This is not a branch instruction\n      return state->nia + 4;\n   }\n}\n\nbool\nstepInto(int core)\n{\n   if (core < 0 || core > 2) {\n      return false;\n   }\n\n   auto next = calculateNextInstr(sController.pausedContexts[core], false);\n   cpu::addBreakpoint(next, cpu::Breakpoint::SingleFire);\n   resume();\n   return true;\n}\n\nbool\nstepOver(int core)\n{\n   if (core < 0 || core > 2) {\n      return false;\n   }\n\n   auto next = calculateNextInstr(sController.pausedContexts[core], true);\n   cpu::addBreakpoint(next, cpu::Breakpoint::SingleFire);\n   resume();\n   return true;\n}\n\nbool\nhasBreakpoint(VirtualAddress address)\n{\n   return cpu::hasBreakpoint(address);\n}\n\nbool\naddBreakpoint(VirtualAddress address)\n{\n   if (!cpu::isValidAddress(static_cast<cpu::VirtualAddress>(address))) {\n      return false;\n   }\n\n   cpu::addBreakpoint(address, cpu::Breakpoint::MultiFire);\n   return true;\n}\n\nbool\nremoveBreakpoint(VirtualAddress address)\n{\n   if (!cpu::isValidAddress(static_cast<cpu::VirtualAddress>(address))) {\n      return false;\n   }\n\n   cpu::removeBreakpoint(address);\n   return true;\n}\n\nvoid\nhandleDebugBreakInterrupt()\n{\n   static constexpr unsigned NoCores = 0;\n   static constexpr unsigned AllCores = (1 << 0) | (1 << 1) | (1 << 2);\n\n   std::unique_lock<std::mutex> lock { sController.pauseMutex };\n   auto coreId = cpu::this_core::id();\n   sController.pausedContexts[coreId] = cpu::this_core::state();\n   copyPauseContext(coreId);\n\n   // Check to see if we were the last core to join on the fun\n   auto coreBit = 1 << coreId;\n   auto coresPausing = sController.coresPausing.fetch_or(coreBit);\n\n   if (coresPausing == NoCores) {\n      // This is the first core to hit a breakpoint\n      sController.pauseInitiator = coreId;\n\n      // Signal the rest of the cores to stop\n      for (auto i = 0; i < 3; ++i) {\n         cpu::interrupt(i, cpu::DBGBREAK_INTERRUPT);\n      }\n   }\n\n   if ((coresPausing | coreBit) == AllCores) {\n      // All cores are now paused!\n      sController.isPaused.store(true);\n      sController.coresPausing.store(0);\n      sController.coresResuming.store(0);\n   }\n\n   // Call the pause callback\n   if (sController.callback) {\n      sController.callback();\n   }\n\n   // Spin around the release condition while we are paused\n   while (sController.coresPausing.load() || sController.isPaused.load()) {\n      sController.pauseReleaseCond.wait(lock);\n   }\n\n   // Clear any additional debug interrupts that occured\n   cpu::this_core::clearInterrupt(cpu::DBGBREAK_INTERRUPT);\n\n   if ((sController.coresResuming.fetch_or(coreBit) | coreBit) == AllCores) {\n      // This is the final core to resume, wake up the other cores\n      sController.pauseReleaseCond.notify_all();\n   } else {\n      // Wait until all cores are ready to resume\n      while ((sController.coresResuming.load() | coreBit) != AllCores) {\n         sController.pauseReleaseCond.wait(lock);\n      }\n   }\n}\n\n\nvoid\nnotifyEntry(uint32_t preinit, uint32_t entry)\n{\n   if (config()->debugger.break_on_entry) {\n      if (preinit) {\n         addBreakpoint(preinit);\n      }\n\n      if (entry) {\n         addBreakpoint(entry);\n      }\n   }\n\n   sController.entryPointFound = true;\n}\n\n} // namespace decaf::debug\n"
  },
  {
    "path": "src/libdecaf/src/debug_api/debug_api_controller.h",
    "content": "#pragma once\n\nnamespace decaf::debug\n{\n\nvoid handleDebugBreakInterrupt();\nvoid notifyEntry(uint32_t preinit, uint32_t entry);\n\n} // namespace decaf::debug\n"
  },
  {
    "path": "src/libdecaf/src/debug_api/debug_api_cpu.cpp",
    "content": "#include \"decaf_debug_api.h\"\n\n#include <libcpu/cpu_breakpoints.h>\n\nnamespace decaf::debug\n{\n\nvoid\nsampleCpuBreakpoints(std::vector<CpuBreakpoint> &breakpoints)\n{\n   breakpoints.clear();\n\n   if (auto list = cpu::getBreakpoints()) {\n      for (auto &bp : *list) {\n         breakpoints.push_back({\n            bp.type == bp.SingleFire ? CpuBreakpoint::SingleFire : CpuBreakpoint::MultiFire,\n            VirtualAddress { bp.address },\n            bp.savedCode,\n         });\n      }\n   }\n}\n\n} // namespace decaf::debug\n"
  },
  {
    "path": "src/libdecaf/src/debug_api/debug_api_memory.cpp",
    "content": "#include \"decaf_debug_api.h\"\n\n#include <libcpu/mem.h>\n\nnamespace decaf::debug\n{\n\nbool\nisValidVirtualAddress(VirtualAddress address)\n{\n   return cpu::isValidAddress(cpu::VirtualAddress { address });\n}\n\nsize_t\ngetMemoryPageSize()\n{\n   return cpu::PageSize;\n}\n\nsize_t\nreadMemory(VirtualAddress address, void *dst, size_t size)\n{\n   auto out = reinterpret_cast<uint8_t *>(dst);\n   constexpr auto pageMask = ~(cpu::PageSize - 1);\n   auto bytesRemaining = size;\n\n   // Copy bytes, checking validity of each memory page as we cross it\n   while (bytesRemaining > 0) {\n      auto currentPage = address & pageMask;\n      if (!cpu::isValidAddress(cpu::VirtualAddress { currentPage })) {\n         break;\n      }\n\n      auto nextPage = currentPage + cpu::PageSize;\n      auto readBytes = std::min<size_t>(bytesRemaining, nextPage - address);\n\n      std::memcpy(out, mem::translate<uint8_t>(address), readBytes);\n      address += static_cast<uint32_t>(readBytes);\n      out += readBytes;\n      bytesRemaining -= readBytes;\n   }\n\n   return size - bytesRemaining;\n}\n\nsize_t\nwriteMemory(VirtualAddress address, const void *src, size_t size)\n{\n   auto in = reinterpret_cast<const uint8_t *>(src);\n   constexpr auto pageMask = ~(cpu::PageSize - 1);\n   auto bytesRemaining = size;\n\n   // Copy bytes, checking validity of each memory page as we cross it\n   while (bytesRemaining > 0) {\n      auto currentPage = address & pageMask;\n      if (!cpu::isValidAddress(cpu::VirtualAddress{ currentPage })) {\n         break;\n      }\n\n      auto nextPage = currentPage + cpu::PageSize;\n      auto readBytes = std::min<size_t>(bytesRemaining, nextPage - address);\n\n      std::memcpy(mem::translate<uint8_t>(address), in, readBytes);\n      address += static_cast<uint32_t>(readBytes);\n      in += readBytes;\n      bytesRemaining -= readBytes;\n   }\n\n   return size - bytesRemaining;\n}\n\n} // namespace decaf::debug\n"
  },
  {
    "path": "src/libdecaf/src/debug_api/debug_api_pm4.cpp",
    "content": "#include \"decaf_debug_api.h\"\n\n#include \"cafe/libraries/gx2/gx2_internal_pm4cap.h\"\n\nnamespace decaf::debug\n{\n\nPm4CaptureState\npm4CaptureState()\n{\n   switch (cafe::gx2::internal::captureState()) {\n   case cafe::gx2::internal::CaptureState::Disabled:\n      return Pm4CaptureState::Disabled;\n   case cafe::gx2::internal::CaptureState::Enabled:\n      return Pm4CaptureState::Enabled;\n   case cafe::gx2::internal::CaptureState::WaitEndNextFrame:\n      return Pm4CaptureState::WaitEndNextFrame;\n   case cafe::gx2::internal::CaptureState::WaitStartNextFrame:\n      return Pm4CaptureState::WaitStartNextFrame;\n   default:\n      return Pm4CaptureState::Disabled;\n   }\n}\n\nbool\npm4CaptureNextFrame()\n{\n   if (cafe::gx2::internal::captureState() != cafe::gx2::internal::CaptureState::Disabled) {\n      return false;\n   }\n\n   cafe::gx2::internal::captureNextFrame();\n   return true;\n}\n\nbool\npm4CaptureBegin()\n{\n   if (cafe::gx2::internal::captureState() != cafe::gx2::internal::CaptureState::Disabled) {\n      return false;\n   }\n\n   cafe::gx2::internal::captureStartAtNextSwap();\n   return true;\n}\n\nbool\npm4CaptureEnd()\n{\n   if (cafe::gx2::internal::captureState() != cafe::gx2::internal::CaptureState::Enabled) {\n      return false;\n   }\n\n   cafe::gx2::internal::captureStopAtNextSwap();\n   return true;\n}\n\n} // namespace decaf::debug\n"
  },
  {
    "path": "src/libdecaf/src/debugger/debugger.cpp",
    "content": "#include \"debugger.h\"\n#include \"debugger_server_gdb.h\"\n#include \"decaf.h\"\n#include \"decaf_config.h\"\n#include \"decaf_debug_api.h\"\n\nnamespace debugger\n{\n\nstatic GdbServer\nsGdbServer { };\n\nvoid\ninitialise()\n{\n   if (decaf::config()->debugger.gdb_stub) {\n      sGdbServer.start(decaf::config()->debugger.gdb_stub_port);\n   }\n}\n\nvoid\nshutdown()\n{\n   // Force resume any paused cores.\n   ::decaf::debug::resume();\n}\n\nvoid\ndraw(unsigned width, unsigned height)\n{\n   sGdbServer.process();\n}\n\n} // namespace debugger\n"
  },
  {
    "path": "src/libdecaf/src/debugger/debugger.h",
    "content": "#pragma once\n#include <cstdint>\n#include <string>\n\nnamespace debugger\n{\n\nvoid\ninitialise();\n\nvoid\nshutdown();\n\nvoid\ndraw(unsigned width, unsigned height);\n\n} // namespace debugger\n"
  },
  {
    "path": "src/libdecaf/src/debugger/debugger_server.h",
    "content": "#pragma once\n\nnamespace debugger\n{\n\nclass DebuggerServer\n{\npublic:\n   virtual ~DebuggerServer() = default;\n\n   virtual bool start(int port) = 0;\n   virtual void process() = 0;\n};\n\n} // namespace debugger\n"
  },
  {
    "path": "src/libdecaf/src/debugger/debugger_server_gdb.cpp",
    "content": "#include \"debugger_server_gdb.h\"\n#include \"decaf_config.h\"\n#include \"decaf_debug_api.h\"\n#include \"decaf_log.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_scheduler.h\"\n#include \"cafe/libraries/coreinit/coreinit_thread.h\"\n\n#include <algorithm>\n#include <common/platform_socket.h>\n#include <common/strutils.h>\n#include <common/log.h>\n#include <fmt/core.h>\n#include <iterator>\n#include <libcpu/cpu_formatters.h>\n#include <libcpu/mem.h>\n#include <numeric>\n\nnamespace debugger\n{\n\n#include \"debugger_server_gdb_xml.inl\"\n\nenum GdbCommand\n{\n   Ack = '+',\n   Nack = '-',\n   Break = 0x03,\n   Start = '$',\n   End = '#',\n   Query = 'q',\n   EnableExtendedMode = '!',\n   GetHaltReason = '?',\n   ReadRegister = 'p',\n   ReadGeneralRegisters = 'g',\n   ReadMemory = 'm',\n   AddBreakpoint = 'Z',\n   RemoveBreakpoint = 'z',\n   VCommand = 'v',\n   SetActiveThread = 'H',\n};\n\nenum RegisterID\n{\n   PC = 64,\n   MSR = 65,\n   CR = 66,\n   LR = 67,\n   CTR = 68,\n   XER = 69,\n};\n\nenum BreakpointType\n{\n   Execute = 1,\n};\n\nbool GdbServer::start(int port)\n{\n   if (!mLog) {\n      mLog = decaf::makeLogger(\"gdb\");\n   }\n\n   mListenSocket = socket(PF_INET, SOCK_STREAM, 0);\n\n   if (mListenSocket < 0) {\n      mLog->error(\"Failed to create socket\");\n      return false;\n   }\n\n   // Set socket to SO_REUSEADDR so it can always bind on the same port\n   auto sockOpt = 1;\n\n   if (setsockopt(mListenSocket, SOL_SOCKET, SO_REUSEADDR,\n                  reinterpret_cast<const char *>(&sockOpt),\n                  sizeof(sockOpt)) < 0) {\n      mLog->error(\"Failed to set SO_REUSEADDR on socket\");\n      closeServer();\n      return false;\n   }\n\n   auto bindAddress = sockaddr_in { 0 };\n   bindAddress.sin_family = AF_INET;\n   bindAddress.sin_port = htons(static_cast<uint16_t>(port));\n   bindAddress.sin_addr.s_addr = INADDR_ANY;\n\n   if (bind(mListenSocket,\n            reinterpret_cast<const sockaddr*>(&bindAddress),\n            sizeof(sockaddr_in)) < 0) {\n      mLog->error(\"Failed to bind on socket\");\n      closeServer();\n      return false;\n   }\n\n   if (listen(mListenSocket, 1) < 0) {\n      mLog->error(\"Failed to listen on socket\");\n      closeServer();\n      return false;\n   }\n\n   platform::socketSetBlocking(mListenSocket, false);\n   return true;\n}\n\nvoid GdbServer::closeServer()\n{\n   if (mListenSocket != InvalidSocket) {\n      platform::socketClose(mListenSocket);\n      mListenSocket = InvalidSocket;\n   }\n}\n\nvoid GdbServer::closeClient()\n{\n   if (mClientSocket != InvalidSocket) {\n      platform::socketClose(mClientSocket);\n      mClientSocket = InvalidSocket;\n   }\n}\n\nvoid GdbServer::handleBreak()\n{\n   mWasPaused = false;\n   decaf::debug::pause();\n}\n\nstatic std::string\nencodeXml(const std::string &src)\n{\n   auto result = std::string { };\n   result.reserve(src.size());\n\n   for (auto i = 0u; i < src.size(); ++i) {\n      auto c = src[i];\n\n      if (c == '#' || c == '$' || c == '*' || c == '}') {\n         result.push_back('}');\n         result.push_back(c ^ 0x20);\n      } else if (c == '\\n' || c == '\\r') {\n         continue;\n      } else {\n         result.push_back(c);\n      }\n   }\n\n   return result;\n}\n\nvoid GdbServer::handleQuery(const std::string &command)\n{\n   if (begins_with(command, \"qSupported\")) {\n      auto features = std::string { };\n      features += \"PacketSize=4096\";\n      features += \";qXfer:features:read+\";\n      features += \";qXfer:threads:read+\";\n      // TODO: features += \";qXfer:libraries:read+\";\n      // TODO: features += \";qXfer:memory-map:read+\";\n      // TODO: features += \";QStartNoAckMode+\";\n      // TODO: features += \";QThreadEvents+\";\n      sendCommand(features);\n   } else if (begins_with(command, \"qfThreadInfo\")) {\n      fmt::memory_buffer reply;\n      cafe::coreinit::internal::lockScheduler();\n      auto firstThread = cafe::coreinit::internal::getFirstActiveThread();\n\n      if (firstThread) {\n         reply.push_back('m');\n      } else {\n         reply.push_back('l');\n      }\n\n      for (auto thread = firstThread; thread; thread = thread->activeLink.next) {\n         if (thread != firstThread) {\n            reply.push_back(',');\n         }\n\n         fmt::format_to(std::back_inserter(reply), \"{:04X}\", thread->id.value());\n      }\n\n      cafe::coreinit::internal::unlockScheduler();\n      sendCommand(to_string(reply));\n   } else if (begins_with(command, \"qAttached\")) {\n      sendCommand(\"1\");\n   } else if (begins_with(command, \"qsThreadInfo\")) {\n      sendCommand(\"l\");\n   } else if (begins_with(command, \"qC\")) {\n      if (mCurrentThread.id != -1) {\n         auto reply = fmt::format(\"QC{:04X}\", mCurrentThread.id);\n         sendCommand(reply);\n      } else {\n         auto initiator = decaf::debug::getPauseInitiatorCoreId();\n         if (decaf::debug::sampleCafeRunningThread(initiator, mCurrentThread)) {\n            auto reply = fmt::format(\"QC{:04X}\", mCurrentThread.id);\n            sendCommand(reply);\n         } else {\n            sendCommand(\"\");\n         }\n      }\n   } else if (begins_with(command, \"qXfer:features:read:\")) {\n      std::vector<std::string> split;\n      split_string(command, ':', split);\n\n      auto xmlName = split[3];\n      auto xml = std::string { };\n\n      if (split[3] == \"target.xml\") {\n         xml = encodeXml(sGdbTargetXML);\n      } else if (split[3] == \"power-core.xml\") {\n         xml = encodeXml(sGdbPowerCoreXml);\n      }\n\n      auto args = split[4];\n      split.clear();\n      split_string(args, ',', split);\n\n      auto offset = std::stoul(split[0], 0, 16);\n      auto size = std::stoul(split[1], 0, 16);\n      auto reply = std::string { };\n\n      if (offset + size < xml.size()) {\n         reply += \"m\";\n         reply += std::string { xml.data() + offset, size };\n      } else {\n         reply += \"l\";\n         reply += std::string { xml.data() + offset, xml.size() - offset };\n      }\n\n      sendCommand(reply);\n   } else if (begins_with(command, \"qXfer:threads:read:\")) {\n      fmt::memory_buffer reply;\n      fmt::format_to(std::back_inserter(reply), \"l<?xml version=\\\"1.0\\\"?>\");\n      fmt::format_to(std::back_inserter(reply), \"<threads>\");\n\n      cafe::coreinit::internal::lockScheduler();\n      auto firstThread = cafe::coreinit::internal::getFirstActiveThread();\n\n      for (auto thread = firstThread; thread; thread = thread->activeLink.next) {\n         fmt::format_to(std::back_inserter(reply), \"<thread id=\\\"{}\\\" core=\\\"0\\\"\", thread->id);\n\n         if (thread->name) {\n            fmt::format_to(std::back_inserter(reply), \" name=\\\"{}\\\"\", encodeXml(thread->name.get()));\n         }\n\n         fmt::format_to(std::back_inserter(reply), \"></thread>\");\n      }\n\n      fmt::format_to(std::back_inserter(reply), \"</threads>\");\n      cafe::coreinit::internal::unlockScheduler();\n\n      sendCommand(std::string_view { reply.data(), reply.size() });\n   } else if (begins_with(command, \"qTStatus\")) {\n      // Trace not supported\n      sendCommand(\"\");\n   } else {\n      mLog->warn(\"Unknown query command {}\", command);\n      sendCommand(\"\");\n   }\n}\n\nvoid GdbServer::handleEnableExtendedMode(const std::string &command)\n{\n   sendCommand(\"\");\n}\n\nvoid GdbServer::handleGetHaltReason(const std::string &command)\n{\n   sendCommand(\"T05\");\n}\n\nvoid GdbServer::handleReadRegister(const std::string &command)\n{\n   auto id = std::stoul(command.substr(1), 0, 16);\n   auto value = uint32_t { 0 };\n\n   if (mCurrentThread.handle) {\n      if (id < 32) {\n         value = mCurrentThread.gpr[id];\n      } else {\n         switch (id) {\n         case RegisterID::PC:\n            value = mCurrentThread.nia;\n            break;\n         case RegisterID::MSR:\n            value = mCurrentThread.msr;\n            break;\n         case RegisterID::CR:\n            value = mCurrentThread.cr;\n            break;\n         case RegisterID::LR:\n            value = mCurrentThread.lr;\n            break;\n         case RegisterID::CTR:\n            value = mCurrentThread.ctr;\n            break;\n         case RegisterID::XER:\n            value = mCurrentThread.xer;\n            break;\n         }\n      }\n   }\n\n   sendCommand(fmt::format(\"{:08X}\", value));\n}\n\nvoid GdbServer::handleReadGeneralRegisters(const std::string &command)\n{\n   fmt::memory_buffer reply;\n\n   for (auto i = 0; i < 32; ++i) {\n      auto value = uint32_t { 0 };\n      if (mCurrentThread.handle) {\n         value = mCurrentThread.gpr[i];\n      }\n      fmt::format_to(std::back_inserter(reply), \"{:08X}\", value);\n   }\n\n   sendCommand(to_string(reply));\n}\n\nvoid GdbServer::handleReadMemory(const std::string &command)\n{\n   fmt::memory_buffer reply;\n   std::vector<std::string> split;\n   split_string(command.data() + 1, ',', split);\n\n   auto address = std::stoul(split[0], 0, 16);\n   auto size = std::stoul(split[1], 0, 16);\n\n   for (auto i = 0u; i < size; ++i) {\n      auto value = uint8_t { 0 };\n\n      if (cpu::isValidAddress(cpu::VirtualAddress { static_cast<uint32_t>(address + i) })) {\n         value = mem::read<uint8_t>(address + i);\n      }\n\n      fmt::format_to(std::back_inserter(reply), \"{:02X}\", value);\n   }\n\n   sendCommand(to_string(reply));\n}\n\nvoid GdbServer::handleAddBreakpoint(const std::string &command)\n{\n   std::vector<std::string> split;\n   split_string(command.data() + 1, ',', split);\n\n   auto type = std::stoul(split[0], 0, 16);\n   auto address = std::stoul(split[1], 0, 16);\n   // auto length = std::stoul(split[2], 0, 16);\n\n   if (type != BreakpointType::Execute) {\n      sendCommand(\"E02\");\n   } else {\n      decaf::debug::addBreakpoint(address);\n      sendCommand(\"OK\");\n   }\n}\n\nvoid GdbServer::handleRemoveBreakpoint(const std::string &command)\n{\n   std::vector<std::string> split;\n   split_string(command.data() + 1, ',', split);\n\n   auto type = std::stoul(split[0], 0, 16);\n   auto address = std::stoul(split[1], 0, 16);\n   // auto length = std::stoul(split[2], 0, 16);\n\n   if (type != BreakpointType::Execute) {\n      sendCommand(\"E02\");\n   } else {\n      decaf::debug::removeBreakpoint(address);\n      sendCommand(\"OK\");\n   }\n}\n\nvoid GdbServer::handleSetActiveThread(const std::string &command)\n{\n   // auto type = command[1];\n   auto id = std::stoi(command.data() + 2, 0, 16);\n\n   // Find thread by id\n   std::vector<decaf::debug::CafeThread> threads;\n   mCurrentThread.handle = 0;\n   mCurrentThread.id = -1;\n\n   if (decaf::debug::sampleCafeThreads(threads)) {\n      for (auto &thread : threads) {\n         if (thread.id == id) {\n            mCurrentThread = thread;\n            break;\n         }\n      }\n   }\n\n   if (id == -1 || id == 0) {\n      sendCommand(\"OK\");\n   } else if (mCurrentThread.id == id) {\n      sendCommand(\"OK\");\n   } else {\n      sendCommand(\"E22\");\n   }\n}\n\nvoid GdbServer::handleVContQuery(const std::vector<std::string> &command)\n{\n   sendCommand(\"vCont;c;C;s;S\");\n}\n\nvoid GdbServer::handleVCont(const std::vector<std::string> &command)\n{\n   std::vector<std::string> split;\n   split_string(command[1], ':', split);\n\n   if (split[0] == \"c\") {\n      mWasPaused = false;\n      decaf::debug::resume();\n   } else if (split[0] == \"s\") {\n      auto threadId = mCurrentThread.id;\n\n      if (split.size() > 1) {\n         threadId = std::stoi(split[1], 0, 16);\n      }\n\n      for (auto i = 0; i < 3; ++i) {\n         auto thread = decaf::debug::CafeThread { };\n         if (decaf::debug::sampleCafeRunningThread(i, thread)) {\n            if (thread.id == threadId) {\n               decaf::debug::stepInto(i);\n               return;\n            }\n         }\n      }\n   }\n}\n\nvoid GdbServer::handleCommand(const std::string &command)\n{\n   switch (command[0]) {\n   case GdbCommand::Query:\n      handleQuery(command);\n      break;\n   case GdbCommand::EnableExtendedMode:\n      handleEnableExtendedMode(command);\n      break;\n   case GdbCommand::GetHaltReason:\n      handleGetHaltReason(command);\n      break;\n   case GdbCommand::ReadRegister:\n      handleReadRegister(command);\n      break;\n   case GdbCommand::ReadGeneralRegisters:\n      handleReadGeneralRegisters(command);\n      break;\n   case GdbCommand::ReadMemory:\n      handleReadMemory(command);\n      break;\n   case GdbCommand::AddBreakpoint:\n      handleAddBreakpoint(command);\n      break;\n   case GdbCommand::RemoveBreakpoint:\n      handleRemoveBreakpoint(command);\n      break;\n   case GdbCommand::VCommand:\n   {\n      std::vector<std::string> vCommand;\n      split_string(command, ';', vCommand);\n\n      if (vCommand[0] == \"vCont\") {\n         handleVCont(vCommand);\n      } else if (vCommand[0] == \"vCont?\") {\n         handleVContQuery(vCommand);\n      } else if (vCommand[0] == \"vMustReplyEmpty\") {\n         sendCommand(\"\");\n      } else {\n         mLog->warn(\"Unknown vCommand {}\", command);\n         sendCommand(\"\");\n      }\n      break;\n   }\n   case GdbCommand::SetActiveThread:\n      handleSetActiveThread(command);\n      break;\n   default:\n      mLog->warn(\"Unknown command {}\", command);\n      sendCommand(\"\");\n   }\n}\n\ntemplate<class InputIt>\nstatic uint8_t\ncalculateChecksum(InputIt first, InputIt last)\n{\n   return static_cast<uint8_t>(std::accumulate(first, last, 0, std::plus<int>()));\n}\n\nvoid GdbServer::sendAck()\n{\n   auto ack = char { GdbCommand::Ack };\n   auto result = send(mClientSocket, &ack, 1, 0);\n\n   if (result < 0) {\n      mLog->error(\"Error sending ack\");\n   }\n}\n\nvoid GdbServer::sendNack()\n{\n   auto ack = char { GdbCommand::Nack };\n   auto result = send(mClientSocket, &ack, 1, 0);\n\n   if (result < 0) {\n      mLog->error(\"Error sending nack\");\n   }\n}\n\nvoid GdbServer::sendCommand(std::string_view command)\n{\n   auto packet = std::string { };\n   packet.push_back(GdbCommand::Start);\n   packet.append(command);\n   packet.push_back(GdbCommand::End);\n\n   auto checksum = calculateChecksum(command.begin(), command.end());\n   packet.append(fmt::format(\"{:02X}\", checksum));\n\n   auto bytesSent = 0;\n\n   while (bytesSent < packet.size()) {\n      auto result = send(mClientSocket,\n                         packet.data() + bytesSent,\n                         static_cast<int>(packet.size() - bytesSent),\n                         0);\n\n      if (result < 0) {\n         mLog->error(\"Error sending command\");\n         break;\n      }\n\n      bytesSent += result;\n   }\n\n   mLastCommand = command;\n}\n\nvoid GdbServer::process()\n{\n   if (mListenSocket == InvalidSocket && mClientSocket == InvalidSocket) {\n      return;\n   }\n\n   fd_set readfds;\n   auto nfds = 0;\n   auto tv = timeval { 0, 0 };\n   FD_ZERO(&readfds);\n\n   if (mListenSocket != InvalidSocket) {\n      FD_SET(mListenSocket, &readfds);\n      nfds = std::max(nfds, static_cast<int>(mListenSocket));\n   }\n\n   if (mClientSocket != InvalidSocket) {\n      FD_SET(mClientSocket, &readfds);\n      nfds = std::max(nfds, static_cast<int>(mClientSocket));\n   }\n\n   select(nfds + 1, &readfds, NULL, NULL, &tv);\n\n   if (mListenSocket != InvalidSocket && FD_ISSET(mListenSocket, &readfds)) {\n      auto clientAddress = sockaddr_in { 0 };\n      auto clientAddressLen = socklen_t { sizeof(sockaddr_in) };\n      auto clientSocket = accept(mListenSocket, reinterpret_cast<sockaddr*>(&clientAddress), &clientAddressLen);\n\n      if (clientSocket < 0) {\n         mLog->error(\"Failed to accept on socket\");\n      } else if (mClientSocket != InvalidSocket) {\n         mLog->error(\"Rejecting connection because we already have a client connected.\");\n         platform::socketClose(clientSocket);\n      } else {\n         mClientSocket = clientSocket;\n      }\n   } else if (mClientSocket != InvalidSocket && FD_ISSET(mClientSocket, &readfds)) {\n      while (mClientSocket != InvalidSocket) {\n         char byte = 0;\n         auto result = recv(mClientSocket, &byte, 1, 0);\n\n         if (result < 0) {\n            if (platform::socketWouldBlock(result)) {\n               break;\n            } else {\n               mLog->debug(\"Client disconnected, recv returned {}\", result);\n               closeClient();\n            }\n         } else if (result == 0) {\n            mLog->debug(\"Client disconnected gracefully\");\n            closeClient();\n         } else {\n            if (mReadState == ReadState::ReadStart) {\n               if (byte == GdbCommand::Ack) {\n                  continue;\n               } else if (byte == GdbCommand::Nack) {\n                  sendCommand(mLastCommand);\n                  continue;\n               } else if (byte == GdbCommand::Break) {\n                  handleBreak();\n               } else if (byte != GdbCommand::Start) {\n                  mLog->error(\"Unexpected start of packet {}\", byte);\n                  closeClient();\n               } else {\n                  mReadState = ReadState::ReadCommand;\n               }\n            } else if (mReadState == ReadState::ReadCommand) {\n               if (byte == GdbCommand::End) {\n                  mReadState = ReadState::ReadChecksum;\n               } else {\n                  mReadBuffer.push_back(byte);\n               }\n            } else if (mReadState == ReadState::ReadChecksum) {\n               mChecksumBuffer.push_back(byte);\n\n               if (mChecksumBuffer.size() == 2) {\n                  auto readChecksum = std::stoi(mChecksumBuffer, nullptr, 16);\n                  auto calculatedChecksum = calculateChecksum(mReadBuffer.begin(), mReadBuffer.end());\n\n                  if (readChecksum != calculatedChecksum) {\n                     mLog->error(\"Unexpected command checksum {} != {}\", readChecksum, calculatedChecksum);\n                     sendNack();\n                  } else {\n                     sendAck();\n                     handleCommand(mReadBuffer);\n                  }\n\n                  mReadState = ReadState::ReadStart;\n                  mReadBuffer.clear();\n                  mChecksumBuffer.clear();\n               }\n            }\n         }\n      }\n   }\n\n   // Check for a transition in paused state.\n   mPaused = decaf::debug::isPaused();\n   if (mPaused) {\n      auto initiator = decaf::debug::getPauseInitiatorCoreId();\n      auto context = decaf::debug::getPausedContext(initiator);\n      if (context->nia != mPausedNia) {\n         mWasPaused = false;\n      }\n\n      if (!mWasPaused) {\n         if (mClientSocket != InvalidSocket) {\n            sendCommand(\"T05\");\n         }\n\n         mPausedNia = context->nia;\n         mWasPaused = true;\n      }\n   }\n}\n\n} // namespace debugger\n"
  },
  {
    "path": "src/libdecaf/src/debugger/debugger_server_gdb.h",
    "content": "#pragma once\n#include \"debugger_server.h\"\n#include \"decaf_debug_api.h\"\n\n#include <common/platform.h>\n#include <common/platform_socket.h>\n#include <string>\n#include <string_view>\n#include <vector>\n#include <spdlog/spdlog.h>\n\nnamespace debugger\n{\n\nclass GdbServer : public DebuggerServer\n{\n   static constexpr platform::Socket InvalidSocket = static_cast<platform::Socket>(-1);\n\n   enum class ReadState\n   {\n      ReadStart,\n      ReadCommand,\n      ReadChecksum\n   };\n\npublic:\n   virtual ~GdbServer() = default;\n\n   virtual bool start(int port) override;\n   virtual void process() override;\n\n   void closeServer();\n   void closeClient();\n\n   void sendAck();\n   void sendNack();\n   void sendCommand(std::string_view command);\n\n   void handleCommand(const std::string &command);\n   void handleBreak();\n   void handleQuery(const std::string &command);\n   void handleEnableExtendedMode(const std::string &command);\n   void handleGetHaltReason(const std::string &command);\n   void handleReadRegister(const std::string &command);\n   void handleReadGeneralRegisters(const std::string &command);\n   void handleReadMemory(const std::string &command);\n   void handleAddBreakpoint(const std::string &command);\n   void handleRemoveBreakpoint(const std::string &command);\n   void handleVCont(const std::vector<std::string> &command);\n   void handleVContQuery(const std::vector<std::string> &command);\n   void handleSetActiveThread(const std::string &command);\n\nprivate:\n   std::shared_ptr<spdlog::logger> mLog;\n\n   bool mPaused = false;\n   bool mWasPaused = false;\n   uint32_t mPausedNia = 0;\n   decaf::debug::CafeThread mCurrentThread = { };\n\n   platform::Socket mListenSocket = InvalidSocket;\n   platform::Socket mClientSocket = InvalidSocket;\n\n   ReadState mReadState = ReadState::ReadStart;\n   std::string mChecksumBuffer;\n   std::string mReadBuffer;\n   std::string mLastCommand;\n};\n\n} // namespace debugger\n"
  },
  {
    "path": "src/libdecaf/src/debugger/debugger_server_gdb_xml.inl",
    "content": "static constexpr const char *\nsGdbTargetXML =\nR\"(<?xml version=\"1.0\"?>\n<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n<target>\n  <architecture>powerpc:common</architecture>\n  <xi:include href=\"power-core.xml\"/>\n</target>\n)\";\n\nstatic constexpr const char *\nsGdbPowerCoreXml =\nR\"(<?xml version=\"1.0\"?>\n<!-- Copyright (C) 2007, 2008 Free Software Foundation, Inc.\n     Copying and distribution of this file, with or without modification,\n     are permitted in any medium without royalty provided the copyright\n     notice and this notice are preserved.  -->\n\n<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">\n<feature name=\"org.gnu.gdb.power.core\">\n  <reg name=\"r0\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r1\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r2\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r3\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r4\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r5\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r6\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r7\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r8\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r9\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r10\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r11\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r12\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r13\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r14\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r15\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r16\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r17\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r18\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r19\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r20\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r21\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r22\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r23\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r24\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r25\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r26\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r27\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r28\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r29\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r30\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"r31\" bitsize=\"32\" type=\"uint32\"/>\n\n  <reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\" regnum=\"64\"/>\n  <reg name=\"msr\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"cr\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"lr\" bitsize=\"32\" type=\"code_ptr\"/>\n  <reg name=\"ctr\" bitsize=\"32\" type=\"uint32\"/>\n  <reg name=\"xer\" bitsize=\"32\" type=\"uint32\"/>\n</feature>\n)\";\n"
  },
  {
    "path": "src/libdecaf/src/decaf.cpp",
    "content": "#include \"decaf.h\"\n#include \"decaf_config.h\"\n#include \"decaf_graphics.h\"\n#include \"decaf_input.h\"\n#include \"decaf_slc.h\"\n#include \"decaf_sound.h\"\n\n#include \"cafe/kernel/cafe_kernel.h\"\n#include \"cafe/kernel/cafe_kernel_process.h\"\n#include \"cafe/libraries/coreinit/coreinit_scheduler.h\"\n#include \"cafe/libraries/coreinit/coreinit_thread.h\"\n#include \"cafe/libraries/swkbd/swkbd_keyboard.h\"\n#include \"debugger/debugger.h\"\n#include \"vfs/vfs_host_device.h\"\n#include \"vfs/vfs_virtual_device.h\"\n#include \"input/input.h\"\n#include \"ios/ios.h\"\n\n#include <chrono>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <common/platform.h>\n#include <common/platform_dir.h>\n#include <condition_variable>\n#include <curl/curl.h>\n#include <filesystem>\n#include <fmt/core.h>\n#include <libcpu/cpu.h>\n#include <libcpu/mem.h>\n#include <mutex>\n\n#ifdef PLATFORM_WINDOWS\n#include <WinSock2.h>\n#endif\n\nnamespace decaf\n{\n\nstd::string\nmakeConfigPath(const std::string &filename)\n{\n   auto configPath = std::filesystem::path { platform::getConfigDirectory() };\n   return (configPath / \"decaf\" / filename).string();\n}\n\nbool\ncreateConfigDirectory()\n{\n   return platform::createParentDirectories(makeConfigPath(\".\"));\n}\n\nstd::string\ngetResourcePath(const std::string &filename)\n{\n#ifdef PLATFORM_WINDOWS\n   return decaf::config()->system.resources_path + \"/\" + filename;\n#else\n   std::string userPath = decaf::config()->system.resources_path + \"/\" + filename;\n   std::string systemPath = std::string(DECAF_INSTALL_RESOURCESDIR) + \"/\" + filename;\n\n   if (platform::fileExists(userPath)) {\n      return userPath;\n   }\n\n   if (platform::fileExists(systemPath)) {\n      return systemPath;\n   }\n\n   decaf_abort(fmt::format(\"Failed to find resource {}\", filename));\n#endif\n}\n\nbool\ninitialise(const std::string &gamePath)\n{\n   if (!getInputDriver()) {\n      gLog->error(\"No input driver set\");\n      return false;\n   }\n\n   if (!getGraphicsDriver()) {\n      gLog->error(\"No graphics driver set\");\n      return false;\n   }\n\n   if (auto result = curl_global_init(CURL_GLOBAL_ALL); result != CURLE_OK) {\n      gLog->error(\"curl_global_init returned {}\", result);\n   }\n\n   // Initialise cpu (because this initialises memory)\n   ::cpu::initialise();\n\n   // Setup debugger\n   debugger::initialise();\n\n   // Setup filesystem\n   auto filesystem = std::make_shared<vfs::VirtualDevice>(\"/\");\n   auto user = vfs::User { 0, 0 };\n\n   // Find a valid application to run\n   auto path = std::filesystem::path { gamePath };\n   auto volPath = std::filesystem::path { };\n   auto rpxPath = std::filesystem::path { };\n\n   if (std::filesystem::is_directory(path)) {\n      if (std::filesystem::is_regular_file(path / \"code\" / \"cos.xml\")) {\n         // Found path/code/cos.xml\n         volPath = path;\n      } else if (std::filesystem::is_regular_file(path / \"data\" / \"code\" / \"cos.xml\")) {\n         // Found path/data/code/cos.xml\n         volPath = path / \"data\";\n      }\n   } else if (std::filesystem::is_regular_file(path)) {\n      auto parent1 = path.parent_path();\n      auto parent2 = parent1.parent_path();\n\n      if (std::filesystem::is_regular_file(parent2 / \"code\" / \"cos.xml\")) {\n         // Found file/../code/cos.xml\n         volPath = parent2;\n      } else if (path.extension().compare(\".rpx\") == 0) {\n         // Found file.rpx\n         rpxPath = path;\n      }\n   }\n\n   // Initialise /vol\n   filesystem->makeFolder(user, \"/vol\");\n   filesystem->makeFolder(user, \"/vol/sys\");\n   filesystem->makeFolder(user, \"/vol/temp\");\n\n   if (!volPath.empty()) {\n      filesystem->mountDevice(user, \"/vol/code\", std::make_shared<vfs::HostDevice>(volPath / \"code\"));\n      filesystem->mountDevice(user, \"/vol/content\", std::make_shared<vfs::HostDevice>(volPath / \"content\"));\n      filesystem->mountDevice(user, \"/vol/meta\", std::make_shared<vfs::HostDevice>(volPath / \"meta\"));\n   } else if (!rpxPath.empty()) {\n      filesystem->mountDevice(user, \"/vol/code\", std::make_shared<vfs::HostDevice>(rpxPath.parent_path()));\n\n      if (!decaf::config()->system.content_path.empty()) {\n         filesystem->mountDevice(user, \"/vol/content\", std::make_shared<vfs::HostDevice>(decaf::config()->system.content_path));\n      }\n\n      cafe::kernel::setExecutableFilename(rpxPath.filename().string());\n   } else {\n      gLog->error(\"Could not find valid application at {}\", path.string());\n      return false;\n   }\n\n   // Ensure paths exist\n   if (!decaf::config()->system.hfio_path.empty()) {\n      auto ec = std::error_code { };\n      std::filesystem::create_directories(decaf::config()->system.hfio_path, ec);\n   }\n\n   if (!decaf::config()->system.mlc_path.empty()) {\n      auto ec = std::error_code { };\n      std::filesystem::create_directories(decaf::config()->system.mlc_path, ec);\n   }\n\n   if (!decaf::config()->system.slc_path.empty()) {\n      auto ec = std::error_code { };\n      std::filesystem::create_directories(decaf::config()->system.slc_path, ec);\n   }\n\n   if (!decaf::config()->system.sdcard_path.empty()) {\n      auto ec = std::error_code { };\n      std::filesystem::create_directories(decaf::config()->system.sdcard_path, ec);\n   }\n\n   // Add device folder\n   filesystem->makeFolder(user, \"/dev\");\n\n   // Mount devices\n   filesystem->mountDevice(user, \"/dev/mlc01\",\n                           std::make_shared<vfs::HostDevice>(decaf::config()->system.mlc_path));\n   filesystem->mountDevice(user, \"/dev/slc01\",\n                           std::make_shared<vfs::HostDevice>(decaf::config()->system.slc_path));\n   filesystem->mountDevice(user, \"/dev/ramdisk01\",\n                           std::make_shared<vfs::VirtualDevice>());\n\n   if (!decaf::config()->system.hfio_path.empty()) {\n      filesystem->mountDevice(user, \"/dev/hfio01\",\n                              std::make_shared<vfs::HostDevice>(decaf::config()->system.hfio_path));\n   }\n\n   // Initialise file system with necessary files\n   internal::initialiseSlc(decaf::config()->system.slc_path);\n\n   // Setup ios\n   ios::setFileSystem(std::move(filesystem));\n   return true;\n}\n\nvoid\nstart()\n{\n   // Start ios\n   ios::start();\n}\n\nbool\nstopping()\n{\n   return cafe::kernel::stopping();\n}\n\nint\nwaitForExit()\n{\n   // Wait for IOS to finish\n   ios::join();\n\n   // Wait for PPC to finish\n   cafe::kernel::join();\n\n   // Make sure we clean up\n   decaf::shutdown();\n\n   return cafe::kernel::getProcessExitCode(cafe::kernel::RamPartitionId::MainApplication);\n}\n\nvoid\nshutdown()\n{\n   // Shut down debugger\n   debugger::shutdown();\n\n   // Stop IOS\n   ios::stop();\n\n   // Stop PPC\n   cafe::kernel::stop();\n\n   // Wait for IOS and PPC to stop\n   ios::join();\n   cafe::kernel::join();\n\n   // Stop graphics driver\n   auto graphicsDriver = getGraphicsDriver();\n\n   if (graphicsDriver) {\n      graphicsDriver->stop();\n   }\n\n   setGraphicsDriver(nullptr);\n\n   // Stop sound driver\n   auto soundDriver = getSoundDriver();\n\n   if (soundDriver) {\n      soundDriver->stop();\n   }\n\n   setSoundDriver(nullptr);\n}\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/src/decaf_configstorage.cpp",
    "content": "#include \"decaf_config.h\"\n#include \"decaf_configstorage.h\"\n\n#include <common/configstorage.h>\n\nnamespace decaf\n{\n\nstatic ConfigStorage<Settings> sSettings;\n\nvoid\nsetConfig(const Settings &settings)\n{\n   sSettings.set(std::make_shared<Settings>(settings));\n}\n\nstd::shared_ptr<const Settings>\nconfig()\n{\n   return sSettings.get();\n}\n\nvoid\nregisterConfigChangeListener(ConfigStorage<Settings>::ChangeListener listener)\n{\n   sSettings.addListener(listener);\n}\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/src/decaf_configstorage.h",
    "content": "#pragma once\n#include \"decaf_config.h\"\n\n#include <common/configstorage.h>\n\nnamespace decaf\n{\n\nvoid registerConfigChangeListener(ConfigStorage<Settings>::ChangeListener listener);\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/src/decaf_erreula.cpp",
    "content": "#include \"decaf_erreula.h\"\n\n#include \"cafe/libraries/erreula/erreula_errorviewer.h\"\n\nnamespace decaf\n{\n\nstatic ErrEulaDriver *sErrEulaDriver = nullptr;\n\nvoid\nsetErrEulaDriver(ErrEulaDriver *driver)\n{\n   sErrEulaDriver = driver;\n}\n\nErrEulaDriver *\nerrEulaDriver()\n{\n   return sErrEulaDriver;\n}\n\nvoid\nErrEulaDriver::buttonClicked()\n{\n   cafe::nn_erreula::internal::buttonClicked();\n}\n\nvoid\nErrEulaDriver::button1Clicked()\n{\n   cafe::nn_erreula::internal::button1Clicked();\n}\n\nvoid\nErrEulaDriver::button2Clicked()\n{\n   cafe::nn_erreula::internal::button2Clicked();\n}\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/src/decaf_eventlistener.cpp",
    "content": "#include \"decaf_eventlistener.h\"\n\n#include <algorithm>\n#include <mutex>\n#include <vector>\n\nnamespace decaf\n{\n\nstruct\n{\n   std::mutex mutex;\n   std::vector<EventListener *> listeners;\n} sEventListenerData;\n\nvoid\naddEventListener(EventListener *listener)\n{\n   std::unique_lock<std::mutex> lock { sEventListenerData.mutex };\n   sEventListenerData.listeners.push_back(listener);\n}\n\nvoid\nremoveEventListener(EventListener *listener)\n{\n   std::unique_lock<std::mutex> lock { sEventListenerData.mutex };\n   sEventListenerData.listeners.erase(\n      std::remove(sEventListenerData.listeners.begin(),\n                  sEventListenerData.listeners.end(),\n                  listener));\n}\n\nnamespace event\n{\n\nvoid\nonGameLoaded(const decaf::GameInfo &info)\n{\n   sEventListenerData.mutex.lock();\n   auto listeners = sEventListenerData.listeners;\n   sEventListenerData.mutex.unlock();\n\n   for (auto listener : listeners) {\n      listener->onGameLoaded(info);\n   }\n}\n\n} // namespace event\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/src/decaf_events.h",
    "content": "#pragma once\n#include \"decaf_game.h\"\n\nnamespace decaf::event\n{\n\nvoid onGameLoaded(const decaf::GameInfo &info);\n\n} // namespace decaf::event\n"
  },
  {
    "path": "src/libdecaf/src/decaf_graphics.cpp",
    "content": "#include \"decaf_graphics.h\"\n#include <common/decaf_assert.h>\n\nnamespace decaf\n{\n\nstatic gpu::GraphicsDriver *\nsGraphicsDriver = nullptr;\n\nvoid\nsetGraphicsDriver(gpu::GraphicsDriver *driver)\n{\n   sGraphicsDriver = driver;\n}\n\ngpu::GraphicsDriver *\ngetGraphicsDriver()\n{\n   return sGraphicsDriver;\n}\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/src/decaf_input.cpp",
    "content": "#include \"decaf_input.h\"\n\nnamespace decaf\n{\n\nInputDriver *\nsInputDriver = nullptr;\n\nvoid\nsetInputDriver(InputDriver *driver)\n{\n   sInputDriver = driver;\n}\n\nInputDriver *\ngetInputDriver()\n{\n   return sInputDriver;\n}\n\n} // namespace input\n"
  },
  {
    "path": "src/libdecaf/src/decaf_log.cpp",
    "content": "#include \"decaf_config.h\"\n#include \"decaf_configstorage.h\"\n#include \"decaf_log.h\"\n\n#include \"cafe/libraries/coreinit/coreinit_scheduler.h\"\n#include \"cafe/libraries/coreinit/coreinit_thread.h\"\n\n#include <common/log.h>\n#include <common/strutils.h>\n#include <decaf_buildinfo.h>\n#include <filesystem>\n#include <iterator>\n#include <libcpu/cpu_control.h>\n#include <libcpu/cpu_formatters.h>\n#include <memory>\n#include <spdlog/async.h>\n#include <spdlog/sinks/basic_file_sink.h>\n#include <spdlog/sinks/stdout_sinks.h>\n\nnamespace decaf\n{\n\n// TODO: Move initialiseLogging here and do shit!!!\n\nstatic std::vector<spdlog::sink_ptr>\nsLogSinks;\n\nclass GlobalLogFormatter : public spdlog::formatter\n{\npublic:\n   virtual void format(const spdlog::details::log_msg &msg, spdlog::memory_buf_t &dest) override\n   {\n      auto tm_time = spdlog::details::os::localtime(spdlog::log_clock::to_time_t(msg.time));\n      auto duration = msg.time.time_since_epoch();\n      auto micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count() % 1000000;\n\n      fmt::format_to(std::back_inserter(dest), \"[{:02}:{:02}:{:02}.{:06} {}:\",\n                     tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, micros,\n                     spdlog::level::to_string_view(msg.level));\n\n      auto core = cpu::this_core::state();\n      if (core) {\n         auto thread = cafe::coreinit::internal::getCurrentThread();\n         if (thread) {\n            fmt::format_to(std::back_inserter(dest), \"p{:01X} t{:02X}\", core->id, thread->id);\n         } else {\n            fmt::format_to(std::back_inserter(dest), \"p{:01X} t{:02X}\", core->id, 0xFF);\n         }\n      } else if (msg.logger_name.size()) {\n         fmt::format_to(std::back_inserter(dest), \"{}\", msg.logger_name);\n      } else {\n         fmt::format_to(std::back_inserter(dest), \"h{}\", msg.thread_id);\n      }\n\n      fmt::format_to(std::back_inserter(dest), \"] {}{}\",\n                     std::string_view { msg.payload.data(), msg.payload.size() },\n                     spdlog::details::os::default_eol);\n   }\n\n   virtual std::unique_ptr<formatter> clone() const override\n   {\n      return std::make_unique<GlobalLogFormatter>();\n   }\n};\n\nstatic void\ninitialiseLogSinks(std::string_view filename)\n{\n   if (decaf::config()->log.to_stdout) {\n      sLogSinks.push_back(std::make_shared<spdlog::sinks::stdout_sink_mt>());\n   }\n\n   if (decaf::config()->log.to_file) {\n      auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());\n      auto time = std::localtime(&now);\n\n      auto logFilename =\n         fmt::format(\"{}_{}-{:02}-{:02}_{:02}-{:02}-{:02}.txt\",\n                     filename,\n                     time->tm_year + 1900, time->tm_mon, time->tm_mday,\n                     time->tm_hour, time->tm_min, time->tm_sec);\n\n      auto path = std::filesystem::path { decaf::config()->log.directory } / logFilename;\n      sLogSinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.string()));\n   }\n\n   if (decaf::config()->log.async) {\n      spdlog::init_thread_pool(1024, 1);\n   }\n\n   static std::once_flag sRegisteredConfigChangeListener;\n   std::call_once(sRegisteredConfigChangeListener,\n      []() {\n         decaf::registerConfigChangeListener(\n            [](const decaf::Settings &settings) {\n               auto defaultLevel = spdlog::level::from_str(settings.log.level);\n               spdlog::apply_all([&](std::shared_ptr<spdlog::logger> logger) {\n                  auto level = defaultLevel;\n                  for (auto &item : settings.log.levels) {\n                     if (iequals(item.first, logger->name())) {\n                        level = spdlog::level::from_str(item.second);\n                     }\n                  }\n                  logger->set_level(level);\n               });\n            });\n      });\n}\n\nstatic void\ninitialiseGlobalLogger()\n{\n   auto logLevel = spdlog::level::from_str(decaf::config()->log.level);\n   auto logger = std::shared_ptr<spdlog::logger> { };\n\n   if (decaf::config()->log.async) {\n      logger = std::make_shared<spdlog::async_logger>(\"decaf\",\n                                                      std::begin(sLogSinks),\n                                                      std::end(sLogSinks),\n                                                      spdlog::thread_pool());\n   } else {\n      logger = std::make_shared<spdlog::logger>(\"decaf\",\n                                                std::begin(sLogSinks),\n                                                std::end(sLogSinks));\n      logger->flush_on(spdlog::level::trace);\n   }\n\n   logger->set_level(logLevel);\n   logger->set_formatter(std::make_unique<GlobalLogFormatter>());\n   spdlog::register_logger(logger);\n   gLog = logger;\n}\n\nvoid\ninitialiseLogging(std::string_view filename)\n{\n   initialiseLogSinks(filename);\n   initialiseGlobalLogger();\n\n   gLog->info(\"Built on {} from {}\", BUILD_DATE, GIT_DESC);\n}\n\nstd::shared_ptr<spdlog::logger>\nmakeLogger(std::string name,\n           std::vector<spdlog::sink_ptr> sinks)\n{\n   auto config = decaf::config();\n   auto logger = std::shared_ptr<spdlog::logger> { };\n   sinks.insert(sinks.end(), sLogSinks.begin(), sLogSinks.end());\n\n   if (config->log.async) {\n      logger = std::make_shared<spdlog::async_logger>(name,\n                                                      std::begin(sinks),\n                                                      std::end(sinks),\n                                                      spdlog::thread_pool());\n   } else {\n      logger = std::make_shared<spdlog::logger>(name,\n                                                std::begin(sinks),\n                                                std::end(sinks));\n      logger->flush_on(spdlog::level::trace);\n   }\n\n   auto level = spdlog::level::from_str(config->log.level);\n   for (auto &item : config->log.levels) {\n      if (iequals(item.first, name)) {\n         level = spdlog::level::from_str(item.second);\n      }\n   }\n\n   logger->set_level(level);\n   logger->set_formatter(std::make_unique<GlobalLogFormatter>());\n   spdlog::register_logger(logger);\n   return logger;\n}\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/src/decaf_nullinputdriver.cpp",
    "content": "#include \"decaf_nullinputdriver.h\"\n\nnamespace decaf\n{\n\nusing namespace input;\n\nvoid NullInputDriver::sampleVpadController(int channel, input::vpad::Status &status)\n{\n   status.connected = false;\n}\n\nvoid NullInputDriver::sampleWpadController(int channel, input::wpad::Status &status)\n{\n   status.type = input::wpad::BaseControllerType::Disconnected;\n}\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/src/decaf_slc.cpp",
    "content": "#include \"decaf_config.h\"\n#include \"decaf_slc.h\"\n\n#include \"ios/auxil/ios_auxil_enum.h\"\n\n#include <common/decaf_assert.h>\n#include <common/platform_dir.h>\n#include <common/strutils.h>\n#include <filesystem>\n#include <fmt/core.h>\n#include <pugixml.hpp>\n#include <vector>\n\nusing ios::auxil::UCDataType;\n\nnamespace decaf::internal\n{\n\nstruct InitialiseUCSysConfig\n{\n   const char *name;\n   UCDataType dataType;\n   const char *defaultValue = nullptr;\n   uint32_t length = 0;\n   uint32_t access = 777;\n};\n\nstatic InitialiseUCSysConfig\nDefaultCafe[] = {\n   { \"cafe\",                UCDataType::Complex              },\n   { \"cafe/version\",        UCDataType::UnsignedShort, \"5\"   },\n   { \"cafe/language\",       UCDataType::UnsignedInt,   \"1\"   },\n   { \"cafe/cntry_reg\",      UCDataType::UnsignedInt,   \"110\" },\n   { \"cafe/eula_agree\",     UCDataType::UnsignedByte,  \"0\"   },\n   { \"cafe/eula_version\",   UCDataType::UnsignedInt,   \"100\" },\n   { \"cafe/initial_launch\", UCDataType::UnsignedByte,  \"2\"   },\n   { \"cafe/eco\",            UCDataType::UnsignedByte,  \"0\"   },\n   { \"cafe/fast_boot\",      UCDataType::UnsignedByte,  \"0\"   },\n};\n\nconstexpr InitialiseUCSysConfig\nDefaultCaffeine[] = {\n   { \"caffeine\",                  UCDataType::Complex },\n   { \"caffeine/version\",          UCDataType::UnsignedShort, \"3\"          },\n   { \"caffeine/enable\",           UCDataType::UnsignedByte,  \"1\"          },\n   { \"caffeine/ad_enable\",        UCDataType::UnsignedByte,  \"1\"          },\n   { \"caffeine/push_enable\",      UCDataType::UnsignedByte,  \"1\"          },\n   { \"caffeine/push_time_slot\",   UCDataType::UnsignedInt,   \"2132803072\" },\n   { \"caffeine/push_interval\",    UCDataType::UnsignedShort, \"420\"        },\n   { \"caffeine/drcled_enable\",    UCDataType::UnsignedByte,  \"1\"          },\n   { \"caffeine/push_capabilty\",   UCDataType::UnsignedShort, \"65535\"      },\n   { \"caffeine/invisible_titles\", UCDataType::HexBinary,     nullptr, 512 },\n};\n\nconstexpr InitialiseUCSysConfig\nDefaultParent[] = {\n   { \"parent\",                      UCDataType::Complex },\n   { \"parent/version\",              UCDataType::UnsignedShort, \"10\"          },\n   { \"parent/enable\",               UCDataType::UnsignedByte,  \"0\"           },\n   { \"parent/pin_code\",             UCDataType::String,        nullptr, 5    },\n   { \"parent/sec_question\",         UCDataType::UnsignedByte,  \"0\"           },\n   { \"parent/sec_answer\",           UCDataType::String,        nullptr, 257  },\n   { \"parent/custom_sec_question\",  UCDataType::String,        nullptr, 205  },\n   { \"parent/email_address\",        UCDataType::String,        nullptr, 1025 },\n   { \"parent/permit_delete_all\",    UCDataType::UnsignedByte,  \"0\"           },\n   { \"parent/rating_organization\",  UCDataType::UnsignedInt,   \"7\"           },\n};\n\nconstexpr InitialiseUCSysConfig\nDefaultSpotpass[] = {\n   { \"spotpass\",                       UCDataType::Complex           },\n   { \"spotpass/version\",               UCDataType::UnsignedByte, \"2\" },\n   { \"spotpass/enable\",                UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/auto_dl_app\",           UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info1\",  UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info2\",  UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info3\",  UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info4\",  UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info5\",  UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info6\",  UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info7\",  UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info8\",  UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info9\",  UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info10\", UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info11\", UCDataType::UnsignedByte, \"1\" },\n   { \"spotpass/upload_console_info12\", UCDataType::UnsignedByte, \"1\" },\n};\n\nconstexpr InitialiseUCSysConfig DefaultParentalAccount[] = {\n   { \"p_acct{}\",                           UCDataType::Complex             },\n   { \"p_acct{}/version\",                   UCDataType::UnsignedShort, \"10\" },\n   { \"p_acct{}/game_rating\",               UCDataType::UnsignedInt,   \"18\" },\n   { \"p_acct{}/eshop_purchase\",            UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/friend_reg\",                UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/acct_modify\",               UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/data_manage\",               UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/int_setting\",               UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/country_setting\",           UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/sys_init\",                  UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/int_browser\",               UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/int_movie\",                 UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/net_communication_on_game\", UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/network_launcher\",          UCDataType::UnsignedByte,  \"0\"  },\n   { \"p_acct{}/entertainment_launcher\",    UCDataType::UnsignedByte,  \"0\"  },\n};\n\nstatic void\nwriteDefaultConfig(pugi::xml_node &doc,\n                   const InitialiseUCSysConfig *first,\n                   const InitialiseUCSysConfig *last,\n                   int formatArg = 0)\n{\n   auto formattedName = std::string { };\n   for (auto item = first; item != last; ++item) {\n      auto parent = doc;\n      auto name = std::string_view { item->name };\n\n      if (name.find('{') != std::string::npos) {\n         formattedName = fmt::format(name.data(), formatArg);\n         name = formattedName;\n      }\n\n      if (auto pos = name.find_last_of(\"/\"); pos != std::string_view::npos) {\n         auto parentPath = std::string { name.substr(0, pos) };\n         name = name.substr(pos + 1);\n\n         parent = doc.first_element_by_path(parentPath.c_str());\n         decaf_check(parent);\n      }\n\n      auto node = parent.append_child(name.data());\n\n      if (item->defaultValue) {\n         node.text() = item->defaultValue;\n      } else if (item->dataType == UCDataType::HexBinary) {\n         node.text() = std::string(item->length * 2, '0').c_str();\n      }\n\n      switch (item->dataType) {\n      case UCDataType::Complex:\n         node.append_attribute(\"type\") = \"complex\";\n         break;\n      case UCDataType::UnsignedByte:\n         node.append_attribute(\"type\") = \"unsignedByte\";\n         node.append_attribute(\"length\") = \"1\";\n         break;\n      case UCDataType::UnsignedShort:\n         node.append_attribute(\"type\") = \"unsignedShort\";\n         node.append_attribute(\"length\") = \"2\";\n         break;\n      case UCDataType::UnsignedInt:\n         node.append_attribute(\"type\") = \"unsignedInt\";\n         node.append_attribute(\"length\") = \"4\";\n         break;\n      case UCDataType::SignedInt:\n         node.append_attribute(\"type\") = \"signedInt\";\n         node.append_attribute(\"length\") = \"4\";\n         break;\n      case UCDataType::HexBinary:\n         node.append_attribute(\"type\") = \"hexBinary\";\n         node.append_attribute(\"length\") = item->length;\n         break;\n      case UCDataType::String:\n         node.append_attribute(\"type\") = \"string\";\n         node.append_attribute(\"length\") = item->length;\n         break;\n      default:\n         decaf_abort(\"Unimplemented UCDataType\");\n      }\n\n      if (item->access) {\n         node.append_attribute(\"access\") = item->access;\n      }\n   }\n}\n\nstatic void\nwriteDefaultConfig(const std::filesystem::path &path,\n                   const InitialiseUCSysConfig *first,\n                   const InitialiseUCSysConfig *last,\n                   int formatArg = 0)\n{\n   if (std::filesystem::exists(path)) {\n      return;\n   }\n\n   auto doc = pugi::xml_document { };\n   auto decl = doc.prepend_child(pugi::node_declaration);\n   decl.append_attribute(\"version\") = \"1.0\";\n   decl.append_attribute(\"encoding\") = \"utf-8\";\n\n   writeDefaultConfig(doc, first, last, formatArg);\n\n   doc.save_file(path.string().c_str(),\n                 \"  \", 1,\n                 pugi::encoding_utf8);\n}\n\nstatic void\napplyRegionSettings()\n{\n   auto language = DefaultCafe[2].defaultValue;\n   auto country = DefaultCafe[3].defaultValue;\n\n   switch (config()->system.region) {\n   case  SystemRegion::Japan:\n      language = \"0\"; // Japanese\n      country = \"3\"; // Japan\n      break;\n   case SystemRegion::USA:\n      language = \"1\"; // English\n      country = \"49\"; // United States\n      break;\n   case SystemRegion::Europe:\n      language = \"1\"; // English\n      country = \"110\"; // United Kingdom\n      break;\n   case SystemRegion::China:\n      language = \"6\"; // Chinese\n      country = \"160\"; // China\n      break;\n   case SystemRegion::Korea:\n      language = \"7\"; // Korean\n      country = \"136\"; // Best Korea\n      break;\n   case SystemRegion::Taiwan:\n      language = \"11\"; // Taiwanese\n      country = \"128\"; // Taiwan\n      break;\n   }\n\n   DefaultCafe[2].defaultValue = language;\n   DefaultCafe[3].defaultValue = country;\n}\n\nvoid\ninitialiseSlc(std::string_view path)\n{\n   // Ensure slc/proc/prefs exists\n   auto prefsPath = std::filesystem::path { path } / \"proc\" / \"prefs\";\n   std::filesystem::create_directories(prefsPath);\n\n   // Apply decaf region config to XML settings before we write them\n   applyRegionSettings();\n\n   // Write some default config settings if they don't already exist\n   writeDefaultConfig(prefsPath / \"cafe.xml\",\n                      std::begin(DefaultCafe),\n                      std::end(DefaultCafe));\n\n   writeDefaultConfig(prefsPath / \"caffeine.xml\",\n                      std::begin(DefaultCaffeine),\n                      std::end(DefaultCaffeine));\n\n   writeDefaultConfig(prefsPath / \"parent.xml\",\n                      std::begin(DefaultParent),\n                      std::end(DefaultParent));\n\n   writeDefaultConfig(prefsPath / \"spotpass.xml\",\n                      std::begin(DefaultSpotpass),\n                      std::end(DefaultSpotpass));\n\n   for (auto i = 1; i <= 12; ++i) {\n      writeDefaultConfig(prefsPath / fmt::format(\"p_acct{}.xml\", i),\n                         std::begin(DefaultParentalAccount),\n                         std::end(DefaultParentalAccount),\n                         i);\n   }\n}\n\n} // namespace decaf::internal\n"
  },
  {
    "path": "src/libdecaf/src/decaf_slc.h",
    "content": "#pragma once\n#include <string_view>\n\nnamespace decaf::internal\n{\n\nvoid\ninitialiseSlc(std::string_view path);\n\n} // namespace decaf::internal\n"
  },
  {
    "path": "src/libdecaf/src/decaf_softwarekeyboard.cpp",
    "content": "#include \"decaf_softwarekeyboard.h\"\n\n#include \"cafe/libraries/swkbd/swkbd_keyboard.h\"\n\nnamespace decaf\n{\n\nstatic SoftwareKeyboardDriver *sSoftwareKeyboardDriver = nullptr;\n\nvoid\nsetSoftwareKeyboardDriver(SoftwareKeyboardDriver *driver)\n{\n   sSoftwareKeyboardDriver = driver;\n}\n\nSoftwareKeyboardDriver *\nsoftwareKeyboardDriver()\n{\n   return sSoftwareKeyboardDriver;\n}\n\nvoid\nSoftwareKeyboardDriver::accept()\n{\n   cafe::swkbd::internal::inputAccept();\n}\n\nvoid\nSoftwareKeyboardDriver::reject()\n{\n   cafe::swkbd::internal::inputReject();\n}\n\nvoid\nSoftwareKeyboardDriver::setInputString(std::u16string_view text)\n{\n   cafe::swkbd::internal::setInputString(text);\n}\n\n} // namespace decaf\n"
  },
  {
    "path": "src/libdecaf/src/decaf_sound.cpp",
    "content": "#include \"decaf_sound.h\"\n\nnamespace decaf\n{\n\nSoundDriver *\nsSoundDriver = nullptr;\n\nvoid\nsetSoundDriver(SoundDriver *driver)\n{\n   sSoundDriver = driver;\n}\n\nSoundDriver *\ngetSoundDriver()\n{\n   return sSoundDriver;\n}\n\n} // namespace sound\n"
  },
  {
    "path": "src/libdecaf/src/input/input.cpp",
    "content": "#include \"decaf.h\"\n#include \"input.h\"\n\nnamespace input\n{\n\nvoid\nsampleVpadController(int channel, vpad::Status &status)\n{\n   decaf::getInputDriver()->sampleVpadController(channel, status);\n}\n\nvoid\nsampleWpadController(int channel, wpad::Status &status)\n{\n   decaf::getInputDriver()->sampleWpadController(channel, status);\n}\n\n} // namespace input\n"
  },
  {
    "path": "src/libdecaf/src/input/input.h",
    "content": "#pragma once\n#include <cstddef>\n#include \"decaf_input.h\"\n\nnamespace input\n{\n\nnamespace vpad = decaf::input::vpad;\nnamespace wpad = decaf::input::wpad;\n\nvoid\nsampleVpadController(int channel, vpad::Status &status);\n\nvoid\nsampleWpadController(int channel, wpad::Status &status);\n\n} // namespace input\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp.cpp",
    "content": "#include \"ios_acp.h\"\n#include \"ios_acp_client_save.h\"\n#include \"ios_acp_log.h\"\n#include \"ios_acp_main_server.h\"\n#include \"ios_acp_nnsm.h\"\n#include \"ios_acp_pdm_server.h\"\n\n#include \"decaf_log.h\"\n#include \"ios/ios_stackobject.h\"\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/mcp/ios_mcp_ipc.h\"\n#include \"ios/nn/ios_nn.h\"\n\n#include <common/log.h>\n\nusing namespace ios::fs;\nusing namespace ios::kernel;\nusing namespace ios::mcp;\n\nnamespace ios::acp\n{\n\nconstexpr auto LocalHeapSize = 0x20000u;\nconstexpr auto CrossHeapSize = 0x80000u;\n\nstruct StaticAcpData\n{\n   be2_val<Handle> fsaHandle;\n};\n\nstatic phys_ptr<StaticAcpData> sAcpData = nullptr;\nstatic phys_ptr<void> sLocalHeapBuffer = nullptr;\n\nnamespace internal\n{\n\nLogger acpLog = { };\n\nvoid\ninitialiseStaticData()\n{\n   sAcpData = allocProcessStatic<StaticAcpData>();\n   sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize);\n}\n\n} // namespace internal\n\nstatic void\nupdateSaveAndBossQuota()\n{\n   // TODO: Update boss and save quota.\n\n   // Make quota for Mii database in all regions\n   // Note: Real IOS only does it for current system region\n   FSAMakeQuota(sAcpData->fsaHandle,\n                \"/vol/storage_mlc01/usr/save/00050010/1004a000/user/common/db\",\n                0x660, 24 * 1024 * 1024);\n   FSAMakeQuota(sAcpData->fsaHandle,\n                \"/vol/storage_mlc01/usr/save/00050010/1004a100/user/common/db\",\n                0x660, 24 * 1024 * 1024);\n   FSAMakeQuota(sAcpData->fsaHandle,\n                \"/vol/storage_mlc01/usr/save/00050010/1004a200/user/common/db\",\n                0x660, 24 * 1024 * 1024);\n}\n\nstatic Error\nacpSaveStart()\n{\n   auto error = client::save::start();\n   if (error < Error::OK) {\n      internal::acpLog->error(\"acpSaveStart: client::save::start failed with error = {}\", error);\n      return error;\n   }\n\n   // TODO: acp::client::save::bossStorage::start();\n\n   // TODO: This is not actually called here, but not sure where yet.\n   updateSaveAndBossQuota();\n   return Error::OK;\n}\n\nstatic Error\nacpSaveStop()\n{\n   return Error::OK;\n}\n\nstatic Error\nacpSystemStart()\n{\n   auto error = FSAOpen();\n   if (error < Error::OK) {\n      internal::acpLog->error(\"acpSystemStart: FSAOpen failed with error = {}\", error);\n      return error;\n   }\n\n   sAcpData->fsaHandle = static_cast<Handle>(error);\n   return Error::OK;\n}\n\nstatic Error\nacpSystemStop()\n{\n   if (sAcpData->fsaHandle > 0) {\n      FSAClose(sAcpData->fsaHandle);\n   }\n\n   return Error::OK;\n}\n\nError\nprocessEntryPoint(phys_ptr<void> /* context */)\n{\n   StackArray<Message, 10> messages;\n   StackObject<Message> message;\n   MessageQueueId messageQueueId;\n\n   // Initialise logger\n   internal::acpLog = decaf::makeLogger(\"IOS_ACP\");\n\n   // Initialise static memory\n   internal::initialiseStaticData();\n   internal::initialiseStaticMainServerData();\n   internal::initialiseStaticNnsmData();\n   internal::initialiseStaticPdmServerData();\n\n   // Initialise nn for current process\n   nn::initialiseProcess();\n\n   auto error = IOS_SetThreadPriority(CurrentThread, 50);\n   if (error < Error::OK) {\n      internal::acpLog->error(\n         \"processEntryPoint: IOS_SetThreadPriority failed with error = {}\", error);\n      return error;\n   }\n\n   // Initialise process heaps\n   error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize);\n   if (error < Error::OK) {\n      internal::acpLog->error(\n         \"processEntryPoint: IOS_CreateLocalProcessHeap failed with error = {}\", error);\n      return error;\n   }\n\n   error = IOS_CreateCrossProcessHeap(CrossHeapSize);\n   if (error < Error::OK) {\n      internal::acpLog->error(\n         \"processEntryPoint: IOS_CreateCrossProcessHeap failed with error = {}\", error);\n      return error;\n   }\n\n   // Setup /dev/acpproc\n   error = IOS_CreateMessageQueue(messages, messages.size());\n   if (error < Error::OK) {\n      internal::acpLog->error(\n         \"processEntryPoint: IOS_CreateMessageQueue failed with error = {}\", error);\n      return error;\n   }\n   messageQueueId = static_cast<MessageQueueId>(error);\n\n   error = MCP_RegisterResourceManager(\"/dev/acpproc\", messageQueueId);\n   if (error < Error::OK) {\n      internal::acpLog->error(\n         \"processEntryPoint: MCP_RegisterResourceManager failed for /dev/acpproc with error = {}\", error);\n      return error;\n   }\n\n   // Start nnsm\n   error = internal::startNnsm();\n   if (error < Error::OK) {\n      internal::acpLog->error(\n         \"processEntryPoint: startNnsm failed with error = {}\", error);\n      return error;\n   }\n\n   // Start nn::ipc::Server for /dev/acp_main\n   error = internal::startMainServer();\n   if (error < Error::OK) {\n      internal::acpLog->error(\n         \"processEntryPoint: startMainServer failed with error = {}\", error);\n      return error;\n   }\n\n   // Start nn::ipc::Server for /dev/pdm\n   error = internal::startPdmServer();\n   if (error < Error::OK) {\n      internal::acpLog->error(\n         \"processEntryPoint: startPdmServer failed with error = {}\", error);\n      return error;\n   }\n\n   // Run acpproc\n   while (true) {\n      error = IOS_ReceiveMessage(messageQueueId, message, MessageFlags::None);\n      if (error < Error::OK) {\n         internal::acpLog->error(\n            \"processEntryPoint: IOS_ReceiveMessage failed with error = {}\", error);\n         break;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      case Command::Close:\n         error = Error::OK;\n         break;\n      case Command::Suspend:\n         error = acpSaveStop();\n         if (error >= Error::OK) {\n            error = acpSystemStop();\n         }\n         break;\n      case Command::Resume:\n         error = acpSystemStart();\n         if (error >= Error::OK) {\n            error = acpSaveStart();\n         }\n         break;\n      default:\n         error = Error::Invalid;\n      }\n\n      IOS_ResourceReply(request, error);\n   }\n\n   // Uninitialise nn for current process\n   nn::uninitialiseProcess();\n   return error;\n}\n\nnamespace internal\n{\n\nHandle\ngetFsaHandle()\n{\n   return sAcpData->fsaHandle;\n}\n\n} // namespace internal\n\n} // namespace ios::acp\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_ipc.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n\nnamespace ios::acp\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\nnamespace internal\n{\n\nHandle\ngetFsaHandle();\n\n} // namespace internal\n\n} // namespace ios::acp\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_client_save.cpp",
    "content": "#include \"ios_acp.h\"\n#include \"ios_acp_client_save.h\"\n#include \"ios_acp_log.h\"\n\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <fmt/core.h>\n\nnamespace ios::acp::client::save\n{\n\nusing namespace ios::fs;\n\nstatic FSAStatus\ncheckExistenceUsingOpenDir(std::string_view path)\n{\n   // TODO: FSAOpenDir, FSACloseDir\n   return FSAStatus::Cancelled;\n}\n\nstatic bool\ncheckExistence(std::string_view path)\n{\n   StackObject<FSAStat> stat;\n   auto error = FSAGetInfoByQuery(internal::getFsaHandle(),\n                                  path, FSAQueryInfoType::Stat, stat);\n   if (error == FSAStatus::PermissionError) {\n      error = checkExistenceUsingOpenDir(path);\n   }\n\n   if (error == FSAStatus::OK) {\n      return true;\n   }\n\n   if (error == FSAStatus::NotFound) {\n      return false;\n   }\n\n   // Unexpected error!\n   return false;\n}\n\nstatic FSAStatus\ncreateDir(std::string_view path, uint32_t mode)\n{\n   return FSAMakeDir(internal::getFsaHandle(), path, mode);\n}\n\nstatic FSAStatus\nmakeQuota(std::string_view path, uint32_t mode, uint64_t size)\n{\n   return FSAMakeQuota(internal::getFsaHandle(), path, mode, size);\n}\n\nstatic Error\ncreateSystemSaveDir(std::string_view devicePath)\n{\n   auto path = fmt::format(\"{}{}\", devicePath, \"usr/save/system/\");\n   if (!checkExistence(path)) {\n      createDir(path, 0x600);\n   }\n\n   path = fmt::format(\"{}{}\", devicePath, \"usr/save/system/acp/\");\n   if (!checkExistence(path)) {\n      makeQuota(path, 0x600, 0x800000u);\n   }\n\n   path = fmt::format(\"{}{}\", devicePath, \"usr/save/system/act/\");\n   if (!checkExistence(path)) {\n      makeQuota(path, 0x660, 0x800000u);\n   }\n\n   path = fmt::format(\"{}{}\", devicePath, \"usr/save/system/fpd/\");\n   if (!checkExistence(path)) {\n      makeQuota(path, 0x660, 0x200000u);\n   }\n\n   path = fmt::format(\"{}{}\", devicePath, \"usr/save/system/boss/\");\n   if (!checkExistence(path)) {\n      makeQuota(path, 0x660, 0x2000000u);\n   }\n\n   path = fmt::format(\"{}{}\", devicePath, \"usr/save/system/nim/\");\n   if (!checkExistence(path)) {\n      makeQuota(path, 0x660, 0x1A00000u);\n   }\n\n   path = fmt::format(\"{}{}\", devicePath, \"usr/save/system/pdm\");\n   if (!checkExistence(path)) {\n      makeQuota(path, 0x660, 0x1400000u);\n   }\n\n   path = fmt::format(\"{}{}\", devicePath, \"usr/save/system/no_delete\");\n   if (!checkExistence(path)) {\n      makeQuota(path, 0x600, 0x800000u);\n   }\n\n   // TODO: FlushQuota devicePath/usr/save\n   return Error::OK;\n}\n\nstatic Error\ncreateNoDeleteDirs(std::string_view devicePath)\n{\n   auto path = fmt::format(\"{}{}{:08X}/\",\n                           devicePath,\n                           \"usr/save/system/no_delete/\",\n                           0x00050000);\n   if (!checkExistence(path)) {\n      createDir(path, 0x600);\n   }\n\n   path = fmt::format(\"{}{}{:08X}/{:08X}/\",\n                      devicePath,\n                      \"usr/save/system/no_delete/\",\n                      0x00050000, 0x10100D00);\n   if (!checkExistence(path)) {\n      createDir(path, 0x600);\n   }\n\n   // TODO: ChangeOwner for path\n   return Error::OK;\n}\n\nstatic Error\ncreateAppBoxCache()\n{\n   auto path = \"/vol/storage_mlc01/usr/save/system/acp/appBoxCache/\";\n   if (!checkExistence(path)) {\n      createDir(path, 0x600);\n   }\n\n   return Error::OK;\n}\n\nError\nstart()\n{\n   auto error = createSystemSaveDir(\"/vol/storage_mlc01/\");\n   if (error) {\n      internal::acpLog->error(\n         \"client::save::start: createSystemSaveDir failed with error = {}\", error);\n      return error;\n   }\n\n   error = createNoDeleteDirs(\"/vol/storage_mlc01/\");\n   if (error) {\n      internal::acpLog->error(\n         \"client::save::start: createNoDeleteDirs failed with error = {}\", error);\n      return error;\n   }\n\n   error = createAppBoxCache();\n   if (error) {\n      internal::acpLog->error(\n         \"client::save::start: createAppBoxCache failed with error = {}\", error);\n      return error;\n   }\n\n   // TODO: StartTimeStampThread\n   return Error::OK;\n}\n\n} // namespace ios::acp::client::save\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_client_save.h",
    "content": "#include \"ios/ios_enum.h\"\n\nnamespace ios::acp::client::save\n{\n\nError\nstart();\n\n} // namespace ios::acp::client::save\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_enum.h",
    "content": "#ifndef IOS_ACP_ENUM_H\n#define IOS_ACP_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\nENUM_NAMESPACE_ENTER(acp)\n\nENUM_BEG(NssmCommand, uint32_t)\n   ENUM_VALUE(RegisterService,                  1)\n   ENUM_VALUE(UnregisterService,                2)\nENUM_END(NssmCommand)\n\nENUM_NAMESPACE_EXIT(acp)\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_ACP_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_log.h",
    "content": "#pragma once\n#include <common/log.h>\n\nnamespace ios::acp::internal\n{\n\nextern Logger acpLog;\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_main_server.cpp",
    "content": "#include \"ios_acp_log.h\"\n#include \"ios_acp_main_server.h\"\n#include \"ios_acp_nn_miscservice.h\"\n#include \"ios_acp_nn_saveservice.h\"\n#include \"ios_acp_spm_extendedstorageservice.h\"\n\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/nn/ios_nn_ipc_server.h\"\n\nnamespace ios::acp::internal\n{\n\nusing namespace kernel;\n\nconstexpr auto AcpMainNumMessages = 100u;\nconstexpr auto AcpMainThreadStackSize = 0x10000u;\nconstexpr auto AcpMainThreadPriority = 50u;\n\nclass AcpMainServer : public nn::ipc::Server\n{\npublic:\n   AcpMainServer() :\n      nn::ipc::Server(true)\n   {\n   }\n};\n\nstruct StaticAcpMainServerData\n{\n   be2_struct<AcpMainServer> server;\n   be2_array<Message, AcpMainNumMessages> messageBuffer;\n   be2_array<uint8_t, AcpMainThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticAcpMainServerData> sMainServerData = nullptr;\n\nError\nstartMainServer()\n{\n   auto &server = sMainServerData->server;\n   auto result = server.initialise(\"/dev/acp_main\",\n                                   phys_addrof(sMainServerData->messageBuffer),\n                                   static_cast<uint32_t>(sMainServerData->messageBuffer.size()));\n   if (result.failed()) {\n      internal::acpLog->error(\n         \"startMainServer: Server initialisation failed for /dev/acp_main, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   // TODO: Services 1, 3, 4, 6, 301, 302, 304\n   server.registerService<MiscService>();\n   server.registerService<SaveService>();\n   server.registerService<ExtendedStorageService>();\n\n   result = server.start(phys_addrof(sMainServerData->threadStack) + sMainServerData->threadStack.size(),\n                         static_cast<uint32_t>(sMainServerData->threadStack.size()),\n                         AcpMainThreadPriority);\n   if (result.failed()) {\n      internal::acpLog->error(\n         \"startMainServer: Server start failed for /dev/acp_main, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticMainServerData()\n{\n   sMainServerData = allocProcessStatic<StaticAcpMainServerData>();\n}\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_main_server.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::acp::internal\n{\n\nError\nstartMainServer();\n\nvoid\ninitialiseStaticMainServerData();\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_metaxml.cpp",
    "content": "#include \"ios_acp.h\"\n#include \"ios_acp_metaxml.h\"\n\n#include \"nn/acp/nn_acp_result.h\"\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n\n#include <common/strutils.h>\n#include <fmt/core.h>\n#include <pugixml.hpp>\n\nusing namespace ios::fs;\nusing namespace ios::kernel;\nusing namespace nn::acp;\n\nnamespace ios::acp::internal\n{\n\nstatic nn::Result\ngetU32(pugi::xml_node &node,\n       const char *name,\n       phys_ptr<uint32_t> outValue)\n{\n   auto child = node.child(name);\n   if (!child) {\n      return ResultXmlItemNotFound;\n   }\n\n   *outValue = static_cast<uint32_t>(strtoul(child.text().get(), nullptr, 10));\n   return ResultSuccess;\n}\n\nstatic nn::Result\ngetHex32(pugi::xml_node &node,\n         const char *name,\n         phys_ptr<uint32_t> outValue)\n{\n   auto child = node.child(name);\n   if (!child) {\n      return ResultXmlItemNotFound;\n   }\n\n   *outValue = static_cast<uint32_t>(strtoul(child.text().get(), nullptr, 16));\n   return ResultSuccess;\n}\n\nstatic nn::Result\ngetHex64(pugi::xml_node &node,\n         const char *name,\n         phys_ptr<uint64_t> outValue)\n{\n   auto child = node.child(name);\n   if (!child) {\n      return ResultXmlItemNotFound;\n   }\n\n   *outValue = static_cast<uint64_t>(strtoull(child.text().get(), nullptr, 16));\n   return ResultSuccess;\n}\n\ntemplate<uint32_t Size>\nstatic nn::Result\ngetString(pugi::xml_node &node,\n          const char *name,\n          be2_array<char, Size> &buffer)\n{\n   auto child = node.child(name);\n   if (!child) {\n      return ResultXmlItemNotFound;\n   }\n\n   if (!child.text()) {\n      buffer[0] = char { 0 };\n      return ResultSuccess;\n   }\n\n   string_copy(phys_addrof(buffer).get(), child.text().get(), Size);\n   return ResultSuccess;\n}\n\nnn::Result\nloadMetaXMLFromPath(std::string_view path,\n                    phys_ptr<nn::acp::ACPMetaXml> metaXml)\n{\n   auto xmlSize = uint32_t { 0 };\n   auto xmlData = phys_ptr<uint8_t> { nullptr };\n   auto fsaStatus = FSAReadFileIntoCrossProcessHeap(getFsaHandle(),\n                                                    \"/vol/meta/meta.xml\",\n                                                    &xmlSize, &xmlData);\n   if (fsaStatus < FSAStatus::OK) {\n      return static_cast<nn::Result>(fsaStatus);\n   }\n\n   auto doc = pugi::xml_document { };\n   auto parseResult = doc.load_buffer(xmlData.get(), xmlSize);\n   IOS_HeapFree(CrossProcessHeapId, xmlData);\n\n   if (!parseResult) {\n      return ResultInvalidXmlFile;\n   }\n\n   auto menu = doc.child(\"menu\");\n   if (!menu) {\n      return ResultInvalidXmlFile;\n   }\n\n   auto result = getU32(menu, \"version\", phys_addrof(metaXml->version));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"product_code\", metaXml->product_code);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"content_platform\", metaXml->content_platform);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"company_code\", metaXml->company_code);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"mastering_date\", metaXml->mastering_date);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"logo_type\", phys_addrof(metaXml->logo_type));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex32(menu, \"app_launch_type\", phys_addrof(metaXml->app_launch_type));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex32(menu, \"invisible_flag\", phys_addrof(metaXml->invisible_flag));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex32(menu, \"no_managed_flag\", phys_addrof(metaXml->no_managed_flag));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex32(menu, \"no_event_log\", phys_addrof(metaXml->no_event_log));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex32(menu, \"no_icon_database\", phys_addrof(metaXml->no_icon_database));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex32(menu, \"launching_flag\", phys_addrof(metaXml->launching_flag));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex32(menu, \"install_flag\", phys_addrof(metaXml->install_flag));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"closing_msg\", phys_addrof(metaXml->closing_msg));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"title_version\", phys_addrof(metaXml->title_version));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex64(menu, \"title_id\", phys_addrof(metaXml->title_id));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex32(menu, \"group_id\", phys_addrof(metaXml->group_id));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex64(menu, \"boss_id\", phys_addrof(metaXml->boss_id));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex64(menu, \"os_version\", phys_addrof(metaXml->os_version));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex64(menu, \"app_size\", phys_addrof(metaXml->app_size));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex64(menu, \"common_save_size\", phys_addrof(metaXml->common_save_size));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex64(menu, \"account_save_size\", phys_addrof(metaXml->account_save_size));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex64(menu, \"common_boss_size\", phys_addrof(metaXml->common_boss_size));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex64(menu, \"account_boss_size\", phys_addrof(metaXml->account_boss_size));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"save_no_rollback\", phys_addrof(metaXml->save_no_rollback));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"bg_daemon_enable\", phys_addrof(metaXml->bg_daemon_enable));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex32(menu, \"join_game_id\", phys_addrof(metaXml->join_game_id));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex64(menu, \"join_game_mode_mask\", phys_addrof(metaXml->join_game_mode_mask));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"olv_accesskey\", phys_addrof(metaXml->olv_accesskey));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"wood_tin\", phys_addrof(metaXml->wood_tin));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"e_manual\", phys_addrof(metaXml->e_manual));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"e_manual_version\", phys_addrof(metaXml->e_manual_version));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getHex32(menu, \"region\", phys_addrof(metaXml->region));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_cero\", phys_addrof(metaXml->pc_cero));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_esrb\", phys_addrof(metaXml->pc_esrb));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_bbfc\", phys_addrof(metaXml->pc_bbfc));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_usk\", phys_addrof(metaXml->pc_usk));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_pegi_gen\", phys_addrof(metaXml->pc_pegi_gen));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_pegi_fin\", phys_addrof(metaXml->pc_pegi_fin));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_pegi_prt\", phys_addrof(metaXml->pc_pegi_prt));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_pegi_bbfc\", phys_addrof(metaXml->pc_pegi_bbfc));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_cob\", phys_addrof(metaXml->pc_cob));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_grb\", phys_addrof(metaXml->pc_grb));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_cgsrr\", phys_addrof(metaXml->pc_cgsrr));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_oflc\", phys_addrof(metaXml->pc_oflc));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_reserved0\", phys_addrof(metaXml->pc_reserved0));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_reserved1\", phys_addrof(metaXml->pc_reserved1));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_reserved2\", phys_addrof(metaXml->pc_reserved2));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"pc_reserved3\", phys_addrof(metaXml->pc_reserved3));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"ext_dev_nunchaku\", phys_addrof(metaXml->ext_dev_nunchaku));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"ext_dev_classic\", phys_addrof(metaXml->ext_dev_classic));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"ext_dev_urcc\", phys_addrof(metaXml->ext_dev_urcc));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"ext_dev_board\", phys_addrof(metaXml->ext_dev_board));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"ext_dev_usb_keyboard\", phys_addrof(metaXml->ext_dev_usb_keyboard));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"ext_dev_etc\", phys_addrof(metaXml->ext_dev_etc));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"ext_dev_etc_name\", metaXml->ext_dev_etc_name);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"eula_version\", phys_addrof(metaXml->eula_version));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"drc_use\", phys_addrof(metaXml->drc_use));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"network_use\", phys_addrof(metaXml->network_use));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"online_account_use\", phys_addrof(metaXml->online_account_use));\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getU32(menu, \"direct_boot\", phys_addrof(metaXml->direct_boot));\n   if (result.failed()) {\n      return result;\n   }\n\n   for (auto i = 0u; i < metaXml->reserved_flags.size(); ++i) {\n      result = getHex32(menu, fmt::format(\"reserved_flag{}\", i).c_str(),\n                        phys_addrof(metaXml->reserved_flags[i]));\n      if (result.failed()) {\n         return result;\n      }\n   }\n\n   result = getString(menu, \"longname_ja\", metaXml->longname_ja);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_en\", metaXml->longname_en);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_fr\", metaXml->longname_fr);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_de\", metaXml->longname_de);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_it\", metaXml->longname_it);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_es\", metaXml->longname_es);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_zhs\", metaXml->longname_zhs);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_ko\", metaXml->longname_ko);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_nl\", metaXml->longname_nl);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_pt\", metaXml->longname_pt);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_ru\", metaXml->longname_ru);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"longname_zht\", metaXml->longname_zht);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_ja\", metaXml->shortname_ja);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_en\", metaXml->shortname_en);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_fr\", metaXml->shortname_fr);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_de\", metaXml->shortname_de);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_it\", metaXml->shortname_it);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_es\", metaXml->shortname_es);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_zhs\", metaXml->shortname_zhs);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_ko\", metaXml->shortname_ko);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_nl\", metaXml->shortname_nl);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_pt\", metaXml->shortname_pt);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_ru\", metaXml->shortname_ru);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"shortname_zht\", metaXml->shortname_zht);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_ja\", metaXml->publisher_ja);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_en\", metaXml->publisher_en);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_fr\", metaXml->publisher_fr);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_de\", metaXml->publisher_de);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_it\", metaXml->publisher_it);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_es\", metaXml->publisher_es);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_zhs\", metaXml->publisher_zhs);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_ko\", metaXml->publisher_ko);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_nl\", metaXml->publisher_nl);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_pt\", metaXml->publisher_pt);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_ru\", metaXml->publisher_ru);\n   if (result.failed()) {\n      return result;\n   }\n\n   result = getString(menu, \"publisher_zht\", metaXml->publisher_zht);\n   if (result.failed()) {\n      return result;\n   }\n\n   for (auto i = 0u; i < metaXml->add_on_unique_ids.size(); ++i) {\n      result = getHex32(menu, fmt::format(\"add_on_unique_id{}\", i).c_str(),\n                        phys_addrof(metaXml->add_on_unique_ids[i]));\n      if (result.failed()) {\n         return result;\n      }\n   }\n\n   return ResultSuccess;\n}\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_metaxml.h",
    "content": "#pragma once\n#include \"nn/acp/nn_acp_types.h\"\n#include \"nn/nn_result.h\"\n\nnamespace ios::acp::internal\n{\n\nnn::Result\nloadMetaXMLFromPath(std::string_view path,\n                    phys_ptr<nn::acp::ACPMetaXml> metaXml);\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_nn_miscservice.cpp",
    "content": "#include \"ios_acp_nn_miscservice.h\"\n#include \"ios_acp_metaxml.h\"\n\n#include \"ios/nn/ios_nn_ipc_server_command.h\"\n#include \"nn/acp/nn_acp_result.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n\n#include <chrono>\n#include <ctime>\n\nusing namespace nn::acp;\n\nusing nn::ipc::CommandHandlerArgs;\nusing nn::ipc::CommandId;\nusing nn::ipc::OutBuffer;\nusing nn::ipc::ServerCommand;\n\nnamespace ios::acp::internal\n{\n\nstatic nn::Result\ngetNetworkTime(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<MiscService::GetNetworkTime> { args };\n   auto networkTime = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::seconds(time(NULL))).count();\n   auto unkValue = 1;\n   command.WriteResponse(networkTime, unkValue);\n   return ResultSuccess;\n}\n\nstatic nn::Result\ngetTitleIdOfMainApplication(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<MiscService::GetTitleIdOfMainApplication> { args };\n   auto buffer = OutBuffer<ACPMetaXml> { };\n   auto titleId = ACPTitleId { 0 };\n\n   // TODO: Cache ACPMetaXML of currently loaded title.\n   // TODO: Read title_id from meta.xml?\n\n   command.WriteResponse(titleId);\n   return ResultSuccess;\n}\n\nstatic nn::Result\ngetTitleMetaXml(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<MiscService::GetTitleMetaXml> { args };\n   auto buffer = OutBuffer<ACPMetaXml> { };\n   auto titleId = ACPTitleId { 0 };\n   command.ReadRequest(buffer, titleId);\n\n   // The whole meta xml should be aligned\n   decaf_check(buffer.unalignedAfterBufferSize == 0);\n   decaf_check(buffer.unalignedBeforeBufferSize == 0);\n   auto metaXml = phys_cast<ACPMetaXml *>(buffer.alignedBuffer);\n\n   // TODO: Use titleId to decide which meta.xml file to read...\n\n   return loadMetaXMLFromPath(\"/vol/meta/meta.xml\", metaXml);\n}\n\nnn::Result\nMiscService::commandHandler(uint32_t unk1,\n                            CommandId command,\n                            CommandHandlerArgs &args)\n{\n   switch (command) {\n   case GetNetworkTime::command:\n      return getNetworkTime(args);\n   case GetTitleIdOfMainApplication::command:\n      return getTitleIdOfMainApplication(args);\n   case GetTitleMetaXml::command:\n      return getTitleMetaXml(args);\n   default:\n      return nn::ipc::ResultInvalidMethodTag;\n   }\n}\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_nn_miscservice.h",
    "content": "#pragma once\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"nn/acp/nn_acp_miscservice.h\"\n\nnamespace ios::acp::internal\n{\n\nstruct MiscService : ::nn::acp::services::MiscService\n{\n   static nn::Result\n   commandHandler(uint32_t unk1,\n                  nn::ipc::CommandId command,\n                  nn::ipc::CommandHandlerArgs &args);\n};\n\n} // namespace ios::acp\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_nn_saveservice.cpp",
    "content": "#include \"ios_acp.h\"\n#include \"ios_acp_nn_saveservice.h\"\n\n#include \"ios/ios_stackobject.h\"\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n#include \"ios/nn/ios_nn_ipc_server_command.h\"\n#include \"nn/acp/nn_acp_result.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n\n#include <fmt/core.h>\n\nusing namespace ios::fs;\nusing namespace nn::acp;\n\nusing nn::ipc::CommandHandlerArgs;\nusing nn::ipc::CommandId;\nusing nn::ipc::OutBuffer;\nusing nn::ipc::ServerCommand;\n\nnamespace ios::acp::internal\n{\n\nstatic nn::Result\ntranslateError(FSAStatus status)\n{\n   switch (status) {\n   case FSAStatus::OK:\n      return nn::ResultSuccess;\n   case FSAStatus::NotInit:\n      return ResultNotInitialised;\n   case FSAStatus::Busy:\n      return ResultBusy;\n   case FSAStatus::Cancelled:\n      return ResultCancelled;\n   case FSAStatus::EndOfDir:\n   case FSAStatus::EndOfFile:\n   case FSAStatus::MaxVolumes:\n   case FSAStatus::MaxClients:\n   case FSAStatus::MaxFiles:\n   case FSAStatus::MaxDirs:\n   case FSAStatus::MaxMountpoints:\n   case FSAStatus::OutOfRange:\n   case FSAStatus::OutOfResources:\n      return ResultFsResource;\n   case FSAStatus::NotFound:\n      return ResultNotFound;\n   case FSAStatus::AlreadyOpen:\n      return ResultAlreadyDone;\n   case FSAStatus::AlreadyExists:\n      return ResultAlreadyExists;\n   case FSAStatus::NotEmpty:\n   case FSAStatus::FileTooBig:\n      return ResultInvalidFile;\n   case FSAStatus::DataCorrupted:\n      return ResultDataCorrupted;\n   case FSAStatus::PermissionError:\n      return ResultNoPermission;\n   case FSAStatus::StorageFull:\n      return ResultDeviceFull;\n   case FSAStatus::JournalFull:\n      return ResultJournalFull;\n   case FSAStatus::AccessError:\n      return ResultFileAccessMode;\n   case FSAStatus::UnavailableCmd:\n   case FSAStatus::UnsupportedCmd:\n   case FSAStatus::InvalidParam:\n   case FSAStatus::InvalidPath:\n   case FSAStatus::InvalidBuffer:\n   case FSAStatus::InvalidAlignment:\n   case FSAStatus::InvalidClientHandle:\n   case FSAStatus::InvalidFileHandle:\n   case FSAStatus::InvalidDirHandle:\n   case FSAStatus::NotFile:\n   case FSAStatus::NotDir:\n      return ResultInvalidParameter;\n   case FSAStatus::MediaNotReady:\n      return ResultMediaNotReady;\n   case FSAStatus::MediaError:\n      return ResultMediaBroken;\n   case FSAStatus::WriteProtected:\n      return ResultMediaWriteProtected;\n   default:\n      return ResultFsaFatal;\n   }\n}\n\nstatic nn::Result\nCreateDir(std::string_view path)\n{\n   auto status = FSAMakeDir(getFsaHandle(), path, 0x600);\n   if (status == FSAStatus::OK || status == FSAStatus::AlreadyExists) {\n      return nn::ResultSuccess;\n   }\n\n   return translateError(status);\n}\n\nstatic nn::Result\nMakeQuota(std::string_view path,\n          uint32_t mode,\n          uint64_t size)\n{\n   auto status = FSAMakeQuota(getFsaHandle(), path, mode, size);\n   if (status == FSAStatus::OK || status == FSAStatus::AlreadyExists) {\n      return nn::ResultSuccess;\n   }\n\n   return translateError(status);\n}\n\nstatic std::string\nGetAccountSaveDirPath(uint32_t persistentId,\n                      TitleId titleId)\n{\n   auto titleHi = static_cast<uint32_t>(titleId >> 32);\n   auto titleLo = static_cast<uint32_t>(titleId & 0xFFFFFFFF);\n\n   if (persistentId == 0) {\n      return fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user/common\",\n                         titleHi, titleLo);\n   } else {\n      return fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user/{:08X}\",\n                         titleHi, titleLo, persistentId);\n   }\n}\n\nstatic std::string\nGetAccountBossDirPath(uint32_t persistentId,\n                      TitleId titleId)\n{\n   auto titleHi = static_cast<uint32_t>(titleId >> 32);\n   auto titleLo = static_cast<uint32_t>(titleId & 0xFFFFFFFF);\n\n   if (persistentId == 0) {\n      return fmt::format(\"/vol/storage_mlc01/usr/boss/{:08x}/{:08x}/user/common\",\n                         titleHi, titleLo);\n   } else {\n      return fmt::format(\"/vol/storage_mlc01/usr/boss/{:08x}/{:08x}/user/{:08X}\",\n                         titleHi, titleLo, persistentId);\n   }\n}\n\nstatic nn::Result\nCreateSaveUserDir(TitleId titleId)\n{\n   auto titleHi = static_cast<uint32_t>(titleId >> 32);\n   auto titleLo = static_cast<uint32_t>(titleId & 0xFFFFFFFF);\n\n   auto result = CreateDir(fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}\", titleHi));\n   if (!result) {\n      return result;\n   }\n\n   result = CreateDir(fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}/{:08x}\", titleHi, titleLo));\n   if (!result) {\n      return result;\n   }\n\n   result = CreateDir(fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user\", titleHi, titleLo));\n   if (!result) {\n      return result;\n   }\n\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\nCreateSaveMetaFiles(TitleId titleId)\n{\n   auto titleHi = static_cast<uint32_t>(titleId >> 32);\n   auto titleLo = static_cast<uint32_t>(titleId & 0xFFFFFFFF);\n\n   auto result = CreateDir(fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}\", titleHi));\n   if (!result) {\n      return result;\n   }\n\n   result = CreateDir(fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}/{:08x}\", titleHi, titleLo));\n   if (!result) {\n      return result;\n   }\n\n   result = MakeQuota(fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}/{:08x}/meta\", titleHi, titleLo), 0x640, 0x80000);\n   if (!result) {\n      return result;\n   }\n\n   // TODO: FSAChangeMode(path, 0x640)\n   // TODO: Copy current title meta.xml to save meta\n   // TODO: Copy current title iconTex.tga to save meta\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\nCreateBossStorage(uint32_t persistentId,\n                  TitleId titleId)\n{\n   // TODO: Only create if meta->common_boss_size || meta->account_boss_size > 0\n   auto titleHi = static_cast<uint32_t>(titleId >> 32);\n   auto titleLo = static_cast<uint32_t>(titleId & 0xFFFFFFFF);\n   auto result = CreateDir(fmt::format(\"/vol/storage_mlc01/usr/boss/{:08x}\", titleHi));\n   if (!result) {\n      return result;\n   }\n\n   result = CreateDir(fmt::format(\"/vol/storage_mlc01/usr/boss/{:08x}/{:08x}\", titleHi, titleLo));\n   if (!result) {\n      return result;\n   }\n\n   result = CreateDir(fmt::format(\"/vol/storage_mlc01/usr/boss/{:08x}/{:08x}/user\", titleHi, titleLo));\n   if (!result) {\n      return result;\n   }\n\n   // TODO: Use meta->account_boss_size, only create if > 0\n   result = MakeQuota(GetAccountBossDirPath(persistentId, titleId), 0x600, 0x40000);\n   if (!result) {\n      return result;\n   }\n\n   // TODO: Use meta->common_boss_size, only create if > 0\n   result = MakeQuota(GetAccountBossDirPath(0, titleId), 0x600, 0x40000);\n   if (!result) {\n      return result;\n   }\n\n   // TODO: Change owner to bossOwner\n   auto bossOwner = FSAProcessInfo { };\n   bossOwner.titleId = 0x100000F3ull;\n   bossOwner.processId = ProcessId::NIM;\n   bossOwner.groupId = 0x400u;\n\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\nCreateSaveDirAndBossStorageImpl(uint32_t persistentId,\n                                TitleId titleId)\n{\n   auto result = CreateSaveUserDir(titleId);\n   if (!result) {\n      return result;\n   }\n\n   // TODO: Use meta->account_save_size, only create if > 0\n   result = MakeQuota(GetAccountSaveDirPath(persistentId, titleId), 0x660, 0x40000);\n   if (!result) {\n      return result;\n   }\n\n   // TODO: Use meta->common_save_size, only create if > 0\n   result = MakeQuota(GetAccountSaveDirPath(0, titleId), 0x660, 0x40000);\n   if (!result) {\n      return result;\n   }\n\n   return CreateBossStorage(persistentId, titleId);\n}\n\nstatic nn::Result\nCreateSaveDirAndBossStorage(uint32_t persistentId,\n                            TitleId titleId)\n{\n   auto result = CreateSaveMetaFiles(titleId);\n   if (!result) {\n      return result;\n   }\n\n   result = CreateSaveDirAndBossStorageImpl(persistentId, titleId);\n   if (!result) {\n      return result;\n   }\n\n   /*\n   TODO: UpdateSaveTimeStamp\n      Sends a message to the save timestamp thread which will do the work.\n      Thread updates saveinfo.xml with:\n      <?xml version=\"1.0\" encoding=\"utf-8\"?>\n      <info>\n         <account persistentId=\"00000000\">\n            <timestamp>0000000023cc86be</timestamp>\n         </account>\n      </info>\n   */\n\n   return nn::ResultSuccess;\n}\n\nnn::Result\ncreateSaveDirInternalEx(uint32_t persistentId,\n                        TitleId titleId,\n                        ProcessId processId,\n                        GroupId groupId)\n{\n   return CreateSaveDirAndBossStorage(persistentId, titleId);\n}\n\nstatic nn::Result\ncreateSaveDir(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<SaveService::CreateSaveDir> { args };\n   auto deviceType = ACPDeviceType { };\n   auto persistentId = uint32_t { 0 };\n   command.ReadRequest(persistentId, deviceType);\n\n   // TODO: Check that account with persistentId exists\n   // 0xA0335200 = AccountNotExist\n   return createSaveDirInternalEx(persistentId,\n                                  args.resourceRequest->requestData.titleId,\n                                  args.resourceRequest->requestData.processId,\n                                  args.resourceRequest->requestData.groupId);\n}\n\nstatic nn::Result\ncreateSaveDirEx(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<SaveService::CreateSaveDirEx> { args };\n   auto deviceType = ACPDeviceType { };\n   auto titleId = ACPTitleId { };\n   auto persistentId = uint32_t { 0 };\n   command.ReadRequest(persistentId, titleId, deviceType);\n\n   // TODO: Check that account with persistentId exists\n   // 0xA0335200 = AccountNotExist\n   return createSaveDirInternalEx(persistentId,\n                                  titleId,\n                                  args.resourceRequest->requestData.processId,\n                                  args.resourceRequest->requestData.groupId);\n}\n\nstatic nn::Result\nrepairSaveMetaDir(CommandHandlerArgs &args)\n{\n   // TODO: repairSaveMetaDir\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\nmountSaveDir(CommandHandlerArgs &args)\n{\n   auto titleHi = static_cast<uint32_t>(args.resourceRequest->requestData.titleId >> 32);\n   auto titleLo = static_cast<uint32_t>(args.resourceRequest->requestData.titleId & 0xFFFFFFFF);\n\n   /* Create the save user and common dir just incase it does not exist.\n    * Note: The real MountSaveDir handler in IOS does not create these paths,\n    * only mounts /vol/save, this must mean the initial path creation is done\n    * somewhere else - I do not know where yet.\n    */\n   CreateSaveUserDir(args.resourceRequest->requestData.titleId);\n   CreateDir(GetAccountSaveDirPath(0, args.resourceRequest->requestData.titleId));\n\n   auto processInfo = FSAProcessInfo { };\n   processInfo.processId = args.resourceRequest->requestData.processId;\n   processInfo.titleId = args.resourceRequest->requestData.titleId;\n   processInfo.groupId = args.resourceRequest->requestData.groupId;\n\n   auto fsaStatus =\n      FSAMountWithProcess(getFsaHandle(),\n                          fmt::format(\"/vol/storage_mlc01/usr/save/{:08x}/{:08x}/user\", titleHi, titleLo),\n                          \"/vol/save\", FSAMountPriority::Base,\n                          &processInfo, nullptr, 0u);\n   if (fsaStatus != FSAStatus::OK) {\n      return translateError(fsaStatus);\n   }\n\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\nunmountSaveDir(CommandHandlerArgs &args)\n{\n   auto processInfo = FSAProcessInfo { };\n   processInfo.processId = args.resourceRequest->requestData.processId;\n   processInfo.titleId = args.resourceRequest->requestData.titleId;\n   processInfo.groupId = args.resourceRequest->requestData.groupId;\n\n   auto fsaStatus = FSAUnmountWithProcess(getFsaHandle(), \"/vol/save\",\n                                          FSAMountPriority::UnmountAll,\n                                          &processInfo);\n   if (fsaStatus != FSAStatus::OK) {\n      return translateError(fsaStatus);\n   }\n\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\nisExternalStorageRequired(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<SaveService::IsExternalStorageRequired> { args };\n   command.WriteResponse(0);\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\nmountExternalStorage(CommandHandlerArgs &args)\n{\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\nunmountExternalStorage(CommandHandlerArgs &args)\n{\n   return nn::ResultSuccess;\n}\n\nnn::Result\nSaveService::commandHandler(uint32_t unk1,\n                            CommandId command,\n                            CommandHandlerArgs &args)\n{\n   switch (command) {\n   case CreateSaveDir::command:\n      return createSaveDir(args);\n   case CreateSaveDirEx::command:\n      return createSaveDirEx(args);\n   case IsExternalStorageRequired::command:\n      return isExternalStorageRequired(args);\n   case MountExternalStorage::command:\n      return mountExternalStorage(args);\n   case UnmountExternalStorage::command:\n      return unmountExternalStorage(args);\n   case MountSaveDir::command:\n      return mountSaveDir(args);\n   case UnmountSaveDir::command:\n      return unmountSaveDir(args);\n   case RepairSaveMetaDir::command:\n      return repairSaveMetaDir(args);\n   default:\n      return nn::ipc::ResultInvalidMethodTag;\n   }\n}\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_nn_saveservice.h",
    "content": "#pragma once\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"nn/acp/nn_acp_saveservice.h\"\n\nnamespace ios::acp::internal\n{\n\nstruct SaveService : ::nn::acp::services::SaveService\n{\n   static nn::Result\n   commandHandler(uint32_t unk1,\n                  nn::ipc::CommandId command,\n                  nn::ipc::CommandHandlerArgs &args);\n};\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_nnsm.cpp",
    "content": "#include \"ios_acp_enum.h\"\n#include \"ios_acp_log.h\"\n#include \"ios_acp_nnsm.h\"\n\n#include \"ios/ios_handlemanager.h\"\n#include \"ios/ios_stackobject.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/mcp/ios_mcp_ipc.h\"\n\n#include <libcpu/cpu_formatters.h>\n\nnamespace ios::acp::internal\n{\n\nusing namespace kernel;\n\nconstexpr auto NnsmNumMessages = 0x10u;\nconstexpr auto NnsmThreadStackSize = 0x2000u;\nconstexpr auto NnsmThreadPriority = 50u;\n\nstruct StaticNnsmData\n{\n   be2_val<BOOL> running;\n   be2_val<ThreadId> threadId;\n   be2_val<MessageQueueId> messageQueueId;\n   be2_array<Message, NnsmNumMessages> messageBuffer;\n   be2_array<uint8_t, NnsmThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticNnsmData>\nsNnsmData = nullptr;\n\nstatic Error\nnnsmWaitForResume()\n{\n   StackObject<Message> message;\n\n   // Read Open\n   auto error = IOS_ReceiveMessage(sNnsmData->messageQueueId,\n                                   message,\n                                   MessageFlags::None);\n   if (error < Error::OK) {\n      internal::acpLog->error(\n         \"nnsmWaitForResume: IOS_ReceiveMessage failed for Open with error = {}\", error);\n      return error;\n   }\n\n   auto request = parseMessage<ResourceRequest>(message);\n   if (request->requestData.command != Command::Open ||\n       request->requestData.processId != ProcessId::MCP) {\n      internal::acpLog->error(\n         \"nnsmWaitForResume: Received unexpected message, expected Open from MCP, command = {}, processId = {}\",\n         request->requestData.command, request->requestData.processId);\n      return Error::FailInternal;\n   }\n\n   if (error = IOS_ResourceReply(request, Error::OK); error < Error::OK) {\n      internal::acpLog->error(\n         \"nnsmWaitForResume: IOS_ResourceReply failed for Open with error = {}\", error);\n      return error;\n   }\n\n   // Read Resume\n   error = IOS_ReceiveMessage(sNnsmData->messageQueueId,\n                              message,\n                              MessageFlags::None);\n   if (error < Error::OK) {\n      internal::acpLog->error(\n         \"nnsmWaitForResume: IOS_ReceiveMessage failed for Resume with error = {}\", error);\n      return error;\n   }\n\n   request = parseMessage<ResourceRequest>(message);\n   if (request->requestData.command != Command::Resume ||\n       request->requestData.processId != ProcessId::MCP) {\n      internal::acpLog->error(\n         \"nnsmWaitForResume: Received unexpected message, expected Resume from MCP, command = {}, processId = {}\",\n         request->requestData.command, request->requestData.processId);\n      return Error::FailInternal;\n   }\n\n   // TODO: Send resume message to /dev/nnmisc\n   if (error = IOS_ResourceReply(request, Error::OK); error < Error::OK) {\n      internal::acpLog->error(\n         \"nnsmWaitForResume: IOS_ResourceReply failed for Resume with error = {}\", error);\n      return error;\n   }\n\n   sNnsmData->running = TRUE;\n   return Error::OK;\n}\n\nstatic void\nnnsmIoctl(phys_ptr<ResourceRequest> resourceRequest)\n{\n   switch (static_cast<NssmCommand>(resourceRequest->requestData.args.ioctl.request)) {\n   case NssmCommand::RegisterService:\n   case NssmCommand::UnregisterService:\n      // TODO: Do something with registered / unregistered services.\n      IOS_ResourceReply(resourceRequest, Error::OK);\n      return;\n   default:\n      IOS_ResourceReply(resourceRequest, Error::InvalidArg);\n   }\n}\n\nstatic Error\nnnsmThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n   if (auto error = nnsmWaitForResume(); error < Error::OK) {\n      internal::acpLog->error(\n         \"nnsmThreadEntry: nnsmWaitForResume failed with error = {}\", error);\n      return error;\n   }\n\n   while (sNnsmData->running) {\n      auto error = IOS_ReceiveMessage(sNnsmData->messageQueueId,\n                                      message,\n                                      MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto resourceRequest = parseMessage<ResourceRequest>(message);\n      switch (resourceRequest->requestData.command) {\n      case Command::Open:\n      case Command::Close:\n         IOS_ResourceReply(resourceRequest, Error::OK);\n         break;\n      case Command::Ioctl:\n         nnsmIoctl(resourceRequest);\n         break;\n      case Command::Suspend:\n         sNnsmData->running = FALSE;\n         IOS_ResourceReply(resourceRequest, Error::OK);\n         break;\n      default:\n         IOS_ResourceReply(resourceRequest, Error::InvalidArg);\n      }\n   }\n\n   return Error::OK;\n}\n\nError\nstartNnsm()\n{\n   // Create message queue\n   auto error = IOS_CreateMessageQueue(phys_addrof(sNnsmData->messageBuffer),\n                                       static_cast<uint32_t>(sNnsmData->messageBuffer.size()));\n   if (error < Error::OK) {\n      return error;\n   }\n   sNnsmData->messageQueueId = static_cast<MessageQueueId>(error);\n\n   // Register the device\n   error = mcp::MCP_RegisterResourceManager(\"/dev/nnsm\", sNnsmData->messageQueueId);\n   if (error < Error::OK) {\n      IOS_DestroyMessageQueue(sNnsmData->messageQueueId);\n      return error;\n   }\n\n   // Create thread\n   error = IOS_CreateThread(&nnsmThreadEntry, nullptr,\n                            phys_addrof(sNnsmData->threadStack) + sNnsmData->threadStack.size(),\n                            static_cast<uint32_t>(sNnsmData->threadStack.size()),\n                            NnsmThreadPriority,\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      IOS_DestroyMessageQueue(sNnsmData->messageQueueId);\n      return error;\n   }\n\n   sNnsmData->threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sNnsmData->threadId, \"NnsmThread\");\n\n   return IOS_StartThread(sNnsmData->threadId);\n}\n\nvoid\ninitialiseStaticNnsmData()\n{\n   sNnsmData = allocProcessStatic<StaticNnsmData>();\n}\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_nnsm.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::acp::internal\n{\n\nError\nstartNnsm();\n\nvoid\ninitialiseStaticNnsmData();\n\n} // namespace ios::namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_nnsm_ipc.cpp",
    "content": "#include \"ios_acp_enum.h\"\n#include \"ios_acp_nnsm_ipc.h\"\n\n#include \"ios/ios_enum.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n\n#include <string_view>\n\nusing namespace ios::kernel;\n\nnamespace ios::acp\n{\n\nError\nNNSM_RegisterServer(std::string_view device)\n{\n   // Open nnsm\n   auto error = IOS_Open(\"/dev/nnsm\", OpenMode::None);\n   if (error < Error::OK) {\n      return error;\n   }\n   auto handle = static_cast<ResourceHandleId>(error);\n\n   // Allocate buffer for name\n   auto nameBufferLen = static_cast<uint32_t>(device.size() + 1);\n   auto nameBuffer = IOS_HeapAllocAligned(CrossProcessHeapId, nameBufferLen, 4);\n   std::memcpy(nameBuffer.get(), device.data(), nameBufferLen - 1);\n   phys_cast<char *>(nameBuffer)[nameBufferLen - 1] = char { 0 };\n\n   // Send ioctl\n   error = IOS_Ioctl(handle,\n                     NssmCommand::RegisterService,\n                     nullptr, 0,\n                     nameBuffer, nameBufferLen);\n\n   // Cleanup\n   IOS_Close(handle);\n   IOS_HeapFree(CrossProcessHeapId, nameBuffer);\n   return error;\n}\n\nError\nNNSM_UnregisterServer(std::string_view device)\n{\n   // Open nnsm\n   auto error = IOS_Open(\"/dev/nnsm\", OpenMode::None);\n   if (error < Error::OK) {\n      return error;\n   }\n   auto handle = static_cast<ResourceHandleId>(error);\n\n   // Allocate buffer for name\n   auto nameBufferLen = static_cast<uint32_t>(device.size() + 1);\n   auto nameBuffer = IOS_HeapAllocAligned(CrossProcessHeapId, nameBufferLen, 4);\n   std::memcpy(nameBuffer.get(), device.data(), nameBufferLen - 1);\n   phys_cast<char *>(nameBuffer)[nameBufferLen - 1] = char { 0 };\n\n   // Send ioctl\n   error = IOS_Ioctl(handle,\n                     NssmCommand::UnregisterService,\n                     nullptr, 0,\n                     nameBuffer, nameBufferLen);\n\n   // Cleanup\n   IOS_Close(handle);\n   IOS_HeapFree(CrossProcessHeapId, nameBuffer);\n   return error;\n}\n\n} // namespace ios::acp\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_nnsm_ipc.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include <string_view>\n\nnamespace ios::acp\n{\n\nError\nNNSM_RegisterServer(std::string_view device);\n\nError\nNNSM_UnregisterServer(std::string_view device);\n\n} // namespace ios::acp\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_pdm_cosservice.cpp",
    "content": "#include \"ios_acp_pdm_cosservice.h\"\n\n#include \"ios/nn/ios_nn_ipc_server_command.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n#include \"nn/pdm/nn_pdm_result.h\"\n\n#include <chrono>\n#include <ctime>\n\nusing namespace nn::pdm;\n\nusing nn::ipc::CommandHandlerArgs;\nusing nn::ipc::CommandId;\nusing nn::ipc::OutBuffer;\nusing nn::ipc::ServerCommand;\n\nnamespace ios::acp::internal\n{\n\nstatic nn::Result\ngetPlayDiaryMaxLength(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<PdmCosService::GetPlayDiaryMaxLength> { args };\n   command.WriteResponse(18250);\n   return ResultSuccess;\n}\n\nstatic nn::Result\ngetPlayStatsMaxLength(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<PdmCosService::GetPlayStatsMaxLength> { args };\n   command.WriteResponse(256);\n   return ResultSuccess;\n}\n\nnn::Result\nPdmCosService::commandHandler(uint32_t unk1,\n                              CommandId command,\n                              CommandHandlerArgs &args)\n{\n   switch (command) {\n   case GetPlayDiaryMaxLength::command:\n      return getPlayDiaryMaxLength(args);\n   case GetPlayStatsMaxLength::command:\n      return getPlayStatsMaxLength(args);\n   default:\n      return nn::ipc::ResultInvalidMethodTag;\n   }\n}\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_pdm_cosservice.h",
    "content": "#pragma once\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"nn/pdm/nn_pdm_cosservice.h\"\n\nnamespace ios::acp::internal\n{\n\nstruct PdmCosService : ::nn::pdm::services::CosService\n{\n   static nn::Result\n   commandHandler(uint32_t unk1,\n                  nn::ipc::CommandId command,\n                  nn::ipc::CommandHandlerArgs &args);\n};\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_pdm_server.cpp",
    "content": "#include \"ios_acp_log.h\"\n#include \"ios_acp_pdm_server.h\"\n#include \"ios_acp_pdm_cosservice.h\"\n\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/nn/ios_nn_ipc_server.h\"\n\nnamespace ios::acp::internal\n{\n\nusing namespace kernel;\n\nconstexpr auto PdmNumMessages = 100u;\nconstexpr auto PdmThreadStackSize = 0x4000u;\nconstexpr auto PdmThreadPriority = 50u;\n\nclass PdmServer : public nn::ipc::Server\n{\npublic:\n   PdmServer() :\n      nn::ipc::Server(true)\n   {\n   }\n};\n\nstruct StaticPdmServerData\n{\n   be2_struct<PdmServer> server;\n   be2_array<Message, PdmNumMessages> messageBuffer;\n   be2_array<uint8_t, PdmThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticPdmServerData> sPdmServerData = nullptr;\n\nError\nstartPdmServer()\n{\n   auto &server = sPdmServerData->server;\n   auto result = server.initialise(\"/dev/pdm\",\n                                   phys_addrof(sPdmServerData->messageBuffer),\n                                   static_cast<uint32_t>(sPdmServerData->messageBuffer.size()));\n   if (result.failed()) {\n      internal::acpLog->error(\n         \"startPdmServer: Server initialisation failed for /dev/pdm, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   // TODO: Register services 1, 2\n   server.registerService<PdmCosService>();\n\n   result = server.start(phys_addrof(sPdmServerData->threadStack) + sPdmServerData->threadStack.size(),\n                         static_cast<uint32_t>(sPdmServerData->threadStack.size()),\n                         PdmThreadPriority);\n   if (result.failed()) {\n      internal::acpLog->error(\n         \"startPdmServer: Server start failed for /dev/pdm, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticPdmServerData()\n{\n   sPdmServerData = allocProcessStatic<StaticPdmServerData>();\n}\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_pdm_server.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::acp::internal\n{\n\nError\nstartPdmServer();\n\nvoid\ninitialiseStaticPdmServerData();\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_spm_extendedstorageservice.cpp",
    "content": "#include \"ios_acp_spm_extendedstorageservice.h\"\n\n#include \"ios/nn/ios_nn_ipc_server_command.h\"\n#include \"nn/nn_result.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n\nusing namespace nn::spm;\n\nusing nn::ipc::CommandHandlerArgs;\nusing nn::ipc::CommandId;\nusing nn::ipc::OutBuffer;\nusing nn::ipc::ServerCommand;\n\nnamespace ios::acp::internal\n{\n\nstatic nn::Result\nsetAutoFatal(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<ExtendedStorageService::SetAutoFatal> { args };\n   auto autoFatal = uint8_t { 0 };\n   command.ReadRequest(autoFatal);\n   return nn::ResultSuccess;\n}\n\n\nnn::Result\nExtendedStorageService::commandHandler(uint32_t unk1,\n                                       CommandId command,\n                                       CommandHandlerArgs &args)\n{\n   switch (command) {\n   case SetAutoFatal::command:\n      return setAutoFatal(args);\n   default:\n      return nn::ipc::ResultInvalidMethodTag;\n   }\n}\n\n} // namespace ios::acp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/acp/ios_acp_spm_extendedstorageservice.h",
    "content": "#pragma once\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"nn/spm/nn_spm_extendedstorageservice.h\"\n\nnamespace ios::acp::internal\n{\n\nstruct ExtendedStorageService : ::nn::spm::services::ExtendedStorageService\n{\n   static nn::Result\n   commandHandler(uint32_t unk1,\n                  nn::ipc::CommandId command,\n                  nn::ipc::CommandHandlerArgs &args);\n};\n\n} // namespace ios::acp\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil.cpp",
    "content": "#include \"ios_auxil.h\"\n#include \"ios_auxil_im_device.h\"\n#include \"ios_auxil_im_thread.h\"\n#include \"ios_auxil_usr_cfg_thread.h\"\n#include \"ios_auxil_usr_cfg_service_thread.h\"\n\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n\n#include \"ios/mcp/ios_mcp_ipc.h\"\n\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <common/log.h>\n#include <libcpu/be2_struct.h>\n\nusing namespace ios::kernel;\n\nnamespace ios::auxil\n{\n\nconstexpr auto LocalHeapSize = 0x20000u;\nconstexpr auto CrossHeapSize = 0x80000u;\n\nconstexpr auto NumMessages = 10u;\n\nstruct StaticAuxilData\n{\n   be2_val<MessageQueueId> messageQueueId;\n   be2_array<Message, NumMessages> messageBuffer;\n};\n\nstatic phys_ptr<StaticAuxilData>\nsData = nullptr;\n\nstatic phys_ptr<void>\nsLocalHeapBuffer = nullptr;\n\nnamespace internal\n{\n\nstatic void\ninitialiseStaticData()\n{\n   sData = allocProcessStatic<StaticAuxilData>();\n   sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize);\n}\n\n} // namespace internal\n\nError\nprocessEntryPoint(phys_ptr<void> context)\n{\n   // Initialise static memory\n   internal::initialiseStaticData();\n   internal::initialiseStaticImDeviceData();\n   internal::initialiseStaticImThreadData();\n   internal::initialiseStaticUsrCfgThreadData();\n   internal::initialiseStaticUsrCfgServiceThreadData();\n\n   // Initialise process heaps\n   auto error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize);\n   if (error < Error::OK) {\n      gLog->error(\"AUXIL: Failed to create local process heap, error = {}.\", error);\n      return error;\n   }\n\n   error = IOS_CreateCrossProcessHeap(CrossHeapSize);\n   if (error < Error::OK) {\n      gLog->error(\"AUXIL: Failed to create cross process heap, error = {}.\", error);\n      return error;\n   }\n\n   // Start usr_cfg threads\n   error = internal::startUsrCfgServiceThread();\n   if (error < Error::OK) {\n      gLog->error(\"AUXIL: Failed to start usr_cfg service thread, error = {}.\", error);\n      return error;\n   }\n\n   error = internal::startUsrCfgThread();\n   if (error < Error::OK) {\n      gLog->error(\"AUXIL: Failed to start usr_cfg thread, error = {}.\", error);\n      return error;\n   }\n\n   // Setup auxilproc\n   error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer),\n                                  static_cast<uint32_t>(sData->messageBuffer.size()));\n   if (error < Error::OK) {\n      gLog->error(\"AUXIL: Failed to create auxil proc message queue, error = {}.\", error);\n      return error;\n   }\n   sData->messageQueueId = static_cast<MessageQueueId>(error);\n\n   error = mcp::MCP_RegisterResourceManager(\"/dev/auxilproc\", sData->messageQueueId);\n   if (error < Error::OK) {\n      gLog->error(\"AUXIL: Failed to register /dev/auxilproc, error = {}.\", error);\n      return error;\n   }\n\n   // Run auxilproc thread\n   StackObject<Message> message;\n\n   while (true) {\n      error = IOS_ReceiveMessage(sData->messageQueueId,\n                                 message,\n                                 MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      case Command::Close:\n      {\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      case Command::Suspend:\n      {\n         internal::stopImThread();\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      case Command::Resume:\n      {\n         internal::startImThread();\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      case Command::SvcMsg:\n      {\n         if (request->requestData.args.svcMsg.command == 1u) {\n            // TODO: \"fast relaunch\"\n         }\n\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      default:\n         IOS_ResourceReply(request, Error::Invalid);\n      }\n   }\n}\n\n} // namespace ios::auxil\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace ios::auxil\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::auxil\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_config.cpp",
    "content": "#include \"ios_auxil_config.h\"\n\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <array>\n#include <common/strutils.h>\n#include <iterator>\n#include <pugixml.hpp>\n#include <sstream>\n#include <fmt/format.h>\n\nnamespace ios::auxil\n{\n\nusing namespace ios::fs;\nusing namespace ios::kernel;\n\nstatic std::array<FSAHandle, NumIosProcess>\nsFsaHandles;\n\nError\nopenFsaHandle()\n{\n   auto error = IOS_GetCurrentProcessId();\n   if (error < Error::OK) {\n      return error;\n   }\n   auto pid = static_cast<ProcessId>(error);\n\n   error = FSAOpen();\n   if (error < Error::OK) {\n      return error;\n   }\n   auto handle = static_cast<FSAHandle>(error);\n\n   sFsaHandles[pid] = handle;\n   return Error::OK;\n}\n\nError\ncloseFsaHandle()\n{\n   auto error = IOS_GetCurrentProcessId();\n   if (error < Error::OK) {\n      return error;\n   }\n   auto pid = static_cast<ProcessId>(error);\n\n   error = FSAClose(sFsaHandles[pid]);\n   sFsaHandles[pid] = Error::Invalid;\n   return error;\n}\n\nstatic FSAHandle\ngetFsaHandle()\n{\n   return sFsaHandles.at(IOS_GetCurrentProcessId());\n}\n\nstatic phys_ptr<uint8_t>\nallocFileData(uint32_t size)\n{\n   auto ptr = IOS_HeapAllocAligned(CrossProcessHeapId, size, 0x40u);\n   if (ptr) {\n      std::memset(ptr.get(), 0, size);\n   }\n\n   return phys_cast<uint8_t *>(ptr);\n}\n\nstatic void\nfreeFileData(phys_ptr<uint8_t> fileData,\n             uint32_t size)\n{\n   IOS_HeapFree(CrossProcessHeapId, fileData);\n}\n\nstatic UCError\nreadFile(std::string_view filename,\n         uint32_t *outBytesRead,\n         phys_ptr<uint8_t> *outBuffer)\n{\n   StackObject<FSAStat> stat;\n   FSAFileHandle fileHandle;\n   auto fsaHandle = getFsaHandle();\n   if (!fsaHandle) {\n      openFsaHandle();\n      fsaHandle = getFsaHandle();\n   }\n\n   auto status = FSAOpenFile(fsaHandle, filename, \"r\", &fileHandle);\n   if (status < FSAStatus::OK) {\n      return UCError::FileOpen;\n   }\n\n   status = FSAStatFile(fsaHandle, fileHandle, stat);\n   if (status < FSAStatus::OK) {\n      FSACloseFile(fsaHandle, fileHandle);\n      return UCError::FileStat;\n   }\n\n   auto fileData = allocFileData(stat->size);\n   if (!fileData) {\n      FSACloseFile(fsaHandle, fileHandle);\n      return UCError::Alloc;\n   }\n\n   status = FSAReadFile(fsaHandle,\n                        fileData,\n                        stat->size,\n                        1,\n                        fileHandle,\n                        FSAReadFlag::None);\n   if (status < FSAStatus::OK) {\n      freeFileData(fileData, stat->size);\n      FSACloseFile(fsaHandle, fileHandle);\n      return UCError::FileRead;\n   }\n\n   status = FSACloseFile(fsaHandle, fileHandle);\n   if (status < FSAStatus::OK) {\n      freeFileData(fileData, stat->size);\n      return UCError::FileClose;\n   }\n\n   *outBytesRead = stat->size;\n   *outBuffer = fileData;\n   return UCError::OK;\n}\n\nstatic UCError\nwriteFile(std::string_view filename,\n          phys_ptr<uint8_t> buffer,\n          uint32_t size)\n{\n   StackObject<FSAStat> stat;\n   FSAFileHandle fileHandle;\n   auto fsaHandle = getFsaHandle();\n\n   if (!buffer) {\n      if (FSARemove(fsaHandle, filename) < FSAStatus::OK) {\n         return UCError::FileRemove;\n      }\n\n      return UCError::OK;\n   }\n\n   auto status = FSAOpenFile(fsaHandle, filename, \"w\", &fileHandle);\n   if (status < FSAStatus::OK) {\n      return UCError::FileOpen;\n   }\n\n   status = FSAWriteFile(fsaHandle,\n                         buffer,\n                         size,\n                         1,\n                         fileHandle,\n                         FSAWriteFlag::None);\n   if (status < FSAStatus::OK) {\n      FSACloseFile(fsaHandle, fileHandle);\n      return UCError::FileWrite;\n   }\n\n   status = FSACloseFile(fsaHandle, fileHandle);\n   if (status < FSAStatus::OK) {\n      return UCError::FileClose;\n   }\n\n   return UCError::OK;\n}\n\nstatic const char *\ngetDataTypeName(UCDataType type)\n{\n   switch (type) {\n   case UCDataType::UnsignedByte:\n      return \"unsignedByte\";\n   case UCDataType::UnsignedShort:\n      return \"unsignedShort\";\n   case UCDataType::UnsignedInt:\n      return \"unsignedInt\";\n   case UCDataType::SignedInt:\n      return \"signedInt\";\n   case UCDataType::Float:\n      return \"float\";\n   case UCDataType::String:\n      return \"string\";\n   case UCDataType::HexBinary:\n      return \"hexBinary\";\n   case UCDataType::Complex:\n      return \"complex\";\n   default:\n      return nullptr;\n   }\n}\n\nstatic UCDataType\ngetDataTypeByName(const char *name)\n{\n   if (strcmp(name, \"unsignedByte\") == 0) {\n      return UCDataType::UnsignedByte;\n   } else if (strcmp(name, \"unsignedShort\") == 0) {\n      return UCDataType::UnsignedShort;\n   } else if (strcmp(name, \"unsignedInt\") == 0) {\n      return UCDataType::UnsignedInt;\n   } else if (strcmp(name, \"signedInt\") == 0) {\n      return UCDataType::SignedInt;\n   } else if (strcmp(name, \"float\") == 0) {\n      return UCDataType::Float;\n   } else if (strcmp(name, \"string\") == 0) {\n      return UCDataType::String;\n   } else if (strcmp(name, \"hexBinary\") == 0) {\n      return UCDataType::HexBinary;\n   } else if (strcmp(name, \"complex\") == 0) {\n      return UCDataType::Complex;\n   } else {\n      return UCDataType::Invalid;\n   }\n}\n\nstatic uint32_t\ngetAccessFromString(const char *str)\n{\n   auto access = 0u;\n\n   if (!str || strlen(str) != 3) {\n      return 0;\n   }\n\n   for (auto i = 0u; i < 3; ++i) {\n      auto val = str[i] - '0';\n      if (val < 0 || val > 7) {\n         return 0;\n      }\n\n      access |= val << (4 * i);\n   }\n\n   return access;\n}\n\nstatic std::string\ngetAccessString(uint32_t access)\n{\n   return fmt::format(\"{}{}{}\", (access >> 8) & 0xf, (access >> 4) & 0xf, access & 0xf);\n}\n\nstatic std::string\nto_string(uint8_t *data,\n          size_t size)\n{\n   fmt::memory_buffer out;\n\n   for (auto i = 0u; i < size; ++i) {\n      fmt::format_to(std::back_inserter(out), \"{:02X}\", data[i]);\n   }\n\n   return to_string(out);\n}\n\nUCFileSys\ngetFileSys(std::string_view name)\n{\n   auto index = name.find_first_of(':');\n   if (index == std::string_view::npos) {\n      return UCFileSys::Sys;\n   }\n\n   auto prefix = name.substr(0, index);\n   if (prefix.compare(\"sys\") == 0) {\n      return UCFileSys::Sys;\n   } else if (prefix.compare(\"slc\") == 0) {\n      return UCFileSys::Slc;\n   } else if (prefix.compare(\"ram\") == 0) {\n      return UCFileSys::Ram;\n   }\n\n   return UCFileSys::Invalid;\n}\n\nstatic UCFileSys\ngetFileSys(phys_ptr<UCItem> item)\n{\n   return getFileSys(std::string_view { phys_addrof(item->name).get() });\n}\n\nstd::string_view\ngetRootKey(std::string_view name)\n{\n   auto rootKeyStart = name.find_first_of(':');\n   if (rootKeyStart == std::string_view::npos) {\n      rootKeyStart = 0;\n   } else {\n      rootKeyStart += 1;\n   }\n\n   auto rootKeyEnd = name.find_first_of('.');\n   if (rootKeyEnd == std::string_view::npos) {\n      return name.substr(rootKeyStart);\n   }\n\n   return name.substr(rootKeyStart, rootKeyEnd - rootKeyStart);\n}\n\nstatic std::string_view\ngetRootKey(phys_ptr<UCItem> item)\n{\n   return getRootKey(std::string_view { phys_addrof(item->name).get() });\n}\n\nstatic UCError\ngetConfigPath(phys_ptr<UCItem> item,\n              std::string_view fileSysPath,\n              std::string &path)\n{\n   auto fileSys = getFileSys(item);\n   if (fileSys == UCFileSys::Invalid) {\n      return UCError::InvalidLocation;\n   }\n\n   auto rootKey = getRootKey(item);\n   if (fileSysPath.length() + rootKey.length() + 7 > 64) {\n      return UCError::StringTooLong;\n   }\n\n   path = fileSysPath;\n   path += rootKey;\n   path += \".xml\";\n   return UCError::OK;\n}\n\nstatic UCError\ngetItemPathName(phys_ptr<UCItem> item,\n                std::string &outPath,\n                std::string &outName)\n{\n   auto key = std::string_view { phys_addrof(item->name).get() };\n   auto colonPos = key.find_first_of(':');\n   if (colonPos != std::string_view::npos) {\n      key.remove_prefix(colonPos + 1);\n   }\n\n   auto lastDot = key.find_last_of('.');\n   if (lastDot == std::string_view::npos) {\n      outPath = {};\n      outName = key;\n   } else {\n      outPath = key.substr(0, lastDot);\n      outName = key.substr(lastDot + 1);\n   }\n\n   return UCError::OK;\n}\n\n/**\n * Ensure all items are in the same XML file and root key.\n */\nstatic UCError\ncheckItems(phys_ptr<UCItem> items,\n           uint32_t count)\n\n{\n   auto fileSys = getFileSys(items);\n   if (fileSys == UCFileSys::Invalid) {\n      return UCError::InvalidLocation;\n   }\n\n   auto rootKey = getRootKey(items);\n\n   for (auto i = 0u; i < count; ++i) {\n      auto item = phys_addrof(items[i]);\n\n      if (getFileSys(item) != fileSys ||\n          rootKey.compare(getRootKey(item)) != 0) {\n         return UCError::RootKeysDiffer;\n      }\n   }\n\n   return UCError::OK;\n}\n\n\n/**\n * Read config items from specified file.\n */\nUCError\nreadItemsFromFile(std::string_view path,\n                  phys_ptr<UCItem> items,\n                  uint32_t count,\n                  phys_ptr<IoctlVec> vecs)\n{\n   auto fileSize = uint32_t { 0 };\n   auto fileBuffer = phys_ptr<uint8_t> { nullptr };\n   auto error = readFile(path, &fileSize, &fileBuffer);\n   if (error < UCError::OK) {\n      return error;\n   }\n\n   // Parse XML\n   auto doc = pugi::xml_document { };\n   auto parseResult = doc.load_buffer(fileBuffer.get(), fileSize);\n   freeFileData(fileBuffer, fileSize);\n\n   if (!parseResult) {\n      return UCError::MalformedXML;\n   }\n\n   for (auto i = 0u; i < count; ++i) {\n      auto item = phys_addrof(items[i]);\n      auto itemPath = std::string { };\n      auto itemName = std::string { };\n\n      error = getItemPathName(item, itemPath, itemName);\n      if (error < UCError::OK) {\n         item->error = error;\n         continue;\n      }\n\n      // Convert to a pugixml element path\n      auto elementPath = itemPath;\n      replace_all(elementPath, '.', '/');\n      if (!itemPath.empty()) {\n         elementPath += '/';\n      }\n      elementPath += itemName;\n\n      // Find item in the xml document\n      auto node = doc.first_element_by_path(elementPath.c_str());\n      if (!node) {\n         item->error = UCError::KeyNotFound;\n         continue;\n      }\n\n      // Verify the data type\n      auto nodeType = getDataTypeByName(node.attribute(\"type\").as_string());\n      if (nodeType != item->dataType) {\n         item->error = UCError::InvalidType;\n         continue;\n      }\n\n      // Verify complex data\n      if (item->dataType != UCDataType::Complex && !item->data) {\n         item->error = UCError::InvalidParam;\n         continue;\n      }\n\n      // Read access (TODO: Verify access??)\n      auto nodeAccess = node.attribute(\"access\");\n      if (nodeAccess) {\n         item->access = getAccessFromString(nodeAccess.value());\n      }\n\n      // Read data\n      phys_ptr<void> itemData;\n\n      if (vecs) {\n         itemData = phys_cast<void *>(vecs[i + 1].paddr);\n      } else {\n         itemData = item->data;\n      }\n\n      switch (item->dataType) {\n      case UCDataType::UnsignedByte:\n         if (item->dataSize >= 1) {\n            *phys_cast<uint8_t *>(itemData) = static_cast<uint8_t>(node.text().as_uint());\n         } else {\n            item->error = UCError::InvalidParam;\n            continue;\n         }\n         break;\n      case UCDataType::UnsignedShort:\n         if (item->dataSize >= 2) {\n            *phys_cast<uint16_t *>(itemData) = static_cast<uint16_t>(node.text().as_uint());\n         } else {\n            item->error = UCError::InvalidParam;\n            continue;\n         }\n         break;\n      case UCDataType::UnsignedInt:\n         if (item->dataSize >= 4) {\n            *phys_cast<uint32_t *>(itemData) = static_cast<uint32_t>(node.text().as_uint());\n         } else {\n            item->error = UCError::InvalidParam;\n            continue;\n         }\n         break;\n      case UCDataType::SignedInt:\n         if (item->dataSize >= 4) {\n            *phys_cast<int32_t *>(itemData) = static_cast<int32_t>(node.text().as_int());\n         } else {\n            item->error = UCError::InvalidParam;\n            continue;\n         }\n         break;\n      case UCDataType::Float:\n         if (item->dataSize >= 4) {\n            *phys_cast<float *>(itemData) = node.text().as_float();\n         } else {\n            item->error = UCError::InvalidParam;\n            continue;\n         }\n         break;\n      case UCDataType::String:\n      {\n         auto str = trim(node.text().get());\n         auto size = static_cast<uint32_t>(str.length());\n\n         if (size < item->dataSize) {\n            std::memcpy(item->data.get(), str.data(), size + 1);\n            item->dataSize = size + 1u;\n         } else {\n            item->error = UCError::StringTooLong;\n            continue;\n         }\n         break;\n      }\n      case UCDataType::HexBinary:\n      {\n         auto src = trim(node.text().get());\n         auto size = static_cast<uint32_t>(src.length() / 2u);\n         static auto hexCharToValue =\n            [](char c)\n            {\n               if (c >= 'a' && c <= 'f') {\n                  return (c - 'a') + 10;\n               } else if (c >= 'A' && c <= 'F') {\n                  return (c - 'A') + 10;\n               } else if (c >= '0' && c <= '9') {\n                  return c - '0';\n               } else {\n                  return 0;\n               }\n            };\n\n         if (size <= item->dataSize) {\n            auto dst = phys_cast<uint8_t *>(itemData);\n\n            for (auto j = 0u; j < size; ++j) {\n               auto value = uint8_t { 0 };\n               value |= hexCharToValue(src[j * 2 + 0]) << 4;\n               value |= hexCharToValue(src[j * 2 + 1]);\n               dst[j] = value;\n            }\n\n            item->dataSize = static_cast<uint32_t>(size);\n         } else {\n            item->error = UCError::StringTooLong;\n            continue;\n         }\n         break;\n      }\n      case UCDataType::Complex:\n         break;\n      default:\n         item->error = UCError::InvalidType;\n         continue;\n      }\n\n      item->error = UCError::OK;\n   }\n\n   return UCError::OK;\n}\n\n\n/**\n * Read the given config items.\n */\nUCError\nreadItems(std::string_view fileSysPath,\n          phys_ptr<UCItem> items,\n          uint32_t count,\n          phys_ptr<IoctlVec> vecs)\n{\n   auto error = checkItems(items, count);\n   if (error < UCError::OK) {\n      return error;\n   }\n\n   auto path = std::string { };\n   error = getConfigPath(items, fileSysPath, path);\n   if (error < UCError::OK) {\n      return error;\n   }\n\n   return  readItemsFromFile(path, items, count, vecs);\n}\n\n\n/**\n * Write the given items to file.\n */\nUCError\nwriteItems(std::string_view fileSysPath,\n           phys_ptr<UCItem> items,\n           uint32_t count,\n           phys_ptr<IoctlVec> vecs)\n{\n   pugi::xml_document doc;\n   uint32_t fileSize;\n   phys_ptr<uint8_t> fileBuffer;\n\n   auto error = checkItems(items, count);\n   if (error < UCError::OK) {\n      return error;\n   }\n\n   auto path = std::string { };\n   error = getConfigPath(items, fileSysPath, path);\n   if (error < UCError::OK) {\n      return error;\n   }\n\n   // Try read existing file first\n   error = readFile(path, &fileSize, &fileBuffer);\n   if (error >= UCError::OK) {\n      auto parseResult = doc.load_buffer(fileBuffer.get(), fileSize);\n      freeFileData(fileBuffer, fileSize);\n\n      if (!parseResult &&\n          parseResult.status != pugi::xml_parse_status::status_no_document_element) {\n         return UCError::MalformedXML;\n      }\n   }\n\n   // Apply modifications\n   for (auto i = 0u; i < count; ++i) {\n      auto item = phys_addrof(items[i]);\n      auto keyPath = std::string { };\n      auto keyName = std::string { };\n\n      error = getItemPathName(item, keyPath, keyName);\n      if (error < UCError::OK) {\n         item->error = error;\n         continue;\n      }\n\n      if (keyPath.empty()) {\n         auto node = doc.first_child();\n         if (node) {\n            if (keyName != node.name()) {\n               item->error = UCError::RootKeysDiffer;\n               continue;\n            }\n\n            if (strcmp(node.attribute(\"type\").as_string(), \"complex\")) {\n               item->error = UCError::RootKeysDiffer;\n               continue;\n            }\n         } else {\n            node = doc.append_child(keyName.c_str());\n            node.append_attribute(\"type\").set_value(\"complex\");\n\n            if (item->access) {\n               node.append_attribute(\"access\");\n            }\n         }\n\n         if (item->access) {\n            node.attribute(\"access\").set_value(getAccessString(item->access).c_str());\n         }\n      } else {\n         auto nodePath = keyPath;\n         replace_all(nodePath, '.', '/');\n\n         auto parent = doc.first_element_by_path(nodePath.c_str());\n         if (!parent) {\n            item->error = UCError::KeyNotFound;\n            continue;\n         }\n\n         auto parentType = parent.attribute(\"type\");\n         if (strcmp(parentType.as_string(), \"complex\")) {\n            item->error = UCError::KeyNotFound;\n            continue;\n         }\n\n         auto node = parent.child(keyName.c_str());\n         if (!node) {\n            node = parent.append_child(keyName.c_str());\n            node.append_attribute(\"type\");\n\n            if (item->dataType != UCDataType::Complex) {\n               node.append_attribute(\"length\");\n            }\n\n            if (item->access) {\n               node.append_attribute(\"access\");\n            }\n         }\n\n         auto dataTypeName = getDataTypeName(item->dataType);\n         if (!dataTypeName) {\n            item->error = UCError::InvalidType;\n            continue;\n         }\n\n         node.attribute(\"type\").set_value(dataTypeName);\n\n         if (item->dataType != UCDataType::Complex) {\n            node.attribute(\"length\").set_value(item->dataSize);\n         }\n\n         if (item->access) {\n            node.attribute(\"access\").set_value(getAccessString(item->access).c_str());\n         }\n\n         phys_ptr<void> itemData;\n\n         if (vecs) {\n            itemData = phys_cast<void *>(vecs[i + 1].paddr);\n         } else {\n            itemData = item->data;\n         }\n\n         switch (item->dataType) {\n         case UCDataType::UnsignedByte:\n            if (item->data) {\n               node.text().set(*phys_cast<uint8_t *>(itemData));\n            } else {\n               node.text().set(0);\n            }\n            break;\n         case UCDataType::UnsignedShort:\n            if (item->data) {\n               node.text().set(*phys_cast<uint16_t *>(itemData));\n            } else {\n               node.text().set(0);\n            }\n            break;\n         case UCDataType::UnsignedInt:\n            if (item->data) {\n               node.text().set(*phys_cast<uint32_t *>(itemData));\n            } else {\n               node.text().set(0);\n            }\n            break;\n         case UCDataType::SignedInt:\n            if (item->data) {\n               node.text().set(*phys_cast<int32_t *>(itemData));\n            } else {\n               node.text().set(0);\n            }\n            break;\n         case UCDataType::Float:\n            if (item->data) {\n               node.text().set(*phys_cast<float *>(itemData));\n            } else {\n               node.text().set(0.0f);\n            }\n            break;\n         case UCDataType::String:\n            if (item->data) {\n               // TODO: Check text format, maybe utf8/utf16 etc?\n               node.text().set(phys_cast<char *>(itemData).get());\n            } else {\n               node.text().set(\"\");\n            }\n            break;\n         case UCDataType::HexBinary:\n            if (item->data) {\n               node.text().set(to_string(phys_cast<uint8_t *>(itemData).get(), item->dataSize).c_str());\n            } else {\n               std::string value(static_cast<size_t>(item->dataSize * 2), '0');\n               node.text().set(value.c_str());\n            }\n            break;\n         default:\n            item->error = UCError::InvalidType;\n            continue;\n         }\n      }\n\n      item->error = UCError::OK;\n   }\n\n   // Write modified config to file\n   std::stringstream ss;\n   doc.save(ss, \"  \", 1, pugi::encoding_utf8);\n\n   auto xmlStr = ss.str();\n\n   // Copy to a physical memory buffer\n   fileSize = static_cast<uint32_t>(xmlStr.size());\n   fileBuffer = allocFileData(fileSize);\n   if (!fileBuffer) {\n      return UCError::Alloc;\n   }\n\n   std::memcpy(fileBuffer.get(), xmlStr.data(), xmlStr.size());\n\n   error = writeFile(path, fileBuffer, fileSize);\n   freeFileData(fileBuffer, fileSize);\n   return error;\n}\n\n\n/**\n * List up to count items from xml\n *\n * Sets name, access, dataSize, dataType\n */\nUCError\nlistItems(phys_ptr<UCItem> items,\n          uint32_t count)\n{\n   return UCError::Unsupported;\n}\n\n\n/**\n * Get access, error, dataSize, dataType for specified items\n */\nUCError\nqueryItems(phys_ptr<UCItem> items,\n           uint32_t count)\n{\n   return UCError::Unsupported;\n}\n\n\n/**\n * Delete specific items from the config xml\n */\nUCError\ndeleteItems(std::string_view fileSysPath,\n            phys_ptr<UCItem> items,\n            uint32_t count)\n{\n   auto error = checkItems(items, count);\n   if (error < UCError::OK) {\n      return error;\n   }\n\n   auto path = std::string { };\n   error = getConfigPath(items, fileSysPath, path);\n   if (error < UCError::OK) {\n      return error;\n   }\n\n   uint32_t fileSize;\n   phys_ptr<uint8_t> fileBuffer;\n   error = readFile(path, &fileSize, &fileBuffer);\n   if (error < UCError::OK) {\n      return error;\n   }\n\n   // Parse XML\n   pugi::xml_document doc;\n   auto parseResult = doc.load_buffer(fileBuffer.get(), fileSize);\n   freeFileData(fileBuffer, fileSize);\n\n   if (!parseResult) {\n      return UCError::MalformedXML;\n   }\n\n   for (auto i = 0u; i < count; ++i) {\n      auto item = phys_addrof(items[i]);\n      auto itemPath = std::string { };\n      auto itemName = std::string { };\n\n      error = getItemPathName(item, itemPath, itemName);\n      if (error < UCError::OK) {\n         item->error = error;\n         continue;\n      }\n\n      // Convert to a pugixml element path\n      auto elementPath = itemPath;\n      replace_all(elementPath, '.', '/');\n      if (!itemPath.empty()) {\n         elementPath += '/';\n      }\n      elementPath += itemName;\n\n      // Find item in the xml document\n      auto node = doc.first_element_by_path(elementPath.c_str());\n      if (!node) {\n         item->error = UCError::KeyNotFound;\n         continue;\n      }\n\n      // Verify the data type\n      auto nodeType = getDataTypeByName(node.attribute(\"type\").as_string());\n      if (nodeType != item->dataType) {\n         item->error = UCError::InvalidType;\n         continue;\n      }\n\n      // Verify complex data\n      if (item->dataType != UCDataType::Complex && !item->data) {\n         item->error = UCError::InvalidParam;\n         continue;\n      }\n\n      // Remove node!\n      node.parent().remove_child(node.name());\n      item->error = UCError::OK;\n   }\n\n   // Write modified config to file\n   std::stringstream ss;\n   doc.save(ss, \"  \", 1, pugi::encoding_utf8);\n\n   auto xmlStr = ss.str();\n\n   fileSize = static_cast<uint32_t>(xmlStr.size());\n   fileBuffer = allocFileData(fileSize);\n   if (!fileBuffer) {\n      return UCError::Alloc;\n   }\n\n   std::memcpy(fileBuffer.get(), xmlStr.data(), xmlStr.size());\n\n   error = writeFile(path, fileBuffer, fileSize);\n   freeFileData(fileBuffer, fileSize);\n   return error;\n}\n\n\n/**\n * Delete the whole config!\n */\nUCError\ndeleteRoot(phys_ptr<UCItem> items,\n           uint32_t count)\n{\n   return UCError::Unsupported;\n}\n\n} // namespace ios::auxil\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_config.h",
    "content": "#pragma once\n#include \"ios_auxil_enum.h\"\n#include \"ios/ios_ipc.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n#include <string_view>\n\nnamespace ios::auxil\n{\n\n/**\n * \\ingroup ios_auxil\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct UCItem\n{\n   be2_array<char, 64> name;\n   be2_val<uint32_t> access;\n   be2_val<UCDataType> dataType;\n   be2_val<UCError> error;\n   be2_val<uint32_t> dataSize;\n   be2_phys_ptr<void> data;\n};\nCHECK_OFFSET(UCItem, 0x00, name);\nCHECK_OFFSET(UCItem, 0x40, access);\nCHECK_OFFSET(UCItem, 0x44, dataType);\nCHECK_OFFSET(UCItem, 0x48, error);\nCHECK_OFFSET(UCItem, 0x4C, dataSize);\nCHECK_OFFSET(UCItem, 0x50, data);\nCHECK_SIZE(UCItem, 0x54);\n\n#pragma pack(pop)\n\nError\nopenFsaHandle();\n\nError\ncloseFsaHandle();\n\nUCFileSys\ngetFileSys(std::string_view name);\n\nstd::string_view\ngetRootKey(std::string_view name);\n\nUCError\nreadItemsFromFile(std::string_view path,\n                  phys_ptr<UCItem> items,\n                  uint32_t count,\n                  phys_ptr<IoctlVec> vecs);\n\nUCError\nreadItems(std::string_view fileSysPath,\n          phys_ptr<UCItem> items,\n          uint32_t count,\n          phys_ptr<IoctlVec> vecs);\n\nUCError\nwriteItems(std::string_view fileSysPath,\n           phys_ptr<UCItem> items,\n           uint32_t count,\n           phys_ptr<IoctlVec> vecs);\n\nUCError\ndeleteItems(std::string_view fileSysPath,\n            phys_ptr<UCItem> items,\n            uint32_t count);\n\n/** @} */\n\n} // namespace ios::auxil\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_enum.h",
    "content": "#ifndef IOS_AUXIL_ENUM_H\n#define IOS_AUXIL_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\n\nENUM_NAMESPACE_ENTER(auxil)\n\nENUM_BEG(IMCommand, uint32_t)\n   ENUM_VALUE(CopyParameterFromNv,  0)\n   ENUM_VALUE(SetNvParameter,       1)\n   ENUM_VALUE(SetParameter,         2)\n   ENUM_VALUE(GetParameter,         3)\n   ENUM_VALUE(GetHomeButtonParams,  7)\n   ENUM_VALUE(GetTimerRemaining,    8)\n   ENUM_VALUE(GetNvParameter,       9)\nENUM_END(IMCommand)\n\nENUM_BEG(IMHomeButtonType, uint32_t)\n   ENUM_VALUE(None,                 0)\n   ENUM_VALUE(WiiRemote,            1)\n   ENUM_VALUE(WiiRemoteUrc,         2)\n   ENUM_VALUE(WiiRemoteExtention,   3)\n   ENUM_VALUE(WiiUDrc,              4)\nENUM_END(IMHomeButtonType)\n\nENUM_BEG(IMTimer, uint32_t)\n   ENUM_VALUE(Dim,                  0)\n   ENUM_VALUE(APD,                  1)\nENUM_END(IMTimer)\n\nENUM_BEG(IMParameter, uint32_t)\n   ENUM_VALUE(InactiveSeconds,      0)\n   ENUM_VALUE(DimEnabled,           1)\n   ENUM_VALUE(DimPeriod,            2)\n   ENUM_VALUE(APDEnabled,           3)\n   ENUM_VALUE(APDPeriod,            4)\n   ENUM_VALUE(ResetEnable,          5)\n   ENUM_VALUE(ResetSeconds,         6)\n   ENUM_VALUE(PowerOffEnable,       7)\n   ENUM_VALUE(ApdOccurred,          8)\n   ENUM_VALUE(DimEnableTv,          9)\n   ENUM_VALUE(DimEnableDrc,         10)\n   ENUM_VALUE(Max,                  11)\nENUM_END(IMParameter)\n\nENUM_BEG(UCCommand, uint32_t)\n   ENUM_VALUE(ReadSysConfig,        0x30)\n   ENUM_VALUE(WriteSysConfig,       0x31)\n   ENUM_VALUE(DeleteSysConfig,      0x32)\n   ENUM_VALUE(QuerySysConfig,       0x33)\n   ENUM_VALUE(ListSysConfig,        0x34)\nENUM_END(UCCommand)\n\nENUM_BEG(UCDataType, uint32_t)\n   ENUM_VALUE(Undefined,            0x00)\n   ENUM_VALUE(UnsignedByte,         0x01)\n   ENUM_VALUE(UnsignedShort,        0x02)\n   ENUM_VALUE(UnsignedInt,          0x03)\n   ENUM_VALUE(SignedInt,            0x04)\n   ENUM_VALUE(Float,                0x05)\n   ENUM_VALUE(String,               0x06)\n   ENUM_VALUE(HexBinary,            0x07)\n   ENUM_VALUE(Complex,              0x08)\n   ENUM_VALUE(Invalid,              0xFF)\nENUM_END(UCDataType)\n\nENUM_BEG(UCError, int32_t)\n   ENUM_VALUE(OK,                   0)\n   ENUM_VALUE(Error,                -1)\n   ENUM_VALUE(Other,                -0x200001)\n   ENUM_VALUE(System,               -0x200002)\n   ENUM_VALUE(Alloc,                -0x200003)\n   ENUM_VALUE(Opcode,               -0x200004)\n   ENUM_VALUE(InvalidParam,         -0x200005)\n   ENUM_VALUE(InvalidType,          -0x200006)\n   ENUM_VALUE(Unsupported,          -0x200007)\n   ENUM_VALUE(NonLeafNode,          -0x200008)\n   ENUM_VALUE(KeyNotFound,          -0x200009)\n   ENUM_VALUE(Modify,               -0x20000A)\n   ENUM_VALUE(StringTooLong,        -0x20000B)\n   ENUM_VALUE(RootKeysDiffer,       -0x20000C)\n   ENUM_VALUE(InvalidLocation,      -0x20000D)\n   ENUM_VALUE(BadComment,           -0x20000E)\n   ENUM_VALUE(ReadAccess,           -0x20000F)\n   ENUM_VALUE(WriteAccess,          -0x200010)\n   ENUM_VALUE(CreateAccess,         -0x200011)\n   ENUM_VALUE(FileSysName,          -0x200012)\n   ENUM_VALUE(FileSysInit,          -0x200013)\n   ENUM_VALUE(FileSysMount,         -0x200014)\n   ENUM_VALUE(FileOpen,             -0x200015)\n   ENUM_VALUE(FileStat,             -0x200016)\n   ENUM_VALUE(FileRead,             -0x200017)\n   ENUM_VALUE(FileWrite,            -0x200018)\n   ENUM_VALUE(FileTooBig,           -0x200019)\n   ENUM_VALUE(FileRemove,           -0x20001A)\n   ENUM_VALUE(FileRename,           -0x20001B)\n   ENUM_VALUE(FileClose,            -0x20001C)\n   ENUM_VALUE(FileSeek,             -0x20001D)\n   ENUM_VALUE(FileConfirm,          -0x20001E)\n   ENUM_VALUE(FileBackup,           -0x20001F)\n   ENUM_VALUE(MalformedXML,         -0x200020)\n   ENUM_VALUE(Version,              -0x200021)\n   ENUM_VALUE(NoIPCBuffers,         -0x200022)\n   ENUM_VALUE(FileLockNeeded,       -0x200024)\n   ENUM_VALUE(SysProt,              -0x200028)\nENUM_END(UCError)\n\nENUM_BEG(UCFileSys, uint32_t)\n   ENUM_VALUE(Invalid,              0x00)\n   ENUM_VALUE(Sys,                  0x01)\n   ENUM_VALUE(Slc,                  0x02)\n   ENUM_VALUE(Ram,                  0x03)\nENUM_END(UCFileSys)\n\nENUM_NAMESPACE_EXIT(auxil)\n\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_AUXIL_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_im.h",
    "content": "#pragma once\n#include \"ios_auxil_enum.h\"\n#include \"ios_auxil_im_request.h\"\n#include \"ios_auxil_im_response.h\"\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_im_device.cpp",
    "content": "#include \"ios_auxil_im_device.h\"\n#include \"ios_auxil_usr_cfg_ipc.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n\n#include \"ios/ios_stackobject.h\"\n\n#include <map>\n\nusing namespace ios::kernel;\n\nnamespace ios::auxil::internal\n{\n\nstruct StaticImDeviceData\n{\n   be2_val<UCHandle> ucHandle;\n   be2_array<uint32_t, IMParameter::Max> parameters;\n   be2_array<uint32_t, IMParameter::Max> nvParameters;\n   be2_array<uint32_t, IMParameter::Max> defaultValues;\n};\n\nstatic phys_ptr<StaticImDeviceData>\nsData = nullptr;\n\nstatic std::map<IMParameter, const char *>\nsParameterKey {\n   { IMParameter::InactiveSeconds,  \"inactive_seconds\" },\n   { IMParameter::DimEnabled,       \"dim_enable\" },\n   { IMParameter::DimPeriod,        \"dim_seconds\" },\n   { IMParameter::APDEnabled,       \"apd_enable\" },\n   { IMParameter::APDPeriod,        \"apd_seconds\" },\n   { IMParameter::ResetEnable,      \"reset_enable\" },\n   { IMParameter::ResetSeconds,     \"reset_secnds\" },\n   { IMParameter::PowerOffEnable,   \"power_off_enable\" },\n   { IMParameter::ApdOccurred,      \"apd_occurred\" },\n   { IMParameter::DimEnableTv,      \"dim_tv_enable\" },\n   { IMParameter::DimEnableDrc,     \"dim_drc_enable\" },\n};\n\nError\nIMDevice::copyParameterFromNv()\n{\n   for (auto i = 0u; i < IMParameter::Max; ++i) {\n      if (i == IMParameter::PowerOffEnable) {\n         // Do not copy parameter 7??\n         continue;\n      }\n\n      setParameter(static_cast<IMParameter>(i), phys_addrof(sData->nvParameters[i]));\n   }\n\n   return Error::OK;\n}\n\nError\nIMDevice::getHomeButtonParams(phys_ptr<IMGetHomeButtonParamResponse> response)\n{\n   response->type = IMHomeButtonType::None;\n   response->index = 0;\n   return Error::OK;\n}\n\nError\nIMDevice::getParameter(IMParameter parameter,\n                       phys_ptr<uint32_t> value)\n{\n   if (parameter >= IMParameter::Max) {\n      return Error::Invalid;\n   }\n\n   *value = sData->parameters[parameter];\n   return Error::OK;\n}\n\nError\nIMDevice::getNvParameter(IMParameter parameter,\n                         phys_ptr<uint32_t> value)\n{\n   if (parameter >= IMParameter::Max) {\n      return Error::Invalid;\n   }\n\n   *value = sData->nvParameters[parameter];\n   return Error::OK;\n}\n\nError\nIMDevice::getTimerRemaining(IMTimer timer,\n                            phys_ptr<uint32_t> value)\n{\n   *value = 30u * 60;\n   return Error::OK;\n}\n\nError\nIMDevice::setParameter(IMParameter parameter,\n                       phys_ptr<uint32_t> value)\n{\n   if (parameter >= IMParameter::Max) {\n      return Error::Invalid;\n   }\n\n   sData->parameters[parameter] = *value;\n   return Error::OK;\n}\n\nError\nIMDevice::setNvParameter(IMParameter parameter,\n                         phys_ptr<uint32_t> value)\n{\n   if (parameter >= IMParameter::Max) {\n      return Error::Invalid;\n   }\n\n   sData->nvParameters[parameter] = *value;\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticImDeviceData()\n{\n   sData = allocProcessStatic<StaticImDeviceData>();\n   sData->defaultValues[IMParameter::InactiveSeconds] = 0xAu;\n   sData->defaultValues[IMParameter::DimEnabled]      = 1u;\n   sData->defaultValues[IMParameter::DimPeriod]       = 300u;\n   sData->defaultValues[IMParameter::APDEnabled]      = 1u;\n   sData->defaultValues[IMParameter::APDPeriod]       = 3600u;\n   sData->defaultValues[IMParameter::ResetEnable]     = 0u;\n   sData->defaultValues[IMParameter::ResetSeconds]    = 120u;\n   sData->defaultValues[IMParameter::PowerOffEnable]  = 1u;\n   sData->defaultValues[IMParameter::ApdOccurred]     = 0u;\n   sData->defaultValues[IMParameter::DimEnableTv]     = 1u;\n   sData->defaultValues[IMParameter::DimEnableDrc]    = 1u;\n}\n\nError\ninitialiseImParameters()\n{\n   StackArray<UCSysConfig, IMParameter::Max> sysConfig;\n\n   auto error = UCOpen();\n   if (error < Error::OK) {\n      return Error::Invalid;\n   }\n\n   sData->ucHandle = static_cast<UCHandle>(error);\n\n   for (auto i = 0u; i < IMParameter::Max; ++i) {\n      auto parameter = static_cast<IMParameter>(i);\n      auto &cfg = sysConfig[i];\n      cfg.name = std::string { \"slc:im_cfg.\" } + sParameterKey[parameter];\n      cfg.data = phys_addrof(sData->nvParameters[i]);\n      cfg.dataSize = 4u;\n      cfg.dataType = UCDataType::UnsignedInt;\n      cfg.error = UCError::OK;\n      cfg.access = 0u;\n   }\n\n\n   auto ucError = UCReadSysConfig(sData->ucHandle,\n                                  IMParameter::Max,\n                                  sysConfig);\n   if (ucError < UCError::OK) {\n      return Error::Invalid;\n   }\n\n   auto numErrorParams = 0u;\n\n   for (auto i = 0u; i < IMParameter::Max; ++i) {\n      if (sysConfig[i].error != UCError::OK) {\n         sData->nvParameters[i] = sData->defaultValues[i];\n         ++numErrorParams;\n      }\n   }\n\n   if (numErrorParams > 0) {\n      ucError = UCWriteSysConfig(sData->ucHandle,\n                                 IMParameter::Max,\n                                 sysConfig);\n      if (ucError < UCError::OK) {\n         error = Error::Invalid;\n      }\n   }\n\n   IMDevice device;\n   device.copyParameterFromNv();\n   return error;\n}\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_im_device.h",
    "content": "#pragma once\n#include \"ios_auxil_enum.h\"\n#include \"ios_auxil_im_request.h\"\n#include \"ios_auxil_im_response.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n\nnamespace ios::auxil::internal\n{\n\nclass IMDevice\n{\npublic:\n   Error\n   copyParameterFromNv();\n\n   Error\n   getHomeButtonParams(phys_ptr<IMGetHomeButtonParamResponse> response);\n\n   Error\n   getParameter(IMParameter parameter,\n                phys_ptr<uint32_t> value);\n\n   Error\n   getNvParameter(IMParameter parameter,\n                  phys_ptr<uint32_t> value);\n\n   Error\n   getTimerRemaining(IMTimer timer,\n                     phys_ptr<uint32_t> value);\n\n   Error\n   setParameter(IMParameter parameter,\n                phys_ptr<uint32_t> value);\n\n   Error\n   setNvParameter(IMParameter parameter,\n                  phys_ptr<uint32_t> value);\n\nprivate:\n};\n\nvoid\ninitialiseStaticImDeviceData();\n\nError\ninitialiseImParameters();\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_im_request.h",
    "content": "#pragma once\n#include \"ios_auxil_enum.h\"\n\n#include <cstdint>\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::auxil\n{\n\n/**\n * \\ingroup ios_auxil\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct IMGetParameterRequest\n{\n   be2_val<IMParameter> parameter;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(IMGetParameterRequest, 0x00, parameter);\nCHECK_SIZE(IMGetParameterRequest, 0x08);\n\nstruct IMGetNvParameterRequest\n{\n   be2_val<IMParameter> parameter;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(IMGetNvParameterRequest, 0x00, parameter);\nCHECK_SIZE(IMGetNvParameterRequest, 0x08);\n\nstruct IMGetTimerRemainingRequest\n{\n   be2_val<IMTimer> timer;\n   UNKNOWN(4);\n};\nCHECK_OFFSET(IMGetTimerRemainingRequest, 0x00, timer);\nCHECK_SIZE(IMGetTimerRemainingRequest, 0x08);\n\nstruct IMSetParameterRequest\n{\n   be2_val<IMParameter> parameter;\n   be2_val<uint32_t> value;\n};\nCHECK_OFFSET(IMSetParameterRequest, 0x00, parameter);\nCHECK_OFFSET(IMSetParameterRequest, 0x04, value);\nCHECK_SIZE(IMSetParameterRequest, 0x08);\n\nstruct IMSetNvParameterRequest\n{\n   be2_val<IMParameter> parameter;\n   be2_val<uint32_t> value;\n};\nCHECK_OFFSET(IMSetNvParameterRequest, 0x00, parameter);\nCHECK_OFFSET(IMSetNvParameterRequest, 0x04, value);\nCHECK_SIZE(IMSetNvParameterRequest, 0x08);\n\nstruct IMRequest\n{\n   union\n   {\n      be2_struct<IMGetParameterRequest> getParameter;\n      be2_struct<IMGetNvParameterRequest> getNvParameter;\n      be2_struct<IMGetTimerRemainingRequest>getTimerRemaining;\n      be2_struct<IMSetParameterRequest> setParameter;\n      be2_struct<IMSetNvParameterRequest> setNvParameter;\n   };\n};\nCHECK_OFFSET(IMRequest, 0x00, getParameter);\nCHECK_OFFSET(IMRequest, 0x00, getNvParameter);\nCHECK_OFFSET(IMRequest, 0x00, getTimerRemaining);\nCHECK_OFFSET(IMRequest, 0x00, setParameter);\nCHECK_OFFSET(IMRequest, 0x00, setNvParameter);\nCHECK_SIZE(IMRequest, 0x08);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::auxil\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_im_response.h",
    "content": "#pragma once\n#include \"ios_auxil_enum.h\"\n\n#include <cstdint>\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::auxil\n{\n\n/**\n * \\ingroup ios_auxil\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct IMGetHomeButtonParamResponse\n{\n   be2_val<IMHomeButtonType> type;\n   be2_val<int32_t> index;\n};\nCHECK_OFFSET(IMGetHomeButtonParamResponse, 0x00, type);\nCHECK_OFFSET(IMGetHomeButtonParamResponse, 0x04, index);\n\nstruct IMGetParameterResponse\n{\n   be2_val<IMParameter> parameter;\n   be2_val<uint32_t> value;\n};\nCHECK_OFFSET(IMGetParameterResponse, 0x00, parameter);\nCHECK_OFFSET(IMGetParameterResponse, 0x04, value);\n\nstruct IMGetNvParameterResponse\n{\n   be2_val<IMParameter> parameter;\n   be2_val<uint32_t> value;\n};\nCHECK_OFFSET(IMGetNvParameterResponse, 0x00, parameter);\nCHECK_OFFSET(IMGetNvParameterResponse, 0x04, value);\n\nstruct IMGetTimerRemainingResponse\n{\n   be2_val<IMTimer> timer;\n   be2_val<uint32_t> value;\n};\nCHECK_OFFSET(IMGetTimerRemainingResponse, 0x00, timer);\nCHECK_OFFSET(IMGetTimerRemainingResponse, 0x04, value);\n\nstruct IMResponse\n{\n   union\n   {\n      be2_struct<IMGetHomeButtonParamResponse> getHomeButtonParam;\n      be2_struct<IMGetParameterResponse> getParameter;\n      be2_struct<IMGetNvParameterResponse> getNvParameter;\n      be2_struct<IMGetTimerRemainingResponse> getTimerRemaining;\n   };\n};\nCHECK_OFFSET(IMResponse, 0x00, getHomeButtonParam);\nCHECK_OFFSET(IMResponse, 0x00, getParameter);\nCHECK_OFFSET(IMResponse, 0x00, getNvParameter);\nCHECK_OFFSET(IMResponse, 0x00, getTimerRemaining);\nCHECK_SIZE(IMResponse, 0x08);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::auxil\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_im_thread.cpp",
    "content": "#include \"ios_auxil_im_device.h\"\n#include \"ios_auxil_im_thread.h\"\n\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/kernel/ios_kernel_timer.h\"\n\n#include \"ios/ios_handlemanager.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <memory>\n\nnamespace ios::auxil::internal\n{\n\nusing namespace kernel;\n\nusing IMDeviceHandle = uint32_t;\n\nconstexpr auto MaxNumIMDevices = 96u;\nconstexpr auto ImNumMessages = 20u;\nconstexpr auto ImThreadStackSize = 0x800u;\nconstexpr auto ImThreadPriority = 69u;\n\nstruct StaticImThreadData\n{\n   be2_val<ThreadId> threadId;\n   be2_val<MessageQueueId> messageQueueId;\n   be2_array<Message, ImNumMessages> messageBuffer;\n   be2_array<uint8_t, ImThreadStackSize> threadStack;\n   be2_val<Command> stopMessageBuffer;\n};\n\nstatic phys_ptr<StaticImThreadData>\nsImThreadData;\n\nstatic HandleManager<IMDevice, IMDeviceHandle, MaxNumIMDevices>\nsDevices;\n\nstatic Error\nimDeviceIoctlv(IMDeviceHandle handle,\n               IMCommand command,\n               phys_ptr<IoctlVec> vecs)\n{\n   IMDevice *device = nullptr;\n   auto error = sDevices.get(handle, &device);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto request = phys_cast<IMRequest *>(vecs[0].paddr);\n   auto response = phys_cast<IMResponse *>(vecs[1].paddr);\n\n   switch (command) {\n   case IMCommand::CopyParameterFromNv:\n      error = device->copyParameterFromNv();\n      break;\n   case IMCommand::SetNvParameter:\n      error = device->setNvParameter(request->setNvParameter.parameter,\n                                     phys_addrof(request->setNvParameter.value));\n      break;\n   case IMCommand::SetParameter:\n      error = device->setParameter(request->setParameter.parameter,\n                                   phys_addrof(request->setParameter.value));\n      break;\n   case IMCommand::GetParameter:\n      error = device->getParameter(request->getParameter.parameter,\n                                   phys_addrof(response->getParameter.value));\n      break;\n   case IMCommand::GetHomeButtonParams:\n      error = device->getHomeButtonParams(phys_addrof(response->getHomeButtonParam));\n      break;\n   case IMCommand::GetTimerRemaining:\n      error = device->getTimerRemaining(request->getTimerRemaining.timer,\n                                        phys_addrof(response->getTimerRemaining.value));\n      break;\n   case IMCommand::GetNvParameter:\n      error = device->getNvParameter(request->getNvParameter.parameter,\n                                     phys_addrof(response->getNvParameter.value));\n      break;\n   default:\n      error = Error::InvalidArg;\n   }\n\n   return error;\n}\n\nstatic Error\nimThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n   auto error = Error::OK;\n\n   initialiseImParameters();\n\n   while (true) {\n      error = IOS_ReceiveMessage(sImThreadData->messageQueueId,\n                                 message,\n                                 MessageFlags::None);\n      if (error < Error::OK) {\n         break;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      if (request->requestData.command == Command::IpcMsg2) {\n         // Shutdown message from stopImThread\n         error = Error::OK;\n         break;\n      }\n\n      switch (request->requestData.command) {\n      case Command::Open:\n      {\n         error = sDevices.open();\n         IOS_ResourceReply(request, error);\n         break;\n      }\n\n      case Command::Close:\n      {\n         error = sDevices.close(request->requestData.handle);\n         IOS_ResourceReply(request, error);\n         break;\n      }\n\n      case Command::Ioctlv:\n      {\n         auto handle = static_cast<IMDeviceHandle>(request->requestData.handle);\n         auto command = static_cast<IMCommand>(request->requestData.args.ioctlv.request);\n         error = imDeviceIoctlv(handle,\n                                command,\n                                request->requestData.args.ioctlv.vecs);\n         IOS_ResourceReply(request, error);\n         break;\n      }\n\n      case Command::IpcMsg1:\n      {\n         // Timer message\n         break;\n      }\n\n      default:\n         IOS_ResourceReply(request, Error::InvalidArg);\n      }\n   }\n\n   sDevices.closeAll();\n   // TODO: Stop timer\n   // TODO: Close UsrCfg handle\n   return error;\n}\n\nError\nstartImThread()\n{\n   // Create message queue\n   auto error = IOS_CreateMessageQueue(phys_addrof(sImThreadData->messageBuffer),\n                                       static_cast<uint32_t>(sImThreadData->messageBuffer.size()));\n   if (error < Error::OK) {\n      return error;\n   }\n   sImThreadData->messageQueueId = static_cast<MessageQueueId>(error);\n\n   // Register /dev/im\n   error = IOS_RegisterResourceManager(\"/dev/im\", sImThreadData->messageQueueId);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Create thread\n   error = IOS_CreateThread(&imThreadEntry, nullptr,\n                            phys_addrof(sImThreadData->threadStack) + sImThreadData->threadStack.size(),\n                            static_cast<uint32_t>(sImThreadData->threadStack.size()),\n                            ImThreadPriority,\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      IOS_DestroyMessageQueue(sImThreadData->messageQueueId);\n      return error;\n   }\n\n   sImThreadData->threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sImThreadData->threadId, \"ImThread\");\n\n   return IOS_StartThread(sImThreadData->threadId);\n}\n\nError\nstopImThread()\n{\n   sImThreadData->stopMessageBuffer = Command::IpcMsg2;\n\n   auto message = makeMessage(phys_addrof(sImThreadData->stopMessageBuffer));\n   auto error = IOS_SendMessage(sImThreadData->messageQueueId, message, MessageFlags::None);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = IOS_JoinThread(sImThreadData->threadId, nullptr);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = IOS_DestroyMessageQueue(sImThreadData->messageQueueId);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticImThreadData()\n{\n   sImThreadData = allocProcessStatic<StaticImThreadData>();\n   sDevices.closeAll();\n}\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_im_thread.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::auxil::internal\n{\n\nError\nstartImThread();\n\nError\nstopImThread();\n\nvoid\ninitialiseStaticImThreadData();\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg.h",
    "content": "#pragma once\n#include \"ios_auxil_enum.h\"\n#include \"ios_auxil_usr_cfg_request.h\"\n#include \"ios_auxil_usr_cfg_types.h\"\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_device.cpp",
    "content": "#include \"ios_auxil_config.h\"\n#include \"ios_auxil_usr_cfg_device.h\"\n#include \"ios_auxil_usr_cfg_fs.h\"\n#include \"ios_auxil_usr_cfg_service_thread.h\"\n\n#include <array>\n#include <common/strutils.h>\n#include <fmt/core.h>\n#include <pugixml.hpp>\n#include <sstream>\n#include <string_view>\n\nnamespace ios::auxil::internal\n{\n\nstatic std::array<const char *, 66>\nsValidRootKeys =\n{\n   \"AVMCfg\",\n   \"DRCCfg\",\n   \"NETCfg0\",\n   \"NETCfg1\",\n   \"NETCfg2\",\n   \"NETCfg3\",\n   \"NETCfg4\",\n   \"NETCfg5\",\n   \"NETCmn\",\n   \"audio\",\n   \"avm_cdc\",\n   \"avmflg\",\n   \"avmStat\",\n   \"btStd\",\n   \"cafe\",\n   \"ccr\",\n   \"ccr_all\",\n   \"coppa\",\n   \"cos\",\n   \"dc_state\",\n   \"im_cfg\",\n   \"nn\",\n   \"nn_ram\",\n   \"hbprefs\",\n   \"p_acct1\",\n   \"p_acct10\",\n   \"p_acct11\",\n   \"p_acct12\",\n   \"p_acct2\",\n   \"p_acct3\",\n   \"p_acct4\",\n   \"p_acct5\",\n   \"p_acct6\",\n   \"p_acct7\",\n   \"p_acct8\",\n   \"p_acct9\",\n   \"parent\",\n   \"PCFSvTCP\",\n   \"console\",\n   \"rmtCfg\",\n   \"rootflg\",\n   \"spotpass\",\n   \"tvecfg\",\n   \"tveEDID\",\n   \"tveHdmi\",\n   \"uvdflag\",\n   \"wii_acct\",\n   \"ums\",\n   \"testProc\",\n   \"clipbd\",\n   \"hdmiEDID\",\n   \"caffeine\",\n   \"DRCDev\",\n   \"hai_sys\",\n   \"s_acct01\",\n   \"s_acct02\",\n   \"s_acct03\",\n   \"s_acct04\",\n   \"s_acct05\",\n   \"s_acct06\",\n   \"s_acct07\",\n   \"s_acct08\",\n   \"s_acct09\",\n   \"s_acct10\",\n   \"s_acct11\",\n   \"s_acct12\",\n};\n\nstatic bool\nisValidRootKey(std::string_view key)\n{\n   for (auto validKey : sValidRootKeys) {\n      if (key.compare(validKey) == 0) {\n         return true;\n      }\n   }\n\n   return false;\n}\n\nstatic std::string_view\ngetFileSysPath(UCFileSys fileSys)\n{\n   switch (fileSys) {\n   case UCFileSys::Sys:\n      return \"/vol/sys/proc/prefs/\";\n   case UCFileSys::Slc:\n      return \"/vol/sys/proc_slc/prefs/\";\n   case UCFileSys::Ram:\n      return \"/vol/sys/proc_ram/prefs/\";\n   default:\n      return \"*error*\";\n   }\n}\n\nvoid\nUCDevice::setCloseRequest(phys_ptr<kernel::ResourceRequest> closeRequest)\n{\n   mCloseRequest = closeRequest;\n}\n\nvoid\nUCDevice::incrementRefCount()\n{\n   mRefCount++;\n}\n\nvoid\nUCDevice::decrementRefCount()\n{\n   mRefCount--;\n\n   if (mRefCount == 0) {\n      if (mCloseRequest) {\n         kernel::IOS_ResourceReply(mCloseRequest, Error::OK);\n      }\n\n      mCloseRequest = nullptr;\n      destroyUCDevice(this);\n   }\n}\n\nUCError\nUCDevice::deleteSysConfig(uint32_t numVecIn,\n                          phys_ptr<IoctlVec> vecs)\n{\n   auto request = phys_cast<UCReadSysConfigRequest *>(vecs[0].paddr);\n   auto items = phys_cast<UCItem *>(phys_addrof(request->settings[0]));\n\n   if (request->count == 0) {\n      return UCError::OK;\n   }\n\n   auto name = std::string_view { phys_addrof(request->settings[0].name).get() };\n   auto fileSys = getFileSys(name);\n   if (fileSys == UCFileSys::Invalid) {\n      return UCError::InvalidLocation;\n   }\n\n   auto rootKey = getRootKey(name);\n   if (!isValidRootKey(rootKey)) {\n      return UCError::FileSysName;\n   }\n\n   return deleteItems(getFileSysPath(fileSys), items, request->count);\n}\n\nUCError\nUCDevice::readSysConfig(uint32_t numVecIn,\n                        phys_ptr<IoctlVec> vecs)\n{\n   auto request = phys_cast<UCReadSysConfigRequest *>(vecs[0].paddr);\n   auto items = phys_cast<UCItem *>(phys_addrof(request->settings[0]));\n\n   if (request->count == 0) {\n      return UCError::OK;\n   }\n\n   auto name = std::string_view { phys_addrof(request->settings[0].name).get() };\n   auto fileSys = getFileSys(name);\n   if (fileSys == UCFileSys::Invalid) {\n      return UCError::InvalidLocation;\n   }\n\n   auto rootKey = getRootKey(name);\n   if (!isValidRootKey(rootKey)) {\n      return UCError::FileSysName;\n   }\n\n   return readItems(getFileSysPath(fileSys), items, request->count, vecs);\n}\n\nUCError\nUCDevice::writeSysConfig(uint32_t numVecIn,\n                         phys_ptr<IoctlVec> vecs)\n{\n   auto request = phys_cast<UCWriteSysConfigRequest *>(vecs[0].paddr);\n   auto items = phys_cast<UCItem *>(phys_addrof(request->settings[0]));\n\n   if (request->count == 0) {\n      return UCError::OK;\n   }\n\n   auto name = std::string_view { phys_addrof(request->settings[0].name).get() };\n   auto fileSys = getFileSys(name);\n   if (fileSys == UCFileSys::Invalid) {\n      return UCError::InvalidLocation;\n   }\n\n   auto rootKey = getRootKey(name);\n   if (!isValidRootKey(rootKey)) {\n      return UCError::FileSysName;\n   }\n\n   return writeItems(getFileSysPath(fileSys), items, request->count, vecs);\n}\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_device.h",
    "content": "#pragma once\n#include \"ios_auxil_usr_cfg_types.h\"\n#include \"ios_auxil_usr_cfg_request.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n\nnamespace ios::auxil::internal\n{\n\nclass UCDevice\n{\npublic:\n   void\n   setCloseRequest(phys_ptr<kernel::ResourceRequest> closeRequest);\n\n   void\n   incrementRefCount();\n\n   void\n   decrementRefCount();\n\n   UCError\n   deleteSysConfig(uint32_t numVecIn,\n                   phys_ptr<IoctlVec> vecs);\n\n   UCError\n   readSysConfig(uint32_t numVecIn,\n                 phys_ptr<IoctlVec> vecs);\n\n   UCError\n   writeSysConfig(uint32_t numVecIn,\n                  phys_ptr<IoctlVec> vecs);\n\nprivate:\n   int mRefCount = 1;\n   phys_ptr<kernel::ResourceRequest> mCloseRequest = nullptr;\n};\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_fs.cpp",
    "content": "#include \"ios_auxil_usr_cfg_fs.h\"\n\n#include \"ios/ios_stackobject.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n\nusing namespace ios::fs;\nusing namespace ios::kernel;\n\nnamespace ios::auxil::internal\n{\n\nstatic FSAHandle\nsFsaHandle;\n\nError\nUCInitFSA()\n{\n   auto error = FSAOpen();\n   if (error < Error::OK) {\n      return Error::NoResource;\n   }\n\n   sFsaHandle = static_cast<ResourceHandleId>(error);\n   return Error::OK;\n}\n\n\nphys_ptr<uint8_t>\nUCAllocFileData(uint32_t size)\n{\n   auto ptr = IOS_HeapAllocAligned(CrossProcessHeapId, size, 0x40u);\n   if (ptr) {\n      std::memset(ptr.get(), 0, size);\n   }\n\n   return phys_cast<uint8_t *>(ptr);\n}\n\n\nvoid\nUCFreeFileData(phys_ptr<uint8_t> fileData,\n               uint32_t size)\n{\n   IOS_HeapFree(CrossProcessHeapId, fileData);\n}\n\n\nUCError\nUCReadConfigFile(std::string_view filename,\n                 uint32_t *outSize,\n                 phys_ptr<uint8_t> *outData)\n{\n   StackObject<FSAStat> stat;\n   FSAFileHandle fileHandle;\n\n   auto status = FSAOpenFile(sFsaHandle, filename, \"r\", &fileHandle);\n   if (status < FSAStatus::OK) {\n      return UCError::FileOpen;\n   }\n\n   status = FSAStatFile(sFsaHandle, fileHandle, stat);\n   if (status < FSAStatus::OK) {\n      FSACloseFile(sFsaHandle, fileHandle);\n      return UCError::FileStat;\n   }\n\n   auto fileData = UCAllocFileData(stat->size);\n   if (!fileData) {\n      FSACloseFile(sFsaHandle, fileHandle);\n      return UCError::Alloc;\n   }\n\n   status = FSAReadFile(sFsaHandle,\n                        fileData,\n                        stat->size,\n                        1,\n                        fileHandle,\n                        FSAReadFlag::None);\n   if (status < FSAStatus::OK) {\n      UCFreeFileData(fileData, stat->size);\n      FSACloseFile(sFsaHandle, fileHandle);\n      return UCError::FileRead;\n   }\n\n   status = FSACloseFile(sFsaHandle, fileHandle);\n   if (status < FSAStatus::OK) {\n      UCFreeFileData(fileData, stat->size);\n      return UCError::FileClose;\n   }\n\n   *outSize = stat->size;\n   *outData = fileData;\n   return UCError::OK;\n}\n\n\nUCError\nUCWriteConfigFile(std::string_view filename,\n                  phys_ptr<uint8_t> buffer,\n                  uint32_t size)\n{\n   StackObject<FSAStat> stat;\n   FSAFileHandle fileHandle;\n\n   if (!buffer) {\n      // REMOVE\n   }\n\n   auto status = FSAOpenFile(sFsaHandle, filename, \"w\", &fileHandle);\n   if (status < FSAStatus::OK) {\n      return UCError::FileOpen;\n   }\n\n\n   status = FSAWriteFile(sFsaHandle,\n                         buffer,\n                         size,\n                         1,\n                         fileHandle,\n                         FSAWriteFlag::None);\n   if (status < FSAStatus::OK) {\n      FSACloseFile(sFsaHandle, fileHandle);\n      return UCError::FileWrite;\n   }\n\n   status = FSACloseFile(sFsaHandle, fileHandle);\n   if (status < FSAStatus::OK) {\n      return UCError::FileClose;\n   }\n\n   return UCError::OK;\n}\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_fs.h",
    "content": "#pragma once\n#include \"ios_auxil_enum.h\"\n#include \"ios/ios_enum.h\"\n\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nnamespace ios::auxil::internal\n{\n\nError\nUCInitFSA();\n\nphys_ptr<uint8_t>\nUCAllocFileData(uint32_t size);\n\nvoid\nUCFreeFileData(phys_ptr<uint8_t> fileData,\n               uint32_t size);\n\nUCError\nUCReadConfigFile(std::string_view filename,\n                 uint32_t *outBytesRead,\n                 phys_ptr<uint8_t> *outBuffer);\n\nUCError\nUCWriteConfigFile(std::string_view filename,\n                  phys_ptr<uint8_t> buffer,\n                  uint32_t size);\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_ipc.cpp",
    "content": "#include \"ios_auxil_usr_cfg_ipc.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n\nnamespace ios::auxil\n{\n\nusing namespace kernel;\n\nusing UCHandle = kernel::ResourceHandleId;\n\nstatic UCError\nallocIpcData(uint32_t size,\n             phys_ptr<void> *outData)\n{\n   auto buffer = IOS_HeapAlloc(CrossProcessHeapId,\n                               size);\n\n   if (!buffer) {\n      return UCError::Alloc;\n   }\n\n   std::memset(buffer.get(), 0, size);\n   *outData = buffer;\n   return UCError::OK;\n}\n\nstatic void\nfreeIpcData(phys_ptr<void> ipcData)\n{\n   IOS_HeapFree(CrossProcessHeapId, ipcData);\n}\n\nError\nUCOpen()\n{\n   return IOS_Open(\"/dev/usr_cfg\", OpenMode::None);\n}\n\nError\nUCClose(UCHandle handle)\n{\n   return IOS_Close(handle);\n}\n\nUCError\nUCReadSysConfig(UCHandle handle,\n                uint32_t count,\n                phys_ptr<UCSysConfig> settings)\n{\n   phys_ptr<void> vecBuffer = nullptr;\n   phys_ptr<void> reqBuffer = nullptr;\n   phys_ptr<UCReadSysConfigRequest> request = nullptr;\n   phys_ptr<IoctlVec> vecs = nullptr;\n   auto vecBufSize = static_cast<uint32_t>((1 + count) * sizeof(IoctlVec));\n   auto reqBufSize = static_cast<uint32_t>(count * sizeof(UCSysConfig) + sizeof(UCReadSysConfigRequest));\n\n   auto error = allocIpcData(vecBufSize, &vecBuffer);\n   if (error < UCError::OK) {\n      goto out;\n   }\n\n   error = allocIpcData(reqBufSize, &reqBuffer);\n   if (error < UCError::OK) {\n      goto out;\n   }\n\n   request = phys_cast<UCReadSysConfigRequest *>(reqBuffer);\n   request->unk0x00 = 0u;\n   request->count = count;\n   std::memcpy(request->settings, settings.get(), sizeof(UCSysConfig) * count);\n\n   vecs = phys_cast<IoctlVec *>(vecBuffer);\n   vecs[0].len = reqBufSize;\n   vecs[0].paddr = phys_cast<phys_addr>(reqBuffer);\n\n   for (auto i = 0u; i < count; ++i) {\n      auto size = settings[i].dataSize;\n      vecs[1 + i].len = size;\n\n      if (size) {\n         phys_ptr<void> dataBuffer;\n         error = allocIpcData(vecBufSize, &dataBuffer);\n         if (error < UCError::OK) {\n            goto out;\n         }\n\n         vecs[1 + i].paddr = phys_cast<phys_addr>(dataBuffer);\n      } else {\n         vecs[1 + i].paddr = phys_addr { 0u };\n      }\n   }\n\n   error = static_cast<UCError>(IOS_Ioctlv(handle,\n                                           UCCommand::ReadSysConfig,\n                                           0,\n                                           1 + count,\n                                           vecs));\n\n   for (auto i = 0u; i < count; ++i) {\n      settings[i].error = request->settings[i].error;\n\n      if (auto len = vecs[i + 1].len) {\n         auto dst = phys_cast<void *>(settings[i].data);\n         auto src = phys_cast<const void *>(vecs[i + 1].paddr);\n         std::memcpy(dst.get(), src.get(), len);\n      }\n   }\n\nout:\n   for (auto i = 0u; i < count; ++i) {\n      if (vecs[i + 1].paddr) {\n         freeIpcData(phys_cast<void *>(vecs[i + 1].paddr));\n      }\n   }\n\n   if (vecBuffer) {\n      freeIpcData(vecBuffer);\n   }\n\n   if (reqBuffer) {\n      freeIpcData(reqBuffer);\n   }\n\n   return error;\n}\n\nUCError\nUCWriteSysConfig(UCHandle handle,\n                 uint32_t count,\n                 phys_ptr<UCSysConfig> settings)\n{\n   phys_ptr<void> vecBuffer = nullptr;\n   phys_ptr<void> reqBuffer = nullptr;\n   phys_ptr<UCWriteSysConfigRequest> request = nullptr;\n   phys_ptr<IoctlVec> vecs = nullptr;\n   auto vecBufSize = static_cast<uint32_t>((1 + count) * sizeof(IoctlVec));\n   auto reqBufSize = static_cast<uint32_t>(count * sizeof(UCSysConfig) + sizeof(UCReadSysConfigRequest));\n\n   auto error = allocIpcData(vecBufSize, &vecBuffer);\n   if (error < UCError::OK) {\n      goto out;\n   }\n\n   error = allocIpcData(reqBufSize, &reqBuffer);\n   if (error < UCError::OK) {\n      goto out;\n   }\n\n   request = phys_cast<UCWriteSysConfigRequest *>(reqBuffer);\n   request->unk0x00 = 0u;\n   request->count = count;\n   std::memcpy(request->settings, settings.get(), sizeof(UCSysConfig) * count);\n\n   vecs = phys_cast<IoctlVec *>(vecBuffer);\n   vecs[0].len = reqBufSize;\n   vecs[0].paddr = phys_cast<phys_addr>(reqBuffer);\n\n   for (auto i = 0u; i < count; ++i) {\n      auto size = settings[i].dataSize;\n      vecs[1 + i].len = size;\n\n      if (size) {\n         phys_ptr<void> dataBuffer;\n         error = allocIpcData(vecBufSize, &dataBuffer);\n         if (error < UCError::OK) {\n            goto out;\n         }\n\n         vecs[1 + i].paddr = phys_cast<phys_addr>(dataBuffer);\n\n         auto src = phys_cast<const void *>(settings[i].data);\n         auto dst = phys_cast<void *>(vecs[i + 1].paddr);\n         std::memcpy(dst.get(), src.get(), size);\n      } else {\n         vecs[1 + i].paddr = phys_addr { 0u };\n      }\n   }\n\n   error = static_cast<UCError>(IOS_Ioctlv(handle,\n                                           UCCommand::WriteSysConfig,\n                                           0,\n                                           1 + count,\n                                           vecs));\n\n   for (auto i = 0u; i < count; ++i) {\n      settings[i].error = request->settings[i].error;\n   }\n\nout:\n   for (auto i = 0u; i < count; ++i) {\n      if (vecs[i + 1].paddr) {\n         freeIpcData(phys_cast<void *>(vecs[i + 1].paddr));\n      }\n   }\n\n   if (vecBuffer) {\n      freeIpcData(vecBuffer);\n   }\n\n   if (reqBuffer) {\n      freeIpcData(reqBuffer);\n   }\n\n   return error;\n}\n\n} // namespace ios::auxil\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_ipc.h",
    "content": "#pragma once\n#include \"ios_auxil_usr_cfg.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n\nnamespace ios::auxil\n{\n\nusing UCHandle = kernel::ResourceHandleId;\n\nError\nUCOpen();\n\nError\nUCClose(UCHandle handle);\n\nUCError\nUCReadSysConfig(UCHandle handle,\n                uint32_t count,\n                phys_ptr<UCSysConfig> settings);\n\nUCError\nUCWriteSysConfig(UCHandle handle,\n                 uint32_t count,\n                 phys_ptr<UCSysConfig> settings);\n\n} // namespace ios::auxil\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_request.h",
    "content": "#pragma once\n#include \"ios_auxil_enum.h\"\n#include \"ios_auxil_usr_cfg_types.h\"\n\n#include <cstdint>\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::auxil\n{\n\n/**\n * \\ingroup ios_auxil\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct UCDeleteSysConfigRequest\n{\n   be2_val<uint32_t> unk0x00;\n   be2_val<uint32_t> count;\n   be2_struct<UCSysConfig> settings[1]; // Size=N\n};\nCHECK_OFFSET(UCDeleteSysConfigRequest, 0x0, unk0x00);\nCHECK_OFFSET(UCDeleteSysConfigRequest, 0x4, count);\nCHECK_OFFSET(UCDeleteSysConfigRequest, 0x8, settings);\nCHECK_SIZE(UCDeleteSysConfigRequest, 0x5C);\n\nstruct UCReadSysConfigRequest\n{\n   be2_val<uint32_t> unk0x00;\n   be2_val<uint32_t> count;\n   be2_struct<UCSysConfig> settings[1]; // Size=N\n};\nCHECK_OFFSET(UCReadSysConfigRequest, 0x0, unk0x00);\nCHECK_OFFSET(UCReadSysConfigRequest, 0x4, count);\nCHECK_OFFSET(UCReadSysConfigRequest, 0x8, settings);\nCHECK_SIZE(UCReadSysConfigRequest, 0x5C);\n\nstruct UCWriteSysConfigRequest\n{\n   be2_val<uint32_t> unk0x00;\n   be2_val<uint32_t> count;\n   be2_struct<UCSysConfig> settings[1]; // Size=N\n};\nCHECK_OFFSET(UCWriteSysConfigRequest, 0x0, unk0x00);\nCHECK_OFFSET(UCWriteSysConfigRequest, 0x4, count);\nCHECK_OFFSET(UCWriteSysConfigRequest, 0x8, settings);\nCHECK_SIZE(UCWriteSysConfigRequest, 0x5C);\n\nstruct UCRequest\n{\n   union\n   {\n      be2_struct<UCDeleteSysConfigRequest> deleteSysConfigRequest;\n      be2_struct<UCReadSysConfigRequest> readSysConfigRequest;\n      be2_struct<UCWriteSysConfigRequest> writeSysConfigRequest;\n   };\n};\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace namespace ios::auxil\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_service_thread.cpp",
    "content": "#include \"ios_auxil_usr_cfg_thread.h\"\n#include \"ios_auxil_usr_cfg_service_thread.h\"\n\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include \"ios/mcp/ios_mcp_ipc.h\"\n\n#include \"ios/ios_handlemanager.h\"\n#include \"ios/ios_stackobject.h\"\n\nnamespace ios::auxil::internal\n{\n\nusing namespace kernel;\n\nconstexpr auto UCServiceNumMessages = 10u;\nconstexpr auto UCServiceThreadStackSize = 0x2000u;\nconstexpr auto UCServiceThreadPriority = 70u;\nconstexpr auto MaxNumDevices = 96u;\n\nstruct StaticUsrCfgServiceData\n{\n   be2_val<bool> serviceRunning;\n   be2_val<ThreadId> threadId;\n   be2_val<MessageQueueId> messageQueueId;\n   be2_array<Message, UCServiceNumMessages> messageBuffer;\n   be2_array<uint8_t, UCServiceThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticUsrCfgServiceData>\nsData = nullptr;\n\nstatic HandleManager<UCDevice, UCDeviceHandle, MaxNumDevices>\nsDevices;\n\nvoid\ndestroyUCDevice(UCDevice *device)\n{\n   sDevices.close(device);\n}\n\nError\ngetUCDevice(UCDeviceHandle handle,\n            UCDevice **outDevice)\n{\n   return sDevices.get(handle, outDevice);\n}\n\nstatic Error\nusrCfgServiceThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n\n   while (true) {\n      auto error = IOS_ReceiveMessage(sData->messageQueueId,\n                                      message,\n                                      MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      {\n         error = sDevices.open();\n         IOS_ResourceReply(request, error);\n         break;\n      }\n\n      case Command::Close:\n      {\n         UCDevice *device = nullptr;\n         auto handle = static_cast<UCDeviceHandle>(request->requestData.handle);\n         error = sDevices.get(handle, &device);\n         if (error < Error::OK) {\n            IOS_ResourceReply(request, error);\n            continue;\n         }\n\n         device->setCloseRequest(request);\n         device->decrementRefCount();\n         break;\n      }\n\n      case Command::Ioctlv:\n      {\n         UCDevice *device = nullptr;\n\n         if (!sData->serviceRunning) {\n            IOS_ResourceReply(request, Error::NotReady);\n            continue;\n         }\n\n         auto handle = static_cast<UCDeviceHandle>(request->requestData.handle);\n         error = sDevices.get(handle, &device);\n         if (error < Error::OK) {\n            IOS_ResourceReply(request, error);\n            continue;\n         }\n\n         device->incrementRefCount();\n         IOS_SendMessage(getUsrCfgMessageQueueId(),\n                         *message,\n                         MessageFlags::None);\n         break;\n      }\n\n      case Command::Suspend:\n      {\n         // TODO: Close FSA Handle\n         sData->serviceRunning = false;\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      case Command::Resume:\n      {\n         // TODO: Open FSA Handle\n         sData->serviceRunning = true;\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      default:\n         IOS_ResourceReply(request, Error::InvalidArg);\n      }\n   }\n}\n\nError\nstartUsrCfgServiceThread()\n{\n   // Create message queue\n   auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer),\n                                       static_cast<uint32_t>(sData->messageBuffer.size()));\n   if (error < Error::OK) {\n      return error;\n   }\n   sData->messageQueueId = static_cast<MessageQueueId>(error);\n\n   // Register the device\n   error = mcp::MCP_RegisterResourceManager(\"/dev/usr_cfg\", sData->messageQueueId);\n   if (error < Error::OK) {\n      IOS_DestroyMessageQueue(sData->messageQueueId);\n      return error;\n   }\n\n   // Create thread\n   error = IOS_CreateThread(&usrCfgServiceThreadEntry, nullptr,\n                            phys_addrof(sData->threadStack) + sData->threadStack.size(),\n                            static_cast<uint32_t>(sData->threadStack.size()),\n                            UCServiceThreadPriority,\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      IOS_DestroyMessageQueue(sData->messageQueueId);\n      return error;\n   }\n\n   sData->threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sData->threadId, \"UsrCfgServiceThread\");\n\n   return IOS_StartThread(sData->threadId);\n}\n\nMessageQueueId\ngetUsrCfgServiceMessageQueueId()\n{\n   return sData->messageQueueId;\n}\n\nvoid\ninitialiseStaticUsrCfgServiceThreadData()\n{\n   sData = allocProcessStatic<StaticUsrCfgServiceData>();\n   sDevices.closeAll();\n}\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_service_thread.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios_auxil_usr_cfg_device.h\"\n\nnamespace ios::auxil::internal\n{\n\nusing UCDeviceHandle = int32_t;\n\nError\nstartUsrCfgServiceThread();\n\nkernel::MessageQueueId\ngetUsrCfgServiceMessageQueueId();\n\nError\ngetUCDevice(UCDeviceHandle handle,\n            UCDevice **outDevice);\n\nvoid\ndestroyUCDevice(UCDevice *device);\n\nvoid\ninitialiseStaticUsrCfgServiceThreadData();\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_thread.cpp",
    "content": "#include \"ios_auxil_enum.h\"\n#include \"ios_auxil_usr_cfg_thread.h\"\n#include \"ios_auxil_usr_cfg_service_thread.h\"\n\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include \"ios/ios_stackobject.h\"\n\nusing namespace ios::kernel;\n\nnamespace ios::auxil::internal\n{\n\nconstexpr auto UsrCfgThreadNumMessages = 10u;\nconstexpr auto UsrCfgThreadStackSize = 0x2000u;\nconstexpr auto UsrCfgThreadPriority = 70u;\n\nstruct StaticUsrCfgThreadData\n{\n   be2_val<ThreadId> threadId;\n   be2_val<MessageQueueId> messageQueueId;\n   be2_array<Message, UsrCfgThreadNumMessages> messageBuffer;\n   be2_array<uint8_t, UsrCfgThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticUsrCfgThreadData>\nsData = nullptr;\n\nstatic Error\nusrCfgThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n\n   while (true) {\n      auto error = IOS_ReceiveMessage(sData->messageQueueId,\n                                      message,\n                                      MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      UCDevice *device = nullptr;\n      auto request = parseMessage<ResourceRequest>(message);\n      auto handle = static_cast<UCDeviceHandle>(request->requestData.handle);\n      error = getUCDevice(handle, &device);\n\n      if (error >= Error::OK) {\n         if (request->requestData.command != Command::Ioctlv) {\n            error = Error::InvalidArg;\n         } else {\n            auto command = static_cast<UCCommand>(request->requestData.args.ioctlv.request);\n            switch (command) {\n            case UCCommand::DeleteSysConfig:\n               error = static_cast<Error>(device->deleteSysConfig(request->requestData.args.ioctlv.numVecIn,\n                                                                  request->requestData.args.ioctlv.vecs));\n               break;\n            case UCCommand::ReadSysConfig:\n               error = static_cast<Error>(device->readSysConfig(request->requestData.args.ioctlv.numVecIn,\n                                                                request->requestData.args.ioctlv.vecs));\n               break;\n            case UCCommand::WriteSysConfig:\n               error = static_cast<Error>(device->writeSysConfig(request->requestData.args.ioctlv.numVecIn,\n                                                                 request->requestData.args.ioctlv.vecs));\n               break;\n            default:\n               error = Error::InvalidArg;\n            }\n         }\n      }\n\n      IOS_ResourceReply(request, error);\n      device->decrementRefCount();\n   }\n}\n\nError\nstartUsrCfgThread()\n{\n   // Create message queue\n   auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer),\n                                       static_cast<uint32_t>(sData->messageBuffer.size()));\n   if (error < Error::OK) {\n      return error;\n   }\n   sData->messageQueueId = static_cast<MessageQueueId>(error);\n\n   // Create thread\n   error = IOS_CreateThread(&usrCfgThreadEntry, nullptr,\n                            phys_addrof(sData->threadStack) + sData->threadStack.size(),\n                            static_cast<uint32_t>(sData->threadStack.size()),\n                            UsrCfgThreadPriority,\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      IOS_DestroyMessageQueue(sData->messageQueueId);\n      return error;\n   }\n\n   sData->threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sData->threadId, \"UsrCfgThread\");\n\n   return IOS_StartThread(sData->threadId);\n}\n\nMessageQueueId\ngetUsrCfgMessageQueueId()\n{\n   return sData->messageQueueId;\n}\n\nvoid\ninitialiseStaticUsrCfgThreadData()\n{\n   sData = allocProcessStatic<StaticUsrCfgThreadData>();\n}\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_thread.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n\nnamespace ios::auxil::internal\n{\n\nError\nstartUsrCfgThread();\n\nkernel::MessageQueueId\ngetUsrCfgMessageQueueId();\n\nvoid\ninitialiseStaticUsrCfgThreadData();\n\n} // namespace ios::auxil::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/auxil/ios_auxil_usr_cfg_types.h",
    "content": "#pragma once\n#include \"ios_auxil_enum.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::auxil\n{\n\n/**\n * \\ingroup ios_auxil\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct UCSysConfig\n{\n   be2_array<char, 64> name;\n   be2_val<uint32_t> access;\n   be2_val<UCDataType> dataType;\n   be2_val<UCError> error;\n   be2_val<uint32_t> dataSize;\n   be2_phys_ptr<void> data;\n};\nCHECK_OFFSET(UCSysConfig, 0x00, name);\nCHECK_OFFSET(UCSysConfig, 0x40, access);\nCHECK_OFFSET(UCSysConfig, 0x44, dataType);\nCHECK_OFFSET(UCSysConfig, 0x48, error);\nCHECK_OFFSET(UCSysConfig, 0x4C, dataSize);\nCHECK_OFFSET(UCSysConfig, 0x50, data);\nCHECK_SIZE(UCSysConfig, 0x54);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::auxil\n"
  },
  {
    "path": "src/libdecaf/src/ios/bsp/ios_bsp.cpp",
    "content": "#include \"ios_bsp.h\"\n#include \"ios_bsp_enum.h\"\n#include \"ios_bsp_bsp_request.h\"\n#include \"ios_bsp_bsp_response.h\"\n#include \"ios/kernel/ios_kernel_hardware.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <common/log.h>\n\nnamespace ios::bsp\n{\n\nusing namespace kernel;\n\nconstexpr auto CrossHeapSize = 0x10000u;\n\nstruct StaticBspData\n{\n   be2_array<Message, 0x40> messageBuffer;\n   be2_val<MessageQueueId> messageQueue;\n};\n\nstatic phys_ptr<StaticBspData>\nsBspData = nullptr;\n\nstatic void\nbspIoctl(phys_ptr<ResourceRequest> resourceRequest,\n         BSPCommand command,\n         be2_phys_ptr<const void> inputBuffer,\n         be2_phys_ptr<void> outputBuffer)\n{\n   auto request = phys_cast<const BSPRequest *>(inputBuffer);\n   auto response = phys_cast<BSPResponse *>(outputBuffer);\n   auto error = Error::OK;\n\n   switch (command) {\n   case BSPCommand::GetHardwareVersion:\n      response->getHardwareVersion.hardwareVersion = HardwareVersion::LATTE_B1X_CAFE;\n      break;\n   default:\n      error = Error::Invalid;\n   }\n\n   IOS_ResourceReply(resourceRequest, error);\n}\n\nstatic void\ninitialiseStaticBspData()\n{\n   sBspData = allocProcessStatic<StaticBspData>();\n}\n\nError\nprocessEntryPoint(phys_ptr<void> /* context */)\n{\n   // Startup bsp process\n   initialiseStaticBspData();\n\n   auto error = IOS_CreateCrossProcessHeap(CrossHeapSize);\n   if (error < Error::OK) {\n      gLog->error(\"BSP: Failed to create cross process heap, error = {}.\", error);\n      return error;\n   }\n\n   // Initialise /dev/bsp\n   error = IOS_CreateMessageQueue(phys_addrof(sBspData->messageBuffer),\n                                  static_cast<uint32_t>(sBspData->messageBuffer.size()));\n   if (error < Error::OK) {\n      gLog->error(\"BSP: Failed to create message queue, error = {}.\", error);\n      return error;\n   }\n\n   sBspData->messageQueue = static_cast<MessageQueueId>(error);\n\n   error = IOS_RegisterResourceManager(\"/dev/bsp\", sBspData->messageQueue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = IOS_AssociateResourceManager(\"/dev/bsp\",\n                                        ResourcePermissionGroup::BSP);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   IOS_SetBspReady();\n\n   while (true) {\n      StackObject<Message> message;\n      error = IOS_ReceiveMessage(sBspData->messageQueue,\n                                 message,\n                                 MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      {\n         IOS_ResourceReply(request, static_cast<Error>(1));\n         break;\n      }\n\n      case Command::Close:\n      {\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      case Command::Ioctl:\n      {\n         bspIoctl(request,\n                  static_cast<BSPCommand>(request->requestData.args.ioctl.request),\n                  request->requestData.args.ioctl.inputBuffer,\n                  request->requestData.args.ioctl.outputBuffer);\n         break;\n      }\n\n      default:\n         IOS_ResourceReply(request, Error::Invalid);\n      }\n   }\n\n   return Error::OK;\n}\n\n} // namespace ios::bsp\n"
  },
  {
    "path": "src/libdecaf/src/ios/bsp/ios_bsp.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_process.h\"\n\nnamespace ios::bsp\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::bsp\n"
  },
  {
    "path": "src/libdecaf/src/ios/bsp/ios_bsp_bsp_request.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace ios::bsp\n{\n\n/**\n * \\ingroup ios_dev_bsp\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct BSPRequest\n{\n   UNKNOWN(0x48);\n};\nCHECK_SIZE(BSPRequest, 0x48);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::mcp\n"
  },
  {
    "path": "src/libdecaf/src/ios/bsp/ios_bsp_bsp_response.h",
    "content": "#pragma once\n#include \"ios_bsp_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace ios::bsp\n{\n\n/**\n * \\ingroup ios_dev_bsp\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct BSPResponseGetHardwareVersion\n{\n   be2_val<HardwareVersion> hardwareVersion;\n};\nCHECK_OFFSET(BSPResponseGetHardwareVersion, 0x00, hardwareVersion);\nCHECK_SIZE(BSPResponseGetHardwareVersion, 0x04);\n\nstruct BSPResponse\n{\n   union\n   {\n      be2_struct<BSPResponseGetHardwareVersion> getHardwareVersion;\n      UNKNOWN(0x200);\n   };\n};\nCHECK_SIZE(BSPResponse, 0x200);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::mcp\n"
  },
  {
    "path": "src/libdecaf/src/ios/bsp/ios_bsp_enum.h",
    "content": "#ifndef IOS_BSP_ENUM_H\n#define IOS_BSP_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\n\nENUM_NAMESPACE_ENTER(bsp)\n\nENUM_BEG(BSPCommand, uint32_t)\n   ENUM_VALUE(GetHardwareVersion,            2)\nENUM_END(BSPCommand)\n\nENUM_BEG(HardwareVersion, uint32_t)\n   ENUM_VALUE(Unknown,                       0x00000000)\n\n   // vWii Hardware Versions\n   ENUM_VALUE(HOLLYWOOD_ENG_SAMPLE_1,        0x00000001)\n   ENUM_VALUE(HOLLYWOOD_ENG_SAMPLE_2,        0x10000001)\n   ENUM_VALUE(HOLLYWOOD_PROD_FOR_WII,        0x10100001)\n   ENUM_VALUE(HOLLYWOOD_CORTADO,             0x10100008)\n   ENUM_VALUE(HOLLYWOOD_CORTADO_ESPRESSO,    0x1010000C)\n   ENUM_VALUE(BOLLYWOOD,                     0x20000001)\n   ENUM_VALUE(BOLLYWOOD_PROD_FOR_WII,        0x20100001)\n\n   // WiiU Hardware Versions\n   ENUM_VALUE(LATTE_A11_EV,                  0x21100010)\n   ENUM_VALUE(LATTE_A11_CAT,                 0x21100020)\n   ENUM_VALUE(LATTE_A12_EV,                  0x21200010)\n   ENUM_VALUE(LATTE_A12_CAT,                 0x21200020)\n   ENUM_VALUE(LATTE_A2X_EV,                  0x22100010)\n   ENUM_VALUE(LATTE_A2X_CAT,                 0x22100020)\n   ENUM_VALUE(LATTE_A3X_EV,                  0x23100010)\n   ENUM_VALUE(LATTE_A3X_CAT,                 0x23100020)\n   ENUM_VALUE(LATTE_A3X_CAFE,                0x23100028)\n   ENUM_VALUE(LATTE_A4X_EV,                  0x24100010)\n   ENUM_VALUE(LATTE_A4X_CAT,                 0x24100020)\n   ENUM_VALUE(LATTE_A4X_CAFE,                0x24100028)\n   ENUM_VALUE(LATTE_A5X_EV,                  0x25100010)\n   ENUM_VALUE(LATTE_A5X_EV_Y,                0x25100011)\n   ENUM_VALUE(LATTE_A5X_CAT,                 0x25100020)\n   ENUM_VALUE(LATTE_A5X_CAFE,                0x25100028)\n   ENUM_VALUE(LATTE_B1X_EV,                  0x26100010)\n   ENUM_VALUE(LATTE_B1X_EV_Y,                0x26100011)\n   ENUM_VALUE(LATTE_B1X_CAT,                 0x26100020)\n   ENUM_VALUE(LATTE_B1X_CAFE,                0x26100028)\nENUM_END(HardwareVersion)\n\nENUM_NAMESPACE_EXIT(bsp)\n\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_BSP_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/crypto/ios_crypto.cpp",
    "content": "#include \"ios_crypto.h\"\n#include \"ios_crypto_enum.h\"\n#include \"ios_crypto_log.h\"\n#include \"ios_crypto_request.h\"\n\n#include \"decaf_log.h\"\n#include \"ios/ios_stackobject.h\"\n#include \"ios/kernel/ios_kernel_debug.h\"\n#include \"ios/kernel/ios_kernel_hardware.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_otp.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/mcp/ios_mcp_ipc.h\"\n\n#include <gsl/gsl-lite.hpp>\n#include <openssl/err.h>\n#include <openssl/evp.h>\n\nusing namespace ios::kernel;\nusing namespace ios::mcp;\n\nnamespace ios::crypto\n{\n\nconstexpr auto kCryptoHeapSize = 0x1000u;\nconstexpr auto kCrossHeapSize = 0x10000u;\nconstexpr auto kMainThreadNumMessages = 260u;\n\nstruct CryptoDevice\n{\n   be2_val<BOOL> used = FALSE;\n   be2_val<ProcessId> processId;\n};\n\nstruct OtpKeys\n{\n   be2_val<uint32_t> ngId;\n   be2_array<uint8_t, OtpFieldSize::CommonKey> commonKey;\n   be2_array<uint8_t, OtpFieldSize::DrhWlanKey> drhWlanKey;\n   be2_array<uint8_t, OtpFieldSize::MlcKey> mlcKey;\n   be2_array<uint8_t, OtpFieldSize::NgPrivateKey> ngPrivateKey;\n   be2_array<uint8_t, OtpFieldSize::NssPrivateKey> nssPrivateKey;\n   be2_array<uint8_t, OtpFieldSize::RngKey> rngKey;\n   be2_array<uint8_t, OtpFieldSize::SeepromKey> seepromKey;\n   be2_array<uint8_t, OtpFieldSize::SlcHmac> slcHmac;\n   be2_array<uint8_t, OtpFieldSize::SlcKey> slcKey;\n   be2_array<uint8_t, OtpFieldSize::SslRsaKey> sslRsaKey;\n   be2_array<uint8_t, OtpFieldSize::StarbuckAncastKey> starbuckAncastKey;\n   be2_array<uint8_t, OtpFieldSize::XorKey> xorKey;\n   be2_array<uint8_t, OtpFieldSize::Unknown0x50> unknown0x50;\n\n   be2_array<uint8_t, OtpFieldSize::WiiCommonKey> wiiCommonKey;\n   be2_array<uint8_t, OtpFieldSize::WiiKoreanKey> wiiKoreanKey;\n   be2_array<uint8_t, OtpFieldSize::WiiNandHmac> wiiNandHmac;\n   be2_array<uint8_t, OtpFieldSize::WiiNandKey> wiiNandKey;\n   be2_array<uint8_t, OtpFieldSize::WiiNssPrivateKey> wiiNssPrivateKey;\n\n   be2_array<uint8_t, OtpFieldSize::VwiiCommonKey> vwiiCommonKey;\n};\n\nstruct KeyData\n{\n   be2_val<BOOL> used = FALSE;\n   be2_array<uint8_t, 32> data;\n   be2_phys_ptr<KeyData> next = nullptr;\n};\n\nstruct KeyHandle\n{\n   be2_val<BOOL> used = FALSE;\n   be2_val<ObjectType> objectType;\n   be2_val<SubObjectType> subObjectType;\n   be2_phys_ptr<KeyData> data;\n   be2_val<uint32_t> permission;\n   be2_val<uint32_t> idData;\n};\n\nstruct StaticCryptoData\n{\n   be2_val<BOOL> initialised = FALSE;\n   be2_val<HeapId> cryptoHeapId = -1;\n   be2_val<MessageQueueId> messageQueueId;\n   be2_array<Message, kMainThreadNumMessages> messages;\n   be2_array<CryptoDevice, 32> handles;\n   be2_struct<OtpKeys> otpKeys;\n\n   be2_array<KeyData, 256> keyDataList;\n   be2_array<KeyHandle, 128> keyHandleList;\n};\n\nstatic phys_ptr<StaticCryptoData> sCryptoData = nullptr;\nstatic phys_ptr<void> sCryptoHeap = nullptr;\n\n\nnamespace internal\n{\n\nLogger cryptoLog = { };\n\nstatic void\ninitialiseStaticData()\n{\n   sCryptoData = allocProcessStatic<StaticCryptoData>();\n   sCryptoHeap = allocProcessLocalHeap(kCryptoHeapSize);\n}\n\nstatic phys_ptr<CryptoDevice>\ngetCryptoDevice(uint32_t handle)\n{\n   if (handle < 0 || handle >= sCryptoData->handles.size()) {\n      return nullptr;\n   }\n\n   if (!sCryptoData->handles[handle].used) {\n      return nullptr;\n   }\n\n   return phys_addrof(sCryptoData->handles[handle]);\n}\n\nstatic phys_ptr<KeyHandle>\ngetCryptoKey(uint32_t handle)\n{\n   if (handle < 0 || handle >= sCryptoData->keyHandleList.size()) {\n      return nullptr;\n   }\n\n   if (!sCryptoData->keyHandleList[handle].used) {\n      return nullptr;\n   }\n\n   return phys_addrof(sCryptoData->keyHandleList[handle]);\n}\n\nstatic phys_ptr<KeyData>\nallocateKeyData(uint32_t size)\n{\n   phys_ptr<KeyData> firstData = nullptr;\n   phys_ptr<KeyData> prevData = nullptr;\n\n   for (auto i = 0u; size > 0 && i < sCryptoData->keyDataList.size(); ++i) {\n      if (sCryptoData->keyDataList[i].used) {\n         continue;\n      }\n\n      if (size < sCryptoData->keyDataList[i].data.size()) {\n         size = 0;\n      } else {\n         size -= sCryptoData->keyDataList[i].data.size();\n      }\n\n      sCryptoData->keyDataList[i].used = TRUE;\n\n      if (prevData) {\n         prevData->next = phys_addrof(sCryptoData->keyDataList[i]);\n      }\n      prevData = phys_addrof(sCryptoData->keyDataList[i]);\n      if (!firstData) {\n         firstData = prevData;\n      }\n   }\n\n   return firstData;\n}\n\nstatic Error\ngetKeyObjectSize(ObjectType objectType,\n                 SubObjectType subObjectType,\n                 uint32_t *outKeySize)\n{\n   struct ObjectSize {\n      ObjectType objectType;\n      SubObjectType subObjectType;\n      uint32_t size;\n   };\n   static const ObjectSize kObjectSizes[] = {\n      { ObjectType::SecretKey,   SubObjectType::Aes128,        16 },\n      { ObjectType::SecretKey,   SubObjectType::Mac,           20 },\n      { ObjectType::SecretKey,   SubObjectType::Ecc233,        30 },\n      { ObjectType::SecretKey,   SubObjectType::Unknown0x7,    64 },\n      { ObjectType::PublicKey,   SubObjectType::Rsa2048,       256 },\n      { ObjectType::PublicKey,   SubObjectType::Rsa4096,       512 },\n      { ObjectType::PublicKey,   SubObjectType::Ecc233,        60 },\n      { ObjectType::Unknown0x2,  SubObjectType::Ecc233,        90 },\n      { ObjectType::Data,        SubObjectType::Data,          0 },\n      { ObjectType::Data,        SubObjectType::Version,       0 },\n   };\n\n   for (const auto &objectSize : kObjectSizes) {\n      if (objectSize.objectType == objectType &&\n          objectSize.subObjectType == subObjectType) {\n         *outKeySize = objectSize.size;\n         return Error::OK;\n      }\n   }\n\n   return static_cast<Error>(-2005);\n}\n\nstatic Error\nregisterSystemKey(KeyId keyId,\n                  ObjectType objectType,\n                  SubObjectType subObjectType,\n                  phys_ptr<void> data,\n                  uint32_t size,\n                  phys_ptr<uint32_t> idData)\n{\n   Error error;\n   uint32_t expectedKeySize = 0;\n\n   error = getKeyObjectSize(objectType, subObjectType, &expectedKeySize);\n   if (error != Error::OK) {\n      return error;\n   }\n\n   if (size != expectedKeySize) {\n      return static_cast<Error>(-2014);\n   }\n\n   phys_ptr<KeyData> keyData = allocateKeyData(size);\n   if (size > 0 && !keyData) {\n      return static_cast<Error>(-2013);\n   }\n\n   phys_ptr<KeyHandle> keyHandle =\n      phys_addrof(sCryptoData->keyHandleList[static_cast<uint32_t>(keyId)]);\n   if (keyHandle->used) {\n      return static_cast<Error>(-2001);\n   }\n\n   keyHandle->used = TRUE;\n   keyHandle->objectType = objectType;\n   keyHandle->subObjectType = subObjectType;\n   keyHandle->data = keyData;\n   keyHandle->permission = 0u;\n   keyHandle->idData = idData ? static_cast<uint32_t>(*idData) : 0u;\n\n   uint32_t bytesCopied = 0;\n   while (bytesCopied < size) {\n      uint32_t copySize = std::min(size - bytesCopied, keyData->data.size());\n      memcpy(std::addressof(keyData->data),\n             static_cast<uint8_t *>(data.get()) + bytesCopied,\n             copySize);\n      bytesCopied += copySize;\n      keyData = keyData->next;\n   }\n\n   return Error::OK;\n}\n\nstatic Error\nsetSystemKeyPermission(KeyId keyId,\n                       uint32_t permission)\n{\n   phys_ptr<KeyHandle> keyHandle =\n      phys_addrof(sCryptoData->keyHandleList[static_cast<uint32_t>(keyId)]);\n   if (!keyHandle->used) {\n      return static_cast<Error>(-2002);\n   }\n\n   keyHandle->permission = permission;\n   return Error::OK;\n}\n\nstatic Error\nreadOtpKeys()\n{\n   StackArray<uint8_t, 64> buffer;\n\n   IOS_ReadOTP(OtpFieldIndex::NgId,\n               phys_addrof(sCryptoData->otpKeys.ngId),\n               sizeof(sCryptoData->otpKeys.ngId));\n   IOS_ReadOTP(OtpFieldIndex::NgPrivateKey,\n               phys_addrof(sCryptoData->otpKeys.ngPrivateKey),\n               sizeof(sCryptoData->otpKeys.ngPrivateKey));\n   IOS_ReadOTP(OtpFieldIndex::NssPrivateKey,\n               phys_addrof(sCryptoData->otpKeys.nssPrivateKey),\n               sizeof(sCryptoData->otpKeys.nssPrivateKey));\n\n   IOS_ReadOTP(OtpFieldIndex::WiiNssPrivateKey,\n               phys_addrof(sCryptoData->otpKeys.wiiNssPrivateKey),\n               sizeof(sCryptoData->otpKeys.wiiNssPrivateKey));\n   IOS_ReadOTP(OtpFieldIndex::WiiCommonKey,\n               phys_addrof(sCryptoData->otpKeys.wiiCommonKey),\n               sizeof(sCryptoData->otpKeys.wiiCommonKey));\n\n   if (sCryptoData->otpKeys.ngId != 0) {\n      IOS_ReadOTP(OtpFieldIndex::WiiNandHmac,\n                  phys_addrof(sCryptoData->otpKeys.wiiNandHmac),\n                  sizeof(sCryptoData->otpKeys.wiiNandHmac));\n      IOS_ReadOTP(OtpFieldIndex::WiiNandKey,\n                  phys_addrof(sCryptoData->otpKeys.wiiNandKey),\n                  sizeof(sCryptoData->otpKeys.wiiNandKey));\n   }\n\n   IOS_ReadOTP(OtpFieldIndex::SlcKey,\n               phys_addrof(sCryptoData->otpKeys.slcKey),\n               sizeof(sCryptoData->otpKeys.slcKey));\n   IOS_ReadOTP(OtpFieldIndex::SlcHmac,\n               phys_addrof(sCryptoData->otpKeys.slcHmac),\n               sizeof(sCryptoData->otpKeys.slcHmac));\n   IOS_ReadOTP(OtpFieldIndex::RngKey,\n               phys_addrof(sCryptoData->otpKeys.rngKey),\n               sizeof(sCryptoData->otpKeys.rngKey));\n   IOS_ReadOTP(OtpFieldIndex::StarbuckAncastKey,\n               phys_addrof(sCryptoData->otpKeys.starbuckAncastKey),\n               sizeof(sCryptoData->otpKeys.starbuckAncastKey));\n   IOS_ReadOTP(OtpFieldIndex::SeepromKey,\n               phys_addrof(sCryptoData->otpKeys.seepromKey),\n               sizeof(sCryptoData->otpKeys.seepromKey));\n   IOS_ReadOTP(OtpFieldIndex::MlcKey,\n               phys_addrof(sCryptoData->otpKeys.mlcKey),\n               sizeof(sCryptoData->otpKeys.mlcKey));\n   IOS_ReadOTP(OtpFieldIndex::DrhWlanKey,\n               phys_addrof(sCryptoData->otpKeys.drhWlanKey),\n               sizeof(sCryptoData->otpKeys.drhWlanKey));\n   IOS_ReadOTP(OtpFieldIndex::VwiiCommonKey,\n               phys_addrof(sCryptoData->otpKeys.vwiiCommonKey),\n               sizeof(sCryptoData->otpKeys.vwiiCommonKey));\n   IOS_ReadOTP(OtpFieldIndex::CommonKey,\n               phys_addrof(sCryptoData->otpKeys.commonKey),\n               sizeof(sCryptoData->otpKeys.commonKey));\n   IOS_ReadOTP(OtpFieldIndex::WiiKoreanKey,\n               phys_addrof(sCryptoData->otpKeys.wiiKoreanKey),\n               sizeof(sCryptoData->otpKeys.wiiKoreanKey));\n   IOS_ReadOTP(OtpFieldIndex::SslRsaKey,\n               phys_addrof(sCryptoData->otpKeys.sslRsaKey),\n               sizeof(sCryptoData->otpKeys.sslRsaKey));\n   IOS_ReadOTP(OtpFieldIndex::NssPrivateKey,\n               phys_addrof(sCryptoData->otpKeys.nssPrivateKey),\n               sizeof(sCryptoData->otpKeys.nssPrivateKey));\n   IOS_ReadOTP(OtpFieldIndex::XorKey,\n               phys_addrof(sCryptoData->otpKeys.xorKey),\n               sizeof(sCryptoData->otpKeys.xorKey));\n   IOS_ReadOTP(OtpFieldIndex::Unknown0x50,\n               phys_addrof(sCryptoData->otpKeys.unknown0x50),\n               sizeof(sCryptoData->otpKeys.unknown0x50));\n\n   registerSystemKey(KeyId::NgId,\n                     ObjectType::Data, SubObjectType::Data,\n                     nullptr, 0,\n                     phys_addrof(sCryptoData->otpKeys.ngId));\n   registerSystemKey(KeyId::NgPrivateKey,\n                     ObjectType::SecretKey, SubObjectType::Ecc233,\n                     phys_addrof(sCryptoData->otpKeys.ngPrivateKey), 30,\n                     nullptr);\n   registerSystemKey(KeyId::NssPrivateKey,\n                     ObjectType::SecretKey, SubObjectType::Ecc233,\n                     phys_addrof(sCryptoData->otpKeys.nssPrivateKey), 30,\n                     nullptr);\n   registerSystemKey(KeyId::WiiNssPrivateKey,\n                     ObjectType::SecretKey, SubObjectType::Ecc233,\n                     phys_addrof(sCryptoData->otpKeys.wiiNssPrivateKey), 30,\n                     nullptr);\n   registerSystemKey(KeyId::SlcKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.slcKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::SlcHmac,\n                     ObjectType::SecretKey, SubObjectType::Mac,\n                     phys_addrof(sCryptoData->otpKeys.slcHmac), 20,\n                     nullptr);\n   registerSystemKey(KeyId::WiiCommonKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.wiiCommonKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::RngKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.rngKey), 16,\n                     nullptr);\n\n   static std::array<uint8_t, 16> wiiSdKey = {\n      0xab, 0x01, 0xb9, 0xd8, 0xe1, 0x62, 0x2b, 0x08,\n      0xaf, 0xba, 0xd8, 0x4d, 0xbf, 0xc2, 0xa5, 0x5d,\n   };\n   memcpy(buffer.get(), wiiSdKey.data(), wiiSdKey.size());\n   registerSystemKey(KeyId::WiiSdKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     buffer, 16,\n                     nullptr);\n\n   registerSystemKey(KeyId::WiiKoreanKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.wiiKoreanKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::StarbuckAncastKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.starbuckAncastKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::CommonKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.commonKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::VwiiCommonKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.vwiiCommonKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::WiiNandKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.wiiNandKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::WiiNandHmac,\n                     ObjectType::SecretKey, SubObjectType::Mac,\n                     phys_addrof(sCryptoData->otpKeys.wiiNandHmac), 20,\n                     nullptr);\n\n   /*\n   registerSystemKey(KeyId::StarbuckAncastModulus,\n                     ObjectType::PublicKey, SubObjectType::Rsa2048,\n                     data, 0x100, id);\n   registerSystemKey(KeyId::Boot1AncastModulus,\n                     ObjectType::PublicKey, SubObjectType::Rsa2048,\n                     data, 0x100, id);\n   */\n\n   registerSystemKey(KeyId::SeepromKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.seepromKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::MlcKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.mlcKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::DrhWlanKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.drhWlanKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::Unknown0x1A,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.unknown0x50), 16,\n                     nullptr);\n   registerSystemKey(KeyId::SslRsaKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.sslRsaKey), 16,\n                     nullptr);\n   registerSystemKey(KeyId::NssPrivateKey2,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     phys_addrof(sCryptoData->otpKeys.nssPrivateKey), 16,\n                     nullptr);\n\n   static std::array<uint8_t, 16> spotpassXorKey = {\n      0x33, 0xAC, 0x6D, 0x15, 0xC2, 0x26, 0x0A, 0x91,\n      0x3B, 0xBF, 0x73, 0xC3, 0x55, 0xD8, 0x66, 0x04,\n   };\n   for (size_t i = 0u; i < spotpassXorKey.size(); ++i) {\n      buffer[i] =\n         spotpassXorKey[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   registerSystemKey(KeyId::SpotPassKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     buffer, 16,\n                     nullptr);\n\n   static std::array<uint8_t, 64> spotpassXorUnknown0x20 = {\n      0x7F, 0xA5, 0x48, 0x15, 0xD3, 0x6E, 0x4E, 0xBC,\n      0x38, 0x87, 0x6A, 0x82, 0x16, 0xF2, 0x59, 0x24,\n      0x39, 0x98, 0x0F, 0x13, 0xD8, 0x26, 0x0E, 0xA7,\n      0x3B, 0x94, 0x42, 0xCB, 0x41, 0xFB, 0x7C, 0x3C,\n      0x45, 0xE9, 0x6F, 0x12, 0xAA, 0x39, 0x5D, 0x9C,\n      0x42, 0x9A, 0x47, 0xC6, 0x49, 0xF7, 0x61, 0x25,\n      0x69, 0x9F, 0x0E, 0x62, 0xE3, 0x6E, 0x06, 0xF8,\n      0x59, 0xA6, 0x4C, 0xB0, 0x41, 0xCF, 0x26, 0x48,\n   };\n   for (size_t i = 0u; i < spotpassXorUnknown0x20.size(); ++i) {\n      buffer[i] =\n         spotpassXorUnknown0x20[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   registerSystemKey(KeyId::SpotPassUnknown0x20,\n                     ObjectType::SecretKey, SubObjectType::Unknown0x7,\n                     buffer, 64,\n                     nullptr);\n\n   // This key is 64 bytes with first 32 bytes of data and last 32 bytes zero\n   static std::array<uint8_t, 32> xorUnknown0x21 = {\n      0x24, 0xF0, 0x31, 0xEE, 0x47, 0x4B, 0xCE, 0x34,\n      0x80, 0x18, 0x1B, 0x0F, 0x11, 0xDB, 0xE5, 0xC6,\n      0x69, 0x16, 0x77, 0xE8, 0x89, 0x89, 0x2E, 0x62,\n      0x61, 0xBE, 0xE4, 0xDC, 0x46, 0xD7, 0x3C, 0x30,\n   };\n   for (size_t i = 0u; i < xorUnknown0x21.size(); ++i) {\n      buffer[i] =\n         xorUnknown0x21[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   memset(buffer.get() + 32, 0, 32);\n   registerSystemKey(KeyId::Unknown0x21,\n                     ObjectType::SecretKey, SubObjectType::Unknown0x7,\n                     buffer, 64,\n                     nullptr);\n\n   setSystemKeyPermission(KeyId::NgId, 0xfffffff);\n   setSystemKeyPermission(KeyId::NgPrivateKey, 2);\n   setSystemKeyPermission(KeyId::NssPrivateKey, 0x200);\n   setSystemKeyPermission(KeyId::WiiNssPrivateKey, 0x200);\n   setSystemKeyPermission(KeyId::SlcKey, 0x20);\n   setSystemKeyPermission(KeyId::SlcHmac, 0x20);\n   setSystemKeyPermission(KeyId::WiiCommonKey, 2);\n   setSystemKeyPermission(KeyId::RngKey, 2);\n   setSystemKeyPermission(KeyId::WiiSdKey, 0xfffffff);\n   setSystemKeyPermission(KeyId::WiiKoreanKey, 2);\n   setSystemKeyPermission(KeyId::CommonKey, 2);\n   setSystemKeyPermission(KeyId::VwiiCommonKey, 2);\n   setSystemKeyPermission(KeyId::WiiNandKey, 0x20);\n   setSystemKeyPermission(KeyId::WiiNandHmac, 0x20);\n   setSystemKeyPermission(KeyId::StarbuckAncastKey, 2);\n   setSystemKeyPermission(KeyId::StarbuckAncastModulus, 2);\n   setSystemKeyPermission(KeyId::Boot1AncastModulus, 2);\n   setSystemKeyPermission(KeyId::SeepromKey, 2);\n   setSystemKeyPermission(KeyId::MlcKey, 0x20);\n   setSystemKeyPermission(KeyId::DrhWlanKey, 0x40);\n   setSystemKeyPermission(KeyId::Unknown0x1A, 0x20);\n   setSystemKeyPermission(KeyId::SslRsaKey, 0x200);\n   setSystemKeyPermission(KeyId::NssPrivateKey2, 0x200);\n\n   static std::array<uint8_t, 16> udsLocalWlanCcmpKey = {\n      0x34, 0x0a, 0xcf, 0xef, 0xb6, 0x95, 0x42, 0x9d,\n      0x69, 0xae, 0x09, 0x44, 0xec, 0x37, 0x38, 0x7d,\n   };\n   for (size_t i = 0u; i < udsLocalWlanCcmpKey.size(); ++i) {\n      buffer[i] =\n         udsLocalWlanCcmpKey[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   registerSystemKey(KeyId::UdsLocalWlanCcmpKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     buffer, 16,\n                     nullptr);\n\n   static std::array<uint8_t, 16> dlpKey = {\n      0x83, 0x7b, 0xe4, 0x1b, 0xcc, 0xb6, 0x5e, 0xaa,\n      0x77, 0x29, 0x4c, 0x7f, 0x1e, 0xee, 0xef, 0xdc,\n   };\n   for (size_t i = 0u; i < dlpKey.size(); ++i) {\n      buffer[i] =\n         dlpKey[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   registerSystemKey(KeyId::DlpKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     buffer, 16,\n                     nullptr);\n\n   static std::array<uint8_t, 16> aptWrapKey = {\n      0x53, 0x20, 0xbb, 0x5e, 0xfe, 0x10, 0xd4, 0xa8,\n      0x9c, 0xca, 0x72, 0xd3, 0xcd, 0x3f, 0xd8, 0x22,\n   };\n   for (size_t i = 0u; i < aptWrapKey.size(); ++i) {\n      buffer[i] =\n         aptWrapKey[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   registerSystemKey(KeyId::AptWrapKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     buffer, 16,\n                     nullptr);\n\n   setSystemKeyPermission(KeyId::AptWrapKey, 0xfffffff);\n   setSystemKeyPermission(KeyId::SpotPassKey, 0x800);\n   setSystemKeyPermission(KeyId::SpotPassUnknown0x20, 0x800);\n   setSystemKeyPermission(KeyId::Unknown0x21, 0x800);\n\n   static std::array<uint8_t, 16> pushmoreKey = {\n      0x3b, 0x66, 0x3d, 0x64, 0x3d, 0xd5, 0x3f, 0xc6,\n      0x7b, 0xc4, 0xb7, 0x39, 0x8e, 0x23, 0x80, 0x92,\n   };\n   for (size_t i = 0u; i < pushmoreKey.size(); ++i) {\n      buffer[i] =\n         pushmoreKey[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   registerSystemKey(KeyId::PushmoreKey,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     buffer, 16,\n                     nullptr);\n   setSystemKeyPermission(KeyId::PushmoreKey, 0x800);\n\n   // This key is 64 bytes with first 16 bytes of data and last 48 bytes zero\n   static std::array<uint8_t, 16> unknown0x23 = {\n      0x17, 0xca, 0x71, 0x17, 0xc1, 0x24, 0x9b, 0x9e,\n      0x24, 0x47, 0x14, 0x97, 0x92, 0x21, 0xd4, 0x75,\n   };\n   for (size_t i = 0u; i < unknown0x23.size(); ++i) {\n      buffer[i] =\n         unknown0x23[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   memset(buffer.get() + 16, 0, 48);\n   registerSystemKey(KeyId::Unknown0x23,\n                     ObjectType::SecretKey, SubObjectType::Unknown0x7,\n                     buffer, 64,\n                     nullptr);\n   setSystemKeyPermission(KeyId::Unknown0x23, 0x40);\n\n   // This key is 64 bytes with first 16 bytes of data and last 48 bytes zero\n   static std::array<uint8_t, 16> unknown0x22 = {\n      0x75, 0xa9, 0x17, 0x08, 0xe9, 0xf4, 0x3e, 0xde,\n      0xf2, 0x06, 0x55, 0xf6, 0x51, 0x12, 0x5d, 0x1d,\n   };\n   for (size_t i = 0u; i < unknown0x22.size(); ++i) {\n      buffer[i] =\n         unknown0x22[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   memset(buffer.get() + 16, 0, 48);\n   registerSystemKey(KeyId::Unknown0x22,\n                     ObjectType::SecretKey, SubObjectType::Unknown0x7,\n                     buffer, 64,\n                     nullptr);\n   setSystemKeyPermission(KeyId::Unknown0x22, 0x40);\n\n   static std::array<uint8_t, 16> unknown0x24 = {\n      0xcb, 0xf7, 0x3d, 0x30, 0x4d, 0x7a, 0xd5, 0x94,\n      0x4f, 0xb7, 0xbe, 0xb0, 0xc7, 0x48, 0xc4, 0x54,\n   };\n   for (size_t i = 0u; i < unknown0x24.size(); ++i) {\n      buffer[i] =\n         unknown0x24[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   registerSystemKey(KeyId::Unknown0x24,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     buffer, 16,\n                     nullptr);\n   setSystemKeyPermission(KeyId::Unknown0x24, 0x40);\n\n   static std::array<uint8_t, 16> unknown0x25 = {\n      0x5f, 0x02, 0x0b, 0x8e, 0x5b, 0x27, 0x0d, 0xee,\n      0xe7, 0xc1, 0xb3, 0x49, 0xd8, 0xa7, 0x13, 0x0b,\n   };\n   for (size_t i = 0u; i < unknown0x25.size(); ++i) {\n      buffer[i] =\n         unknown0x25[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   registerSystemKey(KeyId::Unknown0x25,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     buffer, 16,\n                     nullptr);\n   setSystemKeyPermission(KeyId::Unknown0x25, 0x40);\n\n   static std::array<uint8_t, 16> unknown0x26 = {\n      0x2c, 0x77, 0x3e, 0x7a, 0xbd, 0xe9, 0xea, 0x54,\n      0x69, 0x46, 0x76, 0x3b, 0xe8, 0x09, 0x89, 0xda,\n   };\n   for (size_t i = 0u; i < unknown0x26.size(); ++i) {\n      buffer[i] =\n         unknown0x26[i] ^\n         sCryptoData->otpKeys.xorKey[i % sCryptoData->otpKeys.xorKey.size()];\n   }\n   registerSystemKey(KeyId::Unknown0x26,\n                     ObjectType::SecretKey, SubObjectType::Aes128,\n                     buffer, 16,\n                     nullptr);\n   setSystemKeyPermission(KeyId::Unknown0x26, 0x20);\n   return Error::OK;\n}\n\nstatic Error\ninitialiseCrypto()\n{\n   if (sCryptoData->initialised) {\n      return Error::OK;\n   }\n\n   Error error = IOS_CreateHeap(sCryptoHeap, kCryptoHeapSize);\n   if (error < Error::OK) {\n      internal::cryptoLog->error(\n         \"initialiseCrypto: Failed to create crypto, error = {}.\", error);\n      return error;\n   }\n\n   sCryptoData->cryptoHeapId = static_cast<HeapId>(error);\n\n   internal::cryptoLog->info(\"IOSC Initialize\");\n   readOtpKeys();\n\n   sCryptoData->initialised = TRUE;\n   return Error::OK;\n}\n\nstatic IOSCError\nioscDecrypt(phys_ptr<ResourceRequest> resourceRequest,\n            phys_ptr<IOSCRequestDecrypt> decryptRequest,\n            phys_ptr<void> iv, uint32_t ivSize,\n            phys_ptr<void> input, uint32_t inputSize,\n            phys_ptr<void> output, uint32_t outputSize)\n{\n   auto cryptoDevice = getCryptoDevice(resourceRequest->requestData.handle);\n   if (!cryptoDevice) {\n      return static_cast<IOSCError>(Error::Invalid);\n   }\n\n   if (cryptoDevice->processId > ProcessId::COSKERNEL) {\n      return IOSCError::InvalidParam;\n   }\n\n   auto cryptoKey = getCryptoKey(decryptRequest->keyHandle);\n   if (!cryptoKey) {\n      return static_cast<IOSCError>(Error::Invalid);\n   }\n\n   if (!(cryptoKey->permission & (1 << static_cast<int>(cryptoDevice->processId)))) {\n      return IOSCError::Permission;\n   }\n\n   EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();\n   auto _ = gsl::finally([&]() { EVP_CIPHER_CTX_free(ctx); });\n   if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL,\n                           reinterpret_cast<unsigned char *>(cryptoKey->data.get()),\n                           reinterpret_cast<unsigned char *>(iv.get()))) {\n      return IOSCError::CryptoError;\n   }\n\n   EVP_CIPHER_CTX_set_key_length(ctx, 16);\n   EVP_CIPHER_CTX_set_padding(ctx, 0);\n\n   int updateLength = static_cast<int>(0);\n   int finalLength = static_cast<int>(0);\n   if (!EVP_DecryptUpdate(ctx,\n                          reinterpret_cast<unsigned char *>(output.get()),\n                          &updateLength,\n                          reinterpret_cast<unsigned char *>(input.get()),\n                          static_cast<int>(inputSize))) {\n      return IOSCError::CryptoError;\n   }\n\n   if (!EVP_DecryptFinal_ex(ctx,\n                            reinterpret_cast<unsigned char *>(output.get()) + updateLength,\n                            &finalLength)) {\n      return IOSCError::CryptoError;\n   }\n\n   return IOSCError::OK;\n}\n\nstatic Error\ncryptoIoctlv(phys_ptr<ResourceRequest> resourceRequest)\n{\n   auto error = Error::OK;\n\n   switch (static_cast<IOSCCommand>(resourceRequest->requestData.args.ioctlv.request)) {\n   case IOSCCommand::Decrypt:\n   {\n      if (resourceRequest->requestData.args.ioctlv.numVecIn != 3) {\n         return Error::InvalidArg;\n      }\n\n      if (resourceRequest->requestData.args.ioctlv.numVecOut != 1) {\n         return Error::InvalidArg;\n      }\n\n      if (!resourceRequest->requestData.args.ioctlv.vecs[0].paddr ||\n           resourceRequest->requestData.args.ioctlv.vecs[0].len != sizeof(IOSCRequestDecrypt)) {\n         return Error::InvalidArg;\n      }\n\n      if (!resourceRequest->requestData.args.ioctlv.vecs[1].paddr ||\n           resourceRequest->requestData.args.ioctlv.vecs[1].len != 16) {\n         return Error::InvalidArg;\n      }\n\n      if (!resourceRequest->requestData.args.ioctlv.vecs[2].paddr ||\n          !resourceRequest->requestData.args.ioctlv.vecs[2].len) {\n         return Error::InvalidArg;\n      }\n\n      if (!resourceRequest->requestData.args.ioctlv.vecs[3].paddr ||\n          !resourceRequest->requestData.args.ioctlv.vecs[3].len) {\n         return Error::InvalidArg;\n      }\n\n      auto decryptRequest = phys_cast<IOSCRequestDecrypt *>(\n         resourceRequest->requestData.args.ioctlv.vecs[0].paddr);\n      error = static_cast<Error>(\n         ioscDecrypt(resourceRequest, decryptRequest,\n            phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr),\n            resourceRequest->requestData.args.ioctlv.vecs[1].len,\n            phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[2].paddr),\n            resourceRequest->requestData.args.ioctlv.vecs[2].len,\n            phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[3].paddr),\n            resourceRequest->requestData.args.ioctlv.vecs[3].len));\n      break;\n   }\n   default:\n      error = Error::InvalidArg;\n   }\n\n   return error;\n}\n\n} // namespace internal\n\nError\nprocessEntryPoint(phys_ptr<void> /* context */)\n{\n   StackObject<Message> message;\n\n   // Initialise process static data\n   internal::initialiseStaticData();\n\n   // Initialise logger\n   internal::cryptoLog = decaf::makeLogger(\"IOS_CRYPTO\");\n\n   // Initialise process heaps\n   auto error = IOS_CreateCrossProcessHeap(kCrossHeapSize);\n   if (error < Error::OK) {\n      internal::cryptoLog->error(\n         \"processEntryPoint: Failed to create cross process heap, error = {}.\",\n         error);\n      return error;\n   }\n\n   // Setup /dev/crypto\n   error = IOS_CreateMessageQueue(phys_addrof(sCryptoData->messages),\n                                  sCryptoData->messages.size());\n   if (error < Error::OK) {\n      internal::cryptoLog->error(\n         \"processEntryPoint: IOS_CreateMessageQueue failed with error = {}\",\n         error);\n      return error;\n   }\n   sCryptoData->messageQueueId = static_cast<MessageQueueId>(error);\n\n   error = MCP_RegisterResourceManager(\"/dev/crypto\",\n                                       sCryptoData->messageQueueId);\n   if (error < Error::OK) {\n      internal::cryptoLog->error(\n         \"processEntryPoint: MCP_RegisterResourceManager failed for /dev/crypto with error = {}\",\n         error);\n      return error;\n   }\n\n   // Run /dev/crypto\n   while (true) {\n      error = IOS_ReceiveMessage(sCryptoData->messageQueueId, message, MessageFlags::None);\n      if (error < Error::OK) {\n         internal::cryptoLog->error(\n            \"processEntryPoint: IOS_ReceiveMessage failed with error = {}\", error);\n         break;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n         error = Error::NoResource;\n         for (auto i = 0u; i < sCryptoData->handles.size(); ++i) {\n            if (!sCryptoData->handles[i].used) {\n               sCryptoData->handles[i].used = TRUE;\n               sCryptoData->handles[i].processId = request->requestData.processId;\n               error = static_cast<Error>(i);\n               break;\n            }\n         }\n\n         IOS_ResourceReply(request, error);\n         break;\n      case Command::Close:\n         if (request->requestData.handle < static_cast<Handle>(sCryptoData->handles.size())) {\n            sCryptoData->handles[request->requestData.handle].used = FALSE;\n         }\n\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      case Command::Suspend:\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      case Command::Resume:\n         internal::initialiseCrypto();\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      case Command::Ioctlv:\n         IOS_ResourceReply(request, internal::cryptoIoctlv(request));\n         break;\n      default:\n         IOS_ResourceReply(request, Error::Invalid);\n      }\n   }\n   return Error::OK;\n}\n\n} // namespace ios::crypto\n"
  },
  {
    "path": "src/libdecaf/src/ios/crypto/ios_crypto.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_process.h\"\n\nnamespace ios::crypto\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::crypto\n"
  },
  {
    "path": "src/libdecaf/src/ios/crypto/ios_crypto_enum.h",
    "content": "#ifndef IOS_CRYPTO_ENUM_H\n#define IOS_CRYPTO_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\nENUM_NAMESPACE_ENTER(crypto)\n\nENUM_BEG(IOSCError, int32_t)\n   ENUM_VALUE(OK,                   0)\n   ENUM_VALUE(Permission,           -2000)\n   ENUM_VALUE(InvalidParam,         -2002)\n   ENUM_VALUE(CryptoError,          -2012)\nENUM_END(IOSCError)\n\nENUM_BEG(IOSCCommand, uint32_t)\n   ENUM_VALUE(Decrypt,                    0xE)\nENUM_END(IOSCCommand)\n\nENUM_BEG(KeyId, uint32_t)\n   ENUM_VALUE(NgPrivateKey,               0x0)\n   ENUM_VALUE(NgId,                       0x1)\n   ENUM_VALUE(SlcKey,                     0x2)\n   ENUM_VALUE(SlcHmac,                    0x3)\n   ENUM_VALUE(WiiCommonKey,               0x4)\n   ENUM_VALUE(RngKey,                     0x5)\n   ENUM_VALUE(WiiSdKey,                   0x6)\n   ENUM_VALUE(SeepromKey,                 0x7)\n   ENUM_VALUE(WiiKoreanKey,               0xB)\n   ENUM_VALUE(DriveKey,                   0xC)\n   ENUM_VALUE(StarbuckAncastKey,          0xD)\n   ENUM_VALUE(StarbuckAncastModulus,      0xE)\n   ENUM_VALUE(Boot1AncastModulus,         0xF)\n   ENUM_VALUE(CommonKey,                  0x10)\n   ENUM_VALUE(MlcKey,                     0x11)\n   ENUM_VALUE(WiiNandKey,                 0x13)\n   ENUM_VALUE(WiiNandHmac,                0x14)\n   ENUM_VALUE(VwiiCommonKey,              0x15)\n   ENUM_VALUE(DrhWlanKey,                 0x16)\n   ENUM_VALUE(UdsLocalWlanCcmpKey,        0x17)\n   ENUM_VALUE(DlpKey,                     0x18)\n   ENUM_VALUE(AptWrapKey,                 0x19)\n   ENUM_VALUE(Unknown0x1A,                0x1A)\n   ENUM_VALUE(SslRsaKey,                  0x1B)\n   ENUM_VALUE(NssPrivateKey,              0x1C)\n   ENUM_VALUE(WiiNssPrivateKey,           0x1D)\n   ENUM_VALUE(NssPrivateKey2,             0x1E)\n   ENUM_VALUE(SpotPassKey,                0x1F)\n   ENUM_VALUE(SpotPassUnknown0x20,        0x20)\n   ENUM_VALUE(Unknown0x21,                0x21)\n   ENUM_VALUE(Unknown0x22,                0x22)\n   ENUM_VALUE(Unknown0x23,                0x23)\n   ENUM_VALUE(Unknown0x24,                0x24)\n   ENUM_VALUE(Unknown0x25,                0x25)\n   ENUM_VALUE(Unknown0x26,                0x26)\n   ENUM_VALUE(PushmoreKey,                0x27)\nENUM_END(KeyId)\n\nENUM_BEG(ObjectType, uint8_t)\n   ENUM_VALUE(SecretKey,                  0)\n   ENUM_VALUE(PublicKey,                  1)\n   ENUM_VALUE(Unknown0x2,                 2)\n   ENUM_VALUE(Data,                       3)\n   ENUM_VALUE(Invalid,                    0xFF)\nENUM_END(ObjectType)\n\nENUM_BEG(SubObjectType, uint8_t)\n   ENUM_VALUE(Aes128,                     0)\n   ENUM_VALUE(Mac,                        1)\n   ENUM_VALUE(Rsa2048,                    2)\n   ENUM_VALUE(Rsa4096,                    3)\n   ENUM_VALUE(Ecc233,                     4)\n   ENUM_VALUE(Data,                       5)\n   ENUM_VALUE(Version,                    6)\n   ENUM_VALUE(Unknown0x7,                 7)\nENUM_END(SubObjectType)\n\nENUM_NAMESPACE_EXIT(crypto)\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_CRYPTO_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/crypto/ios_crypto_ipc.cpp",
    "content": "#include \"ios_crypto_ipc.h\"\n#include \"ios_crypto_request.h\"\n\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n\nnamespace ios::crypto\n{\n\nusing namespace kernel;\n\nstatic phys_ptr<void>\nallocIpcData(uint32_t size)\n{\n   auto buffer = IOS_HeapAlloc(CrossProcessHeapId, size);\n\n   if (buffer) {\n      std::memset(buffer.get(), 0, size);\n   }\n\n   return buffer;\n}\n\nstatic void\nfreeIpcData(phys_ptr<void> data)\n{\n   IOS_HeapFree(CrossProcessHeapId, data);\n}\n\nError\nIOSC_Open()\n{\n   return IOS_Open(\"/dev/crypto\", OpenMode::None);\n}\n\nError\nIOSC_Close(IOSCHandle handle)\n{\n   return IOS_Close(handle);\n}\n\nIOSCError\nIOSC_Decrypt(IOSCHandle handle,\n             IOSCKeyHandle keyHandle,\n             phys_ptr<const void> ivData, uint32_t ivSize,\n             phys_ptr<const void> inputData, uint32_t inputSize,\n             phys_ptr<void> outputData, uint32_t outputSize)\n{\n   if (!align_check(inputData.get(), 0x10u) || !align_check(outputData.get(), 0x10u)) {\n      return IOSCError::InvalidParam;\n   }\n\n   auto request = phys_cast<IOSCRequestDecrypt *>(\n      allocIpcData(sizeof(IOSCRequestDecrypt)));\n   request->unknown0x00 = 0u;\n   request->unknown0x04 = 1u;\n   request->keyHandle = keyHandle;\n\n   auto vecs = phys_cast<IoctlVec *>(allocIpcData(sizeof(IoctlVec) * 4));\n   vecs[0].paddr = phys_cast<phys_addr>(request);\n   vecs[0].len = static_cast<uint32_t>(sizeof(IOSCRequestDecrypt));\n\n   vecs[1].paddr = phys_cast<phys_addr>(ivData);\n   vecs[1].len = ivSize;\n\n   vecs[2].paddr = phys_cast<phys_addr>(inputData);\n   vecs[2].len = inputSize;\n\n   vecs[3].paddr = phys_cast<phys_addr>(outputData);\n   vecs[3].len = outputSize;\n\n   auto error = IOS_Ioctlv(handle, IOSCCommand::Decrypt, 3u, 1u, vecs);\n\n   freeIpcData(request);\n   freeIpcData(vecs);\n   return static_cast<IOSCError>(error);\n}\n\n} // namespace ios::crypto\n"
  },
  {
    "path": "src/libdecaf/src/ios/crypto/ios_crypto_ipc.h",
    "content": "#pragma once\n#include \"ios_crypto_enum.h\"\n#include \"ios_crypto_types.h\"\n\n#include \"ios/ios_enum.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n\nnamespace ios::crypto\n{\n\nusing IOSCHandle = kernel::ResourceHandleId;\n\nError\nIOSC_Open();\n\nError\nIOSC_Close(IOSCHandle handle);\n\nIOSCError\nIOSC_Decrypt(IOSCHandle handle,\n             IOSCKeyHandle keyHandle,\n             phys_ptr<const void> ivData, uint32_t ivSize,\n             phys_ptr<const void> inputData, uint32_t inputSize,\n             phys_ptr<void> outputData, uint32_t outputSize);\n\n} // namespace ios::crypto\n"
  },
  {
    "path": "src/libdecaf/src/ios/crypto/ios_crypto_log.h",
    "content": "#pragma once\n#include <common/log.h>\n\nnamespace ios::crypto::internal\n{\n\nextern Logger cryptoLog;\n\n} // namespace ios::crypto::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/crypto/ios_crypto_request.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include \"ios_crypto_enum.h\"\n\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::crypto\n{\n\n/**\n * \\ingroup ios_crypto\n * @{\n */\n\n#pragma pack(push, 1)\n\nusing IOSCKeyHandle = uint32_t;\n\nstruct IOSCRequestDecrypt\n{\n   be2_val<uint32_t> unknown0x00;\n   be2_val<uint32_t> unknown0x04;\n   be2_val<IOSCKeyHandle> keyHandle;\n   PADDING(4);\n};\nCHECK_OFFSET(IOSCRequestDecrypt, 0x00, unknown0x00);\nCHECK_OFFSET(IOSCRequestDecrypt, 0x04, unknown0x04);\nCHECK_OFFSET(IOSCRequestDecrypt, 0x08, keyHandle);\nCHECK_SIZE(IOSCRequestDecrypt, 0x10);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::crypto\n"
  },
  {
    "path": "src/libdecaf/src/ios/crypto/ios_crypto_types.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace ios::crypto\n{\n\nusing IOSCKeyHandle = uint32_t;\n\n} // namespace ios::crypto\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd.cpp",
    "content": "#include \"ios_fpd.h\"\n#include \"ios_fpd_log.h\"\n#include \"ios_fpd_act_server.h\"\n#include \"ios_fpd_act_accountdata.h\"\n\n#include \"decaf_log.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n\nnamespace ios::fpd\n{\n\nusing namespace ios::kernel;\n\nconstexpr auto LocalHeapSize = 0xA0000u;\nconstexpr auto CrossHeapSize = 0xC0000u;\n\nstatic phys_ptr<void> sLocalHeapBuffer = nullptr;\n\nnamespace internal\n{\n\nLogger fpdLog = { };\n\nvoid\ninitialiseStaticData()\n{\n   sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize);\n}\n\n} // namespace internal\n\nError\nprocessEntryPoint(phys_ptr<void> /* context */)\n{\n   auto error = Error::OK;\n\n   // Initialise logger\n   internal::fpdLog = decaf::makeLogger(\"IOS_FPD\");\n\n   internal::initialiseStaticData();\n   internal::initialiseStaticActServerData();\n   internal::initialiseStaticAccountData();\n\n   // Initialise process heaps\n   error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize);\n   if (error < Error::OK) {\n      internal::fpdLog->error(\n         \"processEntryPoint: IOS_CreateLocalProcessHeap failed with error = {}\", error);\n      return error;\n   }\n\n   error = IOS_CreateCrossProcessHeap(CrossHeapSize);\n   if (error < Error::OK) {\n      internal::fpdLog->error(\n         \"processEntryPoint: IOS_CreateCrossProcessHeap failed with error = {}\", error);\n      return error;\n   }\n\n   error = internal::startActServer();\n   if (error < Error::OK) {\n      internal::fpdLog->error(\n         \"processEntryPoint: startActServer failed with error = {}\", error);\n      return error;\n   }\n\n   // TODO: Start /dev/fpd thread, Join /dev/fpd thread\n\n   return error;\n}\n\n} // namespace ios::fpd\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_process.h\"\n\nnamespace ios::fpd\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::fpd\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_accountdata.cpp",
    "content": "#include \"ios_fpd_act_accountdata.h\"\n#include \"ios_fpd_act_server.h\"\n#include \"ios_fpd_log.h\"\n\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"nn/ffl/nn_ffl_miidata.h\"\n#include \"nn/act/nn_act_result.h\"\n\n#include <common/platform_time.h>\n#include <algorithm>\n#include <array>\n#include <charconv>\n#include <chrono>\n#include <common/platform_time.h>\n#include <cstring>\n#include <string_view>\n\nusing namespace nn::act;\nusing namespace nn::ffl;\nusing namespace ios::fs;\nusing namespace ios::kernel;\n\nnamespace ios::fpd::internal\n{\n\n//! TODO: This hash is actually read from /dev/mcp ioctl 0xD3\nstatic constexpr std::array<uint8_t, 4> UnknownHash = { 0xDE, 0xCA, 0xF0, 0x0D };\n\n//! TODO: Device Hash is actually calculated from UnkownHash using some sort of sha256\nstatic constexpr std::array<uint8_t, 8> DeviceHash = { 0xDE, 0xCA, 0xFD, 0xEC, 0xAF, 0xDE, 0xCA, 0xF0 };\n\nstruct AccountData\n{\n   be2_struct<UuidManager> uuidManager;\n   be2_struct<TransferableIdManager> transferableIdManager;\n   be2_struct<PersistentIdManager> persistentIdManager;\n   be2_struct<AccountManager> accountManager;\n   be2_array<AccountInstance, NumSlots> accounts;\n   be2_array<bool, NumSlots> accountUsed;\n\n   be2_phys_ptr<AccountInstance> currentAccount;\n   be2_phys_ptr<AccountInstance> defaultAccount;\n};\n\nstatic phys_ptr<AccountData> sAccountData = nullptr;\n\nphys_ptr<TransferableIdManager>\ngetTransferableIdManager()\n{\n   return phys_addrof(sAccountData->transferableIdManager);\n}\n\nphys_ptr<UuidManager>\ngetUuidManager()\n{\n   return phys_addrof(sAccountData->uuidManager);\n}\n\nphys_ptr<PersistentIdManager>\ngetPersistentIdManager()\n{\n   return phys_addrof(sAccountData->persistentIdManager);\n}\n\nphys_ptr<AccountManager>\ngetAccountManager()\n{\n   return phys_addrof(sAccountData->accountManager);\n}\n\nphys_ptr<AccountInstance>\ngetCurrentAccount()\n{\n   return sAccountData->currentAccount;\n}\n\nvoid\nsetCurrentAccount(phys_ptr<AccountInstance> account)\n{\n   sAccountData->currentAccount = account;\n}\n\nphys_ptr<AccountInstance>\ngetDefaultAccount()\n{\n   return sAccountData->defaultAccount;\n}\n\nstd::array<uint8_t, 8>\ngetDeviceHash()\n{\n   return DeviceHash;\n}\n\nuint8_t\ngetNumAccounts()\n{\n   auto count = uint8_t { 0 };\n   for (auto used : sAccountData->accountUsed) {\n      if (used) {\n         count++;\n      }\n   }\n\n   return count;\n}\n\nSlotNo\ngetSlotNoForAccount(phys_ptr<AccountInstance> account)\n{\n   auto index = account - phys_addrof(sAccountData->accounts[0]);\n   if (index < 0 || index >= sAccountData->accounts.size()) {\n      return InvalidSlot;\n   }\n\n   return static_cast<SlotNo>(index + 1);\n}\n\nphys_ptr<AccountInstance>\ngetAccountBySlotNo(SlotNo slot)\n{\n   if (slot == CurrentUserSlot) {\n      return sAccountData->currentAccount;\n   } else if (slot == InvalidSlot) {\n      return nullptr;\n   }\n\n   auto index = static_cast<unsigned>(slot - 1);\n   if (index >= sAccountData->accounts.size()) {\n      return nullptr;\n   }\n\n   if (!sAccountData->accountUsed[index]) {\n      return nullptr;\n   }\n\n   return phys_addrof(sAccountData->accounts[index]);\n}\n\nphys_ptr<AccountInstance>\ngetAccountByPersistentId(PersistentId id)\n{\n   for (auto i = 0u; i < sAccountData->accounts.size(); ++i) {\n      if (sAccountData->accountUsed[i] &&\n          sAccountData->accounts[i].persistentId == id) {\n         return phys_addrof(sAccountData->accounts[i]);\n      }\n   }\n\n   return nullptr;\n}\n\nstatic uint64_t\nsub_E30BD3A0(uint32_t a, uint32_t b)\n{\n   uint32_t v2 = (b << 8) & 0xFF0000 | (b << 24) | (b >> 8) & 0xFF00 | (b >> 24);\n   uint32_t v3 = (a << 8) & 0xFF0000 | (a << 24) | (a >> 8) & 0xFF00 | (a >> 24);\n   uint32_t v4 = v2 ^ (v2 ^ (v2 >> 7)) & 0xAA00AA ^ (((v2 ^ (v2 >> 7)) & 0xAA00AA) << 7);\n   uint32_t v5 = v3 ^ (v3 ^ (v3 >> 7)) & 0xAA00AA ^ (((v3 ^ (v3 >> 7)) & 0xAA00AA) << 7);\n   uint32_t v6 = v5 ^ (v5 ^ (v5 >> 14)) & 0xCCCC ^ (((v5 ^ (v5 >> 14)) & 0xCCCC) << 14);\n   uint32_t v7 = v4 ^ (v4 ^ (v4 >> 14)) & 0xCCCC ^ (((v4 ^ (v4 >> 14)) & 0xCCCC) << 14);\n\n   uint32_t resultLo =\n      ((v7 & 0xF0F0F0F0 | (v6 >> 4) & 0xF0F0F0F) << 8) & 0xFF0000 |\n      ((v7 & 0xF0F0F0F0 | (v6 >> 4) & 0xF0F0F0F) << 24) |\n      ((v7 & 0xF0F0F0F0 | (v6 >> 4) & 0xF0F0F0F) >> 8) & 0xFF00 |\n      ((v7 & 0xF0F0F0F0 | (v6 >> 4) & 0xF0F0F0F) >> 24);\n   uint32_t resultHi =\n      ((16 * v7 & 0xF0F0F0F0 | v6 & 0xF0F0F0F) << 8) & 0xFF0000 |\n      ((16 * v7 & 0xF0F0F0F0 | v6 & 0xF0F0F0F) << 24) |\n      ((16 * v7 & 0xF0F0F0F0 | v6 & 0xF0F0F0F) >> 8) & 0xFF00 |\n      ((16 * v7 & 0xF0F0F0F0 | v6 & 0xF0F0F0F) >> 24);\n\n   return static_cast<uint64_t>(resultLo) | (static_cast<uint64_t>(resultHi) << 32);\n}\n\nTransferrableId\ncalculateTransferableId(uint64_t transferableIdBase, uint16_t a3)\n{\n   uint32_t a1_hi = static_cast<uint32_t>(transferableIdBase >> 32);\n   uint32_t a1_lo = static_cast<uint32_t>(transferableIdBase);\n   uint32_t v3 = (a3 << 6) | (a1_hi);\n   uint32_t v4 = ((v3 >> 16) & 0x1C0) | ((a1_lo << 3) & 0x38) | ((v3 >> 6) & 7);\n   return sub_E30BD3A0(\n      v3 ^ (((v4 << 31) | (v4 << 22) | (v4 >> 5) | (v4 >> 23) | (v4 >> 14) | (v4 << 4) | (v4 << 13)) & 0xFFFFFE3F),\n      a1_lo ^ ((v4 << 27) | (v4 << 9) | (v4 << 18) | v4));\n}\n\nstatic uint16_t\nFFLiGetCRC16(phys_ptr<const uint8_t> bytes,\n             uint32_t length)\n{\n   auto crc = uint32_t { 0 };\n\n   for (auto byteIndex = 0u; byteIndex < length; byteIndex++) {\n      for (auto bitIndex = 7; bitIndex >= 0; bitIndex--) {\n         crc = (((crc << 1) | ((bytes[byteIndex] >> bitIndex) & 0x1)) ^\n               (((crc & 0x8000) != 0) ? 0x1021 : 0));\n      }\n   }\n\n   for (auto counter = 16; counter > 0u; counter--) {\n      crc = ((crc << 1) ^ (((crc & 0x8000) != 0) ? 0x1021 : 0));\n   }\n\n   return static_cast<uint16_t>(crc & 0xFFFF);\n}\n\nstatic void\nFFLiSetAuthorID(uint64_t *authorId)\n{\n   *authorId = calculateTransferableId(\n      sAccountData->accountManager.commonTransferableIdBase, 0x4A0);\n}\n\nstatic void\nFFLiSetCreateID(FFLCreateID *createId)\n{\n   static constexpr tm MiiEpoch = { 0, 0, 0, 1, 0, 2010 - 1900, 0, 0, 0 };\n   auto epoch = std::chrono::system_clock::from_time_t(platform::make_gm_time(MiiEpoch));\n   auto secondsSinceMiiEpoch =\n      std::chrono::duration_cast<std::chrono::seconds>(\n         std::chrono::system_clock::now() - epoch);\n\n   createId->flags = FFLCreateIDFlags::IsNormalMii | FFLCreateIDFlags::IsWiiUMii;\n   createId->timestamp = static_cast<uint32_t>(secondsSinceMiiEpoch.count() & 0x0FFFFFFF);\n   std::memcpy(createId->deviceHash, DeviceHash.data(), 6);\n}\n\nstatic long long\ngetUuidTime()\n{\n   auto start = std::chrono::system_clock::from_time_t(-12219292800);\n   auto diff = std::chrono::system_clock::now() - start;\n   auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(diff).count();\n   return ns / 100;\n}\n\nstatic std::array<uint8_t, UuidSize>\ngenerateUuid()\n{\n   auto time = getUuidTime();\n   sAccountData->uuidManager.lastTime = time;\n\n   auto time_low = static_cast<uint32_t>(time);\n   auto time_mid = static_cast<uint16_t>((time >> 32) & 0xFFFF);\n   auto time_hi_and_version = static_cast<uint16_t>(\n      static_cast<uint16_t>((time >> 48) & 0x0FFF) | 0x1000);\n\n   auto clock_seq = static_cast<uint16_t>(\n      (++sAccountData->uuidManager.clockSequence & 0x3FFF) | 0x8000);\n   if (sAccountData->uuidManager.clockSequence >= 0x4000) {\n      sAccountData->uuidManager.clockSequence = 0;\n   }\n\n   auto node = std::array<uint8_t, 6> { };\n   node.fill(0);\n   node[0] = 1;\n   node[1] = 1;\n   std::memcpy(node.data() + 2, UnknownHash.data(), UnknownHash.size());\n\n   auto uuid = std::array<uint8_t, UuidSize> { };\n   std::memcpy(uuid.data() + 0, &time_low, 4);\n   std::memcpy(uuid.data() + 4, &time_mid, 2);\n   std::memcpy(uuid.data() + 6, &time_hi_and_version, 2);\n   std::memcpy(uuid.data() + 8, &clock_seq, 2);\n   std::memcpy(uuid.data() + 10, node.data(), 6);\n   return uuid;\n}\n\nstatic PersistentId\ngeneratePersistentId()\n{\n   return ++sAccountData->persistentIdManager.persistentIdHead;\n}\n\nstatic TransferrableId\ngenerateTransferrableIdBase()\n{\n   auto valueLo = *reinterpret_cast<const uint32_t *>(UnknownHash.data());\n   auto valueHi = (sAccountData->transferableIdManager.counter << 22) | 4;\n   sAccountData->transferableIdManager.counter =\n      static_cast<uint32_t>((sAccountData->transferableIdManager.counter + 1) & 0x3FF);\n\n   return static_cast<uint64_t>(valueLo) | (static_cast<uint64_t>(valueHi) << 32);\n}\n\nstatic phys_ptr<AccountInstance>\nallocateAccount()\n{\n   for (auto i = 0u; i < sAccountData->accounts.size(); ++i) {\n      if (!sAccountData->accountUsed[i]) {\n         sAccountData->accountUsed[i] = true;\n         return phys_addrof(sAccountData->accounts[i]);\n      }\n   }\n\n   return nullptr;\n}\n\nphys_ptr<AccountInstance>\ncreateAccount()\n{\n   auto account = allocateAccount();\n   if (!account) {\n      return nullptr;\n   }\n\n   std::memset(account.get(), 0, sizeof(AccountInstance));\n   account->persistentId = generatePersistentId();\n   account->parentalControlSlotNo = uint8_t { 1u };\n   account->principalId = 1u;\n   account->simpleAddressId = 1u;\n   account->transferableIdBase = generateTransferrableIdBase();\n   account->accountId = \"DonaldTrump420\";\n   account->nfsPassword = \"NfsPassword\";\n   account->birthDay = uint8_t { 4 };\n   account->birthMonth = uint8_t { 6 };\n   account->birthYear = uint16_t { 1989 };\n   account->gender = uint8_t { 1 };\n   account->uuid = generateUuid();\n   account->isCommitted = uint8_t { 0 };\n\n   // Default Mii from IOS\n   static uint8_t DefaultMii[] = {\n      0x00, 0x01, 0x00, 0x40, 0x80, 0xF3, 0x41, 0x80, 0x02, 0x65, 0xA0, 0x92,\n      0xD2, 0x3B, 0x13, 0x36, 0xA4, 0xC0, 0xE1, 0xF8, 0x2D, 0x06, 0x00, 0x00,\n      0x00, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00,\n      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,\n      0x00, 0x00, 0x21, 0x01, 0x02, 0x68, 0x44, 0x18, 0x26, 0x34, 0x46, 0x14,\n      0x81, 0x12, 0x17, 0x68, 0x0D, 0x00, 0x00, 0x29, 0x00, 0x52, 0x48, 0x50,\n      0x00, 0x00, 0x61, 0x00, 0x72, 0x00, 0x61, 0x00, 0x68, 0x00, 0x00, 0x00,\n      0x67, 0x00, 0x68, 0x00, 0x69, 0x00, 0x6A, 0x00, 0x00, 0x00, 0xE4, 0x62,\n   };\n\n   static_assert(sizeof(DefaultMii) == sizeof(account->miiData));\n   std::memcpy(std::addressof(account->miiData), DefaultMii, sizeof(DefaultMii));\n   FFLiSetAuthorID(&account->miiData.author_id);\n   FFLiSetCreateID(&account->miiData.mii_id);\n   account->miiData.checksum = FFLiGetCRC16(phys_cast<uint8_t *>(phys_addrof(account->miiData)),\n      sizeof(FFLStoreData) - 2);\n   account->miiName = u\"???\";\n\n   return account;\n}\n\ntemplate<typename ReadKeyValueCallback>\nstatic FSAStatus\nreadPropertyFile(std::string_view path,\n                 std::string_view typeName,\n                 ReadKeyValueCallback readKeyValueCallback)\n{\n   auto fsaHandle = getActFsaHandle();\n   auto fileHandle = FSAFileHandle { -1 };\n   auto result = FSAOpenFile(fsaHandle, path, \"r\", &fileHandle);\n   if (result < 0) {\n      internal::fpdLog->debug(\"Could not open {}\", path);\n      return result;\n   }\n\n   auto fileBufferSize = 1024u;\n   auto fileBuffer = phys_cast<char *>(IOS_HeapAllocAligned(CrossProcessHeapId, fileBufferSize, 0x40));\n   if (!fileBuffer) {\n      internal::fpdLog->debug(\"Could not allocate file buffer\");\n      FSACloseFile(fsaHandle, fileHandle);\n      return FSAStatus::OutOfResources;\n   }\n\n   auto lineStartPos = 0u;\n   auto readBufferOffset = 0u;\n   auto readFileHeader = false;\n\n   while (true) {\n      result = FSAReadFile(fsaHandle, fileBuffer + readBufferOffset,\n                           1, fileBufferSize - readBufferOffset,\n                           fileHandle, FSAReadFlag::None);\n      if (result < 0) {\n         internal::fpdLog->debug(\"FSAReadFile failed\");\n         IOS_HeapFree(CrossProcessHeapId, fileBuffer);\n         FSACloseFile(fsaHandle, fileHandle);\n         return result;\n      }\n\n      auto bytesRead = static_cast<uint32_t>(result);\n      auto bufferPos = readBufferOffset;\n\n      auto eof = bytesRead < (fileBufferSize - readBufferOffset);\n      if (bytesRead == 0) {\n         // Hit exactly eof with the previous read\n         break;\n      }\n\n      while (bufferPos < bytesRead) {\n         if (fileBuffer[bufferPos] == '\\n') {\n            auto line = std::string_view {\n               fileBuffer.get() + lineStartPos, bufferPos - lineStartPos\n            };\n\n            if (!readFileHeader) {\n               // Check that line starts with typeName\n               if (line.substr(0, typeName.size()).compare(typeName) != 0) {\n                  result = FSAStatus::DataCorrupted;\n                  eof = true;\n                  break;\n               }\n\n               readFileHeader = true;\n            } else {\n               // Should be a Key=Value line\n               auto separator = line.find_first_of('=');\n               if (separator == std::string_view::npos) {\n                  result = FSAStatus::DataCorrupted;\n                  eof = true;\n                  break;\n               }\n\n               auto key = line.substr(0, separator);\n               auto value = line.substr(separator + 1);\n               if (key.empty()) {\n                  result = FSAStatus::DataCorrupted;\n                  eof = true;\n                  break;\n               }\n\n               readKeyValueCallback(key, value);\n            }\n\n            lineStartPos = bufferPos + 1;\n         }\n\n         ++bufferPos;\n      }\n\n      if (eof) {\n         // Reached end of file with last read\n         break;\n      }\n\n      if (lineStartPos == 0u) {\n         // Could not fit whole line in our buffer, must be a bad file\n         result = FSAStatus::DataCorrupted;\n         break;\n      }\n\n      if (bufferPos == bytesRead) {\n         std::memmove(fileBuffer.get(),\n                      fileBuffer.get() + lineStartPos,\n                      sizeof(bytesRead - lineStartPos));\n         readBufferOffset = bytesRead - lineStartPos;\n         lineStartPos = 0u;\n      }\n   }\n\n   if (result > 0) {\n      result = FSAStatus::OK;\n   }\n\n   FSACloseFile(fsaHandle, fileHandle);\n   IOS_HeapFree(CrossProcessHeapId, fileBuffer);\n   return result;\n}\n\nstatic void\nparseBoolProperty(std::string_view value, be2_val<uint8_t> &out)\n{\n   int temporary = 0;\n   std::from_chars(value.data(), value.data() + value.size(), temporary, 16);\n   out = temporary != 0;\n}\n\ntemplate<typename Type>\nstatic void\nparseIntegerProperty(std::string_view value, be2_val<Type> &out)\n{\n   Type result { 0 };\n   std::from_chars(value.data(), value.data() + value.size(), result, 16);\n   out = result;\n}\n\nstatic void\nparseHexString(std::string_view value, phys_ptr<uint8_t> out, size_t size)\n{\n   std::memset(out.get(), 0, sizeof(size));\n\n   for (auto i = 0; i < value.size() && i / 2 < size; ++i) {\n      auto nibble = uint8_t { 0 };\n      if (value[i] >= 'a' && value[i] <= 'f') {\n         nibble = (value[i] - 'a') + 0xa;\n      } else if (value[i] >= 'A' && value[i] <= 'F') {\n         nibble = (value[i] - 'A') + 0xA;\n      } else if (value[i] >= '0' && value[i] <= '9') {\n         nibble = (value[i] - '0') + 0x0;\n      }\n\n      out[i / 2] |= static_cast<uint8_t>((i % 2) ? nibble : (nibble << 4));\n   }\n}\n\ntemplate<uint32_t Size>\nstatic void\nparseStringProperty(std::string_view value, be2_array<char, Size> &out)\n{\n   out.fill(0);\n   std::memcpy(phys_addrof(out).get(), value.data(),\n               std::min<std::size_t>(value.size(), out.size()));\n\n   if (value.size() == out.size()) {\n      out.back() = '\\0';\n   }\n}\n\nstatic bool\nloadTransferableIdManager(phys_ptr<TransferableIdManager> transferableIdManager)\n{\n   auto result = readPropertyFile(\n      \"/vol/storage_mlc01/usr/save/system/act/transid.dat\", \"TransferableIdManager\",\n      [transferableIdManager=transferableIdManager.get()]\n      (std::string_view key, std::string_view value) {\n         if (key == \"Counter\") {\n            parseIntegerProperty(value, transferableIdManager->counter);\n         }\n      });\n\n   if (result == FSAStatus::OK) {\n      return true;\n   }\n\n   transferableIdManager->counter = 0u;\n   return false;\n}\n\nstatic bool\nloadUuidManager(phys_ptr<UuidManager> uuidManager)\n{\n   auto result = readPropertyFile(\n      \"/vol/storage_mlc01/usr/save/system/act/uuid.dat\", \"UuidManager\",\n      [uuidManager=uuidManager.get()]\n      (std::string_view key, std::string_view value) {\n         if (key == \"ClockSequence\") {\n            parseIntegerProperty(value, uuidManager->clockSequence);\n         } else if (key == \"LastTime\") {\n            parseIntegerProperty(value, uuidManager->lastTime);\n         }\n      });\n\n   if (result == FSAStatus::OK) {\n      return true;\n   }\n\n   uuidManager->lastTime = 0ll;\n   uuidManager->clockSequence = 0;\n   return false;\n}\n\nstatic bool\nloadPersistentIdManager(phys_ptr<PersistentIdManager> persistentIdManager)\n{\n   auto result = readPropertyFile(\n      \"/vol/storage_mlc01/usr/save/system/act/persisid.dat\", \"PersistentIdManager\",\n      [persistentIdManager=persistentIdManager.get()]\n      (std::string_view key, std::string_view value) {\n         if (key == \"PersistentIdHead\") {\n            parseIntegerProperty(value, persistentIdManager->persistentIdHead);\n         }\n      });\n\n   if (result == FSAStatus::OK) {\n      return true;\n   }\n\n   persistentIdManager->persistentIdHead = 0x80000000;\n   return false;\n}\n\n\nstatic void\nparseNnasSubDomain(std::string_view subDomain,\n                   be2_val<uint32_t> &outNnasType)\n{\n   if (subDomain.compare(\"game-dev.\") == 0) {\n      outNnasType = 1u;\n   } else if (subDomain.compare(\"system-dev.\") == 0) {\n      outNnasType = 2u;\n   } else if (subDomain.compare(\"library-dev.\") == 0) {\n      outNnasType = 3u;\n   } else if (subDomain.compare(\"staging.\") == 0) {\n      outNnasType = 4u;\n   } else {\n      outNnasType = 0u;\n   }\n}\n\nstatic void\nparseNnasNfsEnv(std::string_view nfsEnv,\n                be2_val<uint32_t> &outNfsType,\n                be2_val<uint8_t> &outNfsNo)\n{\n   switch (nfsEnv[0]) {\n   case 'D':\n      outNfsType = 1u;\n      break;\n   case 'J':\n      outNfsType = 4u;\n      break;\n   case 'L':\n      outNfsType = 0u;\n      break;\n   case 'S':\n      outNfsType = 2u;\n      break;\n   case 'T':\n      outNfsType = 3u;\n      break;\n   default:\n      outNfsNo = uint8_t { 1 };\n      outNfsType = 0u;\n      return;\n   }\n\n   outNfsNo = std::clamp<uint8_t>(nfsEnv[1] - '0', 1, 9);\n}\n\nstatic bool\nloadAccountManager(phys_ptr<AccountManager> accountManager)\n{\n   auto result = readPropertyFile(\n      \"/vol/storage_mlc01/usr/save/system/act/common.dat\", \"AccountManager\",\n      [accountManager](std::string_view key, std::string_view value) {\n         if (key == \"PersistentIdList\") {\n            auto start = size_t { 0 };\n            auto end = value.find(\"\\\\0\");\n            auto index = 0;\n            accountManager->persistentIdList.fill(0);\n\n            while (end != std::string_view::npos) {\n               auto persistentId = value.substr(start, end - start);\n               parseIntegerProperty(persistentId, accountManager->persistentIdList[index++]);\n               start = end + 2;\n               end = value.find(\"\\\\0\", start);\n            }\n         } else if (key == \"DefaultAccountPersistentId\") {\n            parseIntegerProperty(value, accountManager->defaultAccountPersistentId);\n         } else if (key == \"CommonTransferableIdBase\") {\n            parseIntegerProperty(value, accountManager->commonTransferableIdBase);\n         } else if (key == \"CommonUuid\") {\n            parseHexString(value, phys_addrof(accountManager->commonUuid), accountManager->commonUuid.size());\n         } else if (key == \"IsApplicationUpdateRequired\") {\n            parseBoolProperty(value, accountManager->isApplicationUpdateRequired);\n         } else if (key == \"DefaultNnasType\") {\n            parseIntegerProperty(value, accountManager->defaultNnasType);\n         } else if (key == \"DefaultNfsType\") {\n            parseIntegerProperty(value, accountManager->defaultNfsType);\n         } else if (key == \"DefaultNfsNo\") {\n            parseIntegerProperty(value, accountManager->defaultNfsNo);\n         } else if (key == \"DefaultNnasSubDomain\") {\n            parseStringProperty(value, accountManager->defaultNnasSubDomain);\n         } else if (key == \"DefaultNnasNfsEnv\") {\n            parseStringProperty(value, accountManager->defaultNnasNfsEnv);\n         }\n      });\n\n   if (result == FSAStatus::OK) {\n      return true;\n   }\n\n   accountManager->persistentIdList.fill(0);\n   accountManager->defaultAccountPersistentId = 0u;\n   accountManager->commonTransferableIdBase = 0u;\n   accountManager->commonUuid.fill(0);\n   accountManager->isApplicationUpdateRequired = false;\n\n   accountManager->defaultNnasSubDomain.fill(0);\n   parseNnasSubDomain(phys_addrof(accountManager->defaultNnasSubDomain).get(),\n                      accountManager->defaultNnasType);\n\n   accountManager->defaultNnasNfsEnv.fill(0);\n   accountManager->defaultNnasNfsEnv = \"L1\";\n   parseNnasNfsEnv(phys_addrof(accountManager->defaultNnasNfsEnv).get(),\n                   accountManager->defaultNfsType,\n                   accountManager->defaultNfsNo);\n   return false;\n}\n\nstatic bool\nloadAccountInstance(phys_ptr<AccountInstance> accountInstance,\n                    PersistentId persistentId)\n{\n   auto path = fmt::format(\"/vol/storage_mlc01/usr/save/system/act/{:08x}/account.dat\", persistentId);\n   auto result = readPropertyFile(\n      path, \"AccountInstance\",\n      [accountInstance](std::string_view key, std::string_view value) {\n         if (key == \"PersistentId\") {\n            parseIntegerProperty(value, accountInstance->persistentId);\n         } else if (key == \"TransferableIdBase\") {\n            parseIntegerProperty(value, accountInstance->transferableIdBase);\n         } else if (key == \"Uuid\") {\n            parseHexString(value, phys_addrof(accountInstance->uuid),\n                           accountInstance->uuid.size());\n         } else if (key == \"ParentalControlSlotNo\") {\n            parseIntegerProperty(value, accountInstance->parentalControlSlotNo);\n         } else if (key == \"MiiData\") {\n            parseHexString(value,\n                           phys_cast<uint8_t *>(phys_addrof(accountInstance->miiData)),\n                           sizeof(accountInstance->miiData));\n         } else if (key == \"MiiName\") {\n            parseHexString(value,\n                           phys_cast<uint8_t *>(phys_addrof(accountInstance->miiName)),\n                           accountInstance->miiName.size() * 2);\n         } else if (key == \"IsMiiUpdated\") {\n            parseBoolProperty(value, accountInstance->isMiiUpdated);\n         } else if (key == \"AccountId\") {\n            parseStringProperty(value, accountInstance->accountId);\n         } else if (key == \"BirthYear\") {\n            parseIntegerProperty(value, accountInstance->birthYear);\n         } else if (key == \"BirthMonth\") {\n            parseIntegerProperty(value, accountInstance->birthMonth);\n         } else if (key == \"BirthDay\") {\n            parseIntegerProperty(value, accountInstance->birthDay);\n         } else if (key == \"Gender\") {\n            parseIntegerProperty(value, accountInstance->gender);\n         } else if (key == \"IsMailAddressValidated\") {\n            parseBoolProperty(value, accountInstance->isMailAddressValidated);\n         } else if (key == \"EmailAddress\") {\n            parseStringProperty(value, accountInstance->emailAddress);\n         } else if (key == \"Country\") {\n            parseIntegerProperty(value, accountInstance->country);\n         } else if (key == \"SimpleAddressId\") {\n            parseIntegerProperty(value, accountInstance->simpleAddressId);\n         } else if (key == \"TimeZoneId\") {\n            parseStringProperty(value, accountInstance->timeZoneId);\n         } else if (key == \"UtcOffset\") {\n            parseIntegerProperty(value, accountInstance->utcOffset);\n         } else if (key == \"PrincipalId\") {\n            parseIntegerProperty(value, accountInstance->principalId);\n         } else if (key == \"NfsPassword\") {\n            parseStringProperty(value, accountInstance->nfsPassword);\n         } else if (key == \"EciVirtualAccount\") {\n            parseStringProperty(value, accountInstance->eciVirtualAccount);\n         } else if (key == \"NeedsToDownloadMiiImage\") {\n            parseBoolProperty(value, accountInstance->needsToDownloadMiiImage);\n         } else if (key == \"MiiImageUrl\") {\n            parseStringProperty(value, accountInstance->miiImageUrl);\n         } else if (key == \"AccountPasswordHash\") {\n            parseHexString(value, phys_addrof(accountInstance->accountPasswordHash),\n                           accountInstance->accountPasswordHash.size());\n         } else if (key == \"IsPasswordCacheEnabled\") {\n            parseBoolProperty(value, accountInstance->isPasswordCacheEnabled);\n         } else if (key == \"AccountPasswordCache\") {\n            parseHexString(value, phys_addrof(accountInstance->accountPasswordCache),\n                           accountInstance->accountPasswordCache.size());\n         } else if (key == \"NnasType\") {\n            parseIntegerProperty(value, accountInstance->nnasType);\n         } else if (key == \"NfsType\") {\n            parseIntegerProperty(value, accountInstance->nfsType);\n         } else if (key == \"NfsNo\") {\n            parseIntegerProperty(value, accountInstance->nfsNo);\n         } else if (key == \"NnasSubDomain\") {\n            parseStringProperty(value, accountInstance->nnasSubDomain);\n         } else if (key == \"NnasNfsEnv\") {\n            parseStringProperty(value, accountInstance->nnasNfsEnv);\n         } else if (key == \"IsPersistentIdUploaded\") {\n            parseBoolProperty(value, accountInstance->isPersistentIdUploaded);\n         } else if (key == \"IsConsoleAccountInfoUploaded\") {\n            parseBoolProperty(value, accountInstance->isConsoleAccountInfoUploaded);\n         } else if (key == \"LastAuthenticationResult\") {\n            parseIntegerProperty(value, accountInstance->lastAuthenticationResult);\n         } else if (key == \"StickyAccountId\") {\n            parseStringProperty(value, accountInstance->stickyAccountId);\n         } else if (key == \"NextAccountId\") {\n            parseStringProperty(value, accountInstance->nextAccountId);\n         } else if (key == \"StickyPrincipalId\") {\n            parseIntegerProperty(value, accountInstance->stickyPrincipalId);\n         } else if (key == \"IsServerAccountDeleted\") {\n            parseBoolProperty(value, accountInstance->isServerAccountDeleted);\n         } else if (key == \"ServerAccountStatus\") {\n            parseIntegerProperty(value, accountInstance->serverAccountStatus);\n         } else if (key == \"MiiImageLastModifiedDate\") {\n            parseStringProperty(value, accountInstance->miiImageLastModifiedDate);\n         } else if (key == \"IsCommitted\") {\n            parseBoolProperty(value, accountInstance->isCommitted);\n         }\n      });\n\n   if (result == FSAStatus::OK) {\n      return true;\n   }\n\n   std::memset(accountInstance.get(), 0, sizeof(AccountInstance));\n   return false;\n}\n\nvoid\ninitialiseAccounts()\n{\n   loadTransferableIdManager(phys_addrof(sAccountData->transferableIdManager));\n   loadUuidManager(phys_addrof(sAccountData->uuidManager));\n   loadPersistentIdManager(phys_addrof(sAccountData->persistentIdManager));\n\n   loadAccountManager(phys_addrof(sAccountData->accountManager));\n   if (!sAccountData->accountManager.commonUuid[0]) {\n      sAccountData->accountManager.commonUuid = generateUuid();\n   }\n\n   if (!sAccountData->accountManager.commonTransferableIdBase) {\n      sAccountData->accountManager.commonTransferableIdBase = generateTransferrableIdBase();\n   }\n\n   for (auto slot = 1u; slot <= sAccountData->accountManager.persistentIdList.size(); ++slot) {\n      auto persistentId = sAccountData->accountManager.persistentIdList[slot - 1];\n      if (persistentId) {\n         sAccountData->accountUsed[slot - 1] =\n            loadAccountInstance(phys_addrof(sAccountData->accounts[slot - 1]),\n                                persistentId);\n      }\n   }\n\n   // Try find the default account\n   auto account = getAccountByPersistentId(sAccountData->accountManager.defaultAccountPersistentId);\n   if (!account) {\n      // Try find any account\n      for (auto i = 0u; i < sAccountData->accounts.size(); ++i) {\n         if (sAccountData->accountUsed[i]) {\n            account = phys_addrof(sAccountData->accounts[i]);\n            break;\n         }\n      }\n\n      if (!account) {\n         // No account found, we can create one instead!\n         account = createAccount();\n         account->isCommitted = uint8_t { 1u };\n         sAccountData->accountManager.persistentIdList[getSlotNoForAccount(account)] = account->persistentId;\n         sAccountData->accountManager.defaultAccountPersistentId = account->persistentId;\n      }\n   }\n\n   sAccountData->currentAccount = account;\n   sAccountData->defaultAccount = account;\n}\n\nvoid\ninitialiseStaticAccountData()\n{\n   sAccountData = kernel::allocProcessStatic<AccountData>();\n}\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_accountdata.h",
    "content": "#pragma once\n#include \"nn/act/nn_act_types.h\"\n#include \"nn/ffl/nn_ffl_miidata.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::fpd::internal\n{\n\nusing nn::act::SlotNo;\nusing nn::act::LocalFriendCode;\nusing nn::act::PersistentId;\nusing nn::act::PrincipalId;\nusing nn::act::SimpleAddressId;\nusing nn::act::TransferrableId;\nusing nn::act::Uuid;\n\nusing nn::act::NumSlots;\nusing nn::act::AccountIdSize;\nusing nn::act::NfsPasswordSize;\nusing nn::act::MiiNameSize;\nusing nn::act::UuidSize;\n\nstruct TransferableIdManager\n{\n   be2_val<uint32_t> counter;\n};\n\nstruct UuidManager\n{\n   be2_val<int32_t> clockSequence;\n   be2_val<int64_t> lastTime;\n};\n\nstruct PersistentIdManager\n{\n   be2_val<uint32_t> persistentIdHead;\n};\n\nstruct AccountManager\n{\n   be2_array<PersistentId, NumSlots> persistentIdList;\n   be2_val<PersistentId> defaultAccountPersistentId;\n   be2_val<TransferrableId> commonTransferableIdBase;\n   be2_array<uint8_t, UuidSize> commonUuid;\n   be2_val<uint8_t> isApplicationUpdateRequired;\n   be2_val<uint32_t> defaultNnasType;\n   be2_val<uint32_t> defaultNfsType;\n   be2_val<uint8_t> defaultNfsNo;\n   be2_array<char, 33> defaultNnasSubDomain;\n   be2_array<char, 3> defaultNnasNfsEnv;\n};\n\nstruct AccountInstance\n{\n   be2_val<PersistentId> persistentId;\n   be2_val<TransferrableId> transferableIdBase;\n   be2_array<uint8_t, UuidSize> uuid;\n   be2_val<SlotNo> parentalControlSlotNo;\n   be2_struct<nn::ffl::FFLStoreData> miiData;\n   be2_array<char16_t, MiiNameSize> miiName;\n   be2_val<uint8_t> isMiiUpdated;\n   be2_array<char, AccountIdSize> accountId;\n   be2_val<uint16_t> birthYear;\n   be2_val<uint8_t> birthMonth;\n   be2_val<uint8_t> birthDay;\n   be2_val<uint32_t> gender;\n   be2_val<uint8_t> isMailAddressValidated;\n   be2_array<char, 257> emailAddress;\n   be2_val<uint32_t> country;\n   be2_val<SimpleAddressId> simpleAddressId;\n   be2_array<char, 65> timeZoneId;\n   be2_val<uint64_t> utcOffset; // TODO: Seconds? Nanoseconds? etc\n   be2_val<PrincipalId> principalId;\n   be2_array<char, 17> nfsPassword;\n   be2_array<char, 32> eciVirtualAccount;\n   be2_val<uint8_t> needsToDownloadMiiImage;\n   be2_array<char, 257> miiImageUrl;\n   be2_array<uint8_t, 32> accountPasswordHash;\n   be2_val<uint8_t> isPasswordCacheEnabled;\n   be2_array<uint8_t, 32> accountPasswordCache;\n   be2_val<uint32_t> nnasType;\n   be2_val<uint32_t> nfsType;\n   be2_val<uint8_t> nfsNo;\n   be2_array<char, 33> nnasSubDomain;\n   be2_array<char, 3> nnasNfsEnv;\n   be2_val<uint8_t> isPersistentIdUploaded;\n   be2_val<uint8_t> isConsoleAccountInfoUploaded;\n   be2_val<uint32_t> lastAuthenticationResult;\n   be2_array<char, AccountIdSize> stickyAccountId;\n   be2_array<char, AccountIdSize> nextAccountId;\n   be2_val<PrincipalId> stickyPrincipalId;\n   be2_val<uint8_t> isServerAccountDeleted;\n   be2_val<uint32_t> serverAccountStatus;\n   be2_array<char, 31> miiImageLastModifiedDate;\n   be2_val<uint8_t> isCommitted;\n};\n\nphys_ptr<TransferableIdManager>\ngetTransferableIdManager();\n\nphys_ptr<UuidManager>\ngetUuidManager();\n\nphys_ptr<PersistentIdManager>\ngetPersistentIdManager();\n\nphys_ptr<AccountManager>\ngetAccountManager();\n\nphys_ptr<AccountInstance>\ngetCurrentAccount();\n\nvoid\nsetCurrentAccount(phys_ptr<AccountInstance> account);\n\nphys_ptr<AccountInstance>\ngetDefaultAccount();\n\nstd::array<uint8_t, 8>\ngetDeviceHash();\n\nuint8_t\ngetNumAccounts();\n\nSlotNo\ngetSlotNoForAccount(phys_ptr<AccountInstance> account);\n\nphys_ptr<AccountInstance>\ngetAccountBySlotNo(SlotNo slot);\n\nphys_ptr<AccountInstance>\ngetAccountByPersistentId(PersistentId id);\n\nTransferrableId\ncalculateTransferableId(uint64_t transferableIdBase, uint16_t a3);\n\nphys_ptr<AccountInstance>\ncreateAccount();\n\nvoid\ninitialiseAccounts();\n\nvoid\ninitialiseStaticAccountData();\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_accountloaderservice.cpp",
    "content": "#include \"ios_fpd_act_accountloaderservice.h\"\n#include \"ios_fpd_act_accountdata.h\"\n\n#include \"ios/nn/ios_nn_ipc_server_command.h\"\n#include \"nn/act/nn_act_result.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n\n#include <common/decaf_assert.h>\n\nusing namespace nn::act;\n\nusing nn::ipc::CommandHandlerArgs;\nusing nn::ipc::CommandId;\nusing nn::ipc::InBuffer;\nusing nn::ipc::ServerCommand;\n\nnamespace ios::fpd::internal\n{\n\nstatic nn::Result\nloadConsoleAccount(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<ActAccountLoaderService::LoadConsoleAccount> { args };\n   auto slotNo = SlotNo { 0 };\n   auto loadOption = ACTLoadOption { };\n   auto unkInBuffer = InBuffer<const char> { };\n   auto unkBool = bool { false };\n   command.ReadRequest(slotNo, loadOption, unkInBuffer, unkBool);\n\n   auto account = getAccountBySlotNo(slotNo);\n   if (!account) {\n      return ResultAccountNotFound;\n   }\n\n   /*\n   TODO: nn::act::LoadConsoleAccount\n   Not really sure what this does tbh.\n   */\n\n   setCurrentAccount(account);\n   return nn::ResultSuccess;\n}\n\nnn::Result\nActAccountLoaderService::commandHandler(uint32_t unk1,\n                                        CommandId command,\n                                        CommandHandlerArgs &args)\n{\n   switch (command) {\n   case LoadConsoleAccount::command:\n      return loadConsoleAccount(args);\n   default:\n      return nn::ipc::ResultInvalidMethodTag;\n   }\n}\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_accountloaderservice.h",
    "content": "#pragma once\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"nn/act/nn_act_accountloaderservice.h\"\n\nnamespace ios::fpd::internal\n{\n\nstruct ActAccountLoaderService : ::nn::act::services::AccountLoaderService\n{\n   static nn::Result\n   commandHandler(uint32_t unk1,\n                  nn::ipc::CommandId command,\n                  nn::ipc::CommandHandlerArgs &args);\n};\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_accountmanagerservice.cpp",
    "content": "#include \"ios_fpd_act_accountdata.h\"\n#include \"ios_fpd_act_accountmanagerservice.h\"\n\n#include \"ios/nn/ios_nn_ipc_server_command.h\"\n#include \"nn/act/nn_act_result.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n\nusing namespace nn::act;\n\nusing nn::ipc::CommandHandlerArgs;\nusing nn::ipc::CommandId;\nusing nn::ipc::OutBuffer;\nusing nn::ipc::ServerCommand;\n\nnamespace ios::fpd::internal\n{\n\nstatic nn::Result\ncreateConsoleAccount(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<ActAccountManagerService::CreateConsoleAccount> { args };\n   auto account = createAccount();\n   if (!account) {\n      return ResultSlotsFull;\n   }\n\n   account->isCommitted = uint8_t { 0 };\n   return nn::ResultSuccess;\n}\n\nnn::Result\nActAccountManagerService::commandHandler(uint32_t unk1,\n                                         CommandId command,\n                                         CommandHandlerArgs &args)\n{\n   switch (command) {\n   case CreateConsoleAccount::command:\n      return createConsoleAccount(args);\n   default:\n      return nn::ipc::ResultInvalidMethodTag;\n   }\n}\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_accountmanagerservice.h",
    "content": "#pragma once\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"nn/act/nn_act_accountmanagerservice.h\"\n\nnamespace ios::fpd::internal\n{\n\nstruct ActAccountManagerService : ::nn::act::services::AccountManagerService\n{\n   static nn::Result\n   commandHandler(uint32_t unk1,\n                  nn::ipc::CommandId command,\n                  nn::ipc::CommandHandlerArgs &args);\n};\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_clientstandardservice.cpp",
    "content": "#include \"ios_fpd_act_accountdata.h\"\n#include \"ios_fpd_act_clientstandardservice.h\"\n#include \"ios_fpd_act_server.h\"\n#include \"ios_fpd_log.h\"\n\n#include \"ios/ios_stackobject.h\"\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_timer.h\"\n#include \"ios/nn/ios_nn_ipc_server_command.h\"\n#include \"nn/act/nn_act_result.h\"\n#include \"nn/ffl/nn_ffl_miidata.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n\n#include <array>\n#include <charconv>\n#include <chrono>\n#include <cstring>\n#include <memory>\n#include <string>\n\nusing namespace nn::act;\nusing namespace nn::ffl;\nusing namespace ios::fs;\nusing namespace ios::kernel;\n\nusing nn::ipc::CommandHandlerArgs;\nusing nn::ipc::CommandId;\nusing nn::ipc::OutBuffer;\nusing nn::ipc::ServerCommand;\n\nnamespace ios::fpd::internal\n{\n\nstatic nn::Result\ngetCommonInfo(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<ActClientStandardService::GetCommonInfo> { args };\n   auto buffer = OutBuffer<void> { };\n   auto type = InfoType { 0 };\n   command.ReadRequest(buffer, type);\n\n   switch (type) {\n   case InfoType::NumOfAccounts:\n   {\n      if (buffer.totalSize() < sizeof(uint8_t)) {\n         return ResultInvalidSize;\n      }\n\n      uint8_t numAccounts = getNumAccounts();\n      buffer.writeOutput(&numAccounts, sizeof(uint8_t));\n      break;\n   }\n   case InfoType::SlotNo:\n   {\n      if (buffer.totalSize() < sizeof(SlotNo)) {\n         return ResultInvalidSize;\n      }\n\n      SlotNo slotNo = getSlotNoForAccount(getCurrentAccount());\n      buffer.writeOutput(&slotNo, sizeof(slotNo));\n      break;\n   }\n   case InfoType::DefaultAccount:\n   {\n      if (buffer.totalSize() < sizeof(SlotNo)) {\n         return ResultInvalidSize;\n      }\n\n      SlotNo slotNo = getSlotNoForAccount(getDefaultAccount());\n      buffer.writeOutput(&slotNo, sizeof(slotNo));\n      break;\n   }\n   case InfoType::NetworkTimeDifference:\n      return ResultNotImplemented;\n   case InfoType::LocalFriendCode:\n   {\n      auto accountManager = getAccountManager();\n      if (buffer.totalSize() < sizeof(accountManager->commonTransferableIdBase)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(accountManager->commonTransferableIdBase),\n                         sizeof(accountManager->commonTransferableIdBase));\n      break;\n   }\n   case InfoType::ApplicationUpdateRequired:\n   {\n      auto accountManager = getAccountManager();\n      if (buffer.totalSize() < sizeof(accountManager->isApplicationUpdateRequired)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(accountManager->isApplicationUpdateRequired),\n                         sizeof(accountManager->isApplicationUpdateRequired));\n      break;\n   }\n   case InfoType::DefaultHostServerSettings:\n      return ResultNotImplemented;\n   case InfoType::DefaultHostServerSettingsEx:\n      return ResultNotImplemented;\n   case InfoType::DeviceHash:\n   {\n      auto hash = getDeviceHash();\n      if (buffer.totalSize() < hash.size()) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(hash.data(), hash.size());\n      break;\n   }\n   case InfoType::NetworkTime:\n      return ResultNotImplemented;\n   default:\n      return ResultInvalidValue;\n   }\n\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\ngetAccountInfo(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<ActClientStandardService::GetAccountInfo> { args };\n   auto slotNo = InvalidSlot;\n   auto buffer = OutBuffer<void> { };\n   auto type = InfoType { 0 };\n   command.ReadRequest(slotNo, buffer, type);\n\n   auto account = getAccountBySlotNo(slotNo);\n   if (!account) {\n      return ResultAccountNotFound;\n   }\n\n   switch (type) {\n   case InfoType::PersistentId:\n      if (buffer.totalSize() < sizeof(account->persistentId)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->persistentId),\n                         sizeof(account->persistentId));\n      break;\n   case InfoType::LocalFriendCode:\n      if (buffer.totalSize() < sizeof(account->transferableIdBase)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->transferableIdBase),\n                         sizeof(account->transferableIdBase));\n      break;\n   case InfoType::Mii:\n      if (buffer.totalSize() < sizeof(account->miiData)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->miiData),\n                         sizeof(account->miiData));\n      break;\n   case InfoType::AccountId:\n      if (buffer.totalSize() < sizeof(account->accountId)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->accountId),\n                         sizeof(account->accountId));\n      break;\n   case InfoType::EmailAddress:\n      if (buffer.totalSize() < sizeof(account->emailAddress)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->emailAddress),\n                         sizeof(account->emailAddress));\n      break;\n   case InfoType::Birthday:\n   {\n      StackObject<Birthday> birthday;\n      if (buffer.totalSize() < sizeof(*birthday)) {\n         return ResultInvalidSize;\n      }\n\n      birthday->year = account->birthYear;\n      birthday->month = account->birthMonth;\n      birthday->day = account->birthDay;\n      buffer.writeOutput(birthday, sizeof(*birthday));\n      break;\n   }\n   case InfoType::Country:\n      return ResultNotImplemented;\n   case InfoType::PrincipalId:\n      if (buffer.totalSize() < sizeof(account->principalId)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->principalId),\n                         sizeof(account->principalId));\n      break;\n   case InfoType::IsPasswordCacheEnabled:\n      if (buffer.totalSize() < sizeof(account->isPasswordCacheEnabled)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->isPasswordCacheEnabled),\n                         sizeof(account->isPasswordCacheEnabled));\n      break;\n   case InfoType::AccountPasswordCache:\n      if (buffer.totalSize() < sizeof(account->accountPasswordCache)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->accountPasswordCache),\n                         sizeof(account->accountPasswordCache));\n      break;\n   case InfoType::AccountInfo:\n   case InfoType::HostServerSettings:\n      return ResultNotImplemented;\n   case InfoType::Gender:\n      if (buffer.totalSize() < sizeof(account->gender)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->gender),\n                         sizeof(account->gender));\n      break;\n   case InfoType::LastAuthenticationResult:\n      if (buffer.totalSize() < sizeof(account->lastAuthenticationResult)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->lastAuthenticationResult),\n                         sizeof(account->lastAuthenticationResult));\n      break;\n   case InfoType::StickyAccountId:\n      if (buffer.totalSize() < sizeof(account->stickyAccountId)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->stickyAccountId),\n                         sizeof(account->stickyAccountId));\n      break;\n   case InfoType::ParentalControlSlot:\n      if (buffer.totalSize() < sizeof(account->parentalControlSlotNo)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->parentalControlSlotNo),\n                         sizeof(account->parentalControlSlotNo));\n      break;\n   case InfoType::SimpleAddressId:\n      if (buffer.totalSize() < sizeof(account->simpleAddressId)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->simpleAddressId),\n                         sizeof(account->simpleAddressId));\n      break;\n   case static_cast<InfoType>(25):\n      return ResultNotImplemented;\n   case InfoType::IsCommitted:\n      if (buffer.totalSize() < sizeof(account->isCommitted)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->isCommitted),\n                         sizeof(account->isCommitted));\n      break;\n   case InfoType::MiiName:\n      if (buffer.totalSize() < sizeof(account->miiName)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->miiName),\n                         sizeof(account->miiName));\n      break;\n   case InfoType::NfsPassword:\n      if (buffer.totalSize() < sizeof(account->nfsPassword)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->nfsPassword),\n                         sizeof(account->nfsPassword));\n      break;\n   case InfoType::HasEciVirtualAccount:\n   {\n      StackObject<uint8_t> hasEciVirtualAccount;\n      if (buffer.totalSize() < sizeof(*hasEciVirtualAccount)) {\n         return ResultInvalidSize;\n      }\n\n      *hasEciVirtualAccount = account->eciVirtualAccount[0] ? 1 : 0;\n      buffer.writeOutput(hasEciVirtualAccount, sizeof(*hasEciVirtualAccount));\n      break;\n   }\n   case InfoType::TimeZoneId:\n      if (buffer.totalSize() < sizeof(account->timeZoneId)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->timeZoneId),\n                         sizeof(account->timeZoneId));\n      break;\n   case InfoType::IsMiiUpdated:\n      if (buffer.totalSize() < sizeof(account->isMiiUpdated)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->isMiiUpdated),\n                         sizeof(account->isMiiUpdated));\n      break;\n   case InfoType::IsMailAddressValidated:\n      if (buffer.totalSize() < sizeof(account->isMailAddressValidated)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->isMailAddressValidated),\n                         sizeof(account->isMailAddressValidated));\n      break;\n   case InfoType::NextAccountId:\n      if (buffer.totalSize() < sizeof(account->nextAccountId)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->nextAccountId),\n                         sizeof(account->nextAccountId));\n      break;\n   case InfoType::Unk34:\n      return ResultNotImplemented;\n   case InfoType::IsServerAccountDeleted:\n      if (buffer.totalSize() < sizeof(account->isServerAccountDeleted)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->isServerAccountDeleted),\n                         sizeof(account->isServerAccountDeleted));\n      break;\n   case InfoType::MiiImageUrl:\n      if (buffer.totalSize() < sizeof(account->miiImageUrl)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->miiImageUrl),\n                         sizeof(account->miiImageUrl));\n      break;\n   case InfoType::StickyPrincipalId:\n      if (buffer.totalSize() < sizeof(account->stickyPrincipalId)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->stickyPrincipalId),\n                         sizeof(account->stickyPrincipalId));\n      break;\n   case InfoType::Unk40:\n   case InfoType::Unk41:\n      return ResultNotImplemented;\n   case InfoType::ServerAccountStatus:\n      if (buffer.totalSize() < sizeof(account->serverAccountStatus)) {\n         return ResultInvalidSize;\n      }\n\n      buffer.writeOutput(phys_addrof(account->serverAccountStatus),\n                         sizeof(account->serverAccountStatus));\n      break;\n   default:\n      return ResultInvalidValue;\n   }\n\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\ngetTransferableId(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<ActClientStandardService::GetTransferableId> { args };\n   auto slotNo = InvalidSlot;\n   auto unkArg2 = uint32_t { 0 };\n   command.ReadRequest(slotNo, unkArg2);\n\n   auto transferableIdBase = TransferrableId { 0 };\n   if (slotNo == SystemSlot) {\n      transferableIdBase = getAccountManager()->commonTransferableIdBase;\n   } else {\n      auto account = getAccountBySlotNo(slotNo);\n      if (!account) {\n         return ResultAccountNotFound;\n      }\n\n      transferableIdBase = account->transferableIdBase;\n   }\n\n   command.WriteResponse(calculateTransferableId(transferableIdBase,\n                                                 static_cast<uint16_t>(unkArg2)));\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\ngetMiiImage(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<ActClientStandardService::GetMiiImage> { args };\n   auto slotNo = InvalidSlot;\n   auto buffer = OutBuffer<void> { };\n   auto imageType = MiiImageType { 0 };\n   command.ReadRequest(slotNo, buffer, imageType);\n\n   auto account = getAccountBySlotNo(slotNo);\n   if (!account) {\n      return ResultAccountNotFound;\n   }\n\n   auto path = fmt::format(\n      \"/vol/storage_mlc01/usr/save/system/act/{:08x}/miiimg{:02}.dat\",\n      static_cast<uint32_t>(account->persistentId),\n      static_cast<int>(imageType));\n\n   // Check that the given buffer is large enough to read file into\n   auto stat = StackObject<FSAStat> { };\n   auto status = FSAGetInfoByQuery(getActFsaHandle(), path,\n                                   FSAQueryInfoType::Stat, stat);\n   if (status < FSAStatus::OK) {\n      if (status == FSAStatus::NotFound) {\n         return ResultFileNotFound;\n      } else {\n         return ResultFileIoError;\n      }\n   }\n\n   if (buffer.totalSize() < stat->size) {\n      return ResultInvalidSize;\n   }\n\n   // Read file split across buffer\n   auto fileHandle = FSAFileHandle { -1 };\n   status = FSAOpenFile(getActFsaHandle(), path, \"r\", &fileHandle);\n   if (status < FSAStatus::OK) {\n      if (status == FSAStatus::NotFound) {\n         return ResultFileNotFound;\n      } else {\n         return ResultFileIoError;\n      }\n   }\n\n   auto bytesRead = 0u;\n   if (bytesRead < stat->size && buffer.unalignedBeforeBufferSize > 0) {\n      auto readSize = std::min<uint32_t>(buffer.unalignedBeforeBufferSize,\n                                         stat->size - bytesRead);\n      status = FSAReadFile(getActFsaHandle(), buffer.unalignedBeforeBuffer,\n                           1, readSize, fileHandle, FSAReadFlag::None);\n      if (status < FSAStatus::OK || static_cast<uint32_t>(status) != readSize) {\n         FSACloseFile(getActFsaHandle(), fileHandle);\n         return ResultFileIoError;\n      }\n\n      bytesRead += readSize;\n   }\n\n   if (bytesRead < stat->size && buffer.alignedBufferSize > 0) {\n      auto readSize = std::min<uint32_t>(buffer.alignedBufferSize,\n                                         stat->size - bytesRead);\n      status = FSAReadFile(getActFsaHandle(), buffer.alignedBuffer,\n                           1, readSize, fileHandle, FSAReadFlag::None);\n      if (status < FSAStatus::OK || static_cast<uint32_t>(status) != readSize) {\n         FSACloseFile(getActFsaHandle(), fileHandle);\n         return ResultFileIoError;\n      }\n\n      bytesRead += readSize;\n   }\n\n   if (bytesRead < stat->size && buffer.unalignedAfterBufferSize > 0) {\n      auto readSize = std::min<uint32_t>(buffer.unalignedAfterBufferSize,\n                                         stat->size - bytesRead);\n      status = FSAReadFile(getActFsaHandle(), buffer.unalignedAfterBuffer,\n                           1, readSize, fileHandle, FSAReadFlag::None);\n      if (status < FSAStatus::OK || static_cast<uint32_t>(status) != readSize) {\n         FSACloseFile(getActFsaHandle(), fileHandle);\n         return ResultFileIoError;\n      }\n\n      bytesRead += readSize;\n   }\n\n   FSACloseFile(getActFsaHandle(), fileHandle);\n   command.WriteResponse(bytesRead);\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\ngetUuid(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<ActClientStandardService::GetUuid> { args };\n   auto slotNo = InvalidSlot;\n   auto buffer = OutBuffer<Uuid> { };\n   auto unkArg3 = int32_t { 0 };\n   command.ReadRequest(slotNo, buffer, unkArg3);\n\n   if (slotNo == SystemSlot) {\n      auto uuid = Uuid { };\n      uuid.fill('b');\n      uuid[0] = 'd';\n      uuid[1] = 'e';\n      uuid[2] = 'c';\n      uuid[3] = 'a';\n      uuid[4] = 'f';\n      buffer.writeOutput(uuid.data(), uuid.size());\n      return nn::ResultSuccess;\n   }\n\n   auto account = getAccountBySlotNo(slotNo);\n   if (!account) {\n      return ResultAccountNotFound;\n   }\n\n   buffer.writeOutput(phys_addrof(account->uuid), account->uuid.size());\n   return nn::ResultSuccess;\n}\n\nnn::Result\nActClientStandardService::commandHandler(uint32_t unk1,\n                                         CommandId command,\n                                         CommandHandlerArgs &args)\n{\n   switch (command) {\n   case GetCommonInfo::command:\n      return getCommonInfo(args);\n   case GetAccountInfo::command:\n      return getAccountInfo(args);\n   case GetTransferableId::command:\n      return getTransferableId(args);\n   case GetMiiImage::command:\n      return getMiiImage(args);\n   case GetUuid::command:\n      return getUuid(args);\n   default:\n      return nn::ipc::ResultInvalidMethodTag;\n   }\n}\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_clientstandardservice.h",
    "content": "#pragma once\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"nn/act/nn_act_clientstandardservice.h\"\n\nnamespace ios::fpd::internal\n{\n\nstruct ActClientStandardService : ::nn::act::services::ClientStandardService\n{\n   static nn::Result\n   commandHandler(uint32_t unk1,\n                  nn::ipc::CommandId command,\n                  nn::ipc::CommandHandlerArgs &args);\n};\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_server.cpp",
    "content": "#include \"ios_fpd_log.h\"\n#include \"ios_fpd_act_accountdata.h\"\n#include \"ios_fpd_act_accountloaderservice.h\"\n#include \"ios_fpd_act_accountmanagerservice.h\"\n#include \"ios_fpd_act_clientstandardservice.h\"\n#include \"ios_fpd_act_server.h\"\n#include \"ios_fpd_act_serverstandardservice.h\"\n\n#include \"ios/auxil/ios_auxil_usr_cfg_ipc.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n\nnamespace ios::fpd::internal\n{\n\nusing namespace kernel;\n\nconstexpr auto ActNumMessages = 100u;\nconstexpr auto ActThreadStackSize = 0x18000u;\nconstexpr auto ActThreadPriority = 50u;\n\nclass ActServer : public nn::ipc::Server\n{\npublic:\n   ActServer() :\n      nn::ipc::Server(true)\n   {\n   }\n\n   void intialiseServer() override\n   {\n      auto error = fs::FSAOpen();\n      if (error < Error::OK) {\n         internal::fpdLog->error(\"ActServer::intialiseServer: FSAOpen failed with error = {}\", error);\n      } else {\n         mFsaHandle = static_cast<Handle>(error);\n      }\n\n      error = auxil::UCOpen();\n      if (error < Error::OK) {\n         internal::fpdLog->error(\"ActServer::intialiseServer: UCOpen failed with error = {}\", error);\n      } else {\n         mUserConfigHandle = static_cast<Handle>(error);\n      }\n\n      initialiseAccounts();\n   }\n\n   void finaliseServer() override\n   {\n      if (mFsaHandle >= 0) {\n         fs::FSAClose(mFsaHandle);\n      }\n   }\n\n   Handle getFsaHandle()\n   {\n      return mFsaHandle;\n   }\n\n   Handle getUserConfigHandle()\n   {\n      return mUserConfigHandle;\n   }\n\nprivate:\n   be2_val<Handle> mFsaHandle = -1;\n   be2_val<Handle> mUserConfigHandle = -1;\n};\n\nstruct StaticActServerData\n{\n   be2_struct<ActServer> server;\n   be2_array<Message, ActNumMessages> messageBuffer;\n   be2_array<uint8_t, ActThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticActServerData> sActServerData = nullptr;\n\nError\nstartActServer()\n{\n   auto &server = sActServerData->server;\n   auto result = server.initialise(\"/dev/act\",\n                                   phys_addrof(sActServerData->messageBuffer),\n                                   static_cast<uint32_t>(sActServerData->messageBuffer.size()));\n   if (result.failed()) {\n      internal::fpdLog->error(\n         \"startActServer: Server initialisation failed for /dev/act, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   server.registerService<ActClientStandardService>();\n   server.registerService<ActServerStandardService>();\n   server.registerService<ActAccountManagerService>();\n   server.registerService<ActAccountLoaderService>();\n   // TODO: Register services 4, 5, 6\n\n   result = server.start(phys_addrof(sActServerData->threadStack) + sActServerData->threadStack.size(),\n                         static_cast<uint32_t>(sActServerData->threadStack.size()),\n                         ActThreadPriority);\n   if (result.failed()) {\n      internal::fpdLog->error(\n         \"startActServer: Server start failed for /dev/act, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   return Error::OK;\n}\n\nHandle\ngetActFsaHandle()\n{\n   return sActServerData->server.getFsaHandle();\n}\n\nHandle\ngetActUserConfigHandle()\n{\n   return sActServerData->server.getUserConfigHandle();\n}\n\nvoid\ninitialiseStaticActServerData()\n{\n   sActServerData = allocProcessStatic<StaticActServerData>();\n}\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_server.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_ipc.h\"\n\nnamespace ios::fpd::internal\n{\n\nError\nstartActServer();\n\nHandle\ngetActFsaHandle();\n\nHandle\ngetActUserConfigHandle();\n\nvoid\ninitialiseStaticActServerData();\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_serverstandardservice.cpp",
    "content": "#include \"ios_fpd_act_serverstandardservice.h\"\n\n#include \"ios/nn/ios_nn_ipc_server_command.h\"\n#include \"nn/act/nn_act_result.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n\nusing namespace nn::act;\n\nusing nn::ipc::CommandHandlerArgs;\nusing nn::ipc::CommandId;\nusing nn::ipc::OutBuffer;\nusing nn::ipc::ServerCommand;\n\nnamespace ios::fpd::internal\n{\n\nstatic nn::Result\nacquireNexServiceToken(CommandHandlerArgs &args)\n{\n   return ResultUcError;\n};\n\n\nstatic nn::Result\ncancel(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<ActServerStandardService::Cancel> { args };\n   return nn::ResultSuccess;\n}\n\nnn::Result\nActServerStandardService::commandHandler(uint32_t unk1,\n                                         CommandId command,\n                                         CommandHandlerArgs &args)\n{\n   switch (command) {\n   case Cancel::command:\n      return cancel(args);\n   case AcquireNexServiceToken::command:\n      return acquireNexServiceToken(args);\n   default:\n      return nn::ipc::ResultInvalidMethodTag;\n   }\n}\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_act_serverstandardservice.h",
    "content": "#pragma once\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"nn/act/nn_act_serverstandardservice.h\"\n\nnamespace ios::fpd::internal\n{\n\nstruct ActServerStandardService : ::nn::act::services::ServerStandardService\n{\n   static nn::Result\n   commandHandler(uint32_t unk1,\n                  nn::ipc::CommandId command,\n                  nn::ipc::CommandHandlerArgs &args);\n};\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fpd/ios_fpd_log.h",
    "content": "#pragma once\n#include <common/log.h>\n\nnamespace ios::fpd::internal\n{\n\nextern Logger fpdLog;\n\n} // namespace ios::fpd::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs.cpp",
    "content": "#include \"ios_fs.h\"\n#include \"ios_fs_log.h\"\n#include \"ios_fs_fsa_async_task.h\"\n#include \"ios_fs_fsa_thread.h\"\n#include \"ios_fs_service_thread.h\"\n\n#include \"decaf_log.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include <common/log.h>\n\nnamespace ios::fs\n{\n\nconstexpr auto LocalHeapSize = 0x56000u;\nconstexpr auto CrossHeapSize = 0x1D80000u;\n\nstatic phys_ptr<void>\nsLocalHeapBuffer = nullptr;\n\nnamespace internal\n{\n\nLogger fsLog = { };\n\nstatic void\ninitialiseStaticData()\n{\n   sLocalHeapBuffer = kernel::allocProcessLocalHeap(LocalHeapSize);\n}\n\n} // namespace internal\n\nError\nprocessEntryPoint(phys_ptr<void> context)\n{\n   // Initialise logger\n   internal::fsLog = decaf::makeLogger(\"IOS_FS\");\n\n   // Initialise static memory\n   internal::initialiseStaticData();\n   internal::initialiseStaticFsaAsyncTaskData();\n   internal::initialiseStaticFsaThreadData();\n   internal::initialiseStaticServiceThreadData();\n\n   // Initialise process heaps\n   auto error = kernel::IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to create local process heap, error = {}.\", error);\n      return error;\n   }\n\n   error = kernel::IOS_CreateCrossProcessHeap(CrossHeapSize);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to create cross process heap, error = {}.\", error);\n      return error;\n   }\n\n   // TODO: Get clock info from bsp\n\n   // TODO: Start dk thread\n\n   // TODO: Start odm thread\n\n   // Start FSA async task thread.\n   error = internal::startFsaAsyncTaskThread();\n   if (error < Error::OK) {\n      gLog->error(\"Failed to start FSA async task thread\");\n      return error;\n   }\n\n   // Start FSA thread.\n   error = internal::startFsaThread();\n   if (error < Error::OK) {\n      gLog->error(\"Failed to start FSA thread\");\n      return error;\n   }\n\n   // Start service thread.\n   error = internal::startServiceThread();\n   if (error < Error::OK) {\n      gLog->error(\"Failed to start Service thread\");\n      return error;\n   }\n\n   // TODO: Start ramdisk service thread.\n\n   // Suspend current thread.\n   error = kernel::IOS_GetCurrentThreadId();\n   if (error < Error::OK) {\n      gLog->error(\"Failed to get current thread id\");\n      return error;\n   }\n\n   auto threadId = static_cast<kernel::ThreadId>(error);\n   error = kernel::IOS_SuspendThread(threadId);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to suspend root fs thread\");\n      return error;\n   }\n\n   return Error::OK;\n}\n\n} // namespace ios\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace ios::fs\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::fs\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_enum.h",
    "content": "#ifndef IOS_FS_ENUM_H\n#define IOS_FS_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\n\nENUM_NAMESPACE_ENTER(fs)\n\nENUM_BEG(FSACommand, uint32_t)\n   ENUM_VALUE(Invalid,                 0x0)\n   ENUM_VALUE(Mount,                   0x1)\n   ENUM_VALUE(Unmount,                 0x2)\n   ENUM_VALUE(GetVolumeInfo,           0x3)\n   ENUM_VALUE(GetAttach,               0x4)\n   ENUM_VALUE(ChangeDir,               0x5)\n   ENUM_VALUE(GetCwd,                  0x6)\n   ENUM_VALUE(MakeDir,                 0x7)\n   ENUM_VALUE(Remove,                  0x8)\n   ENUM_VALUE(Rename,                  0x9)\n   ENUM_VALUE(OpenDir,                 0xA)\n   ENUM_VALUE(ReadDir,                 0xB)\n   ENUM_VALUE(RewindDir,               0xC)\n   ENUM_VALUE(CloseDir,                0xD)\n   ENUM_VALUE(OpenFile,                0xE)\n   ENUM_VALUE(ReadFile,                0xF)\n   ENUM_VALUE(WriteFile,               0x10)\n   ENUM_VALUE(GetPosFile,              0x11)\n   ENUM_VALUE(SetPosFile,              0x12)\n   ENUM_VALUE(IsEof,                   0x13)\n   ENUM_VALUE(StatFile,                0x14)\n   ENUM_VALUE(CloseFile,               0x15)\n   ENUM_VALUE(GetError,                0x16)\n   ENUM_VALUE(FlushFile,               0x17)\n   ENUM_VALUE(GetInfoByQuery,          0x18)\n   ENUM_VALUE(AppendFile,              0x19)\n   ENUM_VALUE(TruncateFile,            0x1A)\n   ENUM_VALUE(FlushVolume,             0x1B)\n   ENUM_VALUE(RollbackVolume,          0x1C)\n   ENUM_VALUE(MakeQuota,               0x1D)\n   ENUM_VALUE(FlushQuota,              0x1E)\n   ENUM_VALUE(RollbackQuota,           0x1F)\n   ENUM_VALUE(ChangeMode,              0x20)\n   ENUM_VALUE(OpenFileByStat,          0x21)\n   ENUM_VALUE(RegisterFlushQuota,      0x22)\n   ENUM_VALUE(FlushMultiQuota,         0x23)\n   ENUM_VALUE(GetFileBlockAddress,     0x25)\n   ENUM_VALUE(AddUserProcess,          0x65)\n   ENUM_VALUE(DelUserProcess,          0x66)\n   ENUM_VALUE(MountWithProcess,        0x67)\n   ENUM_VALUE(UnmountWithProcess,      0x68)\n   ENUM_VALUE(Format,                  0x69)\n   ENUM_VALUE(RawOpen,                 0x6A)\n   ENUM_VALUE(RawRead,                 0x6B)\n   ENUM_VALUE(RawWrite,                0x6C)\n   ENUM_VALUE(RawClose,                0x6D)\n   ENUM_VALUE(GetLastFailedVolume,     0x6E)\n   ENUM_VALUE(GetVolumeExistence,      0x6F)\n   ENUM_VALUE(ChangeOwner,             0x70)\n   ENUM_VALUE(CancelGetAttach,         0x71)\n   ENUM_VALUE(RemoveQuota,             0x72)\n   ENUM_VALUE(SetClientPriority,       0x73)\n   ENUM_VALUE(ApplyMemoryCache,        0x74)\n   ENUM_VALUE(MakeLink,                0x75)\n   ENUM_VALUE(XferParams,              0x76)\n   ENUM_VALUE(ExecDebugProc,           0x78)\n   ENUM_VALUE(DebugSetTitleId,         0x79)\n   ENUM_VALUE(DebugSetCapability,      0x7A)\n   ENUM_VALUE(SetProcessConfig,        0x82)\n   ENUM_VALUE(ConfigSetMemoryCache,    0x83)\n   ENUM_VALUE(ConfigUnsetMemoryCache,  0x84)\n   ENUM_VALUE(ConfigSetPrf2CharCode,   0x85)\n   ENUM_VALUE(GetProcResourceUsage,    0x8C)\n   ENUM_VALUE(GetAllResourceUsage,     0x8D)\n   ENUM_VALUE(SendProfileCmd,          0x8E)\nENUM_END(FSACommand)\n\nENUM_BEG(FSAMediaState, uint32_t)\n   ENUM_VALUE(Ready,                   0)\n   ENUM_VALUE(NoMedia,                 1)\n   ENUM_VALUE(InvalidMedia,            2)\n   ENUM_VALUE(DirtyMedia,              3)\n   ENUM_VALUE(MediaError,              4)\nENUM_END(FSAMediaState)\n\nENUM_BEG(FSAMountPriority, uint32_t)\n   ENUM_VALUE(Base,                    1)\n   ENUM_VALUE(RamDiskCache,            4)\n   ENUM_VALUE(TitleUpdate,             9)\n   ENUM_VALUE(UnmountAll,              0x80000000)\nENUM_END(FSAMountPriority)\n\nENUM_BEG(FSAQueryInfoType, uint32_t)\n   ENUM_VALUE(FreeSpaceSize,           0)\n   ENUM_VALUE(DirSize,                 1)\n   ENUM_VALUE(EntryNum,                2)\n   ENUM_VALUE(FileSystemInfo,          3)\n   ENUM_VALUE(DeviceInfo,              4)\n   ENUM_VALUE(Stat,                    5)\n   ENUM_VALUE(BadBlockInfo,            6)\n   ENUM_VALUE(JournalFreeSpaceSize,    7)\n   ENUM_VALUE(FragmentBlockInfo,       8)\nENUM_END(FSAQueryInfoType)\n\nFLAGS_BEG(FSAReadFlag, uint32_t)\n   FLAGS_VALUE(None,                   0x0)\n   FLAGS_VALUE(ReadWithPos,            1 << 0)\nFLAGS_END(FSAReadFlag)\n\nENUM_BEG(FSAStatFlags, uint32_t)\n   ENUM_VALUE(Quota,                   0x40000000)\n   ENUM_VALUE(Directory,               0x80000000)\nENUM_END(FSAStatFlags)\n\nENUM_BEG(FSAStatus, int32_t)\n   ENUM_VALUE(OK,                      0)\n   ENUM_VALUE(NotInit,                 -0x30001)\n   ENUM_VALUE(Busy,                    -0x30002)\n   ENUM_VALUE(Cancelled,               -0x30003)\n   ENUM_VALUE(EndOfDir,                -0x30004)\n   ENUM_VALUE(EndOfFile,               -0x30005)\n   ENUM_VALUE(MaxMountpoints,          -0x30010)\n   ENUM_VALUE(MaxVolumes,              -0x30011)\n   ENUM_VALUE(MaxClients,              -0x30012)\n   ENUM_VALUE(MaxFiles,                -0x30013)\n   ENUM_VALUE(MaxDirs,                 -0x30014)\n   ENUM_VALUE(AlreadyOpen,             -0x30015)\n   ENUM_VALUE(AlreadyExists,           -0x30016)\n   ENUM_VALUE(NotFound,                -0x30017)\n   ENUM_VALUE(NotEmpty,                -0x30018)\n   ENUM_VALUE(AccessError,             -0x30019)\n   ENUM_VALUE(PermissionError,         -0x3001a)\n   ENUM_VALUE(DataCorrupted,           -0x3001b)\n   ENUM_VALUE(StorageFull,             -0x3001c)\n   ENUM_VALUE(JournalFull,             -0x3001d)\n   ENUM_VALUE(LinkEntry,               -0x3001e)\n   ENUM_VALUE(UnavailableCmd,          -0x3001f)\n   ENUM_VALUE(UnsupportedCmd,          -0x30020)\n   ENUM_VALUE(InvalidParam,            -0x30021)\n   ENUM_VALUE(InvalidPath,             -0x30022)\n   ENUM_VALUE(InvalidBuffer,           -0x30023)\n   ENUM_VALUE(InvalidAlignment,        -0x30024)\n   ENUM_VALUE(InvalidClientHandle,     -0x30025)\n   ENUM_VALUE(InvalidFileHandle,       -0x30026)\n   ENUM_VALUE(InvalidDirHandle,        -0x30027)\n   ENUM_VALUE(NotFile,                 -0x30028)\n   ENUM_VALUE(NotDir,                  -0x30029)\n   ENUM_VALUE(FileTooBig,              -0x3002a)\n   ENUM_VALUE(OutOfRange,              -0x3002b)\n   ENUM_VALUE(OutOfResources,          -0x3002c)\n   ENUM_VALUE(MediaNotReady,           -0x30040)\n   ENUM_VALUE(MediaError,              -0x30041)\n   ENUM_VALUE(WriteProtected,          -0x30042)\n   ENUM_VALUE(InvalidMedia,            -0x30043)\nENUM_END(FSAStatus)\n\nFLAGS_BEG(FSAWriteFlag, uint32_t)\n   FLAGS_VALUE(None,                   0x0)\n   FLAGS_VALUE(WriteWithPos,           0x1)\nFLAGS_END(FSAWriteFlag)\n\nENUM_BEG(FSResourcePermissions, uint32_t)\n   ENUM_VALUE(SdCardRead,              0x90000)\n   ENUM_VALUE(SdCardWrite,             0xA0000)\nENUM_END(FSResourcePermissions)\n\nENUM_NAMESPACE_EXIT(fs)\n\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_FS_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa.h",
    "content": "#pragma once\n#include \"ios_fs_enum.h\"\n#include \"ios_fs_fsa_request.h\"\n#include \"ios_fs_fsa_response.h\"\n#include \"ios_fs_fsa_types.h\"\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_async_task.cpp",
    "content": "#include \"ios_fs_fsa_device.h\"\n#include \"ios_fs_mutex.h\"\n\n#include \"ios/kernel/ios_kernel_hardware.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_handlemanager.h\"\n#include \"ios/ios_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <mutex>\n#include <vector>\n\nusing namespace ios::kernel;\nusing ios::kernel::internal::setInterruptAhbAll;\n\nnamespace ios::fs::internal\n{\n\nstruct CompletedTask\n{\n   phys_ptr<ResourceRequest> resourceRequest;\n   FSAStatus status;\n};\n\nstruct StaticFsaAsyncData\n{\n   be2_val<MessageQueueId> messageQueue;\n   be2_array<Message, 64> messageBuffer;\n\n   be2_val<ThreadId> thread;\n   be2_array<uint8_t, 0x1000> threadStack;\n};\n\nstatic std::mutex sCompletedTasksMutex;\nstatic std::vector<CompletedTask> sCompletedTasks;\nstatic phys_ptr<StaticFsaAsyncData> sFsaAsyncData = nullptr;\n\nstatic void\nfsaTaskCompleteHandler()\n{\n   auto completedTasks = std::vector<CompletedTask> { };\n\n   sCompletedTasksMutex.lock();\n   completedTasks.swap(sCompletedTasks);\n   sCompletedTasksMutex.unlock();\n\n   for (auto &task : completedTasks) {\n      IOS_ResourceReply(task.resourceRequest,\n                        static_cast<Error>(task.status));\n   }\n\n   IOS_ClearAndEnable(DeviceId::Sata);\n}\n\nvoid\nfsaAsyncTaskComplete(phys_ptr<ResourceRequest> resourceRequest,\n                     FSAStatus result)\n{\n   sCompletedTasksMutex.lock();\n   sCompletedTasks.push_back({ resourceRequest, result });\n   sCompletedTasksMutex.unlock();\n\n   setInterruptAhbAll(AHBALL::get(0).Sata(true));\n}\n\nstatic Error\nfsaAsyncTaskThread(phys_ptr<void> /*unused*/)\n{\n   StackObject<Message> message;\n\n   auto error = IOS_HandleEvent(DeviceId::Sata,\n                                sFsaAsyncData->messageQueue,\n                                Message { 1 });\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = IOS_ClearAndEnable(DeviceId::Sata);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   while (true) {\n      error = IOS_ReceiveMessage(sFsaAsyncData->messageQueue,\n                                 message,\n                                 MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      switch (*message) {\n      case 1:\n         fsaTaskCompleteHandler();\n         break;\n      }\n   }\n}\n\nError\nstartFsaAsyncTaskThread()\n{\n   auto error = IOS_CreateMessageQueue(phys_addrof(sFsaAsyncData->messageBuffer),\n                                       static_cast<uint32_t>(sFsaAsyncData->messageBuffer.size()));\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sFsaAsyncData->messageQueue = static_cast<MessageQueueId>(error);\n\n   error = IOS_CreateThread(fsaAsyncTaskThread,\n                            nullptr,\n                            phys_addrof(sFsaAsyncData->threadStack) + sFsaAsyncData->threadStack.size(),\n                            static_cast<uint32_t>(sFsaAsyncData->threadStack.size()),\n                            85,\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sFsaAsyncData->thread = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sFsaAsyncData->thread, \"FsaAsyncTaskThread\");\n\n   error = IOS_StartThread(sFsaAsyncData->thread);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   IOS_YieldCurrentThread();\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticFsaAsyncTaskData()\n{\n   sFsaAsyncData = allocProcessStatic<StaticFsaAsyncData>();\n}\n\n} // namespace ios::fs\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_async_task.h",
    "content": "#pragma once\n#include \"ios_fs_enum.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/ios_enum.h\"\n\nnamespace ios::fs::internal\n{\n\nError\nstartFsaAsyncTaskThread();\n\nvoid\ninitialiseStaticFsaAsyncTaskData();\n\nvoid\nfsaAsyncTaskComplete(phys_ptr<ios::kernel::ResourceRequest> resourceRequest,\n                     FSAStatus result);\n\n} // namespace ios::fs::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_device.cpp",
    "content": "#include \"ios_fs_fsa_device.h\"\n#include \"ios_fs_log.h\"\n\n#include \"ios/ios.h\"\n#include \"vfs/vfs_error.h\"\n#include \"vfs/vfs_filehandle.h\"\n#include \"vfs/vfs_virtual_device.h\"\n\n#include <common/strutils.h>\n#include <libcpu/cpu_formatters.h>\n\nnamespace ios::fs::internal\n{\n\nFSADevice::FSADevice() :\n   mFS(ios::getFileSystem())\n{\n}\n\nFSAStatus\nFSADevice::translateError(vfs::Error error) const\n{\n   switch (error) {\n   case vfs::Error::Success:\n      return FSAStatus::OK;\n   case vfs::Error::EndOfDirectory:\n      return FSAStatus::EndOfDir;\n   case vfs::Error::EndOfFile:\n      return FSAStatus::EndOfFile;\n   case vfs::Error::AlreadyExists:\n      return FSAStatus::AlreadyExists;\n   case vfs::Error::InvalidPath:\n      return FSAStatus::InvalidPath;\n   case vfs::Error::InvalidSeekDirection:\n   case vfs::Error::InvalidSeekPosition:\n   case vfs::Error::InvalidTruncatePosition:\n      return FSAStatus::InvalidParam;\n   case vfs::Error::NotDirectory:\n      return FSAStatus::NotDir;\n   case vfs::Error::NotFile:\n      return FSAStatus::NotFile;\n   case vfs::Error::NotFound:\n      return FSAStatus::NotFound;\n   case vfs::Error::NotOpen:\n      return FSAStatus::InvalidFileHandle;\n   case vfs::Error::OperationNotSupported:\n      return FSAStatus::UnsupportedCmd;\n   case vfs::Error::ExecutePermission:\n   case vfs::Error::ReadOnly:\n   case vfs::Error::Permission:\n      return FSAStatus::PermissionError;\n   default:\n      return FSAStatus::MediaError;\n   }\n}\n\n\nvfs::Path\nFSADevice::translatePath(phys_ptr<const char> path) const\n{\n   if (path[0] == '/') {\n      return { path.get() };\n   } else {\n      return mWorkingPath / path.get();\n   }\n}\n\n\nvfs::FileHandle::Mode\nFSADevice::translateMode(phys_ptr<const char> mode) const\n{\n   auto result = 0u;\n\n   if (std::strchr(mode.get(), 'r')) {\n      result |= vfs::FileHandle::Read;\n   }\n\n   if (std::strchr(mode.get(), 'w')) {\n      result |= vfs::FileHandle::Write;\n   }\n\n   if (std::strchr(mode.get(), 'a')) {\n      result |= vfs::FileHandle::Append;\n   }\n\n   if (std::strchr(mode.get(), '+')) {\n      result |= vfs::FileHandle::Update;\n   }\n\n   return static_cast<vfs::FileHandle::Mode>(result);\n}\n\n\nvoid\nFSADevice::translateStat(const vfs::Status &entry,\n                         phys_ptr<FSAStat> stat) const\n{\n   std::memset(stat.get(), 0, sizeof(FSAStat));\n\n   if (entry.flags == vfs::Status::IsDirectory) {\n      stat->flags |= FSAStatFlags::Directory;\n   }\n\n   if (entry.flags == vfs::Status::HasSize) {\n      stat->size = static_cast<uint32_t>(entry.size);\n   }\n\n   // TODO: Fill out this.\n   stat->permission = 0x660u;\n   stat->owner = 0u;\n   stat->group = 0u;\n   stat->entryId = 0u;\n   stat->created = 0;\n   stat->modified = 0;\n}\n\n\nint32_t\nFSADevice::mapHandle(std::unique_ptr<vfs::FileHandle> file)\n{\n   auto index = 0;\n\n   for (index = 0; index < mHandles.size(); ++index) {\n      if (mHandles[index].type == Handle::Unused) {\n         mHandles[index].type = Handle::File;\n         mHandles[index].file = std::move(file);\n         break;\n      }\n   }\n\n   if (index == mHandles.size()) {\n      mHandles.emplace_back(std::move(file));\n   }\n\n   return index + 1;\n}\n\n\nint32_t\nFSADevice::mapHandle(vfs::DirectoryIterator folder)\n{\n   auto index = 0;\n\n   for (index = 0; index < mHandles.size(); ++index) {\n      if (mHandles[index].type == Handle::Unused) {\n         mHandles[index].type = Handle::Directory;\n         mHandles[index].directory = std::move(folder);\n         break;\n      }\n   }\n\n   if (index == mHandles.size()) {\n      mHandles.emplace_back(std::move(folder));\n   }\n\n   return index + 1;\n}\n\n\nFSAStatus\nFSADevice::mapFileHandle(int32_t index,\n                         Handle *&handle)\n{\n   if (index <= 0 || index > mHandles.size()) {\n      return FSAStatus::InvalidFileHandle;\n   }\n\n   if (mHandles[index - 1].type != Handle::File) {\n      return FSAStatus::NotFile;\n   }\n\n   handle = &mHandles[index - 1];\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::mapFolderHandle(int32_t index,\n                           Handle *&handle)\n{\n   if (index <= 0 || index > mHandles.size()) {\n      return FSAStatus::InvalidDirHandle;\n   }\n\n   if (mHandles[index - 1].type != Handle::Directory) {\n      return FSAStatus::NotFile;\n   }\n\n   handle = &mHandles[index - 1];\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::removeHandle(int32_t index,\n                        Handle::Type type)\n{\n   if (index <= 0 || index > mHandles.size()) {\n      if (type == Handle::File) {\n         return FSAStatus::InvalidFileHandle;\n      } else {\n         return FSAStatus::InvalidDirHandle;\n      }\n   }\n\n   auto &handle = mHandles[index - 1];\n   if (handle.type != type) {\n      if (type == Handle::File) {\n         return FSAStatus::NotFile;\n      } else {\n         return FSAStatus::NotDir;\n      }\n   }\n\n   handle.type = Handle::Unused;\n   handle.file = {};\n   handle.directory = {};\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::appendFile(vfs::User user,\n                      phys_ptr<FSARequestAppendFile> request)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto status = mapFileHandle(request->handle, handle);\n   if (status < 0) {\n      fsLog->info(\"FSADevice::appendFile[{}] mapFileHandle failed with status {}\",\n                  request->handle, status);\n      return status;\n   }\n\n   fsLog->trace(\"FSADevice::appendFile[{}] count: {} size: {}\",\n                request->handle, request->count, request->size);\n\n   // Seek to (count*size - 1) past end of file then write 1 byte.\n   char emptyByte = 0;\n   auto seekResult =\n      handle->file->seek(vfs::FileHandle::SeekEnd,\n                         (request->count * request->size) - 1);\n   if (seekResult != vfs::Error::Success) {\n      fsLog->warn(\"FSADevice::appendFile[{}] unexpected seekResult {}\",\n                  request->handle, seekResult);\n      return translateError(seekResult);\n   }\n\n   auto writeResult = handle->file->write(&emptyByte, 1, 1);\n   if (!writeResult) {\n      fsLog->warn(\"FSADevice::appendFile[{}] unexpected writeResult {}\",\n                  request->handle, seekResult);\n      return translateError(writeResult.error());\n   }\n\n   return static_cast<FSAStatus>(request->count);\n}\n\n\nFSAStatus\nFSADevice::changeDir(vfs::User user,\n                     phys_ptr<FSARequestChangeDir> request)\n{\n   mWorkingPath = translatePath(phys_addrof(request->path));\n   fsLog->debug(\"FSADevice::changeDir path: {}, new working path is: {}\",\n                phys_addrof(request->path).get(), mWorkingPath.path());\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::changeMode(vfs::User user,\n                      phys_ptr<FSARequestChangeMode> request)\n{\n   auto path = translatePath(phys_addrof(request->path));\n   fsLog->debug(\"FSADevice::changeMode path: {} mode1: {} mode2: {}\",\n                path.path(), request->mode1, request->mode2);\n   // TODO: Call mFS->setPermissions\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::closeDir(vfs::User user,\n                    phys_ptr<FSARequestCloseDir> request)\n{\n   fsLog->trace(\"FSADevice::closeDir[{}]\", request->handle);\n   return removeHandle(request->handle, Handle::Directory);\n}\n\n\nFSAStatus\nFSADevice::closeFile(vfs::User user,\n                     phys_ptr<FSARequestCloseFile> request)\n{\n   fsLog->trace(\"FSADevice::closeFile[{}]\", request->handle);\n   return removeHandle(request->handle, Handle::File);\n}\n\n\nFSAStatus\nFSADevice::flushFile(vfs::User user,\n                     phys_ptr<FSARequestFlushFile> request)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto status = mapFileHandle(request->handle, handle);\n   if (status < 0) {\n      fsLog->info(\"FSADevice::fluseFile[{}] mapFileHandle failed with status {}\",\n                  request->handle, status);\n      return status;\n   }\n\n   fsLog->trace(\"FSADevice::flushFile[{}] success\", request->handle);\n   handle->file->flush();\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::flushQuota(vfs::User user,\n                      phys_ptr<FSARequestFlushQuota> request)\n{\n   auto path = translatePath(phys_addrof(request->path));\n   fsLog->trace(\"FSADevice::flushQuota path: {} success\", path.path());\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::getCwd(vfs::User user,\n                  phys_ptr<FSAResponseGetCwd> response)\n{\n   auto cwd = mWorkingPath.path();\n   if (cwd.empty() || cwd.back() != '/') {\n      cwd.push_back('/');\n   }\n   string_copy(phys_addrof(response->path).get(), cwd.c_str(),\n               response->path.size());\n   response->path[response->path.size() - 1] = char { 0 };\n   fsLog->trace(\"FSADevice::getCwd cwd: {}\", cwd);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::getInfoByQuery(vfs::User user,\n                          phys_ptr<FSARequestGetInfoByQuery> request,\n                          phys_ptr<FSAResponseGetInfoByQuery> response)\n{\n   auto path = translatePath(phys_addrof(request->path));\n\n   switch (request->type) {\n   case FSAQueryInfoType::FreeSpaceSize:\n   {\n      if (auto result = mFS->status(user, path); !result) {\n         fsLog->debug(\"FSADevice::getInfoByQuery cmd: FreeSpaceSize path: {} error: {}\",\n                      path.path(), result.error());\n         response->freeSpaceSize = 0ull;\n      } else {\n         fsLog->debug(\"FSADevice::getInfoByQuery cmd: FreeSpaceSize path: {}\",\n                      path.path());\n         response->freeSpaceSize = 4096ull * 1024 * 1024;\n      }\n      break;\n   }\n   case FSAQueryInfoType::Stat:\n   {\n      auto result = mFS->status(user, path);\n      if (!result) {\n         fsLog->debug(\"FSADevice::getInfoByQuery cmd: Stat path: {} error: {}\",\n                      path.path(), result.error());\n         return translateError(result.error());\n      }\n\n      fsLog->debug(\"FSADevice::getInfoByQuery cmd: Stat path: {}\", path.path());\n      translateStat(*result, phys_addrof(response->stat));\n      break;\n   }\n   default:\n      fsLog->warn(\"FSADevice::getInfoByQuery unsupported cmd: {} with path: {}\",\n                  request->type, path.path());\n      return FSAStatus::UnsupportedCmd;\n   }\n\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::getPosFile(vfs::User user,\n                      phys_ptr<FSARequestGetPosFile> request,\n                      phys_ptr<FSAResponseGetPosFile> response)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto status = mapFileHandle(request->handle, handle);\n   if (status < 0) {\n      fsLog->info(\"FSADevice::getPosFile[{}] mapFileHandle failed with status {}\",\n                  request->handle, status);\n      return status;\n   }\n\n   auto result = handle->file->tell();\n   if (!result) {\n      fsLog->warn(\"FSADevice::getPosFile[{}] failed with error {}\",\n                   request->handle, result.error());\n      return translateError(result.error());\n   }\n\n   response->pos = static_cast<uint32_t>(*result);\n   fsLog->trace(\"FSADevice::getPosFile[{}] pos: {}\",\n                request->handle, response->pos);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::isEof(vfs::User user,\n                 phys_ptr<FSARequestIsEof> request)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto status = mapFileHandle(request->handle, handle);\n   if (status < 0) {\n      fsLog->info(\"FSADevice::isEof[{}] mapFileHandle failed with status {}\",\n                  request->handle, status);\n      return status;\n   }\n\n   auto result = handle->file->eof();\n   if (!result) {\n      fsLog->warn(\"FSADevice::isEof[{}] eof failed with error {}\",\n                  request->handle, result.error());\n      return translateError(result.error());\n   }\n\n   if (*result) {\n      status = FSAStatus::EndOfFile;\n   } else {\n      status = FSAStatus::OK;\n   }\n\n   fsLog->trace(\"FSADevice::isEof[{}] eof: {}\",\n                request->handle, *result ? \"true\" : \"false\");\n   return status;\n}\n\n\nFSAStatus\nFSADevice::makeDir(vfs::User user,\n                   phys_ptr<FSARequestMakeDir> request)\n{\n   auto path = translatePath(phys_addrof(request->path));\n   auto error = mFS->makeFolder(user, path);\n   if (error != vfs::Error::Success) {\n      fsLog->debug(\"FSADevice::makeDir path: {} permission: {} failed with \"\n                   \"error {}\", path.path(), request->permission, error);\n      return translateError(error);\n   }\n\n   fsLog->debug(\"FSADevice::makeDir path: {} permission: {} success\",\n                path.path(), request->permission);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::makeQuota(vfs::User user,\n                     phys_ptr<FSARequestMakeQuota> request)\n{\n   auto path = translatePath(phys_addrof(request->path));\n   auto error = mFS->makeFolder(user, path);\n   if (error != vfs::Error::Success) {\n      fsLog->debug(\"FSADevice::makeQuota path: {} mode: {} size: {} \"\n                   \"failed with error {}\",\n                   path.path(), request->mode, request->size, error);\n      return translateError(error);\n   }\n\n   fsLog->debug(\"FSADevice::makeQuota path: {} mode: {} size: {} success\",\n                path.path(), request->mode, request->size);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::mount(vfs::User user,\n                 phys_ptr<FSARequestMount> request)\n{\n   auto sourcePath = translatePath(phys_addrof(request->path));\n   auto targetPath = translatePath(phys_addrof(request->target));\n   auto linkDevice = mFS->getLinkDevice(user, sourcePath);\n   if (!linkDevice) {\n      fsLog->debug(\"FSADevice::mount source: {} target: {} getLinkDevice \"\n                   \"failed with error {}\",\n                   sourcePath.path(), targetPath.path(), linkDevice.error());\n      return translateError(linkDevice.error());\n   }\n\n   auto error = mFS->mountDevice(user, targetPath,\n                                 std::static_pointer_cast<vfs::Device>(*linkDevice));\n   if (error != vfs::Error::Success) {\n      fsLog->debug(\"FSADevice::mount source: {} target: {} mountDevice \"\n                   \"failed with error {}\",\n                   sourcePath.path(), targetPath.path(), error);\n      return translateError(error);\n   }\n\n   fsLog->debug(\"FSADevice::mount source: {} target: {} success\",\n                sourcePath.path(), targetPath.path());\n   return FSAStatus::OK;\n}\n\nFSAStatus\nFSADevice::mountWithProcess(vfs::User user,\n                            phys_ptr<FSARequestMountWithProcess> request)\n{\n   auto sourcePath = translatePath(phys_addrof(request->path));\n   auto targetPath = translatePath(phys_addrof(request->target));\n   auto linkDevice = mFS->getLinkDevice(user, sourcePath);\n   if (!linkDevice) {\n      fsLog->debug(\"FSADevice::mountWithProcess source: {} target: {} \"\n                   \"priority: {} getLinkDevice failed with error {}\",\n                   sourcePath.path(), targetPath.path(), request->priority,\n                   linkDevice.error());\n      return translateError(linkDevice.error());\n   }\n\n   auto error =\n      mFS->mountOverlayDevice(\n         user,\n         static_cast<vfs::OverlayPriority>(request->priority),\n         targetPath,\n         std::static_pointer_cast<vfs::Device>(*linkDevice));\n   if (error != vfs::Error::Success) {\n      fsLog->debug(\"FSADevice::mountWithProcess source: {} target: {} \"\n                   \"priority: {} mountOverlayDevice failed with error {}\",\n                   sourcePath.path(), targetPath.path(), request->priority,\n                   linkDevice.error());\n      return translateError(error);\n   }\n\n   fsLog->debug(\"FSADevice::mountWithProcess source: {} target: {} \"\n                \"priority: {} success\",\n                sourcePath.path(), targetPath.path(), request->priority);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::openDir(vfs::User user,\n                   phys_ptr<FSARequestOpenDir> request,\n                   phys_ptr<FSAResponseOpenDir> response)\n{\n   auto path = translatePath(phys_addrof(request->path));\n   auto result = mFS->openDirectory(user, path);\n   if (!result) {\n      fsLog->debug(\"FSADevice::openDir path: {} failed with error {}\",\n                   path.path(), result.error());\n      return translateError(result.error());\n   }\n\n   response->handle = mapHandle(*result);\n   fsLog->debug(\"FSADevice::openDir[{}] path: {} handle: {}\",\n                response->handle, path.path(), response->handle);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::openFile(vfs::User user,\n                    phys_ptr<FSARequestOpenFile> request,\n                    phys_ptr<FSAResponseOpenFile> response)\n{\n   auto path = translatePath(phys_addrof(request->path));\n   auto mode = translateMode(phys_addrof(request->mode));\n   auto result = mFS->openFile(user, path, mode);\n   if (!result) {\n      fsLog->debug(\"FSADevice::openFile path: {} mode: {} failed with error {}\",\n                   path.path(), mode, result.error());\n      return translateError(result.error());\n   }\n\n   response->handle = mapHandle(std::move(*result));\n   fsLog->debug(\"FSADevice::openFile[{}] path: {} mode: {} handle: {}\",\n                response->handle, path.path(), mode, response->handle);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::readDir(vfs::User user,\n                   phys_ptr<FSARequestReadDir> request,\n                   phys_ptr<FSAResponseReadDir> response)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto status = mapFolderHandle(request->handle, handle);\n   if (status < 0) {\n      fsLog->info(\"FSADevice::readDir[{}] mapFolderHandle failed with status {}\",\n                  request->handle, status);\n      return status;\n   }\n\n   auto result = handle->directory.readEntry();\n   if (!result) {\n      fsLog->debug(\"FSADevice::readDir[{}] readEntry failed with error {}\",\n                   request->handle, result.error());\n      return translateError(result.error());\n   }\n\n   translateStat(*result, phys_addrof(response->entry.stat));\n   string_copy(phys_addrof(response->entry.name).get(),\n               response->entry.name.size(),\n               result->name.c_str(),\n               result->name.size());\n   fsLog->debug(\"FSADevice::readDir[{}] name: {}\",\n                request->handle, result->name);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::readFile(vfs::User user,\n                    phys_ptr<FSARequestReadFile> request,\n                    phys_ptr<uint8_t> buffer,\n                    uint32_t bufferLen)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto status = mapFileHandle(request->handle, handle);\n   if (status < 0) {\n      fsLog->info(\"FSADevice::readFile[{}] mapFileHandle failed with status {}\",\n                  request->handle, status);\n      return status;\n   }\n\n   if (request->readFlags & FSAReadFlag::ReadWithPos) {\n      handle->file->seek(vfs::FileHandle::SeekStart, request->pos);\n   }\n\n   auto result = handle->file->read(buffer.get(), request->size, request->count);\n   if (!result) {\n      if (request->readFlags & FSAReadFlag::ReadWithPos) {\n         fsLog->debug(\"FSADevice::readFile[{}] size: {} count: {} withPos: {} \"\n                      \"failed with error {}\",\n                      request->handle, request->size, request->count,\n                      request->pos, result.error());\n      } else {\n         fsLog->debug(\"FSADevice::readFile[{}] size: {} count: {} failed with \"\n                      \"error {}\",\n                      request->handle, request->size, request->count,\n                      result.error());\n      }\n      return translateError(result.error());\n   }\n\n   auto bytesRead = (*result) * request->size;\n   if (request->readFlags & FSAReadFlag::ReadWithPos) {\n      fsLog->trace(\"FSADevice::readFile[{}] size: {} count: {} withPos: {} \"\n                   \"read bytes: {}\",\n                   request->handle, request->size, request->count, request->pos,\n                   bytesRead);\n   } else {\n      fsLog->trace(\"FSADevice::readFile[{}] size: {} count: {} read bytes: {}\",\n                   request->handle, request->size, request->count, bytesRead);\n   }\n   return static_cast<FSAStatus>(bytesRead);\n}\n\n\nFSAStatus\nFSADevice::remove(vfs::User user,\n                  phys_ptr<FSARequestRemove> request)\n{\n   auto path = translatePath(phys_addrof(request->path));\n   auto error = mFS->remove(user, path);\n   if (error != vfs::Error::Success) {\n      fsLog->debug(\"FSADevice::remove path: {} failed with error {}\",\n                   path.path(), error);\n   } else {\n      fsLog->debug(\"FSADevice::remove path: {} success\", path.path());\n   }\n   return translateError(error);\n}\n\n\nFSAStatus\nFSADevice::rename(vfs::User user,\n                  phys_ptr<FSARequestRename> request)\n{\n   auto src = translatePath(phys_addrof(request->oldPath));\n   auto dst = translatePath(phys_addrof(request->newPath));\n   auto error = mFS->rename(user, src, dst);\n   if (error != vfs::Error::Success) {\n      fsLog->debug(\"FSADevice::rename source: {} target: {} failed with error {}\",\n                   src.path(), dst.path(), error);\n   } else {\n      fsLog->debug(\"FSADevice::rename source: {} target: {} success\",\n                   src.path(), dst.path());\n   }\n   return translateError(error);\n}\n\n\nFSAStatus\nFSADevice::rewindDir(vfs::User user,\n                     phys_ptr<FSARequestRewindDir> request)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto status = mapFolderHandle(request->handle, handle);\n   if (status < 0) {\n      fsLog->info(\"FSADevice::rewindDir[{}] mapFolderHandle failed with status {}\",\n                  request->handle, status);\n      return status;\n   }\n\n   auto error = handle->directory.rewind();\n   if (error != vfs::Error::Success) {\n      fsLog->debug(\"FSADevice::rewindDir[{}] failed with error {}\",\n                   error, request->handle);\n   } else {\n      fsLog->debug(\"FSADevice::rewindDir[{}] success\", request->handle);\n   }\n\n   return translateError(error);\n}\n\n\nFSAStatus\nFSADevice::setPosFile(vfs::User user,\n                      phys_ptr<FSARequestSetPosFile> request)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto status = mapFileHandle(request->handle, handle);\n   if (status < 0) {\n      fsLog->info(\"FSADevice::setPosFile[{}] mapFileHandle failed with error {}\",\n                  request->handle, status);\n      return status;\n   }\n\n   handle->file->seek(vfs::FileHandle::SeekStart, request->pos);\n   fsLog->trace(\"FSADevice::setPosFile[{}] pos: {}\",\n                request->handle, request->pos);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::statFile(vfs::User user,\n                    phys_ptr<FSARequestStatFile> request,\n                    phys_ptr<FSAResponseStatFile> response)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto status = mapFileHandle(request->handle, handle);\n   if (status < 0) {\n      fsLog->info(\"FSADevice::statFile[{}] mapFileHandle failed with status {}\",\n                  request->handle, status);\n      return status;\n   }\n\n   std::memset(phys_addrof(response->stat).get(), 0, sizeof(response->stat));\n   response->stat.flags = static_cast<FSAStatFlags>(0);\n   response->stat.permission = 0x660u;\n   response->stat.owner = 0u;\n   response->stat.group = 0u;\n   response->stat.entryId = 0u;\n   response->stat.created = 0;\n   response->stat.modified = 0;\n\n   auto result = handle->file->size();\n   if (result) {\n      response->stat.size = static_cast<uint32_t>(*result);\n   }\n\n   fsLog->trace(\"FSADevice::statFile[{}] size: {}\",\n                request->handle, response->stat.size);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::truncateFile(vfs::User user,\n                        phys_ptr<FSARequestTruncateFile> request)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto status = mapFileHandle(request->handle, handle);\n   if (status < 0) {\n      fsLog->info(\"FSADevice::truncateFile[{}] mapFileHandle failed with status {}\",\n                  request->handle, status);\n      return status;\n   }\n\n   auto result = handle->file->truncate();\n   if (!result) {\n      fsLog->info(\"FSADevice::truncateFile[{}] truncate failed with error {}\",\n                  request->handle, result.error());\n      return translateError(result.error());\n   }\n\n   fsLog->trace(\"FSADevice::truncateFile[{}] success\", request->handle);\n   return FSAStatus::OK;\n}\n\n\nFSAStatus\nFSADevice::unmount(vfs::User user,\n                   phys_ptr<FSARequestUnmount> request)\n{\n   auto path = translatePath(phys_addrof(request->path));\n   auto error = mFS->unmountDevice(user, path);\n   if (error != vfs::Error::Success) {\n      fsLog->debug(\"FSADevice::unmount path: {} failed with error {}\",\n                   path.path(), error);\n   } else {\n      fsLog->debug(\"FSADevice::unmount path: {} success\", path.path());\n   }\n   return translateError(error);\n}\n\n\nFSAStatus\nFSADevice::unmountWithProcess(vfs::User user,\n                              phys_ptr<FSARequestUnmountWithProcess> request)\n{\n   auto path = translatePath(phys_addrof(request->path));\n\n   if (request->priority == FSAMountPriority::UnmountAll) {\n      // unmountDevice will unmount the base overlay device, thus unmounting\n      // all overlay devices at path.\n      auto error = mFS->unmountDevice(user, path);\n      if (error != vfs::Error::Success) {\n         fsLog->debug(\"FSADevice::unmountWithProcess path: {} priority: {} \"\n                      \"failed with error {}\",\n                      path.path(), request->priority, error);\n      } else {\n         fsLog->debug(\"FSADevice::unmountWithProcess path: {} priority: {} \",\n                      \"success\", path.path(), request->priority);\n      }\n      return translateError(error);\n   } else {\n      fsLog->warn(\"FSADevice::unmountWithProcess path: {} priority: {} \"\n                   \"unsupported\",\n                   path.path(), request->priority);\n      // TODO: unmount overlay device with priority request->priority\n      return FSAStatus::UnsupportedCmd;\n   }\n}\n\n\nFSAStatus\nFSADevice::writeFile(vfs::User user,\n                     phys_ptr<FSARequestWriteFile> request,\n                     phys_ptr<const uint8_t> buffer,\n                     uint32_t bufferLen)\n{\n   auto handle = static_cast<Handle *>(nullptr);\n   auto error = mapFileHandle(request->handle, handle);\n   if (error < 0) {\n      fsLog->info(\"FSADevice::writeFile[{}] mapFileHandle failed with error {}\",\n                  request->handle, error);\n      return error;\n   }\n\n   if (request->writeFlags & FSAWriteFlag::WriteWithPos) {\n      handle->file->seek(vfs::FileHandle::SeekStart, request->pos);\n   }\n\n   auto result = handle->file->write(buffer.get(), request->size, request->count);\n   if (!result) {\n      if (request->writeFlags & FSAWriteFlag::WriteWithPos) {\n         fsLog->debug(\"FSADevice::writeFile[{}] size: {} count: {} withPos: {} \"\n                      \"failed with error {}\",\n                      request->handle, request->size, request->count,\n                      request->pos, result.error());\n      } else {\n         fsLog->debug(\"FSADevice::writeFile[{}] size: {} count: {} failed with \"\n                      \"error {}\",\n                      request->handle, request->size, request->count,\n                      result.error());\n      }\n      return translateError(result.error());\n   }\n\n   auto bytesWritten = (*result) * request->size;\n   if (request->writeFlags & FSAWriteFlag::WriteWithPos) {\n      fsLog->trace(\"FSADevice::writeFile[{}] size: {} count: {} withPos: {} \"\n                   \"write bytes: {}\",\n                   request->handle, request->size, request->count, request->pos,\n                   bytesWritten);\n   } else {\n      fsLog->trace(\"FSADevice::writeFile[{}] size: {} count: {} write bytes: {}\",\n                   request->handle, request->size, request->count, bytesWritten);\n   }\n   return static_cast<FSAStatus>(bytesWritten);\n}\n\n} // namespace ios::fs::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_device.h",
    "content": "#pragma once\n#include \"ios_fs_enum.h\"\n#include \"ios_fs_fsa_request.h\"\n#include \"ios_fs_fsa_response.h\"\n#include \"ios_fs_fsa_types.h\"\n\n#include \"ios/ios_device.h\"\n#include \"vfs/vfs_device.h\"\n#include \"vfs/vfs_permissions.h\"\n\n#include <common/structsize.h>\n#include <vector>\n#include <libcpu/be2_struct.h>\n#include <memory>\n\nnamespace ios::fs::internal\n{\n\n/**\n * \\ingroup ios_fs\n * @{\n */\n\nclass FSADevice\n{\n   struct Handle\n   {\n      enum Type\n      {\n         Unused,\n         File,\n         Directory\n      };\n\n      Handle() :\n         type(Unused)\n      {\n      }\n\n      Handle(std::unique_ptr<vfs::FileHandle> file) :\n         type(File),\n         file(std::move(file))\n      {\n      }\n\n      Handle(vfs::DirectoryIterator itr) :\n         type(Directory),\n         directory(std::move(itr))\n      {\n      }\n\n      Type type;\n      std::unique_ptr<vfs::FileHandle> file;\n      vfs::DirectoryIterator directory;\n   };\n\npublic:\n   FSADevice();\n\n   FSAStatus appendFile(vfs::User user, phys_ptr<FSARequestAppendFile> request);\n   FSAStatus changeDir(vfs::User user, phys_ptr<FSARequestChangeDir> request);\n   FSAStatus changeMode(vfs::User user, phys_ptr<FSARequestChangeMode> request);\n   FSAStatus closeDir(vfs::User user, phys_ptr<FSARequestCloseDir> request);\n   FSAStatus closeFile(vfs::User user, phys_ptr<FSARequestCloseFile> request);\n   FSAStatus flushFile(vfs::User user, phys_ptr<FSARequestFlushFile> request);\n   FSAStatus flushQuota(vfs::User user, phys_ptr<FSARequestFlushQuota> request);\n   FSAStatus getCwd(vfs::User user, phys_ptr<FSAResponseGetCwd> response);\n   FSAStatus getInfoByQuery(vfs::User user, phys_ptr<FSARequestGetInfoByQuery> request, phys_ptr<FSAResponseGetInfoByQuery> response);\n   FSAStatus getPosFile(vfs::User user, phys_ptr<FSARequestGetPosFile> request, phys_ptr<FSAResponseGetPosFile> response);\n   FSAStatus isEof(vfs::User user, phys_ptr<FSARequestIsEof> request);\n   FSAStatus makeDir(vfs::User user, phys_ptr<FSARequestMakeDir> request);\n   FSAStatus makeQuota(vfs::User user, phys_ptr<FSARequestMakeQuota> request);\n   FSAStatus mount(vfs::User user, phys_ptr<FSARequestMount> request);\n   FSAStatus mountWithProcess(vfs::User user, phys_ptr<FSARequestMountWithProcess> request);\n   FSAStatus openDir(vfs::User user, phys_ptr<FSARequestOpenDir> request, phys_ptr<FSAResponseOpenDir> response);\n   FSAStatus openFile(vfs::User user, phys_ptr<FSARequestOpenFile> request, phys_ptr<FSAResponseOpenFile> response);\n   FSAStatus readDir(vfs::User user, phys_ptr<FSARequestReadDir> request, phys_ptr<FSAResponseReadDir> response);\n   FSAStatus readFile(vfs::User user, phys_ptr<FSARequestReadFile> request, phys_ptr<uint8_t> buffer, uint32_t bufferLen);\n   FSAStatus remove(vfs::User user, phys_ptr<FSARequestRemove> request);\n   FSAStatus rename(vfs::User user, phys_ptr<FSARequestRename> request);\n   FSAStatus rewindDir(vfs::User user, phys_ptr<FSARequestRewindDir> request);\n   FSAStatus setPosFile(vfs::User user, phys_ptr<FSARequestSetPosFile> request);\n   FSAStatus statFile(vfs::User user, phys_ptr<FSARequestStatFile> request, phys_ptr<FSAResponseStatFile> response);\n   FSAStatus truncateFile(vfs::User user, phys_ptr<FSARequestTruncateFile> request);\n   FSAStatus unmount(vfs::User user, phys_ptr<FSARequestUnmount> request);\n   FSAStatus unmountWithProcess(vfs::User user, phys_ptr<FSARequestUnmountWithProcess> request);\n   FSAStatus writeFile(vfs::User user, phys_ptr<FSARequestWriteFile> request, phys_ptr<const uint8_t> buffer, uint32_t bufferLen);\n\nprivate:\n   FSAStatus\n   translateError(vfs::Error error) const;\n\n   vfs::Path\n   translatePath(phys_ptr<const char> path) const;\n\n   vfs::FileHandle::Mode\n   translateMode(phys_ptr<const char> mode) const;\n\n   void\n   translateStat(const vfs::Status &entry,\n                 phys_ptr<FSAStat> stat) const;\n\n   int32_t\n   mapHandle(std::unique_ptr<vfs::FileHandle> file);\n\n   int32_t\n   mapHandle(vfs::DirectoryIterator folder);\n\n   FSAStatus\n   mapFileHandle(int32_t handle,\n                 Handle *&file);\n\n   FSAStatus\n   mapFolderHandle(int32_t handle,\n                   Handle *&folder);\n\n   FSAStatus\n   removeHandle(int32_t index,\n                Handle::Type type);\n\nprivate:\n   std::shared_ptr<vfs::Device> mFS;\n   vfs::Path mWorkingPath = \"/\";\n   std::vector<Handle> mHandles;\n};\n\n/** @} */\n\n} // namespace ios::fs::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_ipc.cpp",
    "content": "#include \"ios_fs_fsa_ipc.h\"\n#include \"ios_fs_fsa_request.h\"\n#include \"ios_fs_fsa_response.h\"\n#include \"ios_fs_fsa_types.h\"\n\n#include \"ios/ios_stackobject.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n\n#include <common/strutils.h>\n\nnamespace ios::fs\n{\n\nusing namespace kernel;\n\n#pragma pack(push, 1)\n\nstruct FSAIpcData\n{\n   be2_struct<FSARequest> request;\n   be2_struct<FSAResponse> response;\n   be2_array<IoctlVec, 4> vecs;\n   be2_val<FSACommand> command;\n   be2_val<ResourceHandleId> resourceHandle;\n   UNKNOWN(0x828 - 0x7EB);\n};\nCHECK_SIZE(FSAIpcData, 0x828);\n\n#pragma pack(pop)\n\nstatic FSAStatus\nallocFsaIpcData(phys_ptr<FSAIpcData> *outIpcData)\n{\n   auto buffer = IOS_HeapAlloc(CrossProcessHeapId,\n                               sizeof(FSAIpcData));\n\n   if (!buffer) {\n      return FSAStatus::OutOfResources;\n   }\n\n   std::memset(buffer.get(), 0, sizeof(FSAIpcData));\n   *outIpcData = phys_cast<FSAIpcData *>(buffer);\n   return FSAStatus::OK;\n}\n\nstatic void\nfreeFsaIpcData(phys_ptr<FSAIpcData> ipcData)\n{\n   IOS_HeapFree(CrossProcessHeapId, ipcData);\n}\n\nError\nFSAOpen()\n{\n   return IOS_Open(\"/dev/fsa\", OpenMode::None);\n}\n\nError\nFSAClose(FSAHandle handle)\n{\n   return IOS_Close(handle);\n}\n\nFSAStatus\nFSAOpenDir(FSAHandle handle,\n           std::string_view name,\n           FSADirHandle *outHandle)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   if (name.size() == 0) {\n      return FSAStatus::InvalidPath;\n   }\n\n   if (!outHandle) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::OpenDir;\n   ipcData->resourceHandle = handle;\n\n   auto request = phys_addrof(ipcData->request);\n   string_copy(phys_addrof(request->openDir.path).get(),\n               name.data(),\n               request->openDir.path.size());\n\n   auto error = IOS_Ioctl(ipcData->resourceHandle,\n                          ipcData->command,\n                          phys_addrof(ipcData->request),\n                          sizeof(FSARequest),\n                          phys_addrof(ipcData->response),\n                          sizeof(FSAResponse));\n\n   auto response = phys_addrof(ipcData->response);\n   *outHandle = response->openDir.handle;\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSACloseDir(FSAHandle handle,\n            FSADirHandle dirHandle)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::CloseDir;\n   ipcData->resourceHandle = handle;\n\n   auto request = phys_addrof(ipcData->request);\n   request->closeDir.handle = dirHandle;\n\n   auto error = IOS_Ioctl(ipcData->resourceHandle,\n                          ipcData->command,\n                          phys_addrof(ipcData->request),\n                          sizeof(FSARequest),\n                          phys_addrof(ipcData->response),\n                          sizeof(FSAResponse));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSAOpenFile(FSAHandle handle,\n            std::string_view name,\n            std::string_view mode,\n            FSAFileHandle *outHandle)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   if (name.size() == 0) {\n      return FSAStatus::InvalidPath;\n   }\n\n   if (mode.size() == 0) {\n      return FSAStatus::InvalidParam;\n   }\n\n   if (!outHandle) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::OpenFile;\n   ipcData->resourceHandle = handle;\n\n   auto request = phys_addrof(ipcData->request);\n   string_copy(phys_addrof(request->openFile.path).get(),\n               name.data(),\n               request->openFile.path.size());\n\n   string_copy(phys_addrof(request->openFile.mode).get(),\n               mode.data(),\n               request->openFile.mode.size());\n\n   request->openFile.unk0x290 = 0x60000u;\n\n   auto error = IOS_Ioctl(ipcData->resourceHandle,\n                          ipcData->command,\n                          phys_addrof(ipcData->request),\n                          sizeof(FSARequest),\n                          phys_addrof(ipcData->response),\n                          sizeof(FSAResponse));\n\n   auto response = phys_addrof(ipcData->response);\n   *outHandle = response->openFile.handle;\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSACloseFile(FSAHandle handle,\n             FSAFileHandle fileHandle)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::CloseFile;\n   ipcData->resourceHandle = handle;\n\n   auto request = phys_addrof(ipcData->request);\n   request->closeFile.handle = fileHandle;\n\n   auto error = IOS_Ioctl(ipcData->resourceHandle,\n                          ipcData->command,\n                          phys_addrof(ipcData->request),\n                          sizeof(FSARequest),\n                          phys_addrof(ipcData->response),\n                          sizeof(FSAResponse));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nstatic FSAStatus\nreadFile(FSAHandle handle,\n         phys_ptr<void> buffer,\n         uint32_t size,\n         uint32_t count,\n         uint32_t pos,\n         FSAFileHandle fileHandle,\n         FSAReadFlag readFlags)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::ReadFile;\n   ipcData->resourceHandle = handle;\n\n   auto request = phys_addrof(ipcData->request);\n   request->readFile.handle = fileHandle;\n   request->readFile.size = size;\n   request->readFile.count = count;\n   request->readFile.pos = pos;\n   request->readFile.readFlags = readFlags;\n\n   auto &vecs = ipcData->vecs;\n   vecs[0].paddr = phys_cast<phys_addr>(request);\n   vecs[0].len = static_cast<uint32_t>(sizeof(FSARequest));\n\n   vecs[1].paddr = phys_cast<phys_addr>(buffer);\n   vecs[1].len = size * count;\n\n   vecs[2].paddr = phys_cast<phys_addr>(phys_addrof(ipcData->response));\n   vecs[2].len = static_cast<uint32_t>(sizeof(FSAResponse));\n\n   auto error = IOS_Ioctlv(ipcData->resourceHandle,\n                           ipcData->command,\n                           1u,\n                           2u,\n                           phys_addrof(ipcData->vecs));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSAReadFile(FSAHandle handle,\n            phys_ptr<void> buffer,\n            uint32_t size,\n            uint32_t count,\n            FSAFileHandle fileHandle,\n            FSAReadFlag readFlags)\n{\n   return readFile(handle, buffer, size, count, 0, fileHandle, readFlags);\n}\n\nFSAStatus\nFSAReadFileWithPos(FSAHandle handle,\n                   phys_ptr<void> buffer,\n                   uint32_t size,\n                   uint32_t count,\n                   uint32_t pos,\n                   FSAFileHandle fileHandle,\n                   FSAReadFlag readFlags)\n{\n   return readFile(handle, buffer, size, count, pos, fileHandle,\n                   readFlags | FSAReadFlag::ReadWithPos);\n}\n\nFSAStatus\nFSAWriteFile(FSAHandle handle,\n             phys_ptr<const void> buffer,\n             uint32_t size,\n             uint32_t count,\n             FSAFileHandle fileHandle,\n             FSAWriteFlag writeFlags)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::WriteFile;\n   ipcData->resourceHandle = handle;\n\n   auto request = phys_addrof(ipcData->request);\n   request->writeFile.handle = fileHandle;\n   request->writeFile.size = size;\n   request->writeFile.count = count;\n   request->writeFile.writeFlags = writeFlags;\n\n   auto &vecs = ipcData->vecs;\n   vecs[0].paddr = phys_cast<phys_addr>(request);\n   vecs[0].len = static_cast<uint32_t>(sizeof(FSARequest));\n\n   vecs[1].paddr = phys_cast<phys_addr>(buffer);\n   vecs[1].len = size * count;\n\n   vecs[2].paddr = phys_cast<phys_addr>(phys_addrof(ipcData->response));\n   vecs[2].len = static_cast<uint32_t>(sizeof(FSAResponse));\n\n   auto error = IOS_Ioctlv(ipcData->resourceHandle,\n                           ipcData->command,\n                           2u,\n                           1u,\n                           phys_addrof(ipcData->vecs));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSAStatFile(FSAHandle handle,\n            FSAFileHandle fileHandle,\n            phys_ptr<FSAStat> stat)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::StatFile;\n   ipcData->resourceHandle = handle;\n\n   // Setup request\n   auto request = phys_addrof(ipcData->request);\n   request->statFile.handle = fileHandle;\n\n   // Perform ioctl\n   auto error = IOS_Ioctl(ipcData->resourceHandle,\n                          ipcData->command,\n                          phys_addrof(ipcData->request),\n                          sizeof(FSARequest),\n                          phys_addrof(ipcData->response),\n                          sizeof(FSAResponse));\n\n   // Copy FSAStat\n   auto response = phys_addrof(ipcData->response);\n   std::memcpy(stat.get(),\n               phys_addrof(response->statFile.stat).get(),\n               sizeof(FSAStat));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSARemove(FSAHandle handle,\n          std::string_view name)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::Remove;\n   ipcData->resourceHandle = handle;\n\n   // Setup request\n   auto request = phys_addrof(ipcData->request);\n   string_copy(phys_addrof(request->remove.path).get(),\n               name.data(),\n               request->remove.path.size());\n\n   // Perform ioctl\n   auto error = IOS_Ioctl(ipcData->resourceHandle,\n                          ipcData->command,\n                          phys_addrof(ipcData->request),\n                          sizeof(FSARequest),\n                          phys_addrof(ipcData->response),\n                          sizeof(FSAResponse));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSAMakeDir(FSAHandle handle,\n           std::string_view name,\n           uint32_t mode)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::MakeDir;\n   ipcData->resourceHandle = handle;\n\n   // Setup request\n   auto request = phys_addrof(ipcData->request);\n   request->makeDir.permission = mode;\n\n   string_copy(phys_addrof(request->makeDir.path).get(),\n               name.data(),\n               request->makeDir.path.size());\n\n   // Perform ioctl\n   auto error = IOS_Ioctl(ipcData->resourceHandle,\n                          ipcData->command,\n                          phys_addrof(ipcData->request),\n                          sizeof(FSARequest),\n                          phys_addrof(ipcData->response),\n                          sizeof(FSAResponse));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSAMakeQuota(FSAHandle handle,\n             std::string_view name,\n             uint32_t mode,\n             uint64_t quota)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::MakeQuota;\n   ipcData->resourceHandle = handle;\n\n   // Setup request\n   auto request = phys_addrof(ipcData->request);\n   request->makeQuota.mode = mode;\n   request->makeQuota.size = quota;\n\n   string_copy(phys_addrof(request->makeQuota.path).get(),\n               name.data(),\n               request->makeQuota.path.size());\n\n   // Perform ioctl\n   auto error = IOS_Ioctl(ipcData->resourceHandle,\n                          ipcData->command,\n                          phys_addrof(ipcData->request),\n                          sizeof(FSARequest),\n                          phys_addrof(ipcData->response),\n                          sizeof(FSAResponse));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSAMount(FSAHandle handle,\n         std::string_view src,\n         std::string_view dst,\n         uint32_t unk0x500,\n         phys_ptr<void> unkBuf,\n         uint32_t unkBufLen)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::Mount;\n   ipcData->resourceHandle = handle;\n\n   // Setup request\n   auto request = phys_addrof(ipcData->request);\n   string_copy(phys_addrof(request->mount.path).get(),\n               src.data(),\n               request->mount.path.size());\n\n   string_copy(phys_addrof(request->mount.target).get(),\n               dst.data(),\n               request->mount.target.size());\n\n   request->mount.unk0x500 = unk0x500;\n   request->mount.unkBuf = nullptr;\n   request->mount.unkBufLen = unkBufLen;\n\n   // Perform ioctlv\n   auto &vecs = ipcData->vecs;\n   vecs[0].paddr = phys_cast<phys_addr>(request);\n   vecs[0].len = static_cast<uint32_t>(sizeof(FSARequest));\n\n   vecs[1].paddr = phys_cast<phys_addr>(unkBuf);\n   vecs[1].len = unkBufLen;\n\n   vecs[2].paddr = phys_cast<phys_addr>(phys_addrof(ipcData->response));\n   vecs[2].len = static_cast<uint32_t>(sizeof(FSAResponse));\n\n   auto error = IOS_Ioctlv(ipcData->resourceHandle,\n                           ipcData->command,\n                           2u,\n                           1u,\n                           phys_addrof(ipcData->vecs));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSAMountWithProcess(FSAHandle handle,\n                    std::string_view src,\n                    std::string_view dst,\n                    FSAMountPriority priority,\n                    FSAProcessInfo *process,\n                    phys_ptr<void> unkBuf,\n                    uint32_t unkBufLen)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::MountWithProcess;\n   ipcData->resourceHandle = handle;\n\n   // Setup request\n   auto request = phys_addrof(ipcData->request);\n   string_copy(phys_addrof(request->mountWithProcess.path).get(),\n               src.data(),\n               request->mountWithProcess.path.size());\n\n   string_copy(phys_addrof(request->mountWithProcess.target).get(),\n               dst.data(),\n               request->mountWithProcess.target.size());\n\n   request->mountWithProcess.process = *process;\n   request->mountWithProcess.priority = priority;\n   request->mountWithProcess.unkBuf = nullptr;\n   request->mountWithProcess.unkBufLen = unkBufLen;\n\n   // Perform ioctlv\n   auto &vecs = ipcData->vecs;\n   vecs[0].paddr = phys_cast<phys_addr>(request);\n   vecs[0].len = static_cast<uint32_t>(sizeof(FSARequest));\n\n   vecs[1].paddr = phys_cast<phys_addr>(unkBuf);\n   vecs[1].len = unkBufLen;\n\n   vecs[2].paddr = phys_cast<phys_addr>(phys_addrof(ipcData->response));\n   vecs[2].len = static_cast<uint32_t>(sizeof(FSAResponse));\n\n   auto error = IOS_Ioctlv(ipcData->resourceHandle,\n                           ipcData->command,\n                           2u,\n                           1u,\n                           phys_addrof(ipcData->vecs));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSAUnmountWithProcess(FSAHandle handle,\n                      std::string_view path,\n                      FSAMountPriority priority,\n                      FSAProcessInfo *process)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::UnmountWithProcess;\n   ipcData->resourceHandle = handle;\n\n   // Setup request\n   auto request = phys_addrof(ipcData->request);\n   request->unmountWithProcess.path = path;\n   request->unmountWithProcess.priority = priority;\n   request->unmountWithProcess.process = *process;\n\n   // Perform ioctl\n   auto error = IOS_Ioctl(ipcData->resourceHandle,\n                          ipcData->command,\n                          phys_addrof(ipcData->request),\n                          sizeof(FSARequest),\n                          phys_addrof(ipcData->response),\n                          sizeof(FSAResponse));\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSAGetInfoByQuery(FSAHandle handle,\n                  std::string_view name,\n                  FSAQueryInfoType query,\n                  phys_ptr<void> output)\n{\n   phys_ptr<FSAIpcData> ipcData;\n\n   if (name.empty()) {\n      return FSAStatus::InvalidPath;\n   }\n\n   if (query > FSAQueryInfoType::FragmentBlockInfo) {\n      return FSAStatus::InvalidParam;\n   }\n\n   if (!output) {\n      return FSAStatus::InvalidBuffer;\n   }\n\n   auto status = allocFsaIpcData(&ipcData);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   ipcData->command = FSACommand::GetInfoByQuery;\n   ipcData->resourceHandle = handle;\n\n   // Setup request\n   auto request = phys_addrof(ipcData->request);\n   request->getInfoByQuery.type = query;\n\n   string_copy(phys_addrof(request->getInfoByQuery.path).get(),\n               name.data(),\n               request->getInfoByQuery.path.size());\n\n   // Perform ioctl\n   auto error = IOS_Ioctl(ipcData->resourceHandle,\n                          ipcData->command,\n                          phys_addrof(ipcData->request),\n                          sizeof(FSARequest),\n                          phys_addrof(ipcData->response),\n                          sizeof(FSAResponse));\n\n   if (error >= Error::OK) {\n      auto &response = ipcData->response.getInfoByQuery;\n\n      switch (query) {\n      case FSAQueryInfoType::FreeSpaceSize:\n         std::memcpy(output.get(),\n                     phys_addrof(response.freeSpaceSize).get(),\n                     sizeof(response.freeSpaceSize));\n         break;\n      case FSAQueryInfoType::DirSize:\n         std::memcpy(output.get(),\n                     phys_addrof(response.dirSize).get(),\n                     sizeof(response.dirSize));\n         break;\n      case FSAQueryInfoType::EntryNum:\n         std::memcpy(output.get(),\n                     phys_addrof(response.entryNum).get(),\n                     sizeof(response.entryNum));\n         break;\n      case FSAQueryInfoType::FileSystemInfo:\n         std::memcpy(output.get(),\n                     phys_addrof(response.fileSystemInfo).get(),\n                     sizeof(response.fileSystemInfo));\n         break;\n      case FSAQueryInfoType::DeviceInfo:\n         std::memcpy(output.get(),\n                     phys_addrof(response.deviceInfo).get(),\n                     sizeof(response.deviceInfo));\n         break;\n      case FSAQueryInfoType::Stat:\n         std::memcpy(output.get(),\n                     phys_addrof(response.stat).get(),\n                     sizeof(response.stat));\n         break;\n      case FSAQueryInfoType::BadBlockInfo:\n         std::memcpy(output.get(),\n                     phys_addrof(response.badBlockInfo).get(),\n                     sizeof(response.badBlockInfo));\n         break;\n      case FSAQueryInfoType::JournalFreeSpaceSize:\n         std::memcpy(output.get(),\n                     phys_addrof(response.journalFreeSpaceSize).get(),\n                     sizeof(response.journalFreeSpaceSize));\n         break;\n      case FSAQueryInfoType::FragmentBlockInfo:\n         std::memcpy(output.get(),\n                     phys_addrof(response.fragmentBlockInfo).get(),\n                     sizeof(response.fragmentBlockInfo));\n         break;\n      }\n   }\n\n   freeFsaIpcData(ipcData);\n   return static_cast<FSAStatus>(error);\n}\n\nFSAStatus\nFSAGetStat(FSAHandle handle,\n           std::string_view name,\n           phys_ptr<FSAStat> output)\n{\n   return FSAGetInfoByQuery(handle, name, FSAQueryInfoType::Stat, output);\n}\n\nFSAStatus\nFSAReadFileIntoCrossProcessHeap(FSAHandle fsaHandle,\n                                std::string_view filename,\n                                uint32_t *outBytesRead,\n                                phys_ptr<uint8_t> *outBuffer)\n{\n   StackObject<FSAStat> stat;\n   FSAFileHandle fileHandle;\n\n   *outBytesRead = 0u;\n   *outBuffer = nullptr;\n\n   auto status = FSAOpenFile(fsaHandle, filename, \"r\", &fileHandle);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   status = FSAStatFile(fsaHandle, fileHandle, stat);\n   if (status < FSAStatus::OK) {\n      FSACloseFile(fsaHandle, fileHandle);\n      return status;\n   }\n\n   auto fileData = IOS_HeapAllocAligned(CrossProcessHeapId, stat->size, 0x40u);\n   if (!fileData) {\n      FSACloseFile(fsaHandle, fileHandle);\n      return FSAStatus::OutOfResources;\n   }\n\n   status = FSAReadFile(fsaHandle,\n                        fileData,\n                        stat->size,\n                        1,\n                        fileHandle,\n                        FSAReadFlag::None);\n   if (status < FSAStatus::OK) {\n      IOS_HeapFree(CrossProcessHeapId, fileData);\n      FSACloseFile(fsaHandle, fileHandle);\n      return status;\n   }\n\n   status = FSACloseFile(fsaHandle, fileHandle);\n   if (status < FSAStatus::OK) {\n      IOS_HeapFree(CrossProcessHeapId, fileData);\n      return status;\n   }\n\n   *outBytesRead = stat->size;\n   *outBuffer = phys_cast<uint8_t *>(fileData);\n   return status;\n}\n\n} // namespace ios::fs\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_ipc.h",
    "content": "#pragma once\n#include \"ios_fs_fsa_types.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n\nnamespace ios::fs\n{\n\nusing FSAHandle = kernel::ResourceHandleId;\n\nError\nFSAOpen();\n\nError\nFSAClose(FSAHandle handle);\n\nFSAStatus\nFSAOpenDir(FSAHandle handle,\n           std::string_view name,\n           FSADirHandle *outHandle);\n\nFSAStatus\nFSACloseDir(FSAHandle handle,\n            FSADirHandle dirHandle);\n\nFSAStatus\nFSAOpenFile(FSAHandle handle,\n            std::string_view name,\n            std::string_view mode,\n            FSAFileHandle *outHandle);\n\nFSAStatus\nFSACloseFile(FSAHandle handle,\n             FSAFileHandle fileHandle);\n\nFSAStatus\nFSAReadFile(FSAHandle handle,\n            phys_ptr<void> buffer,\n            uint32_t size,\n            uint32_t count,\n            FSAFileHandle fileHandle,\n            FSAReadFlag readFlags);\n\nFSAStatus\nFSAReadFileWithPos(FSAHandle handle,\n                   phys_ptr<void> buffer,\n                   uint32_t size,\n                   uint32_t count,\n                   uint32_t pos,\n                   FSAFileHandle fileHandle,\n                   FSAReadFlag readFlags);\n\nFSAStatus\nFSAWriteFile(FSAHandle handle,\n             phys_ptr<const void> buffer,\n             uint32_t size,\n             uint32_t count,\n             FSAFileHandle fileHandle,\n             FSAWriteFlag writeFlags);\n\nFSAStatus\nFSAStatFile(FSAHandle handle,\n            FSAFileHandle fileHandle,\n            phys_ptr<FSAStat> stat);\n\nFSAStatus\nFSARemove(FSAHandle handle,\n          std::string_view name);\n\nFSAStatus\nFSAMakeDir(FSAHandle handle,\n           std::string_view name,\n           uint32_t mode);\n\nFSAStatus\nFSAMakeQuota(FSAHandle handle,\n             std::string_view name,\n             uint32_t mode,\n             uint64_t quota);\n\nFSAStatus\nFSAMount(FSAHandle handle,\n         std::string_view src,\n         std::string_view dst,\n         uint32_t unk0x500,\n         phys_ptr<void> unkBuf,\n         uint32_t unkBufLen);\n\nFSAStatus\nFSAMountWithProcess(FSAHandle handle,\n                    std::string_view src,\n                    std::string_view dst,\n                    FSAMountPriority priority,\n                    FSAProcessInfo *process,\n                    phys_ptr<void> unkBuf,\n                    uint32_t unkBufLen);\n\nFSAStatus\nFSAUnmountWithProcess(FSAHandle handle,\n                      std::string_view path,\n                      FSAMountPriority priority,\n                      FSAProcessInfo *process);\n\nFSAStatus\nFSAGetInfoByQuery(FSAHandle handle,\n                  std::string_view name,\n                  FSAQueryInfoType query,\n                  phys_ptr<void> output);\n\nFSAStatus\nFSAGetStat(FSAHandle handle,\n           std::string_view name,\n           phys_ptr<FSAStat> output);\n\nFSAStatus\nFSAReadFileIntoCrossProcessHeap(FSAHandle handle,\n                                std::string_view name,\n                                uint32_t *outBytesRead,\n                                phys_ptr<uint8_t> *outBuffer);\n\n} // namespace ios::fs\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_request.h",
    "content": "#pragma once\n#include \"ios_fs_enum.h\"\n#include \"ios_fs_fsa_types.h\"\n\n#include <cstdint>\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::fs\n{\n\n/**\n * \\ingroup ios_fs\n * @{\n */\n\n#pragma pack(push, 1)\n\n\n/**\n * Request data for Command::AppendFile\n */\nstruct FSARequestAppendFile\n{\n   be2_val<uint32_t> size;\n   be2_val<uint32_t> count;\n   be2_val<FSAFileHandle> handle;\n   be2_val<uint32_t> unk0x0C;\n};\nCHECK_OFFSET(FSARequestAppendFile, 0x0, size);\nCHECK_OFFSET(FSARequestAppendFile, 0x4, count);\nCHECK_OFFSET(FSARequestAppendFile, 0x8, handle);\nCHECK_OFFSET(FSARequestAppendFile, 0xC, unk0x0C);\nCHECK_SIZE(FSARequestAppendFile, 0x10);\n\n\n/**\n * Request data for Command::ChangeDir\n */\nstruct FSARequestChangeDir\n{\n   be2_array<char, FSAPathLength + 1> path;\n};\nCHECK_OFFSET(FSARequestChangeDir, 0x0, path);\nCHECK_SIZE(FSARequestChangeDir, 0x280);\n\n\n/**\n * Request data for Command::ChangeMode\n */\nstruct FSARequestChangeMode\n{\n   be2_array<char, FSAPathLength + 1> path;\n   be2_val<uint32_t> mode1;\n   be2_val<uint32_t> mode2;\n};\nCHECK_OFFSET(FSARequestChangeMode, 0x0, path);\nCHECK_OFFSET(FSARequestChangeMode, 0x280, mode1);\nCHECK_OFFSET(FSARequestChangeMode, 0x284, mode2);\nCHECK_SIZE(FSARequestChangeMode, 0x288);\n\n\n/**\n * Request data for Command::CloseDir\n */\nstruct FSARequestCloseDir\n{\n   be2_val<FSADirHandle> handle;\n};\nCHECK_OFFSET(FSARequestCloseDir, 0x0, handle);\nCHECK_SIZE(FSARequestCloseDir, 0x4);\n\n\n/**\n * Request data for Command::CloseFile\n */\nstruct FSARequestCloseFile\n{\n   be2_val<FSAFileHandle> handle;\n};\nCHECK_OFFSET(FSARequestCloseFile, 0x0, handle);\nCHECK_SIZE(FSARequestCloseFile, 0x4);\n\n\n/**\n * Request data for Command::FlushFile\n */\nstruct FSARequestFlushFile\n{\n   be2_val<FSAFileHandle> handle;\n};\nCHECK_OFFSET(FSARequestFlushFile, 0x0, handle);\nCHECK_SIZE(FSARequestFlushFile, 0x4);\n\n\n/**\n * Request data for Command::FlushQuota\n */\nstruct FSARequestFlushQuota\n{\n   be2_array<char, FSAPathLength + 1> path;\n};\nCHECK_OFFSET(FSARequestFlushQuota, 0x0, path);\nCHECK_SIZE(FSARequestFlushQuota, 0x280);\n\n\n/**\n * Request data for Command::GetInfoByQuery\n */\nstruct FSARequestGetInfoByQuery\n{\n   be2_array<char, FSAPathLength + 1> path;\n   be2_val<FSAQueryInfoType> type;\n};\nCHECK_OFFSET(FSARequestGetInfoByQuery, 0x0, path);\nCHECK_OFFSET(FSARequestGetInfoByQuery, 0x280, type);\nCHECK_SIZE(FSARequestGetInfoByQuery, 0x284);\n\n\n/**\n * Request data for Command::GetPosFile\n */\nstruct FSARequestGetPosFile\n{\n   be2_val<FSAFileHandle> handle;\n};\nCHECK_OFFSET(FSARequestGetPosFile, 0x0, handle);\nCHECK_SIZE(FSARequestGetPosFile, 0x4);\n\n\n/**\n * Request data for Command::IsEof\n */\nstruct FSARequestIsEof\n{\n   be2_val<FSAFileHandle> handle;\n};\nCHECK_OFFSET(FSARequestIsEof, 0x0, handle);\nCHECK_SIZE(FSARequestIsEof, 0x4);\n\n\n/**\n * Request data for Command::MakeDir\n */\nstruct FSARequestMakeDir\n{\n   be2_array<char, FSAPathLength + 1> path;\n   be2_val<uint32_t> permission;\n};\nCHECK_OFFSET(FSARequestMakeDir, 0x0, path);\nCHECK_OFFSET(FSARequestMakeDir, 0x280, permission);\nCHECK_SIZE(FSARequestMakeDir, 0x284);\n\n/**\n * Request data for Command::MakeQuota\n */\nstruct FSARequestMakeQuota\n{\n   be2_array<char, FSAPathLength + 1> path;\n   be2_val<uint32_t> mode;\n   be2_val<uint64_t> size;\n};\nCHECK_OFFSET(FSARequestMakeQuota, 0x0, path);\nCHECK_OFFSET(FSARequestMakeQuota, 0x280, mode);\nCHECK_OFFSET(FSARequestMakeQuota, 0x284, size);\nCHECK_SIZE(FSARequestMakeQuota, 0x28C);\n\n\n/**\n * Request data for Command::Mount\n */\nstruct FSARequestMount\n{\n   be2_array<char, FSAPathLength + 1> path;\n   be2_array<char, FSAPathLength + 1> target;\n   be2_val<uint32_t> unk0x500;\n   be2_virt_ptr<void> unkBuf;\n   be2_val<uint32_t> unkBufLen;\n};\nCHECK_OFFSET(FSARequestMount, 0x0, path);\nCHECK_OFFSET(FSARequestMount, 0x280, target);\nCHECK_OFFSET(FSARequestMount, 0x500, unk0x500);\nCHECK_OFFSET(FSARequestMount, 0x504, unkBuf);\nCHECK_OFFSET(FSARequestMount, 0x508, unkBufLen);\nCHECK_SIZE(FSARequestMount, 0x50C);\n\n\n/**\n * Request data for Command::MountWithProcess\n */\nstruct FSARequestMountWithProcess\n{\n   be2_array<char, FSAPathLength + 1> path;\n   be2_array<char, FSAPathLength + 1> target;\n   be2_val<FSAMountPriority> priority;\n   be2_struct<FSAProcessInfo> process;\n   be2_virt_ptr<void> unkBuf;\n   be2_val<uint32_t> unkBufLen;\n};\nCHECK_OFFSET(FSARequestMountWithProcess, 0x0, path);\nCHECK_OFFSET(FSARequestMountWithProcess, 0x280, target);\nCHECK_OFFSET(FSARequestMountWithProcess, 0x500, priority);\nCHECK_OFFSET(FSARequestMountWithProcess, 0x504, process);\nCHECK_OFFSET(FSARequestMountWithProcess, 0x514, unkBuf);\nCHECK_OFFSET(FSARequestMountWithProcess, 0x518, unkBufLen);\nCHECK_SIZE(FSARequestMountWithProcess, 0x51C);\n\n\n/**\n * Request data for Command::OpenDir\n */\nstruct FSARequestOpenDir\n{\n   be2_array<char, FSAPathLength + 1> path;\n};\nCHECK_OFFSET(FSARequestOpenDir, 0x0, path);\nCHECK_SIZE(FSARequestOpenDir, 0x280);\n\n\n/**\n * Request data for Command::OpenFile\n */\nstruct FSARequestOpenFile\n{\n   be2_array<char, FSAPathLength + 1> path;\n   be2_array<char, FSAModeLength> mode;\n   be2_val<uint32_t> unk0x290;\n   be2_val<uint32_t> unk0x294;\n   be2_val<uint32_t> unk0x298;\n};\nCHECK_OFFSET(FSARequestOpenFile, 0x0, path);\nCHECK_OFFSET(FSARequestOpenFile, 0x280, mode);\nCHECK_OFFSET(FSARequestOpenFile, 0x290, unk0x290);\nCHECK_OFFSET(FSARequestOpenFile, 0x294, unk0x294);\nCHECK_OFFSET(FSARequestOpenFile, 0x298, unk0x298);\nCHECK_SIZE(FSARequestOpenFile, 0x29C);\n\n\n/**\n * Request data for Command::ReadDir\n */\nstruct FSARequestReadDir\n{\n   be2_val<FSADirHandle> handle;\n};\nCHECK_OFFSET(FSARequestReadDir, 0x0, handle);\nCHECK_SIZE(FSARequestReadDir, 0x4);\n\n\n/**\n * Request data for Command::ReadFile\n */\nstruct FSARequestReadFile\n{\n   //! Virtual pointer used only by Cafe, for IOS we should use ioctlv.vecs[1]\n   be2_virt_ptr<uint8_t> buffer;\n   be2_val<uint32_t> size;\n   be2_val<uint32_t> count;\n   be2_val<FSAFilePosition> pos;\n   be2_val<FSAFileHandle> handle;\n   be2_val<FSAReadFlag> readFlags;\n};\nCHECK_OFFSET(FSARequestReadFile, 0x00, buffer);\nCHECK_OFFSET(FSARequestReadFile, 0x04, size);\nCHECK_OFFSET(FSARequestReadFile, 0x08, count);\nCHECK_OFFSET(FSARequestReadFile, 0x0C, pos);\nCHECK_OFFSET(FSARequestReadFile, 0x10, handle);\nCHECK_OFFSET(FSARequestReadFile, 0x14, readFlags);\nCHECK_SIZE(FSARequestReadFile, 0x18);\n\n\n/**\n * Request data for Command::Remove\n */\nstruct FSARequestRemove\n{\n   be2_array<char, FSAPathLength + 1> path;\n};\nCHECK_OFFSET(FSARequestRemove, 0x0, path);\nCHECK_SIZE(FSARequestRemove, 0x280);\n\n\n/**\n * Request data for Command::Rename\n */\nstruct FSARequestRename\n{\n   be2_array<char, FSAPathLength + 1> oldPath;\n   be2_array<char, FSAPathLength + 1> newPath;\n};\nCHECK_OFFSET(FSARequestRename, 0x0, oldPath);\nCHECK_OFFSET(FSARequestRename, 0x280, newPath);\nCHECK_SIZE(FSARequestRename, 0x500);\n\n\n/**\n * Request data for Command::RewindDir\n */\nstruct FSARequestRewindDir\n{\n   be2_val<FSADirHandle> handle;\n};\nCHECK_OFFSET(FSARequestRewindDir, 0x0, handle);\nCHECK_SIZE(FSARequestRewindDir, 0x4);\n\n\n/**\n * Request data for Command::SetPosFile\n */\nstruct FSARequestSetPosFile\n{\n   be2_val<FSAFileHandle> handle;\n   be2_val<FSAFilePosition> pos;\n};\nCHECK_OFFSET(FSARequestSetPosFile, 0x0, handle);\nCHECK_OFFSET(FSARequestSetPosFile, 0x4, pos);\nCHECK_SIZE(FSARequestSetPosFile, 0x8);\n\n\n/**\n * Request data for Command::StatFile\n */\nstruct FSARequestStatFile\n{\n   be2_val<FSAFileHandle> handle;\n};\nCHECK_OFFSET(FSARequestStatFile, 0x0, handle);\nCHECK_SIZE(FSARequestStatFile, 0x4);\n\n\n/**\n * Request data for Command::TruncateFile\n */\nstruct FSARequestTruncateFile\n{\n   be2_val<FSAFileHandle> handle;\n};\nCHECK_OFFSET(FSARequestTruncateFile, 0x0, handle);\nCHECK_SIZE(FSARequestTruncateFile, 0x4);\n\n\n/**\n * Request data for Command::Unmount\n */\nstruct FSARequestUnmount\n{\n   be2_array<char, FSAPathLength + 1> path;\n   be2_val<uint32_t> unk0x280;\n};\nCHECK_OFFSET(FSARequestUnmount, 0x0, path);\nCHECK_OFFSET(FSARequestUnmount, 0x280, unk0x280);\nCHECK_SIZE(FSARequestUnmount, 0x284);\n\n\n/**\n * Request data for Command::UnmountWithProcess\n */\nstruct FSARequestUnmountWithProcess\n{\n   be2_array<char, FSAPathLength + 1> path;\n   be2_val<FSAMountPriority> priority;\n   be2_struct<FSAProcessInfo> process;\n};\nCHECK_OFFSET(FSARequestUnmountWithProcess, 0x0, path);\nCHECK_OFFSET(FSARequestUnmountWithProcess, 0x280, priority);\nCHECK_OFFSET(FSARequestUnmountWithProcess, 0x284, process);\nCHECK_SIZE(FSARequestUnmountWithProcess, 0x294);\n\n\n/**\n * Request data for Command::WriteFile\n */\nstruct FSARequestWriteFile\n{\n   //! Virtual pointer used only by Cafe, for IOS we should use ioctlv.vecs[1]\n   be2_virt_ptr<const uint8_t> buffer;\n   be2_val<uint32_t> size;\n   be2_val<uint32_t> count;\n   be2_val<FSAFilePosition> pos;\n   be2_val<FSAFileHandle> handle;\n   be2_val<FSAWriteFlag> writeFlags;\n};\nCHECK_OFFSET(FSARequestWriteFile, 0x00, buffer);\nCHECK_OFFSET(FSARequestWriteFile, 0x04, size);\nCHECK_OFFSET(FSARequestWriteFile, 0x08, count);\nCHECK_OFFSET(FSARequestWriteFile, 0x0C, pos);\nCHECK_OFFSET(FSARequestWriteFile, 0x10, handle);\nCHECK_OFFSET(FSARequestWriteFile, 0x14, writeFlags);\nCHECK_SIZE(FSARequestWriteFile, 0x18);\n\n\n/**\n * IPC request data for FSA device.\n */\nstruct FSARequest\n{\n   be2_val<FSAStatus> emulatedError;\n\n   union\n   {\n      be2_struct<FSARequestAppendFile> appendFile;\n      be2_struct<FSARequestChangeDir> changeDir;\n      be2_struct<FSARequestChangeMode> changeMode;\n      be2_struct<FSARequestCloseDir> closeDir;\n      be2_struct<FSARequestCloseFile> closeFile;\n      be2_struct<FSARequestFlushFile> flushFile;\n      be2_struct<FSARequestFlushQuota> flushQuota;\n      be2_struct<FSARequestGetInfoByQuery> getInfoByQuery;\n      be2_struct<FSARequestGetPosFile> getPosFile;\n      be2_struct<FSARequestIsEof> isEof;\n      be2_struct<FSARequestMakeDir> makeDir;\n      be2_struct<FSARequestMakeQuota> makeQuota;\n      be2_struct<FSARequestMount> mount;\n      be2_struct<FSARequestMountWithProcess> mountWithProcess;\n      be2_struct<FSARequestOpenDir> openDir;\n      be2_struct<FSARequestOpenFile> openFile;\n      be2_struct<FSARequestReadDir> readDir;\n      be2_struct<FSARequestReadFile> readFile;\n      be2_struct<FSARequestRemove> remove;\n      be2_struct<FSARequestRename> rename;\n      be2_struct<FSARequestRewindDir> rewindDir;\n      be2_struct<FSARequestSetPosFile> setPosFile;\n      be2_struct<FSARequestStatFile> statFile;\n      be2_struct<FSARequestTruncateFile> truncateFile;\n      be2_struct<FSARequestUnmount> unmount;\n      be2_struct<FSARequestUnmountWithProcess> unmountWithProcess;\n      be2_struct<FSARequestWriteFile> writeFile;\n      UNKNOWN(0x51C);\n   };\n};\nCHECK_OFFSET(FSARequest, 0x00, emulatedError);\nCHECK_SIZE(FSARequest, 0x520);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::fs\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_response.h",
    "content": "#pragma once\n#include \"ios_fs_enum.h\"\n#include \"ios_fs_fsa_types.h\"\n\n#include <cstdint>\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::fs\n{\n\n/**\n * \\ingroup ios_fs\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct FSAResponseGetCwd\n{\n   be2_array<char, FSAPathLength + 1> path;\n};\nCHECK_OFFSET(FSAResponseGetCwd, 0x0, path);\nCHECK_SIZE(FSAResponseGetCwd, 0x280);\n\nstruct FSAResponseGetFileBlockAddress\n{\n   be2_val<uint32_t> address;\n};\nCHECK_OFFSET(FSAResponseGetFileBlockAddress, 0x0, address);\nCHECK_SIZE(FSAResponseGetFileBlockAddress, 0x4);\n\nstruct FSAResponseGetPosFile\n{\n   be2_val<FSAFilePosition> pos;\n};\nCHECK_OFFSET(FSAResponseGetPosFile, 0x0, pos);\nCHECK_SIZE(FSAResponseGetPosFile, 0x4);\n\nstruct FSAResponseGetVolumeInfo\n{\n   be2_struct<FSAVolumeInfo> volumeInfo;\n};\nCHECK_OFFSET(FSAResponseGetVolumeInfo, 0x0, volumeInfo);\nCHECK_SIZE(FSAResponseGetVolumeInfo, 0x1BC);\n\nstruct FSAResponseGetInfoByQuery\n{\n   union\n   {\n      be2_struct<FSABlockInfo> badBlockInfo;\n      be2_struct<FSADeviceInfo> deviceInfo;\n      be2_val<uint64_t> dirSize;\n      be2_val<FSAEntryNum> entryNum;\n      be2_struct<FSAFileSystemInfo> fileSystemInfo;\n      be2_struct<FSABlockInfo> fragmentBlockInfo;\n      be2_val<uint64_t> freeSpaceSize;\n      be2_val<uint64_t> journalFreeSpaceSize;\n      be2_struct<FSAStat> stat;\n   };\n};\nCHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, badBlockInfo);\nCHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, deviceInfo);\nCHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, dirSize);\nCHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, entryNum);\nCHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, fragmentBlockInfo);\nCHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, freeSpaceSize);\nCHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, fileSystemInfo);\nCHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, journalFreeSpaceSize);\nCHECK_OFFSET(FSAResponseGetInfoByQuery, 0x0, stat);\nCHECK_SIZE(FSAResponseGetInfoByQuery, 0x64);\n\nstruct FSAResponseOpenFile\n{\n   be2_val<FSAFileHandle> handle;\n};\nCHECK_OFFSET(FSAResponseOpenFile, 0x0, handle);\nCHECK_SIZE(FSAResponseOpenFile, 0x4);\n\nstruct FSAResponseOpenDir\n{\n   be2_val<FSADirHandle> handle;\n};\nCHECK_OFFSET(FSAResponseOpenDir, 0x0, handle);\nCHECK_SIZE(FSAResponseOpenDir, 0x4);\n\nstruct FSAResponseReadDir\n{\n   be2_struct<FSADirEntry> entry;\n};\nCHECK_OFFSET(FSAResponseReadDir, 0x0, entry);\nCHECK_SIZE(FSAResponseReadDir, 0x164);\n\nstruct FSAResponseStatFile\n{\n   be2_struct<FSAStat> stat;\n};\nCHECK_OFFSET(FSAResponseStatFile, 0x0, stat);\nCHECK_SIZE(FSAResponseStatFile, 0x64);\n\nstruct FSAResponse\n{\n   be2_val<uint32_t> word0;\n\n   union\n   {\n      be2_struct<FSAResponseGetCwd> getCwd;\n      be2_struct<FSAResponseGetFileBlockAddress> getFileBlockAddress;\n      be2_struct<FSAResponseGetPosFile> getPosFile;\n      be2_struct<FSAResponseGetVolumeInfo> getVolumeInfo;\n      be2_struct<FSAResponseGetInfoByQuery> getInfoByQuery;\n      be2_struct<FSAResponseOpenDir> openDir;\n      be2_struct<FSAResponseOpenFile> openFile;\n      be2_struct<FSAResponseReadDir> readDir;\n      be2_struct<FSAResponseStatFile> statFile;\n      UNKNOWN(0x28F);\n   };\n};\nCHECK_OFFSET(FSAResponse, 0x0, word0);\nCHECK_OFFSET(FSAResponse, 0x4, getFileBlockAddress);\nCHECK_OFFSET(FSAResponse, 0x4, getPosFile);\nCHECK_OFFSET(FSAResponse, 0x4, getVolumeInfo);\nCHECK_OFFSET(FSAResponse, 0x4, openDir);\nCHECK_OFFSET(FSAResponse, 0x4, openFile);\nCHECK_OFFSET(FSAResponse, 0x4, readDir);\nCHECK_OFFSET(FSAResponse, 0x4, statFile);\nCHECK_SIZE(FSAResponse, 0x293);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::fs\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_thread.cpp",
    "content": "#include \"ios_fs_fsa_async_task.h\"\n#include \"ios_fs_fsa_device.h\"\n#include \"ios_fs_mutex.h\"\n\n#include \"ios/kernel/ios_kernel_hardware.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_handlemanager.h\"\n#include \"ios/ios_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n#include \"ios/ios_worker_thread.h\"\n\nusing namespace ios::kernel;\nusing ios::internal::submitWorkerTask;\n\nnamespace ios::fs::internal\n{\n\nusing FSADeviceHandle = int32_t;\nconstexpr auto FSAMaxClients = 0x270;\n\nstruct RequestOrigin\n{\n   be2_val<TitleId> titleId;\n   be2_val<ProcessId> processId;\n   be2_val<GroupId> groupId;\n};\n\nstruct StaticFsaThreadData\n{\n   be2_val<MessageQueueId> fsaMessageQueue;\n   be2_array<Message, 0x160> fsaMessageBuffer;\n\n   be2_val<ThreadId> fsaThread;\n   be2_array<uint8_t, 0x4000> fsaThreadStack;\n};\n\nstatic phys_ptr<StaticFsaThreadData>\nsData = nullptr;\n\nstatic HandleManager<FSADevice, FSADeviceHandle, FSAMaxClients>\nsDevices;\n\nFSAStatus\ngetDevice(FSADeviceHandle handle,\n          FSADevice **outDevice)\n{\n   auto error = sDevices.get(handle, outDevice);\n   if (error < Error::OK) {\n      return FSAStatus::InvalidClientHandle;\n   }\n\n   return FSAStatus::OK;\n}\n\nstatic FSAStatus\nfsaDeviceOpen(phys_ptr<RequestOrigin> origin,\n              FSADeviceHandle *outHandle,\n              ClientCapabilityMask clientCapabilityMask)\n{\n   auto error = sDevices.open();\n   if(error < Error::OK) {\n      return FSAStatus::MaxClients;\n   }\n\n   *outHandle = static_cast<FSADeviceHandle>(error);\n   return FSAStatus::OK;\n}\n\nstatic FSAStatus\nfsaDeviceClose(FSADeviceHandle handle,\n               phys_ptr<ResourceRequest> resourceRequest)\n{\n   FSADevice *device = nullptr;\n   auto status = getDevice(handle, &device);\n   if (status < FSAStatus::OK) {\n      return status;\n   }\n\n   // TODO: Handle cleanup of async operations\n\n   sDevices.close(device);\n   return FSAStatus::OK;\n}\n\nstatic void\nfsaDeviceIoctl(phys_ptr<ResourceRequest> resourceRequest,\n               FSACommand command,\n               be2_phys_ptr<const void> inputBuffer,\n               be2_phys_ptr<void> outputBuffer)\n{\n   FSADevice *device = nullptr;\n   auto status = getDevice(resourceRequest->requestData.handle, &device);\n   if (status < FSAStatus::OK) {\n      IOS_ResourceReply(resourceRequest, static_cast<Error>(status));\n      return;\n   }\n\n   auto request = phys_cast<const FSARequest *>(inputBuffer);\n   auto response = phys_cast<FSAResponse *>(outputBuffer);\n\n   if (!device) {\n     IOS_ResourceReply(resourceRequest,\n                       static_cast<Error>(FSAStatus::InvalidClientHandle));\n     return;\n   }\n\n   if (!inputBuffer && !outputBuffer) {\n     IOS_ResourceReply(resourceRequest,\n                       static_cast<Error>(FSAStatus::InvalidParam));\n     return;\n   }\n\n   if (request->emulatedError < FSAStatus::OK) {\n      IOS_ResourceReply(resourceRequest,\n                        static_cast<Error>(request->emulatedError));\n      return;\n   }\n\n   auto user = vfs::User { };\n   user.id = static_cast<vfs::OwnerId>(resourceRequest->requestData.titleId & 0xFFFFFFFF);\n   user.group = static_cast<vfs::GroupId>(resourceRequest->requestData.groupId);\n\n   switch (command) {\n   case FSACommand::AppendFile:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->appendFile(user, phys_addrof(request->appendFile)));\n         });\n      break;\n   case FSACommand::ChangeDir:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->changeDir(user, phys_addrof(request->changeDir)));\n         });\n      break;\n   case FSACommand::ChangeMode:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->changeMode(user, phys_addrof(request->changeMode)));\n         });\n      break;\n   case FSACommand::CloseDir:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->closeDir(user, phys_addrof(request->closeDir)));\n         });\n      break;\n   case FSACommand::CloseFile:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->closeFile(user, phys_addrof(request->closeFile)));\n         });\n      break;\n   case FSACommand::FlushFile:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->flushFile(user, phys_addrof(request->flushFile)));\n         });\n      break;\n   case FSACommand::FlushQuota:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->flushQuota(user, phys_addrof(request->flushQuota)));\n         });\n      break;\n   case FSACommand::GetCwd:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->getCwd(user, phys_addrof(response->getCwd)));\n         });\n      break;\n   case FSACommand::GetInfoByQuery:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->getInfoByQuery(user,\n                                      phys_addrof(request->getInfoByQuery),\n                                      phys_addrof(response->getInfoByQuery)));\n         });\n      break;\n   case FSACommand::GetPosFile:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->getPosFile(user,\n                                  phys_addrof(request->getPosFile),\n                                  phys_addrof(response->getPosFile)));\n         });\n      break;\n   case FSACommand::IsEof:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->isEof(user, phys_addrof(request->isEof)));\n         });\n      break;\n   case FSACommand::MakeDir:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->makeDir(user, phys_addrof(request->makeDir)));\n         });\n      break;\n   case FSACommand::MakeQuota:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->makeQuota(user, phys_addrof(request->makeQuota)));\n         });\n      break;\n   case FSACommand::OpenDir:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->openDir(user,\n                               phys_addrof(request->openDir),\n                               phys_addrof(response->openDir)));\n         });\n      break;\n   case FSACommand::OpenFile:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->openFile(user,\n                                phys_addrof(request->openFile),\n                                phys_addrof(response->openFile)));\n         });\n      break;\n   case FSACommand::ReadDir:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->readDir(user,\n                               phys_addrof(request->readDir),\n                               phys_addrof(response->readDir)));\n         });\n      break;\n   case FSACommand::Remove:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->remove(user, phys_addrof(request->remove)));\n         });\n      break;\n   case FSACommand::Rename:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->rename(user, phys_addrof(request->rename)));\n         });\n      break;\n   case FSACommand::RewindDir:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->rewindDir(user, phys_addrof(request->rewindDir)));\n         });\n      break;\n   case FSACommand::SetPosFile:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->setPosFile(user, phys_addrof(request->setPosFile)));\n         });\n      break;\n   case FSACommand::StatFile:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->statFile(user,\n                                phys_addrof(request->statFile),\n                                phys_addrof(response->statFile)));\n         });\n      break;\n   case FSACommand::TruncateFile:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->truncateFile(user, phys_addrof(request->truncateFile)));\n         });\n      break;\n   case FSACommand::Unmount:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->unmount(user, phys_addrof(request->unmount)));\n         });\n      break;\n   case FSACommand::UnmountWithProcess:\n      submitWorkerTask([=]() {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->unmountWithProcess(user,\n                                          phys_addrof(request->unmountWithProcess)));\n         });\n      break;\n   default:\n      IOS_ResourceReply(resourceRequest,\n                        static_cast<Error>(FSAStatus::UnsupportedCmd));\n   }\n}\n\nstatic void\nfsaDeviceIoctlv(phys_ptr<ResourceRequest> resourceRequest,\n                FSACommand command,\n                be2_phys_ptr<IoctlVec> vecs)\n{\n   auto request = phys_cast<FSARequest *>(vecs[0].paddr);\n\n   if (request->emulatedError < FSAStatus::OK) {\n      IOS_ResourceReply(resourceRequest,\n                        static_cast<Error>(request->emulatedError));\n      return;\n   }\n\n   FSADevice *device = nullptr;\n   auto status = getDevice(resourceRequest->requestData.handle, &device);\n   if (status < FSAStatus::OK) {\n      IOS_ResourceReply(resourceRequest, static_cast<Error>(status));\n      return;\n   }\n\n   auto user = vfs::User{ };\n   user.id = static_cast<vfs::OwnerId>(resourceRequest->requestData.titleId & 0xFFFFFFFF);\n   user.group = static_cast<vfs::GroupId>(resourceRequest->requestData.groupId);\n\n   switch (command) {\n   case FSACommand::ReadFile:\n   {\n      submitWorkerTask(\n         [=]() {\n            auto buffer = phys_cast<uint8_t *>(vecs[1].paddr);\n            auto length = vecs[1].len;\n\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->readFile(user, phys_addrof(request->readFile),\n                                buffer, length));\n         });\n      break;\n   }\n   case FSACommand::WriteFile:\n   {\n      submitWorkerTask(\n         [=]()\n         {\n            auto buffer = phys_cast<uint8_t *>(vecs[1].paddr);\n            auto length = vecs[1].len;\n\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->writeFile(user, phys_addrof(request->writeFile),\n                                 buffer, length));\n         });\n      break;\n   }\n   case FSACommand::Mount:\n   {\n      submitWorkerTask(\n         [=]()\n         {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->mount(user, phys_addrof(request->mount)));\n         });\n      break;\n   }\n   case FSACommand::MountWithProcess:\n   {\n      submitWorkerTask(\n         [=]()\n         {\n            fsaAsyncTaskComplete(\n               resourceRequest,\n               device->mountWithProcess(user, phys_addrof(request->mountWithProcess)));\n         });\n      break;\n   }\n   default:\n      IOS_ResourceReply(resourceRequest,\n                        static_cast<Error>(FSAStatus::UnsupportedCmd));\n   }\n}\n\n\nstatic Error\nfsaThreadMain(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n   StackObject<RequestOrigin> origin;\n\n   while (true) {\n      auto error = IOS_ReceiveMessage(sData->fsaMessageQueue,\n                                      message,\n                                      MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      {\n         FSADeviceHandle fsaHandle;\n         origin->titleId = request->requestData.titleId;\n         origin->processId = request->requestData.processId;\n         origin->groupId = request->requestData.groupId;\n\n         auto fsaError = fsaDeviceOpen(origin,\n                                       &fsaHandle,\n                                       request->requestData.args.open.caps);\n         if (fsaError >= FSAStatus::OK) {\n            fsaError = static_cast<FSAStatus>(fsaHandle);\n         }\n\n         IOS_ResourceReply(request, static_cast<Error>(fsaError));\n         break;\n      }\n\n      case Command::Close:\n      {\n         auto fsaError = fsaDeviceClose(request->requestData.handle, request);\n         IOS_ResourceReply(request, static_cast<Error>(fsaError));\n         break;\n      }\n\n      case Command::Ioctl:\n      {\n         fsaDeviceIoctl(request,\n                        static_cast<FSACommand>(request->requestData.args.ioctl.request),\n                        request->requestData.args.ioctl.inputBuffer,\n                        request->requestData.args.ioctl.outputBuffer);\n         break;\n      }\n\n      case Command::Ioctlv:\n      {\n         fsaDeviceIoctlv(request,\n                         static_cast<FSACommand>(request->requestData.args.ioctlv.request),\n                         request->requestData.args.ioctlv.vecs);\n         break;\n      }\n\n      default:\n         IOS_ResourceReply(request, Error::Invalid);\n      }\n   }\n}\n\nError\nstartFsaThread()\n{\n   auto error = IOS_CreateMessageQueue(phys_addrof(sData->fsaMessageBuffer),\n                                       static_cast<uint32_t>(sData->fsaMessageBuffer.size()));\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto queueId = static_cast<MessageQueueId>(error);\n   sData->fsaMessageQueue = queueId;\n\n   error = IOS_RegisterResourceManager(\"/dev/fsa\", sData->fsaMessageQueue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = IOS_AssociateResourceManager(\"/dev/fsa\", ResourcePermissionGroup::FS);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = IOS_CreateThread(fsaThreadMain,\n                            nullptr,\n                            phys_addrof(sData->fsaThreadStack) + sData->fsaThreadStack.size(),\n                            static_cast<uint32_t>(sData->fsaThreadStack.size()),\n                            78,\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sData->fsaThread = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sData->fsaThread, \"FsaThread\");\n\n   error = IOS_StartThread(sData->fsaThread);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticFsaThreadData()\n{\n   sData = allocProcessStatic<StaticFsaThreadData>();\n   sDevices.closeAll();\n}\n\n} // namespace ios::fs::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_thread.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::fs::internal\n{\n\nError\nstartFsaThread();\n\nvoid\ninitialiseStaticFsaThreadData();\n\n} // namespace ios::fs::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_fsa_types.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include \"ios_fs_enum.h\"\n\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::fs\n{\n\n/**\n * \\ingroup ios_fs\n * @{\n */\n\n#pragma pack(push, 1)\n\n\nconstexpr auto FSAPathLength = 639u;\nconstexpr auto FSAModeLength = 16u;\nconstexpr auto FSAFileNameLength = 256u;\nconstexpr auto FSAVolumeLabelLength = 128u;\nconstexpr auto FSAVolumeIdLength = 128u;\nconstexpr auto FSADevicePathLength = 16u;\nconstexpr auto FSAMountPathLength = 128u;\n\n\nusing FSADeviceHandle = int32_t;\nusing FSADirHandle = int32_t;\nusing FSAEntryNum = int32_t;\nusing FSAFileHandle = int32_t;\nusing FSAFilePosition = uint32_t;\n\n\n/**\n * Attach information.\n */\nstruct FSAAttachInfo\n{\n   UNKNOWN(0x1BC);\n};\nCHECK_SIZE(FSAAttachInfo, 0x1BC);\n\n\n/**\n * Block information.\n */\nstruct FSABlockInfo\n{\n   UNKNOWN(0x14);\n};\nCHECK_SIZE(FSABlockInfo, 0x14);\n\n\n/**\n * Device information.\n */\nstruct FSADeviceInfo\n{\n   UNKNOWN(0x28);\n};\nCHECK_SIZE(FSADeviceInfo, 0x28);\n\n\n/**\n * File System information.\n */\nstruct FSAFileSystemInfo\n{\n   UNKNOWN(0x1E);\n};\nCHECK_SIZE(FSAFileSystemInfo, 0x1E);\n\n\n/**\n * Information about a file or directory.\n */\nstruct FSAStat\n{\n   be2_val<FSAStatFlags> flags;\n   be2_val<uint32_t> permission;\n   be2_val<uint32_t> owner;\n   be2_val<uint32_t> group;\n   be2_val<uint32_t> size;\n   UNKNOWN(0xC);\n   be2_val<uint32_t> entryId;\n   be2_val<int64_t> created;\n   be2_val<int64_t> modified;\n   UNKNOWN(0x30);\n};\nCHECK_OFFSET(FSAStat, 0x00, flags);\nCHECK_OFFSET(FSAStat, 0x04, permission);\nCHECK_OFFSET(FSAStat, 0x08, owner);\nCHECK_OFFSET(FSAStat, 0x0C, group);\nCHECK_OFFSET(FSAStat, 0x10, size);\nCHECK_OFFSET(FSAStat, 0x20, entryId);\nCHECK_OFFSET(FSAStat, 0x24, created);\nCHECK_OFFSET(FSAStat, 0x2C, modified);\nCHECK_SIZE(FSAStat, 0x64);\n\n\n/**\n * Information about an item in a directory.\n */\nstruct FSADirEntry\n{\n   //! File stat.\n   be2_struct<FSAStat> stat;\n\n   //! File name.\n   be2_array<char, FSAFileNameLength> name;\n};\nCHECK_OFFSET(FSADirEntry, 0x00, stat);\nCHECK_OFFSET(FSADirEntry, 0x64, name);\nCHECK_SIZE(FSADirEntry, 0x164);\n\n\n/**\n * Volume information.\n */\nstruct FSAVolumeInfo\n{\n   be2_val<uint32_t> flags;\n   be2_val<FSAMediaState> mediaState;\n   UNKNOWN(0x4);\n   be2_val<uint32_t> unk0x0C;\n   be2_val<uint32_t> unk0x10;\n   be2_val<int32_t> unk0x14;\n   be2_val<int32_t> unk0x18;\n   UNKNOWN(0x10);\n   be2_array<char, FSAVolumeLabelLength> volumeLabel;\n   be2_array<char, FSAVolumeIdLength> volumeId;\n   be2_array<char, FSADevicePathLength> devicePath;\n   be2_array<char, FSAMountPathLength> mountPath;\n};\nCHECK_OFFSET(FSAVolumeInfo, 0x00, flags);\nCHECK_OFFSET(FSAVolumeInfo, 0x04, mediaState);\nCHECK_OFFSET(FSAVolumeInfo, 0x0C, unk0x0C);\nCHECK_OFFSET(FSAVolumeInfo, 0x10, unk0x10);\nCHECK_OFFSET(FSAVolumeInfo, 0x14, unk0x14);\nCHECK_OFFSET(FSAVolumeInfo, 0x18, unk0x18);\nCHECK_OFFSET(FSAVolumeInfo, 0x2C, volumeLabel);\nCHECK_OFFSET(FSAVolumeInfo, 0xAC, volumeId);\nCHECK_OFFSET(FSAVolumeInfo, 0x12C, devicePath);\nCHECK_OFFSET(FSAVolumeInfo, 0x13C, mountPath);\nCHECK_SIZE(FSAVolumeInfo, 0x1BC);\n\n/**\n * Process information.\n */\nstruct FSAProcessInfo\n{\n   be2_val<uint64_t> titleId;\n   be2_val<ProcessId> processId;\n   be2_val<uint32_t> groupId;\n};\nCHECK_OFFSET(FSAProcessInfo, 0x00, titleId);\nCHECK_OFFSET(FSAProcessInfo, 0x08, processId);\nCHECK_OFFSET(FSAProcessInfo, 0x0C, groupId);\nCHECK_SIZE(FSAProcessInfo, 0x10);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::fs\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_log.h",
    "content": "#pragma once\n#include <common/log.h>\n\nnamespace ios::fs::internal\n{\n\nextern Logger fsLog;\n\n} // namespace ios::fs::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_mutex.cpp",
    "content": "#include \"ios_fs_mutex.h\"\n\nnamespace ios::fs::internal\n{\n\nError\ninitMutex(phys_ptr<Mutex> mutex)\n{\n   auto error = kernel::IOS_CreateSemaphore(1, 1);\n   mutex->semaphore = static_cast<kernel::SemaphoreId>(error);\n   return error;\n}\n\nError\ndestroyMutex(phys_ptr<Mutex> mutex)\n{\n   auto semaphore = mutex->semaphore;\n   if (mutex->semaphore < 0) {\n      return Error::OK;\n   }\n\n   mutex->semaphore = static_cast<kernel::SemaphoreId>(Error::Invalid);\n   return kernel::IOS_DestroySempahore(mutex->semaphore);\n}\n\nError\nlockMutex(phys_ptr<Mutex> mutex)\n{\n   return kernel::IOS_WaitSemaphore(mutex->semaphore, FALSE);\n}\n\nError\nunlockMutex(phys_ptr<Mutex> mutex)\n{\n   return kernel::IOS_SignalSempahore(mutex->semaphore);\n}\n\n} // namespace ios::fs::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_mutex.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_semaphore.h\"\n\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::fs::internal\n{\n\nstruct Mutex\n{\n   be2_val<kernel::SemaphoreId> semaphore;\n};\nCHECK_OFFSET(Mutex, 0x0, semaphore);\nCHECK_SIZE(Mutex, 0x4);\n\nError\ninitMutex(phys_ptr<Mutex> mutex);\n\nError\ndestroyMutex(phys_ptr<Mutex> mutex);\n\nError\nlockMutex(phys_ptr<Mutex> mutex);\n\nError\nunlockMutex(phys_ptr<Mutex> mutex);\n\n} // namespace ios::fs::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_service_thread.cpp",
    "content": "#include \"ios_fs_service_thread.h\"\n\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include \"ios/mcp/ios_mcp_ipc.h\"\n\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_handlemanager.h\"\n#include \"ios/ios_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n\nusing namespace ios::kernel;\nusing namespace ios::mcp;\n\nnamespace ios::fs::internal\n{\n\nconstexpr auto ServiceThreadStackSize = 0x4000u;\nconstexpr auto ServiceThreadPriority = 85u;\n\nstruct StaticServiceThreadData\n{\n   be2_val<ThreadId> threadId;\n   be2_array<uint8_t, ServiceThreadStackSize> threadStack;\n\n   be2_val<MessageQueueId> messageQueue;\n   be2_array<Message, 0x160> messageBuffer;\n};\n\nstatic phys_ptr<StaticServiceThreadData>\nsData = nullptr;\n\nstruct ServiceDevice\n{\n   std::string_view name;\n   bool open = false;\n   bool resumed = false;\n};\n\nstatic std::array<ServiceDevice, 16>\nsServiceDevices =\n{\n   ServiceDevice { \"/dev/df\" },\n   ServiceDevice { \"/dev/atfs\" },\n   ServiceDevice { \"/dev/isfs\" },\n   ServiceDevice { \"/dev/wfs\" },\n   ServiceDevice { \"/dev/pcfs\" },\n   ServiceDevice { \"/dev/rbfs\" },\n   ServiceDevice { \"/dev/fat\" },\n   ServiceDevice { \"/dev/fla\" },\n   ServiceDevice { \"/dev/ums\" },\n   ServiceDevice { \"/dev/ahcimgr\" },\n   ServiceDevice { \"/dev/shdd\" },\n   ServiceDevice { \"/dev/md\" },\n   ServiceDevice { \"/dev/scfm\" },\n   ServiceDevice { \"/dev/mmc\" },\n   ServiceDevice { \"/dev/timetrace\" },\n   ServiceDevice { \"/dev/tcp_pcfs\" },\n};\n\nstatic Error\nserviceThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n\n   auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer),\n                                       static_cast<uint32_t>(sData->messageBuffer.size()));\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto queueId = static_cast<MessageQueueId>(error);\n   sData->messageQueue = queueId;\n\n   for (auto &device : sServiceDevices) {\n      error = MCP_RegisterResourceManager(device.name, sData->messageQueue);\n      if (error >= Error::OK) {\n         IOS_AssociateResourceManager(device.name, ResourcePermissionGroup::FS);\n      }\n   }\n\n   while (true) {\n      error = IOS_ReceiveMessage(sData->messageQueue,\n                                 message,\n                                 MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      {\n         error = Error::Invalid;\n\n         for (auto i = 0u; i < sServiceDevices.size(); ++i) {\n            auto &device = sServiceDevices[i];\n            if (device.name.compare(request->requestData.args.open.name.get()) == 0) {\n               device.open = true;\n               error = static_cast<Error>(i);\n               break;\n            }\n         }\n\n         IOS_ResourceReply(request, static_cast<Error>(error));\n         break;\n      }\n\n      case Command::Close:\n      {\n         auto deviceIdx = request->requestData.handle;\n         if (deviceIdx < 0 || deviceIdx >= sServiceDevices.size()) {\n            error = Error::Invalid;\n         } else {\n            sServiceDevices[deviceIdx].open = false;\n         }\n\n         IOS_ResourceReply(request, static_cast<Error>(error));\n         break;\n      }\n\n      case Command::Resume:\n      {\n         auto deviceIdx = request->requestData.handle;\n         if (deviceIdx < 0 || deviceIdx >= sServiceDevices.size()) {\n            error = Error::Invalid;\n         } else {\n            sServiceDevices[deviceIdx].resumed = true;\n         }\n\n         IOS_ResourceReply(request, static_cast<Error>(error));\n         break;\n      }\n\n      case Command::Suspend:\n      {\n         auto deviceIdx = request->requestData.handle;\n         if (deviceIdx < 0 || deviceIdx >= sServiceDevices.size()) {\n            error = Error::Invalid;\n         } else {\n            sServiceDevices[deviceIdx].resumed = false;\n         }\n\n         IOS_ResourceReply(request, static_cast<Error>(error));\n         break;\n      }\n\n      default:\n         IOS_ResourceReply(request, Error::Invalid);\n      }\n   }\n}\n\nError\nstartServiceThread()\n{\n   auto error = IOS_CreateThread(&serviceThreadEntry,\n                                 nullptr,\n                                 phys_addrof(sData->threadStack) + sData->threadStack.size(),\n                                 static_cast<uint32_t>(sData->threadStack.size()),\n                                 ServiceThreadPriority,\n                                 ThreadFlags::Detached);\n   if (error < Error::OK) {\n      return error;\n   }\n   sData->threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sData->threadId, \"FsServiceThread\");\n\n   error = IOS_StartThread(sData->threadId);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticServiceThreadData()\n{\n   sData = allocProcessStatic<StaticServiceThreadData>();\n   for (auto &device : sServiceDevices) {\n      device.open = false;\n      device.resumed = false;\n   }\n}\n\n} // namespace ios::fs::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/fs/ios_fs_service_thread.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::fs::internal\n{\n\nError\nstartServiceThread();\n\nvoid\ninitialiseStaticServiceThreadData();\n\n} // namespace ios::fs::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios.cpp",
    "content": "#include \"ios.h\"\n#include \"ios_alarm_thread.h\"\n#include \"ios_network_thread.h\"\n#include \"ios_worker_thread.h\"\n#include \"ios/kernel/ios_kernel.h\"\n#include \"vfs/vfs_virtual_device.h\"\n\n#include <memory>\n\nnamespace ios\n{\n\nstatic std::shared_ptr<vfs::VirtualDevice> sFileSystem;\n\nvoid\nstart()\n{\n   internal::startAlarmThread();\n   internal::startNetworkTaskThread();\n   internal::startWorkerThread();\n   kernel::start();\n}\n\nvoid\njoin()\n{\n   kernel::join();\n   internal::stopWorkerThread();\n   internal::stopNetworkTaskThread();\n   internal::stopAlarmThread();\n}\n\nvoid\nstop()\n{\n   kernel::stop();\n   internal::stopWorkerThread();\n   internal::stopNetworkTaskThread();\n   internal::stopAlarmThread();\n}\n\nvoid\nsetFileSystem(std::shared_ptr<vfs::VirtualDevice> root)\n{\n   sFileSystem = std::move(root);\n}\n\nstd::shared_ptr<vfs::VirtualDevice>\ngetFileSystem()\n{\n   return sFileSystem;\n}\n\n} // namespace ios\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios.h",
    "content": "#pragma once\n#include <memory>\n\nnamespace vfs\n{\nclass VirtualDevice;\n}\n\nnamespace ios\n{\n\nvoid\nstart();\n\nvoid\njoin();\n\nvoid\nstop();\n\nvoid\nsetFileSystem(std::shared_ptr<vfs::VirtualDevice> fs);\n\nstd::shared_ptr<vfs::VirtualDevice>\ngetFileSystem();\n\n} // namespace ios\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_alarm_thread.cpp",
    "content": "#include \"ios_alarm_thread.h\"\n#include \"ios/kernel/ios_kernel_hardware.h\"\n\n#include <atomic>\n#include <condition_variable>\n#include <functional>\n#include <mutex>\n#include <thread>\n\nnamespace ios::internal\n{\n\nstatic std::thread\nsAlarmThread;\n\nstatic std::mutex\nsAlarmMutex;\n\nstatic std::condition_variable\nsAlarmCondition;\n\nstatic std::atomic_bool\nsAlarmThreadRunning = false;\n\nstatic std::chrono::steady_clock::time_point\nsNextAlarm = std::chrono::steady_clock::time_point::max();\n\nstatic void\nalarmThread()\n{\n   while (sAlarmThreadRunning.load()) {\n      std::unique_lock<std::mutex> lock { sAlarmMutex };\n      auto now = std::chrono::steady_clock::now();\n\n      if (now >= sNextAlarm) {\n         sNextAlarm = std::chrono::steady_clock::time_point::max();\n         lock.unlock();\n         kernel::internal::setInterruptAhbAll(kernel::AHBALL::get(0).Timer(true));\n         lock.lock();\n      }\n\n      if (sNextAlarm != std::chrono::steady_clock::time_point::max()) {\n         sAlarmCondition.wait_until(lock, sNextAlarm);\n      } else {\n         sAlarmCondition.wait(lock);\n      }\n   }\n}\n\nvoid\nstartAlarmThread()\n{\n   if (!sAlarmThreadRunning.load()) {\n      sAlarmThreadRunning.store(true);\n      sAlarmThread = std::thread { alarmThread };\n   }\n}\n\nvoid\nstopAlarmThread()\n{\n   if (sAlarmThreadRunning.load()) {\n      sAlarmThreadRunning.store(false);\n      sAlarmCondition.notify_all();\n      sAlarmThread.join();\n   }\n}\n\nvoid\nsetNextAlarm(std::chrono::steady_clock::time_point time)\n{\n   std::unique_lock<std::mutex> lock { sAlarmMutex };\n   sNextAlarm = time;\n   sAlarmCondition.notify_all();\n}\n\n} // namespace ios::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_alarm_thread.h",
    "content": "#pragma once\n#include <chrono>\n\nnamespace ios::internal\n{\n\nvoid\nstartAlarmThread();\n\nvoid\nstopAlarmThread();\n\nvoid\nsetNextAlarm(std::chrono::steady_clock::time_point time);\n\n} // namespace ios::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_device.h",
    "content": "#pragma once\n#include \"ios_enum.h\"\n#include \"ios_ipc.h\"\n#include \"kernel/ios_kernel_resourcemanager.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace ios\n{\n\nclass Device\n{\npublic:\n   virtual ~Device() = 0;\n\n   virtual Error\n   read(phys_ptr<kernel::ResourceRequest> resourceRequest,\n        phys_ptr<void> buffer,\n        uint32_t length) = 0;\n\n   virtual Error\n   write(phys_ptr<kernel::ResourceRequest> resourceRequest,\n         phys_ptr<const void> buffer,\n         uint32_t length) = 0;\n\n   virtual Error\n   seek(phys_ptr<kernel::ResourceRequest> resourceRequest,\n        uint32_t offset,\n        SeekOrigin origin) = 0;\n\n   virtual Error\n   ioctl(phys_ptr<kernel::ResourceRequest> resourceRequest,\n         uint32_t ioctlRequest,\n         phys_ptr<void> inputBuffer,\n         uint32_t inputLength,\n         phys_ptr<void> outputBuffer,\n         uint32_t outputLength) = 0;\n\n   virtual Error\n   ioctlv(phys_ptr<kernel::ResourceRequest> resourceRequest,\n          uint32_t ioctlRequest,\n          uint32_t numVecsIn,\n          uint32_t numVecsOut,\n          phys_ptr<IoctlVec> vecs) = 0;\n};\n\n} // namespace ios\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_enum.h",
    "content": "#ifndef IOS_ENUM_H\n#define IOS_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\n\nFLAGS_BEG(CoreInterruptFlags, uint32_t)\n   FLAGS_VALUE(Shutdown,            1 << 0)\n   FLAGS_VALUE(Scheduler,           1 << 1)\nFLAGS_END(CoreInterruptFlags)\n\nFLAGS_BEG(InterruptFlags, uint32_t)\n   FLAGS_VALUE(Ahb,                 1 << 0)\nFLAGS_END(InterruptFlags)\n\nENUM_BEG(Command, int32_t)\n   ENUM_VALUE(Invalid,              0)\n   ENUM_VALUE(Open,                 1)\n   ENUM_VALUE(Close,                2)\n   ENUM_VALUE(Read,                 3)\n   ENUM_VALUE(Write,                4)\n   ENUM_VALUE(Seek,                 5)\n   ENUM_VALUE(Ioctl,                6)\n   ENUM_VALUE(Ioctlv,               7)\n   ENUM_VALUE(Reply,                8)\n   ENUM_VALUE(IpcMsg0,              9)\n   ENUM_VALUE(IpcMsg1,              10)\n   ENUM_VALUE(IpcMsg2,              11)\n   ENUM_VALUE(Suspend,              12)\n   ENUM_VALUE(Resume,               13)\n   ENUM_VALUE(SvcMsg,               14)\nENUM_END(Command)\n\nENUM_BEG(CpuId, uint32_t)\n   ENUM_VALUE(ARM,                  0)\n   ENUM_VALUE(PPC0,                 1)\n   ENUM_VALUE(PPC1,                 2)\n   ENUM_VALUE(PPC2,                 3)\nENUM_END(CpuId)\n\nENUM_BEG(ErrorCategory, uint32_t)\n   ENUM_VALUE(Kernel,               0)\n   ENUM_VALUE(FSA,                  3)\n   ENUM_VALUE(MCP,                  4)\n   ENUM_VALUE(Unknown7,             7)\n   ENUM_VALUE(Unknown8,             8)\n   ENUM_VALUE(Socket,               10)\n   ENUM_VALUE(ODM,                  14)\n   ENUM_VALUE(Unknown15,            15)\n   ENUM_VALUE(Unknown19,            19)\n   ENUM_VALUE(Unknown30,            30)\n   ENUM_VALUE(Unknown45,            45)\nENUM_END(ErrorCategory)\n\nENUM_BEG(Error, int32_t)\n   ENUM_VALUE(OK,                   0)\n   ENUM_VALUE(Access,               -1)\n   ENUM_VALUE(Exists,               -2)\n   ENUM_VALUE(Intr,                 -3)\n   ENUM_VALUE(Invalid,              -4)\n   ENUM_VALUE(Max,                  -5)\n   ENUM_VALUE(NoExists,             -6)\n   ENUM_VALUE(QEmpty,               -7)\n   ENUM_VALUE(QFull,                -8)\n   ENUM_VALUE(Unknown,              -9)\n   ENUM_VALUE(NotReady,             -10)\n   ENUM_VALUE(Ecc,                  -11)\n   ENUM_VALUE(EccCrit,              -12)\n   ENUM_VALUE(BadBlock,             -13)\n   ENUM_VALUE(InvalidObjType,       -14)\n   ENUM_VALUE(InvalidRNG,           -15)\n   ENUM_VALUE(InvalidFlag,          -16)\n   ENUM_VALUE(InvalidFormat,        -17)\n   ENUM_VALUE(InvalidVersion,       -18)\n   ENUM_VALUE(InvalidSigner,        -19)\n   ENUM_VALUE(FailCheckValue,       -20)\n   ENUM_VALUE(FailInternal,         -21)\n   ENUM_VALUE(FailAlloc,            -22)\n   ENUM_VALUE(InvalidSize,          -23)\n   ENUM_VALUE(NoLink,               -24)\n   ENUM_VALUE(ANFailed,             -25)\n   ENUM_VALUE(MaxSemCount,          -26)\n   ENUM_VALUE(SemUnavailable,       -27)\n   ENUM_VALUE(InvalidHandle,        -28)\n   ENUM_VALUE(InvalidArg,           -29)\n   ENUM_VALUE(NoResource,           -30)\n   ENUM_VALUE(Busy,                 -31)\n   ENUM_VALUE(Timeout,              -32)\n   ENUM_VALUE(Alignment,            -33)\n   ENUM_VALUE(BSP,                  -34)\n   ENUM_VALUE(DataPending,          -35)\n   ENUM_VALUE(Expired,              -36)\n   ENUM_VALUE(NoReadAccess,         -37)\n   ENUM_VALUE(NoWriteAccess,        -38)\n   ENUM_VALUE(NoReadWriteAccess,    -39)\n   ENUM_VALUE(ClientTxnLimit,       -40)\n   ENUM_VALUE(StaleHandle,          -41)\n   ENUM_VALUE(UnknownValue,         -42)\n   ENUM_VALUE(MaxKernelError,       -0x400)\nENUM_END(Error)\n\nENUM_BEG(OpenMode, int32_t)\n   ENUM_VALUE(None,                 0)\n   ENUM_VALUE(Read,                 1)\n   ENUM_VALUE(Write,                2)\n   ENUM_VALUE(ReadWrite,            Read | Write)\nENUM_END(OpenMode)\n\nENUM_BEG(ProcessId, int32_t)\n   ENUM_VALUE(Invalid,              -4)\n   ENUM_VALUE(KERNEL,               0)\n   ENUM_VALUE(MCP,                  1)\n   ENUM_VALUE(BSP,                  2)\n   ENUM_VALUE(CRYPTO,               3)\n   ENUM_VALUE(USB,                  4)\n   ENUM_VALUE(FS,                   5)\n   ENUM_VALUE(PAD,                  6)\n   ENUM_VALUE(NET,                  7)\n   ENUM_VALUE(ACP,                  8)\n   ENUM_VALUE(NSEC,                 9)\n   ENUM_VALUE(AUXIL,                10)\n   ENUM_VALUE(NIM,                  11)\n   ENUM_VALUE(FPD,                  12)\n   ENUM_VALUE(TEST,                 13)\n   ENUM_VALUE(COSKERNEL,            14)\n   ENUM_VALUE(COSROOT,              15)\n   ENUM_VALUE(COS02,                16)\n   ENUM_VALUE(COS03,                17)\n   ENUM_VALUE(COSOVERLAY,           18)\n   ENUM_VALUE(COSHBM,               19)\n   ENUM_VALUE(COSERROR,             20)\n   ENUM_VALUE(COSMASTER,            21)\n   ENUM_VALUE(Max,                  22)\nENUM_END(ProcessId)\n\nENUM_BEG(SeekOrigin, uint32_t)\n   ENUM_VALUE(Beg,                  0)\n   ENUM_VALUE(Cur,                  1)\n   ENUM_VALUE(End,                  2)\nENUM_END(SeekOrigin)\n\nENUM_BEG(SyscallId, uint32_t)\n   ENUM_VALUE(CreateThread,            0x00)\n   ENUM_VALUE(JoinThread,              0x01)\n   ENUM_VALUE(CancelThread,            0x02)\n   ENUM_VALUE(GetCurrentThreadId,      0x03)\n\n   ENUM_VALUE(GetCurrentProcessId,     0x05)\n   ENUM_VALUE(GetCurrentProcessName,   0x06)\n   ENUM_VALUE(StartThread,             0x07)\n   ENUM_VALUE(SuspendThread,           0x08)\n   ENUM_VALUE(YieldThread,             0x09)\n   ENUM_VALUE(GetThreadPriority,       0x0A)\n   ENUM_VALUE(SetThreadPriority,       0x0B)\n   ENUM_VALUE(CreateMessageQueue,      0x0C)\n   ENUM_VALUE(DestroyMessageQueue,     0x0D)\n   ENUM_VALUE(SendMessage,             0x0E)\n   ENUM_VALUE(JamMessage,              0x0F)\n   ENUM_VALUE(ReceiveMessage,          0x10)\n   ENUM_VALUE(HandleEvent,             0x11)\n   ENUM_VALUE(UnhandleEvent,           0x12)\n   ENUM_VALUE(CreateTimer,             0x13)\n   ENUM_VALUE(RestartTimer,            0x14)\n   ENUM_VALUE(StopTimer,               0x15)\n   ENUM_VALUE(DestroyTimer,            0x16)\n\n   ENUM_VALUE(GetUpTimeStruct,         0x19)\n   ENUM_VALUE(GetUpTime64,             0x1A)\n\n   ENUM_VALUE(GetAbsTimeCalendar,      0x1C)\n   ENUM_VALUE(GetAbsTime64,            0x1D)\n   ENUM_VALUE(GetAbsTimeStruct,        0x1E)\n\n   ENUM_VALUE(CreateLocalProcessHeap,  0x24)\n   ENUM_VALUE(CreateCrossProcessHeap,  0x25)\n\n   ENUM_VALUE(Alloc,                   0x27)\n   ENUM_VALUE(AllocAligned,            0x28)\n   ENUM_VALUE(Free,                    0x29)\n   ENUM_VALUE(FreeAndZero,             0x2A)\n   ENUM_VALUE(Realloc,                 0x2B)\n   ENUM_VALUE(RegisterResourceManager, 0x2C)\n\n   ENUM_VALUE(Open,                    0x33)\n   ENUM_VALUE(Close,                   0x34)\n   ENUM_VALUE(Read,                    0x35)\n   ENUM_VALUE(Write,                   0x36)\n   ENUM_VALUE(Seek,                    0x37)\n   ENUM_VALUE(Ioctl,                   0x38)\n   ENUM_VALUE(Ioctlv,                  0x39)\n   ENUM_VALUE(OpenAsync,               0x3A)\n   ENUM_VALUE(CloseAsync,              0x3B)\n   ENUM_VALUE(ReadAsync,               0x3C)\n   ENUM_VALUE(WriteAsync,              0x3D)\n   ENUM_VALUE(SeekAsync,               0x3E)\n   ENUM_VALUE(IoctlAsync,              0x3F)\n   ENUM_VALUE(IoctlvAsync,             0x40)\n\n   ENUM_VALUE(ResourceReply,           0x49)\n\n   ENUM_VALUE(ClearAndEnable,          0x50)\n   ENUM_VALUE(InvalidateDCache,        0x51)\nENUM_END(SyscallId)\n\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_enum_string.cpp",
    "content": "#include \"ios_enum_string.h\"\n\n#undef IOS_ENUM_H\n#include <common/enum_string_define.inl>\n#include \"ios_enum.h\"\n#include <common/enum_end.inl>\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_enum_string.h",
    "content": "#pragma once\n#include <cstdint>\n#include <string>\n#include \"ios_enum.h\"\n\n#undef IOS_ENUM_H\n#include <common/enum_string_declare.inl>\n#include \"ios_enum.h\"\n#include <common/enum_end.inl>\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_error.h",
    "content": "#pragma once\n#include \"ios_enum.h\"\n\nnamespace ios\n{\n\nconstexpr ErrorCategory\ngetErrorCategory(Error error)\n{\n   return static_cast<ErrorCategory>(((~error) >> 16) & 0x3FF);\n}\n\nconstexpr int32_t\ngetErrorCode(Error error)\n{\n   return (error & 0x8000) ? (error | 0xFFFF0000) : (error & 0xFFFF);\n}\n\nconstexpr bool\nisKernelError(int32_t error)\n{\n   return error > Error::MaxKernelError;\n}\n\nconstexpr Error\nmakeError(ErrorCategory category, int32_t code)\n{\n   return static_cast<Error>(code >= 0 ? code : ((~category) << 16) | (code & 0xFFFF));\n}\n\n} // namespace ios\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_handlemanager.h",
    "content": "#pragma once\n#include \"ios_enum.h\"\n\n#include <array>\n#include <memory>\n#include <type_traits>\n\nnamespace ios\n{\n\ntemplate<typename ValueType, typename HandleType, size_t MaxNumHandles>\nclass HandleManager\n{\n   static_assert(MaxNumHandles < 0xFFFF);\n\n   struct Handle\n   {\n      uint16_t instanceNum = 0;\n      std::unique_ptr<ValueType> value = nullptr;\n   };\n\npublic:\n   Error\n   open()\n   {\n      auto index = -1;\n\n      for (auto i = 0u; i < mHandles.size(); ++i) {\n         if (!mHandles[i].value) {\n            index = static_cast<int>(i);\n            break;\n         }\n      }\n\n      if (index < 0) {\n         return Error::Max;\n      }\n\n      ++mHandles[index].instanceNum;\n      mHandles[index].value = std::make_unique<ValueType>();\n      return static_cast<Error>((mHandles[index].instanceNum << 16) | index);\n   }\n\n   Error\n   close(HandleType handle)\n   {\n      auto index = handle & 0xFFFF;\n      auto instanceNum = (handle >> 16) & 0xFFFF;\n\n      if constexpr (std::is_signed<HandleType>::value) {\n         if (index < 0) {\n            return Error::InvalidHandle;\n         }\n      }\n\n      if (index >= mHandles.size()) {\n         return Error::InvalidHandle;\n      }\n\n      if (!mHandles[index].value || mHandles[index].instanceNum != instanceNum) {\n         return Error::StaleHandle;\n      }\n\n      mHandles[index].value = nullptr;\n      return Error::OK;\n   }\n\n   Error\n   close(ValueType *value)\n   {\n      for (auto i = 0u; i < mHandles.size(); ++i) {\n         if (mHandles[i].value.get() == value) {\n            mHandles[i].value = nullptr;\n            return Error::OK;\n         }\n      }\n\n      return Error::InvalidHandle;\n   }\n\n   Error\n   closeAll()\n   {\n      for (auto &handle : mHandles) {\n         handle.value = nullptr;\n      }\n\n      return Error::OK;\n   }\n\n   Error\n   get(HandleType handle,\n       ValueType **outData)\n   {\n      auto index = handle & 0xFFFF;\n      auto instanceNum = (handle >> 16) & 0xFFFF;\n\n      if constexpr (std::is_signed<HandleType>::value) {\n         if (index < 0) {\n            return Error::InvalidHandle;\n         }\n      }\n\n      if (index >= mHandles.size()) {\n         return Error::InvalidHandle;\n      }\n\n      if (!mHandles[index].value || mHandles[index].instanceNum != instanceNum) {\n         return Error::StaleHandle;\n      }\n\n      *outData = mHandles[index].value.get();\n      return Error::OK;\n   }\n\nprivate:\n   std::array<Handle, MaxNumHandles> mHandles;\n};\n\n} // namespace ios\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_ipc.h",
    "content": "#pragma once\n#include \"ios_enum.h\"\n\n#include <common/structsize.h>\n#include <cstddef>\n#include <libcpu/be2_struct.h>\n\nnamespace ios\n{\n\n#pragma pack(push, 1)\n\nusing GroupId = uint32_t;\nusing Handle = int32_t;\nusing TitleId = uint64_t;\n\nstatic constexpr uint32_t IoctlVecAlign = 0x40u;\n\n/**\n * Structure used for ioctlv arguments.\n */\nstruct IoctlVec\n{\n   //! Virtual address of buffer.\n   be2_val<virt_addr> vaddr;\n\n   //! Length of buffer.\n   be2_val<uint32_t> len;\n\n   //! Physical address of buffer.\n   be2_val<phys_addr> paddr;\n};\nCHECK_OFFSET(IoctlVec, 0x00, vaddr);\nCHECK_OFFSET(IoctlVec, 0x04, len);\nCHECK_OFFSET(IoctlVec, 0x08, paddr);\nCHECK_SIZE(IoctlVec, 0x0C);\n\nstruct IpcRequestArgsOpen\n{\n   be2_phys_ptr<const char> name;\n   be2_val<uint32_t> nameLen;\n   be2_val<OpenMode> mode;\n   be2_val<uint64_t> caps;\n};\nCHECK_OFFSET(IpcRequestArgsOpen, 0x00, name);\nCHECK_OFFSET(IpcRequestArgsOpen, 0x04, nameLen);\nCHECK_OFFSET(IpcRequestArgsOpen, 0x08, mode);\nCHECK_OFFSET(IpcRequestArgsOpen, 0x0C, caps);\nCHECK_SIZE(IpcRequestArgsOpen, 0x14);\n\nstruct IpcRequestArgsClose\n{\n   be2_val<uint32_t> unkArg0;\n};\nCHECK_OFFSET(IpcRequestArgsClose, 0x00, unkArg0);\nCHECK_SIZE(IpcRequestArgsClose, 0x04);\n\nstruct IpcRequestArgsRead\n{\n   be2_phys_ptr<void> data;\n   be2_val<uint32_t> length;\n};\nCHECK_OFFSET(IpcRequestArgsRead, 0x00, data);\nCHECK_OFFSET(IpcRequestArgsRead, 0x04, length);\nCHECK_SIZE(IpcRequestArgsRead, 0x08);\n\nstruct IpcRequestArgsWrite\n{\n   be2_phys_ptr<const void> data;\n   be2_val<uint32_t> length;\n};\nCHECK_OFFSET(IpcRequestArgsWrite, 0x00, data);\nCHECK_OFFSET(IpcRequestArgsWrite, 0x04, length);\nCHECK_SIZE(IpcRequestArgsWrite, 0x08);\n\nstruct IpcRequestArgsSeek\n{\n   be2_val<uint32_t> offset;\n   be2_val<SeekOrigin> origin;\n};\nCHECK_OFFSET(IpcRequestArgsSeek, 0x00, offset);\nCHECK_OFFSET(IpcRequestArgsSeek, 0x04, origin);\nCHECK_SIZE(IpcRequestArgsSeek, 0x08);\n\nstruct IpcRequestArgsIoctl\n{\n   be2_val<uint32_t> request;\n   be2_phys_ptr<const void> inputBuffer;\n   be2_val<uint32_t> inputLength;\n   be2_phys_ptr<void> outputBuffer;\n   be2_val<uint32_t> outputLength;\n};\nCHECK_OFFSET(IpcRequestArgsIoctl, 0x00, request);\nCHECK_OFFSET(IpcRequestArgsIoctl, 0x04, inputBuffer);\nCHECK_OFFSET(IpcRequestArgsIoctl, 0x08, inputLength);\nCHECK_OFFSET(IpcRequestArgsIoctl, 0x0C, outputBuffer);\nCHECK_OFFSET(IpcRequestArgsIoctl, 0x10, outputLength);\nCHECK_SIZE(IpcRequestArgsIoctl, 0x14);\n\nstruct IpcRequestArgsIoctlv\n{\n   be2_val<uint32_t> request;\n   be2_val<uint32_t> numVecIn;\n   be2_val<uint32_t> numVecOut;\n   be2_phys_ptr<IoctlVec> vecs;\n};\nCHECK_OFFSET(IpcRequestArgsIoctlv, 0x00, request);\nCHECK_OFFSET(IpcRequestArgsIoctlv, 0x04, numVecIn);\nCHECK_OFFSET(IpcRequestArgsIoctlv, 0x08, numVecOut);\nCHECK_OFFSET(IpcRequestArgsIoctlv, 0x0C, vecs);\nCHECK_SIZE(IpcRequestArgsIoctlv, 0x10);\n\nstruct IpcRequestArgsResume\n{\n   be2_val<uint32_t> unkArg0;\n   be2_val<uint32_t> unkArg1;\n};\nCHECK_OFFSET(IpcRequestArgsResume, 0x00, unkArg0);\nCHECK_OFFSET(IpcRequestArgsResume, 0x04, unkArg1);\nCHECK_SIZE(IpcRequestArgsResume, 0x08);\n\nstruct IpcRequestArgsSuspend\n{\n   be2_val<uint32_t> unkArg0;\n   be2_val<uint32_t> unkArg1;\n};\nCHECK_OFFSET(IpcRequestArgsSuspend, 0x00, unkArg0);\nCHECK_OFFSET(IpcRequestArgsSuspend, 0x04, unkArg1);\nCHECK_SIZE(IpcRequestArgsSuspend, 0x08);\n\nstruct IpcRequestArgsSvcMsg\n{\n   be2_val<uint32_t> command;\n   be2_val<uint32_t> unkArg1;\n   be2_val<uint32_t> unkArg2;\n   be2_val<uint32_t> unkArg3;\n};\nCHECK_OFFSET(IpcRequestArgsSvcMsg, 0x00, command);\nCHECK_OFFSET(IpcRequestArgsSvcMsg, 0x04, unkArg1);\nCHECK_OFFSET(IpcRequestArgsSvcMsg, 0x08, unkArg2);\nCHECK_OFFSET(IpcRequestArgsSvcMsg, 0x0C, unkArg3);\nCHECK_SIZE(IpcRequestArgsSvcMsg, 0x10);\n\nstruct IpcRequestArgs\n{\n   IpcRequestArgs() { }\n\n   union\n   {\n      be2_struct<IpcRequestArgsOpen> open;\n      be2_struct<IpcRequestArgsClose> close;\n      be2_struct<IpcRequestArgsRead> read;\n      be2_struct<IpcRequestArgsWrite> write;\n      be2_struct<IpcRequestArgsSeek> seek;\n      be2_struct<IpcRequestArgsIoctl> ioctl;\n      be2_struct<IpcRequestArgsIoctlv> ioctlv;\n      be2_struct<IpcRequestArgsResume> resume;\n      be2_struct<IpcRequestArgsSuspend> suspend;\n      be2_struct<IpcRequestArgsSvcMsg> svcMsg;\n      be2_array<uint32_t, 5> args;\n   };\n};\nCHECK_OFFSET(IpcRequestArgs, 0x00, open);\nCHECK_OFFSET(IpcRequestArgs, 0x00, read);\nCHECK_OFFSET(IpcRequestArgs, 0x00, write);\nCHECK_OFFSET(IpcRequestArgs, 0x00, seek);\nCHECK_OFFSET(IpcRequestArgs, 0x00, ioctl);\nCHECK_OFFSET(IpcRequestArgs, 0x00, ioctlv);\nCHECK_OFFSET(IpcRequestArgs, 0x00, args);\nCHECK_SIZE(IpcRequestArgs, 0x14);\n\n\n/**\n * The actual data which is sent as an IPC request between IOSU (ARM) and\n * PowerPC cores.\n */\nstruct IpcRequest\n{\n   static constexpr auto ArgCount = 5;\n\n   //! IOS command to execute\n   be2_val<Command> command;\n\n   //! IPC command result\n   be2_val<Error> reply;\n\n   //! Handle for the IOS resource\n   be2_val<Handle> handle;\n\n   //! Flags, always 0\n   be2_val<uint32_t> flags;\n\n   //! CPU the request originated from\n   be2_val<CpuId> cpuId;\n\n   union\n   {\n      //! Cafe/PowerPC process ID the request originated from, only valid when\n      //! receiving the request in the kernel ipc thread.\n      be2_val<int32_t> clientPid;\n\n      //! IOS ProcessId the request originated from, this is the value that\n      //! should be used everywhere except from the kernel ipc thread.\n      be2_val<ProcessId> processId;\n   };\n\n   //! Title ID the request originated from\n   be2_val<TitleId> titleId;\n\n   //! Group ID\n   be2_val<GroupId> groupId;\n\n   //! IPC command args\n   be2_struct<IpcRequestArgs> args;\n};\nCHECK_OFFSET(IpcRequest, 0x00, command);\nCHECK_OFFSET(IpcRequest, 0x04, reply);\nCHECK_OFFSET(IpcRequest, 0x08, handle);\nCHECK_OFFSET(IpcRequest, 0x0C, flags);\nCHECK_OFFSET(IpcRequest, 0x10, cpuId);\nCHECK_OFFSET(IpcRequest, 0x14, clientPid);\nCHECK_OFFSET(IpcRequest, 0x14, processId);\nCHECK_OFFSET(IpcRequest, 0x18, titleId);\nCHECK_OFFSET(IpcRequest, 0x20, groupId);\nCHECK_OFFSET(IpcRequest, 0x24, args);\nCHECK_SIZE(IpcRequest, 0x38);\n\n#pragma pack(pop)\n\n} // namespace ios\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_network_thread.cpp",
    "content": "#include \"ios_network_thread.h\"\n\n#include <algorithm>\n#include <ares.h>\n#include <atomic>\n#include <memory>\n#include <mutex>\n#include <thread>\n#include <uv.h>\n#include <vector>\n\nnamespace ios::internal\n{\n\nstatic uv_loop_t sNetworkLoop = { };\nstatic uv_async_t sAsyncPendingNetworkEvent = { };\nstatic uv_timer_t sAresTimer = { };\nstatic ares_channel sAresChannel = { };\n\nstatic std::atomic<bool> sNetworkTaskThreadRunning { false };\nstatic std::thread sNetworkTaskThread;\nstatic std::mutex sPendingTasksMutex;\nstatic std::vector<NetworkTask> sPendingTasks;\n\nstruct AresTask\n{\n   ares_socket_t socket;\n   uv_poll_t poll;\n};\nstatic std::vector<std::unique_ptr<AresTask>> sAresTasks;\n\nstatic void\nuvAsyncCallback(uv_async_t *handle)\n{\n   std::vector<NetworkTask> tasks;\n   sPendingTasksMutex.lock();\n   sPendingTasks.swap(tasks);\n   sPendingTasksMutex.unlock();\n\n   for (const auto &task : tasks) {\n      task();\n   }\n\n   if (!sNetworkTaskThreadRunning) {\n      uv_stop(&sNetworkLoop);\n   }\n}\n\nstatic void\naresTimerCallback(uv_timer_t *timer)\n{\n   ares_process_fd(sAresChannel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);\n}\n\nstatic void\naresPollCallback(uv_poll_t *watcher, int status, int events)\n{\n   auto task = reinterpret_cast<AresTask *>(watcher->data);\n   uv_timer_again(&sAresTimer);\n\n   if (status < 0) {\n     ares_process_fd(sAresChannel, task->socket, task->socket);\n     return;\n   }\n\n   ares_process_fd(sAresChannel,\n                   (events & UV_READABLE) ? task->socket : ARES_SOCKET_BAD,\n                   (events & UV_WRITABLE) ? task->socket : ARES_SOCKET_BAD);\n}\n\nstatic void\naresSockstateCallback(void *data,\n                      ares_socket_t sock,\n                      int read,\n                      int write)\n{\n   auto itr = std::find_if(sAresTasks.begin(), sAresTasks.end(),\n                           [&](const auto &t) {\n                              return t->socket == sock;\n                           });\n   if (read || write) {\n      if (itr == sAresTasks.end()) {\n         auto task = std::make_unique<AresTask>();\n         uv_poll_init_socket(&sNetworkLoop, &task->poll, sock);\n         task->poll.data = task.get();\n         task->socket = sock;\n         itr = sAresTasks.emplace(sAresTasks.end(), std::move(task));\n      }\n\n      uv_poll_start(&(*itr)->poll,\n                    (read ? UV_READABLE : 0) | (write ? UV_WRITABLE : 0),\n                    aresPollCallback);\n   } else {\n      if (itr != sAresTasks.end()) {\n         uv_poll_stop(&(*itr)->poll);\n         sAresTasks.erase(itr);\n      }\n   }\n}\n\nstatic void\nnetworkTaskThread()\n{\n   ares_library_init(ARES_LIB_INIT_ALL);\n   auto options = ares_options { 0 };\n   options.flags = ARES_FLAG_NOCHECKRESP;\n   options.sock_state_cb = &aresSockstateCallback;\n   options.sock_state_cb_data = nullptr;\n   ares_init_options(&sAresChannel, &options, ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB);\n\n   uv_loop_init(&sNetworkLoop);\n\n   uv_timer_init(&sNetworkLoop, &sAresTimer);\n   uv_timer_start(&sAresTimer, aresTimerCallback, 1000, 1000);\n\n   uv_async_init(&sNetworkLoop, &sAsyncPendingNetworkEvent, &uvAsyncCallback);\n   uv_run(&sNetworkLoop, UV_RUN_DEFAULT);\n   uv_loop_close(&sNetworkLoop);\n\n   ares_destroy(sAresChannel);\n   ares_library_cleanup();\n}\n\nvoid\nstartNetworkTaskThread()\n{\n   sNetworkTaskThreadRunning = true;\n   sNetworkTaskThread = std::thread { networkTaskThread };\n}\n\nvoid\nstopNetworkTaskThread()\n{\n   if (sNetworkTaskThreadRunning) {\n      sNetworkTaskThreadRunning = false;\n      uv_async_send(&sAsyncPendingNetworkEvent);\n      sNetworkTaskThread.join();\n   }\n}\n\nuv_loop_t *\nnetworkUvLoop()\n{\n   return &sNetworkLoop;\n}\n\nares_channel\nnetworkAresChannel()\n{\n   return sAresChannel;\n}\n\nvoid\nsubmitNetworkTask(NetworkTask task)\n{\n   sPendingTasksMutex.lock();\n   sPendingTasks.emplace_back(std::move(task));\n   sPendingTasksMutex.unlock();\n\n   uv_async_send(&sAsyncPendingNetworkEvent);\n}\n\n} // ios::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_network_thread.h",
    "content": "#pragma once\n#include <ares.h>\n#include <functional>\n#include <uv.h>\n\nnamespace ios::internal\n{\n\nusing NetworkTask = std::function<void()>;\n\nvoid\nstartNetworkTaskThread();\n\nvoid\nstopNetworkTaskThread();\n\nvoid\nsubmitNetworkTask(NetworkTask task);\n\nuv_loop_t *\nnetworkUvLoop();\n\nares_channel\nnetworkAresChannel();\n\n} // namespace ios::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_stackobject.h",
    "content": "#pragma once\n#include \"kernel/ios_kernel_thread.h\"\n#include <algorithm>\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <libcpu/be2_struct.h>\n#include <memory>\n\nnamespace ios\n{\n\ntemplate<typename Type, size_t NumElements = 1>\nclass StackObject : public phys_ptr<Type>\n{\n   static constexpr auto\n   AlignedSize = align_up(static_cast<uint32_t>(sizeof(Type) * NumElements),\n                          std::max<std::size_t>(alignof(Type), 4u));\n\npublic:\n   StackObject()\n   {\n      auto thread = kernel::internal::getCurrentThread();\n      auto ptr = phys_cast<uint8_t *>(thread->userContext.stackPointer) - AlignedSize;\n      phys_ptr<Type>::mAddress = phys_cast<phys_addr>(ptr);\n      thread->userContext.stackPointer = ptr;\n\n      std::uninitialized_default_construct_n(phys_ptr<Type>::get(),\n                                             NumElements);\n   }\n\n   ~StackObject()\n   {\n      std::destroy_n(phys_ptr<Type>::get(), NumElements);\n\n      auto thread = kernel::internal::getCurrentThread();\n      auto ptr = phys_cast<uint8_t *>(thread->userContext.stackPointer);\n      decaf_check(phys_cast<phys_addr>(ptr) == phys_ptr<Type>::mAddress);\n      thread->userContext.stackPointer = ptr + AlignedSize;\n   }\n};\n\ntemplate<typename Type, size_t NumElements>\nclass StackArray : public StackObject<Type, NumElements>\n{\npublic:\n   using StackObject<Type, NumElements>::StackObject;\n\n   constexpr uint32_t size() const\n   {\n      return NumElements;\n   }\n\n   constexpr auto &operator[](std::size_t index)\n   {\n      return phys_ptr<Type>::get()[index];\n   }\n\n   constexpr const auto &operator[](std::size_t index) const\n   {\n      return phys_ptr<Type>::get()[index];\n   }\n};\n\n} // namespace ios\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_worker_thread.cpp",
    "content": "#include \"ios_worker_thread.h\"\n\n#include <atomic>\n#include <condition_variable>\n#include <functional>\n#include <mutex>\n#include <queue>\n#include <thread>\n\nnamespace ios::internal\n{\n\nusing WorkerTask = std::function<void()>;\n\nstatic std::thread\nsWorkerThread;\n\nstatic std::atomic<bool>\nsWorkerThreadRunning { false };\n\nstatic std::condition_variable\nsWorkerThreadConditionVariable;\n\nstatic std::mutex\nsWorkerThreadMutex;\n\nstatic std::queue<WorkerTask>\nsWorkerThreadTasks;\n\nstatic void\niosWorkerThread()\n{\n   while (sWorkerThreadRunning) {\n      auto lock = std::unique_lock { sWorkerThreadMutex };\n\n      if (sWorkerThreadTasks.empty()) {\n         sWorkerThreadConditionVariable.wait(lock);\n         if (!sWorkerThreadRunning) {\n            break;\n         }\n      }\n\n      auto task = std::move(sWorkerThreadTasks.front());\n      sWorkerThreadTasks.pop();\n      lock.unlock();\n\n      task();\n   }\n}\n\nvoid\nstartWorkerThread()\n{\n   if (!sWorkerThreadRunning) {\n      sWorkerThreadRunning = true;\n      sWorkerThread = std::thread { iosWorkerThread };\n   }\n}\n\nvoid\nstopWorkerThread()\n{\n   if (sWorkerThreadRunning) {\n      sWorkerThreadRunning = false;\n      sWorkerThreadConditionVariable.notify_all();\n      sWorkerThread.join();\n      sWorkerThreadTasks = {};\n   }\n}\n\nvoid\nsubmitWorkerTask(WorkerTask task)\n{\n   auto lock = std::unique_lock { sWorkerThreadMutex };\n   sWorkerThreadTasks.push(std::move(task));\n   sWorkerThreadConditionVariable.notify_all();\n}\n\n} // namespace ios::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/ios_worker_thread.h",
    "content": "#include <functional>\n\nnamespace ios::internal\n{\n\nusing WorkerTask = std::function<void()>;\n\nvoid\nstartWorkerThread();\n\nvoid\nstopWorkerThread();\n\nvoid\nsubmitWorkerTask(WorkerTask task);\n\n} // namespace ios::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel.cpp",
    "content": "#include \"ios_kernel.h\"\n#include \"ios_kernel_debug.h\"\n#include \"ios_kernel_hardware.h\"\n#include \"ios_kernel_heap.h\"\n#include \"ios_kernel_ipc_thread.h\"\n#include \"ios_kernel_otp.h\"\n#include \"ios_kernel_process.h\"\n#include \"ios_kernel_resourcemanager.h\"\n#include \"ios_kernel_semaphore.h\"\n#include \"ios_kernel_scheduler.h\"\n#include \"ios_kernel_thread.h\"\n#include \"ios_kernel_timer.h\"\n\n#include \"ios/acp/ios_acp.h\"\n#include \"ios/auxil/ios_auxil.h\"\n#include \"ios/bsp/ios_bsp.h\"\n#include \"ios/crypto/ios_crypto.h\"\n#include \"ios/fpd/ios_fpd.h\"\n#include \"ios/fs/ios_fs.h\"\n#include \"ios/mcp/ios_mcp.h\"\n#include \"ios/net/ios_net.h\"\n#include \"ios/nim/ios_nim.h\"\n#include \"ios/nsec/ios_nsec.h\"\n#include \"ios/pad/ios_pad.h\"\n#include \"ios/test/ios_test.h\"\n#include \"ios/usb/ios_usb.h\"\n\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <common/log.h>\n#include <libcpu/cpu_formatters.h>\n#include <functional>\n\nnamespace ios::kernel\n{\n\nusing namespace std::chrono_literals;\nconstexpr auto RootThreadNumMessages = 1u;\nconstexpr auto RootThreadStackSize = 0x2000u;\nconstexpr auto RootThreadPriority = 126u;\n\nstruct RootThreadMessage\n{\n   be2_val<RootThreadCommand> command;\n   be2_array<uint32_t, 13> args;\n};\nCHECK_SIZE(RootThreadMessage, 0x38);\n\nstruct StaticKernelData\n{\n   be2_val<ThreadId> threadId;\n   be2_val<TimerId> timerId;\n   be2_val<MessageQueueId> messageQueueId;\n   be2_array<Message, RootThreadNumMessages> messageBuffer;\n   be2_array<uint8_t, RootThreadStackSize> threadStack;\n   be2_struct<RootThreadMessage> rootTimerMessage;\n   be2_struct<RootThreadMessage> sysprotEventMessage;\n};\n\nstatic phys_ptr<StaticKernelData>\nsData;\n\nstruct ProcessInfo\n{\n   ProcessId pid;\n   ThreadEntryFn entry;\n   int32_t priority;\n   uint32_t stackSize;\n   uint32_t memPermMask;\n   const char *threadName;\n};\n\nstatic ProcessInfo sProcessBootInfo[] = {\n   { ProcessId::MCP,    mcp::processEntryPoint,       124, 0x2000,   0xC0030, \"McpProcess\" },\n   { ProcessId::BSP,    bsp::processEntryPoint,       125, 0x1000,  0x100000, \"BspProcess\" },\n   { ProcessId::CRYPTO, crypto::processEntryPoint,    123, 0x1000,   0xC0030, \"CryptoProcess\" },\n   { ProcessId::USB,    usb::processEntryPoint,       107, 0x4000,   0x38600, \"UsbProcess\" },\n   { ProcessId::FS,     fs::processEntryPoint,         85, 0x4000,  0x1C5870, \"FsProcess\" },\n   { ProcessId::PAD,    pad::processEntryPoint,       117, 0x2000,    0x8180, \"PadProcess\" },\n   { ProcessId::NET,    net::processEntryPoint,        80, 0x4000,    0x2000, \"NetProcess\" },\n   { ProcessId::NIM,    nim::processEntryPoint,        50, 0x4000,         0, \"NimProcess\" },\n   { ProcessId::NSEC,   nsec::processEntryPoint,       50, 0x1000,         0, \"NsecProcess\" },\n   { ProcessId::FPD,    fpd::processEntryPoint,        50, 0x4000,         0, \"FpdProcess\" },\n   { ProcessId::ACP,    acp::processEntryPoint,        50, 0x4000,         0, \"AcpProcess\" },\n   { ProcessId::AUXIL,  auxil::processEntryPoint,      70, 0x4000,         0, \"AuxilProcess\" },\n   { ProcessId::TEST,   test::processEntryPoint,       75, 0x2000,         0, \"TestProcess\" },\n};\n\nstatic Error\nstartProcesses(bool bootOnlyBSP)\n{\n   for (auto &info : sProcessBootInfo) {\n      if (bootOnlyBSP) {\n         if (info.pid != ProcessId::BSP) {\n            continue;\n         }\n      } else if (info.pid == ProcessId::BSP) {\n         continue;\n      }\n\n      auto stackPtr = allocProcessStatic(info.pid, info.stackSize, 0x10);\n      auto error = IOS_CreateThread(info.entry,\n                                    phys_cast<void *>(phys_addr { static_cast<uint32_t>(info.pid) }),\n                                    phys_cast<uint8_t *>(stackPtr) + info.stackSize,\n                                    info.stackSize,\n                                    info.priority,\n                                    ThreadFlags::AllocateTLS | ThreadFlags::Detached);\n      if (error < Error::OK) {\n         gLog->warn(\"Error creating process thread for pid {}, error = {}\", info.pid, error);\n         continue;\n      }\n\n      auto threadId = static_cast<ThreadId>(error);\n      auto thread = internal::getThread(threadId);\n      thread->pid = info.pid;\n      internal::setThreadName(threadId, info.threadName);\n\n      error = IOS_StartThread(threadId);\n      if (error < Error::OK) {\n         gLog->warn(\"Error starting process thread for pid {}, error = {}\", info.pid, error);\n         continue;\n      }\n   }\n\n   return Error::OK;\n}\n\nstatic Error\ninitialiseRootThread()\n{\n   StackObject<Message> message;\n   auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer),\n                                       static_cast<uint32_t>(sData->messageBuffer.size()));\n   if (error < Error::OK) {\n      gLog->error(\"Failed to create root thread message queue, error = {}\", error);\n      return error;\n   }\n\n   sData->messageQueueId = static_cast<MessageQueueId>(error);\n\n   error = IOS_CreateTimer(5000us,\n                           1000000us,\n                           sData->messageQueueId,\n                           makeMessage(phys_addrof(sData->rootTimerMessage)));\n   if (error < Error::OK) {\n      gLog->error(\"Failed to create root thread timer, error = {}\", error);\n      return error;\n   }\n\n   sData->timerId = static_cast<TimerId>(error);\n\n   error = IOS_ReceiveMessage(sData->messageQueueId,\n                              message,\n                              MessageFlags::None);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to receive root thread timer message, error = {}\", error);\n   }\n\n   error = IOS_HandleEvent(DeviceId::SysProt,\n                           sData->messageQueueId,\n                           makeMessage(phys_addrof(sData->sysprotEventMessage)));\n   if (error < Error::OK) {\n      gLog->error(\"Failed to register sysprot event handler, error = {}\", error);\n      return error;\n   }\n   return IOS_ClearAndEnable(DeviceId::SysProt);\n}\n\nstatic Error\nhandleTimerEvent()\n{\n   return Error::OK;\n}\n\nstatic Error\nhandleSysprotEvent()\n{\n   return Error::OK;\n}\n\nstatic Error\nkernelEntryPoint(phys_ptr<void> context)\n{\n   StackObject<Message> message;\n\n   // Start timer thread\n   internal::startTimerThread();\n\n   // Set initial process caps\n   internal::setSecurityLevel(SecurityLevel::Normal);\n   internal::setClientCapability(ProcessId::KERNEL, FeatureId { 0x7FFFFFFF }, 0xFFFFFFFFu);\n   internal::setClientCapability(ProcessId::MCP, FeatureId { 0x7FFFFFFF }, 0xFFFFFFFFu);\n   internal::setClientCapability(ProcessId::BSP, FeatureId { 0x7FFFFFFF }, 0xFFFFFFFFu);\n\n   for (auto i = +ProcessId::CRYPTO; i < NumIosProcess; ++i) {\n      internal::setClientCapability(ProcessId { i }, FeatureId { 1 }, 0xF);\n   }\n\n   // Initialise shared heap\n   auto error = IOS_CreateHeap(phys_cast<void *>(phys_addr { 0x1D000000 }),\n                               0x2B00000);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to create shared heap, error = {}\", error);\n      return error;\n   }\n\n   auto sharedHeapId = static_cast<HeapId>(error);\n   if (sharedHeapId != 1) {\n      gLog->error(\"Expected IOS kernel sharedHeapId to be 1, found {}\", sharedHeapId);\n      return Error::Invalid;\n   }\n\n   error = IOS_CreateCrossProcessHeap(0x20000);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to create cross process heap, error = {}\", error);\n      return error;\n   }\n\n   // Start the BSP process\n   error = startProcesses(true);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to start BSP process, error = {}\", error);\n      return error;\n   }\n\n   // Wait for BSP to start\n   IOS_SetThreadPriority(CurrentThread, 124);\n\n   while (!internal::bspReady()) {\n      IOS_YieldCurrentThread();\n   }\n\n   IOS_SetThreadPriority(CurrentThread, 126);\n\n   // Initialise root kernel thread\n   error = initialiseRootThread();\n   if (error < Error::OK) {\n      gLog->error(\"Failed to initialise root thread, error = {}\", error);\n      return error;\n   }\n\n   // Start the rest of the processes\n   error = startProcesses(false);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to start remaining IOS processes, error = {}\", error);\n      return error;\n   }\n\n   // Start IPC thread\n   internal::startIpcThread();\n\n   while (true) {\n      error = IOS_ReceiveMessage(sData->messageQueueId, message, MessageFlags::None);\n      if (error) {\n         return error;\n      }\n\n      auto rootThreadMessage = parseMessage<RootThreadMessage>(message);\n      switch (rootThreadMessage->command) {\n      case RootThreadCommand::Timer:\n         error = handleTimerEvent();\n         break;\n      case RootThreadCommand::SysprotEvent:\n         error = handleSysprotEvent();\n         break;\n      default:\n         gLog->warn(\"Received unexpected message on root thread, command = {}\", rootThreadMessage->command);\n      }\n   }\n}\n\nError\nstart()\n{\n   // Initialise static memory\n   internal::initialiseProcessStaticAllocators();\n\n   internal::initialiseStaticHardwareData();\n   internal::initialiseStaticHeapData();\n   internal::initialiseStaticSchedulerData();\n   internal::initialiseStaticMessageQueueData();\n   internal::initialiseStaticResourceManagerData();\n   internal::initialiseStaticSemaphoreData();\n   internal::initialiseStaticThreadData();\n   internal::initialiseStaticTimerData();\n\n   internal::initialiseOtp();\n\n   sData = allocProcessStatic<StaticKernelData>();\n   sData->rootTimerMessage.command = RootThreadCommand::Timer;\n   sData->sysprotEventMessage.command = RootThreadCommand::SysprotEvent;\n\n   // Create root kernel thread\n   auto error = IOS_CreateThread(kernelEntryPoint,\n                                 nullptr,\n                                 phys_addrof(sData->threadStack) + sData->threadStack.size(),\n                                 static_cast<uint32_t>(sData->threadStack.size()),\n                                 RootThreadPriority,\n                                 ThreadFlags::Detached);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Force start the root kernel thread. We cannot use IOS_StartThread\n   // because it reschedules and we are not running on an IOS thread.\n   auto threadId = static_cast<ThreadId>(error);\n   auto thread = internal::getThread(threadId);\n   thread->state = ThreadState::Ready;\n   internal::setThreadName(threadId, \"KernelProcess\");\n   internal::queueThread(thread);\n\n   // Send the power on interrupt\n   internal::setInterruptAhbAll(AHBALL {}.PowerButton(true));\n\n   // Start the host hardware thread\n   internal::startHardwareThread();\n   return Error::OK;\n}\n\nvoid\njoin()\n{\n   internal::joinHardwareThread();\n}\n\nvoid\nstop()\n{\n   internal::stopHardwareThread();\n}\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::kernel\n{\n\nError\nstart();\n\nvoid\njoin();\n\nvoid\nstop();\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_debug.cpp",
    "content": "#include \"ios_kernel_debug.h\"\n#include \"ios_kernel_process.h\"\n\nnamespace ios::kernel\n{\n\nstatic SecurityLevel\nsSecurityLevel = SecurityLevel::Normal;\n\nError\nIOS_SetSecurityLevel(SecurityLevel level)\n{\n   if (IOS_GetCurrentProcessId() != static_cast<Error>(ProcessId::MCP)) {\n      return Error::Access;\n   }\n\n   sSecurityLevel = level;\n   return Error::OK;\n}\n\nSecurityLevel\nIOS_GetSecurityLevel()\n{\n   return sSecurityLevel;\n}\n\nnamespace internal\n{\n\nvoid\nsetSecurityLevel(SecurityLevel level)\n{\n   sSecurityLevel = level;\n}\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_debug.h",
    "content": "#pragma once\n#include \"ios_kernel_enum.h\"\n#include \"ios/ios_enum.h\"\n\nnamespace ios::kernel\n{\n\nError\nIOS_SetSecurityLevel(SecurityLevel level);\n\nSecurityLevel\nIOS_GetSecurityLevel();\n\nnamespace internal\n{\n\nvoid\nsetSecurityLevel(SecurityLevel level);\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_enum.h",
    "content": "#ifndef IOS_KERNEL_ENUM_H\n#define IOS_KERNEL_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\n\nENUM_NAMESPACE_ENTER(kernel)\n\nENUM_BEG(DeviceId, uint32_t)\n   ENUM_VALUE(Timer,                0)\n   ENUM_VALUE(NandInterfaceAHBALL,  1)\n   ENUM_VALUE(AesEngineAHBALL,      2)\n   ENUM_VALUE(Sha1EngineAHBALL,     3)\n   ENUM_VALUE(UsbEhci,              4)\n   ENUM_VALUE(UsbOhci0,             5)\n   ENUM_VALUE(UsbOhci1,             6)\n   ENUM_VALUE(SdHostController,     7)\n   ENUM_VALUE(Wireless80211,        8)\n   ENUM_VALUE(SysEvent,             9)\n   ENUM_VALUE(SysProt,              10)\n   ENUM_VALUE(PowerButton,          15)\n   ENUM_VALUE(DriveInterface,       16)\n   ENUM_VALUE(ExiRtc,               18)\n   ENUM_VALUE(Sata,                 26)\n   ENUM_VALUE(IpcStarbuckCompat,    29)\n   ENUM_VALUE(Unknown30,            30)\n   ENUM_VALUE(Unknown31,            31)\n   ENUM_VALUE(Unknown32,            32)\n   ENUM_VALUE(Unknown33,            33)\n   ENUM_VALUE(Drh,                  34)\n   ENUM_VALUE(Unknown35,            35)\n   ENUM_VALUE(Unknown36,            36)\n   ENUM_VALUE(Unknown37,            37)\n   ENUM_VALUE(AesEngineAHBLT,       38)\n   ENUM_VALUE(Sha1EngineAHBLT,      39)\n   ENUM_VALUE(Unknown40,            40)\n   ENUM_VALUE(Unknown41,            41)\n   ENUM_VALUE(Unknown42,            42)\n   ENUM_VALUE(I2CEspresso,          43)\n   ENUM_VALUE(I2CStarbuck,          44)\n   ENUM_VALUE(IpcStarbuckCore2,     45)\n   ENUM_VALUE(IpcStarbuckCore1,     46)\n   ENUM_VALUE(IpcStarbuckCore0,     47)\nENUM_END(DeviceId)\n\nFLAGS_BEG(HeapFlags, uint32_t)\n   FLAGS_VALUE(LocalProcessHeap,    1 << 0)\n   FLAGS_VALUE(CrossProcessHeap,    1 << 1)\nFLAGS_END(HeapFlags)\n\nENUM_BEG(HeapBlockState, uint32_t)\n   ENUM_VALUE(Free,                 0xBABE0000)\n   ENUM_VALUE(Allocated,            0xBABE0001)\n   ENUM_VALUE(InnerBlock,           0xBABE0002)\nENUM_END(HeapBlockState)\n\nFLAGS_BEG(MessageFlags, uint8_t)\n   FLAGS_VALUE(None,                      0)\n   FLAGS_VALUE(NonBlocking,               1)\nFLAGS_END(MessageFlags)\n\nFLAGS_BEG(MessageQueueFlags, uint8_t)\n   FLAGS_VALUE(None,                      0)\n   FLAGS_VALUE(RegisteredEventHandler,    1)\nFLAGS_END(MessageQueueFlags)\n\nENUM_BEG(OtpFieldIndex, uint32_t)\n   ENUM_VALUE(WiiBoot1Sha1Hash,           0x1)\n   ENUM_VALUE(WiiCommonKey,               0x5)\n   ENUM_VALUE(WiiNgId,                    0x9)\n   ENUM_VALUE(WiiNgPrivateKey,            0xA)\n   ENUM_VALUE(WiiNandHmac,                0x11)\n   ENUM_VALUE(WiiNandKey,                 0x16)\n   ENUM_VALUE(WiiRngKey,                  0x1A)\n   ENUM_VALUE(SecurityLevel,              0x20)\n   ENUM_VALUE(StarbuckAncastKey,          0x24)\n   ENUM_VALUE(SeepromKey,                 0x28)\n   ENUM_VALUE(VwiiCommonKey,              0x34)\n   ENUM_VALUE(CommonKey,                  0x38)\n   ENUM_VALUE(SslRsaKey,                  0x48)\n   ENUM_VALUE(UsbStorageKey,              0x4C)\n   ENUM_VALUE(Unknown0x50,                0x50)\n   ENUM_VALUE(XorKey,                     0x54)\n   ENUM_VALUE(RngKey,                     0x58)\n   ENUM_VALUE(SlcKey,                     0x5C)\n   ENUM_VALUE(MlcKey,                     0x60)\n   ENUM_VALUE(ShddKey,                    0x64)\n   ENUM_VALUE(DrhWlanKey,                 0x68)\n   ENUM_VALUE(SlcHmac,                    0x78)\n   ENUM_VALUE(NgId,                       0x87)\n   ENUM_VALUE(NgPrivateKey,               0x88)\n   ENUM_VALUE(NssPrivateKey,              0x90)\n   ENUM_VALUE(OtpRngSeed,                 0x98)\n   ENUM_VALUE(RootCertMsId,               0xA0)\n   ENUM_VALUE(RootCertCaId,               0xA1)\n   ENUM_VALUE(RootCertNgId,               0xC2)\n   ENUM_VALUE(RootCertNgSig,              0xC3)\n   ENUM_VALUE(WiiKoreanKey,               0xD2)\n   ENUM_VALUE(WiiNssPrivateKey,           0xD8)\n   ENUM_VALUE(Boot1Key,                   0xE8)\n   ENUM_VALUE(OtpVersion,                 0xF9)\n   ENUM_VALUE(OtpDateCode,                0xFA)\n   ENUM_VALUE(OtpVersionName,             0xFC)\n   ENUM_VALUE(JtagStatus,                 0xFF)\nENUM_END(OtpFieldIndex)\n\nENUM_BEG(OtpFieldSize, uint32_t)\n   ENUM_VALUE(WiiBoot1Sha1Hash,           0x14)\n   ENUM_VALUE(WiiCommonKey,               0x10)\n   ENUM_VALUE(WiiNgId,                    0x4)\n   ENUM_VALUE(WiiNgPrivateKey,            0x1C)\n   ENUM_VALUE(WiiNandHmac,                0x14)\n   ENUM_VALUE(WiiNandKey,                 0x10)\n   ENUM_VALUE(WiiRngKey,                  0x10)\n   ENUM_VALUE(SecurityLevel,              0x4)\n   ENUM_VALUE(StarbuckAncastKey,          0x10)\n   ENUM_VALUE(SeepromKey,                 0x10)\n   ENUM_VALUE(VwiiCommonKey,              0x10)\n   ENUM_VALUE(CommonKey,                  0x10)\n   ENUM_VALUE(SslRsaKey,                  0x10)\n   ENUM_VALUE(UsbStorageKey,              0x10)\n   ENUM_VALUE(Unknown0x50,                0x10)\n   ENUM_VALUE(XorKey,                     0x10)\n   ENUM_VALUE(RngKey,                     0x10)\n   ENUM_VALUE(SlcKey,                     0x10)\n   ENUM_VALUE(MlcKey,                     0x10)\n   ENUM_VALUE(ShddKey,                    0x10)\n   ENUM_VALUE(DrhWlanKey,                 0x10)\n   ENUM_VALUE(SlcHmac,                    0x14)\n   ENUM_VALUE(NgId,                       0x4)\n   ENUM_VALUE(NgPrivateKey,               0x20)\n   ENUM_VALUE(NssPrivateKey,              0x20)\n   ENUM_VALUE(OtpRngSeed,                 0x10)\n   ENUM_VALUE(RootCertMsId,               0x4)\n   ENUM_VALUE(RootCertCaId,               0x4)\n   ENUM_VALUE(RootCertNgId,               0x4)\n   ENUM_VALUE(RootCertNgSig,              0x3C)\n   ENUM_VALUE(WiiKoreanKey,               0x10)\n   ENUM_VALUE(WiiNssPrivateKey,           0x20)\n   ENUM_VALUE(Boot1Key,                   0x10)\n   ENUM_VALUE(OtpVersion,                 0x4)\n   ENUM_VALUE(OtpDateCode,                0x8)\n   ENUM_VALUE(OtpVersionName,             0x8)\n   ENUM_VALUE(JtagStatus,                 0x4)\nENUM_END(OtpFieldSize)\n\nENUM_BEG(RootThreadCommand, int32_t)\n   ENUM_VALUE(Timer,                0x100)\n   ENUM_VALUE(SysprotEvent,         0x101)\nENUM_END(RootThreadCommand)\n\nENUM_BEG(ResourceHandleState, int32_t)\n   ENUM_VALUE(Free,                 0x00)\n   ENUM_VALUE(Opening,              0x11)\n   ENUM_VALUE(Open,                 0x22)\n   ENUM_VALUE(Closed,               0x33)\nENUM_END(ResourceHandleState)\n\nENUM_BEG(ResourcePermissionGroup, int32_t)\n   ENUM_VALUE(None,                 0)\n   ENUM_VALUE(BSP,                  1)\n   ENUM_VALUE(DK,                   3)\n   ENUM_VALUE(FS,                   11)\n   ENUM_VALUE(UHS,                  12)\n   ENUM_VALUE(MCP,                  13)\n   ENUM_VALUE(NIM,                  14)\n   ENUM_VALUE(ACT,                  15)\n   ENUM_VALUE(FPD,                  16)\n   ENUM_VALUE(BOSS,                 17)\n   ENUM_VALUE(ACP,                  18)\n   ENUM_VALUE(PDM,                  19)\n   ENUM_VALUE(AC,                   20)\n   ENUM_VALUE(NDM,                  21)\n   ENUM_VALUE(NSEC,                 22)\n   ENUM_VALUE(PAD,                  1000)\n   ENUM_VALUE(All,                  0x7FFFFFFF)\nENUM_END(ResourcePermissionGroup)\n\nENUM_BEG(SecurityLevel, uint32_t)\n   ENUM_VALUE(Test,                 10)\n   ENUM_VALUE(Debug,                20)\n   ENUM_VALUE(Normal,               30)\nENUM_END(SecurityLevel)\n\nFLAGS_BEG(ThreadFlags, uint32_t)\n   FLAGS_VALUE(Detached,               1 << 0)\n   FLAGS_VALUE(AllocateTLS,            1 << 1)\nFLAGS_END(ThreadFlags)\n\nENUM_BEG(ThreadState, uint32_t)\n   ENUM_VALUE(Available,               0x00)\n   ENUM_VALUE(Ready,                   0x01)\n   ENUM_VALUE(Running,                 0x02)\n   ENUM_VALUE(Stopped,                 0x03)\n   ENUM_VALUE(Waiting,                 0x04)\n   ENUM_VALUE(Dead,                    0x05)\n   ENUM_VALUE(Faulted,                 0x06)\n   ENUM_VALUE(Unknown,                 0x07)\nENUM_END(ThreadState)\n\nENUM_BEG(TimerState, uint32_t)\n   ENUM_VALUE(Free,                    0x00)\n   ENUM_VALUE(Ready,                   0x01)\n   ENUM_VALUE(Running,                 0x02)\n   ENUM_VALUE(Triggered,               0x03)\n   ENUM_VALUE(Stopped,                 0x04)\nENUM_END(TimerState)\n\nENUM_NAMESPACE_EXIT(kernel)\n\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_KERNEL_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_hardware.cpp",
    "content": "#include \"ios_kernel_hardware.h\"\n#include \"ios_kernel_messagequeue.h\"\n#include \"ios_kernel_scheduler.h\"\n#include \"ios_kernel_process.h\"\n#include \"ios_kernel_thread.h\"\n\n#include <atomic>\n#include <condition_variable>\n#include <thread>\n#include <mutex>\n\nnamespace ios::kernel\n{\n\nstruct EventHandler\n{\n   be2_phys_ptr<MessageQueue> queue;\n   be2_val<Message> message;\n   be2_val<ProcessId> pid;\n   PADDING(0x4);\n};\nCHECK_OFFSET(EventHandler, 0x00, queue);\nCHECK_OFFSET(EventHandler, 0x04, message);\nCHECK_OFFSET(EventHandler, 0x08, pid);\nCHECK_SIZE(EventHandler, 0x10);\n\nstruct StaticHardwareData\n{\n   be2_array<EventHandler, 0x30> eventHandlers;\n   be2_val<BOOL> bspReady;\n};\n\nstatic phys_ptr<StaticHardwareData>\nsHardwareData;\n\n//! Interrupt mask for ARM AHB IRQs\nstatic std::atomic<uint32_t>\nLT_INTMR_AHBALL_ARM { 0 };\n\n//! Triggered interrupts for ARM AHB IRQs\nstatic std::atomic<uint32_t>\nLT_INTSR_AHBALL_ARM { 0 };\n\n//! Interrupt mask for latte ARM AHB IRQs\nstatic std::atomic<uint32_t>\nLT_INTMR_AHBLT_ARM { 0 };\n\n//! Triggered interrupts for latte ARM AHB IRQs\nstatic std::atomic<uint32_t>\nLT_INTSR_AHBLT_ARM { 0 };\n\nstatic std::thread sHardwareThread;\nstatic std::condition_variable sHardwareConditionVariable;\nstatic std::mutex sHardwareMutex;\nstatic std::atomic<bool> sRunning;\n\n/**\n * Registers a message queue as the event handler for a device.\n *\n * Sends the given message to the message queue when an interrupt is received\n * for the given device.\n */\nError\nIOS_HandleEvent(DeviceId id,\n                MessageQueueId qid,\n                Message message)\n{\n   phys_ptr<MessageQueue> queue;\n   auto error = internal::getMessageQueueSafe(qid, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   if (id >= sHardwareData->eventHandlers.size()) {\n      return Error::Invalid;\n   }\n\n   queue->flags |= MessageQueueFlags::RegisteredEventHandler;\n\n   auto &handler = sHardwareData->eventHandlers[id];\n   handler.message = message;\n   handler.queue = queue;\n   handler.pid = internal::getCurrentProcessId();\n\n   return Error::OK;\n}\n\n\n/**\n * Unregister an event handler previously registered by IOS_HandleEvent.\n */\nError\nIOS_UnregisterEventHandler(DeviceId id)\n{\n   if (id >= sHardwareData->eventHandlers.size()) {\n      return Error::Invalid;\n   }\n\n   auto &handler = sHardwareData->eventHandlers[id];\n\n   if (handler.queue) {\n      handler.queue->flags &= ~MessageQueueFlags::RegisteredEventHandler;\n   }\n\n   std::memset(phys_addrof(handler).get(), 0, sizeof(handler));\n   return Error::OK;\n}\n\n\n/**\n * Clear and enable interrupts for given DeviceId.\n */\nError\nIOS_ClearAndEnable(DeviceId id)\n{\n   auto thread = internal::getCurrentThread();\n\n   // We don't actually clear the signalling register so we can avoid race\n   // conditions with signals coming from external threads. Instead we just\n   // enable the mask, which could lead to spurious wakeups but they are much\n   // better than not waking up at all.\n   auto enableAhbAll =\n      [](AHBALL mask) {\n         LT_INTMR_AHBALL_ARM |= mask.value;\n      };\n\n   auto enableAhbLatte =\n      [](AHBLT mask) {\n         LT_INTMR_AHBLT_ARM |= mask.value;\n      };\n\n   switch (id) {\n   case DeviceId::Timer:\n      enableAhbAll(AHBALL::get(0).Timer(true));\n      break;\n   case DeviceId::NandInterfaceAHBALL:\n      enableAhbAll(AHBALL::get(0).NandInterface(true));\n      break;\n   case DeviceId::AesEngineAHBALL:\n      decaf_check(thread->pid == ProcessId::CRYPTO);\n      enableAhbAll(AHBALL::get(0).AesEngine(true));\n      break;\n   case DeviceId::Sha1EngineAHBALL:\n      decaf_check(thread->pid == ProcessId::CRYPTO);\n      enableAhbAll(AHBALL::get(0).Sha1Engine(true));\n      break;\n   case DeviceId::UsbEhci:\n      enableAhbAll(AHBALL::get(0).UsbEhci(true));\n      break;\n   case DeviceId::UsbOhci0:\n      enableAhbAll(AHBALL::get(0).UsbOhci0(true));\n      break;\n   case DeviceId::UsbOhci1:\n      enableAhbAll(AHBALL::get(0).UsbOhci1(true));\n      break;\n   case DeviceId::SdHostController:\n      enableAhbAll(AHBALL::get(0).SdHostController(true));\n      break;\n   case DeviceId::Wireless80211:\n      enableAhbAll(AHBALL::get(0).Wireless80211(true));\n      break;\n   case 9:\n      // TODO: latte gpio int flag = 1\n      // TODO: latte gpio int mask |= 1\n      break;\n   case DeviceId::SysProt:\n      decaf_check(thread->pid == ProcessId::KERNEL);\n      enableAhbAll(AHBALL::get(0).SysProt(true));\n      break;\n   case DeviceId::PowerButton:\n      enableAhbAll(AHBALL::get(0).PowerButton(true));\n      break;\n   case DeviceId::DriveInterface:\n      enableAhbAll(AHBALL::get(0).DriveInterface(true));\n      break;\n   case DeviceId::ExiRtc:\n      enableAhbAll(AHBALL::get(0).ExiRtc(true));\n      break;\n   case DeviceId::Sata:\n      enableAhbAll(AHBALL::get(0).Sata(true));\n      break;\n   case DeviceId::IpcStarbuckCompat:\n      decaf_check(thread->pid == ProcessId::KERNEL);\n      enableAhbAll(AHBALL::get(0).IpcStarbuckCompat(true));\n      break;\n   case DeviceId::Unknown30:\n      enableAhbLatte(AHBLT::get(0).SdHostController(true));\n      break;\n   case DeviceId::Unknown31:\n      enableAhbLatte(AHBLT::get(0).Unknown1(true));\n      break;\n   case DeviceId::Unknown32:\n      enableAhbLatte(AHBLT::get(0).Unknown2(true));\n      break;\n   case DeviceId::Unknown33:\n      enableAhbLatte(AHBLT::get(0).Unknown3(true));\n      break;\n   case DeviceId::Drh:\n      enableAhbLatte(AHBLT::get(0).Drh(true));\n      break;\n   case DeviceId::Unknown35:\n      enableAhbLatte(AHBLT::get(0).Unknown5(true));\n      break;\n   case DeviceId::Unknown36:\n      enableAhbLatte(AHBLT::get(0).Unknown6(true));\n      break;\n   case DeviceId::Unknown37:\n      enableAhbLatte(AHBLT::get(0).Unknown7(true));\n      break;\n   case DeviceId::AesEngineAHBLT:\n      decaf_check(thread->pid == ProcessId::CRYPTO);\n      enableAhbLatte(AHBLT::get(0).AesEngine(true));\n      break;\n   case DeviceId::Sha1EngineAHBLT:\n      decaf_check(thread->pid == ProcessId::CRYPTO);\n      enableAhbLatte(AHBLT::get(0).Sha1Engine(true));\n      break;\n   case DeviceId::Unknown40:\n      enableAhbLatte(AHBLT::get(0).Unknown10(true));\n      break;\n   case DeviceId::Unknown41:\n      enableAhbLatte(AHBLT::get(0).Unknown11(true));\n      break;\n   case DeviceId::Unknown42:\n      enableAhbLatte(AHBLT::get(0).Unknown12(true));\n      break;\n   case DeviceId::I2CEspresso:\n      enableAhbLatte(AHBLT::get(0).I2CEspresso(true));\n      break;\n   case DeviceId::I2CStarbuck:\n      enableAhbLatte(AHBLT::get(0).I2CStarbuck(true));\n      break;\n   case DeviceId::IpcStarbuckCore2:\n      decaf_check(thread->pid == ProcessId::KERNEL);\n      enableAhbLatte(AHBLT::get(0).IpcStarbuckCore2(true));\n      break;\n   case DeviceId::IpcStarbuckCore1:\n      decaf_check(thread->pid == ProcessId::KERNEL);\n      enableAhbLatte(AHBLT::get(0).IpcStarbuckCore1(true));\n      break;\n   case DeviceId::IpcStarbuckCore0:\n      decaf_check(thread->pid == ProcessId::KERNEL);\n      enableAhbLatte(AHBLT::get(0).IpcStarbuckCore0(true));\n      break;\n   default:\n      return Error::Invalid;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Set BSP ready flag used for boot.\n */\nError\nIOS_SetBspReady()\n{\n   sHardwareData->bspReady = TRUE;\n   return Error::OK;\n}\n\n\nnamespace internal\n{\n\n/**\n * Get BSP ready flag.\n */\nbool\nbspReady()\n{\n   return !!sHardwareData->bspReady;\n}\n\n\n/**\n * Sends a message to the device event handler message queue.\n *\n * We cannot use IOS_SendMessage due to different scheduling requirements here.\n */\nstatic void\nsendEventHandlerMessageNoLock(DeviceId id)\n{\n   auto &handler = sHardwareData->eventHandlers[id];\n   auto queue = handler.queue;\n   if (queue && queue->used < queue->size) {\n      auto index = (queue->first + queue->used) % queue->size;\n      queue->messages[index] = handler.message;\n      queue->used++;\n\n      internal::wakeupOneThread(phys_addrof(queue->receiveQueue), Error::OK);\n   }\n}\n\nvoid\nsetInterruptAhbAll(AHBALL mask)\n{\n   auto lock = std::unique_lock { sHardwareMutex };\n   LT_INTSR_AHBALL_ARM |= mask.value;\n   sHardwareConditionVariable.notify_all();\n}\n\nvoid\nsetInterruptAhbLt(AHBLT mask)\n{\n   auto lock = std::unique_lock { sHardwareMutex };\n   LT_INTSR_AHBLT_ARM |= mask.value;\n   sHardwareConditionVariable.notify_all();\n}\n\nvoid\nunregisterEventHandlerQueue(MessageQueueId queue)\n{\n   for (auto &handler : sHardwareData->eventHandlers) {\n      if (handler.queue->uid == queue) {\n         std::memset(phys_addrof(handler).get(), 0, sizeof(handler));\n         break;\n      }\n   }\n}\n\nvoid\ninitialiseStaticHardwareData()\n{\n   LT_INTMR_AHBALL_ARM.store(AHBALL::get(0)\n                             .Timer(true)\n                             .LatteGpioStarbuck(true)\n                             .PowerButton(true)\n                             .value);\n   LT_INTSR_AHBALL_ARM.store(0);\n   LT_INTMR_AHBLT_ARM.store(0);\n   LT_INTSR_AHBLT_ARM.store(0);\n   sHardwareData = allocProcessStatic<StaticHardwareData>();\n}\n\n/**\n * Send a message to the queues registered with IOS_HandleEvent for the events\n * indicated by the AHBALL and AHBLT register values.\n */\nstatic void\nhandleEvents(AHBALL ahbAll,\n             AHBLT ahbLatte)\n{\n   if (ahbAll.Timer()) {\n      sendEventHandlerMessageNoLock(DeviceId::Timer);\n   }\n\n   if (ahbAll.NandInterface()) {\n      sendEventHandlerMessageNoLock(DeviceId::NandInterfaceAHBALL);\n   }\n\n   if (ahbAll.AesEngine()) {\n      sendEventHandlerMessageNoLock(DeviceId::AesEngineAHBALL);\n   }\n\n   if (ahbAll.Sha1Engine()) {\n      sendEventHandlerMessageNoLock(DeviceId::Sha1EngineAHBALL);\n   }\n\n   if (ahbAll.UsbEhci()) {\n      sendEventHandlerMessageNoLock(DeviceId::UsbEhci);\n   }\n\n   if (ahbAll.UsbOhci0()) {\n      sendEventHandlerMessageNoLock(DeviceId::UsbOhci0);\n   }\n\n   if (ahbAll.UsbOhci1()) {\n      sendEventHandlerMessageNoLock(DeviceId::UsbOhci1);\n   }\n\n   if (ahbAll.SdHostController()) {\n      sendEventHandlerMessageNoLock(DeviceId::SdHostController);\n   }\n\n   if (ahbAll.Wireless80211()) {\n      sendEventHandlerMessageNoLock(DeviceId::Wireless80211);\n   }\n\n   if (ahbAll.SysProt()) {\n      sendEventHandlerMessageNoLock(DeviceId::SysProt);\n   }\n\n   if (ahbAll.PowerButton()) {\n      sendEventHandlerMessageNoLock(DeviceId::PowerButton);\n   }\n\n   if (ahbAll.DriveInterface()) {\n      sendEventHandlerMessageNoLock(DeviceId::DriveInterface);\n   }\n\n   if (ahbAll.ExiRtc()) {\n      sendEventHandlerMessageNoLock(DeviceId::ExiRtc);\n   }\n\n   if (ahbAll.Sata()) {\n      sendEventHandlerMessageNoLock(DeviceId::Sata);\n   }\n\n   if (ahbAll.IpcStarbuckCompat()) {\n      sendEventHandlerMessageNoLock(DeviceId::IpcStarbuckCompat);\n   }\n\n   if (ahbLatte.SdHostController()) {\n      sendEventHandlerMessageNoLock(DeviceId::Unknown30);\n   }\n\n   if (ahbLatte.Unknown1()) {\n      sendEventHandlerMessageNoLock(DeviceId::Unknown31);\n   }\n\n   if (ahbLatte.Unknown2()) {\n      sendEventHandlerMessageNoLock(DeviceId::Unknown32);\n   }\n\n   if (ahbLatte.Unknown3()) {\n      sendEventHandlerMessageNoLock(DeviceId::Unknown33);\n   }\n\n   if (ahbLatte.Drh()) {\n      sendEventHandlerMessageNoLock(DeviceId::Drh);\n   }\n\n   if (ahbLatte.Unknown5()) {\n      sendEventHandlerMessageNoLock(DeviceId::Unknown35);\n   }\n\n   if (ahbLatte.Unknown6()) {\n      sendEventHandlerMessageNoLock(DeviceId::Unknown36);\n   }\n\n   if (ahbLatte.Unknown7()) {\n      sendEventHandlerMessageNoLock(DeviceId::Unknown37);\n   }\n\n   if (ahbLatte.AesEngine()) {\n      sendEventHandlerMessageNoLock(DeviceId::AesEngineAHBLT);\n   }\n\n   if (ahbLatte.Sha1Engine()) {\n      sendEventHandlerMessageNoLock(DeviceId::Sha1EngineAHBLT);\n   }\n\n   if (ahbLatte.Unknown10()) {\n      sendEventHandlerMessageNoLock(DeviceId::Unknown40);\n   }\n\n   if (ahbLatte.Unknown11()) {\n      sendEventHandlerMessageNoLock(DeviceId::Unknown41);\n   }\n\n   if (ahbLatte.Unknown12()) {\n      sendEventHandlerMessageNoLock(DeviceId::Unknown42);\n   }\n\n   if (ahbLatte.I2CEspresso()) {\n      sendEventHandlerMessageNoLock(DeviceId::I2CEspresso);\n   }\n\n   if (ahbLatte.I2CStarbuck()) {\n      sendEventHandlerMessageNoLock(DeviceId::I2CStarbuck);\n   }\n\n   if (ahbLatte.IpcStarbuckCore0()) {\n      sendEventHandlerMessageNoLock(DeviceId::IpcStarbuckCore0);\n   }\n\n   if (ahbLatte.IpcStarbuckCore1()) {\n      sendEventHandlerMessageNoLock(DeviceId::IpcStarbuckCore1);\n   }\n\n   if (ahbLatte.IpcStarbuckCore2()) {\n      sendEventHandlerMessageNoLock(DeviceId::IpcStarbuckCore2);\n   }\n}\n\n#if 0\n/**\n * Check and handle any pending interrupts.\n *\n * To be called from kernel scheduler to ensure we do not miss any interrupts\n * in the case that we always have new kernel threads to run.\n */\nvoid\ncheckAndHandleInterrupts()\n{\n   // Check for pending interrupts\n   auto lock = std::unique_lock { sHardwareMutex };\n\n   // Read unmasked interrupts\n   auto ahbLatte = LT_INTSR_AHBLT_ARM & LT_INTMR_AHBLT_ARM;\n   auto ahbAll = LT_INTSR_AHBALL_ARM & LT_INTMR_AHBALL_ARM;\n\n   // Clear and disable handled interrupts\n   LT_INTMR_AHBLT_ARM &= ~ahbLatte;\n   LT_INTSR_AHBLT_ARM &= ~ahbLatte;\n\n   LT_INTMR_AHBALL_ARM &= ~ahbAll;\n   LT_INTSR_AHBALL_ARM &= ~ahbAll;\n\n   if (ahbLatte || ahbAll) {\n      lock.unlock();\n      handleEvents(AHBALL::get(ahbAll), AHBLT::get(ahbLatte));\n   }\n}\n#endif\n\nstatic void\nhardwareThreadEntry()\n{\n   setIdleFiber();\n\n   while (sRunning) {\n      // Check for any pending threads to run\n      reschedule();\n\n      // Check for pending interrupts\n      auto lock = std::unique_lock { sHardwareMutex };\n\n      // Read unmasked interrupts\n      auto ahbLatte = LT_INTSR_AHBLT_ARM & LT_INTMR_AHBLT_ARM;\n      auto ahbAll = LT_INTSR_AHBALL_ARM & LT_INTMR_AHBALL_ARM;\n\n      // Clear and disable handled interrupts\n      LT_INTMR_AHBLT_ARM &= ~ahbLatte;\n      LT_INTSR_AHBLT_ARM &= ~ahbLatte;\n\n      LT_INTMR_AHBALL_ARM &= ~ahbAll;\n      LT_INTSR_AHBALL_ARM &= ~ahbAll;\n\n      if (ahbLatte || ahbAll) {\n         lock.unlock();\n         handleEvents(AHBALL::get(ahbAll), AHBLT::get(ahbLatte));\n      } else if (sRunning) {\n         sHardwareConditionVariable.wait(lock);\n      }\n   }\n}\n\nvoid\nstartHardwareThread()\n{\n   sRunning = true;\n   sHardwareThread = std::thread { hardwareThreadEntry };\n}\n\nvoid\njoinHardwareThread()\n{\n   sHardwareThread.join();\n}\n\nvoid\nstopHardwareThread()\n{\n   sRunning = false;\n   sHardwareConditionVariable.notify_all();\n}\n\n} // namespace internal\n\n} // namespace ios\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_hardware.h",
    "content": "#pragma once\n#include \"ios_kernel_enum.h\"\n#include \"ios_kernel_messagequeue.h\"\n\n#include <chrono>\n#include <common/bitfield.h>\n\nnamespace ios::kernel\n{\n\nBITFIELD_BEG(AHBALL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, Timer)\n   BITFIELD_ENTRY(1, 1, bool, NandInterface)\n   BITFIELD_ENTRY(2, 1, bool, AesEngine)\n   BITFIELD_ENTRY(3, 1, bool, Sha1Engine)\n   BITFIELD_ENTRY(4, 1, bool, UsbEhci)\n   BITFIELD_ENTRY(5, 1, bool, UsbOhci0)\n   BITFIELD_ENTRY(6, 1, bool, UsbOhci1)\n   BITFIELD_ENTRY(7, 1, bool, SdHostController)\n   BITFIELD_ENTRY(8, 1, bool, Wireless80211)\n\n   BITFIELD_ENTRY(10, 1, bool, LatteGpioEspresso)\n   BITFIELD_ENTRY(11, 1, bool, LatteGpioStarbuck)\n   BITFIELD_ENTRY(12, 1, bool, SysProt)\n\n   BITFIELD_ENTRY(17, 1, bool, PowerButton)\n   BITFIELD_ENTRY(18, 1, bool, DriveInterface)\n\n   BITFIELD_ENTRY(20, 1, bool, ExiRtc)\n\n   BITFIELD_ENTRY(28, 1, bool, Sata)\n\n   BITFIELD_ENTRY(30, 1, bool, IpcEspressoCompat)\n   BITFIELD_ENTRY(31, 1, bool, IpcStarbuckCompat)\nBITFIELD_END\n\nBITFIELD_BEG(AHBLT, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, SdHostController)\n   BITFIELD_ENTRY(1, 1, bool, Unknown1)\n   BITFIELD_ENTRY(2, 1, bool, Unknown2)\n   BITFIELD_ENTRY(3, 1, bool, Unknown3)\n   BITFIELD_ENTRY(4, 1, bool, Drh)\n   BITFIELD_ENTRY(5, 1, bool, Unknown5)\n   BITFIELD_ENTRY(6, 1, bool, Unknown6)\n   BITFIELD_ENTRY(7, 1, bool, Unknown7)\n   BITFIELD_ENTRY(8, 1, bool, AesEngine)\n   BITFIELD_ENTRY(9, 1, bool, Sha1Engine)\n   BITFIELD_ENTRY(10, 1, bool, Unknown10)\n   BITFIELD_ENTRY(11, 1, bool, Unknown11)\n   BITFIELD_ENTRY(12, 1, bool, Unknown12)\n   BITFIELD_ENTRY(13, 1, bool, I2CEspresso)\n   BITFIELD_ENTRY(14, 1, bool, I2CStarbuck)\n\n   BITFIELD_ENTRY(26, 1, bool, IpcEspressoCore2)\n   BITFIELD_ENTRY(27, 1, bool, IpcStarbuckCore2)\n   BITFIELD_ENTRY(28, 1, bool, IpcEspressoCore1)\n   BITFIELD_ENTRY(29, 1, bool, IpcStarbuckCore1)\n   BITFIELD_ENTRY(30, 1, bool, IpcEspressoCore0)\n   BITFIELD_ENTRY(31, 1, bool, IpcStarbuckCore0)\nBITFIELD_END\n\nError\nIOS_HandleEvent(DeviceId id,\n                MessageQueueId queue,\n                Message message);\n\nError\nIOS_UnregisterEventHandler(DeviceId id);\n\nError\nIOS_ClearAndEnable(DeviceId id);\n\nError\nIOS_SetBspReady();\n\nnamespace internal\n{\n\nbool\nbspReady();\n\nvoid\ncheckAndHandleInterrupts();\n\nvoid\nsetInterruptAhbAll(AHBALL mask);\n\nvoid\nsetInterruptAhbLt(AHBLT mask);\n\nvoid\nunregisterEventHandlerQueue(MessageQueueId queue);\n\nvoid\nstartHardwareThread();\n\nvoid\njoinHardwareThread();\n\nvoid\nstopHardwareThread();\n\nvoid\ninitialiseStaticHardwareData();\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_heap.cpp",
    "content": "#include \"ios_kernel_heap.h\"\n#include \"ios_kernel_process.h\"\n\n#include <array>\n#include <common/decaf_assert.h>\n#include <common/teenyheap.h>\n\nnamespace ios::kernel\n{\n\nconstexpr auto MaxNumHeaps = 48u;\nconstexpr auto MaxNumProcessHeaps = 14u;\n\nconstexpr auto HeapAllocSizeAlign = 32u;\nconstexpr auto HeapAllocAlignAlign = 32u;\n\nstruct StaticHeapData\n{\n   be2_array<Heap, MaxNumHeaps> heaps;\n   be2_array<HeapId, MaxNumProcessHeaps> localProcessHeaps;\n   be2_array<HeapId, MaxNumProcessHeaps> crossProcessHeaps;\n};\n\nstatic phys_ptr<StaticHeapData>\nsHeapData;\n\nstatic Error\ngetHeap(HeapId id,\n        bool checkPermission,\n        phys_ptr<Heap> *outHeap)\n{\n   auto error = IOS_GetCurrentProcessId();\n   if (error < Error::OK) {\n      return Error::Invalid;\n   }\n\n   auto pid = static_cast<ProcessId>(error);\n\n   if (id == 0 || id == CrossProcessHeapId) {\n      id = sHeapData->crossProcessHeaps[pid];\n   } else if (id == LocalProcessHeapId) {\n      id = sHeapData->localProcessHeaps[pid];\n   }\n\n   if (id >= static_cast<HeapId>(sHeapData->heaps.size())) {\n      return Error::Invalid;\n   }\n\n   if (sHeapData->heaps[id].pid < ProcessId::KERNEL || !sHeapData->heaps[id].base) {\n      return Error::NoExists;\n   }\n\n   if (checkPermission && id != SharedHeapId && sHeapData->heaps[id].pid != pid) {\n      return Error::Access;\n   }\n\n   *outHeap = phys_addrof(sHeapData->heaps[id]);\n   return Error::OK;\n}\n\nError\nIOS_CreateHeap(phys_ptr<void> ptr,\n               uint32_t size)\n{\n   HeapId id;\n\n   if (phys_cast<phys_addr>(ptr).getAddress() & 0x1F) {\n      return Error::Alignment;\n   }\n\n   for (id = HeapId { 1 }; id < static_cast<HeapId>(sHeapData->heaps.size()); ++id) {\n      if (!sHeapData->heaps[id].base) {\n         break;\n      }\n   }\n\n   if (id >= static_cast<HeapId>(sHeapData->heaps.size())) {\n      return Error::Max;\n   }\n\n   auto error = IOS_GetCurrentProcessId();\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto pid = static_cast<ProcessId>(error);\n\n   // Initialise first heap block\n   auto firstBlock = phys_cast<HeapBlock *>(ptr);\n   firstBlock->state = HeapBlockState::Free;\n   firstBlock->size = static_cast<uint32_t>(size - sizeof(HeapBlock));\n   firstBlock->next = nullptr;\n   firstBlock->prev = nullptr;\n\n   // Initialise heap\n   auto &heap = sHeapData->heaps[id];\n   heap.id = id;\n   heap.base = ptr;\n   heap.size = size;\n   heap.firstFreeBlock = firstBlock;\n   heap.pid = pid;\n\n   return static_cast<Error>(id);\n}\n\nError\nIOS_CreateLocalProcessHeap(phys_ptr<void> ptr,\n                           uint32_t size)\n{\n   phys_ptr<Heap> heap;\n\n   auto error = IOS_GetCurrentProcessId();\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto pid = static_cast<ProcessId>(error);\n   if (pid >= static_cast<ProcessId>(sHeapData->localProcessHeaps.size())) {\n      return Error::Invalid;\n   }\n\n   if (sHeapData->localProcessHeaps[pid] >= 0) {\n      return static_cast<Error>(sHeapData->localProcessHeaps[pid]);\n   }\n\n   error = IOS_CreateHeap(ptr, size);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto heapId = static_cast<HeapId>(error);\n   error = getHeap(heapId, true, &heap);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   heap->flags |= HeapFlags::LocalProcessHeap;\n   sHeapData->localProcessHeaps[pid] = heapId;\n   return static_cast<Error>(heapId);\n}\n\nError\nIOS_CreateCrossProcessHeap(uint32_t size)\n{\n   phys_ptr<Heap> heap;\n\n   auto error = IOS_GetCurrentProcessId();\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto pid = static_cast<ProcessId>(error);\n   if (pid >= static_cast<ProcessId>(sHeapData->crossProcessHeaps.size())) {\n      return Error::Invalid;\n   }\n\n   if (sHeapData->crossProcessHeaps[pid] >= 0) {\n      return static_cast<Error>(sHeapData->crossProcessHeaps[pid]);\n   }\n\n   auto ptr = IOS_HeapAlloc(SharedHeapId, size);\n   if (!ptr) {\n      return Error::NoResource;\n   }\n\n   error = IOS_CreateHeap(ptr, size);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto heapId = static_cast<HeapId>(error);\n   error = getHeap(heapId, true, &heap);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   heap->flags |= HeapFlags::CrossProcessHeap;\n   sHeapData->crossProcessHeaps[pid] = heapId;\n   return static_cast<Error>(heapId);\n}\n\nError\nIOS_DestroyHeap(HeapId heapId)\n{\n   phys_ptr<Heap> heap;\n\n   if (heapId == SharedHeapId) {\n      return Error::Invalid;\n   }\n\n   auto error = getHeap(heapId, true, &heap);\n   if (error < Error::OK) {\n      return Error::Invalid;\n   }\n\n   std::memset(heap.get(), 0, sizeof(Heap));\n   heap->pid = ProcessId::Invalid;\n\n   if (heapId == LocalProcessHeapId) {\n      sHeapData->localProcessHeaps[internal::getCurrentProcessId()] = -4;\n   }\n\n   return Error::OK;\n}\n\nphys_ptr<void>\nIOS_HeapAlloc(HeapId id,\n              uint32_t size)\n{\n   return IOS_HeapAllocAligned(id, size, 0x20);\n}\n\nphys_ptr<void>\nIOS_HeapAllocAligned(HeapId heapId,\n                     uint32_t size,\n                     uint32_t alignment)\n{\n   phys_ptr<Heap> heap;\n\n   auto error = getHeap(heapId, true, &heap);\n   if (error < Error::OK || size == 0 || alignment == 0) {\n      return nullptr;\n   }\n\n   auto allocBlock = phys_ptr<HeapBlock> { nullptr };\n   auto allocBase = phys_ptr<uint8_t> { nullptr };\n\n   // Both size and alignment must be aligned.\n   size = align_up(size, HeapAllocSizeAlign);\n   alignment = align_up(alignment, HeapAllocAlignAlign);\n\n   for (auto block = phys_ptr<HeapBlock> { heap->firstFreeBlock }; block; block = block->next) {\n      auto base = phys_cast<uint8_t *>(block) + sizeof(HeapBlock);\n      auto baseAddr = phys_cast<phys_addr>(base);\n      auto alignedBaseAddr = align_up(phys_cast<phys_addr>(base), alignment);\n      auto alignOffset = static_cast<uint32_t>(alignedBaseAddr - baseAddr);\n\n      decaf_check(block->state == HeapBlockState::Free);\n\n      if (alignedBaseAddr == baseAddr) {\n         // Base already aligned\n         if (block->size >= size) {\n            allocBlock = block;\n            allocBase = base;\n            break;\n         }\n      } else if (alignedBaseAddr - baseAddr >= sizeof(HeapBlock)) {\n         // Requires alignment, and has space for a HeapBlock\n         if (block->size >= size + alignOffset) {\n            allocBlock = block;\n            allocBase = phys_cast<uint8_t *>(alignedBaseAddr);\n            size += alignOffset;\n            break;\n         }\n      } else if (block->size >= size + alignOffset + alignment) {\n         // Base required double align to have space for HeapBlock\n         allocBlock = block;\n         allocBase = phys_cast<uint8_t *>(align_up(alignedBaseAddr + 1, alignment));\n         size += alignOffset + alignment;\n         break;\n      }\n   }\n\n   if (!allocBlock) {\n      heap->errorCountAllocOutOfMemory++;\n      return nullptr;\n   }\n\n   auto innerBlock = phys_cast<HeapBlock *>(allocBase - sizeof(HeapBlock));\n   if (innerBlock != allocBlock) {\n      // Create inner block for aligned allocations if needed\n      auto offset = phys_cast<uint8_t *>(innerBlock) - phys_cast<uint8_t *>(allocBlock);\n      innerBlock->state = HeapBlockState::InnerBlock;\n      innerBlock->size = static_cast<uint32_t>(size - offset);\n      innerBlock->prev = allocBlock;\n      innerBlock->next = nullptr;\n   }\n\n   // Check if there is enough size remaining to make it worth creating a new\n   // free block.\n   if (allocBlock->size - size > 0x40) {\n      auto allocBlockEnd = phys_cast<uint8_t *>(allocBlock) + sizeof(HeapBlock) + allocBlock->size;\n      auto freeBlock = phys_cast<HeapBlock *>(phys_cast<uint8_t *>(allocBlock) + sizeof(HeapBlock) + size);\n      freeBlock->state = HeapBlockState::Free;\n      freeBlock->size = static_cast<uint32_t>(allocBlockEnd - phys_cast<uint8_t *>(freeBlock) - sizeof(HeapBlock));\n      freeBlock->prev = allocBlock->prev;\n      freeBlock->next = allocBlock->next;\n\n      if (allocBlock->prev) {\n         allocBlock->prev->next = freeBlock;\n      }\n\n      if (allocBlock->next) {\n         allocBlock->next->prev = freeBlock;\n      }\n\n      allocBlock->size = size;\n\n      if (heap->firstFreeBlock == allocBlock) {\n         heap->firstFreeBlock = freeBlock;\n      }\n   } else if (heap->firstFreeBlock == allocBlock) {\n      heap->firstFreeBlock = allocBlock->next;\n   }\n\n   allocBlock->state = HeapBlockState::Allocated;\n   allocBlock->prev = nullptr;\n   allocBlock->next = nullptr;\n\n   heap->currentAllocatedSize += allocBlock->size;\n   heap->largestAllocatedSize = std::max(heap->largestAllocatedSize, allocBlock->size);\n   heap->totalAllocCount++;\n   return allocBase;\n}\n\nstatic bool\nheapContainsPtr(phys_ptr<Heap> heap,\n                phys_ptr<void> ptr)\n{\n   auto start = phys_cast<uint8_t *>(heap->base);\n   auto end = start + heap->size;\n\n   if (phys_cast<uint8_t *>(ptr) < start + sizeof(Heap) + sizeof(HeapBlock)) {\n      return false;\n   }\n\n   if (phys_cast<uint8_t *>(ptr) >= end) {\n      return false;\n   }\n\n   return true;\n}\n\nstatic void\ntryMergeConsecutiveBlocks(phys_ptr<HeapBlock> first,\n                          phys_ptr<HeapBlock> second)\n{\n   auto firstEnd = phys_cast<uint8_t *>(first) + sizeof(HeapBlock) + first->size;\n\n   if (phys_cast<phys_addr>(firstEnd) == phys_cast<phys_addr>(second)) {\n      first->size += static_cast<uint32_t>(second->size + sizeof(HeapBlock));\n      first->next = second->next;\n\n      if (second->next) {\n         second->next->prev = first;\n      }\n   }\n}\n\nstatic Error\nheapFree(HeapId heapId,\n         phys_ptr<void> ptr,\n         bool clearMemory)\n{\n   phys_ptr<Heap> heap;\n\n   if (!ptr) {\n      return Error::Invalid;\n   }\n\n   auto error = getHeap(heapId, true, &heap);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   if (!heapContainsPtr(heap, ptr)) {\n      heap->errorCountFreeBlockNotInHeap++;\n      return Error::Invalid;\n   }\n\n   auto freeBlock = phys_cast<HeapBlock *>(phys_cast<uint8_t *>(ptr) - sizeof(HeapBlock));\n   if (freeBlock->state == HeapBlockState::InnerBlock) {\n      freeBlock = freeBlock->prev;\n   }\n\n   if (freeBlock->state != HeapBlockState::Allocated) {\n      heap->errorCountFreeUnallocatedBlock++;\n      return Error::Invalid;\n   }\n\n   freeBlock->state = HeapBlockState::Free;\n\n   if (clearMemory) {\n      auto base = phys_cast<uint8_t *>(freeBlock) + sizeof(HeapBlock);\n      auto size = freeBlock->size;\n      std::memset(base.get(), 0, size);\n   }\n\n   heap->currentAllocatedSize -= freeBlock->size;\n\n   auto nextBlock = heap->firstFreeBlock;\n   auto prevBlock = nextBlock->prev;\n   while (nextBlock) {\n      if (phys_cast<phys_addr>(freeBlock) < phys_cast<phys_addr>(nextBlock)) {\n         break;\n      }\n\n      prevBlock = nextBlock;\n      nextBlock = nextBlock->next;\n   }\n\n   if (prevBlock) {\n      prevBlock->next = freeBlock;\n   }\n\n   if (nextBlock) {\n      nextBlock->prev = freeBlock;\n   }\n\n   freeBlock->prev = prevBlock;\n   freeBlock->next = nextBlock;\n\n   if (heap->firstFreeBlock == nextBlock) {\n      heap->firstFreeBlock = freeBlock;\n   }\n\n   if (nextBlock) {\n      tryMergeConsecutiveBlocks(freeBlock, nextBlock);\n   }\n\n   if (prevBlock) {\n      tryMergeConsecutiveBlocks(prevBlock, freeBlock);\n   }\n\n   heap->totalFreeCount++;\n   return Error::OK;\n}\n\nphys_ptr<void>\nIOS_HeapRealloc(HeapId heapId,\n                phys_ptr<void> ptr,\n                uint32_t size)\n{\n   phys_ptr<Heap> heap;\n\n   auto error = getHeap(heapId, true, &heap);\n   if (error < Error::OK || !ptr) {\n      return nullptr;\n   }\n\n   // Allocate a new block if ptr is null\n   if (!ptr) {\n      return IOS_HeapAllocAligned(heapId, size, HeapAllocAlignAlign);\n   }\n\n   if (!heapContainsPtr(heap, ptr)) {\n      heap->errorCountExpandInvalidBlock++;\n      return nullptr;\n   }\n\n   auto block = phys_cast<HeapBlock *>(phys_cast<uint8_t *>(ptr) - sizeof(HeapBlock));\n   if (block->state != HeapBlockState::Allocated && block->state != HeapBlockState::InnerBlock) {\n      heap->errorCountExpandInvalidBlock++;\n      return nullptr;\n   }\n\n   auto blockSize = block->size;\n   size = align_up(size, HeapAllocSizeAlign);\n\n   if (size == 0) {\n      // Realloc to size 0 just means we should free the memory.\n      heapFree(heapId, ptr, false);\n      return nullptr;\n   } else if (block->size == size) {\n      // Realloc to same size, no changes required.\n      return ptr;\n   }\n\n   // Just allocate a new block and copy the data across.\n   auto newPtr = IOS_HeapAllocAligned(heapId, size, HeapAllocAlignAlign);\n   if (newPtr) {\n      std::memcpy(newPtr.get(),\n                  ptr.get(),\n                  std::min<uint32_t>(size, block->size));\n      heapFree(heapId, ptr, false);\n   }\n\n   return newPtr;\n}\n\nError\nIOS_HeapFree(HeapId id,\n             phys_ptr<void> ptr)\n{\n   return heapFree(id, ptr, false);\n}\n\nError\nIOS_HeapFreeAndZero(HeapId id,\n                    phys_ptr<void> ptr)\n{\n   return heapFree(id, ptr, true);\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseStaticHeapData()\n{\n   sHeapData = allocProcessStatic<StaticHeapData>();\n   for (auto i = 0u; i < sHeapData->heaps.size(); ++i) {\n      sHeapData->heaps[i].pid = ProcessId { -4 };\n   }\n\n   for (auto i = 0u; i < sHeapData->localProcessHeaps.size(); ++i) {\n      sHeapData->localProcessHeaps[i] = HeapId { -4 };\n   }\n\n   for (auto i = 0u; i < sHeapData->crossProcessHeaps.size(); ++i) {\n      sHeapData->crossProcessHeaps[i] = HeapId { -4 };\n   }\n}\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_heap.h",
    "content": "#pragma once\n#include \"ios_kernel_enum.h\"\n#include \"ios/ios_enum.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::kernel\n{\n\nusing HeapId = int32_t;\n\nconstexpr auto SharedHeapId = HeapId { 1 };\nconstexpr auto LocalProcessHeapId = HeapId { 0xCAFE };\nconstexpr auto CrossProcessHeapId = HeapId { 0xCAFF };\n\nstruct Heap\n{\n   be2_phys_ptr<void> base;\n   be2_val<ProcessId> pid;\n   be2_val<uint32_t> size;\n   be2_phys_ptr<struct HeapBlock> firstFreeBlock;\n   be2_val<uint32_t> errorCountAllocOutOfMemory;\n   be2_val<uint32_t> errorCountFreeBlockNotInHeap;\n   be2_val<uint32_t> errorCountExpandInvalidBlock;\n   be2_val<uint32_t> errorCountCorruptedBlock;\n   be2_val<HeapFlags> flags;\n   be2_val<uint32_t> currentAllocatedSize;\n   be2_val<uint32_t> largestAllocatedSize;\n   be2_val<uint32_t> totalAllocCount;\n   be2_val<uint32_t> totalFreeCount;\n   be2_val<uint32_t> errorCountFreeUnallocatedBlock;\n   be2_val<uint32_t> errorCountAllocInvalidHeap;\n   be2_val<HeapId> id;\n};\nCHECK_OFFSET(Heap, 0x00, base);\nCHECK_OFFSET(Heap, 0x04, pid);\nCHECK_OFFSET(Heap, 0x08, size);\nCHECK_OFFSET(Heap, 0x0C, firstFreeBlock);\nCHECK_OFFSET(Heap, 0x10, errorCountAllocOutOfMemory);\nCHECK_OFFSET(Heap, 0x14, errorCountFreeBlockNotInHeap);\nCHECK_OFFSET(Heap, 0x18, errorCountExpandInvalidBlock);\nCHECK_OFFSET(Heap, 0x1C, errorCountCorruptedBlock);\nCHECK_OFFSET(Heap, 0x20, flags);\nCHECK_OFFSET(Heap, 0x24, currentAllocatedSize);\nCHECK_OFFSET(Heap, 0x28, largestAllocatedSize);\nCHECK_OFFSET(Heap, 0x2C, totalAllocCount);\nCHECK_OFFSET(Heap, 0x30, totalFreeCount);\nCHECK_OFFSET(Heap, 0x34, errorCountFreeUnallocatedBlock);\nCHECK_OFFSET(Heap, 0x38, errorCountAllocInvalidHeap);\nCHECK_OFFSET(Heap, 0x3C, id);\nCHECK_SIZE(Heap, 0x40);\n\nstruct HeapBlock\n{\n   be2_val<HeapBlockState> state;\n   be2_val<uint32_t> size;\n   be2_phys_ptr<HeapBlock> prev;\n   be2_phys_ptr<HeapBlock> next;\n};\nCHECK_OFFSET(HeapBlock, 0x00, state);\nCHECK_OFFSET(HeapBlock, 0x04, size);\nCHECK_OFFSET(HeapBlock, 0x08, prev);\nCHECK_OFFSET(HeapBlock, 0x0C, next);\nCHECK_SIZE(HeapBlock, 0x10);\n\nError\nIOS_CreateHeap(phys_ptr<void> ptr,\n               uint32_t size);\n\nError\nIOS_CreateLocalProcessHeap(phys_ptr<void> ptr,\n                           uint32_t size);\n\nError\nIOS_CreateCrossProcessHeap(uint32_t size);\n\nError\nIOS_DestroyHeap(HeapId id);\n\nphys_ptr<void>\nIOS_HeapAlloc(HeapId id,\n              uint32_t size);\n\nphys_ptr<void>\nIOS_HeapAllocAligned(HeapId id,\n                     uint32_t size,\n                     uint32_t alignment);\n\nphys_ptr<void>\nIOS_HeapRealloc(HeapId id,\n                phys_ptr<void> ptr,\n                uint32_t size);\n\nError\nIOS_HeapFree(HeapId id,\n             phys_ptr<void> ptr);\n\nError\nIOS_HeapFreeAndZero(HeapId id,\n                    phys_ptr<void> ptr);\n\nnamespace internal\n{\n\nvoid\ninitialiseStaticHeapData();\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_ipc.cpp",
    "content": "#include \"ios_kernel_ipc.h\"\n#include \"ios_kernel_messagequeue.h\"\n#include \"ios_kernel_process.h\"\n#include \"ios_kernel_resourcemanager.h\"\n#include \"ios/ios_stackobject.h\"\n\nnamespace ios::kernel\n{\n\nnamespace internal\n{\n\nstatic Error\nwaitRequestReply(phys_ptr<MessageQueue> queue,\n                 phys_ptr<IpcRequest> request);\n\n} // namespace internal\n\nError\nIOS_Open(std::string_view device,\n         OpenMode mode)\n{\n   StackObject<IpcRequest> request;\n   std::memset(request.get(), 0, sizeof(IpcRequest));\n\n   auto queue = internal::getCurrentThreadMessageQueue();\n   auto error = internal::dispatchIosOpen(device,\n                                          mode,\n                                          queue,\n                                          request,\n                                          internal::getCurrentProcessId(),\n                                          CpuId::ARM);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::waitRequestReply(queue, request);\n}\n\nError\nIOS_OpenAsync(std::string_view device,\n              OpenMode mode,\n              MessageQueueId asyncNotifyQueue,\n              phys_ptr<IpcRequest> asyncNotifyRequest)\n{\n   phys_ptr<MessageQueue> queue;\n\n   auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::dispatchIosOpen(device,\n                                    mode,\n                                    queue,\n                                    asyncNotifyRequest,\n                                    internal::getCurrentProcessId(),\n                                    CpuId::ARM);\n}\n\nError\nIOS_Close(ResourceHandleId handle)\n{\n   StackObject<IpcRequest> request;\n   std::memset(request.get(), 0, sizeof(IpcRequest));\n\n   auto queue = internal::getCurrentThreadMessageQueue();\n   auto error = internal::dispatchIosClose(handle,\n                                           queue,\n                                           request,\n                                           0,\n                                           internal::getCurrentProcessId(),\n                                           CpuId::ARM);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::waitRequestReply(queue, request);\n}\n\nError\nIOS_CloseAsync(ResourceHandleId handle,\n               MessageQueueId asyncNotifyQueue,\n               phys_ptr<IpcRequest> asyncNotifyRequest)\n{\n   phys_ptr<MessageQueue> queue;\n\n   auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::dispatchIosClose(handle,\n                                     queue,\n                                     asyncNotifyRequest,\n                                     0,\n                                     internal::getCurrentProcessId(),\n                                     CpuId::ARM);\n}\n\nError\nIOS_Read(ResourceHandleId handle,\n         phys_ptr<void> buffer,\n         uint32_t length)\n{\n   // TODO: Implement IOS_Read\n   return Error::Invalid;\n}\n\nError\nIOS_ReadAsync(ResourceHandleId handle,\n              phys_ptr<void> buffer,\n              uint32_t length,\n              MessageQueueId asyncNotifyQueue,\n              phys_ptr<IpcRequest> asyncNotifyRequest)\n{\n   // TODO: Implement IOS_Read\n   return Error::Invalid;\n}\n\nError\nIOS_Write(ResourceHandleId handle,\n          phys_ptr<const void> buffer,\n          uint32_t length)\n{\n   // TODO: Implement IOS_Write\n   return Error::Invalid;\n}\n\nError\nIOS_WriteAsync(ResourceHandleId handle,\n               phys_ptr<const void> buffer,\n               uint32_t length,\n               MessageQueueId asyncNotifyQueue,\n               phys_ptr<IpcRequest> asyncNotifyRequest)\n{\n   // TODO: Implement IOS_WriteAsync\n   return Error::Invalid;\n}\n\nError\nIOS_Seek(ResourceHandleId handle,\n         uint32_t offset,\n         uint32_t origin)\n{\n   // TODO: Implement IOS_Seek\n   return Error::Invalid;\n}\n\nError\nIOS_SeekAsync(ResourceHandleId handle,\n              uint32_t offset,\n              uint32_t origin,\n              MessageQueueId asyncNotifyQueue,\n              phys_ptr<IpcRequest> asyncNotifyRequest)\n{\n   // TODO: Implement IOS_SeekAsync\n   return Error::Invalid;\n}\n\nError\nIOS_Ioctl(ResourceHandleId handle,\n          uint32_t ioctlRequest,\n          phys_ptr<const void> inputBuffer,\n          uint32_t inputBufferLength,\n          phys_ptr<void> outputBuffer,\n          uint32_t outputBufferLength)\n{\n   StackObject<IpcRequest> request;\n   std::memset(request.get(), 0, sizeof(IpcRequest));\n\n   auto queue = internal::getCurrentThreadMessageQueue();\n   auto error = internal::dispatchIosIoctl(handle,\n                                           ioctlRequest,\n                                           inputBuffer,\n                                           inputBufferLength,\n                                           outputBuffer,\n                                           outputBufferLength,\n                                           queue,\n                                           request,\n                                           internal::getCurrentProcessId(),\n                                           CpuId::ARM);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::waitRequestReply(queue, request);\n}\n\nError\nIOS_IoctlAsync(ResourceHandleId handle,\n               uint32_t ioctlRequest,\n               phys_ptr<const void> inputBuffer,\n               uint32_t inputBufferLength,\n               phys_ptr<void> outputBuffer,\n               uint32_t outputBufferLength,\n               MessageQueueId asyncNotifyQueue,\n               phys_ptr<IpcRequest> asyncNotifyRequest)\n{\n   phys_ptr<MessageQueue> queue;\n\n   auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::dispatchIosIoctl(handle,\n                                     ioctlRequest,\n                                     inputBuffer,\n                                     inputBufferLength,\n                                     outputBuffer,\n                                     outputBufferLength,\n                                     queue,\n                                     asyncNotifyRequest,\n                                     internal::getCurrentProcessId(),\n                                     CpuId::ARM);\n}\n\nError\nIOS_Ioctlv(ResourceHandleId handle,\n           uint32_t ioctlRequest,\n           uint32_t numVecIn,\n           uint32_t numVecOut,\n           phys_ptr<IoctlVec> vecs)\n{\n   StackObject<IpcRequest> request;\n   std::memset(request.get(), 0, sizeof(IpcRequest));\n\n   auto queue = internal::getCurrentThreadMessageQueue();\n   auto error = internal::dispatchIosIoctlv(handle,\n                                            ioctlRequest,\n                                            numVecIn,\n                                            numVecOut,\n                                            vecs,\n                                            queue,\n                                            request,\n                                            internal::getCurrentProcessId(),\n                                            CpuId::ARM);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::waitRequestReply(queue, request);\n}\n\nError\nIOS_IoctlvAsync(ResourceHandleId handle,\n                uint32_t ioctlRequest,\n                uint32_t numVecIn,\n                uint32_t numVecOut,\n                phys_ptr<IoctlVec> vecs,\n                MessageQueueId asyncNotifyQueue,\n                phys_ptr<IpcRequest> asyncNotifyRequest)\n{\n   phys_ptr<MessageQueue> queue;\n\n   auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::dispatchIosIoctlv(handle,\n                                      ioctlRequest,\n                                      numVecIn,\n                                      numVecOut,\n                                      vecs,\n                                      queue,\n                                      asyncNotifyRequest,\n                                      internal::getCurrentProcessId(),\n                                      CpuId::ARM);\n}\n\nError\nIOS_Resume(ResourceHandleId handle,\n           uint32_t unkArg0,\n           uint32_t unkArg1)\n{\n   StackObject<IpcRequest> request;\n   std::memset(request.get(), 0, sizeof(IpcRequest));\n\n   auto queue = internal::getCurrentThreadMessageQueue();\n   auto error = internal::dispatchIosResume(handle,\n                                            unkArg0,\n                                            unkArg1,\n                                            queue,\n                                            request,\n                                            internal::getCurrentProcessId(),\n                                            CpuId::ARM);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::waitRequestReply(queue, request);\n}\n\nError\nIOS_ResumeAsync(ResourceHandleId handle,\n                uint32_t unkArg0,\n                uint32_t unkArg1,\n                MessageQueueId asyncNotifyQueue,\n                phys_ptr<IpcRequest> asyncNotifyRequest)\n{\n   phys_ptr<MessageQueue> queue;\n\n   auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::dispatchIosResume(handle,\n                                      unkArg0,\n                                      unkArg1,\n                                      queue,\n                                      asyncNotifyRequest,\n                                      internal::getCurrentProcessId(),\n                                      CpuId::ARM);\n}\n\nError\nIOS_Suspend(ResourceHandleId handle,\n            uint32_t unkArg0,\n            uint32_t unkArg1)\n{\n   StackObject<IpcRequest> request;\n   std::memset(request.get(), 0, sizeof(IpcRequest));\n\n   auto queue = internal::getCurrentThreadMessageQueue();\n   auto error = internal::dispatchIosSuspend(handle,\n                                             unkArg0,\n                                             unkArg1,\n                                             queue,\n                                             request,\n                                             internal::getCurrentProcessId(),\n                                             CpuId::ARM);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::waitRequestReply(queue, request);\n}\n\nError\nIOS_SuspendAsync(ResourceHandleId handle,\n                 uint32_t unkArg0,\n                 uint32_t unkArg1,\n                 MessageQueueId asyncNotifyQueue,\n                 phys_ptr<IpcRequest> asyncNotifyRequest)\n{\n   phys_ptr<MessageQueue> queue;\n\n   auto error = internal::getMessageQueueSafe(asyncNotifyQueue, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::dispatchIosSuspend(handle,\n                                       unkArg0,\n                                       unkArg1,\n                                       queue,\n                                       asyncNotifyRequest,\n                                       internal::getCurrentProcessId(),\n                                       CpuId::ARM);\n}\n\nError\nIOS_SvcMsg(ResourceHandleId handle,\n           uint32_t command,\n           uint32_t unkArg1,\n           uint32_t unkArg2,\n           uint32_t unkArg3)\n{\n   StackObject<IpcRequest> request;\n   std::memset(request.get(), 0, sizeof(IpcRequest));\n\n   auto queue = internal::getCurrentThreadMessageQueue();\n   auto error = internal::dispatchIosSvcMsg(handle,\n                                            command,\n                                            unkArg1,\n                                            unkArg2,\n                                            unkArg3,\n                                            queue,\n                                            request,\n                                            internal::getCurrentProcessId(),\n                                            CpuId::ARM);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::waitRequestReply(queue, request);\n}\n\nnamespace internal\n{\n\nstatic Error\nwaitRequestReply(phys_ptr<MessageQueue> queue,\n                 phys_ptr<IpcRequest> request)\n{\n   StackObject<Message> message;\n   auto error = internal::receiveMessage(queue, message, MessageFlags::None);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto receivedRequest = kernel::parseMessage<IpcRequest>(message);\n   if (receivedRequest != request) {\n      return Error::FailInternal;\n   }\n\n   return static_cast<Error>(request->reply);\n}\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_ipc.h",
    "content": "#pragma once\n#include \"ios_kernel_messagequeue.h\"\n#include \"ios_kernel_resourcemanager.h\"\n#include \"ios/ios_ipc.h\"\n\nnamespace ios::kernel\n{\n\nError\nIOS_Open(std::string_view device,\n         OpenMode mode);\n\nError\nIOS_OpenAsync(std::string_view device,\n              OpenMode mode,\n              MessageQueueId asyncNotifyQueue,\n              phys_ptr<IpcRequest> asyncNotifyRequest);\n\nError\nIOS_Close(ResourceHandleId handle);\n\nError\nIOS_CloseAsync(ResourceHandleId handle,\n               MessageQueueId asyncNotifyQueue,\n               phys_ptr<IpcRequest> asyncNotifyRequest);\n\nError\nIOS_Read(ResourceHandleId handle,\n         phys_ptr<void> buffer,\n         uint32_t length);\n\nError\nIOS_ReadAsync(ResourceHandleId handle,\n              phys_ptr<void> buffer,\n              uint32_t length,\n              MessageQueueId asyncNotifyQueue,\n              phys_ptr<IpcRequest> asyncNotifyRequest);\n\nError\nIOS_Write(ResourceHandleId handle,\n          phys_ptr<const void> buffer,\n          uint32_t length);\n\nError\nIOS_WriteAsync(ResourceHandleId handle,\n               phys_ptr<const void> buffer,\n               uint32_t length,\n               MessageQueueId asyncNotifyQueue,\n               phys_ptr<IpcRequest> asyncNotifyRequest);\n\nError\nIOS_Seek(ResourceHandleId handle,\n         uint32_t offset,\n         uint32_t origin);\n\nError\nIOS_SeekAsync(ResourceHandleId handle,\n              uint32_t offset,\n              uint32_t origin,\n              MessageQueueId asyncNotifyQueue,\n              phys_ptr<IpcRequest> asyncNotifyRequest);\n\nError\nIOS_Ioctl(ResourceHandleId handle,\n          uint32_t ioctlRequest,\n          phys_ptr<const void> inputBuffer,\n          uint32_t inputBufferLength,\n          phys_ptr<void> outputBuffer,\n          uint32_t outputBufferLength);\n\nError\nIOS_IoctlAsync(ResourceHandleId handle,\n               uint32_t ioctlRequest,\n               phys_ptr<const void> inputBuffer,\n               uint32_t inputBufferLength,\n               phys_ptr<void> outputBuffer,\n               uint32_t outputBufferLength,\n               MessageQueueId asyncNotifyQueue,\n               phys_ptr<IpcRequest> asyncNotifyRequest);\n\nError\nIOS_Ioctlv(ResourceHandleId handle,\n           uint32_t ioctlRequest,\n           uint32_t numVecIn,\n           uint32_t numVecOut,\n           phys_ptr<IoctlVec> vecs);\n\nError\nIOS_IoctlvAsync(ResourceHandleId handle,\n                uint32_t request,\n                uint32_t numVecIn,\n                uint32_t numVecOut,\n                phys_ptr<IoctlVec> vecs,\n                MessageQueueId asyncNotifyQueue,\n                phys_ptr<IpcRequest> asyncNotifyRequest);\n\nError\nIOS_Resume(ResourceHandleId handle,\n           uint32_t unkArg0,\n           uint32_t unkArg1);\n\nError\nIOS_ResumeAsync(ResourceHandleId handle,\n                uint32_t unkArg0,\n                uint32_t unkArg1,\n                MessageQueueId asyncNotifyQueue,\n                phys_ptr<IpcRequest> asyncNotifyRequest);\n\nError\nIOS_Suspend(ResourceHandleId handle,\n            uint32_t unkArg0,\n            uint32_t unkArg1);\n\nError\nIOS_SuspendAsync(ResourceHandleId handle,\n                 uint32_t unkArg0,\n                 uint32_t unkArg1,\n                 MessageQueueId asyncNotifyQueue,\n                 phys_ptr<IpcRequest> asyncNotifyRequest);\n\nError\nIOS_SvcMsg(ResourceHandleId handle,\n           uint32_t command,\n           uint32_t unkArg1,\n           uint32_t unkArg2,\n           uint32_t unkArg3);\n\nError\nIOS_SvcMsgAsync(ResourceHandleId handle,\n                uint32_t command,\n                uint32_t unkArg1,\n                uint32_t unkArg2,\n                uint32_t unkArg3,\n                MessageQueueId asyncNotifyQueue,\n                phys_ptr<IpcRequest> asyncNotifyRequest);\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_ipc_thread.cpp",
    "content": "#include \"ios_kernel_hardware.h\"\n#include \"ios_kernel_ipc_thread.h\"\n#include \"ios_kernel_messagequeue.h\"\n#include \"ios_kernel_process.h\"\n#include \"ios_kernel_resourcemanager.h\"\n#include \"ios/ios_stackobject.h\"\n#include \"cafe/kernel/cafe_kernel_ipckdriver.h\"\n\n#include <common/atomicqueue.h>\n#include <common/log.h>\n#include <libcpu/cpu_formatters.h>\n#include <mutex>\n#include <queue>\n\nnamespace ios::kernel\n{\n\nconstexpr auto IpcThreadNumMessages = 0x100u;\nconstexpr auto IpcThreadStackSize = 0x800u;\nconstexpr auto IpcThreadPriority = 95u;\n\nstruct StaticIpcData\n{\n   be2_val<MessageQueueId> messageQueueId;\n   be2_array<Message, IpcThreadNumMessages> messageBuffer;\n   be2_val<ThreadId> threadId;\n   be2_array<uint8_t, IpcThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticIpcData>\nsData = nullptr;\n\nstatic AtomicQueue<phys_ptr<IpcRequest>, 128>\nsIpcRequestQueue;\n\nvoid\nsubmitIpcRequest(phys_ptr<IpcRequest> request)\n{\n   decaf_check(!sIpcRequestQueue.wasFull());\n   sIpcRequestQueue.push(request);\n\n   switch (request->cpuId) {\n   case CpuId::PPC0:\n      internal::setInterruptAhbLt(AHBLT::get(0).IpcStarbuckCore0(true));\n      break;\n   case CpuId::PPC1:\n      internal::setInterruptAhbLt(AHBLT::get(0).IpcStarbuckCore1(true));\n      break;\n   case CpuId::PPC2:\n      internal::setInterruptAhbLt(AHBLT::get(0).IpcStarbuckCore2(true));\n      break;\n   }\n}\n\nnamespace internal\n{\n\nstatic Error\nipcThreadEntry(phys_ptr<void> context)\n{\n   StackObject<Message> message;\n   phys_ptr<MessageQueue> queue;\n\n   auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer),\n                                       static_cast<uint32_t>(sData->messageBuffer.size()));\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Register interrupt handlers and enable the interrupts.\n   auto queueId = static_cast<MessageQueueId>(error);\n   error = internal::getMessageQueue(queueId, &queue);\n   sData->messageQueueId = queueId;\n\n   IOS_HandleEvent(DeviceId::IpcStarbuckCore0, queueId, Message { Command::IpcMsg0 });\n   IOS_ClearAndEnable(DeviceId::IpcStarbuckCore0);\n\n   IOS_HandleEvent(DeviceId::IpcStarbuckCore1, queueId, Message { Command::IpcMsg1 });\n   IOS_ClearAndEnable(DeviceId::IpcStarbuckCore1);\n\n   IOS_HandleEvent(DeviceId::IpcStarbuckCore2, queueId, Message { Command::IpcMsg2 });\n   IOS_ClearAndEnable(DeviceId::IpcStarbuckCore2);\n\n   IOS_HandleEvent(DeviceId::IpcStarbuckCompat, queueId, Message { Command::IpcMsg1 });\n   IOS_ClearAndEnable(DeviceId::IpcStarbuckCompat);\n\n   while (true) {\n      error = IOS_ReceiveMessage(queueId, message, MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      /*\n       * We're kind of cheating here, we have combined all 3 IPC queues into a\n       * single queue in order to make it easier to reduce chances of race\n       * conditions. Also we attempt to fully empty the ipc request queue\n       * rather than processing a single message per interrupt like on hardware.\n       */\n      while (!sIpcRequestQueue.wasEmpty()) {\n         auto request = sIpcRequestQueue.pop();\n\n         if (request->clientPid > 7) {\n            gLog->error(\"Received IPC request with invalid clientPid of {}\", request->clientPid);\n            error = Error::Invalid;\n         } else {\n            auto pid = static_cast<ProcessId>(request->clientPid + ProcessId::COSKERNEL);\n\n            switch (request->command) {\n            case Command::Open:\n               error = internal::dispatchIosOpen(request->args.open.name.get(),\n                                                 request->args.open.mode,\n                                                 queue,\n                                                 request,\n                                                 pid,\n                                                 request->cpuId);\n               break;\n            case Command::Close:\n               error = internal::dispatchIosClose(request->handle,\n                                                  queue,\n                                                  request,\n                                                  request->args.close.unkArg0,\n                                                  pid,\n                                                  request->cpuId);\n               break;\n            case Command::Read:\n               error = internal::dispatchIosRead(request->handle,\n                                                 request->args.read.data,\n                                                 request->args.read.length,\n                                                 queue,\n                                                 request,\n                                                 pid,\n                                                 request->cpuId);\n               break;\n            case Command::Write:\n               error = internal::dispatchIosWrite(request->handle,\n                                                  request->args.write.data,\n                                                  request->args.write.length,\n                                                  queue,\n                                                  request,\n                                                  pid,\n                                                  request->cpuId);\n               break;\n            case Command::Seek:\n               error = internal::dispatchIosSeek(request->handle,\n                                                 request->args.seek.offset,\n                                                 request->args.seek.origin,\n                                                 queue,\n                                                 request,\n                                                 pid,\n                                                 request->cpuId);\n               break;\n            case Command::Ioctl:\n               error = internal::dispatchIosIoctl(request->handle,\n                                                  request->args.ioctl.request,\n                                                  request->args.ioctl.inputBuffer,\n                                                  request->args.ioctl.inputLength,\n                                                  request->args.ioctl.outputBuffer,\n                                                  request->args.ioctl.outputLength,\n                                                  queue,\n                                                  request,\n                                                  pid,\n                                                  request->cpuId);\n               break;\n            case Command::Ioctlv:\n               error = internal::dispatchIosIoctlv(request->handle,\n                                                   request->args.ioctlv.request,\n                                                   request->args.ioctlv.numVecIn,\n                                                   request->args.ioctlv.numVecOut,\n                                                   request->args.ioctlv.vecs,\n                                                   queue,\n                                                   request,\n                                                   pid,\n                                                   request->cpuId);\n               break;\n            default:\n               error = Error::Invalid;\n            }\n         }\n\n         if (error < Error::OK) {\n            // Reply with error!\n            request->command = Command::Reply;\n            request->reply = error;\n            cafe::kernel::ipckDriverIosSubmitReply(request);\n            continue;\n         }\n      }\n\n      IOS_ClearAndEnable(DeviceId::IpcStarbuckCore0);\n      IOS_ClearAndEnable(DeviceId::IpcStarbuckCore1);\n      IOS_ClearAndEnable(DeviceId::IpcStarbuckCore2);\n   }\n}\n\nMessageQueueId\ngetIpcMessageQueueId()\n{\n   return sData->messageQueueId;\n}\n\nError\nstartIpcThread()\n{\n   sData = allocProcessStatic<StaticIpcData>();\n\n   // Create thread\n   auto error = IOS_CreateThread(&ipcThreadEntry, nullptr,\n                                 phys_addrof(sData->threadStack) + sData->threadStack.size(),\n                                 static_cast<uint32_t>(sData->threadStack.size()),\n                                 IpcThreadPriority,\n                                 kernel::ThreadFlags::Detached);\n   if (error < Error::OK) {\n      kernel::IOS_DestroyMessageQueue(sData->messageQueueId);\n      return error;\n   }\n\n   sData->threadId = static_cast<kernel::ThreadId>(error);\n   internal::setThreadName(sData->threadId, \"IpcThread\");\n\n   return kernel::IOS_StartThread(sData->threadId);\n}\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_ipc_thread.h",
    "content": "#pragma once\n#include \"ios_kernel_messagequeue.h\"\n#include \"ios/ios_ipc.h\"\n\nnamespace ios::kernel\n{\n\nvoid\nsubmitIpcRequest(phys_ptr<IpcRequest> request);\n\nnamespace internal\n{\n\nMessageQueueId\ngetIpcMessageQueueId();\n\nError\nstartIpcThread();\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_messagequeue.cpp",
    "content": "#include \"ios_kernel_hardware.h\"\n#include \"ios_kernel_messagequeue.h\"\n#include \"ios_kernel_process.h\"\n#include \"ios_kernel_scheduler.h\"\n#include \"ios_kernel_thread.h\"\n\n#include <common/log.h>\n\nnamespace ios::kernel\n{\n\nstruct StaticMessageQueueData\n{\n   be2_array<MessageQueue, MaxNumMessageQueues> queues;\n   be2_val<uint32_t> numCreatedQueues;\n\n   be2_array<MessageQueue, MaxNumThreads> perThreadQueues;\n   be2_array<Message, MaxNumThreads> perThreadMesssages;\n};\n\nstatic phys_ptr<StaticMessageQueueData>\nsData = nullptr;\n\n\n/**\n * Create a message queue.\n */\nError\nIOS_CreateMessageQueue(phys_ptr<Message> messages,\n                       uint32_t size)\n{\n   phys_ptr<MessageQueue> queue = nullptr;\n\n   for (auto i = 0u; i < sData->queues.size(); ++i) {\n      if (sData->queues[i].size == 0) {\n         queue = phys_addrof(sData->queues[i]);\n         queue->uid = static_cast<int32_t>((sData->numCreatedQueues << 12) | i);\n         break;\n      }\n   }\n\n   if (!queue) {\n      return Error::Max;\n   }\n\n   queue->first = 0u;\n   queue->used = 0u;\n   queue->size = size;\n   queue->messages = messages;\n   queue->flags = MessageQueueFlags::None;\n   queue->pid = static_cast<uint8_t>(internal::getCurrentProcessId());\n\n   ThreadQueue_Initialise(phys_addrof(queue->receiveQueue));\n   ThreadQueue_Initialise(phys_addrof(queue->sendQueue));\n\n   sData->numCreatedQueues++;\n\n   return static_cast<Error>(queue->uid);\n}\n\n\n/**\n * Destroy message queue.\n *\n * Interrupts any threads waiting on the receive or send queue.\n */\nError\nIOS_DestroyMessageQueue(MessageQueueId id)\n{\n   phys_ptr<MessageQueue> queue;\n   auto error = internal::getMessageQueueSafe(id, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   if (queue->flags & MessageQueueFlags::RegisteredEventHandler) {\n      internal::unregisterEventHandlerQueue(queue->uid);\n      queue->flags &= ~MessageQueueFlags::RegisteredEventHandler;\n   }\n\n   internal::wakeupAllThreads(phys_addrof(queue->sendQueue), Error::Intr);\n   internal::wakeupAllThreads(phys_addrof(queue->receiveQueue), Error::Intr);\n\n   std::memset(queue.get(), 0, sizeof(ThreadQueue));\n   return Error::OK;\n}\n\n\n/**\n * Insert a message to the back of the message queue.\n */\nError\nIOS_SendMessage(MessageQueueId id,\n                Message message,\n                MessageFlags flags)\n{\n   phys_ptr<MessageQueue> queue;\n   auto error = internal::getMessageQueueSafe(id, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::sendMessage(queue, message, flags);\n}\n\n\n/**\n * Insert a message to front of the message queue.\n */\nError\nIOS_JamMessage(MessageQueueId id,\n               Message message,\n               MessageFlags flags)\n{\n   phys_ptr<MessageQueue> queue;\n   auto error = internal::getMessageQueueSafe(id, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   while (queue->used == queue->size) {\n      if (flags & MessageFlags::NonBlocking) {\n         return Error::Max;\n      }\n\n      internal::sleepThread(phys_addrof(queue->sendQueue));\n      internal::reschedule();\n\n      auto thread = internal::getCurrentThread();\n      if (thread->context.queueWaitResult != Error::OK) {\n         return thread->context.queueWaitResult;\n      }\n   }\n\n   if (queue->first == 0) {\n      queue->first = queue->size - 1;\n   } else {\n      queue->first--;\n   }\n\n   queue->messages[queue->first] = message;\n   queue->used++;\n\n   internal::wakeupOneThread(phys_addrof(queue->receiveQueue), Error::OK);\n   internal::reschedule();\n   return Error::OK;\n}\n\n\n/**\n * Receive a message from the front of the message queue.\n */\nError\nIOS_ReceiveMessage(MessageQueueId id,\n                   phys_ptr<Message> message,\n                   MessageFlags flags)\n{\n   phys_ptr<MessageQueue> queue;\n   auto error = internal::getMessageQueueSafe(id, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return internal::receiveMessage(queue, message, flags);\n}\n\n\nnamespace internal\n{\n\n/**\n * Find a message queue from it's ID.\n */\nError\ngetMessageQueue(MessageQueueId id,\n                phys_ptr<MessageQueue> *outQueue)\n{\n   auto idx = static_cast<size_t>(id & 0xFFF);\n   if (idx >= sData->queues.size()) {\n      return Error::Invalid;\n   }\n\n   auto queue = phys_addrof(sData->queues[idx]);\n   if (outQueue) {\n      *outQueue = queue;\n   }\n\n   return Error::OK;\n}\n\n/**\n * Find a message queue from it's ID.\n *\n * Verifies that the queue belongs to the caller process.\n */\nError\ngetMessageQueueSafe(MessageQueueId id,\n                    phys_ptr<MessageQueue> *outQueue)\n{\n   auto idx = static_cast<size_t>(id & 0xFFF);\n   if (idx >= sData->queues.size()) {\n      return Error::Invalid;\n   }\n\n   auto queue = phys_addrof(sData->queues[idx]);\n   if (queue->pid != internal::getCurrentProcessId()) {\n      // Can only access queues belonging to same process.\n      return Error::Access;\n   }\n\n   if (outQueue) {\n      *outQueue = queue;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Get the message queue for this thread.\n *\n * Used for blocking requests.\n */\nphys_ptr<MessageQueue>\ngetCurrentThreadMessageQueue()\n{\n   return phys_addrof(sData->perThreadQueues[internal::getCurrentThreadId()]);\n}\n\n\n/**\n * Insert message to the back of the message queue.\n */\nError\nsendMessage(phys_ptr<MessageQueue> queue,\n            Message message,\n            MessageFlags flags)\n{\n   while (queue->used == queue->size) {\n      if (flags & MessageFlags::NonBlocking) {\n         return Error::Max;\n      }\n\n      internal::sleepThread(phys_addrof(queue->sendQueue));\n      internal::reschedule();\n\n      auto thread = internal::getCurrentThread();\n      if (thread->context.queueWaitResult != Error::OK) {\n         return thread->context.queueWaitResult;\n      }\n   }\n\n   auto index = (queue->first + queue->used) % queue->size;\n   queue->messages[index] = message;\n   queue->used++;\n\n   internal::wakeupOneThread(phys_addrof(queue->receiveQueue), Error::OK);\n   internal::reschedule();\n   return Error::OK;\n}\n\n\n/**\n * Receive a message from the front of the message queue.\n */\nError\nreceiveMessage(phys_ptr<MessageQueue> queue,\n               phys_ptr<Message> message,\n               MessageFlags flags)\n{\n   while (queue->used == 0) {\n      if (flags & MessageFlags::NonBlocking) {\n         return Error::Max;\n      }\n\n      internal::sleepThread(phys_addrof(queue->receiveQueue));\n      internal::reschedule();\n\n      auto thread = internal::getCurrentThread();\n      if (thread->context.queueWaitResult != Error::OK) {\n         return thread->context.queueWaitResult;\n      }\n   }\n\n   *message = queue->messages[queue->first];\n   queue->first = (queue->first + 1) % queue->size;\n   queue->used--;\n\n   internal::wakeupOneThread(phys_addrof(queue->sendQueue), Error::OK);\n   internal::reschedule();\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticMessageQueueData()\n{\n   sData = allocProcessStatic<StaticMessageQueueData>();\n\n   for (auto i = 0u; i < sData->perThreadQueues.size(); ++i) {\n      auto &queue = sData->perThreadQueues[i];\n\n      queue.used = 0u;\n      queue.first = 0u;\n      queue.size = 1u;\n      queue.messages = phys_addrof(sData->perThreadMesssages[i]);\n      queue.uid = -4;\n      queue.pid = uint8_t { 0 };\n      queue.flags = MessageQueueFlags::None;\n      queue.unk0x1E = uint16_t { 0 };\n\n      ThreadQueue_Initialise(phys_addrof(queue.receiveQueue));\n      ThreadQueue_Initialise(phys_addrof(queue.sendQueue));\n   }\n}\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_messagequeue.h",
    "content": "#pragma once\n#include \"ios_kernel_enum.h\"\n#include \"ios_kernel_threadqueue.h\"\n#include \"ios/ios_enum.h\"\n\n#include <common/cbool.h>\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::kernel\n{\n\nstatic constexpr auto MaxNumMessageQueues = 750u;\n\nstruct Thread;\n\nusing MessageQueueId = int32_t;\nusing Message = uint32_t;\n\nstruct MessageQueue\n{\n   be2_struct<ThreadQueue> receiveQueue;\n   be2_struct<ThreadQueue> sendQueue;\n   be2_val<uint32_t> used;\n   be2_val<uint32_t> first;\n   be2_val<uint32_t> size;\n   be2_phys_ptr<Message> messages;\n   be2_val<int32_t> uid;\n   be2_val<uint8_t> pid;\n   be2_val<MessageQueueFlags> flags;\n   be2_val<uint16_t> unk0x1E;\n};\nCHECK_OFFSET(MessageQueue, 0x00, receiveQueue);\nCHECK_OFFSET(MessageQueue, 0x04, sendQueue);\nCHECK_OFFSET(MessageQueue, 0x08, used);\nCHECK_OFFSET(MessageQueue, 0x0C, first);\nCHECK_OFFSET(MessageQueue, 0x10, size);\nCHECK_OFFSET(MessageQueue, 0x14, messages);\nCHECK_OFFSET(MessageQueue, 0x18, uid);\nCHECK_OFFSET(MessageQueue, 0x1C, pid);\nCHECK_OFFSET(MessageQueue, 0x1D, flags);\nCHECK_OFFSET(MessageQueue, 0x1E, unk0x1E);\nCHECK_SIZE(MessageQueue, 0x20);\n\nError\nIOS_CreateMessageQueue(phys_ptr<Message> messageBuffer,\n                       uint32_t numMessages);\n\nError\nIOS_DestroyMessageQueue(MessageQueueId id);\n\nError\nIOS_SendMessage(MessageQueueId id,\n                Message message,\n                MessageFlags flags);\n\nError\nIOS_JamMessage(MessageQueueId id,\n               Message message,\n               MessageFlags flags);\n\nError\nIOS_ReceiveMessage(MessageQueueId id,\n                   phys_ptr<Message> message,\n                   MessageFlags flags);\n\ntemplate<typename Type>\nMessage\nmakeMessage(phys_ptr<Type> ptr)\n{\n   return static_cast<Message>(phys_cast<phys_addr>(ptr).getAddress());\n}\n\ntemplate<typename Type>\nMessage\nmakeMessage(be2_phys_ptr<Type> ptr)\n{\n   return static_cast<Message>(phys_cast<phys_addr>(ptr).getAddress());\n}\n\ntemplate<typename Type>\nphys_ptr<Type>\nparseMessage(phys_ptr<Message> message)\n{\n   return phys_cast<Type *>(phys_addr { static_cast<Message>(*message) });\n}\n\nnamespace internal\n{\n\nError\ngetMessageQueue(MessageQueueId id,\n                phys_ptr<MessageQueue> *outQueue);\n\nError\ngetMessageQueueSafe(MessageQueueId id,\n                    phys_ptr<MessageQueue> *outQueue);\n\nphys_ptr<MessageQueue>\ngetCurrentThreadMessageQueue();\n\nError\nsendMessage(phys_ptr<MessageQueue> queue,\n            Message message,\n            MessageFlags flags);\n\nError\nreceiveMessage(phys_ptr<MessageQueue> queue,\n               phys_ptr<Message> message,\n               MessageFlags flags);\n\nvoid\ninitialiseStaticMessageQueueData();\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_otp.cpp",
    "content": "#include \"ios_kernel_enum.h\"\n#include \"ios_kernel_thread.h\"\n#include \"ios_kernel_otp.h\"\n\n#include \"decaf_config.h\"\n\n#include <array>\n#include <cstddef>\n#include <common/log.h>\n#include <fstream>\n\nnamespace ios::kernel\n{\n\nstatic bool sOtpLoaded = false;\nstatic std::array<std::byte, 0x400> sOtpData;\n\nError\nIOS_ReadOTP(OtpFieldIndex fieldIndex,\n            phys_ptr<void> buffer,\n            uint32_t bufferSize)\n{\n   auto thread = internal::getCurrentThread();\n   if (thread->pid != ProcessId::CRYPTO) {\n      return Error::Access;\n   }\n\n   if (!sOtpLoaded) {\n      return Error::NoExists;\n   }\n\n   auto offset = fieldIndex * 4;\n   if (offset + bufferSize > sOtpData.size()) {\n      return Error::Invalid;\n   }\n\n   std::memcpy(buffer.get(), sOtpData.data() + offset, bufferSize);\n   return Error::OK;\n}\n\nnamespace internal\n{\n\nError\ninitialiseOtp()\n{\n   auto config = decaf::config();\n   if (!config->system.otp_path.empty()) {\n      std::ifstream fh { config->system.otp_path, std::fstream::binary };\n      if (fh.is_open()) {\n         fh.read(reinterpret_cast<char *>(sOtpData.data()), sOtpData.size());\n         if (fh) {\n            sOtpLoaded = true;\n         }\n      }\n   }\n\n   if (!sOtpLoaded) {\n      gLog->warn(\"Failed to load otp.bin from {}\", config->system.otp_path);\n      return Error::NoExists;\n   }\n\n   return Error::OK;\n}\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_otp.h",
    "content": "#pragma once\n#include \"ios_kernel_enum.h\"\n#include \"ios/ios_error.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::kernel\n{\n\nError\nIOS_ReadOTP(OtpFieldIndex fieldIndex,\n            phys_ptr<void> buffer,\n            uint32_t bufferSize);\n\nnamespace internal\n{\n\nError\ninitialiseOtp();\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_process.cpp",
    "content": "#include \"ios_kernel_process.h\"\n#include \"ios_kernel_thread.h\"\n\n#include <array>\n#include <cstring>\n#include <common/frameallocator.h>\n#include <common/strutils.h>\n\nnamespace ios::kernel\n{\n\nstatic std::array<FrameAllocator, NumIosProcess>\nsProcessStaticAllocators;\n\nError\nIOS_GetCurrentProcessId()\n{\n   auto thread = internal::getCurrentThread();\n   if (!thread) {\n      return Error::Invalid;\n   }\n\n   return static_cast<Error>(thread->pid);\n}\n\nError\nIOS_GetProcessName(ProcessId process,\n                   char *buffer)\n{\n   const char *name = nullptr;\n\n   switch (process) {\n   case ProcessId::KERNEL:\n      name = \"IOS-KERNEL\";\n      break;\n   case ProcessId::MCP:\n      name = \"IOS-MCP\";\n      break;\n   case ProcessId::BSP:\n      name = \"IOS-BSP\";\n      break;\n   case ProcessId::CRYPTO:\n      name = \"IOS-CRYPTO\";\n      break;\n   case ProcessId::USB:\n      name = \"IOS-USB\";\n      break;\n   case ProcessId::FS:\n      name = \"IOS-FS\";\n      break;\n   case ProcessId::PAD:\n      name = \"IOS-PAD\";\n      break;\n   case ProcessId::NET:\n      name = \"IOS-NET\";\n      break;\n   case ProcessId::ACP:\n      name = \"IOS-ACP\";\n      break;\n   case ProcessId::NSEC:\n      name = \"IOS-NSEC\";\n      break;\n   case ProcessId::AUXIL:\n      name = \"IOS-AUXIL\";\n      break;\n   case ProcessId::NIM:\n      name = \"IOS-NIM\";\n      break;\n   case ProcessId::FPD:\n      name = \"IOS-FPD\";\n      break;\n   case ProcessId::TEST:\n      name = \"IOS-TEST\";\n      break;\n   case ProcessId::COSKERNEL:\n      name = \"COS-KERNEL\";\n      break;\n   case ProcessId::COSROOT:\n      name = \"COS-ROOT\";\n      break;\n   case ProcessId::COS02:\n      name = \"COS-02\";\n      break;\n   case ProcessId::COS03:\n      name = \"COS-03\";\n      break;\n   case ProcessId::COSOVERLAY:\n      name = \"COS-OVERLAY\";\n      break;\n   case ProcessId::COSHBM:\n      name = \"COS-HBM\";\n      break;\n   case ProcessId::COSERROR:\n      name = \"COS-ERROR\";\n      break;\n   case ProcessId::COSMASTER:\n      name = \"COS-MASTER\";\n      break;\n   default:\n      return Error::Invalid;\n   }\n\n   strcpy(buffer, name);\n   return Error::OK;\n}\n\n\nphys_ptr<char>\nallocProcessStatic(std::string_view str)\n{\n   auto buffer = phys_cast<char *>(\n      allocProcessStatic(internal::getCurrentProcessId(), str.size() + 1, 4));\n   std::copy(str.begin(), str.end(), buffer.get());\n   buffer[str.size()] = char { 0 };\n   return buffer;\n}\n\n\nphys_ptr<void>\nallocProcessStatic(ProcessId pid,\n                   size_t size,\n                   size_t align)\n{\n   decaf_check(pid < sProcessStaticAllocators.size());\n   auto &allocator = sProcessStaticAllocators[pid];\n   auto buffer = allocator.allocate(size, align);\n   std::memset(buffer, 0, size);\n   return phys_cast<void *>(cpu::translatePhysical(buffer));\n}\n\n\nphys_ptr<void>\nallocProcessLocalHeap(size_t size)\n{\n   auto pid = internal::getCurrentProcessId();\n   auto &allocator = sProcessStaticAllocators[pid];\n   auto buffer = allocator.allocate(size, 0x20);\n   std::memset(buffer, 0, size);\n   return phys_cast<void *>(cpu::translatePhysical(buffer));\n}\n\n\nnamespace internal\n{\n\nvoid\ninitialiseProcessStaticAllocators()\n{\n   struct PhysicalMemoryLayout\n   {\n      ProcessId id;\n      phys_addr addr;\n      uint32_t size;\n   };\n\n   static constexpr PhysicalMemoryLayout\n   ProcessMemoryLayout[] = {\n      { ProcessId::KERNEL,    phys_addr { 0x08120000 },   0xA0000 },\n      { ProcessId::MCP,       phys_addr { 0x081C0000 },   0xC0000 },\n      { ProcessId::BSP,       phys_addr { 0x13CC0000 },   0xC0000 },\n      { ProcessId::CRYPTO,    phys_addr { 0x08280000 },   0x40000 },\n      { ProcessId::USB,       phys_addr { 0x10100000 },  0x600000 },\n      { ProcessId::FS,        phys_addr { 0x10700000 }, 0x1800000 },\n      { ProcessId::PAD,       phys_addr { 0x11F00000 },  0x400000 },\n      { ProcessId::NET,       phys_addr { 0x12300000 },  0x600000 },\n      { ProcessId::ACP,       phys_addr { 0x12900000 },  0x2C0000 },\n      { ProcessId::NSEC,      phys_addr { 0x12BC0000 },  0x300000 },\n      { ProcessId::AUXIL,     phys_addr { 0x13C00000 },   0xC0000 },\n      { ProcessId::NIM,       phys_addr { 0x12EC0000 },  0x780000 },\n      { ProcessId::FPD,       phys_addr { 0x13640000 },  0x400000 },\n      { ProcessId::TEST,      phys_addr { 0x13A40000 },  0x1C0000 },\n   };\n\n   for (auto &layout : ProcessMemoryLayout) {\n      sProcessStaticAllocators[layout.id] =\n         FrameAllocator {\n            phys_cast<uint8_t *>(layout.addr).get(),\n            layout.size\n         };\n   }\n}\n\nProcessId\ngetCurrentProcessId()\n{\n   auto thread = getCurrentThread();\n   if (!thread) {\n      return ProcessId::KERNEL;\n   }\n\n   return thread->pid;\n}\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_process.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <string_view>\n#include <utility>\n\nnamespace ios::kernel\n{\n\nconstexpr auto NumIosProcess = 14;\n\nError\nIOS_GetCurrentProcessId();\n\nError\nIOS_GetProcessName(ProcessId process,\n                   char *buffer);\n\nphys_ptr<void>\nallocProcessStatic(ProcessId pid,\n                   size_t size,\n                   size_t align);\n\nphys_ptr<void>\nallocProcessLocalHeap(size_t size);\n\nphys_ptr<char>\nallocProcessStatic(std::string_view str);\n\nnamespace internal\n{\n\nProcessId\ngetCurrentProcessId();\n\nvoid\ninitialiseProcessStaticAllocators();\n\n} // namespace internal\n\ntemplate<typename Type>\ninline phys_ptr<Type>\nallocProcessStatic()\n{\n   auto data = allocProcessStatic(internal::getCurrentProcessId(), sizeof(Type), alignof(Type));\n   new (data.get()) Type { };\n   return phys_cast<Type *>(data);\n}\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_resourcemanager.cpp",
    "content": "#include \"ios_kernel_ipc_thread.h\"\n#include \"ios_kernel_messagequeue.h\"\n#include \"ios_kernel_resourcemanager.h\"\n#include \"ios_kernel_process.h\"\n#include \"ios/ios_enum_string.h\"\n#include \"ios/ios_stackobject.h\"\n#include \"cafe/kernel/cafe_kernel_ipckdriver.h\"\n\n#include <common/log.h>\n#include <common/strutils.h>\n#include <libcpu/cpu_formatters.h>\n#include <map>\n#include <string>\n#include <string_view>\n\nnamespace ios::kernel\n{\n\nstruct StaticResourceManagerData\n{\n   be2_val<BOOL> registrationEnabled = TRUE;\n   be2_struct<ResourceManagerList> resourceManagerList;\n   be2_struct<ResourceRequestList> resourceRequestList;\n   be2_array<ResourceHandleManager, ProcessId::Max> resourceHandleManagers;\n   be2_val<uint32_t> totalOpenedHandles = 0u;\n};\n\nstatic phys_ptr<StaticResourceManagerData>\nsData;\n\nnamespace internal\n{\n\nstatic Error\nfindResourceManager(std::string_view device,\n                    phys_ptr<ResourceManager> *outResourceManager);\n\nstatic phys_ptr<ResourceHandleManager>\ngetResourceHandleManager(ProcessId id);\n\nstatic Error\nallocResourceRequest(phys_ptr<ResourceHandleManager> resourceHandleManager,\n                     CpuId cpuId,\n                     phys_ptr<ResourceManager> resourceManager,\n                     phys_ptr<MessageQueue> messageQueue,\n                     phys_ptr<IpcRequest> ipcRequest,\n                     phys_ptr<ResourceRequest> *outResourceRequest);\n\nstatic Error\nfreeResourceRequest(phys_ptr<ResourceRequest> resourceRequest);\n\nstatic Error\nallocResourceHandle(phys_ptr<ResourceHandleManager> resourceHandleManager,\n                    phys_ptr<ResourceManager> resourceManager,\n                    ResourceHandleId *outResourceHandleId);\n\nstatic Error\nfreeResourceHandle(phys_ptr<ResourceHandleManager> resourceHandleManager,\n                   ResourceHandleId id);\n\nstatic Error\ngetResourceHandle(ResourceHandleId id,\n                  phys_ptr<ResourceHandleManager> resourceHandleManager,\n                  phys_ptr<ResourceHandle> *outResourceHandle);\n\nstatic Error\ndispatchResourceReply(phys_ptr<ResourceRequest> resourceRequest,\n                      Error reply,\n                      bool freeRequest);\n\nstatic Error\ndispatchRequest(phys_ptr<ResourceRequest> request);\n\n} // namespace internal\n\n\n/**\n * Enable or disable any further resource manager registrations.\n *\n * \\returns Error:Access\n * Registration can only be disabled or enabled from the MCP process.\n *\n * \\returns Error::Busy\n * For some reason this function returns Error::Busy on success.\n */\nError\nIOS_SetResourceManagerRegistrationDisabled(bool disable)\n{\n   auto pid = internal::getCurrentProcessId();\n   if (pid != ProcessId::MCP) {\n      return Error::Access;\n   }\n\n   sData->registrationEnabled = disable ? FALSE : TRUE;\n   return Error::Busy;\n}\n\n\n/**\n * Register a message queue to receive messages for a device.\n */\nError\nIOS_RegisterResourceManager(std::string_view device,\n                            MessageQueueId queue)\n{\n   auto pid = internal::getCurrentProcessId();\n   auto resourceHandleManager = internal::getResourceHandleManager(pid);\n   if (!resourceHandleManager) {\n      return Error::Invalid;\n   }\n\n   auto error = internal::findResourceManager(device, nullptr);\n   if (error >= Error::OK) {\n      return Error::Exists;\n   }\n\n   if (!sData->registrationEnabled) {\n      return Error::NotReady;\n   }\n\n   auto &resourceManagerList = sData->resourceManagerList;\n   auto resourceManagerIdx = resourceManagerList.firstFreeIdx;\n   if (resourceHandleManager->numResourceManagers >= resourceHandleManager->maxResourceManagers ||\n       resourceManagerIdx < 0) {\n      return Error::FailAlloc;\n   }\n\n   auto &resourceManager = resourceManagerList.resourceManagers[resourceManagerIdx];\n   auto nextFreeIdx = resourceManager.nextResourceManagerIdx;\n\n   if (nextFreeIdx < 0) {\n      resourceManagerList.firstFreeIdx = int16_t { -1 };\n      resourceManagerList.lastFreeIdx = int16_t { -1 };\n   } else {\n      auto &nextFreeResourceManager = resourceManagerList.resourceManagers[nextFreeIdx];\n      nextFreeResourceManager.prevResourceManagerIdx = int16_t { -1 };\n      resourceManagerList.firstFreeIdx = nextFreeIdx;\n   }\n\n   resourceManager.queueId = queue;\n\n   string_copy(phys_addrof(resourceManager.device).get(),\n               device.data(),\n               resourceManager.device.size());\n   resourceManager.deviceLen = static_cast<uint16_t>(device.size());\n\n   resourceManager.numRequests = uint16_t { 0u };\n   resourceManager.firstRequestIdx = int16_t { -1 };\n   resourceManager.lastRequestIdx = int16_t { -1 };\n\n   resourceManager.prevResourceManagerIdx = int16_t { -1 };\n   resourceManager.nextResourceManagerIdx = int16_t { -1 };\n\n   resourceManager.numHandles = uint16_t { 0u };\n   resourceManager.unk0x3C = uint16_t { 8u };\n\n   resourceManagerList.numRegistered++;\n   if (resourceManagerList.numRegistered > resourceManagerList.mostRegistered) {\n      resourceManagerList.mostRegistered = resourceManagerList.numRegistered;\n   }\n\n   resourceHandleManager->numResourceManagers++;\n   resourceManager.resourceHandleManager = resourceHandleManager;\n\n   if (resourceManagerList.firstRegisteredIdx < 0) {\n      resourceManagerList.firstRegisteredIdx = resourceManagerIdx;\n      resourceManagerList.lastRegisteredIdx = resourceManagerIdx;\n   } else {\n      auto insertBeforeIndex = resourceManagerList.firstRegisteredIdx;\n      while (insertBeforeIndex >= 0) {\n         auto &other = resourceManagerList.resourceManagers[insertBeforeIndex];\n         if (device.compare(phys_addrof(other.device).get()) < 0) {\n            break;\n         }\n\n         insertBeforeIndex = other.nextResourceManagerIdx;\n      }\n\n      if (insertBeforeIndex == resourceManagerList.firstRegisteredIdx) {\n         // Insert at front\n         auto &insertBefore = resourceManagerList.resourceManagers[insertBeforeIndex];\n         insertBefore.prevResourceManagerIdx = resourceManagerIdx;\n         resourceManager.nextResourceManagerIdx = insertBeforeIndex;\n         resourceManagerList.firstRegisteredIdx = resourceManagerIdx;\n      } else if (insertBeforeIndex < 0) {\n         // Insert at back\n         auto insertAfterIndex = resourceManagerList.lastRegisteredIdx;\n         auto &insertAfter = resourceManagerList.resourceManagers[insertAfterIndex];\n         insertAfter.nextResourceManagerIdx = resourceManagerIdx;\n         resourceManager.prevResourceManagerIdx = insertAfterIndex;\n         resourceManagerList.lastRegisteredIdx = resourceManagerIdx;\n      } else {\n         // Insert in middle\n         auto &insertBefore = resourceManagerList.resourceManagers[insertBeforeIndex];\n         auto insertAfterIndex = insertBefore.prevResourceManagerIdx;\n         auto &insertAfter = resourceManagerList.resourceManagers[insertAfterIndex];\n\n         insertAfter.nextResourceManagerIdx = resourceManagerIdx;\n         insertBefore.prevResourceManagerIdx = resourceManagerIdx;\n\n         resourceManager.nextResourceManagerIdx = insertBeforeIndex;\n         resourceManager.prevResourceManagerIdx = insertAfterIndex;\n      }\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Associate a resource manager with a permission group (matching cos.xml)\n */\nError\nIOS_AssociateResourceManager(std::string_view device,\n                             ResourcePermissionGroup group)\n{\n   auto resourceManager = phys_ptr<ResourceManager> { nullptr };\n   auto error = internal::findResourceManager(device, &resourceManager);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto pid = internal::getCurrentProcessId();\n   if (pid != resourceManager->resourceHandleManager->processId) {\n      return Error::Access;\n   }\n\n   resourceManager->permissionGroup = group;\n   return Error::OK;\n}\n\n\n/**\n * Send a reply to a resource request.\n */\nError\nIOS_ResourceReply(phys_ptr<ResourceRequest> resourceRequest,\n                  Error reply)\n{\n   // Get the resource handle manager for the current process\n   auto pid = internal::getCurrentProcessId();\n   auto resourceHandleManager = internal::getResourceHandleManager(pid);\n   if (!resourceHandleManager) {\n      resourceHandleManager->failedResourceReplies++;\n      return Error::Invalid;\n   }\n\n   // Calculate the request index\n   auto &resourceRequestList = sData->resourceRequestList;\n   auto resourceRequestIndex = resourceRequest - phys_addrof(resourceRequestList.resourceRequests[0]);\n   if (resourceRequestIndex < 0 ||\n       resourceRequestIndex >= resourceRequestList.resourceRequests.size()) {\n      resourceHandleManager->failedResourceReplies++;\n      return Error::Invalid;\n   }\n\n   if (resourceRequest != phys_addrof(resourceRequestList.resourceRequests[resourceRequestIndex]) ||\n      resourceHandleManager != resourceRequest->resourceManager->resourceHandleManager) {\n      resourceHandleManager->failedResourceReplies++;\n      return Error::Invalid;\n   }\n\n   auto resourceHandle = phys_ptr<ResourceHandle> { nullptr };\n   auto requestHandleManager = resourceRequest->resourceHandleManager;\n   auto error = internal::getResourceHandle(resourceRequest->resourceHandleId,\n                                            requestHandleManager,\n                                            &resourceHandle);\n   if (error < Error::OK) {\n      gLog->warn(\"IOS_ResourceReply({}, {}) passed invalid resource request.\",\n                 phys_cast<phys_addr>(resourceRequest),\n                 reply);\n      resourceHandleManager->failedResourceReplies++;\n   } else if (resourceRequest->requestData.command == Command::Open) {\n      if (reply < Error::OK) {\n         // Resource open failed, free the resource handle.\n         internal::freeResourceHandle(requestHandleManager,\n                                      resourceRequest->resourceHandleId);\n      } else {\n         // Resource open succeeded, save the resource handle.\n         resourceHandle->handle = static_cast<int32_t>(reply);\n         resourceHandle->state = ResourceHandleState::Open;\n         reply = static_cast<Error>(resourceHandle->id);\n      }\n   } else if (resourceRequest->requestData.command == Command::Close) {\n      // Resource closed, close the resource handle.\n      internal::freeResourceHandle(requestHandleManager,\n                                   resourceRequest->resourceHandleId);\n   }\n\n   return internal::dispatchResourceReply(resourceRequest, reply, true);\n}\n\n\n/**\n * Set the client capability mask for a specific process & feature ID.\n *\n * Can only be set by the MCP process.\n */\nError\nIOS_SetClientCapabilities(ProcessId pid,\n                          FeatureId featureId,\n                          phys_ptr<uint64_t> mask)\n{\n   if (internal::getCurrentProcessId() != ProcessId::MCP) {\n      return Error::Access;\n   }\n\n   return internal::setClientCapability(pid, featureId, *mask);\n}\n\nError\nIOS_SetProcessTitle(ProcessId process,\n                    TitleId title,\n                    GroupId group)\n{\n   if (internal::getCurrentProcessId() != ProcessId::MCP) {\n      return Error::Access;\n   }\n\n   auto resourceHandleManager = internal::getResourceHandleManager(process);\n   if (!resourceHandleManager) {\n      return Error::InvalidArg;\n   }\n\n   resourceHandleManager->titleId = title;\n   resourceHandleManager->groupId = group;\n   return Error::OK;\n}\n\nnamespace internal\n{\n\n\n/**\n * Find a registered ResourceManager for the given device name.\n */\nstatic Error\nfindResourceManager(std::string_view device,\n                    phys_ptr<ResourceManager> *outResourceManager)\n{\n   auto index = sData->resourceManagerList.firstRegisteredIdx;\n   while (index >= 0) {\n      auto &resourceManager = sData->resourceManagerList.resourceManagers[index];\n      auto resourceManagerDevice = std::string_view {\n            phys_addrof(resourceManager.device).get(),\n            resourceManager.deviceLen\n         };\n\n      if (device.compare(resourceManagerDevice) == 0) {\n         if (outResourceManager) {\n            *outResourceManager = phys_addrof(resourceManager);\n         }\n\n         return Error::OK;\n      }\n\n      index = resourceManager.nextResourceManagerIdx;\n   }\n\n   return Error::NoExists;\n}\n\n\n/**\n * Get the ResourceHandleManager for the specified process.\n */\nstatic phys_ptr<ResourceHandleManager>\ngetResourceHandleManager(ProcessId id)\n{\n   if (id >= ProcessId::Max) {\n      return nullptr;\n   }\n\n   return phys_addrof(sData->resourceHandleManagers[id]);\n}\n\n\n/**\n * Allocate a ResourceRequest.\n */\nstatic Error\nallocResourceRequest(phys_ptr<ResourceHandleManager> resourceHandleManager,\n                     CpuId cpuId,\n                     phys_ptr<ResourceManager> resourceManager,\n                     phys_ptr<MessageQueue> messageQueue,\n                     phys_ptr<IpcRequest> ipcRequest,\n                     phys_ptr<ResourceRequest> *outResourceRequest)\n{\n   if (resourceHandleManager->numResourceRequests >= resourceHandleManager->maxResourceRequests) {\n      return Error::ClientTxnLimit;\n   }\n\n   // Find a free resource request to allocate.\n   auto &resourceRequestList = sData->resourceRequestList;\n   if (resourceRequestList.firstFreeIdx < 0) {\n      return Error::FailAlloc;\n   }\n\n   auto resourceRequestIdx = resourceRequestList.firstFreeIdx;\n   auto resourceRequest = phys_addrof(resourceRequestList.resourceRequests[resourceRequestIdx]);\n\n   auto nextFreeIdx = resourceRequest->nextIdx;\n   if (nextFreeIdx < 0) {\n      resourceRequestList.firstFreeIdx = int16_t { -1 };\n      resourceRequestList.lastFreeIdx = int16_t { -1 };\n   } else {\n      auto &nextFreeResourceRequest = resourceRequestList.resourceRequests[nextFreeIdx];\n      nextFreeResourceRequest.prevIdx = int16_t { -1 };\n      resourceRequestList.firstFreeIdx = nextFreeIdx;\n   }\n\n   resourceRequest->resourceHandleId = static_cast<ResourceHandleId>(Error::Invalid);\n   resourceRequest->messageQueueId = messageQueue->uid;\n   resourceRequest->messageQueue = messageQueue;\n   resourceRequest->resourceHandleManager = resourceHandleManager;\n   resourceRequest->resourceManager = resourceManager;\n   resourceRequest->ipcRequest = ipcRequest;\n\n   resourceRequest->requestData.cpuId = cpuId;\n   resourceRequest->requestData.processId = resourceHandleManager->processId;\n   resourceRequest->requestData.titleId = resourceHandleManager->titleId;\n   resourceRequest->requestData.groupId = resourceHandleManager->groupId;\n\n   // Insert into the allocated request list.\n   resourceRequest->nextIdx = int16_t { -1 };\n   resourceRequest->prevIdx = resourceManager->lastRequestIdx;\n\n   if (resourceManager->lastRequestIdx < 0) {\n      resourceManager->firstRequestIdx = resourceRequestIdx;\n      resourceManager->lastRequestIdx = resourceRequestIdx;\n   } else {\n      auto &lastRequest = resourceRequestList.resourceRequests[resourceManager->lastRequestIdx];\n      lastRequest.nextIdx = resourceRequestIdx;\n      resourceManager->lastRequestIdx = resourceRequestIdx;\n   }\n\n   // Increment our counters!\n   resourceManager->numRequests++;\n\n   resourceRequestList.numRegistered++;\n   if (resourceRequestList.numRegistered > resourceRequestList.mostRegistered) {\n      resourceRequestList.mostRegistered = resourceRequestList.numRegistered;\n   }\n\n   resourceHandleManager->numResourceRequests++;\n   if (resourceHandleManager->numResourceRequests > resourceHandleManager->mostResourceRequests) {\n      resourceHandleManager->mostResourceRequests = resourceHandleManager->numResourceRequests;\n   }\n\n   *outResourceRequest = resourceRequest;\n   return Error::OK;\n}\n\n\n/**\n * Free a ResourceRequest.\n */\nstatic Error\nfreeResourceRequest(phys_ptr<ResourceRequest> resourceRequest)\n{\n   auto &resourceRequestList = sData->resourceRequestList;\n   auto resourceManager = resourceRequest->resourceManager;\n\n   if (!resourceManager) {\n      return Error::Invalid;\n   }\n\n   // Remove from the request list\n   auto resourceRequestIndex = static_cast<int16_t>(resourceRequest - phys_addrof(resourceRequestList.resourceRequests[0]));\n   auto lastFreeIdx = resourceRequestList.lastFreeIdx;\n   auto nextIdx = resourceRequest->nextIdx;\n   auto prevIdx = resourceRequest->prevIdx;\n\n   if (nextIdx >= 0) {\n      auto &nextResourceRequest = resourceRequestList.resourceRequests[nextIdx];\n      nextResourceRequest.prevIdx = prevIdx;\n   }\n\n   if (prevIdx >= 0) {\n      auto &prevResourceRequest = resourceRequestList.resourceRequests[prevIdx];\n      prevResourceRequest.nextIdx = nextIdx;\n   }\n\n   if (resourceManager->firstRequestIdx == resourceRequestIndex) {\n      resourceManager->firstRequestIdx = nextIdx;\n   }\n\n   if (resourceManager->lastRequestIdx == resourceRequestIndex) {\n      resourceManager->lastRequestIdx = prevIdx;\n   }\n\n   // Decrement our counters!\n   auto resourceHandleManager = resourceRequest->resourceHandleManager;\n   resourceHandleManager->numResourceRequests--;\n   resourceRequestList.numRegistered--;\n   resourceManager->numRequests--;\n\n   // Reset the resource request\n   std::memset(resourceRequest.get(), 0, sizeof(ResourceRequest));\n   resourceRequest->prevIdx = lastFreeIdx;\n   resourceRequest->nextIdx = int16_t { -1 };\n\n   // Reinsert into free list\n   resourceRequestList.lastFreeIdx = resourceRequestIndex;\n\n   if (lastFreeIdx < 0) {\n      resourceRequestList.firstFreeIdx = resourceRequestIndex;\n   } else {\n      auto &lastFreeResourceRequest = resourceRequestList.resourceRequests[lastFreeIdx];\n      lastFreeResourceRequest.nextIdx = resourceRequestIndex;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Allocate a ResourceHandle.\n */\nstatic Error\nallocResourceHandle(phys_ptr<ResourceHandleManager> resourceHandleManager,\n                    phys_ptr<ResourceManager> resourceManager,\n                    ResourceHandleId *outResourceHandleId)\n{\n   // Check if we have a free resource handle to register.\n   if (resourceHandleManager->numResourceHandles >= resourceHandleManager->maxResourceHandles) {\n      return Error::Max;\n   }\n\n   // Find a free resource handle\n   phys_ptr<ResourceHandle> resourceHandle = nullptr;\n   auto resourceHandleIdx = 0u;\n\n   for (auto i = 0u; i < resourceHandleManager->handles.size(); ++i) {\n      if (resourceHandleManager->handles[i].state == ResourceHandleState::Free) {\n         resourceHandle = phys_addrof(resourceHandleManager->handles[i]);\n         resourceHandleIdx = i;\n         break;\n      }\n   }\n\n   // Double check we have one... should never happen really.\n   if (!resourceHandle) {\n      return Error::Max;\n   }\n\n   resourceHandle->id = static_cast<ResourceHandleId>(resourceHandleIdx | ((sData->totalOpenedHandles << 12) & 0x7FFFFFFF));\n   resourceHandle->resourceManager = resourceManager;\n   resourceHandle->state = ResourceHandleState::Opening;\n   resourceHandle->handle = -10;\n\n   *outResourceHandleId = resourceHandle->id;\n   return Error::OK;\n}\n\n\n/**\n * Free a ResourceHandle.\n */\nstatic Error\nfreeResourceHandle(phys_ptr<ResourceHandleManager> resourceHandleManager,\n                   ResourceHandleId id)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n   auto error = getResourceHandle(id, resourceHandleManager, &resourceHandle);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto resourceManager = resourceHandle->resourceManager;\n   resourceHandle->handle = -4;\n   resourceHandle->resourceManager = nullptr;\n   resourceHandle->state = ResourceHandleState::Free;\n   resourceHandle->id = static_cast<ResourceHandleId>(Error::Invalid);\n\n   resourceHandleManager->numResourceHandles--;\n   resourceManager->numHandles--;\n   return Error::OK;\n}\n\n\n/**\n * Get ResourceHandle by id.\n */\nstatic Error\ngetResourceHandle(ResourceHandleId id,\n                  phys_ptr<ResourceHandleManager> resourceHandleManager,\n                  phys_ptr<ResourceHandle> *outResourceHandle)\n{\n   auto index = id & 0xFFF;\n\n   if (id < 0 || index >= static_cast<ResourceHandleId>(resourceHandleManager->handles.size())) {\n      return Error::Invalid;\n   }\n\n   if (resourceHandleManager->handles[index].id != id) {\n      return Error::NoExists;\n   }\n\n   *outResourceHandle = phys_addrof(resourceHandleManager->handles[index]);\n   return Error::OK;\n}\n\n\n/**\n * Lookup an open resource handle.\n */\nstatic Error\ngetOpenResource(ProcessId pid,\n                ResourceHandleId id,\n                phys_ptr<ResourceHandleManager> *outResourceHandleManager,\n                phys_ptr<ResourceHandle> *outResourceHandle)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n\n   // Try get the resource handle manager for this process.\n   auto resourceHandleManager = getResourceHandleManager(pid);\n   if (!resourceHandleManager) {\n      return Error::Invalid;\n   }\n\n   auto error = getResourceHandle(id, resourceHandleManager, &resourceHandle);\n   if (error) {\n      return error;\n   }\n\n   if (resourceHandle->state == ResourceHandleState::Closed) {\n      return Error::StaleHandle;\n   } else if (resourceHandle->state != ResourceHandleState::Open) {\n      return Error::Invalid;\n   }\n\n   *outResourceHandle = resourceHandle;\n   *outResourceHandleManager = resourceHandleManager;\n   return Error::OK;\n}\n\n\n/**\n * Dispatch a reply to a ResourceRequest.\n */\nstatic Error\ndispatchResourceReply(phys_ptr<ResourceRequest> resourceRequest,\n                      Error reply,\n                      bool freeRequest)\n{\n   auto error = Error::Invalid;\n   phys_ptr<IpcRequest> ipcRequest = resourceRequest->ipcRequest;\n   ipcRequest->command = Command::Reply;\n   ipcRequest->reply = reply;\n\n   if (resourceRequest->messageQueueId == getIpcMessageQueueId()) {\n      cafe::kernel::ipckDriverIosSubmitReply(ipcRequest);\n      error = Error::OK;\n   } else {\n      phys_ptr<MessageQueue> queue = nullptr;\n\n      if (resourceRequest->messageQueueId < 0) {\n         queue = resourceRequest->messageQueue;\n      } else {\n         error = getMessageQueue(resourceRequest->messageQueueId, &queue);\n      }\n\n      if (queue) {\n         error = sendMessage(queue,\n                             makeMessage(ipcRequest),\n                             MessageFlags::NonBlocking);\n      }\n   }\n\n   if (freeRequest) {\n      freeResourceRequest(resourceRequest);\n   }\n\n   return error;\n}\n\n\n/**\n * Dispatch a request to it's message queue.\n */\nstatic Error\ndispatchRequest(phys_ptr<ResourceRequest> request)\n{\n   phys_ptr<MessageQueue> queue;\n   auto error = getMessageQueue(request->resourceManager->queueId, &queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return sendMessage(queue, makeMessage(request), MessageFlags::NonBlocking);\n}\n\n\n/**\n * Dispatch an IOS_Open request.\n */\nError\ndispatchIosOpen(std::string_view device,\n                OpenMode mode,\n                phys_ptr<MessageQueue> queue,\n                phys_ptr<IpcRequest> ipcRequest,\n                ProcessId pid,\n                CpuId cpuId)\n{\n   phys_ptr<ClientCapability> clientCapability;\n   phys_ptr<ResourceManager> resourceManager;\n   phys_ptr<ResourceRequest> resourceRequest;\n   ResourceHandleId resourceHandleId;\n\n   // Try get the resource handle manager for this process.\n   auto resourceHandleManager = internal::getResourceHandleManager(pid);\n   if (!resourceHandleManager) {\n      return Error::Invalid;\n   }\n\n   // Try find the resource manager for this device name.\n   auto error = internal::findResourceManager(device, &resourceManager);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Try get the cap bits\n   error = getClientCapability(resourceHandleManager,\n                               resourceManager->permissionGroup,\n                               &clientCapability);\n   if (error < Error::OK) {\n      return Error::Access;\n   }\n\n   // Try allocate a resource request.\n   error = allocResourceRequest(resourceHandleManager,\n                                cpuId,\n                                resourceManager,\n                                queue,\n                                ipcRequest,\n                                &resourceRequest);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   resourceRequest->requestData.command = Command::Open;\n\n   // Set the IOS_Open arguments\n   resourceRequest->requestData.args.open.name = phys_addrof(resourceRequest->openNameBuffer);\n   resourceRequest->requestData.args.open.mode = mode;\n   resourceRequest->requestData.args.open.caps = clientCapability->mask;\n\n   string_copy(phys_addrof(resourceRequest->openNameBuffer).get(),\n               device.data(),\n               resourceRequest->openNameBuffer.size());\n\n   // Try allocate a resource handle.\n   error = allocResourceHandle(resourceHandleManager,\n                               resourceManager,\n                               &resourceHandleId);\n   if (error < Error::OK) {\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   resourceRequest->resourceHandleId = resourceHandleId;\n\n   // Increment our counters!\n   sData->totalOpenedHandles++;\n   resourceManager->numHandles++;\n   resourceHandleManager->numResourceHandles++;\n\n   // Try dispatch the request to the relevant resource manager.\n   error = dispatchRequest(resourceRequest);\n   if (error < Error::OK) {\n      freeResourceHandle(resourceHandleManager, resourceHandleId);\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Dispatch an IOS_Close request.\n */\nError\ndispatchIosClose(ResourceHandleId resourceHandleId,\n                 phys_ptr<MessageQueue> queue,\n                 phys_ptr<IpcRequest> ipcRequest,\n                 uint32_t unkArg0,\n                 ProcessId pid,\n                 CpuId cpuId)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n   phys_ptr<ResourceRequest> resourceRequest;\n\n   // Try get the resource handle manager for this process.\n   auto resourceHandleManager = getResourceHandleManager(pid);\n   if (!resourceHandleManager) {\n      return Error::Invalid;\n   }\n\n   // Try lookup the resource handle.\n   auto error = getResourceHandle(resourceHandleId,\n                                  resourceHandleManager,\n                                  &resourceHandle);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // If the handle no longer has a resource manager associated with it then\n   // we need to hack in a resource request.\n   auto resourceManager = resourceHandle->resourceManager;\n   if (!resourceManager) {\n      ios::StackObject<ResourceRequest> directResourceRequest;\n      std::memset(directResourceRequest.get(), 0, sizeof(ResourceRequest));\n      directResourceRequest->requestData.command = Command::Close;\n      directResourceRequest->requestData.cpuId = cpuId;\n      directResourceRequest->requestData.processId = pid;\n      directResourceRequest->requestData.handle = resourceHandleId;\n      directResourceRequest->ipcRequest = ipcRequest;\n      directResourceRequest->messageQueue = queue;\n      directResourceRequest->messageQueueId = queue->uid;\n\n      freeResourceHandle(resourceHandleManager, resourceHandleId);\n      return dispatchRequest(directResourceRequest);\n   }\n\n   // Try allocate a resource request.\n   error = allocResourceRequest(resourceHandleManager,\n                                cpuId,\n                                resourceManager,\n                                queue,\n                                ipcRequest,\n                                &resourceRequest);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   resourceRequest->requestData.command = Command::Close;\n   resourceRequest->resourceHandleId = resourceHandleId;\n   resourceRequest->requestData.handle = resourceHandle->handle;\n   resourceRequest->requestData.args.close.unkArg0 = unkArg0;\n\n   auto previousResourceHandleState = resourceHandle->state;\n   resourceHandle->state = ResourceHandleState::Closed;\n\n   // Try dispatch the request to the relevant resource manager.\n   error = dispatchRequest(resourceRequest);\n   if (error < Error::OK) {\n      resourceHandle->state = previousResourceHandleState;\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Dispatch an IOS_Read request.\n */\nError\ndispatchIosRead(ResourceHandleId resourceHandleId,\n                phys_ptr<void> buffer,\n                uint32_t length,\n                phys_ptr<MessageQueue> queue,\n                phys_ptr<IpcRequest> ipcRequest,\n                ProcessId pid,\n                CpuId cpuId)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n   phys_ptr<ResourceHandleManager> resourceHandleManager;\n   phys_ptr<ResourceRequest> resourceRequest;\n\n   auto error = getOpenResource(pid, resourceHandleId, &resourceHandleManager, &resourceHandle);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   if (length && !buffer) {\n      return Error::Access;\n   }\n\n   // Try allocate a resource request.\n   error = allocResourceRequest(resourceHandleManager,\n                                cpuId,\n                                resourceHandle->resourceManager,\n                                queue,\n                                ipcRequest,\n                                &resourceRequest);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   resourceRequest->requestData.command = Command::Read;\n   resourceRequest->resourceHandleId = resourceHandleId;\n   resourceRequest->requestData.handle = resourceHandle->handle;\n   resourceRequest->requestData.args.read.data = buffer;\n   resourceRequest->requestData.args.read.length = length;\n\n   // Try dispatch the request to the relevant resource manager.\n   error = dispatchRequest(resourceRequest);\n   if (error < Error::OK) {\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Dispatch an IOS_Write request.\n */\nError\ndispatchIosWrite(ResourceHandleId resourceHandleId,\n                 phys_ptr<const void> buffer,\n                 uint32_t length,\n                 phys_ptr<MessageQueue> queue,\n                 phys_ptr<IpcRequest> ipcRequest,\n                 ProcessId pid,\n                 CpuId cpuId)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n   phys_ptr<ResourceHandleManager> resourceHandleManager;\n   phys_ptr<ResourceRequest> resourceRequest;\n\n   auto error = getOpenResource(pid,\n                                resourceHandleId,\n                                &resourceHandleManager,\n                                &resourceHandle);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   if (length && !buffer) {\n      return Error::Access;\n   }\n\n   // Try allocate a resource request.\n   error = allocResourceRequest(resourceHandleManager,\n                                cpuId,\n                                resourceHandle->resourceManager,\n                                queue,\n                                ipcRequest,\n                                &resourceRequest);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   resourceRequest->requestData.command = Command::Write;\n   resourceRequest->resourceHandleId = resourceHandleId;\n   resourceRequest->requestData.handle = resourceHandle->handle;\n   resourceRequest->requestData.args.write.data = buffer;\n   resourceRequest->requestData.args.write.length = length;\n\n   // Try dispatch the request to the relevant resource manager.\n   error = dispatchRequest(resourceRequest);\n   if (error < Error::OK) {\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Dispatch an IOS_Seek request.\n */\nError\ndispatchIosSeek(ResourceHandleId resourceHandleId,\n                uint32_t offset,\n                SeekOrigin origin,\n                phys_ptr<MessageQueue> queue,\n                phys_ptr<IpcRequest> ipcRequest,\n                ProcessId pid,\n                CpuId cpuId)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n   phys_ptr<ResourceHandleManager> resourceHandleManager;\n   phys_ptr<ResourceRequest> resourceRequest;\n\n   auto error = getOpenResource(pid,\n                                resourceHandleId,\n                                &resourceHandleManager,\n                                &resourceHandle);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Try allocate a resource request.\n   error = allocResourceRequest(resourceHandleManager,\n                                cpuId,\n                                resourceHandle->resourceManager,\n                                queue,\n                                ipcRequest,\n                                &resourceRequest);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   resourceRequest->requestData.command = Command::Seek;\n   resourceRequest->resourceHandleId = resourceHandleId;\n   resourceRequest->requestData.handle = resourceHandle->handle;\n   resourceRequest->requestData.args.seek.offset = offset;\n   resourceRequest->requestData.args.seek.origin = origin;\n\n   // Try dispatch the request to the relevant resource manager.\n   error = dispatchRequest(resourceRequest);\n   if (error < Error::OK) {\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Dispatch an IOS_Ioctl request.\n */\nError\ndispatchIosIoctl(ResourceHandleId resourceHandleId,\n                 uint32_t ioctlRequest,\n                 phys_ptr<const void> inputBuffer,\n                 uint32_t inputLength,\n                 phys_ptr<void> outputBuffer,\n                 uint32_t outputLength,\n                 phys_ptr<MessageQueue> queue,\n                 phys_ptr<IpcRequest> ipcRequest,\n                 ProcessId pid,\n                 CpuId cpuId)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n   phys_ptr<ResourceHandleManager> resourceHandleManager;\n   phys_ptr<ResourceRequest> resourceRequest;\n\n   auto error = getOpenResource(pid,\n                                resourceHandleId,\n                                &resourceHandleManager,\n                                &resourceHandle);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   if ((inputLength && !inputBuffer) || (outputLength && !outputBuffer)) {\n      return Error::Access;\n   }\n\n   // Try allocate a resource request.\n   error = allocResourceRequest(resourceHandleManager,\n                                cpuId,\n                                resourceHandle->resourceManager,\n                                queue,\n                                ipcRequest,\n                                &resourceRequest);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   resourceRequest->requestData.command = Command::Ioctl;\n   resourceRequest->resourceHandleId = resourceHandleId;\n   resourceRequest->requestData.handle = resourceHandle->handle;\n   resourceRequest->requestData.args.ioctl.request = ioctlRequest;\n   resourceRequest->requestData.args.ioctl.inputBuffer = inputBuffer;\n   resourceRequest->requestData.args.ioctl.inputLength = inputLength;\n   resourceRequest->requestData.args.ioctl.outputBuffer = outputBuffer;\n   resourceRequest->requestData.args.ioctl.outputLength = outputLength;\n\n   // Try dispatch the request to the relevant resource manager.\n   error = dispatchRequest(resourceRequest);\n   if (error < Error::OK) {\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Dispatch an IOS_Ioctlv request.\n */\nError\ndispatchIosIoctlv(ResourceHandleId resourceHandleId,\n                  uint32_t ioctlRequest,\n                  uint32_t numVecIn,\n                  uint32_t numVecOut,\n                  phys_ptr<IoctlVec> vecs,\n                  phys_ptr<MessageQueue> queue,\n                  phys_ptr<IpcRequest> ipcRequest,\n                  ProcessId pid,\n                  CpuId cpuId)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n   phys_ptr<ResourceHandleManager> resourceHandleManager;\n   phys_ptr<ResourceRequest> resourceRequest;\n\n   auto error = getOpenResource(pid,\n                                resourceHandleId,\n                                &resourceHandleManager,\n                                &resourceHandle);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   if (numVecIn + numVecOut > 0 && !vecs) {\n      return Error::Access;\n   }\n\n   // Try allocate a resource request.\n   error = allocResourceRequest(resourceHandleManager,\n                                cpuId,\n                                resourceHandle->resourceManager,\n                                queue,\n                                ipcRequest,\n                                &resourceRequest);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   resourceRequest->requestData.command = Command::Ioctlv;\n   resourceRequest->resourceHandleId = resourceHandleId;\n   resourceRequest->requestData.handle = resourceHandle->handle;\n   resourceRequest->requestData.args.ioctlv.request = ioctlRequest;\n   resourceRequest->requestData.args.ioctlv.numVecIn = numVecIn;\n   resourceRequest->requestData.args.ioctlv.numVecOut = numVecOut;\n   resourceRequest->requestData.args.ioctlv.vecs = vecs;\n\n   // Try dispatch the request to the relevant resource manager.\n   error = dispatchRequest(resourceRequest);\n   if (error < Error::OK) {\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n* Dispatch an IOS_Resume request.\n*/\nError\ndispatchIosResume(ResourceHandleId resourceHandleId,\n                  uint32_t unkArg0,\n                  uint32_t unkArg1,\n                  phys_ptr<MessageQueue> queue,\n                  phys_ptr<IpcRequest> ipcRequest,\n                  ProcessId pid,\n                  CpuId cpuId)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n   phys_ptr<ResourceHandleManager> resourceHandleManager;\n   phys_ptr<ResourceRequest> resourceRequest;\n\n   auto error = getOpenResource(pid,\n                                resourceHandleId,\n                                &resourceHandleManager,\n                                &resourceHandle);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Try allocate a resource request.\n   error = allocResourceRequest(resourceHandleManager,\n                                cpuId,\n                                resourceHandle->resourceManager,\n                                queue,\n                                ipcRequest,\n                                &resourceRequest);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   resourceRequest->requestData.command = Command::Resume;\n   resourceRequest->resourceHandleId = resourceHandleId;\n   resourceRequest->requestData.handle = resourceHandle->handle;\n   resourceRequest->requestData.args.resume.unkArg0 = unkArg0;\n   resourceRequest->requestData.args.resume.unkArg1 = unkArg1;\n\n   // Try dispatch the request to the relevant resource manager.\n   error = dispatchRequest(resourceRequest);\n   if (error < Error::OK) {\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Dispatch an IOS_Suspend request.\n */\nError\ndispatchIosSuspend(ResourceHandleId resourceHandleId,\n                   uint32_t unkArg0,\n                   uint32_t unkArg1,\n                   phys_ptr<MessageQueue> queue,\n                   phys_ptr<IpcRequest> ipcRequest,\n                   ProcessId pid,\n                   CpuId cpuId)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n   phys_ptr<ResourceHandleManager> resourceHandleManager;\n   phys_ptr<ResourceRequest> resourceRequest;\n\n   auto error = getOpenResource(pid,\n                                resourceHandleId,\n                                &resourceHandleManager,\n                                &resourceHandle);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Try allocate a resource request.\n   error = allocResourceRequest(resourceHandleManager,\n                                cpuId,\n                                resourceHandle->resourceManager,\n                                queue,\n                                ipcRequest,\n                                &resourceRequest);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   resourceRequest->requestData.command = Command::Suspend;\n   resourceRequest->resourceHandleId = resourceHandleId;\n   resourceRequest->requestData.handle = resourceHandle->handle;\n   resourceRequest->requestData.args.suspend.unkArg0 = unkArg0;\n   resourceRequest->requestData.args.suspend.unkArg1 = unkArg1;\n\n   // Try dispatch the request to the relevant resource manager.\n   error = dispatchRequest(resourceRequest);\n   if (error < Error::OK) {\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Dispatch an IOS_SvcMsg request.\n */\nError\ndispatchIosSvcMsg(ResourceHandleId resourceHandleId,\n                  uint32_t command,\n                  uint32_t unkArg1,\n                  uint32_t unkArg2,\n                  uint32_t unkArg3,\n                  phys_ptr<MessageQueue> queue,\n                  phys_ptr<IpcRequest> ipcRequest,\n                  ProcessId pid,\n                  CpuId cpuId)\n{\n   phys_ptr<ResourceHandle> resourceHandle;\n   phys_ptr<ResourceHandleManager> resourceHandleManager;\n   phys_ptr<ResourceRequest> resourceRequest;\n\n   auto error = getOpenResource(pid,\n                                resourceHandleId,\n                                &resourceHandleManager,\n                                &resourceHandle);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Try allocate a resource request.\n   error = allocResourceRequest(resourceHandleManager,\n                                cpuId,\n                                resourceHandle->resourceManager,\n                                queue,\n                                ipcRequest,\n                                &resourceRequest);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   resourceRequest->requestData.command = Command::SvcMsg;\n   resourceRequest->resourceHandleId = resourceHandleId;\n   resourceRequest->requestData.handle = resourceHandle->handle;\n   resourceRequest->requestData.args.svcMsg.command = command;\n   resourceRequest->requestData.args.svcMsg.unkArg1 = unkArg1;\n   resourceRequest->requestData.args.svcMsg.unkArg2 = unkArg2;\n   resourceRequest->requestData.args.svcMsg.unkArg3 = unkArg3;\n\n   // Try dispatch the request to the relevant resource manager.\n   error = dispatchRequest(resourceRequest);\n   if (error < Error::OK) {\n      freeResourceRequest(resourceRequest);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n\n/**\n * Find the ClientCapability structure for a specific feature ID.\n */\nError\ngetClientCapability(phys_ptr<ResourceHandleManager> resourceHandleManager,\n                    FeatureId featureId,\n                    phys_ptr<ClientCapability> *outClientCapability)\n{\n   for (auto i = 0u; i < resourceHandleManager->clientCapabilities.size(); ++i) {\n      auto caps = phys_addrof(resourceHandleManager->clientCapabilities[i]);\n      if (caps->featureId == ResourcePermissionGroup::All ||\n          caps->featureId == featureId) {\n         if (outClientCapability) {\n            *outClientCapability = caps;\n         }\n\n         return Error::OK;\n      }\n   }\n\n   return Error::NoExists;\n}\n\n\n/**\n * Set the client capability mask for a specific process & feature ID.\n */\nError\nsetClientCapability(ProcessId pid,\n                    FeatureId featureId,\n                    uint64_t mask)\n{\n   auto clientCapability = phys_ptr<ClientCapability> { nullptr };\n   auto resourceHandleManager = getResourceHandleManager(pid);\n   if (!resourceHandleManager) {\n      return Error::InvalidArg;\n   }\n\n   auto error = getClientCapability(resourceHandleManager,\n                                    featureId,\n                                    &clientCapability);\n   if (error >= Error::OK) {\n      if (mask == 0) {\n         // Delete client cap\n         clientCapability->featureId = -1;\n         clientCapability->mask = 0ull;\n         return Error::OK;\n      }\n\n      // Update client cap\n      clientCapability->mask = mask;\n      return Error::OK;\n   }\n\n   if (mask == 0) {\n      return Error::OK;\n   }\n\n   // Add new client cap\n   clientCapability = nullptr;\n\n   for (auto i = 0u; i < resourceHandleManager->clientCapabilities.size(); ++i) {\n      auto cap = phys_addrof(resourceHandleManager->clientCapabilities[i]);\n\n      if (cap->featureId == -1) {\n         clientCapability = cap;\n         break;\n      }\n   }\n\n   if (!clientCapability) {\n      return Error::FailAlloc;\n   }\n\n   clientCapability->featureId = featureId;\n   clientCapability->mask = mask;\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticResourceManagerData()\n{\n   sData = allocProcessStatic<StaticResourceManagerData>();\n\n   // Initialise resourceManagerList\n   auto &resourceManagerList = sData->resourceManagerList;\n   resourceManagerList.firstRegisteredIdx = int16_t { -1 };\n   resourceManagerList.lastRegisteredIdx = int16_t { -1 };\n\n   resourceManagerList.firstFreeIdx = int16_t { 0 };\n   resourceManagerList.lastFreeIdx = static_cast<int16_t>(MaxNumResourceManagers - 1);\n\n   for (auto i = 0; i < MaxNumResourceManagers; ++i) {\n      auto &resourceManager = resourceManagerList.resourceManagers[i];\n      resourceManager.prevResourceManagerIdx = static_cast<int16_t>(i - 1);\n      resourceManager.nextResourceManagerIdx = static_cast<int16_t>(i + 1);\n   }\n\n   resourceManagerList.resourceManagers[resourceManagerList.firstFreeIdx].prevResourceManagerIdx = int16_t { -1 };\n   resourceManagerList.resourceManagers[resourceManagerList.lastFreeIdx].nextResourceManagerIdx = int16_t { -1 };\n\n   // Initialise resourceRequestList\n   auto &resourceRequestList = sData->resourceRequestList;\n   resourceRequestList.firstFreeIdx = int16_t { 0 };\n   resourceRequestList.lastFreeIdx = static_cast<int16_t>(MaxNumResourceRequests - 1);\n\n   for (auto i = 0; i < MaxNumResourceRequests; ++i) {\n      auto &resourceRequest = resourceRequestList.resourceRequests[i];\n      resourceRequest.prevIdx = static_cast<int16_t>(i - 1);\n      resourceRequest.nextIdx = static_cast<int16_t>(i + 1);\n   }\n\n   resourceRequestList.resourceRequests[resourceRequestList.firstFreeIdx].prevIdx = int16_t { -1 };\n   resourceRequestList.resourceRequests[resourceRequestList.lastFreeIdx].nextIdx = int16_t { -1 };\n\n   // Initialise resourceHandleManagers\n   auto &resourceHandleManagers = sData->resourceHandleManagers;\n   for (auto i = 0u; i < resourceHandleManagers.size(); ++i) {\n      auto &resourceHandleManager = resourceHandleManagers[i];\n\n      resourceHandleManager.processId = static_cast<ProcessId>(i);\n      resourceHandleManager.maxResourceHandles = MaxNumResourceHandlesPerProcess;\n      resourceHandleManager.maxResourceRequests = MaxNumResourceRequestsPerProcess;\n\n      if (resourceHandleManager.processId >= ProcessId::COSKERNEL) {\n         resourceHandleManager.maxResourceManagers = 0u;\n      } else {\n         resourceHandleManager.maxResourceManagers = MaxNumResourceManagersPerProcess;\n      }\n\n      for (auto j = 0u; j < MaxNumResourceHandlesPerProcess; ++j) {\n         auto &handle = resourceHandleManager.handles[j];\n         handle.handle = -4;\n         handle.id = -4;\n         handle.resourceManager = nullptr;\n         handle.state = ResourceHandleState::Free;\n      }\n\n      for (auto j = 0u; j < MaxNumClientCapabilitiesPerProcess; ++j) {\n         auto &caps = resourceHandleManager.clientCapabilities[j];\n         caps.featureId = -1;\n         caps.mask = 0ull;\n      }\n\n      setClientCapability(resourceHandleManager.processId, 0, 0xFFFFFFFFFFFFFFFFull);\n   }\n}\n\n} // namespace internal\n\n} // namespace ios\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_resourcemanager.h",
    "content": "#pragma once\n#include \"ios_kernel_enum.h\"\n#include \"ios_kernel_messagequeue.h\"\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_ipc.h\"\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::kernel\n{\n\nstatic constexpr auto MaxNumResourceManagers = 96u;\nstatic constexpr auto MaxNumResourceRequests = 256u;\n\nstatic constexpr auto MaxNumResourceHandlesPerProcess = 96u;\nstatic constexpr auto MaxNumResourceManagersPerProcess = 32u;\nstatic constexpr auto MaxNumResourceRequestsPerProcess = 32u;\nstatic constexpr auto MaxNumClientCapabilitiesPerProcess = 20u;\n\nusing ResourceHandleId = int32_t;\nusing FeatureId = int32_t;\nusing ClientCapabilityMask = uint64_t;\n\nstruct ResourceManager;\nstruct ResourceManagerList;\n\nstruct ResourceHandle;\nstruct ResourceHandleManager;\n\nstruct ResourceRequest;\nstruct ResourceRequestList;\n\n#pragma pack(push, 1)\n\nstruct ResourceManager\n{\n   //! Name of the device this resource manager manages.\n   be2_array<char, 32> device;\n\n   //! ID of the message queue associated with this resource.\n   be2_val<MessageQueueId> queueId;\n\n   //! Pointer to the resource handle manager for this resource manager.\n   be2_phys_ptr<ResourceHandleManager> resourceHandleManager;\n\n   //! Permission Group this resource belongs to, this matches values in cos.xml.\n   be2_val<ResourcePermissionGroup> permissionGroup;\n\n   //! Length of the string in this->device\n   be2_val<uint16_t> deviceLen;\n\n   //! Index of the first resource request for this resource in the global\n   //! resource request list.\n   be2_val<int16_t> firstRequestIdx;\n\n   //! Index of the last resource request for this resource in the global\n   //! resource request list.\n   be2_val<int16_t> lastRequestIdx;\n\n   //! Number of resource requests active for this resource.\n   be2_val<uint16_t> numRequests;\n\n   //! Number of resource handles active for this resource.\n   be2_val<uint16_t> numHandles;\n\n   //! Index of the next active resource manager.\n   be2_val<int16_t> nextResourceManagerIdx;\n\n   //! Index of the previous active resource manager.\n   be2_val<int16_t> prevResourceManagerIdx;\n\n   be2_val<uint16_t> unk0x3A;\n   be2_val<uint16_t> unk0x3C;\n   be2_val<uint16_t> unk0x3E;\n};\nCHECK_OFFSET(ResourceManager, 0x00, device);\nCHECK_OFFSET(ResourceManager, 0x20, queueId);\nCHECK_OFFSET(ResourceManager, 0x24, resourceHandleManager);\nCHECK_OFFSET(ResourceManager, 0x28, permissionGroup);\nCHECK_OFFSET(ResourceManager, 0x2C, deviceLen);\nCHECK_OFFSET(ResourceManager, 0x2E, firstRequestIdx);\nCHECK_OFFSET(ResourceManager, 0x30, lastRequestIdx);\nCHECK_OFFSET(ResourceManager, 0x32, numRequests);\nCHECK_OFFSET(ResourceManager, 0x34, numHandles);\nCHECK_OFFSET(ResourceManager, 0x36, nextResourceManagerIdx);\nCHECK_OFFSET(ResourceManager, 0x38, prevResourceManagerIdx);\nCHECK_OFFSET(ResourceManager, 0x3A, unk0x3A);\nCHECK_OFFSET(ResourceManager, 0x3C, unk0x3C);\nCHECK_OFFSET(ResourceManager, 0x3E, unk0x3E);\nCHECK_SIZE(ResourceManager, 0x40);\n\nstruct ResourceManagerList\n{\n   //! Number of registered resource managers.\n   be2_val<uint16_t> numRegistered;\n\n   //! The highest number of registered resource managers at one time.\n   be2_val<uint16_t> mostRegistered;\n\n   //! Index of the first registered resource manager.\n   be2_val<int16_t> firstRegisteredIdx;\n\n   //! Index of the last registered resource manager.\n   be2_val<int16_t> lastRegisteredIdx;\n\n   //! Index of the first free resource manager.\n   be2_val<int16_t> firstFreeIdx;\n\n   //! Index of the last free resource manager.\n   be2_val<int16_t> lastFreeIdx;\n\n   //! List of resource managers.\n   be2_array<ResourceManager, MaxNumResourceManagers> resourceManagers;\n};\nCHECK_OFFSET(ResourceManagerList, 0x00, numRegistered);\nCHECK_OFFSET(ResourceManagerList, 0x02, mostRegistered);\nCHECK_OFFSET(ResourceManagerList, 0x04, firstRegisteredIdx);\nCHECK_OFFSET(ResourceManagerList, 0x06, lastRegisteredIdx);\nCHECK_OFFSET(ResourceManagerList, 0x08, firstFreeIdx);\nCHECK_OFFSET(ResourceManagerList, 0x0A, lastFreeIdx);\nCHECK_OFFSET(ResourceManagerList, 0x0C, resourceManagers);\nCHECK_SIZE(ResourceManagerList, 0x180C);\n\nstruct ResourceHandle\n{\n   //! The internal process handle returned by a successful IOS_Open request.\n   be2_val<int32_t> handle;\n\n   //! The unique id of this resource handle.\n   be2_val<ResourceHandleId> id;\n\n   //! The resource manager this handle belongs to.\n   be2_phys_ptr<ResourceManager> resourceManager;\n\n   //! The state of this resource handle.\n   be2_val<ResourceHandleState> state;\n};\nCHECK_OFFSET(ResourceHandle, 0x00, handle);\nCHECK_OFFSET(ResourceHandle, 0x04, id);\nCHECK_OFFSET(ResourceHandle, 0x08, resourceManager);\nCHECK_OFFSET(ResourceHandle, 0x0C, state);\nCHECK_SIZE(ResourceHandle, 0x10);\n\nstruct ClientCapability\n{\n   be2_val<FeatureId> featureId;\n   be2_val<ClientCapabilityMask> mask;\n};\nCHECK_OFFSET(ClientCapability, 0x00, featureId);\nCHECK_OFFSET(ClientCapability, 0x04, mask);\nCHECK_SIZE(ClientCapability, 0x0C);\n\n/**\n * A per process structure to manage resource handles.\n */\nstruct ResourceHandleManager\n{\n   //! Title ID this resource handle manager belongs to.\n   be2_val<TitleId> titleId;\n\n   //! Group ID this resource handle manager belongs to.\n   be2_val<GroupId> groupId;\n\n   //! Process this resource handle manager belongs to.\n   be2_val<ProcessId> processId;\n\n   //! Number of current resource handles.\n   be2_val<uint32_t> numResourceHandles;\n\n   //! Highest number of resource handles at one time.\n   be2_val<uint32_t> mostResourceHandles;\n\n   //! Maximum number of resource handles, aka size of handles array.\n   be2_val<uint32_t> maxResourceHandles;\n\n   //! List of resource handles.\n   be2_array<ResourceHandle, MaxNumResourceHandlesPerProcess> handles;\n\n   //! Number of resource requests.\n   be2_val<uint32_t> numResourceRequests;\n\n   //! Highest number of resource requests at once.\n   be2_val<uint32_t> mostResourceRequests;\n\n   //! Number of times registerIpcRequest failed due to max number of resource requests.\n   be2_val<uint32_t> failedRegisterMaxResourceRequests;\n\n   //! Maxmimum allowed number of resource requests per process.\n   be2_val<uint32_t> maxResourceRequests;\n\n   //! Client Capabilities\n   be2_array<ClientCapability, MaxNumClientCapabilitiesPerProcess> clientCapabilities;\n\n   //! Number of resource managers for this process.\n   be2_val<uint32_t> numResourceManagers;\n\n   //! Maximum allowed number of resource managers for this process.\n   be2_val<uint32_t> maxResourceManagers;\n\n   //! Number of times IOS_ResourceReply failed\n   be2_val<uint32_t> failedResourceReplies;\n};\nCHECK_OFFSET(ResourceHandleManager, 0x00, titleId);\nCHECK_OFFSET(ResourceHandleManager, 0x08, groupId);\nCHECK_OFFSET(ResourceHandleManager, 0x0C, processId);\nCHECK_OFFSET(ResourceHandleManager, 0x10, numResourceHandles);\nCHECK_OFFSET(ResourceHandleManager, 0x14, mostResourceHandles);\nCHECK_OFFSET(ResourceHandleManager, 0x18, maxResourceHandles);\nCHECK_OFFSET(ResourceHandleManager, 0x1C, handles);\nCHECK_OFFSET(ResourceHandleManager, 0x61C, numResourceRequests);\nCHECK_OFFSET(ResourceHandleManager, 0x620, mostResourceRequests);\nCHECK_OFFSET(ResourceHandleManager, 0x624, failedRegisterMaxResourceRequests);\nCHECK_OFFSET(ResourceHandleManager, 0x628, maxResourceRequests);\nCHECK_OFFSET(ResourceHandleManager, 0x62C, clientCapabilities);\nCHECK_OFFSET(ResourceHandleManager, 0x71C, numResourceManagers);\nCHECK_OFFSET(ResourceHandleManager, 0x720, maxResourceManagers);\nCHECK_OFFSET(ResourceHandleManager, 0x724, failedResourceReplies);\nCHECK_SIZE(ResourceHandleManager, 0x728);\n\nstruct ResourceRequest\n{\n   //! Data store for the actual request.\n   be2_struct<IpcRequest> requestData;\n\n   //! Message queue this resource request came from.\n   be2_phys_ptr<MessageQueue> messageQueue;\n\n   //! Message queue id, why store this and message queue, who knows..?\n   be2_val<MessageQueueId> messageQueueId;\n\n   //! Pointer to the IpcRequest that this resource request originated from.\n   be2_phys_ptr<IpcRequest> ipcRequest;\n\n   //! Pointer to the resource handle manager for this request.\n   be2_phys_ptr<ResourceHandleManager> resourceHandleManager;\n\n   //! Pointer to the resource manager for this request.\n   be2_phys_ptr<ResourceManager> resourceManager;\n\n   //! ID of the resource handle associated with this request.\n   be2_val<ResourceHandleId> resourceHandleId;\n\n   //! Index of the next resource request, used for either free or registered\n   //! list in ResourceRequestList.\n   be2_val<int16_t> nextIdx;\n\n   //! Index of the previous resource request, used for either free or registered\n   //! list in ResourceRequestList.\n   be2_val<int16_t> prevIdx;\n\n   //! Buffer to copy the device name to for IOS_Open calls.\n   be2_array<char, 32> openNameBuffer;\n\n   UNKNOWN(0xB4 - 0x74);\n};\nCHECK_OFFSET(ResourceRequest, 0x00, requestData);\nCHECK_OFFSET(ResourceRequest, 0x38, messageQueue);\nCHECK_OFFSET(ResourceRequest, 0x3C, messageQueueId);\nCHECK_OFFSET(ResourceRequest, 0x40, ipcRequest);\nCHECK_OFFSET(ResourceRequest, 0x44, resourceHandleManager);\nCHECK_OFFSET(ResourceRequest, 0x48, resourceManager);\nCHECK_OFFSET(ResourceRequest, 0x4C, resourceHandleId);\nCHECK_OFFSET(ResourceRequest, 0x50, nextIdx);\nCHECK_OFFSET(ResourceRequest, 0x52, prevIdx);\nCHECK_OFFSET(ResourceRequest, 0x54, openNameBuffer);\nCHECK_SIZE(ResourceRequest, 0xB4);\n\n/**\n * Storage for all resource requests.\n */\nstruct ResourceRequestList\n{\n   //! Number of registered resource requests.\n   be2_val<uint16_t> numRegistered;\n\n   //! Highest number of registered resource requests.\n   be2_val<uint16_t> mostRegistered;\n\n   be2_val<uint16_t> unk0x04;\n\n   //! Index of first free element in resourceRequests.\n   be2_val<int16_t> firstFreeIdx;\n\n   //! Index of last free element in resourceRequests.\n   be2_val<int16_t> lastFreeIdx;\n\n   be2_val<uint16_t> unk0x0A;\n\n   //! Resource Request storage.\n   be2_array<ResourceRequest, MaxNumResourceRequests> resourceRequests;\n};\nCHECK_OFFSET(ResourceRequestList, 0x00, numRegistered);\nCHECK_OFFSET(ResourceRequestList, 0x02, mostRegistered);\nCHECK_OFFSET(ResourceRequestList, 0x04, unk0x04);\nCHECK_OFFSET(ResourceRequestList, 0x06, firstFreeIdx);\nCHECK_OFFSET(ResourceRequestList, 0x08, lastFreeIdx);\nCHECK_OFFSET(ResourceRequestList, 0x0A, unk0x0A);\nCHECK_OFFSET(ResourceRequestList, 0x0C, resourceRequests);\nCHECK_SIZE(ResourceRequestList, 0xB40C);\n\n#pragma pack(pop)\n\nError\nIOS_SetResourceManagerRegistrationDisabled(bool enable);\n\nError\nIOS_RegisterResourceManager(std::string_view device,\n                            MessageQueueId queue);\n\nError\nIOS_AssociateResourceManager(std::string_view device,\n                             ResourcePermissionGroup group);\n\nError\nIOS_ResourceReply(phys_ptr<ResourceRequest> request,\n                  Error reply);\n\nError\nIOS_SetClientCapabilities(ProcessId pid,\n                          FeatureId featureId,\n                          phys_ptr<uint64_t> mask);\n\nError\nIOS_SetProcessTitle(ProcessId process,\n                    TitleId title,\n                    GroupId group);\n\nnamespace internal\n{\n\nError\ndispatchIosOpen(std::string_view name,\n                OpenMode mode,\n                phys_ptr<MessageQueue> queue,\n                phys_ptr<IpcRequest> ipcRequest,\n                ProcessId pid,\n                CpuId cpuId);\n\nError\ndispatchIosClose(ResourceHandleId resourceHandleId,\n                 phys_ptr<MessageQueue> queue,\n                 phys_ptr<IpcRequest> ipcRequest,\n                 uint32_t unkArg0,\n                 ProcessId pid,\n                 CpuId cpuId);\n\nError\ndispatchIosRead(ResourceHandleId resourceHandleId,\n                phys_ptr<void> buffer,\n                uint32_t length,\n                phys_ptr<MessageQueue> queue,\n                phys_ptr<IpcRequest> ipcRequest,\n                ProcessId pid,\n                CpuId cpuId);\n\nError\ndispatchIosWrite(ResourceHandleId resourceHandleId,\n                 phys_ptr<const void> buffer,\n                 uint32_t length,\n                 phys_ptr<MessageQueue> queue,\n                 phys_ptr<IpcRequest> ipcRequest,\n                 ProcessId pid,\n                 CpuId cpuId);\n\nError\ndispatchIosSeek(ResourceHandleId resourceHandleId,\n                  uint32_t offset,\n                  SeekOrigin origin,\n                  phys_ptr<MessageQueue> queue,\n                  phys_ptr<IpcRequest> ipcRequest,\n                  ProcessId pid,\n                  CpuId cpuId);\n\nError\ndispatchIosIoctl(ResourceHandleId resourceHandleId,\n                 uint32_t ioctlRequest,\n                 phys_ptr<const void> inputBuffer,\n                 uint32_t inputLength,\n                 phys_ptr<void> outputBuffer,\n                 uint32_t outputLength,\n                 phys_ptr<MessageQueue> queue,\n                 phys_ptr<IpcRequest> ipcRequest,\n                 ProcessId pid,\n                 CpuId cpuId);\n\nError\ndispatchIosIoctlv(ResourceHandleId resourceHandleId,\n                  uint32_t ioctlRequest,\n                  uint32_t numVecIn,\n                  uint32_t numVecOut,\n                  phys_ptr<IoctlVec> vecs,\n                  phys_ptr<MessageQueue> queue,\n                  phys_ptr<IpcRequest> ipcRequest,\n                  ProcessId pid,\n                  CpuId cpuId);\n\nError\ndispatchIosResume(ResourceHandleId resourceHandleId,\n                  uint32_t unkArg0,\n                  uint32_t unkArg1,\n                  phys_ptr<MessageQueue> queue,\n                  phys_ptr<IpcRequest> ipcRequest,\n                  ProcessId pid,\n                  CpuId cpuId);\n\nError\ndispatchIosSuspend(ResourceHandleId resourceHandleId,\n                   uint32_t unkArg0,\n                   uint32_t unkArg1,\n                   phys_ptr<MessageQueue> queue,\n                   phys_ptr<IpcRequest> ipcRequest,\n                   ProcessId pid,\n                   CpuId cpuId);\n\nError\ndispatchIosSvcMsg(ResourceHandleId resourceHandleId,\n                  uint32_t command,\n                  uint32_t unkArg1,\n                  uint32_t unkArg2,\n                  uint32_t unkArg3,\n                  phys_ptr<MessageQueue> queue,\n                  phys_ptr<IpcRequest> ipcRequest,\n                  ProcessId pid,\n                  CpuId cpuId);\n\nError\ngetClientCapability(phys_ptr<ResourceHandleManager> resourceHandleManager,\n                    FeatureId featureId,\n                    phys_ptr<ClientCapability> *outClientCapability);\n\nError\nsetClientCapability(ProcessId pid,\n                    FeatureId featureId,\n                    uint64_t mask);\n\nvoid\ninitialiseStaticResourceManagerData();\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_scheduler.cpp",
    "content": "#include \"ios_kernel_hardware.h\"\n#include \"ios_kernel_scheduler.h\"\n#include \"ios_kernel_thread.h\"\n#include \"ios_kernel_threadqueue.h\"\n#include \"ios_kernel_process.h\"\n\n#include <fmt/format.h>\n#include <common/log.h>\n#include <common/platform_fiber.h>\n#include <libcpu/cpu_formatters.h>\n\n#include <iterator>\n\nnamespace ios::kernel::internal\n{\n\nstruct StaticSchedulerData\n{\n   be2_struct<ThreadQueue> runQueue;\n};\n\nstatic phys_ptr<StaticSchedulerData>\nsData = nullptr;\n\nstatic phys_ptr<Thread>\nsCurrentThreadContext = nullptr;\n\nstatic platform::Fiber *\nsIdleFiber = nullptr;\n\nphys_ptr<Thread>\ngetCurrentThread()\n{\n   return sCurrentThreadContext;\n}\n\nThreadId\ngetCurrentThreadId()\n{\n   return sCurrentThreadContext->id;\n}\n\nvoid\nsleepThread(phys_ptr<ThreadQueue> queue)\n{\n   auto thread = sCurrentThreadContext;\n   thread->state = ThreadState::Waiting;\n   ThreadQueue_PushThread(queue, thread);\n}\n\nvoid\nwakeupOneThread(phys_ptr<ThreadQueue> queue,\n                Error waitResult)\n{\n   if (auto thread = ThreadQueue_PopThread(queue)) {\n      thread->state = ThreadState::Ready;\n      thread->context.queueWaitResult = waitResult;\n      queueThread(thread);\n   }\n}\n\nvoid\nwakeupAllThreads(phys_ptr<ThreadQueue> queue,\n                 Error waitResult)\n{\n   while (queue->first) {\n      wakeupOneThread(queue, waitResult);\n   }\n}\n\nvoid\nqueueThread(phys_ptr<Thread> thread)\n{\n   ThreadQueue_PushThread(phys_addrof(sData->runQueue), thread);\n}\n\nbool\nisThreadInRunQueue(phys_ptr<Thread> thread)\n{\n   return thread->threadQueue == phys_addrof(sData->runQueue);\n}\n\nvoid\nreschedule(bool yielding)\n{\n   auto currentThread = sCurrentThreadContext;\n   auto nextThread = ThreadQueue_PeekThread(phys_addrof(sData->runQueue));\n\n   if (currentThread && currentThread->state == ThreadState::Running) {\n      if (!nextThread) {\n         // No other threads to run, we're stuck with this one!\n         return;\n      }\n\n      if (currentThread->priority > nextThread->priority) {\n         // Next thread has lower priority, keep running current.\n         return;\n      }\n\n      if (!yielding && currentThread->priority == nextThread->priority) {\n         // Next thread has same priority, but we are not yielding.\n         return;\n      }\n\n      currentThread->state = ThreadState::Ready;\n\n      decaf_check(ThreadQueue_PopThread(phys_addrof(sData->runQueue)) == nextThread);\n      queueThread(currentThread);\n   } else {\n      decaf_check(ThreadQueue_PopThread(phys_addrof(sData->runQueue)) == nextThread);\n   }\n\n   // Trace log the thread switch\n   if (gLog->should_log(Logger::Level::trace)) {\n      fmt::memory_buffer out;\n      fmt::format_to(std::back_inserter(out), \"IOS leaving\");\n\n      if (currentThread) {\n         fmt::format_to(std::back_inserter(out), \" thread {}\", currentThread->id);\n\n         if (currentThread->context.threadName) {\n            fmt::format_to(std::back_inserter(out), \" [{}]\", currentThread->context.threadName);\n         }\n      } else {\n         fmt::format_to(std::back_inserter(out), \" idle\");\n      }\n\n      fmt::format_to(std::back_inserter(out), \" to\");\n\n      if (nextThread) {\n         fmt::format_to(std::back_inserter(out), \" thread {}\", nextThread->id);\n\n         if (nextThread->context.threadName) {\n            fmt::format_to(std::back_inserter(out), \" [{}]\", nextThread->context.threadName);\n         }\n      } else {\n         fmt::format_to(std::back_inserter(out), \" idle\");\n      }\n\n      gLog->trace(\"{}\", std::string_view { out.data(), out.size() });\n   }\n\n   sCurrentThreadContext = nextThread;\n\n   if (nextThread) {\n      nextThread->state = ThreadState::Running;\n   }\n\n   auto fiberSrc = currentThread ? currentThread->context.fiber : sIdleFiber;\n   auto fiberDst = nextThread ? nextThread->context.fiber : sIdleFiber;\n\n   if (fiberSrc != fiberDst) {\n      platform::swapToFiber(fiberSrc, fiberDst);\n   }\n}\n\nvoid\nsetIdleFiber()\n{\n   sIdleFiber = platform::getThreadFiber();\n}\n\nvoid\ninitialiseStaticSchedulerData()\n{\n   sData = allocProcessStatic<StaticSchedulerData>();\n}\n\n} // namespace ios::kernel::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_scheduler.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace ios::kernel\n{\n\nstruct Thread;\nstruct ThreadQueue;\n\nnamespace internal\n{\n\nvoid\nsleepThread(phys_ptr<ThreadQueue> queue);\n\nvoid\nwakeupOneThread(phys_ptr<ThreadQueue> queue,\n                Error waitResult);\n\nvoid\nwakeupAllThreads(phys_ptr<ThreadQueue> queue,\n                 Error waitResult);\n\nvoid\nqueueThread(phys_ptr<Thread> thread);\n\nbool\nisThreadInRunQueue(phys_ptr<Thread> thread);\n\nvoid\nreschedule(bool yielding = false);\n\nvoid\nsetIdleFiber();\n\nvoid\ninitialiseStaticSchedulerData();\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_semaphore.cpp",
    "content": "#include \"ios_kernel_process.h\"\n#include \"ios_kernel_semaphore.h\"\n#include \"ios_kernel_scheduler.h\"\n#include \"ios_kernel_thread.h\"\n#include <array>\n#include <mutex>\n\nnamespace ios::kernel\n{\n\nstruct StaticSemaphoreData\n{\n   be2_val<uint32_t> numCreatedSemaphores = uint32_t { 0 };\n   be2_val<int16_t> firstFreeSemaphoreIndex = int16_t { 0 };\n   be2_val<int16_t> lastFreeSemaphoreIndex = int16_t { 0 };\n   be2_array<Semaphore, 750> semaphores;\n};\n\nstatic phys_ptr<StaticSemaphoreData>\nsData;\n\nstatic phys_ptr<Semaphore>\ngetSemaphore(SemaphoreId id)\n{\n   auto idx = static_cast<size_t>(id & 0xFFF);\n   if (idx >= sData->semaphores.size()) {\n      return nullptr;\n   }\n\n   auto semaphore = phys_addrof(sData->semaphores[idx]);\n   if (semaphore->pid != internal::getCurrentProcessId()) {\n      // Can only access semaphores belonging to same process.\n      return nullptr;\n   }\n\n   return semaphore;\n}\n\nError\nIOS_CreateSemaphore(int32_t maxCount,\n                    int32_t initialCount)\n{\n   if (sData->firstFreeSemaphoreIndex < 0) {\n      return Error::Max;\n   }\n\n   auto semaphore = phys_addrof(sData->semaphores[sData->firstFreeSemaphoreIndex]);\n   auto semaphoreId = sData->firstFreeSemaphoreIndex;\n\n   // Remove semaphore from free semaphore linked list\n   sData->firstFreeSemaphoreIndex = semaphore->nextFreeSemaphoreIndex;\n\n   if (semaphore->nextFreeSemaphoreIndex >= 0) {\n      sData->semaphores[semaphore->nextFreeSemaphoreIndex].prevFreeSemaphoreIndex = int16_t { -1 };\n   } else {\n      sData->lastFreeSemaphoreIndex = int16_t { -1 };\n   }\n\n   semaphore->nextFreeSemaphoreIndex = int16_t { -1 };\n   semaphore->prevFreeSemaphoreIndex = int16_t { -1 };\n\n   sData->numCreatedSemaphores++;\n   semaphore->id = static_cast<SemaphoreId>(semaphoreId | (sData->numCreatedSemaphores << 12));\n   semaphore->count = initialCount;\n   semaphore->maxCount = maxCount;\n   semaphore->pid = internal::getCurrentProcessId();\n   semaphore->unknown0x04 = nullptr;\n   ThreadQueue_Initialise(phys_addrof(semaphore->waitThreadQueue));\n\n   return static_cast<Error>(semaphore->id);\n}\n\nError\nIOS_DestroySempahore(SemaphoreId id)\n{\n   auto semaphore = getSemaphore(id);\n   if (!semaphore) {\n      return Error::Invalid;\n   }\n\n   semaphore->count = 0;\n   semaphore->maxCount = 0;\n\n   // Add semaphore to the free semaphore linked list.\n   auto index = static_cast<int16_t>(semaphore - phys_addrof(sData->semaphores));\n   auto prevSemaphoreIndex = sData->lastFreeSemaphoreIndex;\n\n   if (prevSemaphoreIndex >= 0) {\n      auto prevSemaphore = phys_addrof(sData->semaphores[sData->lastFreeSemaphoreIndex]);\n      prevSemaphore->nextFreeSemaphoreIndex = index;\n   }\n\n   semaphore->prevFreeSemaphoreIndex = prevSemaphoreIndex;\n   semaphore->nextFreeSemaphoreIndex = int16_t { -1 };\n\n   sData->lastFreeSemaphoreIndex = index;\n\n   if (sData->firstFreeSemaphoreIndex < 0) {\n      sData->firstFreeSemaphoreIndex = index;\n   }\n\n   internal::wakeupAllThreads(phys_addrof(semaphore->waitThreadQueue),\n                              Error::Intr);\n   internal::reschedule();\n   return Error::Invalid;\n}\n\nError\nIOS_WaitSemaphore(SemaphoreId id,\n                  BOOL tryWait)\n{\n   auto semaphore = getSemaphore(id);\n   if (!semaphore) {\n      return Error::Invalid;\n   }\n\n   if (semaphore->count <= 0 && tryWait) {\n      return Error::SemUnavailable;\n   }\n\n   while (semaphore->count <= 0) {\n      internal::sleepThread(phys_addrof(semaphore->waitThreadQueue));\n      internal::reschedule();\n\n      auto thread = internal::getCurrentThread();\n      if (thread->context.queueWaitResult != Error::OK) {\n         return thread->context.queueWaitResult;\n      }\n   }\n\n   semaphore->count -= 1;\n   return Error::OK;\n}\n\nError\nIOS_SignalSempahore(SemaphoreId id)\n{\n   auto semaphore = getSemaphore(id);\n   if (!semaphore) {\n      return Error::Invalid;\n   }\n\n   if (semaphore->count < semaphore->maxCount) {\n      semaphore->count += 1;\n   }\n\n   internal::wakeupOneThread(phys_addrof(semaphore->waitThreadQueue), Error::OK);\n   internal::reschedule();\n   return Error::OK;\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseStaticSemaphoreData()\n{\n   sData = allocProcessStatic<StaticSemaphoreData>();\n}\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_semaphore.h",
    "content": "#pragma once\n#include \"ios_kernel_threadqueue.h\"\n#include \"ios/ios_enum.h\"\n\n#include <common/cbool.h>\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::kernel\n{\n\n// Max num semaphores: 750\n\n// SemaphoreId seems to be (id | (something << 12));\nusing SemaphoreId = int32_t;\n\nstruct Thread;\n\nstruct Semaphore\n{\n   //! List of threads waiting on this semaphore, ordered by priority.\n   be2_struct<ThreadQueue> waitThreadQueue;\n\n   //! Unknown list of threads.\n   be2_phys_ptr<Thread> unknown0x04;\n\n   //! Unique semaphore identifier.\n   be2_val<SemaphoreId> id;\n\n   //! Process this semaphore belongs to.\n   be2_val<ProcessId> pid;\n\n   //! Current semaphore signal count.\n   be2_val<int32_t> count;\n\n   //! Maximum semaphore signal count.\n   be2_val<int32_t> maxCount;\n\n   //! Previous free semaphore, linked list.\n   be2_val<int16_t> prevFreeSemaphoreIndex;\n\n   //! Next free semaphore, linked list.\n   be2_val<int16_t> nextFreeSemaphoreIndex;\n};\nCHECK_OFFSET(Semaphore, 0x00, waitThreadQueue);\nCHECK_OFFSET(Semaphore, 0x04, unknown0x04);\nCHECK_OFFSET(Semaphore, 0x08, id);\nCHECK_OFFSET(Semaphore, 0x0C, pid);\nCHECK_OFFSET(Semaphore, 0x10, count);\nCHECK_OFFSET(Semaphore, 0x14, maxCount);\nCHECK_OFFSET(Semaphore, 0x18, prevFreeSemaphoreIndex);\nCHECK_OFFSET(Semaphore, 0x1A, nextFreeSemaphoreIndex);\nCHECK_SIZE(Semaphore, 0x1C);\n\nError\nIOS_CreateSemaphore(int32_t maxCount,\n                    int32_t initialCount);\n\nError\nIOS_DestroySempahore(SemaphoreId id);\n\nError\nIOS_WaitSemaphore(SemaphoreId id,\n                  BOOL tryWait);\n\nError\nIOS_SignalSempahore(SemaphoreId id);\n\nnamespace internal\n{\n\nvoid\ninitialiseStaticSemaphoreData();\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_thread.cpp",
    "content": "#include \"ios_kernel_thread.h\"\n#include \"ios_kernel_threadqueue.h\"\n#include \"ios_kernel_process.h\"\n#include \"ios_kernel_scheduler.h\"\n\n#include <array>\n#include <mutex>\n\nnamespace ios::kernel\n{\n\nstruct StaticThreadData\n{\n   be2_array<Thread, MaxNumThreads> threads;\n   be2_val<uint32_t> numActiveThreads = uint32_t { 0 };\n};\n\nstatic phys_ptr<StaticThreadData>\nsData = nullptr;\n\nnamespace internal\n{\n\nstatic void\nmemset32(phys_ptr<void> ptr,\n         uint32_t value,\n         uint32_t bytes);\n\nstatic void\niosFiberEntryPoint(void *context);\n\n} // namespace internal\n\nError\nIOS_CreateThread(ThreadEntryFn entry,\n                 phys_ptr<void> context,\n                 phys_ptr<uint8_t> stackTop,\n                 uint32_t stackSize,\n                 int priority,\n                 ThreadFlags flags)\n{\n   phys_ptr<Thread> thread = nullptr;\n\n   if (priority > MaxThreadPriority) {\n      return Error::Invalid;\n   }\n\n   // We cannot create thread with priority higher than current thread's max\n   // priority\n   auto currentThread = internal::getCurrentThread();\n   if (currentThread && priority > currentThread->maxPriority) {\n      return Error::Invalid;\n   }\n\n   // Check stack pointer and alignment\n   if (!stackTop || (phys_cast<phys_addr>(stackTop) & 3)) {\n      return Error::Invalid;\n   }\n\n   // Check stack size and alignment\n   if (stackSize < 0x68 || (stackSize & 3)) {\n      return Error::Invalid;\n   }\n\n   // Find a free thread\n   for (auto i = 0u; i < sData->threads.size(); ++i) {\n      if (sData->threads[i].state == ThreadState::Available) {\n         thread = phys_addrof(sData->threads[i]);\n         thread->id = i;\n         break;\n      }\n   }\n\n   if (!thread) {\n      // Maximum number of threads running\n      return Error::Max;\n   }\n\n   if (currentThread) {\n      thread->pid = currentThread->pid;\n   } else {\n      thread->pid = ProcessId::KERNEL;\n   }\n\n   thread->state = ThreadState::Stopped;\n   thread->maxPriority = priority;\n   thread->priority = priority;\n   thread->userStackAddr = stackTop;\n   thread->userStackSize = stackSize;\n   // TODO: thread->sysStackAddr = systemStack + thread->id * 0x400;\n   thread->userContext.stackPointer = stackTop - 0x10;\n\n   internal::memset32(stackTop - stackSize, 0xF5A5A5A5, stackSize);\n\n   if (flags & ThreadFlags::AllocateTLS) {\n      stackTop -= sizeof(ThreadLocalStorage);\n      thread->threadLocalStorage = phys_cast<ThreadLocalStorage *>(stackTop);\n      std::memset(thread->threadLocalStorage.get(), 0, 0x24);\n   } else {\n      thread->threadLocalStorage = nullptr;\n   }\n\n   sData->numActiveThreads++;\n   thread->flags = flags;\n   thread->threadQueueNext = nullptr;\n   thread->threadQueue = nullptr;\n   ThreadQueue_Initialise(phys_addrof(thread->joinQueue));\n\n#ifdef IOS_EMULATE_ARM\n   // We are not doing ARM emulation...\n   thread->context.gpr[0] = phys_addr { context }.getAddress();\n   thread->context.gpr[13] = phys_addr { stackTop - 0x20 }.getAddress();\n   thread->context.pc = entry;\n\n   if (entry & 1) {\n      // Disable both FIQ and IRQ interrupts\n      thread->context.cpsr = 0x30;\n   } else {\n      // Only disable IRQ interrupts\n      thread->context.cpsr = 0x10;\n   }\n\n   thread->context.lr = 0xDEADC0DE;\n#else\n   thread->context.entryPoint = entry;\n   thread->context.entryPointArg = context;\n   thread->context.fiber = platform::createFiber(internal::iosFiberEntryPoint,\n                                                 thread.get());\n#endif\n\n   return static_cast<Error>(thread->id);\n}\n\nError\nIOS_JoinThread(ThreadId id,\n               phys_ptr<Error> returnedValue)\n{\n   auto currentThread = internal::getCurrentThread();\n\n   if (!id) {\n      id = currentThread->id;\n   } else if (id >= sData->threads.size()) {\n      return Error::Invalid;\n   }\n\n   auto thread = phys_addrof(sData->threads[id]);\n   if (thread->pid != currentThread->pid) {\n      // Can only join threads belonging to the same process.\n      return Error::Invalid;\n   }\n\n   if (thread == currentThread) {\n      // Can't join self.\n      return Error::Invalid;\n   }\n\n   if (thread->flags & ThreadFlags::Detached) {\n      // Can't join a detached thread.\n      return Error::Invalid;\n   }\n\n   /*\n   if (thread->unknown0x64 != dword_81430D4) {\n      return Error::Invalid;\n   }\n   */\n\n   if (thread->state != ThreadState::Dead) {\n      internal::sleepThread(phys_addrof(thread->joinQueue));\n      internal::reschedule();\n   }\n\n   if (returnedValue) {\n      *returnedValue = thread->exitValue;\n   }\n\n   thread->state = ThreadState::Available;\n   return Error::OK;\n}\n\nError\nIOS_CancelThread(ThreadId id,\n                 Error exitValue)\n{\n   auto currentThread = internal::getCurrentThread();\n\n   if (!id) {\n      id = currentThread->id;\n   } else if (id >= sData->threads.size()) {\n      return Error::Invalid;\n   }\n\n   auto thread = phys_addrof(sData->threads[id]);\n   if (thread->pid != currentThread->pid) {\n      // Can only cancel threads belonging to the same process.\n      return Error::Invalid;\n   }\n\n   if (thread->state != ThreadState::Stopped) {\n      ThreadQueue_RemoveThread(thread->threadQueue, thread);\n   }\n\n   thread->exitValue = exitValue;\n   sData->numActiveThreads--;\n\n   if (thread->flags & ThreadFlags::Detached) {\n      thread->state = ThreadState::Available;\n   } else {\n      thread->state = ThreadState::Dead;\n      internal::wakeupAllThreads(phys_addrof(thread->joinQueue), Error::OK);\n   }\n\n   if (thread == currentThread) {\n      internal::reschedule();\n   }\n\n   return Error::Invalid;\n}\n\nError\nIOS_StartThread(ThreadId id)\n{\n   auto currentThread = internal::getCurrentThread();\n\n   if (!id) {\n      id = currentThread->id;\n   } else if (id >= sData->threads.size()) {\n      return Error::Invalid;\n   }\n\n   auto thread = phys_addrof(sData->threads[id]);\n   if (currentThread->pid != ProcessId::KERNEL && thread->pid != currentThread->pid) {\n      // Can only start threads belonging to the same process.\n      // Unless we're the kernel of course.\n      return Error::Invalid;\n   }\n\n   if (thread->state != ThreadState::Stopped) {\n      // Can only start a stopped thread.\n      return Error::Invalid;\n   }\n\n   if (thread->threadQueue && !internal::isThreadInRunQueue(thread)) {\n      thread->state = ThreadState::Waiting;\n      ThreadQueue_PushThread(thread->threadQueue, thread);\n   } else {\n      thread->state = ThreadState::Ready;\n      internal::queueThread(thread);\n   }\n\n   internal::reschedule();\n   return Error::OK;\n}\n\nError\nIOS_SuspendThread(ThreadId id)\n{\n   auto currentThread = internal::getCurrentThread();\n\n   if (!id) {\n      id = currentThread->id;\n   } else if (id >= sData->threads.size()) {\n      return Error::Invalid;\n   }\n\n   auto thread = phys_addrof(sData->threads[id]);\n   if (thread->pid != currentThread->pid) {\n      // Can only suspend threads belonging to the same process.\n      return Error::Invalid;\n   }\n\n   if (thread->state == ThreadState::Running) {\n      thread->state = ThreadState::Stopped;\n   } else if (thread->state != ThreadState::Waiting &&\n              thread->state != ThreadState::Ready) {\n      // Cannot suspend a thread which is not Running, Ready or Waiting.\n      return Error::Invalid;\n   } else {\n      thread->state = ThreadState::Stopped;\n      ThreadQueue_RemoveThread(thread->threadQueue, thread);\n   }\n\n   if (thread == currentThread) {\n      internal::reschedule();\n   }\n\n   return Error::OK;\n}\n\nError\nIOS_YieldCurrentThread()\n{\n   internal::reschedule(true);\n   return Error::Invalid;\n}\n\nError\nIOS_GetCurrentThreadId()\n{\n   auto thread = internal::getCurrentThread();\n   if (!thread) {\n      return Error::Invalid;\n   }\n\n   return static_cast<Error>(thread->id);\n}\n\nphys_ptr<ThreadLocalStorage>\nIOS_GetCurrentThreadLocalStorage()\n{\n   auto thread = internal::getCurrentThread();\n   if (!thread) {\n      return nullptr;\n   }\n\n   return thread->threadLocalStorage;\n}\n\nError\nIOS_GetThreadPriority(ThreadId id)\n{\n   auto currentThread = internal::getCurrentThread();\n\n   if (!id) {\n      id = currentThread->id;\n   } else if (id >= sData->threads.size()) {\n      return Error::Invalid;\n   }\n\n   auto thread = phys_addrof(sData->threads[id]);\n   if (thread->pid != currentThread->pid) {\n      // Can only access threads in same process.\n      return Error::Invalid;\n   }\n\n   return static_cast<Error>(thread->priority);\n}\n\nError\nIOS_SetThreadPriority(ThreadId id,\n                      ThreadPriority priority)\n{\n   auto currentThread = internal::getCurrentThread();\n\n   if (!id) {\n      id = currentThread->id;\n   } else if (id >= sData->threads.size()) {\n      return Error::Invalid;\n   }\n\n   auto thread = phys_addrof(sData->threads[id]);\n   if (thread->pid != currentThread->pid) {\n      // Can only access threads in same process.\n      return Error::Invalid;\n   }\n\n   if (priority > MaxThreadPriority || priority > thread->maxPriority) {\n      return Error::Invalid;\n   }\n\n   if (thread->priority == priority) {\n      return Error::OK;\n   }\n\n   thread->priority = priority;\n\n   if (thread != currentThread) {\n      ThreadQueue_RemoveThread(thread->threadQueue, thread);\n      ThreadQueue_PushThread(thread->threadQueue, thread);\n   }\n\n   internal::reschedule();\n   return Error::OK;\n}\n\nnamespace internal\n{\n\nstatic void\nmemset32(phys_ptr<void> ptr,\n         uint32_t value,\n         uint32_t bytes)\n{\n   auto ptr32 = phys_cast<uint32_t *>(ptr);\n\n   for (auto i = 0u; i < bytes / 4; ++i) {\n      ptr32[i] = value;\n   }\n}\n\nstatic void\niosFiberEntryPoint(void *context)\n{\n   auto thread = reinterpret_cast<Thread *>(context);\n#ifndef IOS_EMULATE_ARM\n   auto exitValue = thread->context.entryPoint(thread->context.entryPointArg);\n#else\n   decaf_abort(\"iosFiberEntryPoint for IOS_EMULATE_ARM unimplemented\");\n#endif\n\n   if (thread->flags & ThreadFlags::Detached) {\n      IOS_CancelThread(thread->id, exitValue);\n   } else {\n      thread->exitValue = exitValue;\n      IOS_SuspendThread(thread->id);\n   }\n}\n\nphys_ptr<Thread>\ngetThread(ThreadId id)\n{\n   auto thread = phys_addrof(sData->threads[id]);\n   if (thread->id == id) {\n      return thread;\n   }\n\n   return nullptr;\n}\n\nvoid\nsetThreadName(ThreadId id,\n              const char *name)\n{\n   if (auto thread = getThread(id)) {\n      thread->context.threadName = name;\n   }\n}\n\nvoid\ninitialiseStaticThreadData()\n{\n   sData = allocProcessStatic<StaticThreadData>();\n}\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_thread.h",
    "content": "#pragma once\n#include \"ios_kernel_enum.h\"\n#include \"ios_kernel_threadqueue.h\"\n#include \"ios/ios_enum.h\"\n\n#include <cstdint>\n#include <common/structsize.h>\n#include <common/platform_fiber.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::kernel\n{\n\nstatic constexpr auto MaxNumThreads = 180u;\nstatic constexpr auto MaxThreadPriority = 127;\n\n#pragma pack(push, 1)\n\nusing ThreadId = uint32_t;\nusing ThreadPriority = int32_t;\nusing ThreadEntryFn = Error(*)(phys_ptr<void>);\n\nconstexpr auto CurrentThread = ThreadId { 0 };\n\nstruct ContextLLE\n{\n   be2_val<uint32_t> cpsr;\n   be2_val<uint32_t> gpr[13];\n   be2_phys_ptr<void> stackPointer;\n   be2_val<uint32_t> lr;\n   be2_val<uint32_t> pc;\n};\nCHECK_OFFSET(ContextLLE, 0x00, cpsr);\nCHECK_OFFSET(ContextLLE, 0x04, gpr);\nCHECK_OFFSET(ContextLLE, 0x38, stackPointer);\nCHECK_OFFSET(ContextLLE, 0x3C, lr);\nCHECK_OFFSET(ContextLLE, 0x40, pc);\nCHECK_SIZE(ContextLLE, 0x44);\n\nstruct ContextHLE\n{\n   ThreadEntryFn entryPoint;\n   phys_ptr<void> entryPointArg;\n   platform::Fiber *fiber;\n   Error queueWaitResult;\n   const char *threadName;\n   PADDING(0x24);\n};\nCHECK_SIZE(ContextHLE, 0x44);\n\nstruct ThreadLocalStorage\n{\n   be2_array<uint32_t, 9> storage;\n};\nCHECK_OFFSET(ThreadLocalStorage, 0, storage);\nCHECK_SIZE(ThreadLocalStorage, 0x24);\n\nstruct Thread\n{\n   be2_struct<ContextHLE> context;\n\n   //! Link to next item in the thread queue.\n   be2_phys_ptr<Thread> threadQueueNext;\n\n   be2_val<ThreadPriority> maxPriority;\n   be2_val<ThreadPriority> priority;\n   be2_val<ThreadState> state;\n   be2_val<ProcessId> pid;\n   be2_val<ThreadId> id;\n   be2_val<ThreadFlags> flags;\n   be2_val<Error> exitValue;\n\n   //! Queue of threads waiting to join this thread.\n   be2_struct<ThreadQueue> joinQueue;\n\n   //! The thread queue this therad is currently in.\n   be2_phys_ptr<ThreadQueue> threadQueue;\n\n   be2_struct<ContextLLE> userContext;\n   be2_phys_ptr<void> sysStackAddr;\n   be2_phys_ptr<void> userStackAddr;\n   be2_val<uint32_t> userStackSize;\n   be2_phys_ptr<ThreadLocalStorage> threadLocalStorage;\n   be2_val<uint32_t> profileCount;\n   be2_val<uint32_t> profileTime;\n};\nCHECK_OFFSET(Thread, 0, context);\nCHECK_OFFSET(Thread, 0x44, threadQueueNext);\nCHECK_OFFSET(Thread, 0x48, maxPriority);\nCHECK_OFFSET(Thread, 0x4C, priority);\nCHECK_OFFSET(Thread, 0x50, state);\nCHECK_OFFSET(Thread, 0x54, pid);\nCHECK_OFFSET(Thread, 0x58, id);\nCHECK_OFFSET(Thread, 0x5C, flags);\nCHECK_OFFSET(Thread, 0x60, exitValue);\nCHECK_OFFSET(Thread, 0x64, joinQueue);\nCHECK_OFFSET(Thread, 0x68, threadQueue);\nCHECK_OFFSET(Thread, 0x6C, userContext);\nCHECK_OFFSET(Thread, 0xB0, sysStackAddr);\nCHECK_OFFSET(Thread, 0xB4, userStackAddr);\nCHECK_OFFSET(Thread, 0xB8, userStackSize);\nCHECK_OFFSET(Thread, 0xBC, threadLocalStorage);\nCHECK_OFFSET(Thread, 0xC0, profileCount);\nCHECK_OFFSET(Thread, 0xC4, profileTime);\nCHECK_SIZE(Thread, 0xC8);\n\n#pragma pack(pop)\n\nError\nIOS_CreateThread(ThreadEntryFn entry,\n                 phys_ptr<void> context,\n                 phys_ptr<uint8_t> stackTop,\n                 uint32_t stackSize,\n                 int priority,\n                 ThreadFlags flags);\n\nError\nIOS_JoinThread(ThreadId id,\n               phys_ptr<Error> returnedValue);\n\nError\nIOS_CancelThread(ThreadId id,\n                 Error exitValue);\n\nError\nIOS_StartThread(ThreadId id);\n\nError\nIOS_SuspendThread(ThreadId id);\n\nError\nIOS_YieldCurrentThread();\n\nError\nIOS_GetCurrentThreadId();\n\nphys_ptr<ThreadLocalStorage>\nIOS_GetCurrentThreadLocalStorage();\n\nError\nIOS_GetThreadPriority(ThreadId id);\n\nError\nIOS_SetThreadPriority(ThreadId id,\n                      ThreadPriority priority);\n\nnamespace internal\n{\n\nphys_ptr<Thread>\ngetCurrentThread();\n\nThreadId\ngetCurrentThreadId();\n\nphys_ptr<Thread>\ngetThread(ThreadId id);\n\nvoid\nsetThreadName(ThreadId id,\n              const char *name);\n\nvoid\ninitialiseStaticThreadData();\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_threadqueue.cpp",
    "content": "#include \"ios_kernel_thread.h\"\n#include \"ios_kernel_threadqueue.h\"\n\nnamespace ios::kernel\n{\n\nvoid\nThreadQueue_Initialise(phys_ptr<ThreadQueue> queue)\n{\n   queue->first = nullptr;\n}\n\nvoid\nThreadQueue_PushThread(phys_ptr<ThreadQueue> queue,\n                       phys_ptr<Thread> thread)\n{\n   if (!queue || !thread) {\n      return;\n   }\n\n   auto insertAt = phys_addrof(queue->first);\n   auto next = queue->first;\n\n   for (auto itr = queue->first; itr; itr = itr->threadQueueNext) {\n      if (thread->priority > itr->priority) {\n         break;\n      }\n\n      insertAt = phys_addrof(itr->threadQueueNext);\n      next = itr->threadQueueNext;\n   }\n\n   *insertAt = thread;\n   thread->threadQueue = queue;\n   thread->threadQueueNext = next;\n}\n\nphys_ptr<Thread>\nThreadQueue_PeekThread(phys_ptr<ThreadQueue> queue)\n{\n   return queue->first;\n}\n\nphys_ptr<Thread>\nThreadQueue_PopThread(phys_ptr<ThreadQueue> queue)\n{\n   auto thread = queue->first;\n\n   if (thread) {\n      queue->first = thread->threadQueueNext;\n      thread->threadQueue = nullptr;\n      thread->threadQueueNext = nullptr;\n   } else {\n      queue->first = nullptr;\n   }\n\n   return thread;\n}\n\nvoid\nThreadQueue_RemoveThread(phys_ptr<ThreadQueue> queue,\n                         phys_ptr<Thread> removeThread)\n{\n   if (!queue || !removeThread) {\n      return;\n   }\n\n   auto thread = queue->first;\n   auto removeAt = phys_addrof(queue->first);\n\n   while (thread) {\n      if (thread == removeThread) {\n         *removeAt = removeThread->threadQueueNext;\n         break;\n      }\n\n      removeAt = phys_addrof(thread->threadQueueNext);\n      thread = thread->threadQueueNext;\n   }\n}\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_threadqueue.h",
    "content": "#pragma once\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::kernel\n{\n\nstruct Thread;\n\nstruct ThreadQueue\n{\n   //! Linked list on thread->threadQueueNext.\n   be2_phys_ptr<Thread> first;\n};\nCHECK_OFFSET(ThreadQueue, 0x0, first);\nCHECK_SIZE(ThreadQueue, 0x4);\n\nvoid\nThreadQueue_Initialise(phys_ptr<ThreadQueue> queue);\n\nvoid\nThreadQueue_PushThread(phys_ptr<ThreadQueue> queue,\n                       phys_ptr<Thread> thread);\n\nphys_ptr<Thread>\nThreadQueue_PeekThread(phys_ptr<ThreadQueue> queue);\n\nphys_ptr<Thread>\nThreadQueue_PopThread(phys_ptr<ThreadQueue> queue);\n\nvoid\nThreadQueue_RemoveThread(phys_ptr<ThreadQueue> queue,\n                         phys_ptr<Thread> removeThread);\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_timer.cpp",
    "content": "#include \"ios_kernel_timer.h\"\n#include \"ios_kernel_hardware.h\"\n#include \"ios_kernel_messagequeue.h\"\n#include \"ios_kernel_process.h\"\n#include \"ios_kernel_thread.h\"\n\n#include \"ios/ios_alarm_thread.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <chrono>\n\nnamespace ios::kernel\n{\n\nconstexpr auto TimerThreadNumMessages = 1u;\nconstexpr auto TimerThreadStackSize = 0x400u;\nconstexpr auto TimerThreadPriority = 125u;\n\nstruct StaticTimerData\n{\n   be2_val<ThreadId> threadId;\n   be2_val<MessageQueueId> messageQueueId;\n   be2_array<Message, TimerThreadNumMessages> messageBuffer;\n   be2_array<uint8_t, TimerThreadStackSize> threadStack;\n   be2_struct<TimerManager> timerManager;\n};\n\nstatic phys_ptr<StaticTimerData>\nsData;\n\nstatic std::chrono::time_point<std::chrono::steady_clock>\nsStartupTime;\n\nnamespace internal\n{\n\nuint32_t\nstartTimer(phys_ptr<Timer> timer);\n\nvoid\nsetAlarm(TimerTicks when);\n\n} // namespace internal\n\nError\nIOS_GetUpTime64(phys_ptr<TimerTicks> outTime)\n{\n   *outTime = internal::getUpTime64();\n   return Error::OK;\n}\n\nError\nIOS_CreateTimer(std::chrono::microseconds delay,\n                std::chrono::microseconds period,\n                MessageQueueId queue,\n                Message message)\n{\n   auto &timerManager = sData->timerManager;\n\n   // Verify the message queue exists.\n   auto error = internal::getMessageQueueSafe(queue, nullptr);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Check the process has not exceeded its maximum number of processes.\n   auto pid = internal::getCurrentProcessId();\n   if (timerManager.numProcessTimers[pid] >= MaxNumTimersPerProcess) {\n      return Error::Max;\n   }\n\n   if (timerManager.firstFreeIdx < 0) {\n      return Error::FailAlloc;\n   }\n\n   auto timerIdx = timerManager.firstFreeIdx;\n   auto &timer = timerManager.timers[timerIdx];\n   auto nextFreeIdx = timer.nextTimerIdx;\n\n   if (nextFreeIdx < 0) {\n      timerManager.firstFreeIdx = int16_t { -1 };\n      timerManager.lastFreeIdx = int16_t { -1 };\n   } else {\n      auto &nextTimer = timerManager.timers[nextFreeIdx];\n      nextTimer.prevTimerIdx = int16_t { -1 };\n      timerManager.firstFreeIdx = nextFreeIdx;\n   }\n\n   timerManager.totalCreatedTimers++;\n\n   timer.uid = timerIdx | static_cast<int32_t>((timerManager.totalCreatedTimers << 12) & 0x7FFFFFFF);\n   timer.state = TimerState::Ready;\n   timer.nextTriggerTime = TimerTicks { 0 };\n   timer.period = static_cast<TimeMicroseconds32>(period.count());\n   timer.queueId = queue;\n   timer.message = message;\n   timer.processId = pid;\n   timer.prevTimerIdx = int16_t { -1 };\n   timer.nextTimerIdx = int16_t { -1 };\n\n   timerManager.numRegistered++;\n   if (timerManager.numRegistered > timerManager.mostRegistered) {\n      timerManager.mostRegistered = timerManager.numRegistered;\n   }\n\n   if (delay.count() || period.count()) {\n      timer.nextTriggerTime = internal::getUpTime64() + internal::durationToTicks(delay);\n      if (internal::startTimer(phys_addrof(timer)) == 0) {\n         internal::setAlarm(timer.nextTriggerTime);\n      }\n   }\n\n   return static_cast<Error>(timer.uid);\n}\n\nError\nIOS_DestroyTimer(TimerId timerId)\n{\n   phys_ptr<Timer> timer;\n   auto &timerManager = sData->timerManager;\n\n   auto error = internal::getTimer(timerId, &timer);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   if (timer->state == TimerState::Running) {\n      error = internal::stopTimer(timer);\n      if (error < Error::OK) {\n         return error;\n      }\n   }\n\n   auto pid = internal::getCurrentProcessId();\n   timerManager.numProcessTimers[pid]--;\n\n   timer->state = TimerState::Free;\n   timer->uid = TimerId { -4 };\n   timer->processId = ProcessId { -4 };\n   timer->queueId = MessageQueueId { -4 };\n   timer->message = Message { 0 };\n   timer->period = 0u;\n   timer->nextTriggerTime = TimerTicks{ 0 };\n\n   if (timerManager.lastFreeIdx < 0) {\n      timerManager.firstFreeIdx = timer->index;\n      timerManager.lastFreeIdx = timer->index;\n\n      timer->nextTimerIdx = int16_t { -1 };\n      timer->prevTimerIdx = int16_t { -1 };\n   } else {\n      auto &lastFreeTimer = timerManager.timers[timerManager.lastFreeIdx];\n      lastFreeTimer.nextTimerIdx = timer->index;\n\n      timer->prevTimerIdx = timerManager.lastFreeIdx;\n      timer->nextTimerIdx = int16_t { -1 };\n\n      timerManager.lastFreeIdx = timer->index;\n   }\n\n   timerManager.numRegistered--;\n   return Error::OK;\n}\n\nError\nIOS_StopTimer(TimerId timerId)\n{\n   phys_ptr<Timer> timer;\n   auto error = internal::getTimer(timerId, &timer);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   if (timer->state == TimerState::Running) {\n      error = internal::stopTimer(timer);\n   } else {\n      timer->state = TimerState::Stopped;\n      error = Error::Expired;\n   }\n\n   return error;\n}\n\nError\nIOS_RestartTimer(TimerId timerId,\n                 std::chrono::microseconds delay,\n                 std::chrono::microseconds period)\n{\n   phys_ptr<Timer> timer;\n\n   auto error = internal::getTimer(timerId, &timer);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   if (timer->state == TimerState::Running) {\n      error = internal::stopTimer(timer);\n      if (error < Error::OK) {\n         return error;\n      }\n   }\n\n   timer->nextTriggerTime = TimerTicks { 0 };\n   timer->period = static_cast<TimeMicroseconds32>(period.count());\n\n   if (delay.count() || period.count()) {\n      timer->nextTriggerTime = internal::getUpTime64() + internal::durationToTicks(delay);\n      if (internal::startTimer(timer) == 0) {\n         internal::setAlarm(timer->nextTriggerTime);\n      }\n   }\n\n   return error;\n}\n\nnamespace internal\n{\n\nstatic Error\ntimerThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n   auto &timerManager = sData->timerManager;\n\n   while (true) {\n      auto error = IOS_ReceiveMessage(sData->messageQueueId,\n                                      message,\n                                      MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto now = internal::getUpTime64();\n      while (timerManager.firstRunningTimerIdx >= 0) {\n         auto &timer = timerManager.timers[timerManager.firstRunningTimerIdx];\n         auto queue = phys_ptr<MessageQueue>(nullptr);\n         if (timer.nextTriggerTime >= now) {\n            // No more timers to run\n            break;\n         }\n\n         // Send message to notify any waiters on the timer\n         error = internal::getMessageQueue(timer.queueId, &queue);\n         if (error >= 0) {\n            internal::sendMessage(queue, timer.message, MessageFlags::NonBlocking);\n         }\n\n         // Remove timer from queue\n         timerManager.firstRunningTimerIdx = timer.nextTimerIdx;\n         if (timerManager.firstRunningTimerIdx < 0) {\n            timerManager.lastRunningTimerIdx = int16_t { -1 };\n         } else {\n            timerManager.timers[timerManager.firstRunningTimerIdx].prevTimerIdx = int16_t { -1 };\n         }\n\n         // Update timer\n         timer.prevTimerIdx = int16_t { -1 };\n         timer.nextTimerIdx = int16_t { -1 };\n         timer.state = TimerState::Triggered;\n\n         if (!timer.period) {\n            continue;\n         }\n\n         // Setup periodic timer next trigger\n         timer.nextTriggerTime += timer.period;\n         startTimer(phys_addrof(timer));\n      }\n   }\n}\n\nvoid\nsetAlarm(TimerTicks when)\n{\n   auto nextAlarm = std::chrono::nanoseconds { when } + sStartupTime;\n   ios::internal::setNextAlarm(nextAlarm);\n}\n\nTimerTicks\ntimeToTicks(std::chrono::steady_clock::time_point time)\n{\n   auto dt = time - sStartupTime;\n   return std::chrono::duration_cast<std::chrono::nanoseconds>(dt).count();\n}\n\nError\ngetTimer(TimerId id,\n         phys_ptr<Timer> *outTimer)\n{\n   auto index = id & 0xFFF;\n\n   if (id < 0 || index >= static_cast<TimerId>(sData->timerManager.timers.size())) {\n      return Error::Invalid;\n   }\n\n   if (sData->timerManager.timers[index].uid != id) {\n      return Error::NoExists;\n   }\n\n   *outTimer = phys_addrof(sData->timerManager.timers[index]);\n   return Error::OK;\n}\n\n/**\n * Put the timer into the running timer list.\n *\n * \\return\n * Returns the position of the timer in the queue, if the position is 0 then\n * the new timer will be the first one to trigger in which case the caller\n * is responsbile for updating the next interrupt time.\n */\nuint32_t\nstartTimer(phys_ptr<Timer> timer)\n{\n   auto &timerManager = sData->timerManager;\n   auto timerQueuePosition = 0u;\n\n   // Insert timer into the running timer list ordered by nextTriggerTime.\n   if (timerManager.firstRunningTimerIdx < 0) {\n      timerManager.firstRunningTimerIdx = timer->index;\n      timerManager.lastRunningTimerIdx = timer->index;\n\n      timer->prevTimerIdx = int16_t { -1 };\n      timer->nextTimerIdx = int16_t { -1 };\n   } else {\n      auto prevTimerIdx = int16_t { -1 };\n      auto nextTimerIdx = int16_t { -1 };\n      auto itrTimerIdx = timerManager.firstRunningTimerIdx;\n\n      while (itrTimerIdx >= 0) {\n         const auto &itrTimer = timerManager.timers[itrTimerIdx];\n         if (itrTimer.nextTriggerTime >= timer->nextTriggerTime) {\n            break;\n         }\n\n         itrTimerIdx = itrTimer.nextTimerIdx;\n         timerQueuePosition++;\n      }\n\n      if (prevTimerIdx < 0) {\n         nextTimerIdx = timerManager.firstRunningTimerIdx;\n         timerManager.firstRunningTimerIdx = timer->index;\n      } else {\n         auto &prevTimer = timerManager.timers[prevTimerIdx];\n         nextTimerIdx = prevTimer.nextTimerIdx;\n         prevTimer.nextTimerIdx = timer->index;\n      }\n\n      if (nextTimerIdx < 0) {\n         decaf_check(timerManager.lastRunningTimerIdx == prevTimerIdx);\n         timerManager.lastRunningTimerIdx = timer->index;\n      } else {\n         auto &nextTimer = timerManager.timers[nextTimerIdx];\n         nextTimer.prevTimerIdx = timer->index;\n      }\n\n      timer->prevTimerIdx = prevTimerIdx;\n      timer->nextTimerIdx = nextTimerIdx;\n   }\n\n   timer->state = TimerState::Running;\n   timerManager.numRunningTimers++;\n   return timerQueuePosition;\n}\n\nError\nstopTimer(phys_ptr<Timer> timer)\n{\n   auto &timerManager = sData->timerManager;\n   auto prevTimerIdx = timer->prevTimerIdx;\n   auto nextTimerIdx = timer->nextTimerIdx;\n\n   if (prevTimerIdx < 0) {\n      decaf_check(timerManager.firstRunningTimerIdx == timer->index);\n      timerManager.firstRunningTimerIdx = nextTimerIdx;\n   } else {\n      auto &prevTimer = timerManager.timers[prevTimerIdx];\n      prevTimer.nextTimerIdx = nextTimerIdx;\n   }\n\n   if (nextTimerIdx < 0) {\n      decaf_check(timerManager.lastRunningTimerIdx == timer->index);\n      timerManager.lastRunningTimerIdx = prevTimerIdx;\n   } else {\n      auto &nextTimer = timerManager.timers[nextTimerIdx];\n      nextTimer.prevTimerIdx = prevTimerIdx;\n   }\n\n   timer->state = TimerState::Stopped;\n   timer->prevTimerIdx = int16_t { -1 };\n   timer->nextTimerIdx = int16_t { -1 };\n   timerManager.numRunningTimers--;\n   return Error::OK;\n}\n\nError\nstartTimerThread()\n{\n   // Create message queue\n   auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer),\n                                       static_cast<uint32_t>(sData->messageBuffer.size()));\n   if (error < Error::OK) {\n      return error;\n   }\n   sData->messageQueueId = static_cast<MessageQueueId>(error);\n\n   // Set timer event handler\n   error = IOS_HandleEvent(DeviceId::Timer, sData->messageQueueId, 0);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Create thread\n   error = IOS_CreateThread(&timerThreadEntry, nullptr,\n                            phys_addrof(sData->threadStack) + sData->threadStack.size(),\n                            static_cast<uint32_t>(sData->threadStack.size()),\n                            TimerThreadPriority,\n                            kernel::ThreadFlags::Detached);\n   if (error < Error::OK) {\n      kernel::IOS_DestroyMessageQueue(sData->messageQueueId);\n      return error;\n   }\n\n   sData->threadId = static_cast<kernel::ThreadId>(error);\n   internal::setThreadName(sData->threadId, \"TimerThread\");\n\n   return kernel::IOS_StartThread(sData->threadId);\n}\n\nvoid\ninitialiseStaticTimerData()\n{\n   sStartupTime = std::chrono::steady_clock::now();\n   sData = allocProcessStatic<StaticTimerData>();\n\n   for (auto i = 0u; i < sData->timerManager.timers.size(); ++i) {\n      auto &timer = sData->timerManager.timers[i];\n      timer.uid = TimerId { -4 };\n      timer.processId = ProcessId { -4 };\n      timer.state = TimerState::Free;\n      timer.prevTimerIdx = static_cast<int16_t>(i - 1);\n      timer.index = static_cast<int16_t>(i);\n      timer.nextTimerIdx = static_cast<int16_t>(i + 1);\n   }\n\n   sData->timerManager.timers[sData->timerManager.timers.size() - 1].nextTimerIdx = int16_t { -1 };\n   sData->timerManager.firstFreeIdx = int16_t { 0 };\n   sData->timerManager.lastFreeIdx = int16_t { 255 };\n   sData->timerManager.firstRunningTimerIdx = int16_t { -1 };\n   sData->timerManager.lastRunningTimerIdx = int16_t { -1 };\n}\n\nTimerTicks\ngetUpTime64()\n{\n   return internal::timeToTicks(std::chrono::steady_clock::now());\n}\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/kernel/ios_kernel_timer.h",
    "content": "#pragma once\n#include \"ios_kernel_enum.h\"\n#include \"ios_kernel_messagequeue.h\"\n#include \"ios_kernel_process.h\"\n#include \"ios/ios_enum.h\"\n\n#include <chrono>\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::kernel\n{\n\n#pragma pack(push, 1)\n\nstatic constexpr auto MaxNumTimers = 256u;\nstatic constexpr auto MaxNumTimersPerProcess = 64u;\n\nusing TimerId = int32_t;\nusing TimerTicks = uint64_t;\nusing TimeMicroseconds32 = uint32_t;\nusing TimeMicroseconds64 = uint64_t;\n\nstruct Timer\n{\n   be2_val<TimerId> uid;\n   be2_val<TimerState> state;\n   be2_val<TimerTicks> nextTriggerTime;\n   be2_val<TimeMicroseconds32> period;\n   be2_val<MessageQueueId> queueId;\n   be2_val<Message> message;\n   be2_val<ProcessId> processId;\n\n   //! This timers index in the TimerManager timers list.\n   be2_val<int16_t> index;\n\n   //! If state == Free this is the index of the next free timer.\n   //! If state == Running this is the index of the next running timer.\n   be2_val<int16_t> nextTimerIdx;\n\n   //! If state == Free this is the index of the previous free timer.\n   //! If state == Running this is the index of the previous running timer.\n   be2_val<int16_t> prevTimerIdx;\n\n   be2_val<uint16_t> unk0x26;\n};\nCHECK_OFFSET(Timer, 0x00, uid);\nCHECK_OFFSET(Timer, 0x04, state);\nCHECK_OFFSET(Timer, 0x08, nextTriggerTime);\nCHECK_OFFSET(Timer, 0x10, period);\nCHECK_OFFSET(Timer, 0x14, queueId);\nCHECK_OFFSET(Timer, 0x18, message);\nCHECK_OFFSET(Timer, 0x1C, processId);\nCHECK_OFFSET(Timer, 0x20, index);\nCHECK_OFFSET(Timer, 0x22, nextTimerIdx);\nCHECK_OFFSET(Timer, 0x24, prevTimerIdx);\nCHECK_OFFSET(Timer, 0x26, unk0x26);\nCHECK_SIZE(Timer, 0x28);\n\nstruct TimerManager\n{\n   //! Total number of timers that have been created.\n   be2_val<uint32_t> totalCreatedTimers;\n\n   //! Number of timers each process has\n   be2_array<uint16_t, NumIosProcess> numProcessTimers;\n\n   //! Index of the first running Timer, ordered by nextTriggerTime.\n   be2_val<int16_t> firstRunningTimerIdx;\n\n   //! Index of the last running Timer, ordered by nextTriggerTime.\n   be2_val<int16_t> lastRunningTimerIdx;\n\n   //! Number of actively running timers.\n   be2_val<uint16_t> numRunningTimers;\n\n   //! Index of the first free Timer.\n   be2_val<int16_t> firstFreeIdx;\n\n   //! Index of the last free Timer.\n   be2_val<int16_t> lastFreeIdx;\n\n   //! Number of registered Timers.\n   be2_val<uint16_t> numRegistered;\n\n   //! Highest number of registered Timers at one time.\n   be2_val<uint16_t> mostRegistered;\n\n   be2_val<uint16_t> unk0x2E;\n\n   be2_array<Timer, MaxNumTimers> timers;\n};\nCHECK_OFFSET(TimerManager, 0x00, totalCreatedTimers);\nCHECK_OFFSET(TimerManager, 0x04, numProcessTimers);\nCHECK_OFFSET(TimerManager, 0x20, firstRunningTimerIdx);\nCHECK_OFFSET(TimerManager, 0x22, lastRunningTimerIdx);\nCHECK_OFFSET(TimerManager, 0x24, numRunningTimers);\nCHECK_OFFSET(TimerManager, 0x26, firstFreeIdx);\nCHECK_OFFSET(TimerManager, 0x28, lastFreeIdx);\nCHECK_OFFSET(TimerManager, 0x2A, numRegistered);\nCHECK_OFFSET(TimerManager, 0x2C, mostRegistered);\nCHECK_OFFSET(TimerManager, 0x2E, unk0x2E);\nCHECK_OFFSET(TimerManager, 0x30, timers);\n\n#pragma pack(pop)\n\nError\nIOS_GetUpTime64(phys_ptr<TimerTicks> outTime);\n\nError\nIOS_CreateTimer(std::chrono::microseconds delay,\n                std::chrono::microseconds period,\n                MessageQueueId queue,\n                Message message);\n\nError\nIOS_DestroyTimer(TimerId timerId);\n\nError\nIOS_StopTimer(TimerId timerId);\n\nError\nIOS_RestartTimer(TimerId timerId,\n                 std::chrono::microseconds delay,\n                 std::chrono::microseconds period);\n\nnamespace internal\n{\n\nError\ngetTimer(TimerId id,\n         phys_ptr<Timer> *outTimer);\n\nError\nstopTimer(phys_ptr<Timer> timer);\n\nTimerTicks\ntimeToTicks(std::chrono::steady_clock::time_point time);\n\ntemplate<class Rep, class Period>\nTimerTicks\ndurationToTicks(std::chrono::duration<Rep, Period> duration)\n{\n   return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count();\n}\n\nError\nstartTimerThread();\n\nvoid\ninitialiseStaticTimerData();\n\nTimerTicks\ngetUpTime64();\n\n} // namespace internal\n\n} // namespace ios::kernel\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp.cpp",
    "content": "#include \"ios_mcp.h\"\n#include \"ios_mcp_config.h\"\n#include \"ios_mcp_enum.h\"\n#include \"ios_mcp_mcp_thread.h\"\n#include \"ios_mcp_pm_thread.h\"\n#include \"ios_mcp_ppc_thread.h\"\n#include \"ios_mcp_title.h\"\n\n#include \"ios/kernel/ios_kernel_debug.h\"\n#include \"ios/kernel/ios_kernel_hardware.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n\n#include \"ios/ios_stackobject.h\"\n\n#include <common/log.h>\n\nusing namespace ios::kernel;\n\nnamespace ios::mcp\n{\n\nconstexpr auto LocalHeapSize = 0x8000u;\nconstexpr auto CrossHeapSize = 0x31E000u;\n\nconstexpr auto MainThreadNumMessages = 30u;\n\nstruct StaticMcpData\n{\n   be2_val<uint32_t> bootFlags;\n   be2_val<uint32_t> systemModeFlags;\n   be2_val<SystemFileSys> systemFileSys;\n   be2_struct<IpcRequest> sysEventMsg;\n   be2_val<MessageQueueId> messageQueueId;\n   be2_array<Message, MainThreadNumMessages> messageBuffer;\n};\n\nstatic phys_ptr<StaticMcpData>\nsData = nullptr;\n\nstatic phys_ptr<void>\nsLocalHeapBuffer = nullptr;\n\nnamespace internal\n{\n\nstatic void\ninitialiseStaticData()\n{\n   sData = allocProcessStatic<StaticMcpData>();\n   sData->sysEventMsg.command = static_cast<Command>(MainThreadCommand::SysEvent);\n\n   sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize);\n}\n\nstatic void\ninitialiseClientCaps()\n{\n   StackObject<uint64_t> mask;\n\n   struct\n   {\n      ProcessId pid;\n      FeatureId fid;\n      uint64_t mask;\n   } caps[] = {\n      { ProcessId::MCP,    0x7FFFFFFF, 0xFFFFFFFFFFFFFFFF },\n      { ProcessId::CRYPTO,          1,               0xFF },\n      { ProcessId::USB,             1,                0xF },\n      { ProcessId::USB,           0xC, 0xFFFFFFFFFFFFFFFF },\n      { ProcessId::USB,             9, 0xFFFFFFFFFFFFFFFF },\n      { ProcessId::USB,           0xB,          0x3300300 },\n      { ProcessId::FS,              1,                0xF },\n      { ProcessId::FS,              3,                  3 },\n      { ProcessId::FS,              9,                  1 },\n      { ProcessId::FS,            0xB, 0xFFFFFFFFFFFFFFFF },\n      { ProcessId::FS,              2, 0xFFFFFFFFFFFFFFFF },\n      { ProcessId::FS,            0xC,                  1 },\n      { ProcessId::PAD,           0xB,           0x101000 },\n      { ProcessId::PAD,             1,                0xF },\n      { ProcessId::PAD,           0xD,                  1 },\n      { ProcessId::PAD,          0x18, 0xFFFFFFFFFFFFFFFF },\n      { ProcessId::NET,             1,                0xF },\n      { ProcessId::NET,             8,                  1 },\n      { ProcessId::NET,           0xC,                  1 },\n      { ProcessId::NET,          0x1A, 0xFFFFFFFFFFFFFFFF },\n   };\n\n   for (auto &cap : caps) {\n      *mask = cap.mask;\n      IOS_SetClientCapabilities(cap.pid, cap.fid, mask);\n   }\n}\n\nstatic Error\ninitialiseProcessTitles()\n{\n   return static_cast<Error>(\n      IOS_SetProcessTitle(ProcessId::MCP, 0, 0) |\n      IOS_SetProcessTitle(ProcessId::BSP, 0x100000F8ull, 0x100000FF) |\n      IOS_SetProcessTitle(ProcessId::CRYPTO, 0x100000F9ull, 0x100000FF) |\n      IOS_SetProcessTitle(ProcessId::USB, 0x100000FAull, 0x100000FF) |\n      IOS_SetProcessTitle(ProcessId::FS, 0x100000F1ull, 0x100000FF) |\n      IOS_SetProcessTitle(ProcessId::PAD, 0x100000F2ull, 0x100000FF) |\n      IOS_SetProcessTitle(ProcessId::NET, 0x100000FBull, 0x100000FF) |\n      IOS_SetProcessTitle(ProcessId::ACP, 0x100000F6ull, 0x400u) |\n      IOS_SetProcessTitle(ProcessId::NSEC, 0x100000F4ull, 0x100000FF) |\n      IOS_SetProcessTitle(ProcessId::AUXIL, 0x100000F5ull, 0x100000FF) |\n      IOS_SetProcessTitle(ProcessId::NIM, 0x100000F3ull, 0x400u) |\n      IOS_SetProcessTitle(ProcessId::FPD, 0x100000F7ull, 0x400u) |\n      IOS_SetProcessTitle(ProcessId::TEST, 0x100000FCull, 0x100000FF));\n\n}\n\nstatic Error\nmainThreadLoop()\n{\n   StackObject<Message> message;\n\n   while (true) {\n      auto error = IOS_ReceiveMessage(sData->messageQueueId, message, MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      /* TODO: Handle commands\n      switch (request->requestData.command) {\n      default:\n      }\n      */\n      IOS_ResourceReply(request, Error::InvalidArg);\n   }\n}\n\nuint32_t\ngetBootFlags()\n{\n   return sData->bootFlags;\n}\n\nuint32_t\ngetSystemModeFlags()\n{\n   return sData->systemModeFlags;\n}\n\nSystemFileSys\ngetSystemFileSys()\n{\n   return sData->systemFileSys;\n}\n\n} // namespace internal\n\nError\nprocessEntryPoint(phys_ptr<void> /* context */)\n{\n   // Initialise process static data\n   internal::initialiseStaticData();\n   internal::initialiseStaticConfigData();\n   internal::initialiseStaticMcpThreadData();\n   internal::initialiseStaticPmThreadData();\n   internal::initialiseStaticPpcThreadData();\n   internal::initialiseTitleStaticData();\n\n   // Initialise process heaps\n   auto error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to create local process heap, error = {}.\", error);\n      return error;\n   }\n\n   error = IOS_CreateCrossProcessHeap(CrossHeapSize);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to create cross process heap, error = {}.\", error);\n      return error;\n   }\n\n   // Set normal system flags\n   sData->bootFlags = 0xC200000u;\n   sData->systemModeFlags = 0x100000u;\n   sData->systemFileSys = SystemFileSys::Nand;\n   IOS_SetSecurityLevel(SecurityLevel::Normal);\n\n   // Start pm thread\n   error = internal::startPmThread();\n   if (error < Error::OK) {\n      gLog->error(\"Failed to start pm thread, error = {}.\", error);\n      return error;\n   }\n\n   // Create main thread message queue\n   error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer),\n                                  sData->messageBuffer.size());\n   if (error < Error::OK) {\n      gLog->error(\"Failed to create main thread message buffer, error = {}.\", error);\n      return error;\n   }\n\n   sData->messageQueueId = static_cast<MessageQueueId>(error);\n\n   // Set process client caps\n   internal::initialiseClientCaps();\n   internal::initialiseProcessTitles();\n\n   // Stat mcp thread\n   internal::startMcpThread();\n\n   // Start PPC thread\n   internal::startPpcThread();\n\n   // Register main thread as SysEvent handler\n   error = IOS_HandleEvent(DeviceId::SysEvent,\n                           sData->messageQueueId,\n                           makeMessage(phys_addrof(sData->sysEventMsg)));\n   if (error < Error::OK) {\n      gLog->error(\"Failed to register SysEvent event handler, error = {}.\", error);\n      return error;\n   }\n\n   // Handle all pending resource manager registrations\n   error = internal::handleResourceManagerRegistrations(sData->systemModeFlags,\n                                                        sData->bootFlags);\n   if (error < Error::OK) {\n      gLog->error(\"Failed to handle resource manager registrations, error = {}.\", error);\n      return error;\n   }\n\n   return internal::mainThreadLoop();\n}\n\n} // namespace ios::mcp\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp.h",
    "content": "#pragma once\n#include \"ios_mcp_enum.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n\n#include <cstdint>\n\nnamespace ios::mcp\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\nnamespace internal\n{\n\nuint32_t\ngetBootFlags();\n\nuint32_t\ngetSystemModeFlags();\n\nSystemFileSys\ngetSystemFileSys();\n\n} // namespace internal\n\n} // namespace ios::mcp\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_config.cpp",
    "content": "#include \"ios_mcp_config.h\"\n#include \"ios/kernel/ios_kernel_debug.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include \"decaf_config.h\"\n\n#include <array>\n#include <libcpu/be2_struct.h>\n#include <string_view>\n\nusing namespace ios::auxil;\nusing namespace ios::kernel;\n\nnamespace ios::mcp::internal\n{\n\nstruct StaticConfigData\n{\n   be2_struct<RtcConfig> rtcConfig;\n   be2_struct<SystemConfig> systemConfig;\n   be2_struct<SysProdConfig> sysProdConfig;\n};\n\nstatic phys_ptr<StaticConfigData>\nsData = nullptr;\n\nstatic std::array<const char *, 5>\nsValidRootKeys =\n{\n   \"app\",\n   \"eco\",\n   \"rtc\",\n   \"system\",\n   \"sys_prod\"\n};\n\nstatic bool\nisValidRootKey(std::string_view key)\n{\n   auto securityLevel = IOS_GetSecurityLevel();\n   if (securityLevel != ios::kernel::SecurityLevel::Debug &&\n       securityLevel != ios::kernel::SecurityLevel::Test) {\n      return true;\n   }\n\n   for (auto validKey : sValidRootKeys) {\n      if (key.compare(validKey) == 0) {\n         return true;\n      }\n   }\n\n   return false;\n}\n\nstatic std::string_view\ngetFileSysPath(UCFileSys fileSys)\n{\n   switch (fileSys) {\n   case UCFileSys::Sys:\n      return \"/vol/system/config/\";\n   case UCFileSys::Slc:\n      return \"/vol/system_slc/config/\";\n   case UCFileSys::Ram:\n      return \"/vol/system_ram/config/\";\n   default:\n      return \"*error*\";\n   }\n}\n\nMCPError\ntranslateUCError(UCError error)\n{\n   switch (error) {\n   case UCError::OK:\n      return MCPError::OK;\n   case UCError::Other:\n      return MCPError::Invalid;\n   case UCError::System:\n      return MCPError::System;\n   case UCError::Alloc:\n      return MCPError::Alloc;\n   case UCError::Opcode:\n      return MCPError::Opcode;\n   case UCError::InvalidParam:\n      return MCPError::InvalidParam;\n   case UCError::InvalidType:\n      return MCPError::InvalidType;\n   case UCError::Unsupported:\n      return MCPError::Unsupported;\n   case UCError::NonLeafNode:\n      return MCPError::NonLeafNode;\n   case UCError::KeyNotFound:\n      return MCPError::KeyNotFound;\n   case UCError::Modify:\n      return MCPError::Modify;\n   case UCError::StringTooLong:\n      return MCPError::StringTooLong;\n   case UCError::RootKeysDiffer:\n      return MCPError::RootKeysDiffer;\n   case UCError::InvalidLocation:\n      return MCPError::InvalidLocation;\n   case UCError::BadComment:\n      return MCPError::BadComment;\n   case UCError::ReadAccess:\n      return MCPError::ReadAccess;\n   case UCError::WriteAccess:\n      return MCPError::WriteAccess;\n   case UCError::CreateAccess:\n      return MCPError::CreateAccess;\n   case UCError::FileSysName:\n      return MCPError::FileSysName;\n   case UCError::FileSysInit:\n      return MCPError::FileSysInit;\n   case UCError::FileSysMount:\n      return MCPError::FileSysMount;\n   case UCError::FileOpen:\n      return MCPError::FileOpen;\n   case UCError::FileStat:\n      return MCPError::FileStat;\n   case UCError::FileRead:\n      return MCPError::FileRead;\n   case UCError::FileWrite:\n      return MCPError::FileWrite;\n   case UCError::FileTooBig:\n      return MCPError::FileTooBig;\n   case UCError::FileRemove:\n      return MCPError::FileRemove;\n   case UCError::FileRename:\n      return MCPError::FileRename;\n   case UCError::FileClose:\n      return MCPError::FileClose;\n   case UCError::FileSeek:\n      return MCPError::FileSeek;\n   case UCError::MalformedXML:\n      return MCPError::MalformedXML;\n   case UCError::Version:\n      return MCPError::Version;\n   case UCError::NoIPCBuffers:\n      return MCPError::NoIpcBuffers;\n   case UCError::FileLockNeeded:\n      return MCPError::FileLockNeeded;\n   case UCError::SysProt:\n      return MCPError::SysProt;\n   default:\n      return MCPError::Invalid;\n   }\n}\n\nMCPError\nreadConfigItems(phys_ptr<UCItem> items,\n                uint32_t count)\n{\n   if (count == 0) {\n      return MCPError::OK;\n   }\n\n   auto name = std::string_view { phys_addrof(items[0].name).get() };\n   auto fileSys = getFileSys(name);\n   if (fileSys == UCFileSys::Invalid) {\n      return MCPError::InvalidLocation;\n   }\n\n   auto rootKey = getRootKey(name);\n   if (!isValidRootKey(rootKey)) {\n      return MCPError::FileSysName;\n   }\n\n   return translateUCError(readItems(getFileSysPath(fileSys), items, count, nullptr));\n}\n\nMCPError\nwriteConfigItems(phys_ptr<UCItem> items,\n                 uint32_t count)\n{\n   if (count == 0) {\n      return MCPError::OK;\n   }\n\n   auto name = std::string_view { phys_addrof(items[0].name).get() };\n   auto fileSys = getFileSys(name);\n   if (fileSys == UCFileSys::Invalid) {\n      return MCPError::InvalidLocation;\n   }\n\n   auto rootKey = getRootKey(name);\n   if (!isValidRootKey(rootKey)) {\n      return MCPError::FileSysName;\n   }\n\n   return translateUCError(writeItems(getFileSysPath(fileSys), items, count, nullptr));\n}\n\nMCPError\ndeleteConfigItems(phys_ptr<UCItem> items,\n                  uint32_t count)\n{\n   if (count == 0) {\n      return MCPError::OK;\n   }\n\n   auto name = std::string_view { phys_addrof(items[0].name).get() };\n   auto fileSys = getFileSys(name);\n   if (fileSys == UCFileSys::Invalid) {\n      return MCPError::InvalidLocation;\n   }\n\n   auto rootKey = getRootKey(name);\n   if (!isValidRootKey(rootKey)) {\n      return MCPError::FileSysName;\n   }\n\n   return translateUCError(deleteItems(getFileSysPath(fileSys), items, count));\n}\n\nMCPError\nloadRtcConfig()\n{\n   StackArray<UCItem, 3> items;\n   auto config = phys_addrof(sData->rtcConfig);\n\n   items[0].name = \"slc:rtc\";\n   items[0].access = 0x777u;\n   items[0].dataType = UCDataType::Complex;\n\n   items[1].name = \"slc:rtc.version\";\n   items[1].dataType = UCDataType::UnsignedInt;\n   items[1].dataSize = 4u;\n   items[1].data = phys_addrof(config->version);\n\n   items[2].name = \"slc:rtc.rtc_offset\";\n   items[2].dataType = UCDataType::UnsignedInt;\n   items[2].dataSize = 4u;\n   items[2].data = phys_addrof(config->rtc_offset);\n\n   auto error = readConfigItems(items, items.size());\n   if (error < MCPError::OK || config->version < 21) {\n      // Factory reset items\n      std::memset(config.get(), 0, sizeof(RtcConfig));\n      config->version = 21u;\n      config->rtc_offset = 0x4EFFA200u;\n\n      // Try save them to file\n      deleteConfigItems(items, 1);\n      writeConfigItems(items, items.size());\n   }\n\n   return MCPError::OK;\n}\n\nMCPError\nloadSystemConfig()\n{\n   StackArray<UCItem, 20> items;\n   auto config = phys_addrof(sData->systemConfig);\n\n   items[0].name = \"system\";\n   items[0].access = 0x777u;\n   items[0].dataType = UCDataType::Complex;\n\n   items[1].name = \"system.version\";\n   items[1].dataType = UCDataType::UnsignedInt;\n   items[1].dataSize = 4u;\n   items[1].data = phys_addrof(config->version);\n\n   items[2].name = \"system.cmdFlags\";\n   items[2].dataType = UCDataType::UnsignedInt;\n   items[2].dataSize = 4u;\n   items[2].data = phys_addrof(config->cmdFlags);\n\n   items[3].name = \"system.default_os_id\";\n   items[3].dataType = UCDataType::HexBinary;\n   items[3].dataSize = 8u;\n   items[3].data = phys_addrof(config->default_os_id);\n\n   items[4].name = \"system.default_title_id\";\n   items[4].dataType = UCDataType::HexBinary;\n   items[4].dataSize = 8u;\n   items[4].data = phys_addrof(config->default_title_id);\n\n   items[5].name = \"system.log.enable\";\n   items[5].dataType = UCDataType::UnsignedInt;\n   items[5].dataSize = 4u;\n   items[5].data = phys_addrof(config->log.enable);\n\n   items[6].name = \"system.log.max_size\";\n   items[6].dataType = UCDataType::UnsignedInt;\n   items[6].dataSize = 4u;\n   items[6].data = phys_addrof(config->log.max_size);\n\n   items[7].name = \"system.standby.enable\";\n   items[7].dataType = UCDataType::UnsignedInt;\n   items[7].dataSize = 4u;\n   items[7].data = phys_addrof(config->standby.enable);\n\n   items[8].name = \"system.ramdisk.cache_user_code\";\n   items[8].dataType = UCDataType::UnsignedInt;\n   items[8].dataSize = 4u;\n   items[8].data = phys_addrof(config->ramdisk.cache_user_code);\n\n   items[9].name = \"system.ramdisk.max_file_size\";\n   items[9].dataType = UCDataType::UnsignedInt;\n   items[9].dataSize = 4u;\n   items[9].data = phys_addrof(config->ramdisk.max_file_size);\n\n   items[10].name = \"system.ramdisk.cache_delay_ms\";\n   items[10].dataType = UCDataType::UnsignedInt;\n   items[10].dataSize = 4u;\n   items[10].data = phys_addrof(config->ramdisk.cache_delay_ms);\n\n   items[11].name = \"system.simulated_ppc_mem2_size\";\n   items[11].dataType = UCDataType::UnsignedInt;\n   items[11].dataSize = 4u;\n   items[11].data = phys_addrof(config->simulated_ppc_mem2_size);\n\n   items[12].name = \"system.dev_mode\";\n   items[12].dataType = UCDataType::UnsignedInt;\n   items[12].dataSize = 4u;\n   items[12].data = phys_addrof(config->dev_mode);\n\n   items[13].name = \"system.prev_title_id\";\n   items[13].dataType = UCDataType::HexBinary;\n   items[13].dataSize = 8u;\n   items[13].data = phys_addrof(config->prev_title_id);\n\n   items[14].name = \"system.prev_os_id\";\n   items[14].dataType = UCDataType::HexBinary;\n   items[14].dataSize = 8u;\n   items[14].data = phys_addrof(config->prev_os_id);\n\n   items[15].name = \"system.default_app_type\";\n   items[15].dataType = UCDataType::HexBinary;\n   items[15].dataSize = 4u;\n   items[15].error = static_cast<UCError>(0x90000001); // whyy???\n   items[15].data = phys_addrof(config->default_app_type);\n\n   items[16].name = \"system.default_device_type\";\n   items[16].dataType = UCDataType::String;\n   items[16].dataSize = 16u;\n   items[16].data = phys_addrof(config->default_device_type);\n\n   items[17].name = \"system.default_device_index\";\n   items[17].dataType = UCDataType::UnsignedInt;\n   items[17].dataSize = 4u;\n   items[17].data = phys_addrof(config->default_device_index);\n\n   items[18].name = \"system.fast_relaunch_value\";\n   items[18].dataType = UCDataType::UnsignedInt;\n   items[18].dataSize = 4u;\n   items[18].data = phys_addrof(config->fast_relaunch_value);\n\n   items[19].name = \"system.default_eco_title_id\";\n   items[19].dataType = UCDataType::HexBinary;\n   items[19].dataSize = 8u;\n   items[19].data = phys_addrof(config->default_eco_title_id);\n\n   auto error = readConfigItems(items, items.size());\n   if (error < MCPError::OK || config->version < 21) {\n      // Factory reset items\n      std::memset(config.get(), 0, sizeof(SystemConfig));\n      config->version = 21u;\n      config->dev_mode = 0u;\n      config->default_eco_title_id = 0x0005001010066000ull;\n      config->standby.enable = 0u;\n      config->ramdisk.max_file_size = 0xA00000u;\n      config->simulated_ppc_mem2_size = 0u;\n      config->default_app_type = 0x90000001u;\n\n      // Try save them to file\n      deleteConfigItems(items, 1);\n      writeConfigItems(items, items.size());\n   }\n\n   return MCPError::OK;\n}\n\nMCPError\nloadSysProdConfig()\n{\n   StackArray<UCItem, 11> items;\n   auto config = phys_addrof(sData->sysProdConfig);\n\n   items[0].name = \"slc:sys_prod\";\n   items[0].access = 0x510u;\n   items[0].dataType = UCDataType::Complex;\n\n   items[1].name = \"slc:sys_prod.version\";\n   items[1].access = 0x510u;\n   items[1].dataType = UCDataType::UnsignedInt;\n   items[1].dataSize = 4u;\n   items[1].data = phys_addrof(config->version);\n\n   items[2].name = \"slc:sys_prod.eeprom_version\";\n   items[2].access = 0x510u;\n   items[2].dataType = UCDataType::UnsignedShort;\n   items[2].dataSize = 2u;\n   items[2].data = phys_addrof(config->eeprom_version);\n\n   items[3].name = \"slc:sys_prod.product_area\";\n   items[3].access = 0x510u;\n   items[3].dataType = UCDataType::UnsignedInt;\n   items[3].dataSize = 4u;\n   items[3].data = phys_addrof(config->product_area);\n\n   items[4].name = \"slc:sys_prod.game_region\";\n   items[4].access = 0x510u;\n   items[4].dataType = UCDataType::UnsignedInt;\n   items[4].dataSize = 4u;\n   items[4].data = phys_addrof(config->game_region);\n\n   items[5].name = \"slc:sys_prod.ntsc_pal\";\n   items[5].access = 0x510u;\n   items[5].dataType = UCDataType::String;\n   items[5].dataSize = 5u;\n   items[5].data = phys_addrof(config->ntsc_pal);\n\n   items[6].name = \"slc:sys_prod.5ghz_country_code\";\n   items[6].access = 0x510u;\n   items[6].dataType = UCDataType::String;\n   items[6].dataSize = 4u;\n   items[6].data = phys_addrof(config->wifi_5ghz_country_code);\n\n   items[7].name = \"slc:sys_prod.5ghz_country_code_revision\";\n   items[7].access = 0x510u;\n   items[7].dataType = UCDataType::UnsignedByte;\n   items[7].dataSize = 1u;\n   items[7].data = phys_addrof(config->wifi_5ghz_country_code_revision);\n\n   items[8].name = \"slc:sys_prod.code_id\";\n   items[8].access = 0x510u;\n   items[8].dataType = UCDataType::String;\n   items[8].dataSize = 8u;\n   items[8].data = phys_addrof(config->code_id);\n\n   items[9].name = \"slc:sys_prod.serial_id\";\n   items[9].access = 0x510u;\n   items[9].dataType = UCDataType::String;\n   items[9].dataSize = 12u;\n   items[9].data = phys_addrof(config->serial_id);\n\n   items[10].name = \"slc:sys_prod.model_number\";\n   items[10].access = 0x510u;\n   items[10].dataType = UCDataType::String;\n   items[10].dataSize = 16u;\n   items[10].data = phys_addrof(config->model_number);\n\n   auto error = readConfigItems(items, items.size());\n   if (error < MCPError::OK || config->version <= 4) {\n      // Factory reset items\n      std::memset(config.get(), 0, sizeof(SysProdConfig));\n      config->version = 5u;\n      config->eeprom_version = uint16_t { 1 };\n      config->code_id = \"FEM\";\n      config->serial_id = std::to_string(100000000 | 0xDECAF);\n      config->model_number = \"WUP-101(03)\";\n\n      // This probably varies per region but I do not know the values\n      config->wifi_5ghz_country_code_revision = uint8_t { 24 };\n      config->ntsc_pal = \"NTSC\";\n\n      switch (decaf::config()->system.region) {\n      case decaf::SystemRegion::Japan:\n         config->game_region = MCPRegion::Japan;\n         config->product_area = MCPRegion::Japan;\n         config->wifi_5ghz_country_code = \"JP3\";\n         break;\n      case decaf::SystemRegion::USA:\n         config->game_region = MCPRegion::USA;\n         config->product_area = MCPRegion::USA;\n         config->wifi_5ghz_country_code = \"US\";\n         break;\n      case decaf::SystemRegion::China:\n         config->game_region = MCPRegion::China;\n         config->product_area = MCPRegion::China;\n         config->wifi_5ghz_country_code = \"CN\";\n         break;\n      case decaf::SystemRegion::Korea:\n         config->game_region = MCPRegion::Korea;\n         config->product_area = MCPRegion::Korea;\n         config->wifi_5ghz_country_code = \"KR\";\n         break;\n      case decaf::SystemRegion::Taiwan:\n         config->game_region = MCPRegion::Taiwan;\n         config->product_area = MCPRegion::Taiwan;\n         config->wifi_5ghz_country_code = \"TW\";\n         break;\n      case decaf::SystemRegion::Europe:\n      default:\n         config->game_region = MCPRegion::Europe;\n         config->product_area = MCPRegion::Europe;\n         config->wifi_5ghz_country_code = \"EU\";\n         config->ntsc_pal = \"PAL\";\n         break;\n      }\n\n      // Try save them to file\n      deleteConfigItems(items, 1);\n      writeConfigItems(items, items.size());\n   }\n\n   return MCPError::OK;\n}\n\nphys_ptr<RtcConfig>\ngetRtcConfig()\n{\n   return phys_addrof(sData->rtcConfig);\n}\n\nphys_ptr<SystemConfig>\ngetSystemConfig()\n{\n   return phys_addrof(sData->systemConfig);\n}\n\nphys_ptr<SysProdConfig>\ngetSysProdConfig()\n{\n   return phys_addrof(sData->sysProdConfig);\n}\n\nvoid\ninitialiseStaticConfigData()\n{\n   sData = allocProcessStatic<StaticConfigData>();\n}\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_config.h",
    "content": "#pragma once\n#include \"ios_mcp_enum.h\"\n#include \"ios/auxil/ios_auxil_config.h\"\n\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::mcp::internal\n{\n\n#pragma pack(push, 1)\n\nstruct RtcConfig\n{\n   be2_val<uint32_t> version;\n   be2_val<uint32_t> rtc_offset;\n};\nCHECK_OFFSET(RtcConfig, 0x00, version);\nCHECK_OFFSET(RtcConfig, 0x04, rtc_offset);\nCHECK_SIZE(RtcConfig, 0x08);\n\nstruct SystemConfig\n{\n   be2_val<uint32_t> version;\n   be2_val<uint32_t> cmdFlags;\n   be2_val<uint64_t> default_os_id;\n   be2_val<uint64_t> default_title_id;\n\n   struct\n   {\n      be2_val<uint32_t> enable;\n      be2_val<uint32_t> max_size;\n   } log;\n\n   struct\n   {\n      be2_val<uint32_t> enable;\n   } standby;\n\n   struct\n   {\n      be2_val<uint32_t> cache_user_code;\n      be2_val<uint32_t> max_file_size;\n      be2_val<uint32_t> cache_delay_ms;\n   } ramdisk;\n\n   be2_val<uint32_t> simulated_ppc_mem2_size;\n   be2_val<uint32_t> dev_mode;\n   be2_val<uint64_t> prev_title_id;\n   be2_val<uint64_t> prev_os_id;\n   be2_val<uint32_t> default_app_type;\n   be2_array<char, 16> default_device_type;\n   be2_val<uint32_t> default_device_index;\n   be2_val<uint32_t> fast_relaunch_value;\n   be2_val<uint64_t> default_eco_title_id;\n};\nCHECK_OFFSET(SystemConfig, 0x00, version);\nCHECK_OFFSET(SystemConfig, 0x04, cmdFlags);\nCHECK_OFFSET(SystemConfig, 0x08, default_os_id);\nCHECK_OFFSET(SystemConfig, 0x10, default_title_id);\nCHECK_OFFSET(SystemConfig, 0x18, log.enable);\nCHECK_OFFSET(SystemConfig, 0x1C, log.max_size);\nCHECK_OFFSET(SystemConfig, 0x20, standby.enable);\nCHECK_OFFSET(SystemConfig, 0x24, ramdisk.cache_user_code);\nCHECK_OFFSET(SystemConfig, 0x28, ramdisk.max_file_size);\nCHECK_OFFSET(SystemConfig, 0x2C, ramdisk.cache_delay_ms);\nCHECK_OFFSET(SystemConfig, 0x30, simulated_ppc_mem2_size);\nCHECK_OFFSET(SystemConfig, 0x34, dev_mode);\nCHECK_OFFSET(SystemConfig, 0x38, prev_title_id);\nCHECK_OFFSET(SystemConfig, 0x40, prev_os_id);\nCHECK_OFFSET(SystemConfig, 0x48, default_app_type);\nCHECK_OFFSET(SystemConfig, 0x4C, default_device_type);\nCHECK_OFFSET(SystemConfig, 0x5C, default_device_index);\nCHECK_OFFSET(SystemConfig, 0x60, fast_relaunch_value);\nCHECK_OFFSET(SystemConfig, 0x64, default_eco_title_id);\nCHECK_SIZE(SystemConfig, 0x6C);\n\nstruct SysProdConfig\n{\n   be2_val<MCPRegion> product_area;\n   be2_val<uint16_t> eeprom_version;\n   PADDING(2);\n   be2_val<MCPRegion> game_region;\n   UNKNOWN(4);\n   be2_array<char, 5> ntsc_pal;\n\n   //! Actually 5ghz_country_code, but can't start a variable with a number!!\n   be2_array<char, 4> wifi_5ghz_country_code;\n\n   //! Actually 5ghz_country_code_revision, but can't start a variable with a number!!\n   be2_val<uint8_t> wifi_5ghz_country_code_revision;\n\n   be2_array<char, 8> code_id;\n   be2_array<char, 12> serial_id;\n   UNKNOWN(4);\n   be2_array<char, 16> model_number;\n   be2_val<uint32_t> version;\n};\nCHECK_OFFSET(SysProdConfig, 0x00, product_area);\nCHECK_OFFSET(SysProdConfig, 0x04, eeprom_version);\nCHECK_OFFSET(SysProdConfig, 0x08, game_region);\nCHECK_OFFSET(SysProdConfig, 0x10, ntsc_pal);\nCHECK_OFFSET(SysProdConfig, 0x15, wifi_5ghz_country_code);\nCHECK_OFFSET(SysProdConfig, 0x19, wifi_5ghz_country_code_revision);\nCHECK_OFFSET(SysProdConfig, 0x1A, code_id);\nCHECK_OFFSET(SysProdConfig, 0x22, serial_id);\nCHECK_OFFSET(SysProdConfig, 0x32, model_number);\nCHECK_OFFSET(SysProdConfig, 0x42, version);\nCHECK_SIZE(SysProdConfig, 0x46);\n\n#pragma pack(pop)\n\nMCPError\ntranslateUCError(auxil::UCError error);\n\nMCPError\nreadConfigItems(phys_ptr<auxil::UCItem> items,\n                uint32_t count);\n\nMCPError\nwriteConfigItems(phys_ptr<auxil::UCItem> items,\n                 uint32_t count);\n\nMCPError\ndeleteConfigItems(phys_ptr<auxil::UCItem> items,\n                  uint32_t count);\n\nMCPError\nloadRtcConfig();\n\nMCPError\nloadSystemConfig();\n\nMCPError\nloadSysProdConfig();\n\nphys_ptr<RtcConfig>\ngetRtcConfig();\n\nphys_ptr<SystemConfig>\ngetSystemConfig();\n\nphys_ptr<SysProdConfig>\ngetSysProdConfig();\n\nvoid\ninitialiseStaticConfigData();\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_enum.h",
    "content": "#ifndef IOS_MCP_ENUM_H\n#define IOS_MCP_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\n\nENUM_NAMESPACE_ENTER(mcp)\n\nENUM_BEG(MainThreadCommand, uint32_t)\n   ENUM_VALUE(SysEvent,                   0x101)\nENUM_END(MainThreadCommand)\n\nENUM_BEG(MCPAppType, uint32_t)\n   ENUM_VALUE(Unk0x0800000E,        0x0800000E)\nENUM_END(MCPAppType)\n\nENUM_BEG(MCPCommand, uint32_t)\n   ENUM_VALUE(GetEnvironmentVariable,     0x20)\n   ENUM_VALUE(GetSysProdSettings,         0x40)\n   ENUM_VALUE(SetSysProdSettings,         0x41)\n   ENUM_VALUE(GetOwnTitleInfo,            0x4C)\n   ENUM_VALUE(TitleCount,                 0x4D)\n   ENUM_VALUE(DeviceList,                 0x4E)\n   ENUM_VALUE(CloseTitle,                 0x50)\n   ENUM_VALUE(SwitchTitle,                0x51)\n   ENUM_VALUE(PrepareTitle0x52,           0x52)\n   ENUM_VALUE(LoadFile,                   0x53)\n   ENUM_VALUE(GetFileLength,              0x57)\n   ENUM_VALUE(SearchTitleList,            0x58)\n   ENUM_VALUE(PrepareTitle0x59,           0x59)\n   ENUM_VALUE(GetLaunchParameters,        0x5A)\n   ENUM_VALUE(GetTitleId,                 0x5B)\n   ENUM_VALUE(UpdateGetProgress,          0x92)\n   ENUM_VALUE(UpdateCheckResume,          0x96)\n   ENUM_VALUE(UpdateCheckContext,         0x99)\nENUM_END(MCPCommand)\n\nENUM_BEG(MCPCountryCode, uint32_t)\n   ENUM_VALUE(USA,                  0x31)\n   ENUM_VALUE(UnitedKingdom,        0x63)\nENUM_END(MCPCountryCode)\n\nFLAGS_BEG(MCPDeviceFlags, uint32_t)\n   FLAGS_VALUE(Unk1,                 1 << 0)\n   FLAGS_VALUE(Unk2,                 1 << 1)\n   FLAGS_VALUE(Unk4,                 1 << 2)\n   FLAGS_VALUE(Unk8,                 1 << 3)\nFLAGS_END(MCPDeviceFlags)\n\nENUM_BEG(MCPError, int32_t)\n   ENUM_VALUE(OK,                   0)\n   ENUM_VALUE(Invalid,              -0x40001)\n   ENUM_VALUE(System,               -0x40002)\n   ENUM_VALUE(Alloc,                -0x40003)\n   ENUM_VALUE(Opcode,               -0x40004)\n   ENUM_VALUE(InvalidParam,         -0x40005)\n   ENUM_VALUE(InvalidType,          -0x40006)\n   ENUM_VALUE(Unsupported,          -0x40007)\n   ENUM_VALUE(NonLeafNode,          -0x4000A)\n   ENUM_VALUE(KeyNotFound,          -0x4000B)\n   ENUM_VALUE(Modify,               -0x4000C)\n   ENUM_VALUE(StringTooLong,        -0x4000D)\n   ENUM_VALUE(RootKeysDiffer,       -0x4000E)\n   ENUM_VALUE(InvalidLocation,      -0x4000F)\n   ENUM_VALUE(BadComment,           -0x40010)\n   ENUM_VALUE(ReadAccess,           -0x40011)\n   ENUM_VALUE(WriteAccess,          -0x40012)\n   ENUM_VALUE(CreateAccess,         -0x40013)\n   ENUM_VALUE(FileSysName,          -0x40014)\n   ENUM_VALUE(FileSysInit,          -0x40015)\n   ENUM_VALUE(FileSysMount,         -0x40016)\n   ENUM_VALUE(FileOpen,             -0x40017)\n   ENUM_VALUE(FileStat,             -0x40018)\n   ENUM_VALUE(FileRead,             -0x40019)\n   ENUM_VALUE(FileWrite,            -0x4001A)\n   ENUM_VALUE(FileTooBig,           -0x4001B)\n   ENUM_VALUE(FileRemove,           -0x4001C)\n   ENUM_VALUE(FileRename,           -0x4001D)\n   ENUM_VALUE(FileClose,            -0x4001E)\n   ENUM_VALUE(FileSeek,             -0x4001F)\n   ENUM_VALUE(MalformedXML,         -0x40022)\n   ENUM_VALUE(Version,              -0x40023)\n   ENUM_VALUE(NoIpcBuffers,         -0x40024)\n   ENUM_VALUE(FileLockNeeded,       -0x40026)\n   ENUM_VALUE(SysProt,              -0x40032)\n   ENUM_VALUE(AlreadyOpen,          -0x40054)\n   ENUM_VALUE(StorageFull,          -0x40055)\n   ENUM_VALUE(WriteProtected,       -0x40056)\n   ENUM_VALUE(DataCorrupted,        -0x40057)\n   ENUM_VALUE(KernelErrorBase,      -0x403E8)\nENUM_END(MCPError)\n\nENUM_BEG(MCPFileType, uint32_t)\n   //! Load from the process's code directory (process title id)/code/%s\n   ENUM_VALUE(ProcessCode,          0x00)\n\n   //! Load from the CafeOS directory (00050010-1000400A)/code/%s\n   ENUM_VALUE(CafeOS,               0x01)\n\n   //! Load from the shared data content directory (0005001B-10042400)/content/%s\n   ENUM_VALUE(SharedDataContent,    0x02)\n\n   //! Load from the shared data code directory (0005001B-10042400)/code/%s\n   ENUM_VALUE(SharedDataCode,       0x03)\nENUM_END(MCPFileType)\n\nENUM_BEG(MCPResourcePermissions, uint32_t)\n   ENUM_VALUE(AddOnContent,         0x80)\n   ENUM_VALUE(SciErrorLog,          0x200)\nENUM_END(MCPResourcePermissions)\n\nENUM_BEG(MCPRegion, uint32_t)\n   ENUM_VALUE(Japan,                0x01)\n   ENUM_VALUE(USA,                  0x02)\n   ENUM_VALUE(Europe,               0x04)\n   ENUM_VALUE(Unknown8,             0x08)\n   ENUM_VALUE(China,                0x10)\n   ENUM_VALUE(Korea,                0x20)\n   ENUM_VALUE(Taiwan,               0x40)\nENUM_END(MCPRegion)\n\nFLAGS_BEG(MCPTitleListSearchFlags, uint32_t)\n   FLAGS_VALUE(None,                0)\n   FLAGS_VALUE(TitleId,             1 << 0)\n   FLAGS_VALUE(AppType,             1 << 2)\n   FLAGS_VALUE(IndexedDevice,       1 << 6)\n   FLAGS_VALUE(Unk0x60,             1 << 8)\n   FLAGS_VALUE(Path,                1 << 9)\n   FLAGS_VALUE(UniqueId,            1 << 10)\nFLAGS_END(MCPTitleListSearchFlags)\n\nENUM_BEG(PMCommand, uint32_t)\n   ENUM_VALUE(GetResourceManagerId,       0xE0)\n   ENUM_VALUE(RegisterResourceManager,    0xE1)\nENUM_END(PMCommand)\n\nENUM_BEG(PPCAppCommand, uint32_t)\n   ENUM_VALUE(StartupEvent,               0xB0)\n   ENUM_VALUE(PowerOff,                   0xB2)\n   ENUM_VALUE(UnrecoverableError,         0xB3)\nENUM_END(PPCAppCommand)\n\nENUM_BEG(ResourceManagerCommand, int32_t)\n   ENUM_VALUE(Timeout,                    -32)  // ios::Error::Timeout\n   ENUM_VALUE(ResumeReply,                8)    // ios::Command::Reply\n   ENUM_VALUE(Register,                   16)\nENUM_END(ResourceManagerCommand)\n\nENUM_BEG(ResourceManagerRegistrationState, uint32_t)\n   ENUM_VALUE(Invalid,                    0)\n   ENUM_VALUE(Registered,                 1)\n   ENUM_VALUE(NotRegistered,              2)\n   ENUM_VALUE(Resumed,                    3)\n   ENUM_VALUE(Suspended,                  4)\n   ENUM_VALUE(Pending,                    5)\n   ENUM_VALUE(Failed,                     6)\nENUM_END(ResourceManagerRegistrationState)\n\nENUM_BEG(SystemFileSys, uint32_t)\n   ENUM_VALUE(Nand,                       1)\n   ENUM_VALUE(Pcfs,                       2)\n   ENUM_VALUE(SdCard,                     3)\nENUM_END(SystemFileSys)\n\nENUM_NAMESPACE_EXIT(mcp)\n\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_MCP_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_ipc.cpp",
    "content": "#include \"ios_mcp_ipc.h\"\n#include \"ios_mcp_enum.h\"\n\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n\nnamespace ios::mcp\n{\n\nconstexpr auto DeviceNameLength = 32u;\n\nusing namespace kernel;\n\nstatic phys_ptr<void>\nallocIpcData(uint32_t size)\n{\n   auto buffer = IOS_HeapAlloc(CrossProcessHeapId, size);\n\n   if (buffer) {\n      std::memset(buffer.get(), 0, size);\n   }\n\n   return buffer;\n}\n\nstatic void\nfreeIpcData(phys_ptr<void> data)\n{\n   IOS_HeapFree(CrossProcessHeapId, data);\n}\n\nError\nMCP_Open()\n{\n   return IOS_Open(\"/dev/mcp\", OpenMode::None);\n}\n\nError\nMCP_Close(MCPHandle handle)\n{\n   return IOS_Close(handle);\n}\n\nError\nMCP_RegisterResourceManager(std::string_view device,\n                            kernel::MessageQueueId queue)\n{\n   StackObject<uint32_t> resourceManagerId;\n   auto error = IOS_Open(\"/dev/pm\", OpenMode::None);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Alloc a cross process buffer to copy the name to\n   auto handle = static_cast<ResourceHandleId>(error);\n   auto nameBuffer = phys_cast<char *>(allocIpcData(DeviceNameLength + 1));\n   if (!nameBuffer) {\n      error = Error::FailAlloc;\n      goto out;\n   }\n\n   // Ensure device name does not exceed DeviceNameLength\n   if (device.size() >= 32) {\n      device.remove_suffix(device.size() - DeviceNameLength);\n   }\n\n   // Copy device to ipc buffer\n   std::copy(device.begin(), device.end(), nameBuffer.get());\n   nameBuffer[device.size()] = char { 0 };\n\n   // Send ioctl to get the manager id\n   error = IOS_Ioctl(handle,\n                     PMCommand::GetResourceManagerId,\n                     nameBuffer, DeviceNameLength + 1,\n                     nullptr, 0);\n\n   if (error < Error::OK) {\n      goto out;\n   }\n\n   *resourceManagerId = static_cast<uint32_t>(error);\n\n   // Register resource manager with kernel\n   error = IOS_RegisterResourceManager(device, queue);\n\n   if (error < Error::OK) {\n      goto out;\n   }\n\n   // Register resource manager with MCP\n   error = IOS_Ioctl(handle,\n                     PMCommand::RegisterResourceManager,\n                     resourceManagerId, sizeof(uint32_t),\n                     nullptr, 0);\n\nout:\n   if (nameBuffer) {\n      freeIpcData(nameBuffer);\n   }\n\n   IOS_Close(handle);\n   return error;\n}\n\n} // namespace ios::mcp\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_ipc.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n\nnamespace ios::mcp\n{\n\nusing MCPHandle = kernel::ResourceHandleId;\n\nError\nMCP_Open();\n\nError\nMCP_Close(MCPHandle handle);\n\nError\nMCP_RegisterResourceManager(std::string_view device,\n                            kernel::MessageQueueId messageQueueId);\n\n} // namespace ios::mcp\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_mcp.h",
    "content": "#pragma once\n#include \"ios_mcp_enum.h\"\n#include \"ios_mcp_mcp_request.h\"\n#include \"ios_mcp_mcp_response.h\"\n#include \"ios_mcp_mcp_types.h\"\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_mcp_device.cpp",
    "content": "#include \"ios_mcp_config.h\"\n#include \"ios_mcp_enum.h\"\n#include \"ios_mcp_mcp_device.h\"\n#include \"ios_mcp_mcp_types.h\"\n#include \"ios_mcp_mcp_request.h\"\n#include \"ios_mcp_mcp_response.h\"\n#include \"ios_mcp_mcp_thread.h\"\n#include \"ios_mcp_title.h\"\n\n#include \"cafe/libraries/cafe_hle.h\"\n#include \"decaf_config.h\"\n#include \"ios/ios.h\"\n#include \"ios/ios_stackobject.h\"\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"vfs/vfs_host_device.h\"\n#include \"vfs/vfs_virtual_device.h\"\n\n#include <common/log.h>\n\nnamespace ios::mcp::internal\n{\n\nusing namespace ios::fs;\nusing namespace ios::kernel;\n\nstatic std::string sCurrentLoadFilePath = { };\nstatic FSAHandle sCurrentLoadFileHandle = { };\nstatic size_t sCurrentLoadFileSize = 0u;\n\nMCPError\nmcpDeviceList(phys_ptr<const MCPRequestDeviceList> request,\n              phys_ptr<MCPDevice> deviceList,\n              uint32_t deviceListSizeBytes)\n{\n   auto deviceListCount = deviceListSizeBytes / sizeof(MCPDevice);\n   auto devicesAdded = 0;\n   auto config = decaf::config();\n   auto deviceId = 0u;\n\n   /*\n   Example device list from my console:\n   path                    type      fs  flags  index uid\n   /vol/storage_usb01,     usb,      wfs,  0xF,     1   7\n   /vol/storage_bt00,      bt,       \"\",   0x3,     0   6\n   /vol/storage_drh00,     drh,      \"\",   0x3,     0   5\n   /vol/storage_slccmpt01, slccmpt,  isfs, 0x7,     1   4\n   /vol/storage_slc01,     slc,      isfs, 0xF,     1   3\n   /vol/storage_sdcard01,  sdcard,   fat,  0x7,     1   2\n   /vol/storage_ramdisk01, ramdisk,  wfs,  0x7,     1   1\n   /vol/storage_mlc01,     mlc,      wfs,  0xF,     1   0\n\n   Only mlc/ramdisk/usb had unk0x08 set to some sort of 7 character unique\n   identifier looking string.\n   */\n\n   if (devicesAdded < deviceListCount &&\n       (request->flags & MCPDeviceFlags::Unk1) &&\n       !config->system.mlc_path.empty()) {\n      auto &device = deviceList[devicesAdded++];\n      std::memset(std::addressof(device), 0, sizeof(MCPDevice));\n\n      device.type = \"mlc\";\n      device.filesystem = \"wfs\";\n      device.flags = MCPDeviceFlags::Unk1 | MCPDeviceFlags::Unk2 | MCPDeviceFlags::Unk4 | MCPDeviceFlags::Unk8;\n      device.index = 1u;\n      device.path = \"/vol/storage_mlc01\";\n      device.uid = deviceId++;\n   }\n\n   if (devicesAdded < deviceListCount &&\n       (request->flags & MCPDeviceFlags::Unk1) &&\n       !config->system.slc_path.empty()) {\n      auto &device = deviceList[devicesAdded++];\n      std::memset(std::addressof(device), 0, sizeof(MCPDevice));\n\n      device.type = \"slc\";\n      device.filesystem = \"isfs\";\n      device.flags = MCPDeviceFlags::Unk1 | MCPDeviceFlags::Unk2 | MCPDeviceFlags::Unk4 | MCPDeviceFlags::Unk8;\n      device.index = 1u;\n      device.path = \"/vol/storage_slc01\";\n      device.uid = deviceId++;\n   }\n\n   return static_cast<MCPError>(devicesAdded);\n}\n\nMCPError\nmcpGetFileLength(phys_ptr<const MCPRequestGetFileLength> request)\n{\n   auto path = std::string { };\n   auto name = std::string_view { phys_addrof(request->name).get() };\n\n   if (request->fileType == MCPFileType::CafeOS) {\n      if (std::find(decaf::config()->system.lle_modules.begin(),\n                    decaf::config()->system.lle_modules.end(),\n                    name) == decaf::config()->system.lle_modules.end()) {\n         auto library = cafe::hle::getLibrary(name);\n         if (library) {\n            auto &rpl = library->getGeneratedRpl();\n            return static_cast<MCPError>(rpl.size());\n         }\n      }\n   }\n\n   switch (request->fileType) {\n   case MCPFileType::ProcessCode:\n      path = fmt::format(\"/vol/code/{}\", name);\n      break;\n   case MCPFileType::CafeOS:\n      path = fmt::format(\"/vol/storage_mlc01/sys/title/00050010/1000400A/code/{}\", name);\n      break;\n   case MCPFileType::SharedDataCode:\n      path = fmt::format(\"/vol/storage_mlc01/sys/title/0005001B/10042400/code/{}\", name);\n      break;\n   case MCPFileType::SharedDataContent:\n      path = fmt::format(\"/vol/storage_mlc01/sys/title/0005001B/10042400/content/{}\", name);\n      break;\n   default:\n      return static_cast<MCPError>(Error::InvalidArg);\n   }\n\n   StackObject<FSAStat> stat;\n   auto error = FSAGetInfoByQuery(getFsaHandle(),\n                                  path,\n                                  FSAQueryInfoType::Stat,\n                                  stat);\n   if (error < FSAStatus::OK) {\n      return static_cast<MCPError>(error);\n   }\n\n   return static_cast<MCPError>(stat->size);\n}\n\nMCPError\nmcpGetSysProdSettings(phys_ptr<MCPResponseGetSysProdSettings> response)\n{\n   std::memcpy(phys_addrof(response->settings).get(),\n               getSysProdConfig().get(),\n               sizeof(MCPSysProdSettings));\n   return MCPError::OK;\n}\n\nMCPError\nmcpGetTitleId(phys_ptr<kernel::ResourceRequest> resourceRequest,\n              phys_ptr<MCPResponseGetTitleId> response)\n{\n   response->titleId = resourceRequest->requestData.titleId;\n   return MCPError::OK;\n}\n\nMCPError\nmcpLoadFile(phys_ptr<const MCPRequestLoadFile> request,\n            phys_ptr<void> outputBuffer,\n            uint32_t outputBufferLength)\n{\n   auto path = std::string { };\n   auto name = std::string_view { phys_addrof(request->name).get() };\n\n   if (request->fileType == MCPFileType::CafeOS) {\n      if (std::find(decaf::config()->system.lle_modules.begin(),\n                    decaf::config()->system.lle_modules.end(),\n                    name) == decaf::config()->system.lle_modules.end()) {\n         auto library = cafe::hle::getLibrary(name);\n         if (library) {\n            auto &rpl = library->getGeneratedRpl();\n            auto bytesRead = std::min<uint32_t>(static_cast<uint32_t>(rpl.size() - request->pos),\n                                                outputBufferLength);\n            std::memcpy(outputBuffer.get(),\n                        rpl.data() + request->pos,\n                        bytesRead);\n            return static_cast<MCPError>(bytesRead);\n         }\n      }\n   }\n\n   switch (request->fileType) {\n   case MCPFileType::ProcessCode:\n      path = fmt::format(\"/vol/code/{}\", name);\n      break;\n   case MCPFileType::CafeOS:\n      path = fmt::format(\"/vol/system_slc/title/00050010/1000400A/code/{}\", name);\n      break;\n   case MCPFileType::SharedDataCode:\n      path = fmt::format(\"/vol/storage_mlc01/sys/title/0005001B/10042400/code/{}\", name);\n      break;\n   case MCPFileType::SharedDataContent:\n      path = fmt::format(\"/vol/storage_mlc01/sys/title/0005001B/10042400/content/{}\", name);\n      break;\n   default:\n      return static_cast<MCPError>(Error::InvalidArg);\n   }\n\n   auto fsaHandle = getFsaHandle();\n   if (path != sCurrentLoadFilePath) {\n      auto fileHandle = FSAHandle { };\n\n      // Open a new file\n      auto error = FSAOpenFile(fsaHandle, path, \"r\", &fileHandle);\n      if (error < 0) {\n         return static_cast<MCPError>(error);\n      }\n\n      StackObject<FSAStat> stat;\n      error = FSAStatFile(fsaHandle, fileHandle, stat);\n      if (error < 0) {\n         FSACloseFile(fsaHandle, fileHandle);\n         return static_cast<MCPError>(error);\n      }\n\n      sCurrentLoadFileSize = stat->size;\n      sCurrentLoadFileHandle = fileHandle;\n      sCurrentLoadFilePath = path;\n   }\n\n   auto error = FSAReadFileWithPos(fsaHandle,\n                                   outputBuffer,\n                                   1,\n                                   outputBufferLength,\n                                   request->pos,\n                                   sCurrentLoadFileHandle,\n                                   FSAReadFlag::None);\n   if (error < 0 || request->pos + error >= sCurrentLoadFileSize) {\n      FSACloseFile(fsaHandle, sCurrentLoadFileHandle);\n      sCurrentLoadFileSize = 0u;\n      sCurrentLoadFileHandle = static_cast<FSAHandle>(Error::Invalid);\n      sCurrentLoadFilePath.clear();\n   }\n\n   return static_cast<MCPError>(error);\n}\n\nstatic bool\ncheckExistenceUsingOpenDir(std::string_view path)\n{\n   auto fsaHandle = getFsaHandle();\n   auto dirHandle = FSADirHandle { -1 };\n   auto result = FSAOpenDir(fsaHandle, path, &dirHandle);\n   if (result == FSAStatus::OK) {\n      FSACloseDir(fsaHandle, dirHandle);\n      return true;\n   }\n\n   if (result == FSAStatus::NotDir) {\n      return true;\n   }\n\n   return false;\n}\n\nstatic bool\ncheckExistence(std::string_view path)\n{\n   StackObject<FSAStat> stat;\n   auto result = FSAGetStat(getFsaHandle(), path, stat);\n   if (result == FSAStatus::OK) {\n      return true;\n   }\n\n   if (result == FSAStatus::PermissionError) {\n      return checkExistenceUsingOpenDir(path);\n   }\n\n   return false;\n}\n\nMCPError\nmcpPrepareTitle52(phys_ptr<const MCPRequestPrepareTitle> request,\n                  phys_ptr<MCPResponsePrepareTitle> response)\n{\n   auto titleInfoBuffer = getPrepareTitleInfoBuffer();\n   auto titleId = request->titleId;\n   if (titleId == DefaultTitleId) {\n      StackObject<MCPTitleAppXml> appXml;\n      if (auto error = readTitleAppXml(appXml); error < MCPError::OK) {\n         titleInfoBuffer = getPrepareTitleInfoBuffer();\n         std::memset(titleInfoBuffer.get(), 0x0, sizeof(MCPPPrepareTitleInfo));\n         titleInfoBuffer->permissions[0].group = static_cast<uint32_t>(ResourcePermissionGroup::All);\n         titleInfoBuffer->permissions[0].mask = 0xFFFFFFFFFFFFFFFFull;\n         return error;\n      }\n\n      titleInfoBuffer->titleId = appXml->title_id;\n      titleInfoBuffer->groupId = appXml->group_id;\n\n      titleId = appXml->title_id;\n   }\n\n   // TODO: When we have title switching we will need to read the title id and\n   // mount the correct title to /vol - until then libdecaf already mounted it.\n   auto error = readTitleCosXml(titleInfoBuffer);\n   if (error < MCPError::OK) {\n      // If there is no cos.xml then let's grant full permissions\n      std::memset(titleInfoBuffer.get(), 0x0, sizeof(MCPPPrepareTitleInfo));\n      titleInfoBuffer->permissions[0].group = static_cast<uint32_t>(ResourcePermissionGroup::All);\n      titleInfoBuffer->permissions[0].mask = 0xFFFFFFFFFFFFFFFFull;\n   } else {\n      // If there is cos.xml but it doesn't have any permissions then grant full permissions\n      bool havePermissions = false;\n      for (auto i = 0u; i <= 18; ++i) {\n         if (titleInfoBuffer->permissions[i].group != 0 ||\n             titleInfoBuffer->permissions[i].mask != 0) {\n            havePermissions = true;\n            break;\n         }\n      }\n      if (!havePermissions) {\n         titleInfoBuffer->permissions[0].group = static_cast<uint32_t>(ResourcePermissionGroup::All);\n         titleInfoBuffer->permissions[0].mask = 0xFFFFFFFFFFFFFFFFull;\n      }\n   }\n\n   // Try mount updates for the title\n   auto titleIdLo = titleId & 0xFFFFFFFF;\n   auto titleUpdatePath = fmt::format(\"/vol/storage_mlc01/usr/title/0005000e/{:08x}\", titleIdLo);\n   if (checkExistence(titleUpdatePath)) {\n      gLog->info(\"Title update found at {}\", titleUpdatePath);\n\n      auto processInfo = FSAProcessInfo { };\n      processInfo.groupId = titleInfoBuffer->groupId;\n      processInfo.titleId = titleInfoBuffer->titleId;\n      processInfo.processId = ios::ProcessId::COSKERNEL; // TODO: Use correct process id.\n\n      auto fsaHandle = getFsaHandle();\n      auto mountResult =\n         FSAMountWithProcess(fsaHandle, titleUpdatePath + \"/code\",\n                             \"/vol/code\", FSAMountPriority::TitleUpdate,\n                             &processInfo, nullptr, 0);\n      if (mountResult) {\n         gLog->warn(\"Error mounting update path {}/code to /vol/code\",\n                    titleUpdatePath);\n      } else {\n         gLog->info(\"Mounted update {}/code to /vol/code\", titleUpdatePath);\n      }\n\n      mountResult =\n         FSAMountWithProcess(fsaHandle, titleUpdatePath + \"/content\",\n                             \"/vol/content\", FSAMountPriority::TitleUpdate,\n                             &processInfo, nullptr, 0);\n      if (mountResult) {\n         gLog->warn(\"Error mounting update path {}/content to /vol/content\",\n                    titleUpdatePath + \"/content\");\n      } else {\n         gLog->info(\"Mounted update {}/content to /vol/content\", titleUpdatePath);\n      }\n\n      mountResult =\n         FSAMountWithProcess(fsaHandle, titleUpdatePath + \"/meta\",\n                             \"/vol/meta\", FSAMountPriority::TitleUpdate,\n                             &processInfo, nullptr, 0);\n      if (mountResult) {\n         gLog->warn(\"Error mounting update path {}/meta to /vol/meta\",\n                    titleUpdatePath + \"/meta\");\n      } else {\n         gLog->info(\"Mounted update {}/meta to /vol/meta\", titleUpdatePath);\n      }\n   } else {\n      gLog->info(\"No title update found at {}\", titleUpdatePath);\n   }\n\n   // Return result\n   std::memcpy(phys_addrof(response->titleInfo).get(),\n               titleInfoBuffer.get(),\n               sizeof(MCPPPrepareTitleInfo));\n   std::memset(phys_addrof(response->titleInfo.permissions).get(),\n               0, sizeof(response->titleInfo.permissions));\n   return MCPError::OK;\n}\n\nMCPError\nmcpSwitchTitle(phys_ptr<const MCPRequestSwitchTitle> request)\n{\n   auto titleInfoBuffer = getPrepareTitleInfoBuffer();\n   auto processId = static_cast<ProcessId>(ProcessId::COSKERNEL + request->cafeProcessId);\n   auto sdCardPermissions = vfs::NoPermissions;\n\n   // Apply title permissions\n   for (auto &permission : titleInfoBuffer->permissions) {\n      if (!permission.group) {\n         break;\n      }\n\n      IOS_SetClientCapabilities(processId,\n                                permission.group,\n                                phys_addrof(permission.mask));\n\n      if (permission.group == ResourcePermissionGroup::FS ||\n          permission.group == ResourcePermissionGroup::All) {\n         if (permission.mask & FSResourcePermissions::SdCardRead) {\n            sdCardPermissions = sdCardPermissions | vfs::OtherRead;\n         }\n\n         if (permission.mask & FSResourcePermissions::SdCardWrite) {\n            sdCardPermissions = sdCardPermissions | vfs::OtherWrite;\n         }\n      }\n   }\n\n   IOS_SetProcessTitle(processId,\n                       titleInfoBuffer->titleId,\n                       titleInfoBuffer->groupId);\n\n   // Mount sdcard if title has permissions\n   if (sdCardPermissions != vfs::NoPermissions) {\n      auto filesystem = ios::getFileSystem();\n      filesystem->mountDevice({}, \"/dev/sdcard01\",\n                              std::make_shared<vfs::HostDevice>(decaf::config()->system.sdcard_path));\n      filesystem->setPermissions({}, \"/dev/sdcard01\", sdCardPermissions);\n   }\n\n   std::memset(titleInfoBuffer.get(),\n               0xFF,\n               sizeof(MCPPPrepareTitleInfo));\n   return MCPError::OK;\n}\n\nMCPError\nmcpUpdateCheckContext(phys_ptr<MCPResponseUpdateCheckContext> response)\n{\n   // TODO: Implement mcpUpdateCheckContext\n   std::memset(response.get(), 0, sizeof(MCPResponseUpdateCheckContext));\n   return MCPError::OK;\n}\n\nMCPError\nmcpUpdateCheckResume(phys_ptr<MCPResponseUpdateCheckResume> response)\n{\n   // TODO: Implement mcpUpdateCheckResume\n   std::memset(response.get(), 0, sizeof(MCPResponseUpdateCheckResume));\n   return MCPError::OK;\n}\n\nMCPError\nmcpUpdateGetProgress(phys_ptr<MCPResponseUpdateProgress> response)\n{\n   // TODO: Implement mcpUpdateGetProgress\n   std::memset(response.get(), 0, sizeof(MCPResponseUpdateProgress));\n   return MCPError::OK;\n}\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_mcp_device.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios_mcp_enum.h\"\n#include \"ios_mcp_mcp_response.h\"\n#include \"ios_mcp_mcp_request.h\"\n\nnamespace ios::mcp::internal\n{\n\nMCPError\nmcpDeviceList(phys_ptr<const MCPRequestDeviceList> request,\n              phys_ptr<MCPDevice> deviceList,\n              uint32_t deviceListSizeBytes);\n\nMCPError\nmcpGetFileLength(phys_ptr<const MCPRequestGetFileLength> request);\n\nMCPError\nmcpGetSysProdSettings(phys_ptr<MCPResponseGetSysProdSettings> response);\n\nMCPError\nmcpGetTitleId(phys_ptr<kernel::ResourceRequest> resourceRequest,\n              phys_ptr<MCPResponseGetTitleId> response);\n\nMCPError\nmcpLoadFile(phys_ptr<const MCPRequestLoadFile> request,\n            phys_ptr<void> outputBuffer,\n            uint32_t outputBufferLength);\n\nMCPError\nmcpPrepareTitle52(phys_ptr<const MCPRequestPrepareTitle> request,\n                  phys_ptr<MCPResponsePrepareTitle> response);\n\nMCPError\nmcpSwitchTitle(phys_ptr<const MCPRequestSwitchTitle> request);\n\nMCPError\nmcpUpdateCheckContext(phys_ptr<MCPResponseUpdateCheckContext> response);\n\nMCPError\nmcpUpdateCheckResume(phys_ptr<MCPResponseUpdateCheckResume> response);\n\nMCPError\nmcpUpdateGetProgress(phys_ptr<MCPResponseUpdateProgress> response);\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_mcp_request.h",
    "content": "#pragma once\n#include \"ios_mcp_enum.h\"\n#include \"ios_mcp_mcp_types.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::mcp\n{\n\n/**\n * \\ingroup ios_dev_mcp\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct MCPRequestDeviceList\n{\n   be2_val<uint32_t> flags;\n};\nCHECK_OFFSET(MCPRequestDeviceList, 0x00, flags);\nCHECK_SIZE(MCPRequestDeviceList, 0x04);\n\nstruct MCPRequestGetFileLength\n{\n   UNKNOWN(0x14);\n   be2_val<MCPFileType> fileType;\n   be2_val<uint32_t> unk0x18;\n   UNKNOWN(0x28 - 0x1C);\n   be2_array<char, 64> name;\n   PADDING(0x12D8 - 0x68);\n};\nCHECK_OFFSET(MCPRequestGetFileLength, 0x14, fileType);\nCHECK_OFFSET(MCPRequestGetFileLength, 0x18, unk0x18);\nCHECK_OFFSET(MCPRequestGetFileLength, 0x28, name);\nCHECK_SIZE(MCPRequestGetFileLength, 0x12D8);\n\nstruct MCPRequestGetOwnTitleInfo\n{\n   be2_val<uint32_t> unk0x00;\n};\nCHECK_OFFSET(MCPRequestGetOwnTitleInfo, 0x00, unk0x00);\nCHECK_SIZE(MCPRequestGetOwnTitleInfo, 0x04);\n\nstruct MCPRequestLoadFile\n{\n   UNKNOWN(0x10);\n   be2_val<uint32_t> pos;\n   be2_val<MCPFileType> fileType;\n   be2_val<uint32_t> cafeProcessId;\n   UNKNOWN(0xC);\n   be2_array<char, 0x40> name;\n   UNKNOWN(0x12D8 - 0x68);\n};\nCHECK_OFFSET(MCPRequestLoadFile, 0x10, pos);\nCHECK_OFFSET(MCPRequestLoadFile, 0x14, fileType);\nCHECK_OFFSET(MCPRequestLoadFile, 0x18, cafeProcessId);\nCHECK_OFFSET(MCPRequestLoadFile, 0x28, name);\nCHECK_SIZE(MCPRequestLoadFile, 0x12D8);\n\nstruct MCPRequestPrepareTitle\n{\n   be2_val<MCPTitleId> titleId;\n   UNKNOWN(0x60);\n   be2_array<char, 4096> argvStr;\n   PADDING(0x12D8 - 0x1068);\n};\nCHECK_OFFSET(MCPRequestPrepareTitle, 0x00, titleId);\nCHECK_OFFSET(MCPRequestPrepareTitle, 0x68, argvStr);\nCHECK_SIZE(MCPRequestPrepareTitle, 0x12D8);\n\nstruct MCPRequestSearchTitleList\n{\n   be2_struct<MCPTitleListType> searchTitle;\n   be2_val<MCPTitleListSearchFlags> searchFlags;\n};\nCHECK_OFFSET(MCPRequestSearchTitleList, 0x00, searchTitle);\nCHECK_OFFSET(MCPRequestSearchTitleList, 0x61, searchFlags);\nCHECK_SIZE(MCPRequestSearchTitleList, 0x65);\n\nstruct MCPRequestSwitchTitle\n{\n   UNKNOWN(0x18);\n   be2_val<uint32_t> cafeProcessId;\n   be2_val<phys_addr> dataStart;\n   be2_val<phys_addr> codeEnd;\n   be2_val<phys_addr> codeGenStart;\n   PADDING(0x12D8 - 0x28);\n};\nCHECK_OFFSET(MCPRequestSwitchTitle, 0x18, cafeProcessId);\nCHECK_OFFSET(MCPRequestSwitchTitle, 0x1C, dataStart);\nCHECK_OFFSET(MCPRequestSwitchTitle, 0x20, codeEnd);\nCHECK_OFFSET(MCPRequestSwitchTitle, 0x24, codeGenStart);\nCHECK_SIZE(MCPRequestSwitchTitle, 0x12D8);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::mcp\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_mcp_response.h",
    "content": "#pragma once\n#include \"ios_mcp_enum.h\"\n#include \"ios_mcp_mcp_types.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::mcp\n{\n\n/**\n * \\ingroup ios_mcp\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct MCPResponseGetTitleId\n{\n   be2_val<uint64_t> titleId;\n};\nCHECK_OFFSET(MCPResponseGetTitleId, 0x00, titleId);\nCHECK_SIZE(MCPResponseGetTitleId, 8);\n\nstruct MCPResponseGetOwnTitleInfo\n{\n   be2_struct<MCPTitleListType> titleInfo;\n};\nCHECK_OFFSET(MCPResponseGetOwnTitleInfo, 0x00, titleInfo);\nCHECK_SIZE(MCPResponseGetOwnTitleInfo, 0x61);\n\nstruct MCPResponseGetSysProdSettings\n{\n   be2_struct<MCPSysProdSettings> settings;\n};\nCHECK_OFFSET(MCPResponseGetSysProdSettings, 0x00, settings);\nCHECK_SIZE(MCPResponseGetSysProdSettings, 0x46);\n\nstruct MCPResponsePrepareTitle\n{\n   UNKNOWN(0x68);\n   be2_struct<MCPPPrepareTitleInfo> titleInfo;\n};\nCHECK_OFFSET(MCPResponsePrepareTitle, 0x68, titleInfo);\nCHECK_SIZE(MCPResponsePrepareTitle, 0x12D8);\n\nstruct MCPResponseUpdateProgress\n{\n   be2_struct<MCPUpdateProgress> progress;\n};\nCHECK_SIZE(MCPResponseUpdateProgress, 0x38);\n\nstruct MCPResponseUpdateCheckContext\n{\n   be2_val<uint32_t> result;\n};\nCHECK_OFFSET(MCPResponseUpdateCheckContext, 0x00, result);\nCHECK_SIZE(MCPResponseUpdateCheckContext, 4);\n\nstruct MCPResponseUpdateCheckResume\n{\n   be2_val<uint32_t> result;\n};\nCHECK_OFFSET(MCPResponseUpdateCheckResume, 0x00, result);\nCHECK_SIZE(MCPResponseUpdateCheckResume, 4);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::mcp\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_mcp_thread.cpp",
    "content": "#include \"ios_mcp.h\"\n#include \"ios_mcp_config.h\"\n#include \"ios_mcp_enum.h\"\n#include \"ios_mcp_mcp_device.h\"\n#include \"ios_mcp_mcp_thread.h\"\n#include \"ios_mcp_mcp_request.h\"\n#include \"ios_mcp_mcp_response.h\"\n#include \"ios_mcp_pm_thread.h\"\n\n#include \"ios/ios_stackobject.h\"\n#include \"ios/auxil/ios_auxil_config.h\"\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n#include \"ios/kernel/ios_kernel_debug.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include <common/log.h>\n\nnamespace ios::mcp::internal\n{\n\nconstexpr auto McpThreadStackSize = 0x8000u;\nconstexpr auto McpThreadPriority = 123u;\nconstexpr auto MaxNumMessages = 200u;\nconstexpr auto MaxNumMcpHandles = 256u;\n\nusing MCPHandle = int32_t;\n\nusing namespace fs;\nusing namespace kernel;\n\nstruct StaticMcpThreadData\n{\n   be2_val<uint32_t> systemMode;\n   be2_val<FSAHandle> fsaHandle;\n   be2_val<ThreadId> threadId;\n   be2_array<uint8_t, McpThreadStackSize> threadStack;\n   be2_array<Message, MaxNumMessages> messageBuffer;\n   be2_array<uint8_t, MaxNumMcpHandles / 8> handleOpenBitset;\n   be2_array<char, 0x100> cafeTitlePath;\n};\n\nstatic phys_ptr<StaticMcpThreadData>\nsData;\n\nios::Handle\ngetFsaHandle()\n{\n   return sData->fsaHandle;\n}\n\nstatic MCPError\nmcpIoctl(phys_ptr<ResourceRequest> request)\n{\n   auto error = MCPError::OK;\n   auto &ioctl = request->requestData.args.ioctl;\n\n   switch (static_cast<MCPCommand>(request->requestData.args.ioctl.request)) {\n   case MCPCommand::DeviceList:\n      if (ioctl.inputBuffer &&\n          ioctl.inputLength == sizeof(MCPRequestDeviceList) &&\n          ioctl.outputBuffer &&\n          ioctl.outputLength >= sizeof(MCPDevice)) {\n         error = mcpDeviceList(phys_cast<const MCPRequestDeviceList *>(ioctl.inputBuffer),\n                               phys_cast<MCPDevice *>(ioctl.outputBuffer),\n                               ioctl.outputLength);\n      } else {\n         error = MCPError::InvalidParam;\n      }\n      break;\n   case MCPCommand::GetFileLength:\n      if (ioctl.inputBuffer &&\n          ioctl.inputLength == sizeof(MCPRequestGetFileLength) &&\n          !ioctl.outputBuffer &&\n          !ioctl.outputLength) {\n         error = mcpGetFileLength(phys_cast<const MCPRequestGetFileLength *>(ioctl.inputBuffer));\n      } else {\n         error = MCPError::InvalidParam;\n      }\n      break;\n   case MCPCommand::GetTitleId:\n      if (ioctl.outputLength == sizeof(MCPResponseGetTitleId)) {\n         error = mcpGetTitleId(request,\n                               phys_cast<MCPResponseGetTitleId *>(ioctl.outputBuffer));\n      } else {\n         error = MCPError::InvalidParam;\n      }\n      break;\n   case MCPCommand::LoadFile:\n      if (ioctl.inputLength >= sizeof(MCPResponseGetTitleId) &&\n          ioctl.outputLength) {\n         error = mcpLoadFile(phys_cast<const MCPRequestLoadFile *>(ioctl.inputBuffer),\n                             ioctl.outputBuffer,\n                             ioctl.outputLength);\n      } else {\n         error = MCPError::InvalidParam;\n      }\n      break;\n   case MCPCommand::PrepareTitle0x52:\n      if (ioctl.inputBuffer &&\n          ioctl.inputLength == sizeof(MCPRequestPrepareTitle) &&\n          ioctl.outputBuffer &&\n          ioctl.outputLength == sizeof(MCPResponsePrepareTitle)) {\n         error = mcpPrepareTitle52(phys_cast<const MCPRequestPrepareTitle *>(ioctl.inputBuffer),\n                                   phys_cast<MCPResponsePrepareTitle *>(ioctl.outputBuffer));\n      } else {\n         error = MCPError::InvalidParam;\n      }\n      break;\n   case MCPCommand::SwitchTitle:\n      if (ioctl.inputBuffer &&\n          ioctl.inputLength == sizeof(MCPRequestSwitchTitle) &&\n          !ioctl.outputBuffer && !ioctl.outputLength) {\n         error = mcpSwitchTitle(phys_cast<const MCPRequestSwitchTitle *>(ioctl.inputBuffer));\n      } else {\n         error = MCPError::InvalidParam;\n      }\n      break;\n   case MCPCommand::UpdateCheckContext:\n      if (ioctl.outputBuffer &&\n          ioctl.outputLength == sizeof(MCPResponseUpdateCheckContext)) {\n         error = mcpUpdateCheckContext(phys_cast<MCPResponseUpdateCheckContext *>(ioctl.outputBuffer));\n      } else {\n         error = MCPError::InvalidParam;\n      }\n      break;\n   case MCPCommand::UpdateCheckResume:\n      if (ioctl.outputBuffer &&\n          ioctl.outputLength == sizeof(MCPResponseUpdateCheckResume)) {\n         error = mcpUpdateCheckResume(phys_cast<MCPResponseUpdateCheckResume *>(ioctl.outputBuffer));\n      } else {\n         error = MCPError::InvalidParam;\n      }\n      break;\n   default:\n      error = MCPError::Opcode;\n   }\n\n   return error;\n}\n\nstatic MCPError\nmcpIoctlv(phys_ptr<ResourceRequest> request)\n{\n   auto error = MCPError::OK;\n   auto &ioctlv = request->requestData.args.ioctlv;\n\n   switch (static_cast<MCPCommand>(ioctlv.request)) {\n   case MCPCommand::GetSysProdSettings:\n      if (ioctlv.numVecIn == 0 &&\n          ioctlv.numVecOut == 1 &&\n          ioctlv.vecs[0].paddr &&\n          ioctlv.vecs[0].len == sizeof(MCPResponseGetSysProdSettings)) {\n         error = mcpGetSysProdSettings(phys_cast<MCPResponseGetSysProdSettings *>(ioctlv.vecs[0].paddr));\n      } else {\n         error = MCPError::InvalidParam;\n      }\n      break;\n   case MCPCommand::UpdateGetProgress:\n      if (ioctlv.numVecIn == 0 &&\n          ioctlv.numVecOut == 1 &&\n          ioctlv.vecs[0].paddr &&\n          ioctlv.vecs[0].len == sizeof(MCPResponseUpdateProgress)) {\n         error = mcpUpdateGetProgress(phys_cast<MCPResponseUpdateProgress *>(ioctlv.vecs[0].paddr));\n      } else {\n         error = MCPError::InvalidParam;\n      }\n      break;\n   default:\n      error = MCPError::Opcode;\n   }\n\n   return error;\n}\n\nstatic bool\nisOpenHandle(MCPHandle handle)\n{\n   if (handle < 0) {\n      return false;\n   }\n\n   auto handleIndex = handle >> 16;\n   if (handleIndex >= MaxNumMcpHandles) {\n      return false;\n   }\n\n   auto byteIndex = handleIndex / 8;\n   auto bitIndex = handleIndex % 8;\n   return sData->handleOpenBitset[byteIndex] & (1 << bitIndex);\n}\n\nstatic Error\nmcpOpen(ClientCapabilityMask caps)\n{\n   auto handleIndex = -1;\n   auto byteIndex = 0;\n   auto bitIndex = 0;\n\n   for (auto i = 0u; i < MaxNumMcpHandles; ++i) {\n      byteIndex = i / 8;\n      bitIndex = i % 8;\n\n      if ((sData->handleOpenBitset[byteIndex] & (1 << bitIndex)) == 0) {\n         handleIndex = i;\n         break;\n      }\n   }\n\n   if (handleIndex < 0) {\n      return Error::Max;\n   }\n\n   // Set the open bit\n   sData->handleOpenBitset[byteIndex] |= (1 << bitIndex);\n\n   auto handle = (handleIndex << 16) | static_cast<int32_t>(caps & 0xFFFF);\n   return static_cast<Error>(handle);\n}\n\nstatic Error\nmcpClose(MCPHandle handle)\n{\n   auto handleIndex = handle >> 16;\n   if (handleIndex >= MaxNumMcpHandles) {\n      return Error::InvalidHandle;\n   }\n\n   // Clear the open bit\n   auto byteIndex = handleIndex / 8;\n   auto bitIndex = handleIndex % 8;\n   sData->handleOpenBitset[byteIndex] &= ~(1 << bitIndex);\n\n   return Error::OK;\n}\n\nstatic MCPError\ninitialiseClientCaps()\n{\n   StackObject<uint64_t> mask;\n\n   struct\n   {\n      ProcessId pid;\n      FeatureId fid;\n      uint64_t mask;\n   } caps[] = {\n      { ProcessId::CRYPTO,        1,                       0xFF },\n      { ProcessId::USB,           1,                        0xF },\n      { ProcessId::USB,           0xC,       0xFFFFFFFFFFFFFFFF },\n      { ProcessId::USB,           9,         0xFFFFFFFFFFFFFFFF },\n      { ProcessId::USB,           0xB,                0x3300300 },\n      { ProcessId::FS,            0xB,           0x400000000F00 },\n      { ProcessId::FS,            0xD,                        1 },\n      { ProcessId::FS,            0xC,                        1 },\n      { ProcessId::PAD,           0xB,                 0x101000 },\n      { ProcessId::PAD,           1,                        0xF },\n      { ProcessId::PAD,           0xD,                     0x11 },\n      { ProcessId::PAD,           2,         0xFFFFFFFFFFFFFFFF },\n      { ProcessId::PAD,           0x18,      0xFFFFFFFFFFFFFFFF },\n      { ProcessId::NET,           1,                        0xF },\n      { ProcessId::NET,           8,                          1 },\n      { ProcessId::NET,           0xB,               0x101B1001 },\n      { ProcessId::NET,           3,                          3 },\n      { ProcessId::NET,           0xE,                     0x10 },\n      { ProcessId::NET,           0x10,                   0x800 },\n      { ProcessId::NET,           0x11,                       8 },\n      { ProcessId::NET,           2,         0xFFFFFFFFFFFFFFFF },\n      { ProcessId::NET,           0x12,                     0xF },\n      { ProcessId::NET,           0x14,                     0xF },\n      { ProcessId::NET,           0xC,                        1 },\n      { ProcessId::NET,           0x1A,      0xFFFFFFFFFFFFFFFF },\n      { ProcessId::NET,           0xD,                     0x11 },\n      { ProcessId::ACP,           0xB,       0xFFFFFFFFF33F3091 },\n      { ProcessId::ACP,           0xD,                     0x11 },\n      { ProcessId::ACP,           1,                        0xF },\n      { ProcessId::ACP,           0x12,                     0xF },\n      { ProcessId::ACP,           0xF,                        1 },\n      { ProcessId::ACP,           0x10,                    0xFF },\n      { ProcessId::ACP,           2,         0xFFFFFFFFFFFFFFFF },\n      { ProcessId::ACP,           0xE,                   0x27CB },\n      { ProcessId::ACP,           0x11,                     0xA },\n      { ProcessId::ACP,           0x14,                     0xF },\n      { ProcessId::ACP,           9,                          1 },\n      { ProcessId::NSEC,          0xB,                 0x303300 },\n      { ProcessId::NSEC,          0xD,                        1 },\n      { ProcessId::NSEC,          2,         0xFFFFFFFFFFFFFFFF },\n      { ProcessId::FPD,           0xB,                0x3303000 },\n      { ProcessId::FPD,           0xD,                     0x11 },\n      { ProcessId::FPD,           0x12,                     0xF },\n      { ProcessId::FPD,           0xF,                        3 },\n      { ProcessId::FPD,           2,         0xFFFFFFFFFFFFFFFF },\n      { ProcessId::FPD,           0x14,                     0xF },\n      { ProcessId::FPD,           0x16,      0xFFFFFFFFFFFFFFFF },\n      { ProcessId::NIM,           0xB,            0x200303B3000 },\n      { ProcessId::NIM,           1,                        0xF },\n      { ProcessId::NIM,           0xD,                     0x15 },\n      { ProcessId::NIM,           0x12,                    0x13 },\n      { ProcessId::NIM,           0xF,                        3 },\n      { ProcessId::NIM,           0x11,                       3 },\n      { ProcessId::NIM,           2,         0xFFFFFFFFFFFFFFFF },\n      { ProcessId::NIM,           0x13,      0xFFFFFFFFFFFFFFFF },\n      { ProcessId::NIM,           0x14,                     0xF },\n      { ProcessId::NIM,           0x16,      0xFFFFFFFFFFFFFFFF },\n      { ProcessId::AUXIL,         0xB,                0x3300300 },\n      { ProcessId::TEST,          1,                        0xF },\n      { ProcessId::TEST,          3,                          3 },\n      { ProcessId::TEST,          0xB,       0xFFFFFFFFF03FFF00 },\n      { ProcessId::TEST,          0xD,                        1 },\n      { ProcessId::TEST,          0x16,                       1 },\n      { ProcessId::COSKERNEL,     1,                     0xFF00 },\n      { ProcessId::COSKERNEL,     3,                          6 },\n      { ProcessId::COSKERNEL,     0xD,                        1 },\n      { ProcessId::COSROOT,       1,                      0xF00 },\n      { ProcessId::COSROOT,       3,                          6 },\n      { ProcessId::COSROOT,       0xD,                     0x11 },\n   };\n\n   for (auto &cap : caps) {\n      *mask = cap.mask;\n      IOS_SetClientCapabilities(cap.pid, cap.fid, mask);\n   }\n\n   return MCPError::OK;\n}\n\nstatic FSAStatus\ninitialiseDirectories()\n{\n   static const struct\n   {\n      const char *path;\n      uint32_t changeOwnerArg0;\n      uint32_t changeOwnerArg1;\n      uint32_t changeOwnerArg3;\n      ProcessId changeOwnerArg2;\n      uint32_t mode;\n      uint32_t sysModePcfs;\n      uint32_t sysModeNand;\n      uint32_t sysModeSdcard;\n      int32_t makeQuotaSizeHi;\n      uint32_t makeQuotaSizeLo;\n      uint32_t flushBit;\n   } sDirs[] = {\n      { \"/vol/system_slc/logs\",             0,          0,     0, ProcessId::MCP,       0, 0x164000, 0x1C4000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/system_slc/rights\",           0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/system_slc/rights/ticket\",    0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/system_slc/title\",            0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/system_slc/import\",           0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/system_slc/security\",         0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/system_slc/config\",           0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/system_slc/proc\",             0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/system_slc/proc/acp\",         0, 0x100000F6,     0, ProcessId::ACP,   0x600, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/system_slc/proc/prefs\",       0, 0x100000F5,     0, ProcessId::AUXIL, 0x600, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/system_slc/proc/usb\",         0, 0x100000FA,     0, ProcessId::USB,   0x600, 0x1E4000, 0x1C4000,  0x40000, -1, 0xFFFFFFFC, 0 },\n      { \"/vol/system_slc/tmp\",              0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 2 },\n      { \"/vol/storage_mlc01/sys\",           0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000,  0, 0xC0000000, 1 },\n      { \"/vol/storage_mlc01/usr\",           0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/storage_mlc01/sys/title\",     0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/storage_mlc01/sys/config\",    0,          0,     0, ProcessId::MCP,       0, 0x164000, 0x144000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/storage_mlc01/sys/import\",    0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/storage_mlc01/sys/update\",    0, 0x100000F3,     0, ProcessId::NIM,   0x600, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/storage_mlc01/usr/tmp\",       0,          0,     0, ProcessId::MCP,   0x666, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/storage_mlc01/usr/title\",     0,          0,     0, ProcessId::MCP,       0, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/storage_mlc01/usr/import\",    0,          0,     0, ProcessId::MCP,       0, 0x164000, 0x144000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/storage_mlc01/usr/save\",      0, 0x100000F6,     0, ProcessId::ACP,   0x600, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/storage_mlc01/usr/boss\",      0, 0x100000F6,     0, ProcessId::ACP,   0x600, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/storage_mlc01/usr/nsec\",      0, 0x100000F4,     0, ProcessId::NSEC,  0x600, 0x164000, 0x144000,  0x40000,  0,   0xA00000, 1 },\n      { \"/vol/storage_mlc01/usr/packages\",  0, 0x100000F3,     0, ProcessId::NIM,   0x600, 0x164000,  0x44000,  0x40000, -1, 0xFFFFFFFC, 1 },\n      { \"/vol/system_ram/cache\",            0,          0,     0, ProcessId::MCP,       0, 0x1A4000, 0x184000, 0x140000,  0,  0x6400000, 4 },\n      { \"/vol/system_ram/es\",               0,          0,     0, ProcessId::MCP,       0, 0x1E4000, 0x1C4000, 0x140000,  0,   0x300000, 4 },\n      { \"/vol/system_ram/config\",           0,          0,     0, ProcessId::MCP,       0, 0x1A4000, 0x184000, 0x140000, -1, 0xFFFFFFFC, 4 },\n      { \"/vol/system_ram/proc\",             0,          0,     0, ProcessId::MCP,       0, 0x1A4000, 0x184000, 0x140000, -1, 0xFFFFFFFC, 4 },\n      { \"/vol/system_ram/proc/cache\",       0,          0,     0, ProcessId::MCP,   0x600, 0x1A0000, 0x180000, 0x140000,  0,   0xA00000, 4 },\n      { \"/vol/system_ram/proc/fpd\",         0, 0x100000F7, 0x400, ProcessId::FPD,   0x600, 0x1A4000, 0x184000, 0x140000,  0,    0x40000, 4 },\n      { \"/vol/system_ram/proc/prefs\",       0, 0x100000F5,     0, ProcessId::AUXIL, 0x600, 0x1A4000, 0x184000, 0x140000,  0,   0x100000, 4 },\n      { \"/vol/system_ram/proc/usb\",         0, 0x100000FA,     0, ProcessId::USB,   0x600, 0x1A4000, 0x184000, 0x140000,  0,    0x40000, 4 },\n      { \"/vol/system_hfio/config\",          0,          0,     0, ProcessId::MCP,       0, 0x1A0000,        0,        0, -1, 0xFFFFFFFC, 0 },\n      { \"/vol/system_hfio/proc\",            0,          0,     0, ProcessId::MCP,       0, 0x1A0000,        0,        0, -1, 0xFFFFFFFC, 0 },\n      { \"/vol/system_hfio/proc/acp\",        0, 0x100000F6,     0, ProcessId::ACP,   0x600, 0x1A0000,        0,        0, -1, 0xFFFFFFFC, 0 },\n      { \"/vol/system_hfio/proc/usb\",        0, 0x100000FA,     0, ProcessId::USB,   0x600, 0x1A0000,        0,        0, -1, 0xFFFFFFFC, 0 },\n      { \"/vol/system_hfio/proc/prefs\",      0, 0x100000F5,     0, ProcessId::AUXIL, 0x600, 0x1A0000,        0,        0, -1, 0xFFFFFFFC, 0 },\n      { \"/vol/system_hfio/logs\",            0,          0,     0, ProcessId::MCP,       0, 0x120000,        0,        0, -1, 0xFFFFFFFC, 0 },\n   };\n\n   StackObject<FSAStat> stat;\n   auto flushBits = 0u;\n   auto systemFileSys = getSystemFileSys();\n   auto systemModeFlags = getSystemModeFlags();\n\n   // TODO: Remove this hack which force creates all directories\n   systemModeFlags |= 0xFFFFFFFF;\n\n   for (auto &dir : sDirs) {\n      auto dirSystemMode = dir.sysModeNand;\n\n      if (systemFileSys == SystemFileSys::Pcfs) {\n         dirSystemMode = dir.sysModePcfs;\n      } else if (systemFileSys == SystemFileSys::SdCard) {\n         dirSystemMode = dir.sysModeSdcard;\n      }\n\n      if (!(systemModeFlags & dirSystemMode)) {\n         continue;\n      }\n\n      auto error = FSAGetInfoByQuery(sData->fsaHandle, dir.path, FSAQueryInfoType::Stat, stat);\n      if (error == FSAStatus::NotFound) {\n         if (dir.makeQuotaSizeHi == -1) {\n            error = FSAMakeDir(sData->fsaHandle, dir.path, dir.mode);\n         } else {\n            auto quota = (static_cast<uint64_t>(dir.makeQuotaSizeHi) << 32) | dir.makeQuotaSizeLo;\n            error = FSAMakeQuota(sData->fsaHandle, dir.path, dir.mode, quota);\n         }\n\n         if (error == FSAStatus::OK) {\n            // TODO: FSAChangeOwner\n         }\n      } else {\n         // TODO: FSAChangeMode\n      }\n\n      if (error < FSAStatus::OK) {\n         return error;\n      }\n\n      flushBits |= dir.flushBit;\n   }\n\n   if (flushBits & 1) {\n      // TODO: FSAFlushVolume \"/vol/storage_mlc01\"\n   }\n\n   if (flushBits & 2) {\n      // TODO: FSAFlushVolume \"/vol/system_slc\"\n   }\n\n   if (flushBits & 4) {\n      // TODO: FSAFlushVolume \"/vol/system_ram\"\n   }\n\n   return FSAStatus::OK;\n}\n\nstatic Error\ncleanTmpDirectories()\n{\n   // TODO: Clean dirs in /vol/system_slc/tmp\n   // TODO: Clean dirs in /vol/storage_mlc01/usr/tmp\n   return Error::OK;\n}\n\nstatic MCPError\nmcpResume()\n{\n   auto iosError = FSAOpen();\n   if (iosError < Error::OK) {\n      gLog->error(\"Failed to open FSA handle\");\n      return MCPError::Invalid;\n   }\n\n   sData->fsaHandle = static_cast<FSAHandle>(iosError);\n\n   // Mount the system devices\n   auto fsaStatus = FSAMount(sData->fsaHandle, \"/dev/slc01\", \"/vol/system_slc\", 0, nullptr, 0);\n   if (fsaStatus < FSAStatus::OK) {\n      gLog->error(\"Failed to mount /dev/slc01 to /vol/system_slc\");\n      return MCPError::Invalid;\n   }\n\n   fsaStatus = FSAMount(sData->fsaHandle, \"/dev/mlc01\", \"/vol/storage_mlc01\", 0, nullptr, 0);\n   if (fsaStatus < FSAStatus::OK) {\n      gLog->error(\"Failed to mount /dev/mlc01 to /vol/storage_mlc01\");\n      return MCPError::Invalid;\n   }\n\n   fsaStatus = FSAMount(sData->fsaHandle, \"/dev/ramdisk01\", \"/vol/system_ram\", 0, nullptr, 0);\n   if (fsaStatus < FSAStatus::OK) {\n      gLog->error(\"Failed to mount /dev/ramdisk01 to /vol/system_ram\");\n      return MCPError::Invalid;\n   }\n\n   auto securityLevel = IOS_GetSecurityLevel();\n   if (getSystemFileSys() == SystemFileSys::Pcfs && securityLevel != SecurityLevel::Normal) {\n      fsaStatus = FSAMount(sData->fsaHandle, \"/dev/hfio01\", \"/vol/system_hfio\", 0, nullptr, 0);\n      if (fsaStatus < FSAStatus::OK) {\n         gLog->error(\"Failed to mount /dev/hfio01 to /vol/system_hfio\");\n         return MCPError::Invalid;\n      }\n   }\n\n   // Cleanup tmp directories\n   auto error = cleanTmpDirectories();\n   if (error < Error::OK) {\n      return MCPError::Invalid;\n   }\n\n   // Initialise the default directories\n   initialiseDirectories();\n\n   // Mount /vol/system\n   if (getSystemFileSys() == SystemFileSys::Pcfs) {\n      fsaStatus = FSAMount(sData->fsaHandle, \"/vol/system_hfio\", \"/vol/system\", 0, nullptr, 0);\n      if (fsaStatus < FSAStatus::OK) {\n         gLog->error(\"Failed to mount /vol/system_hfio to /vol/system\");\n         return MCPError::Invalid;\n      }\n   } else {\n      fsaStatus = FSAMount(sData->fsaHandle, \"/vol/system_slc\", \"/vol/system\", 0, nullptr, 0);\n      if (fsaStatus < FSAStatus::OK) {\n         gLog->error(\"Failed to mount /vol/system_slc to /vol/system\");\n         return MCPError::Invalid;\n      }\n   }\n\n   // Mount /vol/sys/proc\n   fsaStatus = FSAMount(sData->fsaHandle, \"/vol/system/proc\", \"/vol/sys/proc\", 0, nullptr, 0);\n   if (fsaStatus < FSAStatus::OK) {\n      gLog->error(\"Failed to mount /vol/system/proc to /vol/sys/proc\");\n      return MCPError::Invalid;\n   }\n\n   fsaStatus = FSAMount(sData->fsaHandle, \"/vol/system_slc/proc\", \"/vol/sys/proc_slc\", 0, nullptr, 0);\n   if (fsaStatus < FSAStatus::OK) {\n      gLog->error(\"Failed to mount /vol/system_slc/proc to /vol/sys/proc_slc\");\n      return MCPError::Invalid;\n   }\n\n   fsaStatus = FSAMount(sData->fsaHandle, \"/vol/system_ram/proc\", \"/vol/sys/proc_ram\", 0, nullptr, 0);\n   if (fsaStatus < FSAStatus::OK) {\n      gLog->error(\"Failed to mount /vol/system_ram/proc to /vol/sys/proc_ram\");\n      return MCPError::Invalid;\n   }\n\n   // Open FSA handle for config\n   iosError = auxil::openFsaHandle();\n   if (iosError < Error::OK) {\n      gLog->error(\"Failed to open config FSA handle\");\n      return MCPError::Invalid;\n   }\n\n   // Load configs\n   auto mcpError = loadRtcConfig();\n   if (mcpError < MCPError::OK) {\n      gLog->error(\"Failed to initialise rtc config\");\n      return mcpError;\n   }\n\n   mcpError = loadSystemConfig();\n   if (mcpError < MCPError::OK) {\n      gLog->error(\"Failed to initialise system config\");\n      return mcpError;\n   }\n\n   mcpError = loadSysProdConfig();\n   if (mcpError < MCPError::OK) {\n      gLog->error(\"Failed to initialise sys_prod config\");\n      return mcpError;\n   }\n\n   // Format the Cafe OS title path\n   auto default_os_id = getSystemConfig()->default_os_id;\n   sData->cafeTitlePath = fmt::format(\"/vol/system/title/{:08x}/{:08x}/code\",\n                                      default_os_id >> 32,\n                                      default_os_id & 0xFFFFFFFF);\n\n   // Init client caps\n   mcpError = initialiseClientCaps();\n   if (mcpError < MCPError::OK) {\n      gLog->error(\"Failed to initialise client capabilities\");\n      return mcpError;\n   }\n\n   if (securityLevel == SecurityLevel::Debug || securityLevel == SecurityLevel::Test) {\n      fsaStatus = FSAMount(sData->fsaHandle, \"/dev/hfio01\", \"/vol/storage_hfiomlc01\", 0, nullptr, 0);\n      if (fsaStatus < FSAStatus::OK) {\n         gLog->error(\"Failed to mount /dev/hfio01 to /vol/storage_hfiomlc01\");\n         return MCPError::Invalid;\n      }\n   }\n\n   if (getSystemFileSys() == SystemFileSys::Nand) {\n      getSystemConfig()->ramdisk.cache_user_code = 1u;\n   }\n\n   return MCPError::OK;\n}\n\nstatic MCPError\nmcpSuspend()\n{\n   // TODO: mcp suspend\n   return MCPError::OK;\n}\n\nstatic Error\nmcpThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n\n   auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer),\n                                       sData->messageBuffer.size());\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto messageQueueId = static_cast<MessageQueueId>(error);\n   error = registerResourceManager(\"/dev/mcp\", messageQueueId);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = IOS_AssociateResourceManager(\"/dev/mcp\", ResourcePermissionGroup::MCP);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   while (true) {\n      error = IOS_ReceiveMessage(messageQueueId,\n                                 message,\n                                 MessageFlags::None);\n      if (error < Error::OK) {\n         break;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n         if (!(request->requestData.args.open.caps & 1)) {\n            IOS_ResourceReply(request, Error::Access);\n         } else {\n            error = mcpOpen(request->requestData.args.open.caps);\n            IOS_ResourceReply(request, error);\n         }\n         break;\n      case Command::Close:\n         IOS_ResourceReply(request, mcpClose(request->requestData.handle));\n         break;\n      case Command::Ioctl:\n         if (!isOpenHandle(request->requestData.handle)) {\n            IOS_ResourceReply(request, Error::InvalidHandle);\n         } else {\n            IOS_ResourceReply(request, static_cast<Error>(mcpIoctl(request)));\n         }\n         break;\n      case Command::Ioctlv:\n         if (!isOpenHandle(request->requestData.handle)) {\n            IOS_ResourceReply(request, Error::InvalidHandle);\n         } else {\n            IOS_ResourceReply(request, static_cast<Error>(mcpIoctlv(request)));\n         }\n         break;\n      case Command::Suspend:\n         IOS_ResourceReply(request, static_cast<Error>(mcpSuspend()));\n         break;\n      case Command::Resume:\n         IOS_ResourceReply(request, static_cast<Error>(mcpResume()));\n         break;\n      default:\n         IOS_ResourceReply(request, Error::InvalidArg);\n      }\n   }\n\n   return error;\n}\n\nError\nstartMcpThread()\n{\n   auto error = IOS_CreateThread(&mcpThreadEntry,\n                                 nullptr,\n                                 phys_addrof(sData->threadStack) + sData->threadStack.size(),\n                                 sData->threadStack.size(),\n                                 McpThreadPriority,\n                                 ThreadFlags::Detached);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(threadId, \"McpThread\");\n   return IOS_StartThread(threadId);\n}\n\nvoid\ninitialiseStaticMcpThreadData()\n{\n   sData = allocProcessStatic<StaticMcpThreadData>();\n}\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_mcp_thread.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::mcp::internal\n{\n\nError\nstartMcpThread();\n\nvoid\ninitialiseStaticMcpThreadData();\n\nios::Handle\ngetFsaHandle();\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_mcp_types.h",
    "content": "#pragma once\n#include \"ios_mcp_config.h\"\n#include \"ios_mcp_enum.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::mcp\n{\n\n/**\n * \\ingroup ios_mcp\n * @{\n */\n\n#pragma pack(push, 1)\n\nusing MCPSysProdSettings = internal::SysProdConfig;\nusing MCPTitleId = uint64_t;\n\nconstexpr auto DefaultTitleId = static_cast<MCPTitleId>(-3);\n\nstruct MCPDevice\n{\n   //! Device type e.g. mlc, slc, usb\n   be2_array<char, 8> type;\n\n   //! This seems to be some 7 character unique identifier which is only set\n   //! for mlc, ramdisk, usb\n   be2_array<char, 128> unk0x08;\n\n   //! Filesystem e.g. wfs, fat, isfs\n   be2_array<char, 8> filesystem;\n\n   //! Path, e.g. /vol/storage_mlc01\n   be2_array<char, 0x27F> path;\n\n   be2_val<MCPDeviceFlags> flags;\n\n   //! Unique index - incrementing id in order of device added to system\n   be2_val<uint32_t> uid;\n\n   //! Device type index, e.g. 3 for /vol/storage_mlc03\n   be2_val<uint32_t> index;\n};\nCHECK_OFFSET(MCPDevice, 0x00, type);\nCHECK_OFFSET(MCPDevice, 0x08, unk0x08);\nCHECK_OFFSET(MCPDevice, 0x88, filesystem);\nCHECK_OFFSET(MCPDevice, 0x90, path);\nCHECK_OFFSET(MCPDevice, 0x30F, flags);\nCHECK_OFFSET(MCPDevice, 0x313, uid);\nCHECK_OFFSET(MCPDevice, 0x317, index);\nCHECK_SIZE(MCPDevice, 0x31B);\n\n// Offsets of MCPTitleAppXml not verified.\nstruct MCPTitleAppXml\n{\n   be2_val<uint32_t> version;\n   be2_val<uint64_t> os_version;\n   be2_val<uint64_t> title_id;\n   be2_val<uint16_t> title_version;\n   be2_val<uint32_t> sdk_version;\n   be2_val<uint32_t> app_type;\n   be2_val<uint32_t> group_id;\n   be2_array<uint8_t, 32> os_mask;\n   be2_val<uint64_t> common_id;\n};\n\nstruct MCPPPrepareTitleInfo\n{\n   struct Permission\n   {\n      be2_val<uint32_t> group;\n      be2_val<uint64_t> mask;\n   };\n\n   be2_val<uint32_t> version;\n   UNKNOWN(8);\n   be2_val<MCPTitleId> titleId;\n   be2_val<uint32_t> groupId;\n   be2_val<uint32_t> cmdFlags;\n   be2_array<char, 4096> argstr;\n   be2_array<virt_ptr<char>, 64> argv;\n   be2_val<uint32_t> max_size;\n   be2_val<uint32_t> avail_size;\n   be2_val<uint32_t> codegen_size;\n   be2_val<uint32_t> codegen_core;\n   be2_val<uint32_t> max_codesize;\n   be2_val<uint32_t> overlay_arena;\n   be2_val<uint32_t> num_workarea_heap_blocks;\n   be2_val<uint32_t> num_codearea_heap_blocks;\n   be2_array<Permission, 19> permissions;\n   be2_val<uint32_t> default_stack0_size;\n   be2_val<uint32_t> default_stack1_size;\n   be2_val<uint32_t> default_stack2_size;\n   be2_val<uint32_t> default_redzone0_size;\n   be2_val<uint32_t> default_redzone1_size;\n   be2_val<uint32_t> default_redzone2_size;\n   be2_val<uint32_t> exception_stack0_size;\n   be2_val<uint32_t> exception_stack1_size;\n   be2_val<uint32_t> exception_stack2_size;\n   be2_val<uint32_t> sdkVersion;\n   be2_val<uint32_t> titleVersion;\n   UNKNOWN(0x1270 - 0x124C);\n};\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x00, version);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x0C, titleId);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x14, groupId);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x18, cmdFlags);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1C, argstr);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x101C, argv);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x111C, max_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1120, avail_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1124, codegen_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1128, codegen_core);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x112C, max_codesize);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1130, overlay_arena);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1134, num_workarea_heap_blocks);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1138, num_codearea_heap_blocks);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1220, default_stack0_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1224, default_stack1_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1228, default_stack2_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x122C, default_redzone0_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1230, default_redzone1_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1234, default_redzone2_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1238, exception_stack0_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x123C, exception_stack1_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1240, exception_stack2_size);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1244, sdkVersion);\nCHECK_OFFSET(MCPPPrepareTitleInfo, 0x1248, titleVersion);\nCHECK_SIZE(MCPPPrepareTitleInfo, 0x1270);\n\nstruct MCPTitleListType\n{\n   be2_val<uint64_t> titleId;\n   UNKNOWN(4);\n   be2_array<char, 56> path;\n   be2_val<MCPAppType> appType;\n   UNKNOWN(0x54 - 0x48);\n   be2_val<uint8_t> device;\n   UNKNOWN(1);\n   be2_array<char, 10> indexedDevice;\n   be2_val<uint8_t> unk0x60;\n};\nCHECK_OFFSET(MCPTitleListType, 0x00, titleId);\nCHECK_OFFSET(MCPTitleListType, 0x0C, path);\nCHECK_OFFSET(MCPTitleListType, 0x44, appType);\nCHECK_OFFSET(MCPTitleListType, 0x54, device);\nCHECK_OFFSET(MCPTitleListType, 0x56, indexedDevice);\nCHECK_OFFSET(MCPTitleListType, 0x60, unk0x60);\nCHECK_SIZE(MCPTitleListType, 0x61);\n\nstruct MCPUpdateProgress\n{\n   UNKNOWN(0x38);\n};\nCHECK_SIZE(MCPUpdateProgress, 0x38);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::mcp\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_pm_thread.cpp",
    "content": "#include \"ios_mcp_enum.h\"\n#include \"ios_mcp_pm_thread.h\"\n\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/kernel/ios_kernel_timer.h\"\n\n#include \"ios/ios_stackobject.h\"\n\n#include <array>\n#include <common/log.h>\n#include <libcpu/cpu_formatters.h>\n#include <memory>\n\nnamespace ios::mcp::internal\n{\n\nusing namespace kernel;\n\nusing RegisteredResourceManagerId = int32_t;\n\nconstexpr auto MaxNumRmQueueMessages = 0x80u;\nconstexpr auto MaxNumResourceManagers = 0x56u;\n\nconstexpr auto PmThreadStackSize = 0x2000u;\nconstexpr auto PmThreadPriority = 124u;\n\nstruct ResourceManagerRegistration\n{\n   struct Data // Why another struct? Who knows...\n   {\n      be2_val<BOOL> isDummyRM;\n      be2_val<ResourceHandleId> resourceHandle;\n      be2_val<Error> error;\n      be2_phys_ptr<IpcRequest> messageBuffer;\n      be2_val<ResourceManagerRegistrationState> state;\n      be2_val<uint32_t> systemModeFlags;\n      be2_val<ProcessId> processId;\n      be2_val<uint32_t> unk0x1C;\n      be2_val<TimeMicroseconds64> timeResumeStart;\n      be2_val<TimeMicroseconds64> timeResumeFinished;\n      be2_val<TimeMicroseconds64> timeSuspendStart;\n      be2_val<TimeMicroseconds64> timeSuspendFinished;\n      be2_val<TimeMicroseconds64> timeOpenStart;\n      be2_val<TimeMicroseconds64> timeOpenFinished;\n   };\n\n   be2_phys_ptr<char> name;\n   be2_val<uint32_t> unk0x04;\n   Data data;\n};\nCHECK_OFFSET(ResourceManagerRegistration, 0x00, name);\nCHECK_OFFSET(ResourceManagerRegistration, 0x04, unk0x04);\nCHECK_OFFSET(ResourceManagerRegistration, 0x08, data);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x00, isDummyRM);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x04, resourceHandle);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x08, error);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x0C, messageBuffer);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x10, state);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x14, systemModeFlags);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x18, processId);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x1C, unk0x1C);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x20, timeResumeStart);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x28, timeResumeFinished);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x30, timeSuspendStart);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x38, timeSuspendFinished);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x40, timeOpenStart);\nCHECK_OFFSET(ResourceManagerRegistration::Data, 0x48, timeOpenFinished);\nCHECK_SIZE(ResourceManagerRegistration::Data, 0x50);\nCHECK_SIZE(ResourceManagerRegistration, 0x58);\n\nstruct StaticPmThreadData\n{\n   be2_val<ThreadId> threadId;\n   be2_array<uint8_t, PmThreadStackSize> threadStack;\n\n   be2_array<ResourceManagerRegistration, MaxNumResourceManagers> resourceManagers;\n\n   be2_val<MessageQueueId> resourceManagerMessageQueueId;\n   be2_val<TimerId> resourceManagerTimerId;\n   be2_struct<IpcRequest> resourceManagerTimeoutMessage;\n   be2_array<Message, MaxNumRmQueueMessages> resourceManagerMessageBuffer;\n};\n\nstatic phys_ptr<StaticPmThreadData>\nsPmThreadData;\n\nstatic Error\ngetResourceManagerId(std::string_view name)\n{\n   for (auto i = 0u; i < sPmThreadData->resourceManagers.size(); ++i) {\n      auto &resourceManager = sPmThreadData->resourceManagers[i];\n      if (resourceManager.data.isDummyRM || !resourceManager.name) {\n         continue;\n      }\n\n      if (name.compare(resourceManager.name.get()) == 0) {\n         return static_cast<Error>(i);\n      }\n   }\n\n   return Error::NoResource;\n}\n\nstatic Error\nsendRegisterResourceManagerMessage(RegisteredResourceManagerId id)\n{\n   if (id < 0 || id >= static_cast<RegisteredResourceManagerId>(sPmThreadData->resourceManagers.size())) {\n      return Error::InvalidArg;\n   }\n\n   auto &resourceManager = sPmThreadData->resourceManagers[id];\n   if (resourceManager.data.isDummyRM) {\n      return Error::InvalidArg;\n   }\n\n   if (resourceManager.data.state != ResourceManagerRegistrationState::NotRegistered) {\n      return Error::InvalidArg;\n   }\n\n   resourceManager.data.messageBuffer->command = static_cast<Command>(ResourceManagerCommand::Register);\n   resourceManager.data.messageBuffer->handle = id;\n\n   return IOS_SendMessage(sPmThreadData->resourceManagerMessageQueueId,\n                          makeMessage(resourceManager.data.messageBuffer),\n                          MessageFlags::None);\n}\n\nstatic Error\npmIoctl(PMCommand command,\n        phys_ptr<const void> inputBuffer,\n        uint32_t inputLength,\n        phys_ptr<void> outputBuffer,\n        uint32_t outputLength)\n{\n   auto error = Error::OK;\n\n   switch (command) {\n   case PMCommand::GetResourceManagerId:\n      error = getResourceManagerId(phys_cast<const char *>(inputBuffer).get());\n      break;\n   case PMCommand::RegisterResourceManager:\n      error = sendRegisterResourceManagerMessage(*phys_cast<const RegisteredResourceManagerId *>(inputBuffer));\n      break;\n   default:\n      error = Error::InvalidArg;\n   }\n\n   return error;\n}\n\nstatic Error\npmThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackArray<Message, 0x80u> messageBuffer;\n   StackObject<Message> message;\n\n   // Create message queue\n   auto error = IOS_CreateMessageQueue(messageBuffer, 0x80u);\n   if (error < Error::OK) {\n      return error;\n   }\n   auto messageQueueId = static_cast<MessageQueueId>(error);\n\n   error = IOS_RegisterResourceManager(\"/dev/pm\", messageQueueId);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   while (true) {\n      error = IOS_ReceiveMessage(messageQueueId,\n                                 message,\n                                 MessageFlags::None);\n      if (error < Error::OK) {\n         break;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      case Command::Close:\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      case Command::Ioctl:\n      {\n         error = pmIoctl(static_cast<PMCommand>(request->requestData.args.ioctl.request),\n                         request->requestData.args.ioctl.inputBuffer,\n                         request->requestData.args.ioctl.inputLength,\n                         request->requestData.args.ioctl.outputBuffer,\n                         request->requestData.args.ioctl.outputLength);\n         IOS_ResourceReply(request, error);\n         break;\n      }\n      default:\n         IOS_ResourceReply(request, Error::InvalidArg);\n      }\n   }\n\n   return error;\n}\n\nError\nregisterResourceManager(std::string_view device,\n                        MessageQueueId queue)\n{\n   auto error = getResourceManagerId(device);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto resourceManagerId = static_cast<RegisteredResourceManagerId>(error);\n   error = IOS_RegisterResourceManager(device, queue);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   return sendRegisterResourceManagerMessage(resourceManagerId);\n}\n\nError\nstartPmThread()\n{\n   // Create resource manager message queue\n   auto error = IOS_CreateMessageQueue(phys_addrof(sPmThreadData->resourceManagerMessageBuffer),\n                                       sPmThreadData->resourceManagerMessageBuffer.size());\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sPmThreadData->resourceManagerMessageQueueId = static_cast<MessageQueueId>(error);\n\n   // Create resource manager timeout timer\n   error = IOS_CreateTimer(std::chrono::microseconds { 0 },\n                           std::chrono::microseconds { 0 },\n                           sPmThreadData->resourceManagerMessageQueueId,\n                           makeMessage(phys_addrof(sPmThreadData->resourceManagerTimeoutMessage)));\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sPmThreadData->resourceManagerTimerId = static_cast<TimerId>(error);\n\n   // Initialise resource manager registrations\n   for (auto i = 0u; i < sPmThreadData->resourceManagers.size(); ++i) {\n      auto &rm = sPmThreadData->resourceManagers[i];\n      rm.data.state = ResourceManagerRegistrationState::NotRegistered;\n      rm.data.resourceHandle = static_cast<ResourceHandleId>(Error::Invalid);\n      rm.data.error = Error::Invalid;\n\n      if (!rm.data.isDummyRM) {\n         auto buffer = IOS_HeapAlloc(CrossProcessHeapId,\n                                     static_cast<uint32_t>(sizeof(IpcRequest)));\n         if (!buffer) {\n            return error;\n         }\n\n         rm.data.messageBuffer = phys_cast<IpcRequest *>(buffer);\n      }\n   }\n\n   // Create thread\n   error = IOS_CreateThread(&pmThreadEntry, nullptr,\n                            phys_addrof(sPmThreadData->threadStack) + sPmThreadData->threadStack.size(),\n                            static_cast<uint32_t>(sPmThreadData->threadStack.size()),\n                            PmThreadPriority,\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sPmThreadData->threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sPmThreadData->threadId, \"PmThread\");\n\n   return IOS_StartThread(sPmThreadData->threadId);\n}\n\nstatic Error\nwaitAsyncReplyWithTimeout(MessageQueueId queue,\n                          phys_ptr<Message> message,\n                          TimeMicroseconds32 timeout)\n{\n   if (timeout == -1) {\n      return IOS_ReceiveMessage(queue, message, MessageFlags::None);\n   }\n\n   auto error = IOS_RestartTimer(sPmThreadData->resourceManagerTimerId,\n                                 std::chrono::microseconds { timeout },\n                                 std::chrono::microseconds { 0 });\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = IOS_ReceiveMessage(queue, message, MessageFlags::None);\n   IOS_StopTimer(sPmThreadData->resourceManagerTimerId);\n   return error;\n}\n\n/**\n * Handle all pending resource manager registrations.\n *\n * This code is needlessly complex, thanks Nint*ndo. I think I know what you\n * were trying to do - not overfill the message queue with pending messages\n * whilst at the same time trying to have multiple devices registering\n * asynchronously. Still - it's not great a way to do that is it.\n *\n * A basic summary is that we try to transition states for the RM as:\n * NotRegistered -> Registered -> Pending -> Resumed.\n *\n * The non-trivial logic is to handle all incoming messages on the message\n * queue whilst at the same time ensuring every resource manager gets\n * registered and resumed.\n */\nError\nhandleResourceManagerRegistrations(uint32_t systemModeFlags,\n                                   uint32_t bootFlags)\n{\n   StackObject<Message> message;\n   auto id = 0u;\n   auto numPendingResumes = 0;\n   auto error = Error::OK;\n\n   while (id < sPmThreadData->resourceManagers.size()) {\n      if (sPmThreadData->resourceManagers[id].data.isDummyRM) {\n         auto &dummyRm = sPmThreadData->resourceManagers[id];\n\n         if (numPendingResumes == 0) {\n            // We've processed all our resumes, on to the next resource manager id!\n            IOS_GetUpTime64(phys_addrof(dummyRm.data.timeResumeStart));\n            ++id;\n            continue;\n         }\n      } else {\n         if (!sPmThreadData->resourceManagers[id].name) {\n            // Skip this unimplemented resource.\n            ++id;\n            continue;\n         }\n\n         if ((sPmThreadData->resourceManagers[id].data.systemModeFlags & systemModeFlags) == 0) {\n            // Skip this device if it is not enable for the current system mode.\n            ++id;\n            continue;\n         }\n\n         if (sPmThreadData->resourceManagers[id].data.state == ResourceManagerRegistrationState::Registered) {\n            auto &rm = sPmThreadData->resourceManagers[id];\n\n            // Send a resume request - transition from Registered to Pending.\n            IOS_GetUpTime64(phys_addrof(rm.data.timeResumeStart));\n            rm.data.state = ResourceManagerRegistrationState::Pending;\n            rm.data.error = Error::Invalid;\n\n            error = IOS_ResumeAsync(rm.data.resourceHandle,\n                                    systemModeFlags,\n                                    bootFlags,\n                                    sPmThreadData->resourceManagerMessageQueueId,\n                                    rm.data.messageBuffer);\n            if (error < Error::OK) {\n               gLog->error(\"Unexpected error for IOS_ResumeAsync on resource manager {}, error = {}\",\n                           rm.name.get(), error);\n               return error;\n            }\n\n            // Increase the number of pending resumes so we wait for the reply on\n            // the next dummy manager.\n            ++numPendingResumes;\n\n            // Move onto the next resource manager\n            ++id;\n            continue;\n         }\n      }\n\n      // Check for any pending messages\n      error = waitAsyncReplyWithTimeout(sPmThreadData->resourceManagerMessageQueueId, message, 10000);\n      if (error < Error::OK) {\n         gLog->error(\"Unexpected error for waitAsyncReplyWithTimeout, error = {}\", error);\n         return error;\n      }\n\n      auto request = parseMessage<IpcRequest>(message);\n      auto command = static_cast<ResourceManagerCommand>(request->command);\n      if (command == ResourceManagerCommand::Timeout) {\n         gLog->error(\"Unexpected timeout whilst waiting for resource manager message\");\n         return Error::Timeout;\n      } else if (command == ResourceManagerCommand::Register) {\n         auto &rm = sPmThreadData->resourceManagers[request->handle];\n\n         // Open a resource handle\n         IOS_GetUpTime64(phys_addrof(rm.data.timeOpenStart));\n         error = IOS_Open(rm.name.get(), static_cast<OpenMode>(0x80000000));\n         IOS_GetUpTime64(phys_addrof(rm.data.timeOpenFinished));\n\n         if (error < Error::OK) {\n            gLog->error(\"Unexpected error for IOS_Open on resource manager {}, error = {}\",\n                        rm.name.get(), error);\n            return error;\n         }\n\n         // Transition from NotRegistered to Registered.\n         decaf_check(rm.data.state == ResourceManagerRegistrationState::NotRegistered);\n         rm.data.state = ResourceManagerRegistrationState::Registered;\n         rm.data.resourceHandle = static_cast<ResourceHandleId>(error);\n      } else if (command == ResourceManagerCommand::ResumeReply) {\n         // This is a reply to our resume request - transition from Pending to Resumed.\n         auto resumeId = request->handle;\n         auto &resumeRm = sPmThreadData->resourceManagers[resumeId];\n\n         decaf_check(resumeRm.data.state == ResourceManagerRegistrationState::Pending);\n         resumeRm.data.error = request->reply;\n         resumeRm.data.state = ResourceManagerRegistrationState::Resumed;\n         IOS_GetUpTime64(phys_addrof(resumeRm.data.timeResumeFinished));\n\n         if (request->reply < Error::OK) {\n            resumeRm.data.state = ResourceManagerRegistrationState::Failed;\n            gLog->error(\"Unexpected reply from IOS_ResumeAsync for resource manager {}, error = {}\",\n                        resumeRm.name.get(), request->reply);\n            return request->reply;\n         }\n\n         --numPendingResumes;\n         decaf_check(numPendingResumes >= 0);\n      }\n   }\n\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticPmThreadData()\n{\n   sPmThreadData = allocProcessStatic<StaticPmThreadData>();\n   sPmThreadData->resourceManagerTimeoutMessage.command = static_cast<Command>(Error::Timeout);\n\n   auto dummyRM = ResourceManagerRegistration {\n         nullptr, 0u,\n         ResourceManagerRegistration::Data {\n            TRUE, ResourceHandleId { 0 }, Error::OK, nullptr,\n            ResourceManagerRegistrationState::Invalid, 0u, ProcessId { 0 },\n            0u, 0ull, 0ull, 0ull, 0ull, 0ull, 0ull\n         }\n      };\n\n   auto ss = [](const char *str)\n      {\n         return allocProcessStatic(str);\n      };\n   auto rm = [](uint32_t systemModeFlags, ProcessId pid, uint32_t unk0x1C)\n      {\n         return ResourceManagerRegistration::Data\n            {\n               FALSE, ResourceHandleId { 0 }, Error::OK, nullptr,\n               ResourceManagerRegistrationState::Invalid, systemModeFlags,\n               pid, unk0x1C, 0ull, 0ull, 0ull, 0ull, 0ull, 0ull\n            };\n      };\n\n   // This table was taken from firmware 5.5.1\n   sPmThreadData->resourceManagers = std::array<ResourceManagerRegistration, 86>\n      {\n         dummyRM,\n         { ss(\"/dev/crypto\"),          1u, rm(0x1E8000, ProcessId::CRYPTO, 0) },\n         { ss(\"/dev/ahcimgr\"),         1u, rm(0x1E8000, ProcessId::FS, 0) },\n         //{ ss(\"/dev/usbproc1\"),        1u, rm(0x1C0000, ProcessId::USB, 0) },\n         dummyRM,\n         //{ ss(\"/dev/usb_cdc\"),         1u, rm(0x1C0000, ProcessId::USB, 0) },\n         dummyRM,\n         //{ ss(\"/dev/testproc1\"),       1u, rm(0x1C0000, ProcessId::TEST, 0) },\n         dummyRM,\n         //{ ss(\"/dev/usb_syslog\"),      0u, rm(0x1E8000, ProcessId::MCP, 0) },\n         { ss(\"/dev/mmc\"),             1u, rm(0x1E8000, ProcessId::FS, 0) },\n         //{ ss(\"/dev/odm\"),             1u, rm(0x1E8000, ProcessId::FS, 0) },\n         { ss(\"/dev/shdd\"),            1u, rm(0x1E8000, ProcessId::FS, 0) },\n         { ss(\"/dev/fla\"),             1u, rm(0x1E8000, ProcessId::FS, 0) },\n         dummyRM,\n         //{ ss(\"/dev/dk\"),              1u, rm(0x1E8000, ProcessId::FS, 0) },\n         //{ ss(\"/dev/ramdisk_svc\"),     1u, rm(0x1E8000, ProcessId::FS, 0) },\n         dummyRM,\n         //{ ss(\"/dev/dk_syslog\"),       0u, rm(0x1E8000, ProcessId::MCP, 0) },\n         { ss(\"/dev/df\"),              1u, rm(0x1E8000, ProcessId::FS, 0) },\n         dummyRM,\n         { ss(\"/dev/atfs\"),            1u, rm(0x1E8000, ProcessId::FS, 0) },\n         { ss(\"/dev/isfs\"),            1u, rm(0x1E8000, ProcessId::FS, 0) },\n         { ss(\"/dev/wfs\"),             1u, rm(0x1E8000, ProcessId::FS, 0) },\n         { ss(\"/dev/fat\"),             1u, rm(0x1E8000, ProcessId::FS, 0) },\n         dummyRM,\n         { ss(\"/dev/rbfs\"),            1u, rm(0x1E8000, ProcessId::FS, 0) },\n         dummyRM,\n         { ss(\"/dev/scfm\"),            1u, rm(0x1E8000, ProcessId::FS, 0) },\n         dummyRM,\n         { ss(\"/dev/md\"),              1u, rm(0x1E8000, ProcessId::FS, 0) },\n         { ss(\"/dev/pcfs\"),            1u, rm(0x1E8000, ProcessId::FS, 0) },\n         dummyRM,\n         { ss(\"/dev/mcp\"),             1u, rm(0x1A8000, ProcessId::MCP, 0) },\n         //{ ss(\"/dev/mcp_recovery\"),    0u, rm( 0x40000, ProcessId::MCP, 0) },\n         dummyRM,\n         //{ ss(\"/dev/usbproc2\"),        1u, rm(0x1C0000, ProcessId::USB, 0) },\n         { ss(\"/dev/usr_cfg\"),         1u, rm(0x180000, ProcessId::AUXIL, 0) },\n         //{ ss(\"/dev/usb_hid\"),         1u, rm(0x100000, ProcessId::USB, 0) },\n         //{ ss(\"/dev/usb_uac\"),         1u, rm(0x100000, ProcessId::USB, 0) },\n         //{ ss(\"/dev/usb_midi\"),        1u, rm(0x100000, ProcessId::USB, 0) },\n         dummyRM,\n         { ss(\"/dev/ppc_kernel\"),      1u, rm(0x180000, ProcessId::MCP, 0) },\n         //{ ss(\"/dev/ccr_io\"),          1u, rm(0x1C8000, ProcessId::PAD, 0) },\n         { ss(\"/dev/usb/early_btrm\"),  0u, rm(0x1C0000, ProcessId::PAD, 3) },\n         //{ ss(\"/dev/testproc2\"),       1u, rm(0x1C0000, ProcessId::TEST, 0) },\n         dummyRM,\n         { ss(\"/dev/ums\"),             1u, rm(0x1C0000, ProcessId::USB, 0) }, //  WTF?? Should be FS surely?\n         //{ ss(\"/dev/wifi24\"),          0u, rm(0x188000, ProcessId::PAD, 0) }, // WTF?? Should be NET surely?\n         dummyRM,\n         { ss(\"/dev/auxilproc\"),       1u, rm(0x100000, ProcessId::AUXIL, 1) },\n         { ss(\"/dev/network\"),         1u, rm(0x180000, ProcessId::NET, 0) },\n         dummyRM,\n         { ss(\"/dev/nsec\"),            1u, rm(0x180000, ProcessId::NET, 0) },\n         { ss(\"/dev/usb/btrm\"),        0u, rm(0x1C0000, ProcessId::PAD, 1) },\n         { ss(\"/dev/acpproc\"),         1u, rm(0x188000, ProcessId::ACP, 0) },\n         dummyRM,\n         //{ ss(\"/dev/ifuds\"),           1u, rm(0x100000, ProcessId::PAD, 0) }, // WTF?? Should be NET surely?\n         //{ ss(\"/dev/udscntrl\"),        1u, rm(0x100000, ProcessId::PAD, 0) }, // WTF?? Should be NET surely?\n         dummyRM,\n         { ss(\"/dev/nnsm\"),            1u, rm(0x180000, ProcessId::ACP, 0) },\n         dummyRM,\n         //{ ss(\"/dev/dlp\"),             1u, rm(0x100000, ProcessId::NET, 0) },\n         dummyRM,\n         { ss(\"/dev/ac_main\"),         1u, rm(0x180000, ProcessId::NET, 1) },\n         dummyRM,\n         { ss(\"/dev/tcp_pcfs\"),        1u, rm(0x1E8000, ProcessId::FS, 0) },\n         dummyRM,\n         { ss(\"/dev/act\"),             1u, rm(0x180000, ProcessId::FPD, 1) },\n         dummyRM,\n         //{ ss(\"/dev/fpd\"),             1u, rm(0x180000, ProcessId::FPD, 1) },\n         dummyRM,\n         { ss(\"/dev/acp_main\"),        1u, rm(0x180000, ProcessId::ACP, 1) },\n         dummyRM,\n         { ss(\"/dev/pdm\"),             1u, rm(0x180000, ProcessId::ACP, 1) },\n         dummyRM,\n         { ss(\"/dev/boss\"),            1u, rm(0x180000, ProcessId::NIM, 1) },\n         dummyRM,\n         { ss(\"/dev/nim\"),             1u, rm(0x180000, ProcessId::NIM, 1) },\n         dummyRM,\n         { ss(\"/dev/ndm\"),             1u, rm(0x180000, ProcessId::NET, 1) },\n         dummyRM,\n         //{ ss(\"/dev/emd\"),             1u, rm(0x180000, ProcessId::ACP, 1) },\n         dummyRM,\n         { ss(\"/dev/ppc_app\"),         1u, rm(0x180000, ProcessId::MCP, 2) },\n         dummyRM,\n      };\n}\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_pm_thread.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n\n#include <string_view>\n\nnamespace ios::mcp::internal\n{\n\nError\nstartPmThread();\n\nError\nregisterResourceManager(std::string_view device,\n                        kernel::MessageQueueId queue);\n\nError\nhandleResourceManagerRegistrations(uint32_t systemModeFlags,\n                                   uint32_t bootFlags);\n\nvoid\ninitialiseStaticPmThreadData();\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_ppc_thread.cpp",
    "content": "#include \"ios_mcp_enum.h\"\n#include \"ios_mcp_pm_thread.h\"\n#include \"ios_mcp_ppc_thread.h\"\n\n#include \"cafe/kernel/cafe_kernel.h\"\n#include \"ios/ios_stackobject.h\"\n#include \"ios/kernel/ios_kernel_hardware.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\nnamespace ios::mcp::internal\n{\n\nusing namespace kernel;\n\nusing RegisteredResourceManagerId = int32_t;\n\nconstexpr auto MaxNumPpcMessages = 10u;\n\nconstexpr auto PpcThreadStackSize = 0x2000u;\nconstexpr auto PpcThreadPriority = 123u;\n\nconstexpr auto PpcAppHandle = 0x707061; // 'ppa'\nconstexpr auto PpcKernelHandle = 0x6E726B; // 'nrk'\n\nstruct StaticPpcThreadData\n{\n   be2_val<ThreadId> threadId;\n   be2_array<uint8_t, PpcThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticPpcThreadData>\nsPpcThreadData;\n\nstatic Error\nmcpPpcIoctl(MCPCommand command,\n            phys_ptr<const void> inputBuffer,\n            uint32_t inputLength,\n            phys_ptr<void> outputBuffer,\n            uint32_t outputLength)\n{\n   auto error = Error::OK;\n\n   auto ppcAppCommand = static_cast<PPCAppCommand>(command);\n   switch (ppcAppCommand) {\n   case PPCAppCommand::PowerOff:\n      ios::kernel::internal::stopHardwareThread();\n      break;\n   default:\n      error = Error::InvalidArg;\n   }\n\n   return error;\n}\n\nstatic Error\nppcThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackArray<Message, MaxNumPpcMessages> messageBuffer;\n   StackObject<Message> message;\n\n   // Create message queue\n   auto error = IOS_CreateMessageQueue(messageBuffer, messageBuffer.size());\n   if (error < Error::OK) {\n      return error;\n   }\n   auto messageQueueId = static_cast<MessageQueueId>(error);\n\n   // Register devices\n   error = registerResourceManager(\"/dev/ppc_app\", messageQueueId);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = registerResourceManager(\"/dev/ppc_kernel\", messageQueueId);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   while (true) {\n      error = IOS_ReceiveMessage(messageQueueId,\n                                 message,\n                                 MessageFlags::None);\n      if (error < Error::OK) {\n         break;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      {\n         auto name = std::string_view { request->requestData.args.open.name.get() };\n         error = Error::InvalidArg;\n\n         if (name.compare(\"/dev/ppc_kernel\") == 0) {\n            error = static_cast<Error>(PpcKernelHandle);\n         } else if (name.compare(\"/dev/ppc_app\") == 0) {\n            error = static_cast<Error>(PpcAppHandle);\n         }\n\n         IOS_ResourceReply(request, error);\n         break;\n      }\n\n      case Command::Close:\n      {\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      case Command::Resume:\n      {\n         if (request->requestData.handle == PpcKernelHandle) {\n            // TODO: Until we have proper permission initialisation in IOS for\n            // CafeOS kernel let's just force all permission\n            StackObject<uint64_t> mask;\n            *mask = 0xFFFFFFFFFFFFFFFFull;\n            IOS_SetClientCapabilities(ProcessId::COSKERNEL,\n                                      ResourcePermissionGroup::All,\n                                      mask);\n\n            // Boot the PPC kernel!\n            cafe::kernel::start();\n         }\n\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      case Command::Ioctl:\n      {\n         error = mcpPpcIoctl(static_cast<MCPCommand>(request->requestData.args.ioctl.request),\n                             request->requestData.args.ioctl.inputBuffer,\n                             request->requestData.args.ioctl.inputLength,\n                             request->requestData.args.ioctl.outputBuffer,\n                             request->requestData.args.ioctl.outputLength);\n         IOS_ResourceReply(request, error);\n      }\n\n      default:\n         IOS_ResourceReply(request, Error::InvalidArg);\n      }\n   }\n\n   return error;\n}\n\nError\nstartPpcThread()\n{\n   // Create thread\n   auto error = IOS_CreateThread(&ppcThreadEntry, nullptr,\n                                 phys_addrof(sPpcThreadData->threadStack) + sPpcThreadData->threadStack.size(),\n                                 static_cast<uint32_t>(sPpcThreadData->threadStack.size()),\n                                 PpcThreadPriority,\n                                 ThreadFlags::Detached);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sPpcThreadData->threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sPpcThreadData->threadId, \"PpcThread\");\n\n   return IOS_StartThread(sPpcThreadData->threadId);\n}\n\nvoid\ninitialiseStaticPpcThreadData()\n{\n   sPpcThreadData = allocProcessStatic<StaticPpcThreadData>();\n}\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_ppc_thread.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::mcp::internal\n{\n\nError\nstartPpcThread();\n\nvoid\ninitialiseStaticPpcThreadData();\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_title.cpp",
    "content": "#include \"ios_mcp_config.h\"\n#include \"ios_mcp_title.h\"\n\n#include \"ios/ios_stackobject.h\"\n#include \"ios/auxil/ios_auxil_config.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n\n#include <fmt/format.h>\n\nusing namespace ios::auxil;\nusing namespace ios::kernel;\n\nnamespace ios::mcp::internal\n{\n\nstruct StaticTitleData\n{\n   be2_struct<MCPPPrepareTitleInfo> prepareTitleInfoBuffer;\n};\n\nstatic phys_ptr<StaticTitleData>\nsTitleData = nullptr;\n\nstatic MCPError\nreadTitleConfigItems(std::string_view path,\n                     phys_ptr<UCItem> items,\n                     uint32_t count)\n{\n   return translateUCError(readItemsFromFile(path, items, count, nullptr));\n}\n\nphys_ptr<MCPPPrepareTitleInfo>\ngetPrepareTitleInfoBuffer()\n{\n   return phys_addrof(sTitleData->prepareTitleInfoBuffer);\n}\n\nMCPError\nreadTitleAppXml(phys_ptr<MCPTitleAppXml> titleInfo)\n{\n   StackArray<UCItem, 10> items;\n   items[0].name = \"app\";\n   items[0].access = 0x777u;\n   items[0].dataType = UCDataType::Complex;\n\n   items[1].name = \"app.version\";\n   items[1].dataType = UCDataType::UnsignedInt;\n   items[1].dataSize = 4u;\n   items[1].data = phys_addrof(titleInfo->version);\n\n   items[2].name = \"app.os_version\";\n   items[2].dataType = UCDataType::HexBinary;\n   items[2].dataSize = 8u;\n   items[2].data = phys_addrof(titleInfo->os_version);\n\n   items[3].name = \"app.title_id\";\n   items[3].dataType = UCDataType::HexBinary;\n   items[3].dataSize = 8u;\n   items[3].data = phys_addrof(titleInfo->title_id);\n\n   items[4].name = \"app.title_version\";\n   items[4].dataType = UCDataType::HexBinary;\n   items[4].dataSize = 2u;\n   items[4].data = phys_addrof(titleInfo->title_version);\n\n   items[5].name = \"app.sdk_version\";\n   items[5].dataType = UCDataType::UnsignedInt;\n   items[5].dataSize = 4u;\n   items[5].data = phys_addrof(titleInfo->sdk_version);\n\n   items[6].name = \"app.app_type\";\n   items[6].dataType = UCDataType::UnsignedInt;\n   items[6].dataSize = 4u;\n   items[6].data = phys_addrof(titleInfo->app_type);\n\n   items[7].name = \"app.group_id\";\n   items[7].dataType = UCDataType::UnsignedInt;\n   items[7].dataSize = 4u;\n   items[7].data = phys_addrof(titleInfo->group_id);\n\n   items[8].name = \"app.os_mask\";\n   items[8].dataType = UCDataType::HexBinary;\n   items[8].dataSize = 32u;\n   items[8].data = phys_addrof(titleInfo->os_mask);\n\n   items[9].name = \"app.common_id\";\n   items[9].dataType = UCDataType::HexBinary;\n   items[9].dataSize = 8u;\n   items[9].data = phys_addrof(titleInfo->common_id);\n\n   auto error = readTitleConfigItems(\"/vol/code/app.xml\", items, items.size());\n   if (error < MCPError::OK && error != MCPError::KeyNotFound) {\n      // KeyNotFound is allowed because not all fields are required in xml\n      return error;\n   }\n\n   return MCPError::OK;\n}\n\nMCPError\nreadTitleCosXml(phys_ptr<MCPPPrepareTitleInfo> titleInfo)\n{\n   StackArray<UCItem, 60> items;\n   items[0].name = \"app\";\n   items[0].access = 0x777u;\n   items[0].dataType = UCDataType::Complex;\n\n   items[1].name = \"app.version\";\n   items[1].dataType = UCDataType::UnsignedInt;\n   items[1].dataSize = 4u;\n   items[1].data = phys_addrof(titleInfo->version);\n\n   items[2].name = \"app.cmdFlags\";\n   items[2].dataType = UCDataType::UnsignedInt;\n   items[2].dataSize = 4u;\n   items[2].data = phys_addrof(titleInfo->cmdFlags);\n\n   items[3].name = \"app.argstr\";\n   items[3].dataType = UCDataType::String;\n   items[3].dataSize = 4096u;\n   items[3].data = phys_addrof(titleInfo->argstr);\n\n   items[4].name = \"app.max_size\";\n   items[4].dataType = UCDataType::HexBinary;\n   items[4].dataSize = 4u;\n   items[4].data = phys_addrof(titleInfo->max_size);\n\n   items[5].name = \"app.avail_size\";\n   items[5].dataType = UCDataType::HexBinary;\n   items[5].dataSize = 4u;\n   items[5].data = phys_addrof(titleInfo->avail_size);\n\n   items[6].name = \"app.codegen_size\";\n   items[6].dataType = UCDataType::HexBinary;\n   items[6].dataSize = 4u;\n   items[6].data = phys_addrof(titleInfo->codegen_size);\n\n   items[7].name = \"app.codegen_core\";\n   items[7].dataType = UCDataType::HexBinary;\n   items[7].dataSize = 4u;\n   items[7].data = phys_addrof(titleInfo->codegen_core);\n\n   items[8].name = \"app.max_codesize\";\n   items[8].dataType = UCDataType::HexBinary;\n   items[8].dataSize = 4u;\n   items[8].data = phys_addrof(titleInfo->max_codesize);\n\n   items[9].name = \"app.overlay_arena\";\n   items[9].dataType = UCDataType::HexBinary;\n   items[9].dataSize = 4u;\n   items[9].data = phys_addrof(titleInfo->overlay_arena);\n\n   items[10].name = \"app.num_workarea_heap_blocks\";\n   items[10].dataType = UCDataType::UnsignedInt;\n   items[10].dataSize = 4u;\n   items[10].data = phys_addrof(titleInfo->num_workarea_heap_blocks);\n\n   items[11].name = \"app.num_codearea_heap_blocks\";\n   items[11].dataType = UCDataType::UnsignedInt;\n   items[11].dataSize = 4u;\n   items[11].data = phys_addrof(titleInfo->num_codearea_heap_blocks);\n\n   items[12].name = \"app.permissions\";\n   items[12].dataType = UCDataType::Complex;\n\n   for (auto i = 0u; i <= 18; ++i) {\n      auto &mask = items[13 + (i * 2)];\n      *fmt::format_to(phys_addrof(mask.name).get(),\n                      \"app.permissions.p{}.mask\", i) = char { 0 };\n      mask.dataType = UCDataType::HexBinary;\n      mask.dataSize = 8u;\n      mask.data = phys_addrof(titleInfo->permissions[i].mask);\n\n      auto &group = items[14 + (i * 2)];\n      *fmt::format_to(phys_addrof(group.name).get(),\n                      \"app.permissions.p{}.group\", i) = char { 0 };\n      group.dataType = UCDataType::UnsignedInt;\n      group.dataSize = 4u;\n      group.data = phys_addrof(titleInfo->permissions[i].group);\n   }\n\n   items[51].name = \"app.default_stack0_size\";\n   items[51].dataType = UCDataType::HexBinary;\n   items[51].dataSize = 4u;\n   items[51].data = phys_addrof(titleInfo->default_stack0_size);\n\n   items[52].name = \"app.default_stack1_size\";\n   items[52].dataType = UCDataType::HexBinary;\n   items[52].dataSize = 4u;\n   items[52].data = phys_addrof(titleInfo->default_stack1_size);\n\n   items[53].name = \"app.default_stack2_size\";\n   items[53].dataType = UCDataType::HexBinary;\n   items[53].dataSize = 4u;\n   items[53].data = phys_addrof(titleInfo->default_stack2_size);\n\n   items[54].name = \"app.default_redzone0_size\";\n   items[54].dataType = UCDataType::HexBinary;\n   items[54].dataSize = 4u;\n   items[54].data = phys_addrof(titleInfo->default_redzone0_size);\n\n   items[55].name = \"app.default_redzone1_size\";\n   items[55].dataType = UCDataType::HexBinary;\n   items[55].dataSize = 4u;\n   items[55].data = phys_addrof(titleInfo->default_redzone1_size);\n\n   items[56].name = \"app.default_redzone2_size\";\n   items[56].dataType = UCDataType::HexBinary;\n   items[56].dataSize = 4u;\n   items[56].data = phys_addrof(titleInfo->default_redzone2_size);\n\n   items[57].name = \"app.exception_stack0_size\";\n   items[57].dataType = UCDataType::HexBinary;\n   items[57].dataSize = 4u;\n   items[57].data = phys_addrof(titleInfo->exception_stack0_size);\n\n   items[58].name = \"app.exception_stack1_size\";\n   items[58].dataType = UCDataType::HexBinary;\n   items[58].dataSize = 4u;\n   items[58].data = phys_addrof(titleInfo->exception_stack1_size);\n\n   items[59].name = \"app.exception_stack2_size\";\n   items[59].dataType = UCDataType::HexBinary;\n   items[59].dataSize = 4u;\n   items[59].data = phys_addrof(titleInfo->exception_stack2_size);\n\n   auto error = readTitleConfigItems(\"/vol/code/cos.xml\", items, items.size());\n   if (error < MCPError::OK && error != MCPError::KeyNotFound) {\n      // KeyNotFound is allowed because not all fields are required in xml\n      return error;\n   }\n\n   return MCPError::OK;\n}\n\nvoid\ninitialiseTitleStaticData()\n{\n   sTitleData = allocProcessStatic<StaticTitleData>();\n}\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/mcp/ios_mcp_title.h",
    "content": "#pragma once\n#include \"ios_mcp_enum.h\"\n#include \"ios_mcp_mcp_types.h\"\n\nnamespace ios::mcp::internal\n{\n\nphys_ptr<MCPPPrepareTitleInfo>\ngetPrepareTitleInfoBuffer();\n\nMCPError\nreadTitleAppXml(phys_ptr<MCPTitleAppXml> titleInfo);\n\nMCPError\nreadTitleCosXml(phys_ptr<MCPPPrepareTitleInfo> titleInfo);\n\nvoid\ninitialiseTitleStaticData();\n\n} // namespace ios::mcp::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net.cpp",
    "content": "#include \"ios_net.h\"\n#include \"ios_net_ac_main_server.h\"\n#include \"ios_net_log.h\"\n#include \"ios_net_ndm_server.h\"\n#include \"ios_net_subsys.h\"\n#include \"ios_net_socket_async_task.h\"\n#include \"ios_net_socket_thread.h\"\n\n#include \"decaf_log.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/mcp/ios_mcp_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <common/log.h>\n\nusing namespace ios::kernel;\n\nnamespace ios::net\n{\n\nusing namespace kernel;\nusing namespace mcp;\n\nconstexpr auto LocalHeapSize = 0x40000u;\nconstexpr auto CrossHeapSize = 0xC0000u;\n\nconstexpr auto NumNetworkMessages = 10u;\n\nstruct StaticNetData\n{\n   be2_val<bool> subsysStarted;\n};\n\nstatic phys_ptr<StaticNetData>\nsData = nullptr;\n\nstatic phys_ptr<void>\nsLocalHeapBuffer = nullptr;\n\nnamespace internal\n{\n\nLogger netLog = { };\n\nstatic void\ninitialiseStaticData()\n{\n   sData = allocProcessStatic<StaticNetData>();\n   sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize);\n}\n\nstatic Error\nnetworkLoop()\n{\n   StackArray<Message, NumNetworkMessages> messageBuffer;\n   StackObject<Message> message;\n\n   // Create message queue\n   auto error = IOS_CreateMessageQueue(messageBuffer, messageBuffer.size());\n   if (error < Error::OK) {\n      netLog->error(\"NET: Failed to create message queue, error = {}.\", error);\n      return error;\n   }\n\n   auto messageQueueId = static_cast<MessageQueueId>(error);\n\n   // Register resource manager\n   error = MCP_RegisterResourceManager(\"/dev/network\", messageQueueId);\n   if (error < Error::OK) {\n      netLog->error(\"NET: Failed to register resource manager for /dev/network, error = {}.\", error);\n      return error;\n   }\n\n   while (true) {\n      error = IOS_ReceiveMessage(messageQueueId, message, MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      case Command::Close:\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      case Command::Resume:\n         if (!sData->subsysStarted) {\n            error = internal::startSubsys();\n         } else {\n            error = Error::OK;\n         }\n\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      case Command::Suspend:\n         if (sData->subsysStarted) {\n            error = internal::stopSubsys();\n         } else {\n            error = Error::OK;\n         }\n\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      default:\n         IOS_ResourceReply(request, Error::InvalidArg);\n      }\n   }\n\n   IOS_DestroyMessageQueue(messageQueueId);\n   return Error::OK;\n}\n\n} // namespace internal\n\nError\nprocessEntryPoint(phys_ptr<void> /* context */)\n{\n   // Initialise logger\n   internal::netLog = decaf::makeLogger(\"IOS_NET\");\n\n   // Initialise static memory\n   internal::initialiseStaticData();\n   internal::initialiseStaticAcMainServerData();\n   internal::initialiseStaticNdmServerData();\n   internal::initialiseStaticSocketData();\n   internal::initialiseStaticSocketAsyncTaskData();\n   internal::initialiseStaticSubsysData();\n\n   // Initialise process heaps\n   auto error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize);\n   if (error < Error::OK) {\n      internal::netLog->error(\"NET: Failed to create local process heap, error = {}.\", error);\n      return error;\n   }\n\n   error = IOS_CreateCrossProcessHeap(CrossHeapSize);\n   if (error < Error::OK) {\n      internal::netLog->error(\"NET: Failed to create cross process heap, error = {}.\", error);\n      return error;\n   }\n\n   // TODO: bspGetClockInfo\n   // TODO: initIoBuf\n   // TODO: start wifi24 thread (/dev/wifi24)\n   // TODO: start uds threads (/dev/ifuds /dev/udscntrl)\n   // TODO: start nn servers for /dev/dlp\n\n   error = internal::startNdmServer();\n   if (error < Error::OK) {\n      internal::netLog->error(\"NET: Failed to start ndm server, error = {}.\", error);\n      return error;\n   }\n\n   error = internal::startAcMainServer();\n   if (error < Error::OK) {\n      internal::netLog->error(\"NET: Failed to start ac_main server, error = {}.\", error);\n      return error;\n   }\n\n   error = internal::startSocketAsyncTaskThread();\n   if (error < Error::OK) {\n      internal::netLog->error(\"NET: Failed to initialise socket async task thread, error = {}.\", error);\n      return error;\n   }\n\n   error = internal::initSubsys();\n   if (error < Error::OK) {\n      internal::netLog->error(\"NET: Failed to initialise subsystem, error = {}.\", error);\n      return error;\n   }\n\n   error = internal::networkLoop();\n   if (error < Error::OK) {\n      internal::netLog->error(\"NET: networkLoop returned error = {}.\", error);\n      return error;\n   }\n\n   error = internal::joinAcMainServer();\n   if (error < Error::OK) {\n      internal::netLog->error(\"NET: Failed to join ac_main server, error = {}.\", error);\n      return error;\n   }\n\n   return IOS_SuspendThread(IOS_GetCurrentThreadId());\n}\n\n} // namespace ios::net\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_process.h\"\n\nnamespace ios::net\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::net\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_ac_main_server.cpp",
    "content": "#include \"ios_net_log.h\"\n#include \"ios_net_ac_main_server.h\"\n#include \"ios_net_ac_service.h\"\n\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/nn/ios_nn_ipc_server.h\"\n\nnamespace ios::net::internal\n{\n\nusing namespace kernel;\n\nconstexpr auto AcMainNumMessages = 0x64u;\nconstexpr auto AcMainThreadStackSize = 0x2000u;\nconstexpr auto AcMainThreadPriority = 50u;\n\nclass AcMainServer : public nn::ipc::Server\n{\npublic:\n   AcMainServer() :\n      nn::ipc::Server(true)\n   {\n   }\n};\n\nstruct StaticAcMainServerData\n{\n   be2_struct<AcMainServer> server;\n   be2_array<Message, AcMainNumMessages> messageBuffer;\n   be2_array<uint8_t, AcMainThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticAcMainServerData> sAcMainServerData = nullptr;\n\nError\nstartAcMainServer()\n{\n   auto &server = sAcMainServerData->server;\n   auto result = server.initialise(\"/dev/ac_main\",\n                                   phys_addrof(sAcMainServerData->messageBuffer),\n                                   static_cast<uint32_t>(sAcMainServerData->messageBuffer.size()));\n   if (result.failed()) {\n      netLog->error(\n         \"startAcMainServer: Server initialisation failed for /dev/ac_main, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   server.registerService<AcService>();\n\n   result = server.start(phys_addrof(sAcMainServerData->threadStack) + sAcMainServerData->threadStack.size(),\n                         static_cast<uint32_t>(sAcMainServerData->threadStack.size()),\n                         AcMainThreadPriority);\n   if (result.failed()) {\n      netLog->error(\n         \"startAcMainServer: Server start failed for /dev/ac_main, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   return Error::OK;\n}\n\nError\njoinAcMainServer()\n{\n   sAcMainServerData->server.join();\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticAcMainServerData()\n{\n   sAcMainServerData = allocProcessStatic<StaticAcMainServerData>();\n}\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_ac_main_server.h",
    "content": "#pragma once\n#include \"ios/ios_error.h\"\n\nnamespace ios::net::internal\n{\n\nError\nstartAcMainServer();\n\nError\njoinAcMainServer();\n\nvoid\ninitialiseStaticAcMainServerData();\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_ac_service.cpp",
    "content": "#include \"ios_net_ac_service.h\"\n\n#include \"ios/nn/ios_nn_ipc_server_command.h\"\n#include \"nn/ac/nn_ac_result.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n\n#include <chrono>\n#include <ctime>\n#include <uv.h>\n\nusing namespace nn::ac;\n\nusing nn::ipc::CommandHandlerArgs;\nusing nn::ipc::CommandId;\nusing nn::ipc::OutBuffer;\nusing nn::ipc::ServerCommand;\n\nnamespace ios::net::internal\n{\n\nstatic nn::Result\ninitialise(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<AcService::Initialise> { args };\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\nfinalise(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<AcService::Finalise> { args };\n   return nn::ResultSuccess;\n}\n\nstatic nn::Result\ngetAssignedAddress(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<AcService::GetAssignedAddress> { args };\n   auto unkParamater = uint32_t {};\n   command.ReadRequest(unkParamater);\n\n   auto ipAddress = static_cast<uint32_t>((127u << 24) | (0u << 16) | (0u << 8) | 1u);\n   command.WriteResponse(ipAddress);\n   return nn::ResultSuccess;\n}\n\nnn::Result\nAcService::commandHandler(uint32_t unk1,\n                          CommandId command,\n                          CommandHandlerArgs &args)\n{\n   switch (command) {\n   case Initialise::command:\n      return initialise(args);\n   case Finalise::command:\n      return finalise(args);\n   case GetAssignedAddress::command:\n      return getAssignedAddress(args);\n   default:\n      return nn::ipc::ResultInvalidMethodTag;\n   }\n}\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_ac_service.h",
    "content": "#pragma once\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"nn/ac/nn_ac_service.h\"\n\nnamespace ios::net::internal\n{\n\nstruct AcService : ::nn::ac::services::AcService\n{\n   static nn::Result\n   commandHandler(uint32_t unk1,\n                  nn::ipc::CommandId command,\n                  nn::ipc::CommandHandlerArgs &args);\n};\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_enum.h",
    "content": "#ifndef IOS_NET_ENUM_H\n#define IOS_NET_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\n\nENUM_NAMESPACE_ENTER(net)\n\nENUM_BEG(SocketCommand, uint32_t)\n   ENUM_VALUE(Accept,                  0x1)\n   ENUM_VALUE(Bind,                    0x2)\n   ENUM_VALUE(Close,                   0x3)\n   ENUM_VALUE(Connect,                 0x4)\n   ENUM_VALUE(GetPeerName,             0x6)\n   ENUM_VALUE(GetSockName,             0x7)\n   ENUM_VALUE(GetSockOpt,              0x8)\n   ENUM_VALUE(SetSockOpt,              0x9)\n   ENUM_VALUE(Listen,                  0xA)\n   ENUM_VALUE(Recv,                    0xC)\n   ENUM_VALUE(RecvFrom,                0xD)\n   ENUM_VALUE(Send,                    0xE)\n   ENUM_VALUE(SendTo,                  0xF)\n   ENUM_VALUE(Shutdown,                0x10)\n   ENUM_VALUE(Socket,                  0x11)\n   ENUM_VALUE(DnsQuery,                0x26)\n   ENUM_VALUE(Select,                  0x27)\n   ENUM_VALUE(SimplePing,              0x28)\n   ENUM_VALUE(SimplePingResult,        0x29)\n   ENUM_VALUE(GetProcessSocketHandle,  0x2A)\n   ENUM_VALUE(CloseAll,                0x2D)\n   ENUM_VALUE(GetProxyConfig,          0x2E)\n   ENUM_VALUE(GetOpt,                  0x2F)\n   ENUM_VALUE(SetOpt,                  0x30)\n   ENUM_VALUE(ClearResolverCache,      0x32)\n   ENUM_VALUE(SendToMulti,             0x33)\n   ENUM_VALUE(RecvFromMulti,           0x35)\n   ENUM_VALUE(SendToMultiEx,           0x36)\n   ENUM_VALUE(RecvFromEx,              0x38)\nENUM_END(SocketCommand)\n\nENUM_BEG(SocketDnsQueryType, uint8_t)\n   ENUM_VALUE(GetHostByName,           1)\nENUM_END(SocketDnsQueryType)\n\nENUM_BEG(SocketError, uint32_t)\n   ENUM_VALUE(OK,                0)\n   ENUM_VALUE(NoBufs,            1)\n   ENUM_VALUE(TimedOut,          2)\n   ENUM_VALUE(IsConn,            3)\n   ENUM_VALUE(OpNotSupp,         4)\n   ENUM_VALUE(ConnAborted,       5)\n   ENUM_VALUE(WouldBlock,        6)\n   ENUM_VALUE(ConnRefused,       7)\n   ENUM_VALUE(ConnReset,         8)\n   ENUM_VALUE(NotConn,           9)\n   ENUM_VALUE(Already,           10)\n   ENUM_VALUE(Inval,             11)\n   ENUM_VALUE(MsgSize,           12)\n   ENUM_VALUE(Pipe,              13)\n   ENUM_VALUE(DestAddrReq,       14)\n   ENUM_VALUE(Shutdown,          15)\n   ENUM_VALUE(NoProtoOpt,        16)\n   ENUM_VALUE(HaveOob,           17)\n   ENUM_VALUE(NoMem,             18)\n   ENUM_VALUE(AddrNotAvail,      19)\n   ENUM_VALUE(AddrInUse,         20)\n   ENUM_VALUE(AfNoSupport,       21)\n   ENUM_VALUE(InProgress,        22)\n   ENUM_VALUE(Lower,             23)\n   ENUM_VALUE(NotSock,           24)\n   ENUM_VALUE(Eieio,             27)\n   ENUM_VALUE(TooManyRefs,       28)\n   ENUM_VALUE(Fault,             29)\n   ENUM_VALUE(NetUnreach,        30)\n   ENUM_VALUE(ProtoNoSupport,    31)\n   ENUM_VALUE(Prototype,         32)\n   ENUM_VALUE(GenericError,      41)\n   ENUM_VALUE(NoLibRm,           42)\n   ENUM_VALUE(NotInitialised,    43)\n   ENUM_VALUE(Busy,              44)\n   ENUM_VALUE(Unknown,           45)\n   ENUM_VALUE(NoResources,       48)\n   ENUM_VALUE(BadFd,             49)\n   ENUM_VALUE(Aborted,           50)\n   ENUM_VALUE(MFile,             51)\nENUM_END(SocketError)\n\nENUM_BEG(SocketFamily, uint32_t)\n   ENUM_VALUE(Inet,              0x2)\nENUM_END(SocketFamily)\n\nENUM_NAMESPACE_EXIT(net)\n\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_NET_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_log.h",
    "content": "#pragma once\n#include <common/log.h>\n\nnamespace ios::net::internal\n{\n\nextern Logger netLog;\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_ndm_server.cpp",
    "content": "#include \"ios_net_log.h\"\n#include \"ios_net_ndm_server.h\"\n\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/nn/ios_nn_ipc_server.h\"\n\nnamespace ios::net::internal\n{\n\nusing namespace kernel;\n\nconstexpr auto NdmNumMessages = 0x64u;\nconstexpr auto NdmThreadStackSize = 0x2000u;\nconstexpr auto NdmThreadPriority = 45u;\n\nclass NdmServer : public nn::ipc::Server\n{\npublic:\n   NdmServer() :\n      nn::ipc::Server(true)\n   {\n   }\n};\n\nstruct StaticNdmServerData\n{\n   be2_struct<NdmServer> server;\n   be2_array<Message, NdmNumMessages> messageBuffer;\n   be2_array<uint8_t, NdmThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticNdmServerData> sNdmServerData = nullptr;\n\nError\nstartNdmServer()\n{\n   auto &server = sNdmServerData->server;\n   auto result = server.initialise(\"/dev/ndm\",\n                                   phys_addrof(sNdmServerData->messageBuffer),\n                                   static_cast<uint32_t>(sNdmServerData->messageBuffer.size()));\n   if (result.failed()) {\n      netLog->error(\n         \"startNdmServer: Server initialisation failed for /dev/ndm, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   // TODO: Register service 0, 1, 2\n\n   result = server.start(phys_addrof(sNdmServerData->threadStack) + sNdmServerData->threadStack.size(),\n                         static_cast<uint32_t>(sNdmServerData->threadStack.size()),\n                         NdmThreadPriority);\n   if (result.failed()) {\n      netLog->error(\n         \"startNdmServer: Server start failed for /dev/ndm, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   return Error::OK;\n}\n\nError\njoinNdmServer()\n{\n   sNdmServerData->server.join();\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticNdmServerData()\n{\n   sNdmServerData = allocProcessStatic<StaticNdmServerData>();\n}\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_ndm_server.h",
    "content": "#pragma once\n#include \"ios/ios_error.h\"\n\nnamespace ios::net::internal\n{\n\nError\nstartNdmServer();\n\nError\njoinNdmServer();\n\nvoid\ninitialiseStaticNdmServerData();\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_socket.h",
    "content": "#pragma once\n#include \"ios_net_enum.h\"\n#include \"ios_net_socket_request.h\"\n#include \"ios_net_socket_response.h\"\n#include \"ios_net_socket_types.h\"\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_socket_async_task.cpp",
    "content": "#include \"ios_net_socket_async_task.h\"\n\n#include \"ios/kernel/ios_kernel_hardware.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_handlemanager.h\"\n#include \"ios/ios_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <mutex>\n#include <optional>\n#include <vector>\n\nusing namespace ios::kernel;\nusing ios::kernel::internal::setInterruptAhbAll;\n\nnamespace ios::net::internal\n{\n\nstruct CompletedTask\n{\n   phys_ptr<ResourceRequest> resourceRequest;\n   Error status;\n};\n\nstruct StaticSocketAsyncTaskData\n{\n   be2_val<MessageQueueId> messageQueue;\n   be2_array<Message, 64> messageBuffer;\n\n   be2_val<ThreadId> thread;\n   be2_array<uint8_t, 0x1000> threadStack;\n};\n\nstatic std::mutex sCompletedTasksMutex;\nstatic std::vector<CompletedTask> sCompletedTasks;\nstatic phys_ptr<StaticSocketAsyncTaskData> sSocketAsyncTaskData = nullptr;\n\nvoid\ncompleteSocketTask(phys_ptr<ResourceRequest> resourceRequest,\n                   std::optional<Error> result)\n{\n   if (result.has_value()) {\n      sCompletedTasksMutex.lock();\n      sCompletedTasks.push_back({ resourceRequest, result.value() });\n      sCompletedTasksMutex.unlock();\n      setInterruptAhbAll(AHBALL::get(0).Wireless80211(true));\n   }\n}\n\nstatic Error\nsocketAsyncTaskThread(phys_ptr<void> /*unused*/)\n{\n   StackObject<Message> message;\n   auto error = IOS_HandleEvent(DeviceId::Wireless80211,\n                                sSocketAsyncTaskData->messageQueue,\n                                Message { 1 });\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = IOS_ClearAndEnable(DeviceId::Wireless80211);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   while (true) {\n      error = IOS_ReceiveMessage(sSocketAsyncTaskData->messageQueue,\n                                 message, MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      if (*message == 1) {\n         auto completedTasks = std::vector<CompletedTask> { };\n\n         sCompletedTasksMutex.lock();\n         completedTasks.swap(sCompletedTasks);\n         sCompletedTasksMutex.unlock();\n\n         for (auto &task : completedTasks) {\n            IOS_ResourceReply(task.resourceRequest,\n                              static_cast<Error>(task.status));\n         }\n\n         IOS_ClearAndEnable(DeviceId::Wireless80211);\n      }\n   }\n}\n\nError\nstartSocketAsyncTaskThread()\n{\n   auto error = IOS_CreateMessageQueue(phys_addrof(sSocketAsyncTaskData->messageBuffer),\n                                       static_cast<uint32_t>(sSocketAsyncTaskData->messageBuffer.size()));\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sSocketAsyncTaskData->messageQueue = static_cast<MessageQueueId>(error);\n\n   error = IOS_CreateThread(socketAsyncTaskThread,\n                            nullptr,\n                            phys_addrof(sSocketAsyncTaskData->threadStack) + sSocketAsyncTaskData->threadStack.size(),\n                            static_cast<uint32_t>(sSocketAsyncTaskData->threadStack.size()),\n                            80,\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sSocketAsyncTaskData->thread = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sSocketAsyncTaskData->thread, \"SocketAsyncTaskThread\");\n\n   error = IOS_StartThread(sSocketAsyncTaskData->thread);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   IOS_YieldCurrentThread();\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticSocketAsyncTaskData()\n{\n   sSocketAsyncTaskData = allocProcessStatic<StaticSocketAsyncTaskData>();\n}\n\n} // namespace ios::net\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_socket_async_task.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/ios_enum.h\"\n\n#include <optional>\n\nnamespace ios::net::internal\n{\n\nError\nstartSocketAsyncTaskThread();\n\nvoid\ncompleteSocketTask(phys_ptr<kernel::ResourceRequest> resourceRequest,\n                   std::optional<Error> result);\n\nvoid\ninitialiseStaticSocketAsyncTaskData();\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_socket_device.cpp",
    "content": "#include \"ios_net_log.h\"\n#include \"ios_net_socket_async_task.h\"\n#include \"ios_net_socket_device.h\"\n\n#include \"ios/kernel/ios_kernel_hardware.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n\n#include \"ios/ios_error.h\"\n#include \"ios/ios_network_thread.h\"\n\n#include <atomic>\n#include <ares.h>\n#include <common/platform.h>\n#include <common/strutils.h>\n#include <libcpu/cpu_formatters.h>\n#include <functional>\n#include <mutex>\n#include <uv.h>\n#include <vector>\n\nusing namespace ios::kernel;\nusing ios::kernel::internal::setInterruptAhbAll;\nusing ios::internal::networkUvLoop;\n\nnamespace ios::net::internal\n{\n\nstatic constexpr int SO_AF_UNSPEC = 0;\nstatic constexpr int SO_AF_INET = 2;\n\nstatic constexpr int SO_SOCK_STREAM = 1;\nstatic constexpr int SO_SOCK_DGRAM = 2;\n\nstatic constexpr int SO_IPPROTO_IP = 0;\nstatic constexpr int SO_IPPROTO_TCP = 6;\nstatic constexpr int SO_IPPROTO_UDP = 17;\n\nstatic constexpr int SO_MSG_PEEK = 0x2;\nstatic constexpr int SO_MSG_DONTWAIT = 0x20;\n\nstatic void *\nresourceRequestToHandleData(phys_ptr<ResourceRequest> resourceRequest)\n{\n   return reinterpret_cast<void *>(\n      static_cast<uintptr_t>(\n         static_cast<uint32_t>(\n            phys_cast<phys_addr>(resourceRequest))));\n}\n\nstatic phys_ptr<ResourceRequest>\nhandleDataToResourceRequest(void *data)\n{\n   return phys_cast<ResourceRequest *>(\n      static_cast<phys_addr>(\n         static_cast<uint32_t>(\n            reinterpret_cast<uintptr_t>(data))));\n}\n\nstd::optional<Error>\nSocketDevice::accept(phys_ptr<ResourceRequest> resourceRequest,\n                     SocketHandle fd,\n                     phys_ptr<SocketAddrIn> sockAddr,\n                     int32_t sockAddrLen)\n{\n   return makeError(ErrorCategory::Socket, SocketError::Inval);\n}\n\nstd::optional<Error>\nSocketDevice::bind(phys_ptr<ResourceRequest> resourceRequest,\n                   SocketHandle fd,\n                   phys_ptr<SocketAddrIn> sockAddr,\n                   int32_t sockAddrLen)\n{\n   auto socket = getSocket(fd);\n   if (!socket) {\n      return makeError(ErrorCategory::Socket, SocketError::BadFd);\n   }\n\n   auto addr = sockaddr_in { 0 };\n   addr.sin_addr.s_addr = sockAddr->sin_addr.s_addr_;\n   addr.sin_family = sockAddr->sin_family;\n   addr.sin_port = htons(sockAddr->sin_port);\n\n   auto result = 0;\n   if (socket->type == Socket::Tcp) {\n      result = uv_tcp_bind(reinterpret_cast<uv_tcp_t *>(socket->handle.get()),\n                           reinterpret_cast<sockaddr *>(&addr), 0);\n   } else if (socket->type == Socket::Udp) {\n      result = uv_udp_bind(reinterpret_cast<uv_udp_t *>(socket->handle.get()),\n                           reinterpret_cast<sockaddr *>(&addr), 0);\n   }\n\n   if (result != 0) {\n      return makeError(ErrorCategory::Socket, SocketError::GenericError);\n   }\n\n   return Error::OK;\n}\n\nstd::optional<Error>\nSocketDevice::createSocket(phys_ptr<ResourceRequest> resourceRequest,\n                           int32_t family,\n                           int32_t type,\n                           int32_t proto)\n{\n   auto fd = SocketHandle { -1 };\n   for (auto i = 0u; i < mSockets.size(); ++i) {\n      if (!mSockets[i].type) {\n         fd = i + 1;\n         break;\n      }\n   }\n\n   if (fd == -1) {\n      return makeError(ErrorCategory::Socket, SocketError::MFile);\n   }\n\n   if (family != SO_AF_INET) {\n      return makeError(ErrorCategory::Socket, SocketError::AfNoSupport);\n   }\n\n   if (type == SO_SOCK_STREAM) {\n      if (proto != SO_IPPROTO_IP && proto != SO_IPPROTO_TCP) {\n         return makeError(ErrorCategory::Socket, SocketError::ProtoNoSupport);\n      }\n\n      auto handle = std::make_unique<uv_tcp_t>();\n      if (uv_tcp_init(networkUvLoop(), handle.get()) != 0) {\n         return makeError(ErrorCategory::Socket, SocketError::GenericError);\n      }\n\n      auto &socket = mSockets[fd - 1];\n      socket.device = this;\n      socket.type = Socket::Tcp;\n      handle->data = &socket;\n      socket.handle.reset(reinterpret_cast<uv_handle_t *>(handle.release()));\n   } else if (type == SO_SOCK_DGRAM) {\n      if (proto != SO_IPPROTO_IP && proto != SO_IPPROTO_UDP) {\n         return makeError(ErrorCategory::Socket, SocketError::ProtoNoSupport);\n      }\n\n      auto handle = std::make_unique<uv_udp_t>();\n      if (uv_udp_init(networkUvLoop(), handle.get()) != 0) {\n         return makeError(ErrorCategory::Socket, SocketError::GenericError);\n      }\n\n      auto &socket = mSockets[fd - 1];\n      socket.device = this;\n      socket.type = Socket::Udp;\n      handle->data = &socket;\n      socket.handle.reset(reinterpret_cast<uv_handle_t *>(handle.release()));\n   } else {\n      return makeError(ErrorCategory::Socket, SocketError::ProtoNoSupport);\n   }\n\n   return static_cast<Error>(fd);\n}\n\nvoid\nSocketDevice::uvCloseSocketCallback(uv_handle_t *handle)\n{\n   auto socket = reinterpret_cast<SocketDevice::Socket *>(handle->data);\n\n   // Abort any pending read requests\n   for (auto readRequest : socket->pendingReads) {\n      completeSocketTask(readRequest,\n                         makeError(ErrorCategory::Socket, SocketError::Aborted));\n   }\n   socket->pendingReads.clear();\n\n   // Complete the close request\n   completeSocketTask(socket->closeRequest,\n                      makeError(ErrorCategory::Socket, SocketError::OK));\n   socket->closeRequest = nullptr;\n\n   // Ensure that we never dangle any unanswered ResourceRequests!\n   decaf_check(socket->connectRequest == nullptr);\n   decaf_check(socket->closeRequest == nullptr);\n   decaf_check(socket->pendingReads.empty());\n   decaf_check(socket->pendingWrites.empty());\n\n   *socket = SocketDevice::Socket {};\n}\n\nstd::optional<Error>\nSocketDevice::closeSocket(phys_ptr<ResourceRequest> resourceRequest,\n                          SocketHandle fd)\n{\n   auto socket = getSocket(fd);\n   if (!socket) {\n      return makeError(ErrorCategory::Socket, SocketError::BadFd);\n   }\n\n   socket->closeRequest = resourceRequest;\n   uv_close(socket->handle.get(), uvCloseSocketCallback);\n   return {};\n}\n\nstatic void\nuvReadAllocCallback(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)\n{\n   buf->base = new char[suggested_size];\n   buf->len = static_cast<decltype(buf->len)>(suggested_size);\n}\n\nstatic void\nuvReadCallback(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)\n{\n   auto socket = reinterpret_cast<SocketDevice::Socket *>(stream->data);\n\n   if (nread >= 0) {\n      socket->readBuffer.insert(socket->readBuffer.end(), buf->base, buf->base + buf->len);\n   } else if (nread < 0) {\n      socket->except = makeError(ErrorCategory::Socket, SocketError::GenericError);\n   }\n\n   delete buf->base;\n   socket->device->checkPendingReads(socket);\n   socket->device->checkPendingSelects();\n}\n\nstatic void\nuvConnectCallback(uv_connect_t *req, int status)\n{\n   auto socket = reinterpret_cast<SocketDevice::Socket *>(req->data);\n\n   if (status) {\n      if (socket->connectRequest) {\n         completeSocketTask(socket->connectRequest,\n                            makeError(ErrorCategory::Socket, SocketError::ConnRefused));\n      }\n\n      socket->connect.reset();\n      return;\n   }\n\n   // Immediately start reading data so we can fill our incoming read buffer\n   // in order to be able to emulate selects on read fds\n   uv_read_start(reinterpret_cast<uv_stream_t *>(socket->handle.get()),\n                  uvReadAllocCallback, uvReadCallback);\n\n   if (socket->connectRequest) {\n      completeSocketTask(socket->connectRequest, Error::OK);\n   }\n\n   socket->connected = true;\n   socket->device->checkPendingSelects();\n}\n\nstd::optional<Error>\nSocketDevice::connect(phys_ptr<ResourceRequest> resourceRequest,\n                      SocketHandle fd,\n                      phys_ptr<SocketAddrIn> sockAddr,\n                      int32_t sockAddrLen)\n{\n   auto socket = getSocket(fd);\n   if (!socket) {\n      return makeError(ErrorCategory::Socket, SocketError::BadFd);\n   }\n\n   if (socket->connect) {\n      return makeError(ErrorCategory::Socket, SocketError::IsConn);\n   }\n\n   if (socket->type != Socket::Tcp) {\n      return makeError(ErrorCategory::Socket, SocketError::Prototype);\n   }\n\n   auto addr = sockaddr_in { 0 };\n   addr.sin_addr.s_addr = sockAddr->sin_addr.s_addr_;\n   addr.sin_family = sockAddr->sin_family;\n   addr.sin_port = htons(sockAddr->sin_port);\n\n   socket->connect = std::make_unique<uv_connect_t>();\n   socket->connect->data = socket;\n\n   auto error = uv_tcp_connect(socket->connect.get(),\n                               reinterpret_cast<uv_tcp_t *>(socket->handle.get()),\n                               reinterpret_cast<const sockaddr *>(&addr),\n                               &uvConnectCallback);\n   if (error) {\n      socket->connect.reset();\n      return makeError(ErrorCategory::Socket, SocketError::ConnAborted);\n   }\n\n   if (socket->nonBlocking) {\n      socket->connectRequest = nullptr;\n      return makeError(ErrorCategory::Socket, SocketError::InProgress);\n   }\n\n   socket->connectRequest = resourceRequest;\n   return {};\n}\n\nstatic void\ngetHostByNameCallback(void *arg, int status, int timeouts, struct hostent *hostent)\n{\n   auto resourceRequest = handleDataToResourceRequest(arg);\n   auto response = phys_cast<SocketDnsQueryResponse *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr);\n\n   response->hostent.h_name = phys_addrof(response->dnsNames);\n   string_copy(response->hostent.h_name.get(),\n               hostent->h_name,\n               response->dnsNames.size());\n\n   response->hostent.h_addrtype = hostent->h_addrtype;\n   response->hostent.h_length = hostent->h_length;\n\n   // From testing on my Wii U it seems no aliases are returned.\n   response->aliases[0] = 0u;\n\n   response->ipaddrs = 0u;\n   for (auto i = 0u; hostent->h_addr_list[i] && i < response->ipaddrList.size(); ++i) {\n      response->ipaddrList[i] = *reinterpret_cast<uint32_t *>(hostent->h_addr_list[i]);\n      response->ipaddrs++;\n   }\n\n   response->selfPointerOffset = static_cast<uint32_t>(phys_cast<phys_addr>(response));\n   completeSocketTask(resourceRequest, Error::OK);\n}\n\nstd::optional<Error>\nSocketDevice::dnsQuery(phys_ptr<ResourceRequest> resourceRequest,\n                       phys_ptr<SocketDnsQueryRequest> request,\n                       phys_ptr<SocketDnsQueryResponse> response)\n{\n   if (request->queryType == SocketDnsQueryType::GetHostByName) {\n      ares_gethostbyname(ios::internal::networkAresChannel(),\n                         phys_addrof(request->name).get(),\n                         AF_INET,\n                         getHostByNameCallback,\n                         resourceRequestToHandleData(resourceRequest));\n      return {};\n   } else {\n      return makeError(ErrorCategory::Socket, SocketError::Inval);\n   }\n}\n\nstd::optional<Error>\nSocketDevice::getpeername(phys_ptr<kernel::ResourceRequest> resourceRequest,\n                          phys_ptr<const SocketGetPeerNameRequest> request,\n                          phys_ptr<SocketGetPeerNameResponse> response)\n{\n   auto socket = getSocket(request->fd);\n   if (!socket) {\n      return makeError(ErrorCategory::Socket, SocketError::BadFd);\n   }\n\n   if (!socket->connected) {\n      return makeError(ErrorCategory::Socket, SocketError::NotConn);\n   }\n\n   auto addr = sockaddr_in { };\n   auto addrlen = static_cast<int>(sizeof(sockaddr_in));\n   auto error = uv_tcp_getpeername(reinterpret_cast<uv_tcp_t *>(socket->handle.get()),\n                                   reinterpret_cast<struct sockaddr *>(&addr), &addrlen);\n   if (error) {\n      return makeError(ErrorCategory::Socket, SocketError::NotConn);\n   }\n\n   response->addr.sin_family = addr.sin_family;\n   response->addr.sin_port = ntohs(addr.sin_port);\n   response->addr.sin_addr.s_addr_ = addr.sin_addr.s_addr;\n   response->addrlen = static_cast<int32_t>(sizeof(SocketAddrIn));\n   return Error::OK;\n}\n\nstd::optional<Error>\nSocketDevice::getsockname(phys_ptr<kernel::ResourceRequest> resourceRequest,\n                          phys_ptr<const SocketGetSockNameRequest> request,\n                          phys_ptr<SocketGetSockNameResponse> response)\n{\n   auto socket = getSocket(request->fd);\n   if (!socket) {\n      return makeError(ErrorCategory::Socket, SocketError::BadFd);\n   }\n\n   if (!socket->connected) {\n      return makeError(ErrorCategory::Socket, SocketError::NotConn);\n   }\n\n   auto addr = sockaddr_in { };\n   auto addrlen = static_cast<int>(sizeof(sockaddr_in));\n   auto error = uv_tcp_getsockname(reinterpret_cast<uv_tcp_t *>(socket->handle.get()),\n                                   reinterpret_cast<struct sockaddr *>(&addr), &addrlen);\n   if (error) {\n      return makeError(ErrorCategory::Socket, SocketError::NotConn);\n   }\n\n   response->addr.sin_family = addr.sin_family;\n   response->addr.sin_port = ntohs(addr.sin_port);\n   response->addr.sin_addr.s_addr_ = addr.sin_addr.s_addr;\n   response->addrlen = static_cast<int32_t>(sizeof(SocketAddrIn));\n   return Error::OK;\n}\n\nstd::optional<Error>\nSocketDevice::checkRecv(phys_ptr<kernel::ResourceRequest> resourceRequest)\n{\n   auto request = phys_cast<const SocketRecvRequest *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr);\n   auto socket = getSocket(request->fd);\n   if (!socket) {\n      return makeError(ErrorCategory::Socket, SocketError::BadFd);\n   }\n\n   auto alignedBeforeBuffer = phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr);\n   auto alignedBeforeLength = resourceRequest->requestData.args.ioctlv.vecs[1].len;\n\n   auto alignedBuffer = phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[2].paddr);\n   auto alignedLength = resourceRequest->requestData.args.ioctlv.vecs[2].len;\n\n   auto alignedAfterBuffer = phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[3].paddr);\n   auto alignedAfterLength = resourceRequest->requestData.args.ioctlv.vecs[3].len;\n\n   if (socket->readBuffer.empty()) {\n      auto nonBlocking = socket->nonBlocking || !!(request->flags & SO_MSG_DONTWAIT);\n      if (!nonBlocking) {\n         return makeError(ErrorCategory::Socket, SocketError::WouldBlock);\n      }\n\n      return {};\n   }\n\n   auto readBytes = size_t { 0u };\n   if (alignedBeforeBuffer && alignedBeforeLength) {\n      auto len = std::min<size_t>(socket->readBuffer.size() - readBytes, alignedBeforeLength);\n      std::memcpy(alignedBeforeBuffer.get(), socket->readBuffer.data() + readBytes, len);\n      readBytes += len;\n   }\n\n   if (alignedBuffer && alignedLength) {\n      auto len = std::min<size_t>(socket->readBuffer.size() - readBytes, alignedLength);\n      std::memcpy(alignedBuffer.get(), socket->readBuffer.data() + readBytes, len);\n      readBytes += len;\n   }\n\n   if (alignedAfterBuffer && alignedAfterLength) {\n      auto len = std::min<size_t>(socket->readBuffer.size() - readBytes, alignedAfterLength);\n      std::memcpy(alignedAfterBuffer.get(), socket->readBuffer.data() + readBytes, len);\n      readBytes += len;\n   }\n\n   if (!(request->flags & SO_MSG_PEEK)) {\n      if (readBytes == socket->readBuffer.size()) {\n         socket->readBuffer.clear();\n      } else {\n         std::memmove(socket->readBuffer.data(),\n                        socket->readBuffer.data() + readBytes,\n                        socket->readBuffer.size() - readBytes);\n         socket->readBuffer.resize(socket->readBuffer.size() - readBytes);\n      }\n   }\n\n   return static_cast<Error>(readBytes);\n}\n\nstd::optional<Error>\nSocketDevice::recv(phys_ptr<ResourceRequest> resourceRequest,\n                   phys_ptr<const SocketRecvRequest> request,\n                   phys_ptr<char> alignedBeforeBuffer,\n                   uint32_t alignedBeforeLength,\n                   phys_ptr<char> alignedBuffer,\n                   uint32_t alignedLength,\n                   phys_ptr<char> alignedAfterBuffer,\n                   uint32_t alignedAfterLength)\n{\n   auto socket = getSocket(request->fd);\n   if (!socket) {\n      return makeError(ErrorCategory::Socket, SocketError::BadFd);\n   }\n\n   if (!socket->connected) {\n      return makeError(ErrorCategory::Socket, SocketError::NotConn);\n   }\n\n   auto result = checkRecv(resourceRequest);\n   if (result.has_value()) {\n      return result;\n   }\n\n   socket->pendingReads.push_back(resourceRequest);\n   return {};\n}\n\nvoid\nSocketDevice::uvWriteCallback(uv_write_t *req, int32_t status)\n{\n   auto write = reinterpret_cast<SocketDevice::PendingWrite *>(req->data);\n   auto socket = write->socket;\n\n   for (auto itr = socket->pendingWrites.begin(); itr != socket->pendingWrites.end(); ++itr) {\n      if (itr->get() == write) {\n         if (status != 0) {\n            completeSocketTask((*itr)->resourceRequest,\n               makeError(ErrorCategory::Socket, SocketError::GenericError));\n         } else {\n            completeSocketTask((*itr)->resourceRequest,\n                               static_cast<Error>((*itr)->sendBytes));\n         }\n\n         socket->pendingWrites.erase(itr);\n         break;\n      }\n   }\n}\n\nstd::optional<Error>\nSocketDevice::send(phys_ptr<ResourceRequest> resourceRequest,\n                   phys_ptr<const SocketSendRequest> request,\n                   phys_ptr<char> alignedBeforeBuffer,\n                   uint32_t alignedBeforeLength,\n                   phys_ptr<char> alignedBuffer,\n                   uint32_t alignedLength,\n                   phys_ptr<char> alignedAfterBuffer,\n                   uint32_t alignedAfterLength)\n{\n   auto socket = getSocket(request->fd);\n   if (!socket) {\n      return makeError(ErrorCategory::Socket, SocketError::BadFd);\n   }\n\n   if (!socket->connected) {\n      return makeError(ErrorCategory::Socket, SocketError::NotConn);\n   }\n\n   auto buffers = std::array<uv_buf_t, 3> { };\n   auto numBuffers = 0;\n   auto sendBytes = 0u;\n\n   if (alignedBeforeBuffer && alignedBeforeLength) {\n      buffers[numBuffers].len = alignedBeforeLength;\n      buffers[numBuffers].base = alignedBeforeBuffer.get();\n      numBuffers++;\n      sendBytes += alignedBeforeLength;\n   }\n\n   if (alignedBuffer && alignedLength) {\n      buffers[numBuffers].len = alignedLength;\n      buffers[numBuffers].base = alignedBuffer.get();\n      numBuffers++;\n      sendBytes += alignedLength;\n   }\n\n   if (alignedAfterBuffer && alignedAfterLength) {\n      buffers[numBuffers].len = alignedAfterLength;\n      buffers[numBuffers].base = alignedAfterBuffer.get();\n      numBuffers++;\n      sendBytes += alignedAfterLength;\n   }\n\n   auto write = std::make_unique<PendingWrite>();\n   write->socket = socket;\n   write->handle.data = write.get();\n   write->resourceRequest = resourceRequest;\n   write->sendBytes = sendBytes;\n\n   auto error = uv_write(&write->handle,\n                         reinterpret_cast<uv_stream_t *>(socket->handle.get()),\n                         buffers.data(), numBuffers, &uvWriteCallback);\n   if (error) {\n      return makeError(ErrorCategory::Socket, SocketError::GenericError);\n   }\n\n   socket->pendingWrites.push_back(std::move(write));\n   return {};\n}\n\nstd::optional<Error>\nSocketDevice::setsockopt(phys_ptr<kernel::ResourceRequest> resourceRequest,\n                         phys_ptr<const SocketSetSockOptRequest> request,\n                         phys_ptr<void> optval,\n                         uint32_t optlen)\n{\n   auto socket = getSocket(request->fd);\n   if (!socket) {\n      return makeError(ErrorCategory::Socket, SocketError::BadFd);\n   }\n\n   if (request->optname == 4118) {\n      if (!optval || optlen != sizeof(uint32_t)) {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n\n      socket->nonBlocking = !!*phys_cast<uint32_t *>(optval);\n      return Error::OK;\n   }\n\n   netLog->warn(\"Unimplemented setsockopt for optname {}\", request->optname);\n   return Error::OK;\n}\n\nstd::optional<Error>\nSocketDevice::checkSelect(phys_ptr<kernel::ResourceRequest> resourceRequest)\n{\n   auto request = phys_cast<const SocketSelectRequest *>(resourceRequest->requestData.args.ioctl.inputBuffer);\n   auto response = phys_cast<SocketSelectResponse *>(resourceRequest->requestData.args.ioctl.outputBuffer);\n   auto readyReadFds = uint32_t { 0 };\n   auto readyWriteFds = uint32_t { 0 };\n   auto readyExceptFds = uint32_t { 0 };\n   auto numFds = 0;\n\n   for (auto i = 0; i < request->nfds; ++i) {\n      if ((request->readfds >> i) & 1) {\n         auto socket = getSocket(i);\n         if (!socket) {\n            return makeError(ErrorCategory::Socket, SocketError::BadFd);\n         }\n\n         // Check if socket is ready to read\n         if (!socket->readBuffer.empty()) {\n            readyReadFds |= 1u << i;\n            ++numFds;\n         }\n      }\n   }\n\n   for (auto i = 0; i < request->nfds; ++i) {\n      if ((request->writefds >> i) & 1) {\n         auto socket = getSocket(i);\n         if (!socket) {\n            return makeError(ErrorCategory::Socket, SocketError::BadFd);\n         }\n\n         // Our sockets are always ready to write once they have connected\n         if (socket->connected) {\n            readyWriteFds |= 1u << i;\n            ++numFds;\n         }\n      }\n   }\n\n   for (auto i = 0; i < request->nfds; ++i) {\n      if ((request->exceptfds >> i) & 1) {\n         auto socket = getSocket(i);\n         if (!socket) {\n            return makeError(ErrorCategory::Socket, SocketError::BadFd);\n         }\n\n         // Check if socket has errored\n         if (socket->except != Error::OK) {\n            response->exceptfds |= 1u << i;\n            ++numFds;\n         }\n      }\n   }\n\n   if (numFds) {\n      response->readfds = readyReadFds;\n      response->writefds = readyWriteFds;\n      response->exceptfds = readyExceptFds;\n      return static_cast<Error>(numFds);\n   }\n\n   return {};\n}\n\nvoid\nSocketDevice::checkPendingReads(Socket *socket)\n{\n   auto itr = socket->pendingReads.begin();\n\n   while (itr != socket->pendingReads.end()) {\n      auto result = checkRecv(*itr);\n      if (result.has_value()) {\n         completeSocketTask(*itr, result);\n         itr = socket->pendingReads.erase(itr);\n      } else {\n         ++itr;\n      }\n   }\n}\n\nvoid\nSocketDevice::checkPendingSelects()\n{\n   auto now = std::chrono::system_clock::now();\n   auto itr = mPendingSelects.begin();\n\n   while (itr != mPendingSelects.end()) {\n      auto result = checkSelect((*itr)->resourceRequest);\n      if (result.has_value()) {\n         completeSocketTask((*itr)->resourceRequest, result);\n         itr = mPendingSelects.erase(itr);\n      } else {\n         ++itr;\n      }\n   }\n}\n\nvoid\nSocketDevice::uvExpirePendingSelectCallback(uv_timer_t *timer)\n{\n   auto pending = reinterpret_cast<PendingSelect *>(timer->data);\n   completeSocketTask(pending->resourceRequest,\n                      makeError(ErrorCategory::Socket, SocketError::TimedOut));\n\n   for (auto itr = pending->device->mPendingSelects.begin(); itr != pending->device->mPendingSelects.end(); ++itr) {\n      if (itr->get() == pending) {\n         pending->device->mPendingSelects.erase(itr);\n      }\n   }\n}\n\nstd::optional<Error>\nSocketDevice::select(phys_ptr<ResourceRequest> resourceRequest,\n                     phys_ptr<const SocketSelectRequest> request,\n                     phys_ptr<SocketSelectResponse> response)\n{\n   auto result = checkSelect(resourceRequest);\n   if (result.has_value()) {\n      return result;\n   }\n\n   if (request->hasTimeout) {\n      if (request->timeout.tv_sec == 0 && request->timeout.tv_usec == 0) {\n         return makeError(ErrorCategory::Socket, SocketError::TimedOut);\n      }\n\n      auto pending = std::make_unique<PendingSelect>();\n      uv_timer_init(networkUvLoop(), &pending->timer);\n      pending->timer.data = resourceRequestToHandleData(resourceRequest);\n      pending->resourceRequest = resourceRequest;\n      pending->device = this;\n\n      auto expireMS = std::chrono::duration_cast<std::chrono::milliseconds>(\n         std::chrono::seconds(request->timeout.tv_sec)\n         + std::chrono::microseconds(request->timeout.tv_usec)\n         + (std::chrono::milliseconds(1) - std::chrono::microseconds(1)));\n      uv_timer_start(&pending->timer, &uvExpirePendingSelectCallback,\n                     static_cast<uint64_t>(expireMS.count()), 0ull);\n\n      mPendingSelects.push_back(std::move(pending));\n      return {};\n   }\n\n   return static_cast<Error>(0);\n}\n\n#if 0\nstatic void\nuvListenConnectionCallback(uv_stream_t *server, int status)\n{\n   auto socket = reinterpret_cast<SocketDevice::Socket *>(server->data);\n}\n#endif\n\nstd::optional<Error>\nSocketDevice::listen(phys_ptr<ResourceRequest> resourceRequest,\n                     SocketHandle fd,\n                     int32_t backlog)\n{\n   auto socket = getSocket(fd);\n   if (!socket) {\n      return makeError(ErrorCategory::Socket, SocketError::BadFd);\n   }\n\n   return makeError(ErrorCategory::Socket, SocketError::Inval);\n#if 0\n   auto error = uv_listen(reinterpret_cast<uv_stream_t *>(socket->handle.get()),\n                          backlog, uvListenConnectionCallback);\n   if (error != 0) {\n   }\n\n   return {};\n#endif\n}\n\nSocketDevice::Socket *\nSocketDevice::getSocket(SocketHandle fd)\n{\n   if (fd <= 0) {\n      return nullptr;\n   }\n\n   if (fd > mSockets.size()) {\n      return nullptr;\n   }\n\n   if (!mSockets[fd - 1].type) {\n      return nullptr;\n   }\n\n   return &mSockets[fd - 1];\n}\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_socket_device.h",
    "content": "#pragma once\n#include \"ios_net_enum.h\"\n#include \"ios_net_socket_request.h\"\n#include \"ios_net_socket_response.h\"\n#include \"ios_net_socket_types.h\"\n\n#include \"ios/ios_enum.h\"\n\n#include <array>\n#include <chrono>\n#include <memory>\n#include <optional>\n#include <vector>\n#include <uv.h>\n\nnamespace ios::kernel\n{\nstruct ResourceRequest;\n};\n\nnamespace ios::net::internal\n{\n\n/**\n * \\ingroup ios_net\n * @{\n */\n\nclass SocketDevice\n{\npublic:\n   struct Socket;\n\n   struct PendingWrite\n   {\n      Socket *socket;\n      phys_ptr<kernel::ResourceRequest> resourceRequest;\n      uv_write_t handle;\n      uint32_t sendBytes;\n   };\n\n   struct PendingSelect\n   {\n      ~PendingSelect() {\n         uv_timer_stop(&timer);\n      }\n\n      SocketDevice *device;\n      phys_ptr<kernel::ResourceRequest> resourceRequest;\n      uv_timer_t timer;\n   };\n\n   struct Socket\n   {\n      enum Type\n      {\n         Unused,\n         Tcp,\n         Udp,\n      };\n\n      Type type = Unused;\n      SocketDevice *device = nullptr;\n      bool nonBlocking = false;\n      std::unique_ptr<uv_handle_t> handle;\n      std::unique_ptr<uv_connect_t> connect;\n      phys_ptr<kernel::ResourceRequest> connectRequest = nullptr;\n      bool connected = false;\n      Error except = Error::OK;\n      std::vector<char> readBuffer;\n      std::vector<phys_ptr<kernel::ResourceRequest>> pendingReads;\n      std::vector<std::unique_ptr<PendingWrite>> pendingWrites;\n      phys_ptr<kernel::ResourceRequest> closeRequest = nullptr;\n   };\n\npublic:\n   std::optional<Error>\n   accept(phys_ptr<kernel::ResourceRequest> resourceRequest,\n          SocketHandle fd,\n          phys_ptr<SocketAddrIn> sockAddr,\n          int32_t sockAddrLen);\n\n   std::optional<Error>\n   bind(phys_ptr<kernel::ResourceRequest> resourceRequest,\n        SocketHandle fd,\n        phys_ptr<SocketAddrIn> sockAddr,\n        int32_t sockAddrLen);\n\n   std::optional<Error>\n   createSocket(phys_ptr<kernel::ResourceRequest> resourceRequest,\n                int32_t family,\n                int32_t type,\n                int32_t proto);\n\n   std::optional<Error>\n   closeSocket(phys_ptr<kernel::ResourceRequest> resourceRequest,\n               SocketHandle fd);\n\n   std::optional<Error>\n   connect(phys_ptr<kernel::ResourceRequest> resourceRequest,\n           SocketHandle fd,\n           phys_ptr<SocketAddrIn> sockAddr,\n           int32_t sockAddrLen);\n\n   std::optional<Error>\n   dnsQuery(phys_ptr<kernel::ResourceRequest> resourceRequest,\n            phys_ptr<SocketDnsQueryRequest> request,\n            phys_ptr<SocketDnsQueryResponse> response);\n\n   std::optional<Error>\n   getpeername(phys_ptr<kernel::ResourceRequest> resourceRequest,\n               phys_ptr<const SocketGetPeerNameRequest> request,\n               phys_ptr<SocketGetPeerNameResponse> response);\n\n   std::optional<Error>\n   getsockname(phys_ptr<kernel::ResourceRequest> resourceRequest,\n               phys_ptr<const SocketGetSockNameRequest> request,\n               phys_ptr<SocketGetSockNameResponse> response);\n\n   std::optional<Error>\n   recv(phys_ptr<kernel::ResourceRequest> resourceRequest,\n        phys_ptr<const SocketRecvRequest> request,\n        phys_ptr<char> alignedBeforeBuffer,\n        uint32_t alignedBeforeLength,\n        phys_ptr<char> alignedBuffer,\n        uint32_t alignedLength,\n        phys_ptr<char> alignedAfterBuffer,\n        uint32_t alignedAfterLength);\n\n   std::optional<Error>\n   send(phys_ptr<kernel::ResourceRequest> resourceRequest,\n        phys_ptr<const SocketSendRequest> request,\n        phys_ptr<char> alignedBeforeBuffer,\n        uint32_t alignedBeforeLength,\n        phys_ptr<char> alignedBuffer,\n        uint32_t alignedLength,\n        phys_ptr<char> alignedAfterBuffer,\n        uint32_t alignedAfterLength);\n\n   std::optional<Error>\n   setsockopt(phys_ptr<kernel::ResourceRequest> resourceRequest,\n              phys_ptr<const SocketSetSockOptRequest> request,\n              phys_ptr<void> optval,\n              uint32_t optlen);\n\n   std::optional<Error>\n   select(phys_ptr<kernel::ResourceRequest> resourceRequest,\n          phys_ptr<const SocketSelectRequest> request,\n          phys_ptr<SocketSelectResponse> response);\n\n   std::optional<Error>\n   listen(phys_ptr<kernel::ResourceRequest> resourceRequest,\n          SocketHandle fd,\n          int32_t backlog);\n\n   void checkPendingSelects();\n   void checkPendingReads(Socket *socket);\n\nprotected:\n   Socket *getSocket(int sockfd);\n   std::optional<Error> checkSelect(phys_ptr<kernel::ResourceRequest> resourceRequest);\n   std::optional<Error> checkRecv(phys_ptr<kernel::ResourceRequest> resourceRequest);\n\n   static void uvCloseSocketCallback(uv_handle_t *handle);\n   static void uvExpirePendingSelectCallback(uv_timer_t *timer);\n   static void uvWriteCallback(uv_write_t *req, int32_t status);\n\nprivate:\n   std::array<Socket, 64> mSockets;\n   std::vector<std::unique_ptr<PendingSelect>> mPendingSelects;\n};\n\n/** @} */\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_socket_request.h",
    "content": "#pragma once\n#include \"ios_net_enum.h\"\n#include \"ios_net_socket_types.h\"\n\n#include \"ios/ios_ipc.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::net\n{\n\n/**\n * \\ingroup ios_net\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct SocketAcceptRequest\n{\n   be2_val<SocketHandle> fd;\n   be2_struct<SocketAddrIn> addr;\n   be2_val<int32_t> addrlen;\n};\nCHECK_OFFSET(SocketAcceptRequest, 0x00, fd);\nCHECK_OFFSET(SocketAcceptRequest, 0x04, addr);\nCHECK_OFFSET(SocketAcceptRequest, 0x14, addrlen);\nCHECK_SIZE(SocketAcceptRequest, 0x18);\n\nstruct SocketBindRequest\n{\n   be2_val<SocketHandle> fd;\n   be2_struct<SocketAddrIn> addr;\n   be2_val<int32_t> addrlen;\n};\nCHECK_OFFSET(SocketBindRequest, 0x00, fd);\nCHECK_OFFSET(SocketBindRequest, 0x04, addr);\nCHECK_OFFSET(SocketBindRequest, 0x14, addrlen);\nCHECK_SIZE(SocketBindRequest, 0x18);\n\nstruct SocketCloseRequest\n{\n   be2_val<SocketHandle> fd;\n};\nCHECK_OFFSET(SocketCloseRequest, 0x00, fd);\nCHECK_SIZE(SocketCloseRequest, 0x04);\n\nstruct SocketConnectRequest\n{\n   be2_val<SocketHandle> fd;\n   be2_struct<SocketAddrIn> addr;\n   be2_val<int32_t> addrlen;\n};\nCHECK_OFFSET(SocketConnectRequest, 0x00, fd);\nCHECK_OFFSET(SocketConnectRequest, 0x04, addr);\nCHECK_OFFSET(SocketConnectRequest, 0x14, addrlen);\nCHECK_SIZE(SocketConnectRequest, 0x18);\n\nstruct SocketDnsQueryRequest\n{\n   be2_array<char, 0x80> name;\n   be2_val<SocketDnsQueryType> queryType;\n   be2_val<uint8_t> isAsync;\n   PADDING(2);\n   UNKNOWN(4);\n   be2_val<uint32_t> unk0x88;\n   be2_val<uint32_t> unk0x8C;\n};\nCHECK_OFFSET(SocketDnsQueryRequest, 0x00, name);\nCHECK_OFFSET(SocketDnsQueryRequest, 0x80, queryType);\nCHECK_OFFSET(SocketDnsQueryRequest, 0x81, isAsync);\nCHECK_OFFSET(SocketDnsQueryRequest, 0x88, unk0x88);\nCHECK_OFFSET(SocketDnsQueryRequest, 0x8C, unk0x8C);\nCHECK_SIZE(SocketDnsQueryRequest, 0x90);\n\nstruct SocketGetPeerNameRequest\n{\n   be2_val<SocketHandle> fd;\n   be2_struct<SocketAddrIn> addr;\n   be2_val<int32_t> addrlen;\n};\nCHECK_OFFSET(SocketGetPeerNameRequest, 0x00, fd);\nCHECK_OFFSET(SocketGetPeerNameRequest, 0x04, addr);\nCHECK_OFFSET(SocketGetPeerNameRequest, 0x14, addrlen);\nCHECK_SIZE(SocketGetPeerNameRequest, 0x18);\n\nstruct SocketGetProcessSocketHandle\n{\n   be2_val<ios::TitleId> titleId;\n   be2_val<ios::ProcessId> processId;\n};\nCHECK_OFFSET(SocketGetProcessSocketHandle, 0x00, titleId);\nCHECK_OFFSET(SocketGetProcessSocketHandle, 0x08, processId);\nCHECK_SIZE(SocketGetProcessSocketHandle, 0x0C);\n\nstruct SocketGetSockNameRequest\n{\n   be2_val<SocketHandle> fd;\n   be2_struct<SocketAddrIn> addr;\n   be2_val<int32_t> addrlen;\n};\nCHECK_OFFSET(SocketGetSockNameRequest, 0x00, fd);\nCHECK_OFFSET(SocketGetSockNameRequest, 0x04, addr);\nCHECK_OFFSET(SocketGetSockNameRequest, 0x14, addrlen);\nCHECK_SIZE(SocketGetSockNameRequest, 0x18);\n\nstruct SocketListenRequest\n{\n   be2_val<SocketHandle> fd;\n   be2_val<int32_t> backlog;\n};\nCHECK_OFFSET(SocketListenRequest, 0x00, fd);\nCHECK_OFFSET(SocketListenRequest, 0x04, backlog);\nCHECK_SIZE(SocketListenRequest, 0x08);\n\nstruct SocketRecvRequest\n{\n   be2_val<SocketHandle> fd;\n   be2_val<int32_t> flags;\n};\nCHECK_OFFSET(SocketRecvRequest, 0x00, fd);\nCHECK_OFFSET(SocketRecvRequest, 0x04, flags);\nCHECK_SIZE(SocketRecvRequest, 0x08);\n\nstruct SocketSendRequest\n{\n   be2_val<SocketHandle> fd;\n   be2_val<int32_t> flags;\n};\nCHECK_OFFSET(SocketSendRequest, 0x00, fd);\nCHECK_OFFSET(SocketSendRequest, 0x04, flags);\nCHECK_SIZE(SocketSendRequest, 0x08);\n\nstruct SocketSelectRequest\n{\n   be2_val<int32_t> nfds;\n   be2_val<SocketFdSet> readfds;\n   be2_val<SocketFdSet> writefds;\n   be2_val<SocketFdSet> exceptfds;\n   be2_struct<SocketTimeval> timeout;\n   be2_val<int32_t> hasTimeout;\n};\nCHECK_OFFSET(SocketSelectRequest, 0x00, nfds);\nCHECK_OFFSET(SocketSelectRequest, 0x04, readfds);\nCHECK_OFFSET(SocketSelectRequest, 0x08, writefds);\nCHECK_OFFSET(SocketSelectRequest, 0x0C, exceptfds);\nCHECK_OFFSET(SocketSelectRequest, 0x10, timeout);\nCHECK_OFFSET(SocketSelectRequest, 0x18, hasTimeout);\nCHECK_SIZE(SocketSelectRequest, 0x1C);\n\nstruct SocketSetSockOptRequest\n{\n   // For some reason this structure overlaps the ioctlv vecs...\n   PADDING(0xC * 2);\n   be2_val<SocketHandle> fd;\n   be2_val<int32_t> level;\n   be2_val<int32_t> optname;\n};\nCHECK_OFFSET(SocketSetSockOptRequest, 0x18, fd);\nCHECK_OFFSET(SocketSetSockOptRequest, 0x1C, level);\nCHECK_OFFSET(SocketSetSockOptRequest, 0x20, optname);\nCHECK_SIZE(SocketSetSockOptRequest, 0x24);\n\nstruct SocketSocketRequest\n{\n   be2_val<int32_t> family;\n   be2_val<int32_t> type;\n   be2_val<int32_t> proto;\n};\nCHECK_OFFSET(SocketSocketRequest, 0x00, family);\nCHECK_OFFSET(SocketSocketRequest, 0x04, type);\nCHECK_OFFSET(SocketSocketRequest, 0x08, proto);\nCHECK_SIZE(SocketSocketRequest, 0x0C);\n\nstruct SocketRequest\n{\n   union\n   {\n      be2_struct<SocketAcceptRequest> accept;\n      be2_struct<SocketBindRequest> bind;\n      be2_struct<SocketCloseRequest> close;\n      be2_struct<SocketConnectRequest> connect;\n      be2_struct<SocketDnsQueryRequest> dnsQuery;\n      be2_struct<SocketGetPeerNameRequest> getpeername;\n      be2_struct<SocketGetSockNameRequest> getsockname;\n      be2_struct<SocketListenRequest> listen;\n      be2_struct<SocketRecvRequest> recv;\n      be2_struct<SocketSendRequest> send;\n      be2_struct<SocketSelectRequest> select;\n      be2_struct<SocketSetSockOptRequest> setsockopt;\n      be2_struct<SocketSocketRequest> socket;\n      be2_struct<SocketGetProcessSocketHandle> getProcessSocketHandle;\n   };\n};\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::net\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_socket_response.h",
    "content": "#pragma once\n#include \"ios_net_enum.h\"\n#include \"ios_net_socket_types.h\"\n\n#include \"ios/ios_ipc.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::net\n{\n\n/**\n * \\ingroup ios_net\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct SocketAcceptResponse\n{\n   be2_val<SocketHandle> fd;\n   be2_struct<SocketAddrIn> addr;\n   be2_val<int32_t> addrlen;\n};\nCHECK_OFFSET(SocketAcceptResponse, 0x00, fd);\nCHECK_OFFSET(SocketAcceptResponse, 0x04, addr);\nCHECK_OFFSET(SocketAcceptResponse, 0x14, addrlen);\nCHECK_SIZE(SocketAcceptResponse, 0x18);\n\nstruct SocketDnsQueryResponse\n{\n   be2_val<uint32_t> unk0x00;\n   be2_val<uint32_t> unk0x04;\n   be2_val<uint32_t> sendTime;\n   be2_val<uint32_t> expireTime;\n   be2_val<uint16_t> tries;\n   be2_val<uint16_t> lport;\n   be2_val<uint16_t> id;\n   UNKNOWN(0x2);\n   be2_val<uint32_t> unk0x18;\n   be2_val<uint32_t> replies;\n   be2_val<uint32_t> ipaddrs;\n   be2_array<uint32_t, 10> ipaddrList;\n   be2_array<uint32_t, 10> hostentIpaddrList;\n   be2_val<uint32_t> err;\n   be2_val<uint32_t> rcode;\n   be2_array<char, 256> dnsNames;\n   be2_array<char, 129> unk0x17C;\n   UNKNOWN(0x27C - 0x1FD);\n   be2_val<uint32_t> authsIp;\n   be2_array<uint32_t, 2> aliases;\n   UNKNOWN(0x290 - 0x288);\n   be2_struct<SocketHostEnt> hostent;\n   be2_val<SocketDnsQueryType> queryType;\n   be2_array<uint8_t, 2> unk0x2A5;\n   UNKNOWN(0x2B0 - 0x2A7);\n   be2_val<uint32_t> dnsReq;\n   be2_val<uint32_t> next;\n\n   //! Used to adjust pointers in hostent\n   be2_val<uint32_t> selfPointerOffset;\n};\nCHECK_OFFSET(SocketDnsQueryResponse, 0x00, unk0x00);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x04, unk0x04);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x08, sendTime);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x0C, expireTime);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x10, tries);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x12, lport);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x14, id);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x18, unk0x18);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x1C, replies);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x20, ipaddrs);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x24, ipaddrList);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x4C, hostentIpaddrList);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x74, err);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x78, rcode);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x7C, dnsNames);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x17C, unk0x17C);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x27C, authsIp);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x280, aliases);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x290, hostent);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x2A4, queryType);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x2A5, unk0x2A5);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x2B0, dnsReq);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x2B4, next);\nCHECK_OFFSET(SocketDnsQueryResponse, 0x2B8, selfPointerOffset);\nCHECK_SIZE(SocketDnsQueryResponse, 0x2BC);\n\nstruct SocketGetPeerNameResponse\n{\n   be2_val<SocketHandle> fd;\n   be2_struct<SocketAddrIn> addr;\n   be2_val<int32_t> addrlen;\n};\nCHECK_OFFSET(SocketGetPeerNameResponse, 0x00, fd);\nCHECK_OFFSET(SocketGetPeerNameResponse, 0x04, addr);\nCHECK_OFFSET(SocketGetPeerNameResponse, 0x14, addrlen);\nCHECK_SIZE(SocketGetPeerNameResponse, 0x18);\n\nstruct SocketGetSockNameResponse\n{\n   be2_val<SocketHandle> fd;\n   be2_struct<SocketAddrIn> addr;\n   be2_val<int32_t> addrlen;\n};\nCHECK_OFFSET(SocketGetSockNameResponse, 0x00, fd);\nCHECK_OFFSET(SocketGetSockNameResponse, 0x04, addr);\nCHECK_OFFSET(SocketGetSockNameResponse, 0x14, addrlen);\nCHECK_SIZE(SocketGetSockNameResponse, 0x18);\n\nstruct SocketSelectResponse\n{\n   be2_val<int32_t> nfds;\n   be2_val<SocketFdSet> readfds;\n   be2_val<SocketFdSet> writefds;\n   be2_val<SocketFdSet> exceptfds;\n   be2_struct<SocketTimeval> timeout;\n   be2_val<int32_t> hasTimeout;\n};\nCHECK_OFFSET(SocketSelectResponse, 0x00, nfds);\nCHECK_OFFSET(SocketSelectResponse, 0x04, readfds);\nCHECK_OFFSET(SocketSelectResponse, 0x08, writefds);\nCHECK_OFFSET(SocketSelectResponse, 0x0C, exceptfds);\nCHECK_OFFSET(SocketSelectResponse, 0x10, timeout);\nCHECK_OFFSET(SocketSelectResponse, 0x18, hasTimeout);\nCHECK_SIZE(SocketSelectResponse, 0x1C);\n\nstruct SocketResponse\n{\n   union\n   {\n      be2_struct<SocketAcceptResponse> accept;\n      be2_struct<SocketDnsQueryResponse> dnsQuery;\n      be2_struct<SocketGetPeerNameResponse> getpeername;\n      be2_struct<SocketGetSockNameResponse> getsockname;\n      be2_struct<SocketSelectResponse> select;\n   };\n};\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::net\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_socket_thread.cpp",
    "content": "#include \"ios_net_socket_async_task.h\"\n#include \"ios_net_socket_device.h\"\n#include \"ios_net_socket_response.h\"\n#include \"ios_net_socket_request.h\"\n#include \"ios_net_socket_thread.h\"\n\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include \"ios/ios_error.h\"\n#include \"ios/ios_stackobject.h\"\n#include \"ios/ios_network_thread.h\"\n\n#include <array>\n#include <optional>\n\nusing ios::internal::submitNetworkTask;\n\nnamespace ios::net::internal\n{\n\nusing SocketDeviceHandle = int32_t;\nusing namespace kernel;\n\nconstexpr auto NumSocketMessages = 40u;\nconstexpr auto SocketThreadStackSize = 0x4000u;\nconstexpr auto SocketThreadPriority = 69u;\n\nstruct StaticSocketThreadData\n{\n   be2_val<ThreadId> threadId;\n   be2_val<MessageQueueId> messageQueueId;\n   be2_struct<IpcRequest> stopMessage;\n   be2_array<Message, NumSocketMessages> messageBuffer;\n   be2_array<uint8_t, SocketThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticSocketThreadData>\nsData = nullptr;\n\nstatic std::array<std::unique_ptr<SocketDevice>, ProcessId::Max>\nsDevices;\n\nstatic SocketDevice *\ngetDevice(SocketDeviceHandle handle)\n{\n   if (handle < 0 || handle >= sDevices.size()) {\n      return nullptr;\n   }\n\n   return sDevices[handle].get();\n}\n\nstatic Error\nsocketOpen(phys_ptr<ResourceRequest> request)\n{\n   // There is one socket device per process\n   auto pid = request->requestData.clientPid;\n   auto idx = static_cast<size_t>(pid);\n\n   if (!sDevices[idx]) {\n      sDevices[idx] = std::make_unique<SocketDevice>();\n   }\n\n   return static_cast<Error>(idx);\n}\n\nstatic Error\nsocketClose(phys_ptr<ResourceRequest> request)\n{\n   auto pid = request->requestData.clientPid;\n   auto idx = static_cast<size_t>(pid);\n\n   if (idx != request->requestData.handle) {\n      return Error::Exists;\n   }\n\n   sDevices[idx] = nullptr;\n   return Error::OK;\n}\n\nstatic std::optional<Error>\nsocketIoctl(phys_ptr<ResourceRequest> resourceRequest)\n{\n   auto device = getDevice(resourceRequest->requestData.handle);\n   if (!device) {\n      return Error::InvalidHandle;\n   }\n\n   auto request = phys_cast<const SocketRequest *>(resourceRequest->requestData.args.ioctl.inputBuffer);\n   auto response = phys_cast<SocketResponse *>(resourceRequest->requestData.args.ioctl.outputBuffer);\n\n   switch (static_cast<SocketCommand>(resourceRequest->requestData.args.ioctl.request)) {\n   case SocketCommand::Accept:\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketAcceptRequest)) {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n\n      submitNetworkTask([=]() {\n         completeSocketTask(\n            resourceRequest,\n            device->accept(resourceRequest,\n                           request->accept.fd,\n                           phys_addrof(request->accept.addr),\n                           request->accept.addrlen));\n      });\n      break;\n   case SocketCommand::Bind:\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketBindRequest)) {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n\n      submitNetworkTask([=]() {\n         completeSocketTask(\n            resourceRequest,\n            device->bind(resourceRequest,\n                         request->bind.fd,\n                         phys_addrof(request->bind.addr),\n                         request->bind.addrlen));\n      });\n      break;\n   case SocketCommand::Close:\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketCloseRequest)) {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n\n      submitNetworkTask([=]() {\n         completeSocketTask(\n            resourceRequest,\n            device->closeSocket(resourceRequest,\n                                request->close.fd));\n      });\n      break;\n   case SocketCommand::Connect:\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketConnectRequest)) {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n\n      submitNetworkTask([=]() {\n         completeSocketTask(\n            resourceRequest,\n            device->connect(resourceRequest,\n                            request->connect.fd,\n                            phys_addrof(request->connect.addr),\n                            request->connect.addrlen));\n      });\n      break;\n   case SocketCommand::GetPeerName:\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketGetPeerNameRequest) ||\n          resourceRequest->requestData.args.ioctl.outputLength != sizeof(SocketGetPeerNameResponse)) {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n\n      submitNetworkTask([=]() {\n         completeSocketTask(\n            resourceRequest,\n            device->getpeername(resourceRequest,\n                                phys_addrof(request->getpeername),\n                                phys_addrof(response->getpeername)));\n      });\n      break;\n   case SocketCommand::GetSockName:\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketGetSockNameRequest) ||\n          resourceRequest->requestData.args.ioctl.outputLength != sizeof(SocketGetSockNameResponse)) {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n\n      submitNetworkTask([=]() {\n         completeSocketTask(\n            resourceRequest,\n            device->getsockname(resourceRequest,\n                                phys_addrof(request->getsockname),\n                                phys_addrof(response->getsockname)));\n      });\n      break;\n   case SocketCommand::Listen:\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketListenRequest)) {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n\n      submitNetworkTask([=]() {\n         completeSocketTask(\n            resourceRequest,\n            device->listen(resourceRequest,\n                           request->listen.fd,\n                           request->listen.backlog));\n      });\n      break;\n   case SocketCommand::Select:\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketSelectRequest) ||\n          resourceRequest->requestData.args.ioctl.outputLength != sizeof(SocketSelectResponse)) {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n\n      submitNetworkTask([=]() {\n         completeSocketTask(\n            resourceRequest,\n            device->select(resourceRequest,\n                           phys_addrof(request->select),\n                           phys_addrof(response->select)));\n      });\n      break;\n   case SocketCommand::Socket:\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(SocketSocketRequest)) {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n\n      submitNetworkTask([=]() {\n         completeSocketTask(\n            resourceRequest,\n            device->createSocket(resourceRequest,\n                                 request->socket.family,\n                                 request->socket.type,\n                                 request->socket.proto));\n      });\n      break;\n   case SocketCommand::GetProcessSocketHandle:\n      if (static_cast<size_t>(request->getProcessSocketHandle.processId) < sDevices.size() &&\n          sDevices[request->getProcessSocketHandle.processId]) {\n         return static_cast<Error>(request->getProcessSocketHandle.processId);\n      } else {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n      break;\n   default:\n      return Error::Invalid;\n   }\n\n   return {}; // async request\n}\n\nstatic std::optional<Error>\nsocketIoctlv(phys_ptr<ResourceRequest> resourceRequest)\n{\n   auto device = getDevice(resourceRequest->requestData.handle);\n\n   if (!device) {\n      return Error::InvalidHandle;\n   }\n\n   switch (static_cast<SocketCommand>(resourceRequest->requestData.args.ioctlv.request)) {\n   case SocketCommand::DnsQuery:\n      if (resourceRequest->requestData.args.ioctlv.numVecIn == 1 &&\n          resourceRequest->requestData.args.ioctlv.numVecOut == 1 &&\n          resourceRequest->requestData.args.ioctlv.vecs[0].len == sizeof(SocketDnsQueryRequest) &&\n          resourceRequest->requestData.args.ioctlv.vecs[1].len == sizeof(SocketDnsQueryResponse)) {\n         submitNetworkTask([=]() {\n            completeSocketTask(\n               resourceRequest,\n               device->dnsQuery(resourceRequest,\n                                phys_cast<SocketDnsQueryRequest *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr),\n                                phys_cast<SocketDnsQueryResponse *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr)));\n         });\n      } else {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n      break;\n   case SocketCommand::Recv:\n      if (resourceRequest->requestData.args.ioctlv.numVecIn == 1 &&\n          resourceRequest->requestData.args.ioctlv.numVecOut == 3 &&\n          resourceRequest->requestData.args.ioctlv.vecs[0].len == sizeof(SocketRecvRequest)) {\n         submitNetworkTask([=]() {\n            completeSocketTask(\n               resourceRequest,\n               device->recv(resourceRequest,\n                            phys_cast<const SocketRecvRequest *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr),\n                            phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr),\n                            resourceRequest->requestData.args.ioctlv.vecs[1].len,\n                            phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[2].paddr),\n                            resourceRequest->requestData.args.ioctlv.vecs[2].len,\n                            phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[3].paddr),\n                            resourceRequest->requestData.args.ioctlv.vecs[3].len));\n         });\n      } else {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n      break;\n   case SocketCommand::SetSockOpt:\n      if (resourceRequest->requestData.args.ioctlv.numVecIn == 2 &&\n          resourceRequest->requestData.args.ioctlv.numVecOut == 0 &&\n          resourceRequest->requestData.args.ioctlv.vecs[1].len == sizeof(SocketSetSockOptRequest)) {\n         submitNetworkTask([=]() {\n            completeSocketTask(\n               resourceRequest,\n               device->setsockopt(resourceRequest,\n                                  phys_cast<SocketSetSockOptRequest *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr),\n                                  phys_cast<void *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr),\n                                  resourceRequest->requestData.args.ioctlv.vecs[0].len));\n         });\n      } else {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n      break;\n   case SocketCommand::Send:\n      if (resourceRequest->requestData.args.ioctlv.numVecIn == 4 &&\n          resourceRequest->requestData.args.ioctlv.numVecOut == 0 &&\n          resourceRequest->requestData.args.ioctlv.vecs[0].len == sizeof(SocketSendRequest)) {\n         submitNetworkTask([=]() {\n            completeSocketTask(\n               resourceRequest,\n               device->send(resourceRequest,\n                            phys_cast<const SocketSendRequest *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr),\n                            phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr),\n                            resourceRequest->requestData.args.ioctlv.vecs[1].len,\n                            phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[2].paddr),\n                            resourceRequest->requestData.args.ioctlv.vecs[2].len,\n                            phys_cast<char *>(resourceRequest->requestData.args.ioctlv.vecs[3].paddr),\n                            resourceRequest->requestData.args.ioctlv.vecs[3].len));\n         });\n      } else {\n         return makeError(ErrorCategory::Socket, SocketError::Inval);\n      }\n      break;\n   default:\n      return Error::Invalid;\n   }\n\n   return {}; // async request\n}\n\nstatic Error\nsocketThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n\n   while (true) {\n      auto error = IOS_ReceiveMessage(sData->messageQueueId,\n                                      message,\n                                      MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n         IOS_ResourceReply(request, socketOpen(request));\n         break;\n      case Command::Close:\n         IOS_ResourceReply(request, socketClose(request));\n         break;\n      case Command::Ioctl:\n         if (auto result = socketIoctl(request); result.has_value()) {\n            IOS_ResourceReply(request, result.value());\n         }\n         break;\n      case Command::Ioctlv:\n         if (auto result = socketIoctlv(request); result.has_value()) {\n            IOS_ResourceReply(request, result.value());\n         }\n         break;\n      case Command::Suspend:\n         // TODO: Do any necessary cleanup!\n         return Error::OK;\n      default:\n         IOS_ResourceReply(request, Error::InvalidArg);\n      }\n   }\n}\n\nError\nregisterSocketResourceManager()\n{\n   auto error = IOS_CreateMessageQueue(phys_addrof(sData->messageBuffer),\n                                       sData->messageBuffer.size());\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sData->messageQueueId = static_cast<MessageQueueId>(error);\n   return IOS_RegisterResourceManager(\"/dev/socket\", sData->messageQueueId);\n}\n\nError\nstartSocketThread()\n{\n   auto error = IOS_CreateThread(&socketThreadEntry,\n                                 nullptr,\n                                 phys_addrof(sData->threadStack) + sData->threadStack.size(),\n                                 sData->threadStack.size(),\n                                 SocketThreadPriority,\n                                 ThreadFlags::Detached);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   sData->threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sData->threadId, \"SocketThread\");\n\n   return IOS_StartThread(sData->threadId);\n}\n\nError\nstopSocketThread()\n{\n   return IOS_JamMessage(sData->messageQueueId,\n                         makeMessage(phys_addrof(sData->stopMessage)),\n                         MessageFlags::NonBlocking);\n}\n\nvoid\ninitialiseStaticSocketData()\n{\n   sData = allocProcessStatic<StaticSocketThreadData>();\n   sData->stopMessage.command = Command::Suspend;\n}\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_socket_thread.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::net::internal\n{\n\nError\nregisterSocketResourceManager();\n\nError\nstartSocketThread();\n\nError\nstopSocketThread();\n\nvoid\ninitialiseStaticSocketData();\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_socket_types.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::net\n{\n\n/**\n * \\ingroup ios_net\n * @{\n */\n\n#pragma pack(push, 1)\n\nusing SocketHandle = int32_t;\nusing SocketFdSet = uint32_t;\n\nstruct SocketAddr\n{\n   be2_val<uint16_t> sa_family;\n   be2_array<uint8_t, 14> sa_data;\n};\nCHECK_OFFSET(SocketAddr, 0x00, sa_family);\nCHECK_OFFSET(SocketAddr, 0x02, sa_data);\nCHECK_SIZE(SocketAddr, 0x10);\n\nstruct SocketInAddr\n{\n   be2_val<uint32_t> s_addr_;\n};\nCHECK_OFFSET(SocketInAddr, 0x00, s_addr_);\nCHECK_SIZE(SocketInAddr, 0x04);\n\nstruct SocketHostEnt\n{\n   be2_phys_ptr<char> h_name;\n   be2_phys_ptr<char> h_aliases;\n   be2_val<int32_t> h_addrtype;\n   be2_val<int32_t> h_length;\n   be2_phys_ptr<char> h_addr_list;\n};\nCHECK_OFFSET(SocketHostEnt, 0x00, h_name);\nCHECK_OFFSET(SocketHostEnt, 0x04, h_aliases);\nCHECK_OFFSET(SocketHostEnt, 0x08, h_addrtype);\nCHECK_OFFSET(SocketHostEnt, 0x0C, h_length);\nCHECK_OFFSET(SocketHostEnt, 0x10, h_addr_list);\nCHECK_SIZE(SocketHostEnt, 0x14);\n\nstruct SocketAddrIn\n{\n   be2_val<uint16_t> sin_family;\n   be2_val<uint16_t> sin_port;\n   be2_struct<SocketInAddr> sin_addr;\n   be2_array<uint8_t, 8> sin_zero;\n};\nCHECK_OFFSET(SocketAddrIn, 0x00, sin_family);\nCHECK_OFFSET(SocketAddrIn, 0x02, sin_port);\nCHECK_OFFSET(SocketAddrIn, 0x04, sin_addr);\nCHECK_OFFSET(SocketAddrIn, 0x08, sin_zero);\nCHECK_SIZE(SocketAddrIn, 0x10);\n\nstruct SocketTimeval\n{\n   be2_val<int32_t> tv_sec;\n   be2_val<int32_t> tv_usec;\n};\nCHECK_OFFSET(SocketTimeval, 0x00, tv_sec);\nCHECK_OFFSET(SocketTimeval, 0x04, tv_usec);\nCHECK_SIZE(SocketTimeval, 0x08);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::net\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_soshim.cpp",
    "content": "#include \"ios_net_soshim.h\"\n#include \"ios_net_socket_request.h\"\n\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n\nnamespace ios::net\n{\n\nusing namespace kernel;\n\nstatic phys_ptr<void>\nallocIpcData(uint32_t size)\n{\n   auto buffer = IOS_HeapAlloc(CrossProcessHeapId, size);\n\n   if (buffer) {\n      std::memset(buffer.get(), 0, size);\n   }\n\n   return buffer;\n}\n\nstatic void\nfreeIpcData(phys_ptr<void> data)\n{\n   IOS_HeapFree(CrossProcessHeapId, data);\n}\n\nError\nSOShim_Open()\n{\n   return IOS_Open(\"/dev/socket\", OpenMode::None);\n}\n\nError\nSOShim_Close(SOShimHandle handle)\n{\n   return IOS_Close(handle);\n}\n\nError\nSOShim_GetProcessSocketHandle(SOShimHandle handle,\n                           TitleId titleId,\n                           ProcessId processId)\n{\n   auto request = phys_cast<SocketGetProcessSocketHandle *>(allocIpcData(sizeof(SocketGetProcessSocketHandle)));\n   if (!request) {\n      return Error::Access;\n   }\n\n   request->titleId = titleId;\n   request->processId = processId;\n\n   auto error = IOS_Ioctl(handle,\n                          SocketCommand::GetProcessSocketHandle,\n                          request,\n                          sizeof(SocketGetProcessSocketHandle),\n                          nullptr, 0);\n\n   freeIpcData(request);\n   return static_cast<Error>(error);\n}\n\n} // namespace ios::net\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_soshim.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n\nnamespace ios::net\n{\n\nusing SOShimHandle = kernel::ResourceHandleId;\n\nError\nSOShim_Open();\n\nError\nSOShim_Close(SOShimHandle handle);\n\nError\nSOShim_GetProcessSocketHandle(SOShimHandle handle,\n                           TitleId titleId,\n                           ProcessId processId);\n\n} // namespace ios::net\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_subsys.cpp",
    "content": "#include \"ios_net_subsys.h\"\n#include \"ios_net_socket_thread.h\"\n\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\nnamespace ios::net::internal\n{\n\nconstexpr auto SubsysHeapSize = 0x23A000u;\nconstexpr auto InitThreadStackSize = 0x4000u;\nconstexpr auto InitThreadPriority = 69u;\n\nusing namespace kernel;\n\nstruct StaticSubsysData\n{\n   be2_val<HeapId> heap;\n   be2_array<uint8_t, InitThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticSubsysData>\nsData;\n\nstatic phys_ptr<void>\nsSubsysHeapBuffer;\n\nstatic Error\nsubsysInitThread(phys_ptr<void> /*context*/)\n{\n   // Read network config\n\n   // Start ifmgr thread\n\n   // Start socket thread\n   auto error = startSocketThread();\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Start ifnet thread\n\n   // Create some weird timer thread\n\n   return Error::OK;\n}\n\nError\ninitSubsys()\n{\n   // Init ifmgr resource manager\n\n   // Init socket resource manager\n   auto error = registerSocketResourceManager();\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Init ifnet resource manager\n   return Error::OK;\n}\n\nError\nstartSubsys()\n{\n   auto error = IOS_CreateHeap(sSubsysHeapBuffer, SubsysHeapSize);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   error = IOS_CreateThread(&subsysInitThread,\n                            nullptr,\n                            phys_addrof(sData->threadStack) + sData->threadStack.size(),\n                            sData->threadStack.size(),\n                            InitThreadPriority,\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      return error;\n   }\n\n   auto threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(threadId, \"NetSubsysThread\");\n   return IOS_StartThread(threadId);\n}\n\nError\nstopSubsys()\n{\n   stopSocketThread();\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticSubsysData()\n{\n   sData = allocProcessStatic<StaticSubsysData>();\n   sSubsysHeapBuffer = allocProcessLocalHeap(SubsysHeapSize);\n}\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/net/ios_net_subsys.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::net::internal\n{\n\nError\ninitSubsys();\n\nError\nstartSubsys();\n\nError\nstopSubsys();\n\nvoid\ninitialiseStaticSubsysData();\n\n} // namespace ios::net::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nim/ios_nim.cpp",
    "content": "#include \"ios_nim.h\"\n#include \"ios_nim_log.h\"\n#include \"ios_nim_boss_server.h\"\n#include \"ios_nim_nim_server.h\"\n\n#include \"decaf_log.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/nn/ios_nn.h\"\n\nusing namespace ios::kernel;\n\nnamespace ios::nim\n{\n\nconstexpr auto LocalHeapSize = 0x20000u;\nconstexpr auto CrossHeapSize = 0x24E400u;\n\nstatic phys_ptr<void> sLocalHeapBuffer = nullptr;\n\nnamespace internal\n{\n\nLogger nimLog = { };\n\nvoid\ninitialiseStaticData()\n{\n   sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize);\n}\n\n} // namespace internal\n\nError\nprocessEntryPoint(phys_ptr<void> /* context */)\n{\n   auto error = Error::OK;\n\n   // Initialise logger\n   internal::nimLog = decaf::makeLogger(\"IOS_NIM\");\n\n   // Initialise static memory\n   internal::initialiseStaticData();\n   internal::initialiseStaticBossServerData();\n   internal::initialiseStaticNimServerData();\n\n   // Initialise nn for current process\n   nn::initialiseProcess();\n\n   // Initialise process heaps\n   error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize);\n   if (error < Error::OK) {\n      internal::nimLog->error(\n         \"processEntryPoint: IOS_CreateLocalProcessHeap failed with error = {}\", error);\n      return error;\n   }\n\n   error = IOS_CreateCrossProcessHeap(CrossHeapSize);\n   if (error < Error::OK) {\n      internal::nimLog->error(\n         \"processEntryPoint: IOS_CreateCrossProcessHeap failed with error = {}\", error);\n      return error;\n   }\n\n   // Start /dev/boss server\n   error = internal::startBossServer();\n   if (error < Error::OK) {\n      internal::nimLog->error(\"Failed to start boss server\");\n      return error;\n   }\n\n   // Start /dev/nim server\n   error = internal::startNimServer();\n   if (error < Error::OK) {\n      internal::nimLog->error(\"Failed to start nim server\");\n      return error;\n   }\n\n   internal::joinNimServer();\n   internal::joinBossServer();\n\n   return Error::OK;\n}\n\n} // namespace ios::nim\n"
  },
  {
    "path": "src/libdecaf/src/ios/nim/ios_nim.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_process.h\"\n\nnamespace ios::nim\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::nim\n"
  },
  {
    "path": "src/libdecaf/src/ios/nim/ios_nim_boss_privilegedservice.cpp",
    "content": "#include \"ios_nim_boss_privilegedservice.h\"\n\n#include \"ios/nn/ios_nn_ipc_server_command.h\"\n#include \"nn/boss/nn_boss_result.h\"\n#include \"nn/ipc/nn_ipc_result.h\"\n\nusing namespace nn::boss;\n\nusing nn::ipc::CommandHandlerArgs;\nusing nn::ipc::CommandId;\nusing nn::ipc::OutBuffer;\nusing nn::ipc::ServerCommand;\n\nnamespace ios::nim::internal\n{\n\nstatic nn::Result\naddAccount(CommandHandlerArgs &args)\n{\n   auto command = ServerCommand<PrivilegedService::AddAccount> { args };\n   auto persistentId = PersistentId { 0 };\n   command.ReadRequest(persistentId);\n\n   // TODO: Implement nn::boss::PrivilegedService::AddAccount\n\n   return ResultSuccess;\n}\n\nnn::Result\nPrivilegedService::commandHandler(uint32_t unk1,\n                                  CommandId command,\n                                  CommandHandlerArgs &args)\n{\n   switch (command) {\n   case AddAccount::command:\n      return addAccount(args);\n   default:\n      return nn::ipc::ResultInvalidMethodTag;\n   }\n}\n\n} // namespace ios::nim::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nim/ios_nim_boss_privilegedservice.h",
    "content": "#pragma once\n#include \"ios/nn/ios_nn_ipc_server.h\"\n#include \"nn/boss/nn_boss_privileged_service.h\"\n\nnamespace ios::nim::internal\n{\n\nstruct PrivilegedService : ::nn::boss::services::PrivilegedService\n{\n   static nn::Result\n   commandHandler(uint32_t unk1,\n                  nn::ipc::CommandId command,\n                  nn::ipc::CommandHandlerArgs &args);\n};\n\n} // namespace ios::acp\n"
  },
  {
    "path": "src/libdecaf/src/ios/nim/ios_nim_boss_server.cpp",
    "content": "#include \"ios_nim_log.h\"\n#include \"ios_nim_boss_server.h\"\n#include \"ios_nim_boss_privilegedservice.h\"\n\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/nn/ios_nn_ipc_server.h\"\n\nnamespace ios::nim::internal\n{\n\nusing namespace kernel;\n\nconstexpr auto BossNumMessages = 0x64u;\nconstexpr auto BossThreadStackSize = 0x4000u;\nconstexpr auto BossThreadPriority = 50u;\n\nclass BossServer : public nn::ipc::Server\n{\npublic:\n   BossServer() :\n      nn::ipc::Server(true)\n   {\n   }\n};\n\nstruct StaticBossServerData\n{\n   be2_struct<BossServer> server;\n   be2_array<Message, BossNumMessages> messageBuffer;\n   be2_array<uint8_t, BossThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticBossServerData> sBossServerData = nullptr;\n\nError\nstartBossServer()\n{\n   auto &server = sBossServerData->server;\n   auto result = server.initialise(\"/dev/boss\",\n                                   phys_addrof(sBossServerData->messageBuffer),\n                                   static_cast<uint32_t>(sBossServerData->messageBuffer.size()));\n   if (result.failed()) {\n      internal::nimLog->error(\n         \"startBossServer: Server initialisation failed for /dev/boss, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   // TODO: Services 0, 2, 3, 4\n   server.registerService<PrivilegedService>();\n\n   result = server.start(phys_addrof(sBossServerData->threadStack) + sBossServerData->threadStack.size(),\n                         static_cast<uint32_t>(sBossServerData->threadStack.size()),\n                         BossThreadPriority);\n   if (result.failed()) {\n      internal::nimLog->error(\n         \"startBossServer: Server start failed for /dev/boss, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   return Error::OK;\n}\n\nError\njoinBossServer()\n{\n   sBossServerData->server.join();\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticBossServerData()\n{\n   sBossServerData = allocProcessStatic<StaticBossServerData>();\n}\n\n} // namespace ios::nim::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nim/ios_nim_boss_server.h",
    "content": "#include \"ios/ios_error.h\"\n\nnamespace ios::nim::internal\n{\n\nError\nstartBossServer();\n\nError\njoinBossServer();\n\nvoid\ninitialiseStaticBossServerData();\n\n} // namespace ios::nim::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nim/ios_nim_log.h",
    "content": "#pragma once\n#include <common/log.h>\n\nnamespace ios::nim::internal\n{\n\nextern Logger nimLog;\n\n} // namespace ios::nim::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nim/ios_nim_nim_server.cpp",
    "content": "#include \"ios_nim_log.h\"\n#include \"ios_nim_nim_server.h\"\n\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/nn/ios_nn_ipc_server.h\"\n\nnamespace ios::nim::internal\n{\n\nusing namespace kernel;\n\nconstexpr auto NimNumMessages = 0x64u;\nconstexpr auto NimThreadStackSize = 0x8000u;\nconstexpr auto NimThreadPriority = 50u;\n\nclass NimServer : public nn::ipc::Server\n{\npublic:\n   NimServer() :\n      nn::ipc::Server(true)\n   {\n   }\n};\n\nstruct StaticNimServerData\n{\n   be2_struct<NimServer> server;\n   be2_array<Message, NimNumMessages> messageBuffer;\n   be2_array<uint8_t, NimThreadStackSize> threadStack;\n};\n\nstatic phys_ptr<StaticNimServerData> sNimServerData = nullptr;\n\nError\nstartNimServer()\n{\n   auto &server = sNimServerData->server;\n   auto result = server.initialise(\"/dev/nim\",\n                                   phys_addrof(sNimServerData->messageBuffer),\n                                   static_cast<uint32_t>(sNimServerData->messageBuffer.size()));\n   if (result.failed()) {\n      internal::nimLog->error(\n         \"startMainServer: Server initialisation failed for /dev/nim, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   // TODO: Services 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18\n\n   result = server.start(phys_addrof(sNimServerData->threadStack) + sNimServerData->threadStack.size(),\n                         static_cast<uint32_t>(sNimServerData->threadStack.size()),\n                         NimThreadPriority);\n   if (result.failed()) {\n      internal::nimLog->error(\n         \"startMainServer: Server start failed for /dev/nim, result = {}\",\n         result.code());\n      return Error::FailInternal;\n   }\n\n   return Error::OK;\n}\n\nError\njoinNimServer()\n{\n   sNimServerData->server.join();\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticNimServerData()\n{\n   sNimServerData = allocProcessStatic<StaticNimServerData>();\n}\n\n} // namespace ios::nim::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nim/ios_nim_nim_server.h",
    "content": "#pragma once\n#include \"ios/ios_error.h\"\n\nnamespace ios::nim::internal\n{\n\nError\nstartNimServer();\n\nError\njoinNimServer();\n\nvoid\ninitialiseStaticNimServerData();\n\n} // namespace ios::nim::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn.cpp",
    "content": "#include \"ios_nn.h\"\n#include \"ios_nn_criticalsection.h\"\n\nnamespace nn\n{\n\nvoid\ninitialiseProcess()\n{\n   internal::initialiseProcessCriticalSectionData();\n}\n\nvoid\nuninitialiseProcess()\n{\n   internal::freeProcessCriticalSectionData();\n}\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn.h",
    "content": "#pragma once\n\nnamespace nn\n{\n\nvoid\ninitialiseProcess();\n\nvoid\nuninitialiseProcess();\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_criticalsection.cpp",
    "content": "#include \"ios_nn_criticalsection.h\"\n#include \"ios_nn_tls.h\"\n\n#include \"ios/kernel/ios_kernel_semaphore.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n\n#include <array>\n#include <common/decaf_assert.h>\n\nusing namespace ios::kernel;\n\n/**\n * nn implements N critical sections for a process using a few semaphores:\n *  - 1 semaphore per process for critical section data members.\n *  - 1 semaphore per process for condition variable data members.\n *  - 1 semaphore per thread, allocated in TLS.\n */\n\nnamespace nn\n{\n\nclass CriticalSection;\n\nstruct CriticalSectionWaiter\n{\n   SemaphoreId semaphore;\n   CriticalSection *waitObject;\n   CriticalSectionWaiter *prev;\n   CriticalSectionWaiter *next;\n};\n\nstruct PerProcessCriticalSectionData\n{\n   SemaphoreId criticalSectionSemaphore { -1 };\n   SemaphoreId conditionVariableSemaphore { -1 };\n   TlsEntry threadSemaphoreEntry { };\n   CriticalSectionWaiter *waiters { nullptr };\n};\n\nstatic std::array<PerProcessCriticalSectionData, NumIosProcess>\nsPerProcessCriticalSectionData { };\n\nstatic PerProcessCriticalSectionData &\ngetProcessCriticalSectionData()\n{\n   auto error = IOS_GetCurrentProcessId();\n   decaf_check(error >= ::ios::Error::OK);\n   return sPerProcessCriticalSectionData[static_cast<size_t>(error)];\n}\n\nstatic SemaphoreId\ngetCriticalSectionProcessSemaphore()\n{\n   return getProcessCriticalSectionData().criticalSectionSemaphore;\n}\n\nstatic SemaphoreId\ngetConditionVariableThreadSemaphore()\n{\n   auto tls = tlsGetEntry(getProcessCriticalSectionData().threadSemaphoreEntry);\n   return *phys_cast<SemaphoreId *>(tls);\n}\n\nbool CriticalSection::try_lock()\n{\n   auto semaphore = getCriticalSectionProcessSemaphore();\n   auto result = false;\n\n   IOS_WaitSemaphore(semaphore, FALSE);\n   if (!mEntered) {\n      result = true;\n      mEntered = true;\n   }\n\n   IOS_SignalSempahore(semaphore);\n   return result;\n}\n\nvoid CriticalSection::lock()\n{\n   auto processData = getProcessCriticalSectionData();\n\n   // Fast path, try lock with no contention\n   IOS_WaitSemaphore(processData.criticalSectionSemaphore, FALSE);\n   if (!mEntered) {\n      mEntered = true;\n      IOS_SignalSempahore(processData.criticalSectionSemaphore);\n      return;\n   }\n\n   mWaiters++;\n   IOS_SignalSempahore(processData.criticalSectionSemaphore);\n\n   // Contention path, wait on condition variable\n   while (true) {\n      waitWaiterConditionVariable();\n\n      IOS_WaitSemaphore(processData.criticalSectionSemaphore, FALSE);\n      if (!mEntered) {\n         --mWaiters;\n         IOS_SignalSempahore(processData.criticalSectionSemaphore);\n         return;\n      }\n\n      IOS_SignalSempahore(processData.criticalSectionSemaphore);\n   }\n}\n\nvoid CriticalSection::unlock()\n{\n   auto processData = getProcessCriticalSectionData();\n   auto wakeWaiters = false;\n\n   IOS_WaitSemaphore(processData.criticalSectionSemaphore, FALSE);\n   mEntered = false;\n   if (mWaiters) {\n      wakeWaiters = true;\n   }\n   IOS_SignalSempahore(processData.criticalSectionSemaphore);\n\n   if (wakeWaiters) {\n      signalWaiterConditionVariable(1);\n   }\n}\n\nvoid CriticalSection::waitWaiterConditionVariable()\n{\n   auto processData = getProcessCriticalSectionData();\n   IOS_WaitSemaphore(processData.conditionVariableSemaphore, FALSE);\n   if (!mEntered) {\n      auto waiter = CriticalSectionWaiter {\n         getConditionVariableThreadSemaphore(), this,\n         nullptr, nullptr\n      };\n\n      if (processData.waiters) {\n         waiter.next = processData.waiters->next;\n         waiter.prev = processData.waiters;\n\n         processData.waiters->next->prev = &waiter;\n         processData.waiters->next = &waiter;\n      } else {\n         waiter.next = &waiter;\n         waiter.prev = &waiter;\n\n         processData.waiters = &waiter;\n      }\n\n      IOS_SignalSempahore(processData.conditionVariableSemaphore);\n      IOS_WaitSemaphore(waiter.semaphore, FALSE);\n      decaf_check(!waiter.next && !waiter.prev);\n      IOS_WaitSemaphore(processData.conditionVariableSemaphore, FALSE);\n   }\n   IOS_SignalSempahore(processData.conditionVariableSemaphore);\n}\n\nvoid CriticalSection::signalWaiterConditionVariable(int wakeCount)\n{\n   auto processData = getProcessCriticalSectionData();\n   IOS_WaitSemaphore(processData.conditionVariableSemaphore, FALSE);\n\n   while (wakeCount) {\n      if (!processData.waiters) {\n         // No waiters\n         break;\n      }\n\n      // Find a waiter for this wait object\n      auto waiter = static_cast<CriticalSectionWaiter *>(nullptr);\n      auto start = processData.waiters;\n      auto end = processData.waiters->next;\n\n      for (auto itr = start; itr && itr != end; itr = itr->prev) {\n         decaf_check(itr->next && itr->prev);\n\n         if (itr->waitObject == this) {\n            waiter = itr;\n            break;\n         }\n      }\n\n      if (!waiter) {\n         // No waiters for this wait object\n         break;\n      }\n\n      // Remove waiter from queue\n      if (waiter->next == waiter) {\n         // This was the only item in queue\n         processData.waiters = nullptr;\n      } else {\n         auto prev = waiter->prev;\n         auto next = waiter->next;\n\n         prev->next = next;\n         next->prev = prev;\n      }\n\n      waiter->prev = nullptr;\n      waiter->next = nullptr;\n      --wakeCount;\n      IOS_SignalSempahore(waiter->semaphore);\n   }\n\n   IOS_SignalSempahore(processData.conditionVariableSemaphore);\n}\n\nnamespace internal\n{\n\nvoid\ninitialiseProcessCriticalSectionData()\n{\n   auto &data = getProcessCriticalSectionData();\n   data.criticalSectionSemaphore = IOS_CreateSemaphore(1, 1);\n   data.conditionVariableSemaphore = IOS_CreateSemaphore(1, 1);\n\n   tlsAllocateEntry(data.threadSemaphoreEntry,\n                    [](TlsEntryEvent event,\n                       phys_ptr<void> dst,\n                       phys_ptr<void> copySrc) -> uint32_t\n                    {\n                       auto data = phys_cast<SemaphoreId *>(dst);\n                       auto copyData = phys_cast<SemaphoreId *>(copySrc);\n\n                       switch (event) {\n                       case TlsEntryEvent::Create:\n                          *data = IOS_CreateSemaphore(1, 0);\n                          break;\n                       case TlsEntryEvent::Destroy:\n                          IOS_DestroySempahore(*data);\n                          break;\n                       case TlsEntryEvent::Copy:\n                          *data = *copyData;\n                          return 0u;\n                       case TlsEntryEvent::GetSize:\n                          return sizeof(SemaphoreId);\n                       }\n\n                       return sizeof(SemaphoreId);\n                    });\n}\n\nvoid\nfreeProcessCriticalSectionData()\n{\n   auto &data = getProcessCriticalSectionData();\n   IOS_DestroySempahore(data.criticalSectionSemaphore);\n   IOS_DestroySempahore(data.conditionVariableSemaphore);\n}\n\n} // namespace internal\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_criticalsection.h",
    "content": "#pragma once\n\nnamespace nn\n{\n\nclass CriticalSection\n{\npublic:\n   void lock();\n   bool try_lock();\n   void unlock();\n\nprivate:\n   void waitWaiterConditionVariable();\n   void signalWaiterConditionVariable(int wakeCount);\n\nprivate:\n   bool mEntered = false;\n   int mWaiters = 0;\n};\n\nnamespace internal\n{\n\nvoid\ninitialiseProcessCriticalSectionData();\n\nvoid\nfreeProcessCriticalSectionData();\n\n} // namespace internal\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_ipc_server.cpp",
    "content": "#include \"ios_nn_ipc_server.h\"\n#include \"ios_nn_tls.h\"\n\n#include \"ios/ios_stackobject.h\"\n#include \"ios/acp/ios_acp_nnsm_ipc.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/kernel/ios_kernel_timer.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n#include \"ios/mcp/ios_mcp_ipc.h\"\n#include \"nn/ios/nn_ios_error.h\"\n#include \"nn/ipc/nn_ipc_format.h\"\n\nusing namespace ios;\nusing namespace ios::acp;\nusing namespace ios::kernel;\nusing namespace ios::mcp;\n\nnamespace nn::ipc\n{\n\nResult\nServer::initialise(std::string_view deviceName,\n                   phys_ptr<Message> messageBuffer,\n                   uint32_t numMessages)\n{\n   auto error = IOS_CreateMessageQueue(messageBuffer, numMessages);\n   if (error < ::ios::Error::OK) {\n      return ::nn::ios::convertError(error);\n   }\n   mQueueId = static_cast<MessageQueueId>(error);\n\n   error = IOS_CreateTimer(std::chrono::microseconds(0),\n                           std::chrono::microseconds(0),\n                           mQueueId,\n                           mTimerMessage);\n   if (error < ::ios::Error::OK) {\n      return ::nn::ios::convertError(error);\n   }\n   mTimer = static_cast<TimerId>(error);\n\n   if (mIsMcpResourceManager) {\n      error = MCP_RegisterResourceManager(deviceName, mQueueId);\n   } else {\n      error = IOS_RegisterResourceManager(deviceName, mQueueId);\n   }\n\n   mDeviceName = deviceName;\n   return ::nn::ios::convertError(error);\n}\n\nvoid\nServer::registerService(ServiceId serviceId,\n                        CommandHandler commandHandler)\n{\n   for (auto &service : mServices) {\n      if (!service.handler) {\n         service.id = serviceId;\n         service.handler = commandHandler;\n         break;\n      }\n   }\n}\n\nError\nServer::threadEntryWrapper(phys_ptr<void> ptr)\n{\n   auto self = phys_cast<Server *>(ptr);\n   return static_cast<Error>(self->threadEntry().code());\n}\n\nResult\nServer::waitForResume()\n{\n   StackObject<Message> message;\n\n   // Read Open\n   auto error = IOS_ReceiveMessage(mQueueId, message, MessageFlags::None);\n   if (error < Error::OK) {\n      return ::nn::ios::convertError(error);\n   }\n\n   auto request = parseMessage<ResourceRequest>(message);\n   if (request->requestData.command != ::ios::Command::Open) {\n      return ::nn::ios::convertError(Error::FailInternal);\n   }\n\n   if (error = IOS_ResourceReply(request, Error::OK); error < Error::OK) {\n      return ::nn::ios::convertError(error);\n   }\n\n   // Read Resume\n   error = IOS_ReceiveMessage(mQueueId, message, MessageFlags::None);\n   if (error < Error::OK) {\n      return ::nn::ios::convertError(error);\n   }\n\n   request = parseMessage<ResourceRequest>(message);\n   if (request->requestData.command != ::ios::Command::Resume) {\n      return ::nn::ios::convertError(Error::FailInternal);\n   }\n\n   mResumeArgs = request->ipcRequest->args.resume;\n\n   if (error = IOS_ResourceReply(request, Error::OK); error < Error::OK) {\n      return ::nn::ios::convertError(error);\n   }\n\n   return ::nn::ios::convertError(Error::OK);\n}\n\nError\nServer::openDeviceHandle(uint64_t caps,\n                             ProcessId processId)\n{\n   for (auto &deviceHandle : mDeviceHandles) {\n      if (deviceHandle.open) {\n         continue;\n      }\n\n      deviceHandle.open = true;\n      deviceHandle.caps = caps;\n      deviceHandle.processId = processId;\n\n      auto index = &deviceHandle - &mDeviceHandles[0];\n      return static_cast<Error>(index);\n   }\n\n   return Error::Max;\n}\n\nError\nServer::closeDeviceHandle(DeviceHandleId handleId)\n{\n   if (handleId >= mDeviceHandles.size()) {\n      return Error::InvalidHandle;\n   }\n\n   auto &deviceHandle = mDeviceHandles[handleId];\n   if (!deviceHandle.open) {\n      return Error::InvalidHandle;\n   }\n\n   deviceHandle.open = false;\n   deviceHandle.processId = ProcessId::Invalid;\n   deviceHandle.caps = 0ull;\n   return Error::OK;\n}\n\nError\nServer::handleMessage(phys_ptr<ResourceRequest> request)\n{\n   auto &ioctlv = request->requestData.args.ioctlv;\n\n   if (!ioctlv.numVecOut ||\n       ioctlv.vecs[ioctlv.numVecIn].len < sizeof(RequestHeader) ||\n       ioctlv.vecs[0].len < sizeof(ResponseHeader)) {\n      return Error::InvalidArg;\n   }\n\n   auto requestHeader = phys_cast<RequestHeader *>(ioctlv.vecs[ioctlv.numVecIn].paddr);\n   auto responseHeader = phys_cast<ResponseHeader *>(ioctlv.vecs[0].paddr);\n\n   CommandHandlerArgs args;\n   args.resourceRequest = request;\n   args.requestBuffer = requestHeader + 1;\n   args.requestBufferSize = ioctlv.vecs[ioctlv.numVecIn].len - sizeof(RequestHeader);\n\n   args.responseBuffer = responseHeader + 1;\n   args.responseBufferSize = ioctlv.vecs[0].len - sizeof(ResponseHeader);\n\n   args.numVecsIn = ioctlv.numVecIn;\n   args.numVecsOut = ioctlv.numVecOut;\n   args.vecs = ioctlv.vecs;\n\n   for (auto &service : mServices) {\n      if (service.id != static_cast<ServiceId>(requestHeader->service)) {\n         continue;\n      }\n\n      auto result = service.handler(requestHeader->unk0x08, requestHeader->command, args);\n      responseHeader->result = result.code();\n      return Error::OK;\n   }\n\n   return Error::InvalidArg;\n}\n\nResult\nServer::runMessageLoop()\n{\n   StackObject<Message> message;\n   auto error = Error::OK;\n\n   while (true) {\n      mMutex.lock();\n\n      error = IOS_ReceiveMessage(mQueueId, message, MessageFlags::None);\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case ::ios::Command::Open:\n      {\n         error = openDeviceHandle(request->requestData.args.open.caps,\n                                  request->requestData.processId);\n         IOS_ResourceReply(request, error);\n         break;\n      }\n      case ::ios::Command::Close:\n         error = closeDeviceHandle(static_cast<DeviceHandleId>(request->requestData.handle));\n         IOS_ResourceReply(request, error);\n         break;\n      case ::ios::Command::Ioctlv:\n         error = handleMessage(request);\n         IOS_ResourceReply(request, error);\n         break;\n      default:\n         IOS_ResourceReply(request, Error::Invalid);\n      }\n\n      mMutex.unlock();\n   }\n\n   return error;\n}\n\nResult\nServer::resolvePendingMessages()\n{\n   return Error::OK;\n}\n\nResult\nServer::threadEntry()\n{\n   if (mResourcePermissionGroup != ResourcePermissionGroup::None) {\n      IOS_AssociateResourceManager(mDeviceName, mResourcePermissionGroup);\n   }\n\n   if (auto result = waitForResume(); result.failed()) {\n      return result;\n   }\n\n   NNSM_RegisterServer(mDeviceName);\n   intialiseServer();\n   auto result = runMessageLoop();\n\n   finaliseServer();\n   resolvePendingMessages();\n   NNSM_UnregisterServer(mDeviceName);\n   return result;\n}\n\nResult\nServer::start(phys_ptr<uint8_t> stackTop,\n              uint32_t stackSize,\n              kernel::ThreadPriority priority)\n{\n   return mThread.start(Server::threadEntryWrapper,\n                        phys_this(this),\n                        stackTop,\n                        stackSize,\n                        priority);\n}\n\nvoid\nServer::join()\n{\n   mThread.join();\n}\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_ipc_server.h",
    "content": "#pragma once\n#include \"ios_nn_recursivemutex.h\"\n#include \"ios_nn_thread.h\"\n\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_ipc.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/kernel/ios_kernel_timer.h\"\n#include \"nn/ipc/nn_ipc_command.h\"\n#include \"nn/ipc/nn_ipc_service.h\"\n#include \"nn/nn_result.h\"\n\n#include <array>\n#include <cstdint>\n#include <string>\n#include <string_view>\n#include <vector>\n\nnamespace nn::ipc\n{\n\nstruct CommandHandlerArgs\n{\n   phys_ptr<ios::kernel::ResourceRequest> resourceRequest;\n   phys_ptr<void> requestBuffer;\n   uint32_t requestBufferSize;\n   phys_ptr<void> responseBuffer;\n   uint32_t responseBufferSize;\n   uint32_t numVecsIn;\n   uint32_t numVecsOut;\n   phys_ptr<::ios::IoctlVec> vecs;\n};\n\nusing DeviceHandleId = uint32_t;\n\nusing CommandHandler = Result(*)(uint32_t unk1,\n                                 CommandId command,\n                                 CommandHandlerArgs &args);\n\nclass Server\n{\n   static constexpr auto MaxNumDeviceHandles = 32u;\n   static constexpr auto MaxNumServices = 32u;\n\npublic:\n   struct DeviceHandle\n   {\n      bool open = false;\n      uint64_t caps;\n      ::ios::ProcessId processId;\n      RecursiveMutex mutex;\n   };\n\n   struct RegisteredService\n   {\n      ServiceId id;\n      CommandHandler handler = nullptr;\n   };\n\n   Server(bool isMcpResourceManager = false,\n          ::ios::kernel::ResourcePermissionGroup group = ::ios::kernel::ResourcePermissionGroup::None) :\n      mIsMcpResourceManager(isMcpResourceManager),\n      mResourcePermissionGroup(group)\n   {\n   }\n\n   Result initialise(std::string_view deviceName,\n                     phys_ptr<::ios::kernel::Message> messageBuffer,\n                     uint32_t numMessages);\n\n   Result start(phys_ptr<uint8_t> stackTop, uint32_t stackSize,\n                ::ios::kernel::ThreadPriority priority);\n   void join();\n\n   void registerService(ServiceId serviceId, CommandHandler commandHandler);\n\n   template<typename ServiceType>\n   void registerService()\n   {\n      registerService(ServiceType::id, ServiceType::commandHandler);\n   }\n\nprotected:\n   static ::ios::Error threadEntryWrapper(phys_ptr<void> ptr);\n\n   Result threadEntry();\n   Result waitForResume();\n   Result runMessageLoop();\n   Result resolvePendingMessages();\n\n   virtual void intialiseServer() { }\n   virtual void finaliseServer() { }\n\n   ::ios::Error openDeviceHandle(uint64_t caps, ::ios::ProcessId processId);\n   ::ios::Error closeDeviceHandle(DeviceHandleId handleId);\n   ::ios::Error handleMessage(phys_ptr<::ios::kernel::ResourceRequest> request);\n\nprotected:\n   bool mIsMcpResourceManager;\n   ::ios::kernel::MessageQueueId mQueueId = -1;\n   ::ios::kernel::TimerId mTimer = -1;\n   ::ios::kernel::Message mTimerMessage = static_cast<::ios::kernel::Message>(-32);\n   Thread mThread;\n   std::string mDeviceName;\n   std::array<DeviceHandle, MaxNumDeviceHandles> mDeviceHandles;\n   ::ios::kernel::ResourcePermissionGroup mResourcePermissionGroup;\n\n   RecursiveMutex mMutex;\n   std::array<RegisteredService, MaxNumServices> mServices;\n   ::ios::IpcRequestArgsResume mResumeArgs;\n};\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_ipc_server_command.h",
    "content": "#pragma once\n#include \"ios_nn_ipc_server.h\"\n#include \"nn/ipc/nn_ipc_format.h\"\n#include \"nn/ipc/nn_ipc_managedbuffer.h\"\n\nnamespace nn::ipc\n{\n\nnamespace detail\n{\n\ntemplate<typename Type>\nstruct IpcDeserialiser\n{\n   static void\n   read(const CommandHandlerArgs &args, size_t &offset, Type &value)\n   {\n      auto ptr =\n         phys_cast<Type *>(phys_cast<phys_addr>(args.requestBuffer) + offset);\n      value = *ptr;\n      offset += sizeof(Type);\n   }\n};\n\ntemplate<>\nstruct IpcDeserialiser<ManagedBuffer>\n{\n   static void\n   read(const CommandHandlerArgs &args,\n        size_t &offset,\n        ManagedBuffer &value)\n   {\n      auto managedBuffer =\n         phys_cast<ManagedBufferParameter *>(\n            phys_cast<phys_addr>(args.requestBuffer) + offset);\n\n      auto alignedBufferIndex = 1 + managedBuffer->alignedBufferIndex;\n      auto unalignedBufferIndex = 1 + managedBuffer->unalignedBufferIndex;\n\n      value.alignedBuffer =\n         phys_cast<void *>(args.vecs[alignedBufferIndex].paddr);\n      value.alignedBufferSize = managedBuffer->alignedBufferSize;\n\n      value.unalignedBeforeBuffer =\n         phys_cast<void *>(args.vecs[unalignedBufferIndex].paddr);\n      value.unalignedBeforeBufferSize = managedBuffer->unalignedBeforeBufferSize;\n\n      value.unalignedAfterBuffer =\n         phys_cast<void *>(args.vecs[unalignedBufferIndex].paddr\n                           + managedBuffer->unalignedBeforeBufferSize);\n      value.unalignedAfterBufferSize = managedBuffer->unalignedAfterBufferSize;\n\n      offset += sizeof(ManagedBufferParameter);\n   }\n};\n\ntemplate<typename Type>\nstruct IpcDeserialiser<ipc::InBuffer<Type>>\n{\n   static void read(const CommandHandlerArgs &args, size_t &offset,\n                    ipc::InBuffer<Type> &value)\n   {\n      IpcDeserialiser<ManagedBuffer>::read(args, offset, value);\n   }\n};\n\ntemplate<typename Type>\nstruct IpcDeserialiser<ipc::InOutBuffer<Type>>\n{\n   static void read(const CommandHandlerArgs &args, size_t &offset,\n                    ipc::InOutBuffer<Type> &value)\n   {\n      IpcDeserialiser<ManagedBuffer>::read(args, offset, value);\n   }\n};\n\ntemplate<typename Type>\nstruct IpcDeserialiser<ipc::OutBuffer<Type>>\n{\n   static void read(const CommandHandlerArgs &args, size_t &offset,\n                    ipc::OutBuffer<Type> &value)\n   {\n      IpcDeserialiser<ManagedBuffer>::read(args, offset, value);\n   }\n};\n\ntemplate<typename Type>\nstruct IpcSerialiser\n{\n   static void write(const CommandHandlerArgs &args, size_t &offset,\n                     const Type &value)\n   {\n      auto ptr =\n         phys_cast<Type *>(phys_cast<phys_addr>(args.responseBuffer) + offset);\n      *ptr = value;\n      offset += sizeof(Type);\n   }\n};\n\ntemplate<int, int, typename... Types>\nstruct ServerCommandHelper;\n\ntemplate<int ServiceId, int CommandId,\n         typename... ParameterTypes,\n         typename... ResponseTypes>\nstruct ServerCommandHelper<ServiceId, CommandId,\n                           std::tuple<ParameterTypes...>,\n                           std::tuple<ResponseTypes...>>\n{\n   ServerCommandHelper(const CommandHandlerArgs &args) :\n      mArgs(args)\n   {\n   }\n\n   void ReadRequest(ParameterTypes &... parameters)\n   {\n      auto offset = size_t { 0 };\n      (IpcDeserialiser<ParameterTypes>::read(mArgs, offset, parameters), ...);\n   }\n\n   void WriteResponse(const ResponseTypes &... responses)\n   {\n      auto offset = size_t { 0 };\n      (IpcSerialiser<ResponseTypes>::write(mArgs, offset, responses), ...);\n   }\n\n   const CommandHandlerArgs &mArgs;\n};\n\n} // namespace detail\n\ntemplate<typename CommandType>\nstruct ServerCommand;\n\ntemplate<typename CommandType>\nstruct ServerCommand :\n   detail::ServerCommandHelper<CommandType::service,\n                               CommandType::command,\n                               typename CommandType::parameters,\n                               typename CommandType::response>\n{\n   ServerCommand(const CommandHandlerArgs &args) :\n      detail::ServerCommandHelper<CommandType::service,\n                                  CommandType::command,\n                                  typename CommandType::parameters,\n                                  typename CommandType::response>(args)\n   {\n   }\n};\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_recursivemutex.cpp",
    "content": "#include \"ios_nn_recursivemutex.h\"\n\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include <common/decaf_assert.h>\n\nusing namespace ios::kernel;\n\nnamespace nn\n{\n\nvoid\nRecursiveMutex::lock()\n{\n   auto threadId = IOS_GetCurrentThreadId();\n   if (mRecursionCount == 0) {\n      mOwnerThread = threadId;\n   }\n\n   if (mOwnerThread != static_cast<ThreadId>(threadId)) {\n      mCriticalSection.lock();\n      mOwnerThread = threadId;\n      decaf_check(mRecursionCount == 0);\n   }\n\n   ++mRecursionCount;\n}\n\nbool\nRecursiveMutex::try_lock()\n{\n   auto threadId = IOS_GetCurrentThreadId();\n   if (mRecursionCount == 0) {\n      mOwnerThread = threadId;\n   }\n\n   if (mOwnerThread != static_cast<ThreadId>(threadId)) {\n      return false;\n   }\n\n   ++mRecursionCount;\n   return true;\n}\n\nvoid\nRecursiveMutex::unlock()\n{\n   decaf_check(locked());\n   --mRecursionCount;\n\n   if (mRecursionCount == 0) {\n      mOwnerThread = static_cast<ThreadId>(-1);\n      mCriticalSection.unlock();\n   }\n}\n\nbool\nRecursiveMutex::locked()\n{\n   return mRecursionCount > 0 && mOwnerThread == static_cast<ThreadId>(IOS_GetCurrentThreadId());\n}\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_recursivemutex.h",
    "content": "#pragma once\n#include \"ios_nn_criticalsection.h\"\n\n#include \"ios/kernel/ios_kernel_thread.h\"\n\nnamespace nn\n{\n\nclass RecursiveMutex\n{\npublic:\n   void lock();\n   bool try_lock();\n   void unlock();\n\n   bool locked();\n\nprivate:\n   int mRecursionCount = 0;\n   ios::kernel::ThreadId mOwnerThread = static_cast<ios::kernel::ThreadId>(-1);\n   CriticalSection mCriticalSection;\n};\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_thread.cpp",
    "content": "#include \"ios_nn_thread.h\"\n#include \"ios_nn_tls.h\"\n\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"nn/ios/nn_ios_error.h\"\n\n#include <common/decaf_assert.h>\n\nusing namespace ios::kernel;\n\nnamespace nn\n{\n\nThread::Thread(Thread &&other) noexcept\n{\n   mThreadId = other.mThreadId;\n   mJoined = other.mJoined;\n\n   other.mThreadId = static_cast<native_handle_type>(-1);\n   other.mJoined = true;\n}\n\nThread::~Thread()\n{\n   decaf_check(!joinable());\n}\n\nstruct ThreadStartData\n{\n   ThreadEntryFn entry;\n   phys_ptr<void> context;\n   phys_ptr<void> tlsData;\n};\n\nstatic ::ios::Error\nthreadEntryPoint(phys_ptr<void> arg)\n{\n   auto startData = phys_cast<ThreadStartData *>(arg);\n   tlsInitialiseThread(startData->tlsData);\n   auto result = startData->entry(startData->context);\n   tlsDestroyData(startData->tlsData);\n   return result;\n}\n\nResult\nThread::start(ThreadEntryFn entry,\n              phys_ptr<void> context,\n              phys_ptr<uint8_t> stackTop,\n              uint32_t stackSize,\n              ThreadPriority priority)\n{\n   auto tlsDataSize = tlsGetDataSize();\n   auto userStackTop = stackTop;\n\n   // Allocate TLS data from stack\n   stackTop = align_down(stackTop - tlsDataSize, 8);\n   auto tlsData = phys_cast<void *>(stackTop);\n   tlsInitialiseData(tlsData);\n\n   // Allocate thread start context from stack\n   stackTop = align_down(stackTop - sizeof(ThreadStartData), 8);\n   auto threadStartData = phys_cast<ThreadStartData *>(stackTop);\n   threadStartData->entry = entry;\n   threadStartData->context = context;\n   threadStartData->tlsData = tlsData;\n\n   // Calculate new stack size\n   stackSize -= static_cast<uint32_t>(userStackTop - stackTop);\n\n   auto error = IOS_CreateThread(threadEntryPoint,\n                                 threadStartData,\n                                 stackTop,\n                                 stackSize,\n                                 priority,\n                                 ThreadFlags::AllocateTLS);\n   if (error < ::ios::Error::OK) {\n      return ios::convertError(error);\n   }\n\n   mThreadId = static_cast<ThreadId>(error);\n\n   error = IOS_StartThread(mThreadId);\n   if (error < ::ios::Error::OK) {\n      return ios::convertError(error);\n   }\n\n   mJoined = false;\n   return ios::ResultOK;\n}\n\nvoid\nThread::join()\n{\n   if (joinable()) {\n      IOS_JoinThread(mThreadId, nullptr);\n      mJoined = true;\n   }\n}\n\nvoid\nThread::detach()\n{\n   mThreadId = static_cast<native_handle_type>(-1);\n   mJoined = true;\n}\n\nvoid\nThread::swap(Thread &other) noexcept\n{\n   std::swap(mThreadId, other.mThreadId);\n   std::swap(mJoined, other.mJoined);\n}\n\nThread::id\nThread::get_id() const noexcept\n{\n   return mThreadId;\n}\n\nThread::native_handle_type\nThread::native_handle() const noexcept\n{\n   return mThreadId;\n}\n\nbool\nThread::joinable() const noexcept\n{\n   return mThreadId != -1;\n}\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_thread.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"nn/nn_result.h\"\n\nnamespace nn\n{\n\nclass Thread\n{\npublic:\n   using id = ::ios::kernel::ThreadId;\n   using native_handle_type = ::ios::kernel::ThreadId;\n\n   Thread() noexcept = default;\n   Thread(const Thread &) = delete;\n   Thread(Thread &&other) noexcept;\n   ~Thread();\n\n   Result start(::ios::kernel::ThreadEntryFn entry,\n                phys_ptr<void> context,\n                phys_ptr<uint8_t> stackTop,\n                uint32_t stackSize,\n                ::ios::kernel::ThreadPriority priority);\n   void join();\n   void detach();\n   void swap(Thread &other) noexcept;\n\n   id get_id() const noexcept;\n   native_handle_type native_handle() const noexcept;\n   bool joinable() const noexcept;\n\n   static unsigned int hardware_concurrency() noexcept\n   {\n      return 1;\n   }\n\nprivate:\n   native_handle_type mThreadId = static_cast<native_handle_type>(-1);\n   bool mJoined = true;\n};\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_tls.cpp",
    "content": "#include \"ios_nn_tls.h\"\n\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n\n#include <array>\n#include <libcpu/be2_struct.h>\n\nusing namespace ios::kernel;\n\nnamespace nn\n{\n\nstruct NnThreadLocalStorage\n{\n   static constexpr auto Magic = 0x94826575u;\n\n   UNKNOWN(0x4);\n   be2_phys_ptr<void> tlsData;\n   be2_val<uint32_t> magic;\n   UNKNOWN(0x24 - 0x0C);\n};\nCHECK_OFFSET(NnThreadLocalStorage, 0x04, tlsData);\nCHECK_OFFSET(NnThreadLocalStorage, 0x08, magic);\nCHECK_SIZE(NnThreadLocalStorage, 0x24);\n\nstruct TlsTable\n{\n   TlsEntry *entries = nullptr;\n   uint32_t dataSize = 0u;\n};\n\nstatic std::array<TlsTable, NumIosProcess> sProcessTlsTables;\n\nstatic TlsTable *\ngetTlsTable()\n{\n   auto error = IOS_GetCurrentProcessId();\n   if (error < ::ios::Error::OK) {\n      return nullptr;\n   }\n\n   return &sProcessTlsTables[static_cast<size_t>(error)];\n}\n\n\n/**\n * Allocate a TLS entry for the current process.\n */\nvoid\ntlsAllocateEntry(TlsEntry &entry,\n                 TlsEntryEventFn eventCallback,\n                 bool supportsCopy)\n{\n   auto table = getTlsTable();\n\n   // Initialise entry\n   entry.eventFn = eventCallback;\n   entry.supportsCopy = supportsCopy;\n\n   // Assign data position\n   auto size = eventCallback(TlsEntryEvent::GetSize, nullptr, nullptr);\n   entry.dataOffset = table->dataSize;\n   table->dataSize += size;\n\n   // Insert into table\n   entry.next = table->entries;\n   table->entries = &entry;\n}\n\n\n/**\n * Get the data size of all TLS entries in the current process.\n *\n * This should be the size of the data passed to tlsInitialiseData.\n */\nuint32_t\ntlsGetDataSize()\n{\n   return getTlsTable()->dataSize;\n}\n\n\n/**\n * Initialise a TLS data block for the current process.\n */\nvoid\ntlsInitialiseData(phys_ptr<void> data,\n                  phys_ptr<void> copySrc)\n{\n   auto table = getTlsTable();\n   if (copySrc) {\n      decaf_abort(\"TODO: Implement tlsInitialiseData for copies\");\n   }\n\n   auto dataAddr = phys_cast<phys_addr>(data);\n   std::memset(data.get(), 0, table->dataSize);\n\n   for (auto entry = table->entries; entry; entry = entry->next) {\n      entry->eventFn(TlsEntryEvent::Create,\n                     phys_cast<void *>(dataAddr + entry->dataOffset),\n                     nullptr);\n   }\n}\n\n\n/**\n * Clean up a TLS data block for the current process.\n */\nvoid\ntlsDestroyData(phys_ptr<void> data)\n{\n   auto table = getTlsTable();\n   auto dataAddr = phys_cast<phys_addr>(data);\n   for (auto entry = table->entries; entry; entry = entry->next) {\n      entry->eventFn(TlsEntryEvent::Destroy,\n                     phys_cast<void *>(dataAddr + entry->dataOffset),\n                     nullptr);\n   }\n}\n\n\n/**\n * Initialise the current thread's local storage.\n */\nvoid\ntlsInitialiseThread(phys_ptr<void> data)\n{\n   auto tls = phys_cast<NnThreadLocalStorage *>(IOS_GetCurrentThreadLocalStorage());\n   tls->magic = NnThreadLocalStorage::Magic;\n   tls->tlsData = data;\n}\n\n\n/**\n * Get the data pointer for the current thread's TLS entry.\n */\nphys_ptr<void>\ntlsGetEntry(TlsEntry &entry)\n{\n   auto tls = phys_cast<NnThreadLocalStorage *>(IOS_GetCurrentThreadLocalStorage());\n   if (!tls || !tls->tlsData) {\n      return nullptr;\n   }\n\n   return phys_cast<void *>(phys_cast<phys_addr>(tls->tlsData) + entry.dataOffset);\n}\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/ios/nn/ios_nn_tls.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace nn\n{\n\nenum class TlsEntryEvent\n{\n   GetSize     = 0,\n   Create      = 1,\n   Destroy     = 2,\n   Copy        = 3,\n};\n\nusing TlsEntryEventFn = uint32_t(*)(TlsEntryEvent event,\n                                    phys_ptr<void> dst,\n                                    phys_ptr<void> copySrc);\n\nstruct TlsEntry\n{\n   uint32_t dataOffset;\n   TlsEntry *next;\n   TlsEntryEventFn eventFn = nullptr;\n   bool supportsCopy;\n};\n/*\nCHECK_OFFSET(TlsEntry, 0x0, dataOffset);\nCHECK_OFFSET(TlsEntry, 0x4, next);\nCHECK_OFFSET(TlsEntry, 0x8, entryEventFn);\nCHECK_OFFSET(TlsEntry, 0xC, unk0x0c); // I think this is maybe a can copy flag\nCHECK_SIZE(TlsEntry, 0x10);\n*/\n\nvoid\ntlsAllocateEntry(TlsEntry &entry,\n                 TlsEntryEventFn eventCallback,\n                 bool supportsCopy = false);\n\nuint32_t\ntlsGetDataSize();\n\nvoid\ntlsInitialiseData(phys_ptr<void> data,\n                  phys_ptr<void> copySrc = nullptr);\n\nvoid\ntlsDestroyData(phys_ptr<void> data);\n\nvoid\ntlsInitialiseThread(phys_ptr<void> data);\n\nphys_ptr<void>\ntlsGetEntry(TlsEntry &entry);\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec.cpp",
    "content": "#include \"ios_nsec.h\"\n#include \"ios_nsec_log.h\"\n#include \"ios_nsec_nssl_certstore.h\"\n#include \"ios_nsec_nssl_thread.h\"\n\n#include \"decaf_log.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/mcp/ios_mcp_ipc.h\"\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <common/log.h>\n#include <libcpu/be2_struct.h>\n\nusing namespace ios::kernel;\n\nnamespace ios::nsec\n{\n\nconstexpr auto LocalHeapSize = 0x140000u;\nconstexpr auto CrossHeapSize = 0x200000u;\nconstexpr auto InitThreadStackSize = 0x1000u;\nconstexpr auto InitThreadPriority = 50u;\n\n\nstruct StaticNsecData\n{\n   be2_val<BOOL> started;\n   be2_array<uint8_t, InitThreadStackSize> threadStack;\n   be2_val<ThreadId> initThreadId;\n};\n\nstatic phys_ptr<StaticNsecData>\nsNsecData = nullptr;\n\nstatic phys_ptr<void>\nsLocalHeapBuffer = nullptr;\n\nnamespace internal\n{\n\nLogger nsecLog = { };\n\nstatic void\ninitialiseStaticData()\n{\n   sNsecData = allocProcessStatic<StaticNsecData>();\n   sLocalHeapBuffer = allocProcessLocalHeap(LocalHeapSize);\n}\n\nstatic Error\ninitThread(phys_ptr<void> /*context*/)\n{\n   // Some FSA stuff\n\n   // Some cert stuff\n\n   // Start nssl thread\n   auto error = startNsslThread();\n   if (error < Error::OK) {\n      return error;\n   }\n\n   // Start nss thread\n\n   return Error::OK;\n}\n\n} // namespace internal\n\nError\nprocessEntryPoint(phys_ptr<void> context)\n{\n   StackArray<Message, 10> messageBuffer;\n   StackObject<Message> message;\n\n   // Initialise logger\n   internal::nsecLog = decaf::makeLogger(\"IOS_NSEC\");\n\n   // Initialise static memory\n   internal::initialiseStaticData();\n   internal::initialiseStaticNsslData();\n   internal::initialiseStaticCertStoreData();\n\n   // Initialise process heaps\n   auto error = IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize);\n   if (error < Error::OK) {\n      internal::nsecLog->error(\"NSEC: Failed to create local process heap, error = {}.\", error);\n      return error;\n   }\n\n   error = IOS_CreateCrossProcessHeap(CrossHeapSize);\n   if (error < Error::OK) {\n      internal::nsecLog->error(\"NSEC: Failed to create cross process heap, error = {}.\", error);\n      return error;\n   }\n\n   error = internal::registerNsslResourceManager();\n   if (error < Error::OK) {\n      internal::nsecLog->error(\"NSEC: Failed to register NSSL resource manager, error = {}.\", error);\n      return error;\n   }\n   // TODO: registerNssResourceManager\n\n   // Setup nsec\n   error = IOS_CreateMessageQueue(messageBuffer, messageBuffer.size());\n   if (error < Error::OK) {\n      internal::nsecLog->error(\"NSEC: Failed to create nsec proc message queue, error = {}.\", error);\n      return error;\n   }\n   auto messageQueueId = static_cast<MessageQueueId>(error);\n\n   error = mcp::MCP_RegisterResourceManager(\"/dev/nsec\", messageQueueId);\n   if (error < Error::OK) {\n      internal::nsecLog->error(\"NSEC: Failed to register /dev/nsec, error = {}.\", error);\n      return error;\n   }\n\n   // Run nsec\n   while (true) {\n      error = IOS_ReceiveMessage(messageQueueId,\n                                 message,\n                                 MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      {\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      case Command::Suspend:\n      {\n         if (sNsecData->started) {\n            internal::stopNsslThread();\n            // internal::stopNssThread();\n            sNsecData->started = FALSE;\n         }\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      }\n\n      case Command::Resume:\n      {\n         if (sNsecData->started) {\n            IOS_ResourceReply(request, Error::OK);\n         } else {\n            error = IOS_CreateThread(&internal::initThread,\n                                     nullptr,\n                                     phys_addrof(sNsecData->threadStack) + sNsecData->threadStack.size(),\n                                     sNsecData->threadStack.size(),\n                                     InitThreadPriority,\n                                     ThreadFlags::Detached);\n            if (error >= 0) {\n               sNsecData->initThreadId = static_cast<ThreadId>(error);\n               error = IOS_StartThread(sNsecData->initThreadId);\n            }\n\n            if (error >= 0) {\n               IOS_ResourceReply(request, Error::OK);\n               sNsecData->started = TRUE;\n            } else {\n               IOS_ResourceReply(request, Error::Access);\n            }\n         }\n         break;\n      }\n\n      default:\n         IOS_ResourceReply(request, Error::Invalid);\n      }\n   }\n}\n\n} // namespace ios::nsec\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_process.h\"\n\nnamespace ios::nsec\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::nsec\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_enum.h",
    "content": "#ifndef IOS_NSEC_ENUM_H\n#define IOS_NSEC_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\n\nENUM_NAMESPACE_ENTER(nsec)\n\nFLAGS_BEG(NSSLCertProperties, uint32_t)\n   FLAGS_VALUE(HasSecondPath,                      1 << 0)\n   FLAGS_VALUE(Exportable,                         1 << 1)\n   FLAGS_VALUE(Encrypted,                          1 << 2)\nFLAGS_END(NSSLCertProperties)\n\nENUM_BEG(NSSLCertType, int32_t)\n   ENUM_VALUE(Unknown0,                            0)\nENUM_END(NSSLCertType)\n\nENUM_BEG(NSSLCertEncoding, int32_t)\n   ENUM_VALUE(Unknown1,                            1)\nENUM_END(NSSLCertEncoding)\n\nENUM_BEG(NSSLCommand, uint32_t)\n   ENUM_VALUE(CreateContext,                       1)\n   ENUM_VALUE(DestroyContext,                      2)\n   ENUM_VALUE(SetClientPKI,                        3)\n   ENUM_VALUE(SetClientPKIExternal,                4)\n   ENUM_VALUE(AddServerPKI,                        5)\n   ENUM_VALUE(AddServerPKIGroups,                  6)\n   ENUM_VALUE(AddServerPKIExternal,                7)\n   ENUM_VALUE(CreateConnection,                    8)\n   ENUM_VALUE(DestroyConnection,                   9)\n   ENUM_VALUE(DoHandshake,                         10)\n   ENUM_VALUE(Read,                                11)\n   ENUM_VALUE(Write,                               12)\n   ENUM_VALUE(GetSession,                          13)\n   ENUM_VALUE(SetSession,                          14)\n   ENUM_VALUE(FreeSession,                         15)\n   ENUM_VALUE(GetPending,                          16)\n   ENUM_VALUE(GetPeerCertSize,                     17)\n   ENUM_VALUE(GetPeerCert,                         18)\n   ENUM_VALUE(GetCipherInfo,                       19)\n   ENUM_VALUE(RemoveSession,                       20)\n   ENUM_VALUE(NSECEncrypt,                         21)\n   ENUM_VALUE(ExportInternalServerCertificate,     22)\n   ENUM_VALUE(ExportInternalClientCertificate,     23)\nENUM_END(NSSLCommand)\n\nENUM_BEG(NSSLError, int32_t)\n   ENUM_VALUE(OK,                                  0)\n   ENUM_VALUE(Generic,                             -1)\n   ENUM_VALUE(InvalidNsslContext,                  -2621441)\n   ENUM_VALUE(InvalidCertID,                       -2621442)\n   ENUM_VALUE(CertLimit,                           -2621443)\n   ENUM_VALUE(InvalidNsslConnection,               -2621444)\n   ENUM_VALUE(InvalidCert,                         -2621445)\n   ENUM_VALUE(ZeroReturn,                          -2621446)\n   ENUM_VALUE(WantRead,                            -2621447)\n   ENUM_VALUE(WantWrite,                           -2621448)\n   ENUM_VALUE(IoError,                             -2621449)\n   ENUM_VALUE(NsslLibError,                        -2621450)\n   ENUM_VALUE(Unknown,                             -2621451)\n   ENUM_VALUE(OutOfMemory,                         -2621452)\n   ENUM_VALUE(InvalidState,                        -2621453)\n   ENUM_VALUE(HandshakeError,                      -2621454)\n   ENUM_VALUE(NoCert,                              -2621455)\n   ENUM_VALUE(InvalidFd,                           -2621456)\n   ENUM_VALUE(LibNotReady,                         -2621457)\n   ENUM_VALUE(IpcError,                            -2621458)\n   ENUM_VALUE(ResourceLimit,                       -2621459)\n   ENUM_VALUE(InvalidHandle,                       -2621460)\n   ENUM_VALUE(InvalidCertType,                     -2621461)\n   ENUM_VALUE(InvalidKeyType,                      -2621462)\n   ENUM_VALUE(InvalidSize,                         -2621463)\n   ENUM_VALUE(NoPeerCert,                          -2621464)\n   ENUM_VALUE(InsufficientSize,                    -2621465)\n   ENUM_VALUE(NoCipher,                            -2621466)\n   ENUM_VALUE(InvalidArg,                          -2621467)\n   ENUM_VALUE(InvalidNsslSession,                  -2621468)\n   ENUM_VALUE(NoSession,                           -2621469)\n   ENUM_VALUE(SslShutdownError,                    -2621470)\n   ENUM_VALUE(CertSizeLimit,                       -2621471)\n   ENUM_VALUE(CertNoAccess,                        -2621472)\n   ENUM_VALUE(InvalidCertId2,                      -2621473)\n   ENUM_VALUE(CertReadError,                       -2621474)\n   ENUM_VALUE(CertStoreInitFailure,                -2621475)\n   ENUM_VALUE(InvalidCertEncoding,                 -2621476)\n   ENUM_VALUE(CertStoreError,                      -2621477)\n   ENUM_VALUE(PrivateKeyReadError,                 -2621478)\n   ENUM_VALUE(InvalidPrivateKey,                   -2621479)\n   ENUM_VALUE(NotReady,                            -2621480)\n   ENUM_VALUE(EncryptionError,                     -2621481)\n   ENUM_VALUE(NoCertStore,                         -2621482)\n   ENUM_VALUE(PrivateKeySizeLimit,                 -2621483)\n   ENUM_VALUE(ProcessMaxExtCerts,                  -2621484)\n   ENUM_VALUE(ProcessMaxContexts,                  -2621485)\n   ENUM_VALUE(ProcessMaxConnections,               -2621486)\n   ENUM_VALUE(CertNotExportable,                   -2621487)\n   ENUM_VALUE(InvalidCertSize,                     -2621488)\n   ENUM_VALUE(InvalidKeySize,                      -2621489)\nENUM_END(NSSLError)\n\nENUM_BEG(NSSLPrivateKeyType, int32_t)\n   ENUM_VALUE(Unknown0,                            0)\nENUM_END(NSSLPrivateKeyType)\n\nENUM_BEG(NSSLVersion, uint32_t)\n   ENUM_VALUE(Auto,                                0)\nENUM_END(NSSLVersion)\n\nENUM_NAMESPACE_EXIT(nsec)\n\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_NSEC_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_log.h",
    "content": "#pragma once\n#include <common/log.h>\n\nnamespace ios::nsec::internal\n{\n\nextern Logger nsecLog;\n\n} // namespace ios::nsec::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_nssl.h",
    "content": "#pragma once\n#include \"ios_nsec_enum.h\"\n#include \"ios_nsec_nssl_request.h\"\n#include \"ios_nsec_nssl_response.h\"\n#include \"ios_nsec_nssl_types.h\"\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_nssl_certstore.cpp",
    "content": "#include \"ios_nsec_nssl_certstore.h\"\n#include \"ios_nsec_log.h\"\n\n#include \"ios/fs/ios_fs_fsa_ipc.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/ios_error.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <array>\n#include <charconv>\n#include <common/strutils.h>\n#include <gsl/gsl-lite.hpp>\n#include <string_view>\n\nusing namespace ios::fs;\nusing namespace ios::kernel;\n\nnamespace ios::nsec::internal\n{\n\nstruct StaticCertStoreData\n{\n   be2_val<FSAHandle> fsaHandle;\n   be2_array<CertStoreMetaData, MaxNumCertificates> metaData;\n};\n\nstatic phys_ptr<StaticCertStoreData> sCertStoreData = nullptr;\n\nbool\ncheckCertPermission(phys_ptr<CertStoreMetaData> certMetaData,\n                    TitleId titleId,\n                    ProcessId processId,\n                    uint64_t caps)\n{\n   if (caps & certMetaData->capabilityMask) {\n      return true;\n   }\n\n   for (auto tid : certMetaData->titleIds) {\n      if (tid == -1) {\n         break;\n      }\n\n      if (tid == titleId) {\n         return true;\n      }\n   }\n\n   for (auto pid : certMetaData->processIds) {\n      if (pid == static_cast<ProcessId>(-1)) {\n         break;\n      } else if (pid == static_cast<ProcessId>(-4)) {\n         return true;\n      } else if (pid == static_cast<ProcessId>(-2) && processId < ProcessId::TEST) {\n         return true;\n      } else if (pid == static_cast<ProcessId>(-3) && processId >= ProcessId::COSKERNEL) {\n         return true;\n      } else if (processId == ProcessId::TEST) {\n         return true;\n      } else if (processId == pid) {\n         return true;\n      }\n   }\n\n   return false;\n}\n\nbool\ncheckCertExportable(phys_ptr<CertStoreMetaData> certMetaData)\n{\n   return !!(certMetaData->properties & NSSLCertProperties::Exportable);\n}\n\nstd::optional<uint32_t>\ngetCertFileSize(phys_ptr<CertStoreMetaData> certMetaData,\n                int32_t pathIndex)\n{\n   if (pathIndex >= certMetaData->numPaths) {\n      return {};\n   }\n\n   if (pathIndex > 0 && !(certMetaData->properties & NSSLCertProperties::HasSecondPath)) {\n      return {};\n   }\n\n   auto path = std::string { \"/vol/storage_mlc01/sys/title/0005001b/10054000/content/\" };\n   if (pathIndex == 0) {\n      path += phys_addrof(certMetaData->path1).get();\n   } else if (pathIndex == 1) {\n      path += phys_addrof(certMetaData->path2).get();\n   }\n\n   auto stat = StackObject<FSAStat> { };\n   auto error = FSAGetStat(sCertStoreData->fsaHandle, path, stat);\n   if (error < FSAStatus::OK) {\n      return {};\n   }\n\n   return static_cast<uint32_t>(stat->size);\n}\n\nstd::optional<uint32_t>\ngetCertFileData(phys_ptr<CertStoreMetaData> certMetaData,\n                int32_t pathIndex,\n                phys_ptr<void> certBuffer,\n                uint32_t certBufferSize)\n{\n   // Read file data 4096 bytes at a time into allocated buffer copying into certbuiffer\n   auto heapBufferSize = std::min(4096u, certBufferSize);\n   auto heapBuffer = IOS_HeapAllocAligned(CrossProcessHeapId, heapBufferSize, 0x40u);\n   if (!heapBuffer) {\n      return {};\n   }\n   auto _ = gsl::finally([&]() { IOS_HeapFree(CrossProcessHeapId, heapBuffer); });\n\n   auto path = std::string { \"/vol/storage_mlc01/sys/title/0005001b/10054000/content/\" };\n   if (pathIndex == 0) {\n      path += phys_addrof(certMetaData->path1).get();\n   } else if (pathIndex == 1) {\n      path += phys_addrof(certMetaData->path2).get();\n   }\n\n   auto fileHandle = FSAFileHandle { -1 };\n   auto error = FSAOpenFile(sCertStoreData->fsaHandle, path, \"r\", &fileHandle);\n   if (error < FSAStatus::OK) {\n      return {};\n   }\n   auto _2 = gsl::finally([&]() {\n         FSACloseFile(sCertStoreData->fsaHandle, fileHandle);\n      });\n\n   auto certSize = 0;\n\n   while (true) {\n      error = FSAReadFile(sCertStoreData->fsaHandle, heapBuffer, 1,\n                          heapBufferSize, fileHandle, FSAReadFlag::None);\n      if (error < 0) {\n         break;\n      }\n\n      auto bytesRead = static_cast<uint32_t>(error);\n      std::memcpy(reinterpret_cast<uint8_t *>(certBuffer.get()) + certSize,\n                  heapBuffer.get(), error);\n      certSize += bytesRead;\n\n      if (bytesRead < heapBufferSize) {\n         // Reached EOF\n         break;\n      }\n   }\n\n   if (error < FSAStatus::OK && error != FSAStatus::EndOfFile) {\n      return {};\n   }\n\n   return certSize;\n}\n\nphys_ptr<CertStoreMetaData>\nlookupCertMetaData(NSSLCertID id)\n{\n   for (auto &cert : sCertStoreData->metaData) {\n      if (cert.id == id) {\n         return phys_addrof(cert);\n      } else if (cert.id == -1) {\n         break;\n      }\n   }\n\n   return nullptr;\n}\n\nError\nloadCertstoreMetadata()\n{\n   auto openError = FSAOpen();\n   if (openError < Error::OK) {\n      nsecLog->warn(\"loadCertstoreMetadata: FSAOpen failed with error {}\", openError);\n      return openError;\n   }\n   sCertStoreData->fsaHandle = static_cast<FSAHandle>(openError);\n\n   // TODO: Use MCP_SearchTitle to find title path\n   auto path = \"/vol/storage_mlc01/sys/title/0005001b/10054000/content/certstore_metadata.txt\";\n\n   auto stat = StackObject<FSAStat> { };\n   auto error = FSAGetStat(sCertStoreData->fsaHandle, path, stat);\n   if (error < FSAStatus::OK) {\n      nsecLog->warn(\"loadCertstoreMetadata: FSAStat failed with error {} on {}\", error, path);\n      return static_cast<Error>(error);\n   }\n\n   auto size = stat->size ? stat->size.value() : 10240u;\n   auto fileBuffer = IOS_HeapAlloc(LocalProcessHeapId, size);\n   if (!fileBuffer) {\n      nsecLog->warn(\"loadCertstoreMetadata: Failed to allocate file buffer of size {}\", size);\n      return Error::QFull;\n   }\n   auto _ = gsl::finally([&]() { IOS_HeapFree(CrossProcessHeapId, fileBuffer); });\n\n   auto fileHandle = FSAFileHandle { -1 };\n   error = FSAOpenFile(sCertStoreData->fsaHandle, path, \"r\", &fileHandle);\n   if (error < FSAStatus::OK) {\n      nsecLog->warn(\"loadCertstoreMetadata: FSAOpenFile failed with error {} on {}\", error, path);\n      return static_cast<Error>(error);\n   }\n\n   error = FSAReadFile(sCertStoreData->fsaHandle, fileBuffer, 1, size, fileHandle, FSAReadFlag::None);\n   FSACloseFile(sCertStoreData->fsaHandle, fileHandle);\n   if (error < FSAStatus::OK) {\n      nsecLog->warn(\"loadCertstoreMetadata: FSAReadFile failed with error {}\", error);\n      return static_cast<Error>(error);\n   }\n\n   size = static_cast<uint32_t>(error);\n\n   // Now parse fileBuffer, size bytes for metadata\n   auto contents = std::string_view { phys_cast<char *>(fileBuffer).get(), size };\n   auto position = size_t { 0 };\n   auto certIndex = 0u;\n   auto lineIndex = 1u;\n\n   // Iteratre through lines of fileBuffer\n   while (position < size) {\n      auto lineEndPosition = contents.find_first_of('\\n', position);\n      if (lineEndPosition == std::string_view::npos) {\n         lineEndPosition = size;\n      }\n\n      auto line = trim_view(contents.substr(position, lineEndPosition - position));\n      if (!line.empty() && line[0] != '#') {\n         auto linePosition = size_t { 0 };\n         struct {\n            bool hasId = false;\n            bool hasType = false;\n            bool hasEncoding = false;\n            bool hasProperties = false;\n            bool hasCapabilityMask = false;\n            bool hasProcessIds = false;\n            bool hasTitleIds = false;\n            bool hasPaths = false;\n            bool hasGroups = false;\n            bool hasRawE0Size = false;\n            bool hasRawE1Size = false;\n            bool hasChecksum = false;\n\n            long id = 0;\n            long type = 0;\n            long encoding = 0;\n            long properties = 0;\n            unsigned long long capabilityMask = 0;\n            std::array<long, 33> processIds;\n            std::array<long long, 33> titleIds;\n            long numPaths = 0;\n            std::string_view path1;\n            std::string_view path2;\n            long groups = 0;\n            long rawE0Size = 0;\n            long rawE1Size = 0;\n         } certData;\n         certData.titleIds[0] = -1;\n         certData.processIds[0] = -1;\n\n         // Iterator through the line splitting by ;\n         while (linePosition < line.size()) {\n            auto kvEndPosition = std::min(line.find_first_of(';', linePosition), line.size());\n            auto kvPair = line.substr(linePosition, kvEndPosition - linePosition);\n\n            // Split the key=value pair\n            auto eqPos = kvPair.find_first_of('=');\n            if (eqPos != std::string_view::npos && eqPos + 1 < kvPair.size()) {\n               auto key = trim_view(kvPair.substr(0, eqPos));\n               auto value = trim_view(kvPair.substr(eqPos + 1));\n\n               if (iequals(key, \"ID\")) {\n                  certData.id = std::stol(std::string(value), nullptr, 0);\n                  certData.hasId = true;\n               } else if (iequals(key, \"TYPE\")) {\n                  certData.type = std::stol(std::string(value), nullptr, 0);\n                  certData.hasType = true;\n               } else if (iequals(key, \"ENCODING\")) {\n                  certData.encoding = std::stol(std::string(value), nullptr, 0);\n                  certData.hasEncoding = true;\n               } else if (iequals(key, \"PROPERTIES\")) {\n                  certData.properties = std::stol(std::string(value), nullptr, 0);\n                  certData.hasProperties = true;\n               } else if (iequals(key, \"CAPABILITY_MASK\")) {\n                  certData.capabilityMask = std::stol(std::string(value), nullptr, 0);\n                  certData.hasCapabilityMask = true;\n               } else if (iequals(key, \"PID\")) {\n                  auto pidPosition = size_t { 0 };\n                  auto pidIndex = 0u;\n                  while (pidPosition < value.size() && pidIndex < certData.processIds.size() - 1) {\n                     auto pidEndPosition = std::min(value.find_first_of(','), value.size());\n                     auto pid = value.substr(pidPosition, pidEndPosition - pidPosition);\n                     certData.processIds[pidIndex++] = std::stol(std::string(pid), nullptr, 0);\n                     pidPosition = pidEndPosition + 1;\n                  }\n\n                  certData.processIds[pidIndex] = -1;\n                  certData.hasProcessIds = true;\n               } else if (iequals(key, \"TID\")) {\n                  auto tidPosition = size_t {  0 };\n                  auto tidIndex = 0u;\n                  while (tidPosition < value.size() && tidIndex < certData.titleIds.size() - 1) {\n                     auto tidEndPosition = std::min(value.find_first_of(','), value.size());\n                     auto tid = value.substr(tidPosition, tidEndPosition - tidPosition);\n                     certData.titleIds[tidIndex++] = std::stol(std::string(tid), nullptr, 0);\n                     tidPosition = tidEndPosition + 1;\n                  }\n\n                  certData.titleIds[tidIndex] = -1;\n                  certData.hasTitleIds = true;\n               } else if (iequals(key, \"PATHS\")) {\n                  auto path2Position = std::min(value.find_first_of(','), value.size());\n                  certData.path1 = value.substr(0, path2Position);\n                  if (path2Position + 1 < value.size()) {\n                     certData.path2 = value.substr(path2Position + 1);\n                  }\n\n                  if (!certData.path1.empty()) {\n                     certData.numPaths += 1;\n                  }\n\n                  if (!certData.path2.empty()) {\n                     certData.numPaths += 1;\n                  }\n\n                  certData.hasPaths = true;\n               } else if (iequals(key, \"GROUPS\")) {\n                  certData.groups = std::stol(std::string(value), nullptr, 0);\n                  certData.hasGroups = true;\n               } else if (iequals(key, \"RAW_E0_SIZE\") || iequals(key, \"RAW_SIZE\")) {\n                  certData.rawE0Size = std::stol(std::string(value), nullptr, 0);\n                  certData.hasRawE0Size = true;\n               } else if (iequals(key, \"RAW_E1_SIZE\") || iequals(key, \"KEYSIZEDER\")) {\n                  certData.rawE1Size = std::stol(std::string(value), nullptr, 0);\n                  certData.hasRawE1Size = true;\n               } else {\n                  nsecLog->warn(\"CERTSTORE: Unrecognized property name '{}' on line {}\", key, lineIndex);\n               }\n            }\n\n            linePosition = kvEndPosition + 1;\n         }\n\n         if (!certData.hasId) {\n            nsecLog->warn(\"CERTSTORE: ID not specified on line {}\", lineIndex);\n         } else if (!certData.hasType) {\n            nsecLog->warn(\"CERTSTORE: TYPE not specified on line {}\", lineIndex);\n         } else if (!certData.hasEncoding) {\n            nsecLog->warn(\"CERTSTORE: ENCODING not specified on line {}\", lineIndex);\n         } else if (!certData.hasProperties) {\n            nsecLog->warn(\"CERTSTORE: PROPERTIES not specified on line {}\", lineIndex);\n         } else if (!certData.hasCapabilityMask) {\n            nsecLog->warn(\"CERTSTORE: CAPABILITY_MASK not specified on line {}\", lineIndex);\n         } else if (!certData.hasProcessIds && !certData.hasTitleIds) {\n            nsecLog->warn(\"CERTSTORE: Neither PID nor TID specified on line {}\", lineIndex);\n         } else if (!certData.hasPaths) {\n            nsecLog->warn(\"CERTSTORE: PATHS not specified on line {}\", lineIndex);\n         } else if (!certData.hasGroups) {\n            nsecLog->warn(\"CERTSTORE: GROUPS not specified on line {}\", lineIndex);\n         } else if (!certData.hasRawE0Size && (certData.properties & 4)) {\n            nsecLog->warn(\"CERTSTORE: RAW_E0_SIZE not specified on line {} for encrypted entity\", lineIndex);\n         } else if (!certData.hasRawE1Size && (certData.properties & 1)) {\n            nsecLog->warn(\"CERTSTORE: KEYSIZEDER/RAW_E1_SIZE not specified on line {}\", lineIndex);\n         } else {\n            auto &cert = sCertStoreData->metaData[certIndex++];\n            cert.id = static_cast<int32_t>(certData.id);\n            cert.type = static_cast<int32_t>(certData.type);\n            cert.encoding = static_cast<NSSLCertEncoding>(certData.encoding);\n            cert.properties = static_cast<NSSLCertProperties>(certData.properties);\n            cert.groups = static_cast<int32_t>(certData.groups);\n            cert.capabilityMask = static_cast<uint64_t>(certData.capabilityMask);\n            cert.numPaths = static_cast<int32_t>(certData.numPaths);\n            cert.path1 = certData.path1;\n            cert.path2 = certData.path2;\n            cert.rawE0Size = static_cast<int32_t>(certData.rawE0Size);\n            cert.rawE1Size = static_cast<int32_t>(certData.rawE1Size);\n\n            for (auto i = 0u; i < certData.processIds.size(); ++i) {\n               cert.processIds[i] = static_cast<ProcessId>(certData.processIds[i]);\n            }\n\n            for (auto i = 0u; i < certData.titleIds.size(); ++i) {\n               cert.titleIds[i] = static_cast<TitleId>(certData.titleIds[i]);\n            }\n         }\n      }\n\n      if (certIndex == sCertStoreData->metaData.size()) {\n         nsecLog->warn(\"CERTSTORE: Maximum Entity Limit ({}) Reached !\",\n                       sCertStoreData->metaData.size());\n         break;\n      }\n\n      position = lineEndPosition + 1;\n      ++lineIndex;\n   }\n\n   if (certIndex < sCertStoreData->metaData.size()) {\n      sCertStoreData->metaData[certIndex].id = -1;\n   }\n\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticCertStoreData()\n{\n   sCertStoreData = allocProcessStatic<StaticCertStoreData>();\n}\n\n} // namespace ios::nsec::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_nssl_certstore.h",
    "content": "#pragma once\n#include \"ios_nsec_enum.h\"\n#include \"ios_nsec_nssl_types.h\"\n\n#include \"ios/ios_enum.h\"\n#include \"ios/ios_error.h\"\n#include \"ios/ios_ipc.h\"\n\n#include <cstdint>\n#include <common/structsize.h>\n#include <libcpu/be2_struct.h>\n#include <optional>\n\nnamespace ios::nsec::internal\n{\n\n#pragma pack(push, 1)\n\n// Reversing IOS seems to indicate this may be 1000, but lets just set to 100\n// to save memory.\nconstexpr auto MaxNumCertificates = 100u;\n\nstruct CertStoreMetaData\n{\n   be2_val<NSSLCertID> id;\n   be2_val<int32_t> type; // This is NOT an NSSLCertType\n   be2_val<NSSLCertEncoding> encoding;\n   be2_val<NSSLCertProperties> properties;\n   be2_val<int32_t> groups;\n   be2_val<uint64_t> capabilityMask;\n   be2_array<ProcessId, 33> processIds;\n   be2_array<TitleId, 33> titleIds;\n   be2_val<int32_t> numPaths;\n   be2_array<char, 128> path1;\n   be2_array<char, 128> path2;\n   be2_val<int32_t> rawE0Size;\n   be2_val<int32_t> rawE1Size;\n};\nCHECK_OFFSET(CertStoreMetaData, 0x00, id);\nCHECK_OFFSET(CertStoreMetaData, 0x04, type);\nCHECK_OFFSET(CertStoreMetaData, 0x08, encoding);\nCHECK_OFFSET(CertStoreMetaData, 0x0C, properties);\nCHECK_OFFSET(CertStoreMetaData, 0x10, groups);\nCHECK_OFFSET(CertStoreMetaData, 0x14, capabilityMask);\nCHECK_OFFSET(CertStoreMetaData, 0x1C, processIds);\nCHECK_OFFSET(CertStoreMetaData, 0xA0, titleIds);\nCHECK_OFFSET(CertStoreMetaData, 0x1A8, numPaths);\nCHECK_OFFSET(CertStoreMetaData, 0x1AC, path1);\nCHECK_OFFSET(CertStoreMetaData, 0x22C, path2);\nCHECK_OFFSET(CertStoreMetaData, 0x2AC, rawE0Size);\nCHECK_OFFSET(CertStoreMetaData, 0x2B0, rawE1Size);\nCHECK_SIZE(CertStoreMetaData, 0x2B4);\n\n#pragma pack(pop)\n\nbool\ncheckCertPermission(phys_ptr<CertStoreMetaData> certMetaData,\n                    TitleId titleId, ProcessId processId, uint64_t caps);\n\nbool\ncheckCertExportable(phys_ptr<CertStoreMetaData> certMetaData);\n\nstd::optional<uint32_t>\ngetCertFileSize(phys_ptr<CertStoreMetaData> certMetaData,\n                int32_t pathIndex);\n\n\nstd::optional<uint32_t>\ngetCertFileData(phys_ptr<CertStoreMetaData> certMetaData,\n                int32_t pathIndex,\n                phys_ptr<void> certBuffer,\n                uint32_t certBufferSize);\n\nphys_ptr<CertStoreMetaData>\nlookupCertMetaData(NSSLCertID id);\n\nError\nloadCertstoreMetadata();\n\nvoid\ninitialiseStaticCertStoreData();\n\n} // namespace ios::nsec::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_nssl_device.cpp",
    "content": "#include \"ios_nsec_nssl_certstore.h\"\n#include \"ios_nsec_nssl_device.h\"\n\n#include \"ios/crypto/ios_crypto_ipc.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <gsl/gsl-lite.hpp>\n\nusing namespace ::ios::kernel;\n\nnamespace ios::nsec::internal\n{\n\nNSSLError\nNSSLDevice::createContext(NSSLVersion version)\n{\n   for (auto i = 0u; i < mContexts.size(); ++i) {\n      auto &context = mContexts[i];\n      if (context.initialised) {\n         continue;\n      }\n\n      context.initialised = true;\n      context.version = version;\n      return static_cast<NSSLError>(i);\n   }\n\n   return NSSLError::ProcessMaxContexts;\n}\n\nNSSLError\nNSSLDevice::addServerPKI(NSSLContextHandle context, NSSLCertID cert)\n{\n   return NSSLError::OK;\n}\n\nNSSLError\nNSSLDevice::addServerPKIExternal(NSSLContextHandle context,\n                                 phys_ptr<uint8_t> cert,\n                                 uint32_t certSize,\n                                 NSSLCertType certType)\n{\n   return NSSLError::OK;\n}\n\nNSSLError\nNSSLDevice::exportInternalClientCertificate(\n   phys_ptr<NSSLExportInternalClientCertificateRequest> request,\n   phys_ptr<NSSLExportInternalClientCertificateResponse> response,\n   phys_ptr<uint8_t> certBuffer,\n   uint32_t certBufferSize,\n   phys_ptr<uint8_t> privateKeyBuffer,\n   uint32_t privateKeyBufferSize)\n{\n   auto certMetaData = lookupCertMetaData(request->certId);\n   if (!certMetaData) {\n      return NSSLError::InvalidCertId2;\n   }\n\n   if (!checkCertPermission(certMetaData, mTitleId, mProcessId, mCapabilities)) {\n      return NSSLError::CertNoAccess;\n   }\n\n   if (!checkCertExportable(certMetaData)) {\n      return NSSLError::CertNotExportable;\n   }\n\n   if (certMetaData->type != 2) {\n      return NSSLError::InvalidCertType;\n   }\n\n   if (certMetaData->encoding != NSSLCertEncoding::Unknown1) {\n      return NSSLError::InvalidCertEncoding;\n   }\n\n   auto fileSize = getCertFileSize(certMetaData, 1);\n   if (!fileSize.has_value()) {\n      return NSSLError::InvalidCertSize;\n   }\n\n   if (!certBuffer || !certBufferSize) {\n      response->certSize = *fileSize;\n      response->certType = NSSLCertType::Unknown0;\n      return NSSLError::OK;\n   }\n\n   if (certBufferSize < fileSize) {\n      return NSSLError::InsufficientSize;\n   }\n\n   auto encryptedCertBuffer =\n      IOS_HeapAllocAligned(CrossProcessHeapId, certBufferSize, 0x10u);\n   auto decryptedCertBuffer =\n      IOS_HeapAllocAligned(CrossProcessHeapId, certBufferSize, 0x10u);\n   auto _ = gsl::finally([&]() {\n         IOS_HeapFree(CrossProcessHeapId, encryptedCertBuffer);\n         IOS_HeapFree(CrossProcessHeapId, decryptedCertBuffer);\n      });\n\n   fileSize = getCertFileData(certMetaData, 1, encryptedCertBuffer, certBufferSize);\n   if (!fileSize.has_value()) {\n      return NSSLError::CertReadError;\n   }\n\n   StackArray<uint8_t, 16> iv;\n   memset(iv.get(), 0, iv.size());\n\n   auto ioscHandle = static_cast<crypto::IOSCHandle>(crypto::IOSC_Open());\n   auto error = crypto::IOSC_Decrypt(ioscHandle, crypto::KeyId::SslRsaKey,\n                                     iv, iv.size(),\n                                     encryptedCertBuffer, certBufferSize,\n                                     decryptedCertBuffer, certBufferSize);\n   if (error != Error::OK) {\n      return NSSLError::CertReadError;\n   }\n\n   response->certSize = *fileSize;\n   response->certType = NSSLCertType::Unknown0;\n   memcpy(certBuffer.get(), decryptedCertBuffer.get(), certBufferSize);\n\n   return NSSLError::OK;\n}\n\nNSSLError\nNSSLDevice::exportInternalServerCertificate(\n   phys_ptr<NSSLExportInternalServerCertificateRequest> request,\n   phys_ptr<NSSLExportInternalServerCertificateResponse> response,\n   phys_ptr<uint8_t> certBuffer,\n   uint32_t certBufferSize)\n{\n   auto certMetaData = lookupCertMetaData(request->certId);\n   if (!certMetaData) {\n      return NSSLError::InvalidCertId2;\n   }\n\n   if (!checkCertPermission(certMetaData, mTitleId, mProcessId, mCapabilities)) {\n      return NSSLError::CertNoAccess;\n   }\n\n   if (!checkCertExportable(certMetaData)) {\n      return NSSLError::CertNotExportable;\n   }\n\n   if (certMetaData->type != 1) {\n      return NSSLError::InvalidCertType;\n   }\n\n   if (certMetaData->encoding != NSSLCertEncoding::Unknown1) {\n      return NSSLError::InvalidCertEncoding;\n   }\n\n   auto fileSize = getCertFileSize(certMetaData, 0);\n   if (!fileSize.has_value()) {\n      return NSSLError::InvalidCertSize;\n   }\n\n   if (!certBuffer || !certBufferSize) {\n      response->certSize = *fileSize;\n      response->certType = NSSLCertType::Unknown0;\n      return NSSLError::OK;\n   }\n\n   if (certBufferSize < fileSize) {\n      return NSSLError::InsufficientSize;\n   }\n\n   fileSize = getCertFileData(certMetaData, 0, certBuffer, certBufferSize);\n   if (!fileSize.has_value()) {\n      return NSSLError::CertReadError;\n   }\n\n   response->certSize = *fileSize;\n   response->certType = NSSLCertType::Unknown0;\n   return NSSLError::OK;\n}\n\n} // namespace ios::nsec::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_nssl_device.h",
    "content": "#pragma once\n#include \"ios_nsec_enum.h\"\n#include \"ios_nsec_nssl_types.h\"\n#include \"ios_nsec_nssl_request.h\"\n#include \"ios_nsec_nssl_response.h\"\n#include \"ios/ios_ipc.h\"\n\n#include <array>\n#include <libcpu/be2_struct.h>\n\nnamespace ios::nsec::internal\n{\n\nclass NSSLDevice\n{\n   static constexpr const size_t MaxNumContexts = 32;\n\n   struct Context\n   {\n      bool initialised = false;\n      NSSLVersion version = NSSLVersion::Auto;\n   };\n\npublic:\n   NSSLDevice(TitleId titleId,\n              ProcessId processId,\n              uint64_t caps,\n              Handle socketHandle) :\n      mTitleId(titleId),\n      mProcessId(processId),\n      mCapabilities(caps),\n      mSocketHandle(socketHandle)\n   {\n   }\n\n   NSSLError createContext(NSSLVersion version);\n   NSSLError addServerPKI(NSSLContextHandle context, NSSLCertID cert);\n   NSSLError addServerPKIExternal(NSSLContextHandle context,\n                                  phys_ptr<uint8_t> cert, uint32_t certSize,\n                                  NSSLCertType certType);\n   NSSLError exportInternalClientCertificate(\n      phys_ptr<NSSLExportInternalClientCertificateRequest> request,\n      phys_ptr<NSSLExportInternalClientCertificateResponse> response,\n      phys_ptr<uint8_t> certBuffer, uint32_t certBufferSize,\n      phys_ptr<uint8_t> privateKeyBuffer, uint32_t privateKeyBufferSize);\n   NSSLError exportInternalServerCertificate(\n      phys_ptr<NSSLExportInternalServerCertificateRequest> request,\n      phys_ptr<NSSLExportInternalServerCertificateResponse> response,\n      phys_ptr<uint8_t> certBuffer, uint32_t certBufferSize);\n\nprivate:\n   TitleId mTitleId;\n   ProcessId mProcessId;\n   uint64_t mCapabilities;\n\n   Handle mSocketHandle;\n   std::array<Context, MaxNumContexts> mContexts;\n};\n\n} // namespace ios::nsec::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_nssl_request.h",
    "content": "#pragma once\n#include \"ios_nsec_enum.h\"\n#include \"ios_nsec_nssl_types.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::nsec\n{\n\n/**\n * \\ingroup ios_nsec\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct NSSLCreateContextRequest\n{\n   be2_val<NSSLVersion> version;\n};\nCHECK_OFFSET(NSSLCreateContextRequest, 0x00, version);\nCHECK_SIZE(NSSLCreateContextRequest, 0x04);\n\nstruct NSSLDestroyContextRequest\n{\n   be2_val<NSSLContextHandle> context;\n};\nCHECK_OFFSET(NSSLDestroyContextRequest, 0x00, context);\nCHECK_SIZE(NSSLDestroyContextRequest, 0x04);\n\nstruct NSSLAddServerPKIRequest\n{\n   be2_val<NSSLContextHandle> context;\n   be2_val<NSSLCertID> cert;\n};\nCHECK_OFFSET(NSSLAddServerPKIRequest, 0x00, context);\nCHECK_OFFSET(NSSLAddServerPKIRequest, 0x04, cert);\nCHECK_SIZE(NSSLAddServerPKIRequest, 0x08);\n\nstruct NSSLAddServerPKIExternalRequest\n{\n   be2_val<NSSLContextHandle> context;\n   be2_val<NSSLCertType> certType;\n};\nCHECK_OFFSET(NSSLAddServerPKIExternalRequest, 0x00, context);\nCHECK_OFFSET(NSSLAddServerPKIExternalRequest, 0x04, certType);\nCHECK_SIZE(NSSLAddServerPKIExternalRequest, 0x08);\n\nstruct NSSLExportInternalClientCertificateRequest\n{\n   be2_val<NSSLCertID> certId;\n};\nCHECK_OFFSET(NSSLExportInternalClientCertificateRequest, 0x00, certId);\nCHECK_SIZE(NSSLExportInternalClientCertificateRequest, 0x04);\n\nstruct NSSLExportInternalServerCertificateRequest\n{\n   be2_val<NSSLCertID> certId;\n};\nCHECK_OFFSET(NSSLExportInternalServerCertificateRequest, 0x00, certId);\nCHECK_SIZE(NSSLExportInternalServerCertificateRequest, 0x04);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::net\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_nssl_response.h",
    "content": "#pragma once\n#include \"ios_nsec_enum.h\"\n#include \"ios_nsec_nssl_types.h\"\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <common/structsize.h>\n\nnamespace ios::nsec\n{\n\n/**\n * \\ingroup ios_nsec\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct NSSLExportInternalClientCertificateResponse\n{\n   be2_val<NSSLCertType> certType;\n   be2_val<uint32_t> certSize;\n   be2_val<NSSLPrivateKeyType> privateKeyType;\n   be2_val<uint32_t> privateKeySize;\n};\nCHECK_OFFSET(NSSLExportInternalClientCertificateResponse, 0x00, certType);\nCHECK_OFFSET(NSSLExportInternalClientCertificateResponse, 0x04, certSize);\nCHECK_OFFSET(NSSLExportInternalClientCertificateResponse, 0x08, privateKeyType);\nCHECK_OFFSET(NSSLExportInternalClientCertificateResponse, 0x0C, privateKeySize);\nCHECK_SIZE(NSSLExportInternalClientCertificateResponse, 0x10);\n\nstruct NSSLExportInternalServerCertificateResponse\n{\n   be2_val<NSSLCertType> certType;\n   be2_val<uint32_t> certSize;\n};\nCHECK_OFFSET(NSSLExportInternalServerCertificateResponse, 0x00, certType);\nCHECK_OFFSET(NSSLExportInternalServerCertificateResponse, 0x04, certSize);\nCHECK_SIZE(NSSLExportInternalServerCertificateResponse, 0x08);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::net\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_nssl_thread.cpp",
    "content": "#include \"ios_nsec_log.h\"\n#include \"ios_nsec_nssl_certstore.h\"\n#include \"ios_nsec_nssl_device.h\"\n#include \"ios_nsec_nssl_thread.h\"\n#include \"ios_nsec_nssl_request.h\"\n#include \"ios_nsec_nssl_response.h\"\n\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_process.h\"\n#include \"ios/kernel/ios_kernel_resourcemanager.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/kernel/ios_kernel_ipc.h\"\n#include \"ios/net/ios_net_soshim.h\"\n\n#include \"ios/ios_error.h\"\n#include \"ios/ios_stackobject.h\"\n\nnamespace ios::nsec::internal\n{\n\nusing SocketDeviceHandle = int32_t;\nusing namespace kernel;\nusing namespace net;\n\nconstexpr auto NumNsslMessages = 32u;\nconstexpr auto NsslThreadStackSize = 0x4000u;\nconstexpr auto NsslThreadPriority = 47u;\n\nstruct StaticNsslThreadData\n{\n   be2_val<BOOL> suspended;\n   be2_val<ThreadId> threadId;\n   be2_val<MessageQueueId> messageQueueId;\n   be2_struct<IpcRequest> stopMessage;\n   be2_array<Message, NumNsslMessages> messageBuffer;\n   be2_array<uint8_t, NsslThreadStackSize> threadStack;\n\n   be2_val<BOOL> openedSocketHandle;\n   be2_val<Handle> socketHandle;\n};\n\nstatic phys_ptr<StaticNsslThreadData>\nsNsslThreadData = nullptr;\n\nstatic std::array<std::unique_ptr<NSSLDevice>, ProcessId::Max>\nsDevices;\n\nstatic NSSLDevice *\ngetDevice(SocketDeviceHandle handle)\n{\n   if (handle < 0 || handle >= sDevices.size()) {\n      return nullptr;\n   }\n\n   return sDevices[handle].get();\n}\n\nstatic Error\nnsslOpen(phys_ptr<ResourceRequest> request)\n{\n   // Send GetProcessSocketHandle to /dev/socket\n   if (!sNsslThreadData->openedSocketHandle) {\n      auto error = SOShim_Open();\n      if (error < 0) {\n         return static_cast<Error>(NSSLError::IoError);\n      }\n      sNsslThreadData->socketHandle = static_cast<Handle>(error);\n      sNsslThreadData->openedSocketHandle = TRUE;\n   }\n\n   auto error = SOShim_GetProcessSocketHandle(sNsslThreadData->socketHandle,\n                                              request->requestData.titleId,\n                                              request->requestData.processId);\n   if (error < 0) {\n      return static_cast<Error>(NSSLError::IoError);\n   }\n\n   // There is one NSSL device per process\n   auto pid = request->requestData.processId;\n   auto idx = static_cast<size_t>(pid);\n\n   if (!sDevices[idx]) {\n      sDevices[idx] = std::make_unique<NSSLDevice>(\n         request->requestData.titleId,\n         request->requestData.processId,\n         request->requestData.args.open.caps,\n         static_cast<Handle>(error));\n   }\n\n   return static_cast<Error>(idx);\n}\n\nstatic Error\nnsslClose(phys_ptr<ResourceRequest> request)\n{\n   auto pid = request->requestData.clientPid;\n   auto idx = static_cast<size_t>(pid);\n\n   if (idx != request->requestData.handle) {\n      return Error::Exists;\n   }\n\n   sDevices[idx] = nullptr;\n   return Error::OK;\n}\n\nstatic Error\nnsslIoctl(phys_ptr<ResourceRequest> resourceRequest)\n{\n   auto error = Error::OK;\n   auto device = getDevice(resourceRequest->requestData.handle);\n   if (!device) {\n      return static_cast<Error>(NSSLError::InvalidHandle);\n   }\n\n   switch (static_cast<NSSLCommand>(resourceRequest->requestData.args.ioctl.request)) {\n   case NSSLCommand::CreateContext:\n   {\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(NSSLCreateContextRequest)) {\n         return Error::InvalidArg;\n      }\n\n      if (!resourceRequest->requestData.args.ioctl.inputBuffer) {\n         return Error::InvalidArg;\n      }\n\n      auto request = phys_cast<const NSSLCreateContextRequest *>(resourceRequest->requestData.args.ioctl.inputBuffer);\n      error = static_cast<Error>(device->createContext(request->version));\n      break;\n   }\n   case NSSLCommand::AddServerPKI:\n   {\n      if (resourceRequest->requestData.args.ioctl.inputLength != sizeof(NSSLAddServerPKIRequest)) {\n         return Error::InvalidArg;\n      }\n\n      if (!resourceRequest->requestData.args.ioctl.inputBuffer) {\n         return Error::InvalidArg;\n      }\n\n      auto request = phys_cast<const NSSLAddServerPKIRequest *>(resourceRequest->requestData.args.ioctl.inputBuffer);\n      error = static_cast<Error>(device->addServerPKI(request->context, request->cert));\n      break;\n   }\n   default:\n      error = Error::InvalidArg;\n   }\n\n   return error;\n}\n\nstatic Error\nnsslIoctlv(phys_ptr<ResourceRequest> resourceRequest)\n{\n   auto error = Error::OK;\n   auto device = getDevice(resourceRequest->requestData.handle);\n   if (!device) {\n      return static_cast<Error>(NSSLError::InvalidHandle);\n   }\n\n   switch (static_cast<NSSLCommand>(resourceRequest->requestData.args.ioctlv.request)) {\n   case NSSLCommand::AddServerPKIExternal:\n   {\n      if (resourceRequest->requestData.args.ioctlv.numVecIn != 2) {\n         return Error::InvalidArg;\n      }\n\n      if (resourceRequest->requestData.args.ioctlv.numVecOut != 0) {\n         return Error::InvalidArg;\n      }\n\n      if (resourceRequest->requestData.args.ioctlv.vecs[1].len != sizeof(NSSLAddServerPKIExternalRequest)) {\n         return Error::InvalidArg;\n      }\n\n      if (!resourceRequest->requestData.args.ioctlv.vecs[1].paddr) {\n         return Error::InvalidArg;\n      }\n\n      auto request = phys_cast<NSSLAddServerPKIExternalRequest *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr);\n      auto cert = phys_cast<uint8_t *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr);\n      auto certSize = resourceRequest->requestData.args.ioctlv.vecs[0].len;\n\n      error = static_cast<Error>(\n         device->addServerPKIExternal(request->context, cert, certSize, request->certType));\n      break;\n   }\n   case NSSLCommand::ExportInternalClientCertificate:\n   {\n      if (resourceRequest->requestData.args.ioctlv.numVecIn != 1) {\n         return Error::InvalidArg;\n      }\n\n      if (resourceRequest->requestData.args.ioctlv.numVecOut != 3) {\n         return Error::InvalidArg;\n      }\n\n      if (resourceRequest->requestData.args.ioctlv.vecs[0].len !=\n             sizeof(NSSLExportInternalClientCertificateRequest) ||\n          resourceRequest->requestData.args.ioctlv.vecs[3].len !=\n             sizeof(NSSLExportInternalClientCertificateResponse)) {\n         return Error::InvalidArg;\n      }\n\n      if (!resourceRequest->requestData.args.ioctlv.vecs[0].paddr ||\n          !resourceRequest->requestData.args.ioctlv.vecs[3].paddr) {\n         return Error::InvalidArg;\n      }\n\n      auto request = phys_cast<NSSLExportInternalClientCertificateRequest *>(\n         resourceRequest->requestData.args.ioctlv.vecs[0].paddr);\n      auto certBuffer = phys_cast<uint8_t *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr);\n      auto certBufferSize = resourceRequest->requestData.args.ioctlv.vecs[1].len;\n\n      auto privateKeyBuffer = phys_cast<uint8_t *>(resourceRequest->requestData.args.ioctlv.vecs[2].paddr);\n      auto privateKeyBufferSize = resourceRequest->requestData.args.ioctlv.vecs[2].len;\n\n      auto response = phys_cast<NSSLExportInternalClientCertificateResponse *>(\n         resourceRequest->requestData.args.ioctlv.vecs[3].paddr);\n      error = static_cast<Error>(device->exportInternalClientCertificate(\n         request, response, certBuffer, certBufferSize, privateKeyBuffer,\n         privateKeyBufferSize));\n      break;\n   }\n   case NSSLCommand::ExportInternalServerCertificate:\n   {\n      if (resourceRequest->requestData.args.ioctlv.numVecIn != 1) {\n         return Error::InvalidArg;\n      }\n\n      if (resourceRequest->requestData.args.ioctlv.numVecOut != 2) {\n         return Error::InvalidArg;\n      }\n\n      if (resourceRequest->requestData.args.ioctlv.vecs[0].len !=\n             sizeof(NSSLExportInternalServerCertificateRequest) ||\n          resourceRequest->requestData.args.ioctlv.vecs[2].len !=\n             sizeof(NSSLExportInternalServerCertificateResponse)) {\n         return Error::InvalidArg;\n      }\n\n      if (!resourceRequest->requestData.args.ioctlv.vecs[0].paddr ||\n          !resourceRequest->requestData.args.ioctlv.vecs[2].paddr) {\n         return Error::InvalidArg;\n      }\n\n      auto request = phys_cast<NSSLExportInternalServerCertificateRequest *>(\n         resourceRequest->requestData.args.ioctlv.vecs[0].paddr);\n      auto certBuffer = phys_cast<uint8_t *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr);\n      auto certBufferSize = resourceRequest->requestData.args.ioctlv.vecs[1].len;\n\n      auto response = phys_cast<NSSLExportInternalServerCertificateResponse *>(\n         resourceRequest->requestData.args.ioctlv.vecs[2].paddr);\n      error = static_cast<Error>(device->exportInternalServerCertificate(\n         request, response, certBuffer, certBufferSize));\n      break;\n   }\n   default:\n      error = Error::InvalidArg;\n   }\n\n   return error;\n}\n\nstatic Error\nnsslThreadEntry(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n\n   while (true) {\n      auto error = IOS_ReceiveMessage(sNsslThreadData->messageQueueId,\n                                      message,\n                                      MessageFlags::None);\n      if (error < Error::OK) {\n         break;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n         IOS_ResourceReply(request, nsslOpen(request));\n         break;\n      case Command::Close:\n         IOS_ResourceReply(request, nsslClose(request));\n         break;\n      case Command::Ioctl:\n         IOS_ResourceReply(request, nsslIoctl(request));\n         break;\n      case Command::Ioctlv:\n         IOS_ResourceReply(request, nsslIoctlv(request));\n         break;\n      case Command::Suspend:\n         // TODO: /dev/nsec/nssl Suspend\n         return Error::OK;\n      case Command::Resume:\n         // TODO: /dev/nsec/nssl Resume\n         return Error::OK;\n      default:\n         IOS_ResourceReply(request, Error::InvalidArg);\n      }\n   }\n\n   if (sNsslThreadData->messageQueueId > 0) {\n      IOS_DestroyMessageQueue(sNsslThreadData->messageQueueId);\n      sNsslThreadData->messageQueueId = Error::Invalid;\n   }\n\n   IOS_SuspendThread(IOS_GetCurrentThreadId());\n   return Error::OK;\n}\n\nError\nregisterNsslResourceManager()\n{\n   auto error = IOS_CreateMessageQueue(phys_addrof(sNsslThreadData->messageBuffer),\n                                       sNsslThreadData->messageBuffer.size());\n   if (error < Error::OK) {\n      return error;\n   }\n   sNsslThreadData->messageQueueId = static_cast<MessageQueueId>(error);\n\n   error = IOS_RegisterResourceManager(\"/dev/nsec/nssl\", sNsslThreadData->messageQueueId);\n   if (error < Error::OK) {\n      nsecLog->error(\"registerNsslResourceManager: IOS_RegisterResourceManager returned {}\", error);\n      IOS_DestroyMessageQueue(sNsslThreadData->messageQueueId);\n      sNsslThreadData->messageQueueId = Error::Invalid;\n      return error;\n   }\n\n   error = IOS_AssociateResourceManager(\"/dev/nsec/nssl\", ResourcePermissionGroup::NSEC);\n   if (error < Error::OK) {\n      nsecLog->error(\"registerNsslResourceManager: IOS_AssociateResourceManager returned {}\", error);\n      IOS_DestroyMessageQueue(sNsslThreadData->messageQueueId);\n      sNsslThreadData->messageQueueId = Error::Invalid;\n      return error;\n   }\n\n   return Error::OK;\n}\n\nError\nstartNsslThread()\n{\n   auto error = loadCertstoreMetadata();\n   if (error < Error::OK) {\n      nsecLog->warn(\"startNsslThread: Failed to load certstore metadata, error {}\", error);\n   }\n\n   error = IOS_CreateThread(&nsslThreadEntry,\n                            nullptr,\n                            phys_addrof(sNsslThreadData->threadStack) + sNsslThreadData->threadStack.size(),\n                            sNsslThreadData->threadStack.size(),\n                            NsslThreadPriority,\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      nsecLog->error(\"startNsslThread: IOS_CreateThread returned {}\", error);\n      return error;\n   }\n\n   sNsslThreadData->threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sNsslThreadData->threadId, \"NsslThread\");\n\n   return IOS_StartThread(sNsslThreadData->threadId);\n}\n\nError\nstopNsslThread()\n{\n   return IOS_JamMessage(sNsslThreadData->messageQueueId,\n                         makeMessage(phys_addrof(sNsslThreadData->stopMessage)),\n                         MessageFlags::NonBlocking);\n}\n\nvoid\ninitialiseStaticNsslData()\n{\n   sNsslThreadData = allocProcessStatic<StaticNsslThreadData>();\n   sNsslThreadData->stopMessage.command = Command::Suspend;\n}\n\n} // namespace ios::nsec::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_nssl_thread.h",
    "content": "#pragma once\n#include \"ios/ios_enum.h\"\n\nnamespace ios::nsec::internal\n{\n\nError\nregisterNsslResourceManager();\n\nError\nstartNsslThread();\n\nError\nstopNsslThread();\n\nvoid\ninitialiseStaticNsslData();\n\n} // namespace ios::nsec::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/nsec/ios_nsec_nssl_types.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace ios::nsec\n{\n\n/**\n * \\ingroup ios_nesc\n * @{\n */\n\n#pragma pack(push, 1)\n\nusing NSSLCertID = int32_t;\nusing NSSLContextHandle = int32_t;\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::nsec\n"
  },
  {
    "path": "src/libdecaf/src/ios/pad/ios_pad.cpp",
    "content": "#include \"ios_pad.h\"\n#include \"ios_pad_btrm_device.h\"\n#include \"ios_pad_log.h\"\n\n#include \"decaf_log.h\"\n#include \"ios/kernel/ios_kernel_heap.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n\nusing namespace ios::kernel;\n\nnamespace ios::pad\n{\n\nconstexpr auto LocalHeapSize = 0x100000u;\nconstexpr auto CrossHeapSize = 0x20000u;\n\nstatic phys_ptr<void> sLocalHeapBuffer = nullptr;\n\nnamespace internal\n{\n\nLogger padLog = { };\n\nstatic void\ninitialiseStaticData()\n{\n   sLocalHeapBuffer = kernel::allocProcessLocalHeap(LocalHeapSize);\n}\n\n} // namespace internal\n\nError\nprocessEntryPoint(phys_ptr<void> /* context */)\n{\n   // Initialise logger\n   internal::padLog = decaf::makeLogger(\"IOS_PAD\");\n\n   // Initialise static memory\n   internal::initialiseStaticData();\n   internal::initialiseStaticBtrmData();\n\n   // Initialise process heaps\n   auto error = kernel::IOS_CreateLocalProcessHeap(sLocalHeapBuffer, LocalHeapSize);\n   if (error < Error::OK) {\n      internal::padLog->error(\"Failed to create local process heap, error = {}.\", error);\n      return error;\n   }\n\n   error = kernel::IOS_CreateCrossProcessHeap(CrossHeapSize);\n   if (error < Error::OK) {\n      internal::padLog->error(\"Failed to create cross process heap, error = {}.\", error);\n      return error;\n   }\n\n   // TODO: Create /dev/ccr_io thread\n\n   error = internal::startBtrmDeviceThread();\n   if (error < Error::OK) {\n      internal::padLog->error(\"Failed to start btrm device thread, error = {}.\", error);\n      return error;\n   }\n\n   return Error::OK;\n}\n\n} // namespace ios::pad\n"
  },
  {
    "path": "src/libdecaf/src/ios/pad/ios_pad.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_process.h\"\n\nnamespace ios::pad\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::pad\n"
  },
  {
    "path": "src/libdecaf/src/ios/pad/ios_pad_btrm_device.cpp",
    "content": "#include \"ios_pad.h\"\n#include \"ios_pad_btrm_device.h\"\n#include \"ios_pad_btrm_request.h\"\n#include \"ios_pad_btrm_response.h\"\n#include \"ios_pad_log.h\"\n\n#include \"decaf_log.h\"\n#include \"ios/kernel/ios_kernel_messagequeue.h\"\n#include \"ios/kernel/ios_kernel_thread.h\"\n#include \"ios/mcp/ios_mcp_ipc.h\"\n#include \"ios/ios_stackobject.h\"\n\n#include <cstring>\n\nusing namespace ios::kernel;\nusing namespace ios::mcp;\n\nnamespace ios::pad::internal\n{\n\nstruct StaticBtrmData\n{\n   be2_array<Message, 0x40> messageBuffer;\n   be2_val<MessageQueueId> messageQueue;\n\n   be2_array<Message, 0x10> transMessageBuffer;\n   be2_val<MessageQueueId> transMessageQueue;\n\n   be2_val<ThreadId> threadId;\n   be2_array<uint8_t, 0x4000> threadStack;\n};\n\nstatic phys_ptr<StaticBtrmData> sBtrmData = nullptr;\nstatic phys_ptr<void> sLocalHeapBuffer = nullptr;\n\nstatic Error\nbtrmIoctlv(phys_ptr<ResourceRequest> resourceRequest)\n{\n   if (resourceRequest->requestData.args.ioctlv.numVecIn != 1 ||\n       resourceRequest->requestData.args.ioctlv.numVecOut != 1 ||\n       !resourceRequest->requestData.args.ioctlv.vecs[0].paddr ||\n       resourceRequest->requestData.args.ioctlv.vecs[0].len != sizeof(BtrmRequest) ||\n       !resourceRequest->requestData.args.ioctlv.vecs[1].paddr ||\n       resourceRequest->requestData.args.ioctlv.vecs[1].len != sizeof(BtrmResponse)) {\n      return Error::Invalid;\n   }\n\n   auto request = phys_cast<BtrmRequest *>(resourceRequest->requestData.args.ioctlv.vecs[0].paddr);\n   auto response = phys_cast<BtrmResponse *>(resourceRequest->requestData.args.ioctlv.vecs[1].paddr);\n   auto result = Error::OK;\n\n   switch (request->command) {\n   case BtrmCommand::PpcInitDone:\n      std::memset(phys_addrof(response->ppcInitDone).get(), 0, sizeof(response->ppcInitDone));\n      response->ppcInitDone.btChipId = uint8_t { 63 };\n      response->ppcInitDone.btChipBuildNumber = uint16_t { 517 };\n      result = Error::OK;\n      break;\n   case BtrmCommand::Wud:\n   {\n      switch (request->subcommand) {\n      case BtrmSubCommand::UpdateBTDevSize:\n         result = Error::OK;\n         break;\n      default:\n         result = Error::Invalid;\n      }\n   }\n   default:\n      result = Error::Invalid;\n   }\n\n   return result;\n}\n\nstatic Error\nbtrmThreadMain(phys_ptr<void> /*context*/)\n{\n   StackObject<Message> message;\n\n   while (true) {\n      auto error = IOS_ReceiveMessage(sBtrmData->messageQueue,\n                                      message,\n                                      MessageFlags::None);\n      if (error < Error::OK) {\n         return error;\n      }\n\n      auto request = parseMessage<ResourceRequest>(message);\n      switch (request->requestData.command) {\n      case Command::Open:\n      {\n         auto device = std::string_view { phys_addrof(request->openNameBuffer).get() };\n         if (device == \"/dev/usb/btrm\") {\n            IOS_ResourceReply(request, static_cast<Error>(1));\n         } else if (device == \"/dev/usb/early_btrm\") {\n            IOS_ResourceReply(request, static_cast<Error>(2));\n         } else {\n            IOS_ResourceReply(request, static_cast<Error>(3));\n         }\n         break;\n      }\n      case Command::Close:\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      case Command::Ioctlv:\n      {\n         IOS_ResourceReply(request, btrmIoctlv(request));\n         break;\n      }\n      case Command::Resume:\n      case Command::Suspend:\n         IOS_ResourceReply(request, Error::OK);\n         break;\n      default:\n         IOS_ResourceReply(request, Error::Invalid);\n      }\n   }\n}\n\nError\nstartBtrmDeviceThread()\n{\n   auto error = Error::OK;\n\n   error = IOS_CreateMessageQueue(phys_addrof(sBtrmData->messageBuffer),\n                                  static_cast<uint32_t>(sBtrmData->messageBuffer.size()));\n   if (error < Error::OK) {\n      padLog->error(\"Failed to create message queue, error = {}.\", error);\n      return error;\n   }\n\n   sBtrmData->messageQueue = static_cast<MessageQueueId>(error);\n\n   error = MCP_RegisterResourceManager(\"/dev/usb/btrm\", sBtrmData->messageQueue);\n   if (error < Error::OK) {\n      padLog->error(\"Failed to register resource manager for btrm, error = {}.\", error);\n      return error;\n   }\n\n   error = MCP_RegisterResourceManager(\"/dev/usb/early_btrm\", sBtrmData->messageQueue);\n   if (error < Error::OK) {\n      padLog->error(\"Failed to register resource manager for early_btrm, error = {}.\", error);\n      return error;\n   }\n\n   error = IOS_AssociateResourceManager(\"/dev/usb/early_btrm\", ResourcePermissionGroup::PAD);\n   if (error < Error::OK) {\n      padLog->error(\"Failed to associate resource manager for early_btrm, error = {}.\", error);\n      return error;\n   }\n\n   error = IOS_CreateMessageQueue(phys_addrof(sBtrmData->transMessageBuffer),\n                                  static_cast<uint32_t>(sBtrmData->transMessageBuffer.size()));\n   if (error < Error::OK) {\n      padLog->error(\"Failed to create message queue, error = {}.\", error);\n      return error;\n   }\n\n   sBtrmData->transMessageQueue = static_cast<MessageQueueId>(error);\n\n   error = IOS_CreateThread(btrmThreadMain,\n                            nullptr,\n                            phys_addrof(sBtrmData->threadStack) + sBtrmData->threadStack.size(),\n                            static_cast<uint32_t>(sBtrmData->threadStack.size()),\n                            IOS_GetThreadPriority(IOS_GetCurrentThreadId()),\n                            ThreadFlags::Detached);\n   if (error < Error::OK) {\n      padLog->error(\"Failed to create btrm thread, error = {}.\", error);\n      return error;\n   }\n\n   sBtrmData->threadId = static_cast<ThreadId>(error);\n   kernel::internal::setThreadName(sBtrmData->threadId, \"BtrmThread\");\n\n   error = IOS_StartThread(sBtrmData->threadId);\n   if (error < Error::OK) {\n      padLog->error(\"Failed to start btrm thread, error = {}.\", error);\n      return error;\n   }\n\n   return Error::OK;\n}\n\nvoid\ninitialiseStaticBtrmData()\n{\n   sBtrmData = allocProcessStatic<StaticBtrmData>();\n}\n\n} // namespace ios::pad\n"
  },
  {
    "path": "src/libdecaf/src/ios/pad/ios_pad_btrm_device.h",
    "content": "#pragma once\n#include \"ios/ios_error.h\"\n\nnamespace ios::pad::internal\n{\n\nError\nstartBtrmDeviceThread();\n\nvoid\ninitialiseStaticBtrmData();\n\n} // namespace ios::pad::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/pad/ios_pad_btrm_request.h",
    "content": "#pragma once\n#include \"ios_pad_enum.h\"\n\n#include <libcpu/be2_struct.h>\n\nnamespace ios::pad\n{\n\n/**\n * \\ingroup ios_pad\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct BtrmRequest\n{\n   union {\n      be2_array<uint8_t, 0x1000> data;\n   };\n\n   be2_val<BtrmCommand> command;\n   be2_val<BtrmSubCommand> subcommand;\n   be2_val<uint16_t> unk0x1002;\n   be2_val<uint32_t> unk0x1004;\n};\nCHECK_OFFSET(BtrmRequest, 0x1000, command);\nCHECK_OFFSET(BtrmRequest, 0x1001, subcommand);\nCHECK_OFFSET(BtrmRequest, 0x1002, unk0x1002);\nCHECK_OFFSET(BtrmRequest, 0x1004, unk0x1004);\nCHECK_SIZE(BtrmRequest, 0x1008);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::pad\n"
  },
  {
    "path": "src/libdecaf/src/ios/pad/ios_pad_btrm_response.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace ios::pad\n{\n\n/**\n * \\ingroup ios_pad\n * @{\n */\n\n#pragma pack(push, 1)\n\nstruct BtrmResponsePpcInitDone\n{\n   be2_val<uint32_t> unk0x00;\n   be2_array<char, 6> unk0x04;\n   be2_val<uint8_t> btChipId;\n   be2_val<uint16_t> unk0x0B;\n   be2_val<uint16_t> btChipBuildNumber;\n   be2_val<uint8_t> unk0x0F;\n};\nCHECK_OFFSET(BtrmResponsePpcInitDone, 0x00, unk0x00);\nCHECK_OFFSET(BtrmResponsePpcInitDone, 0x04, unk0x04);\nCHECK_OFFSET(BtrmResponsePpcInitDone, 0x0A, btChipId);\nCHECK_OFFSET(BtrmResponsePpcInitDone, 0x0B, unk0x0B);\nCHECK_OFFSET(BtrmResponsePpcInitDone, 0x0D, btChipBuildNumber);\nCHECK_OFFSET(BtrmResponsePpcInitDone, 0x0F, unk0x0F);\n\nstruct BtrmResponse\n{\n   union {\n      be2_array<uint8_t, 0x1000> data;\n\n      be2_struct<BtrmResponsePpcInitDone> ppcInitDone;\n   };\n\n   UNKNOWN(0xC);\n};\nCHECK_SIZE(BtrmResponse, 0x100C);\n\n#pragma pack(pop)\n\n/** @} */\n\n} // namespace ios::pad\n"
  },
  {
    "path": "src/libdecaf/src/ios/pad/ios_pad_enum.h",
    "content": "#ifndef IOS_PAD_ENUM_H\n#define IOS_PAD_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(ios)\nENUM_NAMESPACE_ENTER(pad)\n\nENUM_BEG(BtrmCommand, uint8_t)\n   ENUM_VALUE(PpcInitDone,             1)\n   ENUM_VALUE(Wud,                     3)\n   ENUM_VALUE(Bte,                     4)\nENUM_END(BtrmCommand)\n\nENUM_BEG(BtrmSubCommand, uint8_t)\n   // Category 3\n   ENUM_VALUE(UpdateBTDevSize,         29)\nENUM_END(BtrmSubCommand)\n\nENUM_NAMESPACE_EXIT(pad)\nENUM_NAMESPACE_EXIT(ios)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef IOS_PAD_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/ios/pad/ios_pad_log.h",
    "content": "#pragma once\n#include <common/log.h>\n\nnamespace ios::pad::internal\n{\n\nextern Logger padLog;\n\n} // namespace ios::pad::internal\n"
  },
  {
    "path": "src/libdecaf/src/ios/test/ios_test.cpp",
    "content": "#include \"ios_test.h\"\n\nnamespace ios::test\n{\n\nError\nprocessEntryPoint(phys_ptr<void> /* context */)\n{\n   return Error::OK;\n}\n\n} // namespace ios::test\n"
  },
  {
    "path": "src/libdecaf/src/ios/test/ios_test.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_process.h\"\n\nnamespace ios::test\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::test\n"
  },
  {
    "path": "src/libdecaf/src/ios/usb/ios_usb.cpp",
    "content": "#include \"ios_usb.h\"\n\nnamespace ios::usb\n{\n\nError\nprocessEntryPoint(phys_ptr<void> /* context */)\n{\n   return Error::OK;\n}\n\n} // namespace ios::usb\n"
  },
  {
    "path": "src/libdecaf/src/ios/usb/ios_usb.h",
    "content": "#pragma once\n#include \"ios/kernel/ios_kernel_process.h\"\n\nnamespace ios::usb\n{\n\nError\nprocessEntryPoint(phys_ptr<void> context);\n\n} // namespace ios::usb\n"
  },
  {
    "path": "src/libdecaf/src/nn/ac/nn_ac_result.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace nn::ac\n{\n\nstatic constexpr Result ResultSuccess {\n   nn::Result::MODULE_NN_AC, nn::Result::LEVEL_SUCCESS, 128\n};\n\nstatic constexpr Result ResultInvalidArgument {\n   nn::Result::MODULE_NN_AC, nn::Result::LEVEL_USAGE, 0xC980\n};\n\nstatic constexpr Result ResultLibraryNotInitialiased {\n   nn::Result::MODULE_NN_AC, nn::Result::LEVEL_USAGE, 0xCC00\n};\n\nstatic constexpr Result ResultConnectFailed {\n   nn::Result::MODULE_NN_AC, nn::Result::LEVEL_STATUS, 0xFF80\n};\n\n} // namespace nn::ac\n"
  },
  {
    "path": "src/libdecaf/src/nn/ac/nn_ac_service.h",
    "content": "#pragma once\n#include \"nn/ipc/nn_ipc_command.h\"\n#include \"nn/ipc/nn_ipc_managedbuffer.h\"\n#include \"nn/ipc/nn_ipc_service.h\"\n\n#include <cstdint>\n\nnamespace nn::ac::services\n{\n\nstruct AcService : ipc::Service<0>\n{\n   using Initialise =\n      ipc::Command<AcService, 80>\n         ::Parameters<>\n         ::Response<>;\n\n   using Finalise =\n      ipc::Command<AcService, 81>\n         ::Parameters<>\n         ::Response<>;\n\n   using GetAssignedAddress =\n      ipc::Command<AcService, 0x610>\n         ::Parameters<uint32_t>\n         ::Response<uint32_t>;\n};\n\n} // namespace nn::ac::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/acp/nn_acp_enum.h",
    "content": "#ifndef NN_ACP_ENUM_H\n#define NN_ACP_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(nn)\nENUM_NAMESPACE_ENTER(acp)\n\nENUM_BEG(ACPDeviceType, int32_t)\n   ENUM_VALUE(Unknown1,             1)\nENUM_END(ACPDeviceType)\n\nENUM_NAMESPACE_EXIT(acp)\nENUM_NAMESPACE_EXIT(nn)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef NN_ACP_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/nn/acp/nn_acp_miscservice.h",
    "content": "#pragma once\n#include \"nn_acp_types.h\"\n\n#include \"nn/ipc/nn_ipc_command.h\"\n#include \"nn/ipc/nn_ipc_managedbuffer.h\"\n#include \"nn/ipc/nn_ipc_service.h\"\n\n#include <cstdint>\n\nnamespace nn::acp::services\n{\n\nstruct MiscService : ipc::Service<0>\n{\n   using GetNetworkTime =\n      ipc::Command<MiscService, 101>\n         ::Parameters<>\n         ::Response<int64_t, uint32_t>;\n\n   using GetTitleIdOfMainApplication =\n      ipc::Command<MiscService, 201>\n      ::Parameters<>\n      ::Response<ACPTitleId>;\n\n   using GetTitleMetaXml =\n      ipc::Command<MiscService, 205>\n         ::Parameters<ipc::OutBuffer<ACPMetaXml>, ACPTitleId>;\n};\n\n} // namespace nn::acp::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/acp/nn_acp_result.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace nn::acp\n{\n\nstatic constexpr Result ResultSuccess {\n   nn::Result::MODULE_NN_ACP, nn::Result::LEVEL_SUCCESS, 128\n};\n\n\nstatic constexpr const auto ResultInvalid =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6400, 0x9600>();\n\nstatic constexpr const auto ResultInvalidParameter =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6480, 0x6500>();\n\nstatic constexpr const auto ResultInvalidFile =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6500, 0x6580>();\n\nstatic constexpr const auto ResultInvalidXmlFile =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6580, 0x6600>();\n\nstatic constexpr const auto ResultFileAccessMode =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6600, 0x6680>();\n\nstatic constexpr const auto ResultInvalidNetworkTime =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_USAGE, 0x6680, 0x6700>();\n\n\nstatic constexpr const auto ResultNotFound =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFA00, 0x12C00>();\n\nstatic constexpr const auto ResultFileNotFound =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFA80, 0xFB00>();\n\nstatic constexpr const auto ResultDirNotFound =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFB00, 0xFB80>();\n\nstatic constexpr const auto ResultDeviceNotFound =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFB80, 0xFC00>();\n\nstatic constexpr const auto ResultTitleNotFound =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFC00, 0xFC80>();\n\nstatic constexpr const auto ResultApplicationNotFound =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFC80, 0xFD00>();\n\nstatic constexpr const auto ResultSystemConfigNotFound =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFD00, 0xFD80>();\n\nstatic constexpr const auto ResultXmlItemNotFound =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0xFD80, 0xFE00>();\n\n\nstatic constexpr const auto ResultAlreadyExists =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x12C00, 0x15E00>();\n\nstatic constexpr const auto ResultFileAlreadyExists =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x12C80, 0x12D00>();\n\nstatic constexpr const auto ResultDirAlreadyExists =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x12D00, 0x12D80>();\n\n\nstatic constexpr const auto ResultAlreadyDone =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x15E00, 0x19000>();\n\n\nstatic constexpr const auto ResultAuthentication =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F400, 0x22600>();\n\nstatic constexpr const auto ResultInvalidRegion =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F480, 0x1F500>();\n\nstatic constexpr const auto ResultRestrictedRating =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F500, 0x1F580>();\n\nstatic constexpr const auto ResultNotPresentRating =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F580, 0x1F600>();\n\nstatic constexpr const auto ResultPendingRating =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F600, 0x1F680>();\n\nstatic constexpr const auto ResultNetSettingRequired =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F680, 0x1F700>();\n\nstatic constexpr const auto ResultNetAccountRequired =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F700, 0x1F780>();\n\nstatic constexpr const auto ResultNetAccountError =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F780, 0x1F800>();\n\nstatic constexpr const auto ResultBrowserRequired =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F800, 0x1F880>();\n\nstatic constexpr const auto ResultOlvRequired =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F880, 0x1F900>();\n\nstatic constexpr const auto ResultPincodeRequired =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F900, 0x1F980>();\n\nstatic constexpr const auto ResultIncorrectPincode =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1F980, 0x1FA00>();\n\nstatic constexpr const auto ResultInvalidLogo =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1FA00, 0x1FA80>();\n\nstatic constexpr const auto ResultDemoExpiredNumber =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1FA80, 0x1FB00>();\n\nstatic constexpr const auto ResultDrcRequired =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x1FB00, 0x1FB80>();\n\n\nstatic constexpr const auto ResultNoPermission =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x22600, 0x25800>();\n\nstatic constexpr const auto ResultNoFilePermission =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x22680, 0x22700>();\n\nstatic constexpr const auto ResultNoDirPermission =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x22700, 0x22780>();\n\n\nstatic constexpr const auto ResultBusy =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x28A00, 0x2BC00>();\n\nstatic constexpr const auto ResultUsbStorageNotReady =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x28A80, 0x28B00>();\n\n\nstatic constexpr const auto ResultCancelled =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2BC00, 0x2EE00>();\n\n\nstatic constexpr const auto ResultResource =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2EE00, 0x32000>();\n\nstatic constexpr const auto ResultDeviceFull =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2EE80, 0x2EF00>();\n\nstatic constexpr const auto ResultJournalFull =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2EF00, 0x2EF80>();\n\nstatic constexpr const auto ResultSystemMemory =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2EF80, 0x2F000>();\n\nstatic constexpr const auto ResultFsResource =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2F000, 0x2F080>();\n\nstatic constexpr const auto ResultIpcResource =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x2F080, 0x2F100>();\n\n\nstatic constexpr const auto ResultNotInitialised =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x32000, 0x35200>();\n\n\nstatic constexpr const auto ResultAccountError =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x35200, 0x38400>();\n\n\nstatic constexpr const auto ResultUnsupported =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x38400, 0x3B600>();\n\n\nstatic constexpr const auto ResultDevice =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x3E800, 0x41A00>();\n\nstatic constexpr const auto ResultDataCorrupted =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x3E880, 0x3E900>();\n\nstatic constexpr const auto ResultSlcDataCorrupted =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x3E900, 0x3E980>();\n\nstatic constexpr const auto ResultMlcDataCorrupted =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x3E980, 0x3EA00>();\n\nstatic constexpr const auto ResultUsbDataCorrupted =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x3EA00, 0x3EA80>();\n\n\nstatic constexpr const auto ResultMedia =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41A00, 0x44C00>();\n\nstatic constexpr const auto ResultMediaNotReady =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41A80, 0x41B00>();\n\nstatic constexpr const auto ResultMediaBroken =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41B00, 0x41B80>();\n\nstatic constexpr const auto ResultOddMediaNotReady =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41B80, 0x41C00>();\n\nstatic constexpr const auto ResultOddMediaBroken =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41C00, 0x41C80>();\n\nstatic constexpr const auto ResultUsbMediaNotReady =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41C80, 0x41D00>();\n\nstatic constexpr const auto ResultUsbMediaBroken =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41D00, 0x41D80>();\n\nstatic constexpr const auto ResultMediaWriteProtected =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41D80, 0x41E00>();\n\nstatic constexpr const auto ResultUsbWriteProtected =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x41E00, 0x41E80>();\n\n\nstatic constexpr const auto ResultMii =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x44C00, 0x47E00>();\n\nstatic constexpr const auto ResultEncryptionError =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_STATUS, 0x44C80, 0x44D00>();\n\n\nstatic constexpr const auto ResultFatal =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D000, 0x80000>();\n\nstatic constexpr const auto ResultFsaFatal =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D080, 0x7D100>();\n\nstatic constexpr const auto ResultFsaAddClientFatal =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D100, 0x7D180>();\n\nstatic constexpr const auto ResultMcpTitleFatal =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D180, 0x7D200>();\n\nstatic constexpr const auto ResultMcpPatchFatal =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D200, 0x7D280>();\n\nstatic constexpr const auto ResultMcpFatal =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D280, 0x7D300>();\n\nstatic constexpr const auto ResultSaveFatal =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D300, 0x7D380>();\n\nstatic constexpr const auto ResultUcFatal =\n   ResultRange<Result::MODULE_NN_ACP, Result::LEVEL_FATAL, 0x7D380, 0x7D400>();\n\n\n} // namespace nn::acp\n"
  },
  {
    "path": "src/libdecaf/src/nn/acp/nn_acp_saveservice.h",
    "content": "#pragma once\n#include \"nn_acp_enum.h\"\n#include \"nn_acp_types.h\"\n\n#include \"nn/ipc/nn_ipc_service.h\"\n\n#include <cstdint>\n\nnamespace nn::acp::services\n{\n\nstruct SaveService : ipc::Service<2>\n{\n   using MountSaveDir =\n      ipc::Command<SaveService, 101>\n      ::Parameters<>;\n\n   using UnmountSaveDir =\n      ipc::Command<SaveService, 102>\n      ::Parameters<>;\n\n   using CreateSaveDir =\n      ipc::Command<SaveService, 103>\n      ::Parameters<uint32_t, ACPDeviceType>;\n\n   using CreateSaveDirEx =\n      ipc::Command<SaveService, 108>\n      ::Parameters<uint32_t, ACPTitleId, ACPDeviceType>;\n\n   using RepairSaveMetaDir =\n      ipc::Command<SaveService, 125>\n      ::Parameters<>;\n\n   using MountExternalStorage =\n      ipc::Command<SaveService, 150>\n      ::Parameters<>;\n\n   using UnmountExternalStorage =\n      ipc::Command<SaveService, 151>\n      ::Parameters<>;\n\n   using IsExternalStorageRequired =\n      ipc::Command<SaveService, 152>\n      ::Parameters<>\n      ::Response<int32_t>;\n};\n\n} // namespace nn::acp::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/acp/nn_acp_types.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace nn::acp\n{\n\nusing ACPTitleId = uint64_t;\n\nstruct ACPMetaXml\n{\n   be2_val<uint64_t> title_id;\n   be2_val<uint64_t> boss_id;\n   be2_val<uint64_t> os_version;\n   be2_val<uint64_t> app_size;\n   be2_val<uint64_t> common_save_size;\n   be2_val<uint64_t> account_save_size;\n   be2_val<uint64_t> common_boss_size;\n   be2_val<uint64_t> account_boss_size;\n   be2_val<uint64_t> join_game_mode_mask;\n   be2_val<uint32_t> version;\n   be2_array<char, 32> product_code;\n   be2_array<char, 32> content_platform;\n   be2_array<char, 8> company_code;\n   be2_array<char, 32> mastering_date;\n   be2_val<uint32_t> logo_type;\n   be2_val<uint32_t> app_launch_type;\n   be2_val<uint32_t> invisible_flag;\n   be2_val<uint32_t> no_managed_flag;\n   be2_val<uint32_t> no_event_log;\n   be2_val<uint32_t> no_icon_database;\n   be2_val<uint32_t> launching_flag;\n   be2_val<uint32_t> install_flag;\n   be2_val<uint32_t> closing_msg;\n   be2_val<uint32_t> title_version;\n   be2_val<uint32_t> group_id;\n   be2_val<uint32_t> save_no_rollback;\n   be2_val<uint32_t> bg_daemon_enable;\n   be2_val<uint32_t> join_game_id;\n   be2_val<uint32_t> olv_accesskey;\n   be2_val<uint32_t> wood_tin;\n   be2_val<uint32_t> e_manual;\n   be2_val<uint32_t> e_manual_version;\n   be2_val<uint32_t> region;\n   be2_val<uint32_t> pc_cero;\n   be2_val<uint32_t> pc_esrb;\n   be2_val<uint32_t> pc_bbfc;\n   be2_val<uint32_t> pc_usk;\n   be2_val<uint32_t> pc_pegi_gen;\n   be2_val<uint32_t> pc_pegi_fin;\n   be2_val<uint32_t> pc_pegi_prt;\n   be2_val<uint32_t> pc_pegi_bbfc;\n   be2_val<uint32_t> pc_cob;\n   be2_val<uint32_t> pc_grb;\n   be2_val<uint32_t> pc_cgsrr;\n   be2_val<uint32_t> pc_oflc;\n   be2_val<uint32_t> pc_reserved0;\n   be2_val<uint32_t> pc_reserved1;\n   be2_val<uint32_t> pc_reserved2;\n   be2_val<uint32_t> pc_reserved3;\n   be2_val<uint32_t> ext_dev_nunchaku;\n   be2_val<uint32_t> ext_dev_classic;\n   be2_val<uint32_t> ext_dev_urcc;\n   be2_val<uint32_t> ext_dev_board;\n   be2_val<uint32_t> ext_dev_usb_keyboard;\n   be2_val<uint32_t> ext_dev_etc;\n   be2_array<char, 512> ext_dev_etc_name;\n   be2_val<uint32_t> eula_version;\n   be2_val<uint32_t> drc_use;\n   be2_val<uint32_t> network_use;\n   be2_val<uint32_t> online_account_use;\n   be2_val<uint32_t> direct_boot;\n   be2_array<uint32_t, 8> reserved_flags;\n   be2_array<char, 512> longname_ja;\n   be2_array<char, 512> longname_en;\n   be2_array<char, 512> longname_fr;\n   be2_array<char, 512> longname_de;\n   be2_array<char, 512> longname_it;\n   be2_array<char, 512> longname_es;\n   be2_array<char, 512> longname_zhs;\n   be2_array<char, 512> longname_ko;\n   be2_array<char, 512> longname_nl;\n   be2_array<char, 512> longname_pt;\n   be2_array<char, 512> longname_ru;\n   be2_array<char, 512> longname_zht;\n   be2_array<char, 256> shortname_ja;\n   be2_array<char, 256> shortname_en;\n   be2_array<char, 256> shortname_fr;\n   be2_array<char, 256> shortname_de;\n   be2_array<char, 256> shortname_it;\n   be2_array<char, 256> shortname_es;\n   be2_array<char, 256> shortname_zhs;\n   be2_array<char, 256> shortname_ko;\n   be2_array<char, 256> shortname_nl;\n   be2_array<char, 256> shortname_pt;\n   be2_array<char, 256> shortname_ru;\n   be2_array<char, 256> shortname_zht;\n   be2_array<char, 256> publisher_ja;\n   be2_array<char, 256> publisher_en;\n   be2_array<char, 256> publisher_fr;\n   be2_array<char, 256> publisher_de;\n   be2_array<char, 256> publisher_it;\n   be2_array<char, 256> publisher_es;\n   be2_array<char, 256> publisher_zhs;\n   be2_array<char, 256> publisher_ko;\n   be2_array<char, 256> publisher_nl;\n   be2_array<char, 256> publisher_pt;\n   be2_array<char, 256> publisher_ru;\n   be2_array<char, 256> publisher_zht;\n   be2_array<uint32_t, 32> add_on_unique_ids;\n   UNKNOWN(52);\n};\nCHECK_OFFSET(ACPMetaXml, 0x0000, title_id);\nCHECK_OFFSET(ACPMetaXml, 0x0008, boss_id);\nCHECK_OFFSET(ACPMetaXml, 0x0010, os_version);\nCHECK_OFFSET(ACPMetaXml, 0x0018, app_size);\nCHECK_OFFSET(ACPMetaXml, 0x0020, common_save_size);\nCHECK_OFFSET(ACPMetaXml, 0x0028, account_save_size);\nCHECK_OFFSET(ACPMetaXml, 0x0030, common_boss_size);\nCHECK_OFFSET(ACPMetaXml, 0x0038, account_boss_size);\nCHECK_OFFSET(ACPMetaXml, 0x0040, join_game_mode_mask);\nCHECK_OFFSET(ACPMetaXml, 0x0048, version);\nCHECK_OFFSET(ACPMetaXml, 0x004C, product_code);\nCHECK_OFFSET(ACPMetaXml, 0x006C, content_platform);\nCHECK_OFFSET(ACPMetaXml, 0x008C, company_code);\nCHECK_OFFSET(ACPMetaXml, 0x0094, mastering_date);\nCHECK_OFFSET(ACPMetaXml, 0x00B4, logo_type);\nCHECK_OFFSET(ACPMetaXml, 0x00B8, app_launch_type);\nCHECK_OFFSET(ACPMetaXml, 0x00BC, invisible_flag);\nCHECK_OFFSET(ACPMetaXml, 0x00C0, no_managed_flag);\nCHECK_OFFSET(ACPMetaXml, 0x00C4, no_event_log);\nCHECK_OFFSET(ACPMetaXml, 0x00C8, no_icon_database);\nCHECK_OFFSET(ACPMetaXml, 0x00CC, launching_flag);\nCHECK_OFFSET(ACPMetaXml, 0x00D0, install_flag);\nCHECK_OFFSET(ACPMetaXml, 0x00D4, closing_msg);\nCHECK_OFFSET(ACPMetaXml, 0x00D8, title_version);\nCHECK_OFFSET(ACPMetaXml, 0x00DC, group_id);\nCHECK_OFFSET(ACPMetaXml, 0x00E0, save_no_rollback);\nCHECK_OFFSET(ACPMetaXml, 0x00E4, bg_daemon_enable);\nCHECK_OFFSET(ACPMetaXml, 0x00E8, join_game_id);\nCHECK_OFFSET(ACPMetaXml, 0x00EC, olv_accesskey);\nCHECK_OFFSET(ACPMetaXml, 0x00F0, wood_tin);\nCHECK_OFFSET(ACPMetaXml, 0x00F4, e_manual);\nCHECK_OFFSET(ACPMetaXml, 0x00F8, e_manual_version);\nCHECK_OFFSET(ACPMetaXml, 0x00FC, region);\nCHECK_OFFSET(ACPMetaXml, 0x0100, pc_cero);\nCHECK_OFFSET(ACPMetaXml, 0x0104, pc_esrb);\nCHECK_OFFSET(ACPMetaXml, 0x0108, pc_bbfc);\nCHECK_OFFSET(ACPMetaXml, 0x010C, pc_usk);\nCHECK_OFFSET(ACPMetaXml, 0x0110, pc_pegi_gen);\nCHECK_OFFSET(ACPMetaXml, 0x0114, pc_pegi_fin);\nCHECK_OFFSET(ACPMetaXml, 0x0118, pc_pegi_prt);\nCHECK_OFFSET(ACPMetaXml, 0x011C, pc_pegi_bbfc);\nCHECK_OFFSET(ACPMetaXml, 0x0120, pc_cob);\nCHECK_OFFSET(ACPMetaXml, 0x0124, pc_grb);\nCHECK_OFFSET(ACPMetaXml, 0x0128, pc_cgsrr);\nCHECK_OFFSET(ACPMetaXml, 0x012C, pc_oflc);\nCHECK_OFFSET(ACPMetaXml, 0x0130, pc_reserved0);\nCHECK_OFFSET(ACPMetaXml, 0x0134, pc_reserved1);\nCHECK_OFFSET(ACPMetaXml, 0x0138, pc_reserved2);\nCHECK_OFFSET(ACPMetaXml, 0x013C, pc_reserved3);\nCHECK_OFFSET(ACPMetaXml, 0x0140, ext_dev_nunchaku);\nCHECK_OFFSET(ACPMetaXml, 0x0144, ext_dev_classic);\nCHECK_OFFSET(ACPMetaXml, 0x0148, ext_dev_urcc);\nCHECK_OFFSET(ACPMetaXml, 0x014C, ext_dev_board);\nCHECK_OFFSET(ACPMetaXml, 0x0150, ext_dev_usb_keyboard);\nCHECK_OFFSET(ACPMetaXml, 0x0154, ext_dev_etc);\nCHECK_OFFSET(ACPMetaXml, 0x0158, ext_dev_etc_name);\nCHECK_OFFSET(ACPMetaXml, 0x0358, eula_version);\nCHECK_OFFSET(ACPMetaXml, 0x035C, drc_use);\nCHECK_OFFSET(ACPMetaXml, 0x0360, network_use);\nCHECK_OFFSET(ACPMetaXml, 0x0364, online_account_use);\nCHECK_OFFSET(ACPMetaXml, 0x0368, direct_boot);\nCHECK_OFFSET(ACPMetaXml, 0x036C, reserved_flags);\nCHECK_OFFSET(ACPMetaXml, 0x038C, longname_ja);\nCHECK_OFFSET(ACPMetaXml, 0x058C, longname_en);\nCHECK_OFFSET(ACPMetaXml, 0x078C, longname_fr);\nCHECK_OFFSET(ACPMetaXml, 0x098C, longname_de);\nCHECK_OFFSET(ACPMetaXml, 0x0B8C, longname_it);\nCHECK_OFFSET(ACPMetaXml, 0x0D8C, longname_es);\nCHECK_OFFSET(ACPMetaXml, 0x0F8C, longname_zhs);\nCHECK_OFFSET(ACPMetaXml, 0x118C, longname_ko);\nCHECK_OFFSET(ACPMetaXml, 0x138C, longname_nl);\nCHECK_OFFSET(ACPMetaXml, 0x158C, longname_pt);\nCHECK_OFFSET(ACPMetaXml, 0x178C, longname_ru);\nCHECK_OFFSET(ACPMetaXml, 0x198C, longname_zht);\nCHECK_OFFSET(ACPMetaXml, 0x1B8C, shortname_ja);\nCHECK_OFFSET(ACPMetaXml, 0x1C8C, shortname_en);\nCHECK_OFFSET(ACPMetaXml, 0x1D8C, shortname_fr);\nCHECK_OFFSET(ACPMetaXml, 0x1E8C, shortname_de);\nCHECK_OFFSET(ACPMetaXml, 0x1F8C, shortname_it);\nCHECK_OFFSET(ACPMetaXml, 0x208C, shortname_es);\nCHECK_OFFSET(ACPMetaXml, 0x218C, shortname_zhs);\nCHECK_OFFSET(ACPMetaXml, 0x228C, shortname_ko);\nCHECK_OFFSET(ACPMetaXml, 0x238C, shortname_nl);\nCHECK_OFFSET(ACPMetaXml, 0x248C, shortname_pt);\nCHECK_OFFSET(ACPMetaXml, 0x258C, shortname_ru);\nCHECK_OFFSET(ACPMetaXml, 0x268C, shortname_zht);\nCHECK_OFFSET(ACPMetaXml, 0x278C, publisher_ja);\nCHECK_OFFSET(ACPMetaXml, 0x288C, publisher_en);\nCHECK_OFFSET(ACPMetaXml, 0x298C, publisher_fr);\nCHECK_OFFSET(ACPMetaXml, 0x2A8C, publisher_de);\nCHECK_OFFSET(ACPMetaXml, 0x2B8C, publisher_it);\nCHECK_OFFSET(ACPMetaXml, 0x2C8C, publisher_es);\nCHECK_OFFSET(ACPMetaXml, 0x2D8C, publisher_zhs);\nCHECK_OFFSET(ACPMetaXml, 0x2E8C, publisher_ko);\nCHECK_OFFSET(ACPMetaXml, 0x2F8C, publisher_nl);\nCHECK_OFFSET(ACPMetaXml, 0x308C, publisher_pt);\nCHECK_OFFSET(ACPMetaXml, 0x318C, publisher_ru);\nCHECK_OFFSET(ACPMetaXml, 0x328C, publisher_zht);\nCHECK_OFFSET(ACPMetaXml, 0x338C, add_on_unique_ids);\nCHECK_SIZE(ACPMetaXml, 0x3440);\n\n} // namespace nn::acp\n"
  },
  {
    "path": "src/libdecaf/src/nn/act/nn_act_accountloaderservice.h",
    "content": "#pragma once\n#include \"nn_act_enum.h\"\n#include \"nn_act_types.h\"\n\n#include \"nn/ipc/nn_ipc_command.h\"\n#include \"nn/ipc/nn_ipc_service.h\"\n#include \"nn/ipc/nn_ipc_managedbuffer.h\"\n\nnamespace nn::act::services\n{\n\nstruct AccountLoaderService : ipc::Service<3>\n{\n   using LoadConsoleAccount =\n      ipc::Command<AccountLoaderService, 1>\n         ::Parameters<SlotNo, ACTLoadOption, ipc::InBuffer<const char>, bool>;\n};\n\n} // namespace nn::act::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/act/nn_act_accountmanagerservice.h",
    "content": "#pragma once\n#include \"nn_act_enum.h\"\n#include \"nn_act_types.h\"\n\n#include \"nn/ipc/nn_ipc_command.h\"\n#include \"nn/ipc/nn_ipc_service.h\"\n#include \"nn/ipc/nn_ipc_managedbuffer.h\"\n\nnamespace nn::act::services\n{\n\nstruct AccountManagerService : ipc::Service<2>\n{\n   using CreateConsoleAccount =\n      ipc::Command<AccountManagerService, 1>\n         ::Parameters<>;\n};\n\n} // namespace nn::act::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/act/nn_act_clientstandardservice.h",
    "content": "#pragma once\n#include \"nn_act_enum.h\"\n#include \"nn_act_types.h\"\n\n#include \"nn/ipc/nn_ipc_command.h\"\n#include \"nn/ipc/nn_ipc_service.h\"\n#include \"nn/ipc/nn_ipc_managedbuffer.h\"\n\n#include <array>\n#include <cstdint>\n\nnamespace nn::act::services\n{\n\nstruct ClientStandardService : ipc::Service<0>\n{\n   using GetCommonInfo =\n      ipc::Command<ClientStandardService, 1>\n         ::Parameters<ipc::OutBuffer<void>, InfoType>;\n\n   using GetAccountInfo =\n      ipc::Command<ClientStandardService, 2>\n         ::Parameters<SlotNo, ipc::OutBuffer<void>, InfoType>;\n\n   using GetTransferableId =\n      ipc::Command<ClientStandardService, 4>\n         ::Parameters<SlotNo, uint32_t>\n         ::Response<TransferrableId>;\n\n   using GetMiiImage =\n      ipc::Command<ClientStandardService, 6>\n         ::Parameters<SlotNo, ipc::OutBuffer<void>, MiiImageType>\n         ::Response<uint32_t>;\n\n   using GetUuid =\n      ipc::Command<ClientStandardService, 22>\n         ::Parameters<SlotNo, ipc::OutBuffer<Uuid>, int32_t>;\n\n   using FindSlotNoByUuid =\n      ipc::Command<ClientStandardService, 23>\n         ::Parameters<Uuid, int32_t>\n         ::Response<uint8_t>;\n};\n\n} // namespace nn::act::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/act/nn_act_enum.h",
    "content": "#ifndef NN_ACT_ENUM_H\n#define NN_ACT_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(nn)\nENUM_NAMESPACE_ENTER(act)\n\nENUM_BEG(ACTLoadOption, int32_t)\nENUM_END(ACTLoadOption)\n\nENUM_BEG(InfoType, int32_t)\n   ENUM_VALUE(NumOfAccounts,                 1)\n   ENUM_VALUE(SlotNo,                        2)\n   ENUM_VALUE(DefaultAccount,                3)\n   ENUM_VALUE(NetworkTimeDifference,         4)\n   ENUM_VALUE(PersistentId,                  5)\n   ENUM_VALUE(LocalFriendCode,               6)\n   ENUM_VALUE(Mii,                           7)\n   ENUM_VALUE(AccountId,                     8)\n   ENUM_VALUE(EmailAddress,                  9)\n   ENUM_VALUE(Birthday,                      10)\n   ENUM_VALUE(Country,                       11)\n   ENUM_VALUE(PrincipalId,                   12)\n   ENUM_VALUE(IsPasswordCacheEnabled,        14)\n   ENUM_VALUE(AccountPasswordCache,          15)\n   ENUM_VALUE(AccountInfo,                   17)\n   ENUM_VALUE(HostServerSettings,            18)\n   ENUM_VALUE(Gender,                        19)\n   ENUM_VALUE(LastAuthenticationResult,      20)\n   ENUM_VALUE(StickyAccountId,               21)\n   ENUM_VALUE(ParentalControlSlot,           22)\n   ENUM_VALUE(SimpleAddressId,               23)\n   ENUM_VALUE(UtcOffset,                     25)\n   ENUM_VALUE(IsCommitted,                   26)\n   ENUM_VALUE(MiiName,                       27)\n   ENUM_VALUE(NfsPassword,                   28)\n   ENUM_VALUE(HasEciVirtualAccount,          29)\n   ENUM_VALUE(TimeZoneId,                    30)\n   ENUM_VALUE(IsMiiUpdated,                  31)\n   ENUM_VALUE(IsMailAddressValidated,        32)\n   ENUM_VALUE(NextAccountId,                 33)\n   ENUM_VALUE(Unk34,                         34)\n   ENUM_VALUE(ApplicationUpdateRequired,     35)\n   ENUM_VALUE(DefaultHostServerSettings,     36)\n   ENUM_VALUE(IsServerAccountDeleted,        37)\n   ENUM_VALUE(MiiImageUrl,                   38)\n   ENUM_VALUE(StickyPrincipalId,             39)\n   ENUM_VALUE(Unk40,                         40)\n   ENUM_VALUE(Unk41,                         41)\n   ENUM_VALUE(DefaultHostServerSettingsEx,   42)\n   ENUM_VALUE(DeviceHash,                    43)\n   ENUM_VALUE(ServerAccountStatus,           44)\n   ENUM_VALUE(NetworkTime,                   46)\nENUM_END(InfoType)\n\nENUM_BEG(MiiImageType, int32_t)\nENUM_END(MiiImageType)\n\nENUM_NAMESPACE_EXIT(act)\nENUM_NAMESPACE_EXIT(nn)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef NN_ACT_ENUM_H\n"
  },
  {
    "path": "src/libdecaf/src/nn/act/nn_act_result.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace nn::act\n{\n\nstatic constexpr Result ResultMailAddressNotConfirmed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x80\n};\n\n\nstatic constexpr Result ResultLibraryError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0xFA00\n};\n\nstatic constexpr Result ResultNotInitialised {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0xFA80\n};\n\nstatic constexpr Result ResultAlreadyInitialised {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0xFB00\n};\n\nstatic constexpr Result ResultBusy {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0xFF80\n};\n\n\nstatic constexpr Result ResultNotImplemented {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12780\n};\n\nstatic constexpr Result ResultDeprecated {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12800\n};\n\nstatic constexpr Result ResultDevelopmentOnly {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12880\n};\n\nstatic constexpr Result ResultInvalidArgument {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12C00\n};\n\nstatic constexpr Result ResultInvalidPointer {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12C80\n};\n\nstatic constexpr Result ResultOutOfRange {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12D00\n};\n\nstatic constexpr Result ResultInvalidSize {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12D80\n};\n\nstatic constexpr Result ResultInvalidFormat {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12E00\n};\n\nstatic constexpr Result ResultInvalidHandle {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12E80\n};\n\nstatic constexpr Result ResultInvalidValue {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x12F00\n};\n\n\nstatic constexpr Result ResultInternalError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x15E00\n};\n\nstatic constexpr Result ResultEndOfStream {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x15E80\n};\n\n\nstatic constexpr Result ResultFileError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x16300\n};\n\nstatic constexpr Result ResultFileNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_USAGE, 0x16380\n};\n\nstatic constexpr Result ResultFileVersionMismatch {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x16400\n};\n\nstatic constexpr Result ResultFileIoError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_FATAL, 0x16480\n};\n\nstatic constexpr Result ResultFileTypeMismatch {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x16500\n};\n\n\nstatic constexpr Result ResultOutOfResource {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x16D00\n};\n\nstatic constexpr Result ResultShortOfBuffer {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x16D80\n};\n\nstatic constexpr Result ResultOutOfMemory {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x17200\n};\n\nstatic constexpr Result ResultOutOfGlobalHeap {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x17280\n};\n\nstatic constexpr Result ResultOutOfCrossProcessHeap {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x17300\n};\n\nstatic constexpr Result ResultOutOfProcessLocalHeap {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x17380\n};\n\nstatic constexpr Result ResultOutOfMxmlHeap {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x17400\n};\n\n\nstatic constexpr Result ResultUcError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19000\n};\n\nstatic constexpr Result ResultUcReadSysConfigError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19080\n};\n\n\nstatic constexpr Result ResultMcpError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19500\n};\n\nstatic constexpr Result ResultMcpOpenError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19580\n};\n\nstatic constexpr Result ResultMcpGetInfoError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19600\n};\n\n\nstatic constexpr Result ResultIsoError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19A00\n};\n\nstatic constexpr Result ResultIsoInitFailure {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19A80\n};\n\nstatic constexpr Result ResultIsoGetCountryCodeFailure {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19B00\n};\n\nstatic constexpr Result ResultIsoGetLanguageCodeFailure {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x19B80\n};\n\nstatic constexpr Result ResultMxmlError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1a900\n};\n\nstatic constexpr Result ResultIosError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1c200\n};\n\nstatic constexpr Result ResultIosOpenError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1c280\n};\n\nstatic constexpr Result ResultAccountManagementError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1f400\n};\n\nstatic constexpr Result ResultAccountNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1f480\n};\n\nstatic constexpr Result ResultSlotsFull {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1f500\n};\n\nstatic constexpr Result ResultAccountNotLoaded {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1f980\n};\n\nstatic constexpr Result ResultAccountAlreadyLoaded {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1fa00\n};\n\nstatic constexpr Result ResultAccountLocked {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1fa80\n};\n\nstatic constexpr Result ResultNotNetworkAccount {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1fe80\n};\n\nstatic constexpr Result ResultNotLocalAccount {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1ff00\n};\n\nstatic constexpr Result ResultAccountNotCommitted {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x1ff80\n};\n\nstatic constexpr Result ResultNetworkClockInvalid {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x22680\n};\n\nstatic constexpr Result ResultAuthenticationError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x3e800\n};\n\nstatic constexpr Result ResultHttpError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41a00\n};\n\nstatic constexpr Result ResultHttpUnsupportedProtocol {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41a80\n};\n\nstatic constexpr Result ResultHttpFailedInit {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41b00\n};\n\nstatic constexpr Result ResultHttpURLMalformat {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41b80\n};\n\nstatic constexpr Result ResultHttpNotBuiltIn {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41c00\n};\n\nstatic constexpr Result ResultHttpCouldntResolveProxy {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41c80\n};\n\nstatic constexpr Result ResultHttpCouldntResolveHost {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41d00\n};\n\nstatic constexpr Result ResultHttpCouldntConnect {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41d80\n};\n\nstatic constexpr Result ResultHttpFtpWeirdServerReply {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41e00\n};\n\nstatic constexpr Result ResultHttpRemoteAccessDenied {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41e80\n};\n\nstatic constexpr Result ResultHttpObsolete10 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41f00\n};\n\nstatic constexpr Result ResultHttpFtpWeirdPassReply {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x41f80\n};\n\nstatic constexpr Result ResultHttpObsolete12 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42000\n};\n\nstatic constexpr Result ResultHttpFtpWeirdPasvReply {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42080\n};\n\nstatic constexpr Result ResultHttpFtpWeird227Format {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42100\n};\n\nstatic constexpr Result ResultHttpFtpCantGetHost {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42180\n};\n\nstatic constexpr Result ResultHttpObsolete16 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42200\n};\n\nstatic constexpr Result ResultHttpFtpCouldntSetType {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42280\n};\n\nstatic constexpr Result ResultHttpPartialFile {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42300\n};\n\nstatic constexpr Result ResultHttpFtpCouldntRetrFile {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42380\n};\n\nstatic constexpr Result ResultHttpObsolete20 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42400\n};\n\nstatic constexpr Result ResultHttpQuoteError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42480\n};\n\nstatic constexpr Result ResultHttpHttpReturnedError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42500\n};\n\nstatic constexpr Result ResultHttpWriteError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42580\n};\n\nstatic constexpr Result ResultHttpObsolete24 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42600\n};\n\nstatic constexpr Result ResultHttpUploadFailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42680\n};\n\nstatic constexpr Result ResultHttpReadError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42700\n};\n\nstatic constexpr Result ResultHttpOutOfMemory {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42780\n};\n\nstatic constexpr Result ResultHttpOperationTimedout {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42800\n};\n\nstatic constexpr Result ResultHttpObsolete29 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42880\n};\n\nstatic constexpr Result ResultHttpFtpPortFailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42900\n};\n\nstatic constexpr Result ResultHttpFtpCouldntUseRest {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42980\n};\n\nstatic constexpr Result ResultHttpObsolete32 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42a00\n};\n\nstatic constexpr Result ResultHttpRangeError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42a80\n};\n\nstatic constexpr Result ResultHttpHttpPostError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42b00\n};\n\nstatic constexpr Result ResultHttpSslConnectError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42b80\n};\n\nstatic constexpr Result ResultHttpBadDownloadResume {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42c00\n};\n\nstatic constexpr Result ResultHttpFileCouldntReadFile {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42c80\n};\n\nstatic constexpr Result ResultHttpLdapCannotBind {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42d00\n};\n\nstatic constexpr Result ResultHttpLdapSearchFailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42d80\n};\n\nstatic constexpr Result ResultHttpObsolete40 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42e00\n};\n\nstatic constexpr Result ResultHttpFunctionNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42e80\n};\n\nstatic constexpr Result ResultHttpAbortedByCallback {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42f00\n};\n\nstatic constexpr Result ResultHttpBadFunctionArgument {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x42f80\n};\n\nstatic constexpr Result ResultHttpObsolete44 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43000\n};\n\nstatic constexpr Result ResultHttpInterfaceFailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43080\n};\n\nstatic constexpr Result ResultHttpObsolete46 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43100\n};\n\nstatic constexpr Result ResultHttpTooManyRedirects {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43180\n};\n\nstatic constexpr Result ResultHttpUnknownOption {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43200\n};\n\nstatic constexpr Result ResultHttpTelnetOptionSyntax {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43280\n};\n\nstatic constexpr Result ResultHttpObsolete50 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43300\n};\n\nstatic constexpr Result ResultHttpPeerFailedVerification {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43380\n};\n\nstatic constexpr Result ResultHttpGotNothing {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43400\n};\n\nstatic constexpr Result ResultHttpSslEngineNotfound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43480\n};\n\nstatic constexpr Result ResultHttpSslEngineSetfailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43500\n};\n\nstatic constexpr Result ResultHttpSendError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43580\n};\n\nstatic constexpr Result ResultHttpRecvError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43600\n};\n\nstatic constexpr Result ResultHttpObsolete57 {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43680\n};\n\nstatic constexpr Result ResultHttpSslCertproblem {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43700\n};\n\nstatic constexpr Result ResultHttpSslCipher {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43780\n};\n\nstatic constexpr Result ResultHttpSslCacert {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43800\n};\n\nstatic constexpr Result ResultHttpBadContentEncoding {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43880\n};\n\nstatic constexpr Result ResultHttpLdapInvalidURL {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43900\n};\n\nstatic constexpr Result ResultHttpFilesizeExceeded {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43980\n};\n\nstatic constexpr Result ResultHttpUseSslFailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43a00\n};\n\nstatic constexpr Result ResultHttpSendFailRewind {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43a80\n};\n\nstatic constexpr Result ResultHttpSslEngineInitfailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43b00\n};\n\nstatic constexpr Result ResultHttpLoginDenied {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43b80\n};\n\nstatic constexpr Result ResultHttpTftpNotfound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43c00\n};\n\nstatic constexpr Result ResultHttpTftpPerm {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43c80\n};\n\nstatic constexpr Result ResultHttpRemoteDiskFull {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43d00\n};\n\nstatic constexpr Result ResultHttpTftpIllegal {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43d80\n};\n\nstatic constexpr Result ResultHttpTftpUnknownid {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43e00\n};\n\nstatic constexpr Result ResultHttpRemoteFileExists {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43e80\n};\n\nstatic constexpr Result ResultHttpTftpNosuchuser {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43f00\n};\n\nstatic constexpr Result ResultHttpConvFailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x43f80\n};\n\nstatic constexpr Result ResultHttpConvReqd {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44000\n};\n\nstatic constexpr Result ResultHttpSslCacertBadfile {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44080\n};\n\nstatic constexpr Result ResultHttpRemoteFileNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44100\n};\n\nstatic constexpr Result ResultHttpSsh {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44180\n};\n\nstatic constexpr Result ResultHttpSslShutdownFailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44200\n};\n\nstatic constexpr Result ResultHttpAgain {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44280\n};\n\nstatic constexpr Result ResultHttpSslCrlBadfile {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44300\n};\n\nstatic constexpr Result ResultHttpSslIssuerError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44380\n};\n\nstatic constexpr Result ResultHttpFtpPretFailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44400\n};\n\nstatic constexpr Result ResultHttpRtspCseqError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44480\n};\n\nstatic constexpr Result ResultHttpRtspSessionError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44500\n};\n\nstatic constexpr Result ResultHttpFtpBadFileList {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44580\n};\n\nstatic constexpr Result ResultHttpChunkFailed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44600\n};\n\nstatic constexpr Result ResultHttpNsslNoCtx {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x44680\n};\n\nstatic constexpr Result ResultSoError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x45100\n};\n\nstatic constexpr Result ResultSoSelectError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x45180\n};\n\nstatic constexpr Result ResultRequestError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4b000\n};\n\nstatic constexpr Result ResultBadFormatParameter {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4b080\n};\n\nstatic constexpr Result ResultBadFormatRequest {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4b100\n};\n\nstatic constexpr Result ResultRequestParameterMissing {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4b180\n};\n\nstatic constexpr Result ResultWrongHttpMethod {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4b200\n};\n\nstatic constexpr Result ResultResponseError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ba00\n};\n\nstatic constexpr Result ResultBadFormatResponse {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ba80\n};\n\nstatic constexpr Result ResultResponseItemMissing {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4bb00\n};\n\nstatic constexpr Result ResultResponseTooLarge {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4bb80\n};\n\nstatic constexpr Result ResultNotModified {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4c480\n};\n\nstatic constexpr Result ResultInvalidCommonParameter {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4c900\n};\n\nstatic constexpr Result ResultInvalidPlatformID {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4c980\n};\n\nstatic constexpr Result ResultUnauthorizedDevice {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ca00\n};\n\nstatic constexpr Result ResultInvalidSerialID {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ca80\n};\n\nstatic constexpr Result ResultInvalidMacAddress {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cb00\n};\n\nstatic constexpr Result ResultInvalidRegion {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cb80\n};\n\nstatic constexpr Result ResultInvalidCountry {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cc00\n};\n\nstatic constexpr Result ResultInvalidLanguage {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cc80\n};\n\nstatic constexpr Result ResultUnauthorizedClient {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cd00\n};\n\nstatic constexpr Result ResultDeviceIDEmpty {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4cd80\n};\n\nstatic constexpr Result ResultSerialIDEmpty {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ce00\n};\n\nstatic constexpr Result ResultPlatformIDEmpty {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ce80\n};\n\nstatic constexpr Result ResultInvalidUniqueID {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d380\n};\n\nstatic constexpr Result ResultInvalidClientID {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d400\n};\n\nstatic constexpr Result ResultInvalidClientKey {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d480\n};\n\nstatic constexpr Result ResultInvalidNexClientID {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d880\n};\n\nstatic constexpr Result ResultInvalidGameServerID {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d900\n};\n\nstatic constexpr Result ResultGameServerIDEnvironmentNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4d980\n};\n\nstatic constexpr Result ResultGameServerIDUniqueIDNotLinked {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4da00\n};\n\nstatic constexpr Result ResultClientIDUniqueIDNotLinked {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4da80\n};\n\nstatic constexpr Result ResultDeviceMismatch {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e280\n};\n\nstatic constexpr Result ResultCountryMismatch {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e300\n};\n\nstatic constexpr Result ResultEulaNotAccepted {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e380\n};\n\nstatic constexpr Result ResultUpdateRequired {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e700\n};\n\nstatic constexpr Result ResultSystemUpdateRequired {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e780\n};\n\nstatic constexpr Result ResultApplicationUpdateRequired {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4e800\n};\n\nstatic constexpr Result ResultUnauthorizedRequest {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ec00\n};\n\nstatic constexpr Result ResultRequestForbidden {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4ed00\n};\n\nstatic constexpr Result ResultResourceNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f100\n};\n\nstatic constexpr Result ResultPidNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f180\n};\n\nstatic constexpr Result ResultNexAccountNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f200\n};\n\nstatic constexpr Result ResultGenerateTokenFailure {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f280\n};\n\nstatic constexpr Result ResultRequestNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f300\n};\n\nstatic constexpr Result ResultMasterPinNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f380\n};\n\nstatic constexpr Result ResultMailTextNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f400\n};\n\nstatic constexpr Result ResultSendMailFailure {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f480\n};\n\nstatic constexpr Result ResultApprovalIDNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f500\n};\n\nstatic constexpr Result ResultInvalidEulaParameter {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f600\n};\n\nstatic constexpr Result ResultInvalidEulaCountry {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f680\n};\n\nstatic constexpr Result ResultInvalidEulaCountryAndVersion {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f700\n};\n\nstatic constexpr Result ResultEulaNotFound {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x4f780\n};\n\nstatic constexpr Result ResultPhraseNotAcceptable {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50500\n};\n\nstatic constexpr Result ResultAccountIDAlreadyExists {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50580\n};\n\nstatic constexpr Result ResultAccountIDNotAcceptable {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50600\n};\n\nstatic constexpr Result ResultAccountPasswordNotAcceptable {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50680\n};\n\nstatic constexpr Result ResultMiiNameNotAcceptable {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50700\n};\n\nstatic constexpr Result ResultMailAddressNotAcceptable {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50780\n};\n\nstatic constexpr Result ResultAccountIDFormatInvalid {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50800\n};\n\nstatic constexpr Result ResultAccountIDPasswordSame {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50880\n};\n\nstatic constexpr Result ResultAccountIDCharNotAcceptable {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50900\n};\n\nstatic constexpr Result ResultAccountIDSuccessiveSymbol {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50980\n};\n\nstatic constexpr Result ResultAccountIDSymbolPositionNotAcceptable {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50a00\n};\n\nstatic constexpr Result ResultAccountIDTooManyDigit {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50a80\n};\n\nstatic constexpr Result ResultAccountPasswordCharNotAcceptable {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50b00\n};\n\nstatic constexpr Result ResultAccountPasswordTooFewCharTypes {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50b80\n};\n\nstatic constexpr Result ResultAccountPasswordSuccessiveSameChar {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50c00\n};\n\nstatic constexpr Result ResultMailAddressDomainNameNotAcceptable {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50c80\n};\n\nstatic constexpr Result ResultMailAddressDomainNameNotResolved {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50d00\n};\n\nstatic constexpr Result ResultReachedAssociationLimit {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x50f80\n};\n\nstatic constexpr Result ResultReachedRegistrationLimit {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51000\n};\n\nstatic constexpr Result ResultCoppaNotAccepted {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51080\n};\n\nstatic constexpr Result ResultParentalControlsRequired {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51100\n};\n\nstatic constexpr Result ResultMiiNotRegistered {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51180\n};\n\nstatic constexpr Result ResultDeviceEulaCountryMismatch {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51200\n};\n\nstatic constexpr Result ResultPendingMigration {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51280\n};\n\nstatic constexpr Result ResultWrongUserInput {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51900\n};\n\nstatic constexpr Result ResultWrongAccountPassword {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51980\n};\n\nstatic constexpr Result ResultWrongMailAddress {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51a00\n};\n\nstatic constexpr Result ResultWrongAccountPasswordOrMailAddress {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51a80\n};\n\nstatic constexpr Result ResultWrongConfirmationCode {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51b00\n};\n\nstatic constexpr Result ResultWrongBirthDateOrMailAddress {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51b80\n};\n\nstatic constexpr Result ResultWrongAccountMail {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x51c00\n};\n\nstatic constexpr Result ResultAccountAlreadyDeleted {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52380\n};\n\nstatic constexpr Result ResultAccountIDChanged {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52400\n};\n\nstatic constexpr Result ResultAuthenticationLocked {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52480\n};\n\nstatic constexpr Result ResultDeviceInactive {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52500\n};\n\nstatic constexpr Result ResultCoppaAgreementCanceled {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52580\n};\n\nstatic constexpr Result ResultDomainAccountAlreadyExists {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52600\n};\n\nstatic constexpr Result ResultAccountTokenExpired {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52880\n};\n\nstatic constexpr Result ResultInvalidAccountToken {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52900\n};\n\nstatic constexpr Result ResultAuthenticationRequired {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52980\n};\n\nstatic constexpr Result ResultConfirmationCodeExpired {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x52d80\n};\n\nstatic constexpr Result ResultMailAddressNotValidated {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53280\n};\n\nstatic constexpr Result ResultExcessiveMailSendRequest {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53300\n};\n\nstatic constexpr Result ResultCreditCardError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53700\n};\n\nstatic constexpr Result ResultCreditCardGeneralFailure {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53780\n};\n\nstatic constexpr Result ResultCreditCardDeclined {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53800\n};\n\nstatic constexpr Result ResultCreditCardBlacklisted {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53880\n};\n\nstatic constexpr Result ResultInvalidCreditCardNumber {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53900\n};\n\nstatic constexpr Result ResultInvalidCreditCardDate {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53980\n};\n\nstatic constexpr Result ResultInvalidCreditCardPin {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53a00\n};\n\nstatic constexpr Result ResultInvalidPostalCode {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53a80\n};\n\nstatic constexpr Result ResultInvalidLocation {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53b00\n};\n\nstatic constexpr Result ResultCreditCardDateExpired {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53b80\n};\n\nstatic constexpr Result ResultCreditCardNumberWrong {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53c00\n};\n\nstatic constexpr Result ResultCreditCardPinWrong {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x53c80\n};\n\nstatic constexpr Result ResultBanned {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57800\n};\n\nstatic constexpr Result ResultBannedAccount {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57880\n};\n\nstatic constexpr Result ResultBannedAccountAll {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57900\n};\n\nstatic constexpr Result ResultBannedAccountInApplication {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57980\n};\n\nstatic constexpr Result ResultBannedAccountInNexService {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57a00\n};\n\nstatic constexpr Result ResultBannedAccountInIndependentService {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57a80\n};\n\nstatic constexpr Result ResultBannedDevice {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57d80\n};\n\nstatic constexpr Result ResultBannedDeviceAll {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57e00\n};\n\nstatic constexpr Result ResultBannedDeviceInApplication {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57e80\n};\n\nstatic constexpr Result ResultBannedDeviceInNexService {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57f00\n};\n\nstatic constexpr Result ResultBannedDeviceInIndependentService {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x57f80\n};\n\nstatic constexpr Result ResultBannedAccountTemporarily {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58280\n};\n\nstatic constexpr Result ResultBannedAccountAllTemporarily {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58300\n};\n\nstatic constexpr Result ResultBannedAccountInApplicationTemporarily {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58380\n};\n\nstatic constexpr Result ResultBannedAccountInNexServiceTemporarily {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58400\n};\n\nstatic constexpr Result ResultBannedAccountInIndependentServiceTemporarily {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58480\n};\n\nstatic constexpr Result ResultBannedDeviceTemporarily {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58780\n};\n\nstatic constexpr Result ResultBannedDeviceAllTemporarily {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58800\n};\n\nstatic constexpr Result ResultBannedDeviceInApplicationTemporarily {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58880\n};\n\nstatic constexpr Result ResultBannedDeviceInNexServiceTemporarily {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58900\n};\n\nstatic constexpr Result ResultBannedDeviceInIndependentServiceTemporarily {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x58980\n};\n\nstatic constexpr Result ResultServiceNotProvided {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5a000\n};\n\nstatic constexpr Result ResultUnderMaintenance {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5a080\n};\n\nstatic constexpr Result ResultServiceClosed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5a100\n};\n\nstatic constexpr Result ResultNintendoNetworkClosed {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5a180\n};\n\nstatic constexpr Result ResultNotProvidedCountry {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5a200\n};\n\nstatic constexpr Result ResultRestrictionError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5aa00\n};\n\nstatic constexpr Result ResultRestrictedByAge {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5aa80\n};\n\nstatic constexpr Result ResultRestrictedByParentalControls {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5af00\n};\n\nstatic constexpr Result ResultOnGameInternetCommunicationRestricted {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5af80\n};\n\nstatic constexpr Result ResultInternalServerError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5b980\n};\n\nstatic constexpr Result ResultUnknownServerError {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5ba00\n};\n\nstatic constexpr Result ResultUnauthenticatedAfterSalvage {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5db00\n};\n\nstatic constexpr Result ResultAuthenticationFailureUnknown {\n   nn::Result::MODULE_NN_ACT, nn::Result::LEVEL_STATUS, 0x5db80\n};\n\n} // namespace nn::act\n"
  },
  {
    "path": "src/libdecaf/src/nn/act/nn_act_serverstandardservice.h",
    "content": "#pragma once\n#include \"nn_act_enum.h\"\n#include \"nn_act_types.h\"\n\n#include \"nn/ipc/nn_ipc_command.h\"\n#include \"nn/ipc/nn_ipc_service.h\"\n#include \"nn/ipc/nn_ipc_managedbuffer.h\"\n\n#include <array>\n#include <cstdint>\n\nnamespace nn::act::services\n{\n\nstruct ServerStandardService : ipc::Service<1>\n{\n   using AcquireNexServiceToken =\n      ipc::Command<ServerStandardService, 4>\n         ::Parameters<SlotNo, ipc::OutBuffer<NexAuthenticationResult>, uint32_t, bool>;\n\n   using Cancel =\n      ipc::Command<ServerStandardService, 100>\n         ::Parameters<>;\n};\n\n} // namespace nn::act::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/act/nn_act_types.h",
    "content": "#pragma once\n#include <array>\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n\nnamespace nn::act\n{\n\n#pragma pack(push, 1)\n\nusing SlotNo = uint8_t;\nusing LocalFriendCode = uint64_t;\nusing PersistentId = uint32_t;\nusing PrincipalId = uint32_t;\nusing SimpleAddressId = uint32_t;\nusing TransferrableId = uint64_t;\nusing Uuid = std::array<char, 0x10>;\n\nstatic constexpr SlotNo InvalidSlot = 0;\nstatic constexpr SlotNo NumSlots = 12;\nstatic constexpr SlotNo CurrentUserSlot = 254;\nstatic constexpr SlotNo SystemSlot = 255;\n\nstatic constexpr uint32_t AccountIdSize = 17;\nstatic constexpr uint32_t NfsPasswordSize = 17;\nstatic constexpr uint32_t MiiNameSize = 11;\nstatic constexpr uint32_t UuidSize = 16;\n\nstruct Birthday\n{\n   be2_val<uint16_t> year;\n   be2_val<uint8_t> month;\n   be2_val<uint8_t> day;\n};\nCHECK_OFFSET(Birthday, 0x00, year);\nCHECK_OFFSET(Birthday, 0x02, month);\nCHECK_OFFSET(Birthday, 0x03, day);\nCHECK_SIZE(Birthday, 0x4);\n\nstruct NexAuthenticationResult\n{\n   be2_array<char, 513> token;\n   PADDING(3);\n   be2_array<char, 65> password;\n   PADDING(3);\n   be2_array<char, 16> host;\n   be2_val<uint16_t> port;\n   PADDING(2);\n};\nCHECK_OFFSET(NexAuthenticationResult, 0x000, token);\nCHECK_OFFSET(NexAuthenticationResult, 0x204, password);\nCHECK_OFFSET(NexAuthenticationResult, 0x248, host);\nCHECK_OFFSET(NexAuthenticationResult, 0x258, port);\nCHECK_SIZE(NexAuthenticationResult, 0x25C);\n\n#pragma pack(pop)\n\n} // namespace nn::act\n"
  },
  {
    "path": "src/libdecaf/src/nn/boss/nn_boss_management_service.h",
    "content": "#pragma once\n#include \"nn/ipc/nn_ipc_service.h\"\n\nnamespace nn::boss::services\n{\n\nstruct ManagementService : ipc::Service<3>\n{\n};\n\n} // namespace nn::boss::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/boss/nn_boss_private_service.h",
    "content": "#pragma once\n#include \"nn/ipc/nn_ipc_service.h\"\n\nnamespace nn::boss::services\n{\n\nstruct PrivateService : ipc::Service<4>\n{\n};\n\n} // namespace nn::boss::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/boss/nn_boss_privileged_service.h",
    "content": "#pragma once\n#include \"nn_boss_types.h\"\n\n#include \"nn/ipc/nn_ipc_service.h\"\n\nnamespace nn::boss::services\n{\n\nstruct PrivilegedService : ipc::Service<1>\n{\n   using AddAccount =\n      ipc::Command<PrivilegedService, 316>\n      ::Parameters<PersistentId>;\n};\n\n} // namespace nn::boss::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/boss/nn_boss_result.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace nn::boss\n{\n\nstatic constexpr Result ResultSuccess {\n   Result::MODULE_NN_BOSS, Result::LEVEL_SUCCESS, 0x00080\n};\n\nstatic constexpr Result ResultNotInitialised {\n   Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03200\n};\n\nstatic constexpr Result ResultLibraryNotInitialiased {\n   Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03280\n};\n\nstatic constexpr Result ResultInvalid {\n   Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03700\n};\n\nstatic constexpr Result ResultInvalidParameter {\n   Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03780\n};\n\nstatic constexpr Result ResultInvalidFormat {\n   Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03800\n};\n\nstatic constexpr Result ResultInvalidAccount {\n   Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03880\n};\n\nstatic constexpr Result ResultInvalidTitle {\n   Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x03900\n};\n\nstatic constexpr Result ResultNoSupport {\n   Result::MODULE_NN_BOSS, Result::LEVEL_USAGE, 0x04100\n};\n\nstatic constexpr Result ResultInitialized {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1f400\n};\n\nstatic constexpr Result ResultNotExist {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1f900\n};\n\nstatic constexpr Result ResultFileNotExist {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1f980\n};\n\nstatic constexpr Result ResultBossStorageNotExist {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1fa00\n};\n\nstatic constexpr Result ResultDbNotExist {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1fa80\n};\n\nstatic constexpr Result ResultRecordNotExist {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x1fb00\n};\n\nstatic constexpr Result ResultNotCompleted {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x20300\n};\n\nstatic constexpr Result ResultNotPermitted {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x20800\n};\n\nstatic constexpr Result ResultFull {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x20d00\n};\n\nstatic constexpr Result ResultSizeFull {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x20d80\n};\n\nstatic constexpr Result ResultCountFull {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x20e00\n};\n\nstatic constexpr Result ResultFinished {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x21200\n};\n\nstatic constexpr Result ResultServiceFinished {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x21280\n};\n\nstatic constexpr Result ResultCanceled {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x21700\n};\n\nstatic constexpr Result ResultStoppedByPolicylist {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x21c00\n};\n\nstatic constexpr Result ResultAlreadyExist {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x22100\n};\n\nstatic constexpr Result ResultCannotGetNetworkTime {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x22600\n};\n\nstatic constexpr Result ResultNotNetworkAccount {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x22b00\n};\n\nstatic constexpr Result ResultRestrictedByParentalControl {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x23000\n};\n\nstatic constexpr Result ResultDisableUploadConsoleInformation {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x23500\n};\n\nstatic constexpr Result ResultNotConnectNetwork {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x23a00\n};\n\nstatic constexpr Result ResultRestrictedByParentalControlTotalEnable {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x23f00\n};\n\nstatic constexpr Result ResultNotFound {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x24400\n};\n\nstatic constexpr Result ResultBossStorageNotFound {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x24480\n};\n\nstatic constexpr Result ResultHTTPError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x3e800\n};\n\nstatic constexpr Result ResultFsError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4E200\n};\n\nstatic constexpr Result ResultFsErrorNotInit {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e280\n};\n\nstatic constexpr Result ResultFsErrorBusy {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e300\n};\n\nstatic constexpr Result ResultFsErrorCanceled {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e380\n};\n\nstatic constexpr Result ResultFsErrorEndOfDirectory {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e400\n};\n\nstatic constexpr Result ResultFsErrorEndOfFile {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e480\n};\n\nstatic constexpr Result ResultFsErrorMaxMountpoints {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e500\n};\n\nstatic constexpr Result ResultFsErrorMaxVolumes {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e580\n};\n\nstatic constexpr Result ResultFsErrorMaxClients {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e600\n};\n\nstatic constexpr Result ResultFsErrorMaxFiles {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e680\n};\n\nstatic constexpr Result ResultFsErrorMaxDirs {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e700\n};\n\nstatic constexpr Result ResultFsErrorAlreadyOpen {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e780\n};\n\nstatic constexpr Result ResultFsErrorAlreadyExists {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e800\n};\n\nstatic constexpr Result ResultFsErrorNotFound {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e880\n};\n\nstatic constexpr Result ResultFsErrorNotEmpty {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e900\n};\n\nstatic constexpr Result ResultFsErrorAccessError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4e980\n};\n\nstatic constexpr Result ResultFsErrorPermissionError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ea00\n};\n\nstatic constexpr Result ResultFsErrorDataCorrupted {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ea80\n};\n\nstatic constexpr Result ResultFsErrorStorageFull {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4eb00\n};\n\nstatic constexpr Result ResultFsErrorJournalFull {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4eb80\n};\n\nstatic constexpr Result ResultFsErrorUnavailableCmd {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ec00\n};\n\nstatic constexpr Result ResultFsErrorUnsupportedCmd {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ec80\n};\n\nstatic constexpr Result ResultFsErrorInvalidParam {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ed00\n};\n\nstatic constexpr Result ResultFsErrorInvalidPath {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ed80\n};\n\nstatic constexpr Result ResultFsErrorInvalidBuffer {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ee00\n};\n\nstatic constexpr Result ResultFsErrorInvalidAlignment {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ee80\n};\n\nstatic constexpr Result ResultFsErrorInvalidClientHandle {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ef00\n};\n\nstatic constexpr Result ResultFsErrorInvalidFileHandle {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4ef80\n};\n\nstatic constexpr Result ResultFsErrorInvalidDirHandle {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f000\n};\n\nstatic constexpr Result ResultFsErrorNotFile {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f080\n};\n\nstatic constexpr Result ResultFsErrorNotDir {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f100\n};\n\nstatic constexpr Result ResultFsErrorFileTooBig {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f180\n};\n\nstatic constexpr Result ResultFsErrorOutOfRange {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f200\n};\n\nstatic constexpr Result ResultFsErrorOutOfResources {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f280\n};\n\nstatic constexpr Result ResultFsErrorMediaNotReady {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f300\n};\n\nstatic constexpr Result ResultFsErrorMediaError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f380\n};\n\nstatic constexpr Result ResultFsErrorWriteProtected {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f400\n};\n\nstatic constexpr Result ResultFsErrorUnknown {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x4f480\n};\n\nstatic constexpr Result ResultFail {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5aa00\n};\n\nstatic constexpr Result ResultMemoryAllocateError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5dc00\n};\n\nstatic constexpr Result ResultInitializeError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e100\n};\n\nstatic constexpr Result ResultSslInitializeError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e180\n};\n\nstatic constexpr Result ResultAcpInitializeError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e200\n};\n\nstatic constexpr Result ResultActInitializeError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e280\n};\n\nstatic constexpr Result ResultPdmInitializeError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e300\n};\n\nstatic constexpr Result ResultConfigInitializeError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e380\n};\n\nstatic constexpr Result ResultFsInitializeError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e400\n};\n\nstatic constexpr Result ResultHTTPInitializeError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e480\n};\n\nstatic constexpr Result ResultAcInitializeError {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x5e500\n};\n\nstatic constexpr Result ResultUnexpect {\n   Result::MODULE_NN_BOSS, Result::LEVEL_STATUS, 0x7ff80\n};\n\n} // namespace nn::boss\n"
  },
  {
    "path": "src/libdecaf/src/nn/boss/nn_boss_service.h",
    "content": "#pragma once\n#include \"nn/ipc/nn_ipc_service.h\"\n\nnamespace nn::boss::services\n{\n\nstruct BossService : ipc::Service<0>\n{\n};\n\n} // namespace nn::boss::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/boss/nn_boss_test_service.h",
    "content": "#pragma once\n#include \"nn/ipc/nn_ipc_service.h\"\n\nnamespace nn::boss::services\n{\n\nstruct TestService : ipc::Service<3>\n{\n};\n\n} // namespace nn::boss::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/boss/nn_boss_types.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace nn::boss\n{\n\nusing PersistentId = uint32_t;\n\n} // namespace nn::boss\n"
  },
  {
    "path": "src/libdecaf/src/nn/dbg/nn_dbg_result_string.cpp",
    "content": "#include \"nn_dbg_result_string.h\"\n#include \"nn/acp/nn_acp_result.h\"\n#include \"nn/act/nn_act_result.h\"\n\nnamespace nn::dbg\n{\n\nconst char *\nGetLevelString(nn::Result result)\n{\n   if (result.isLegacyRepresentation()) {\n      switch (result.level()) {\n      case nn::Result::LEGACY_LEVEL_INFO:\n         return \"LEVEL_INFO\";\n      case nn::Result::LEGACY_LEVEL_RESET:\n         return \"LEVEL_RESET\";\n      case nn::Result::LEGACY_LEVEL_REINIT:\n         return \"LEVEL_REINIT\";\n      case nn::Result::LEGACY_LEVEL_PERMANENT:\n         return \"LEVEL_PERMANENT\";\n      case nn::Result::LEGACY_LEVEL_TEMPORARY:\n         return \"LEVEL_TEMPORARY\";\n      default:\n         return \"<unknown>\";\n      }\n   } else {\n      switch (result.level()) {\n      case nn::Result::LEVEL_SUCCESS:\n         return \"LEVEL_SUCCESS\";\n      case nn::Result::LEVEL_FATAL:\n         return \"LEVEL_FATAL\";\n      case nn::Result::LEVEL_USAGE:\n         return \"LEVEL_USAGE\";\n      case nn::Result::LEVEL_STATUS:\n         return \"LEVEL_STATUS\";\n      case nn::Result::LEVEL_END:\n         return \"LEVEL_END\";\n      default:\n         return \"<unknown>\";\n      }\n   }\n}\n\nconst char *\nGetModuleString(nn::Result result)\n{\n   if (result.isLegacyRepresentation()) {\n      switch (result.module()) {\n      case nn::Result::LEGACY_MODULE_COMMON:\n         return \"MODULE_COMMON\";\n      case nn::Result::LEGACY_MODULE_NN_KERNEL:\n         return \"MODULE_NN_KERNEL\";\n      case nn::Result::LEGACY_MODULE_NN_UTIL:\n         return \"MODULE_NN_UTIL\";\n      case nn::Result::LEGACY_MODULE_NN_FILE_SERVER:\n         return \"MODULE_NN_FILE_SERVER\";\n      case nn::Result::LEGACY_MODULE_NN_LOADER_SERVER:\n         return \"MODULE_NN_LOADER_SERVER\";\n      case nn::Result::LEGACY_MODULE_NN_TCB:\n         return \"MODULE_NN_TCB\";\n      case nn::Result::LEGACY_MODULE_NN_OS:\n         return \"MODULE_NN_OS\";\n      case nn::Result::LEGACY_MODULE_NN_DBG:\n         return \"MODULE_NN_DBG\";\n      case nn::Result::LEGACY_MODULE_NN_DMNT:\n         return \"MODULE_NN_DMNT\";\n      case nn::Result::LEGACY_MODULE_NN_PDN:\n         return \"MODULE_NN_PDN\";\n      case nn::Result::LEGACY_MODULE_NN_GX:\n         return \"MODULE_NN_GX\";\n      case nn::Result::LEGACY_MODULE_NN_I2C:\n         return \"MODULE_NN_I2C\";\n      case nn::Result::LEGACY_MODULE_NN_GPIO:\n         return \"MODULE_NN_GPIO\";\n      case nn::Result::LEGACY_MODULE_NN_DD:\n         return \"MODULE_NN_DD\";\n      case nn::Result::LEGACY_MODULE_NN_CODEC:\n         return \"MODULE_NN_CODEC\";\n      case nn::Result::LEGACY_MODULE_NN_SPI:\n         return \"MODULE_NN_SPI\";\n      case nn::Result::LEGACY_MODULE_NN_PXI:\n         return \"MODULE_NN_PXI\";\n      case nn::Result::LEGACY_MODULE_NN_FS:\n         return \"MODULE_NN_FS\";\n      case nn::Result::LEGACY_MODULE_NN_DI:\n         return \"MODULE_NN_DI\";\n      case nn::Result::LEGACY_MODULE_NN_HID:\n         return \"MODULE_NN_HID\";\n      case nn::Result::LEGACY_MODULE_NN_CAMERA:\n         return \"MODULE_NN_CAMERA\";\n      case nn::Result::LEGACY_MODULE_NN_PI:\n         return \"MODULE_NN_PI\";\n      case nn::Result::LEGACY_MODULE_NN_PM:\n         return \"MODULE_NN_PM\";\n      case nn::Result::LEGACY_MODULE_NN_PMLOW:\n         return \"MODULE_NN_PMLOW\";\n      case nn::Result::LEGACY_MODULE_NN_FSI:\n         return \"MODULE_NN_FSI\";\n      case nn::Result::LEGACY_MODULE_NN_SRV:\n         return \"MODULE_NN_SRV\";\n      case nn::Result::LEGACY_MODULE_NN_NDM:\n         return \"MODULE_NN_NDM\";\n      case nn::Result::LEGACY_MODULE_NN_NWM:\n         return \"MODULE_NN_NWM\";\n      case nn::Result::LEGACY_MODULE_NN_SOCKET:\n         return \"MODULE_NN_SOCKET\";\n      case nn::Result::LEGACY_MODULE_NN_LDR:\n         return \"MODULE_NN_LDR\";\n      case nn::Result::LEGACY_MODULE_NN_ACC:\n         return \"MODULE_NN_ACC\";\n      case nn::Result::LEGACY_MODULE_NN_ROMFS:\n         return \"MODULE_NN_ROMFS\";\n      case nn::Result::LEGACY_MODULE_NN_AM:\n         return \"MODULE_NN_AM\";\n      case nn::Result::LEGACY_MODULE_NN_HIO:\n         return \"MODULE_NN_HIO\";\n      case nn::Result::LEGACY_MODULE_NN_UPDATER:\n         return \"MODULE_NN_UPDATER\";\n      case nn::Result::LEGACY_MODULE_NN_MIC:\n         return \"MODULE_NN_MIC\";\n      case nn::Result::LEGACY_MODULE_NN_FND:\n         return \"MODULE_NN_FND\";\n      case nn::Result::LEGACY_MODULE_NN_MP:\n         return \"MODULE_NN_MP\";\n      case nn::Result::LEGACY_MODULE_NN_MPWL:\n         return \"MODULE_NN_MPWL\";\n      case nn::Result::LEGACY_MODULE_NN_AC:\n         return \"MODULE_NN_AC\";\n      case nn::Result::LEGACY_MODULE_NN_HTTP:\n         return \"MODULE_NN_HTTP\";\n      case nn::Result::LEGACY_MODULE_NN_DSP:\n         return \"MODULE_NN_DSP\";\n      case nn::Result::LEGACY_MODULE_NN_SND:\n         return \"MODULE_NN_SND\";\n      case nn::Result::LEGACY_MODULE_NN_DLP:\n         return \"MODULE_NN_DLP\";\n      case nn::Result::LEGACY_MODULE_NN_HIOLOW:\n         return \"MODULE_NN_HIOLOW\";\n      case nn::Result::LEGACY_MODULE_NN_CSND:\n         return \"MODULE_NN_CSND\";\n      case nn::Result::LEGACY_MODULE_NN_SSL:\n         return \"MODULE_NN_SSL\";\n      case nn::Result::LEGACY_MODULE_NN_AMLOW:\n         return \"MODULE_NN_AMLOW\";\n      case nn::Result::LEGACY_MODULE_NN_NEX:\n         return \"MODULE_NN_NEX\";\n      case nn::Result::LEGACY_MODULE_NN_FRIENDS:\n         return \"MODULE_NN_FRIENDS\";\n      case nn::Result::LEGACY_MODULE_NN_RDT:\n         return \"MODULE_NN_RDT\";\n      case nn::Result::LEGACY_MODULE_NN_APPLET:\n         return \"MODULE_NN_APPLET\";\n      case nn::Result::LEGACY_MODULE_NN_NIM:\n         return \"MODULE_NN_NIM\";\n      case nn::Result::LEGACY_MODULE_NN_PTM:\n         return \"MODULE_NN_PTM\";\n      case nn::Result::LEGACY_MODULE_NN_MIDI:\n         return \"MODULE_NN_MIDI\";\n      case nn::Result::LEGACY_MODULE_NN_MC:\n         return \"MODULE_NN_MC\";\n      case nn::Result::LEGACY_MODULE_NN_SWC:\n         return \"MODULE_NN_SWC\";\n      case nn::Result::LEGACY_MODULE_NN_FATFS:\n         return \"MODULE_NN_FATFS\";\n      case nn::Result::LEGACY_MODULE_NN_NGC:\n         return \"MODULE_NN_NGC\";\n      case nn::Result::LEGACY_MODULE_NN_CARD:\n         return \"MODULE_NN_CARD\";\n      case nn::Result::LEGACY_MODULE_NN_CARDNOR:\n         return \"MODULE_NN_CARDNOR\";\n      case nn::Result::LEGACY_MODULE_NN_SDMC:\n         return \"MODULE_NN_SDMC\";\n      case nn::Result::LEGACY_MODULE_NN_BOSS:\n         return \"MODULE_NN_BOSS\";\n      case nn::Result::LEGACY_MODULE_NN_DBM:\n         return \"MODULE_NN_DBM\";\n      case nn::Result::LEGACY_MODULE_NN_CFG:\n         return \"MODULE_NN_CFG\";\n      case nn::Result::LEGACY_MODULE_NN_PS:\n         return \"MODULE_NN_PS\";\n      case nn::Result::LEGACY_MODULE_NN_CEC:\n         return \"MODULE_NN_CEC\";\n      case nn::Result::LEGACY_MODULE_NN_IR:\n         return \"MODULE_NN_IR\";\n      case nn::Result::LEGACY_MODULE_NN_UDS:\n         return \"MODULE_NN_UDS\";\n      case nn::Result::LEGACY_MODULE_NN_PL:\n         return \"MODULE_NN_PL\";\n      case nn::Result::LEGACY_MODULE_NN_CUP:\n         return \"MODULE_NN_CUP\";\n      case nn::Result::LEGACY_MODULE_NN_GYROSCOPE:\n         return \"MODULE_NN_GYROSCOPE\";\n      case nn::Result::LEGACY_MODULE_NN_MCU:\n         return \"MODULE_NN_MCU\";\n      case nn::Result::LEGACY_MODULE_NN_NS:\n         return \"MODULE_NN_NS\";\n      case nn::Result::LEGACY_MODULE_NN_NEWS:\n         return \"MODULE_NN_NEWS\";\n      case nn::Result::LEGACY_MODULE_NN_RO:\n         return \"MODULE_NN_RO\";\n      case nn::Result::LEGACY_MODULE_NN_GD:\n         return \"MODULE_NN_GD\";\n      case nn::Result::LEGACY_MODULE_NN_CARDSPI:\n         return \"MODULE_NN_CARDSPI\";\n      case nn::Result::LEGACY_MODULE_NN_EC:\n         return \"MODULE_NN_EC\";\n      case nn::Result::LEGACY_MODULE_NN_WEBBRS:\n         return \"MODULE_NN_WEBBRS\";\n      case nn::Result::LEGACY_MODULE_NN_TEST:\n         return \"MODULE_NN_TEST\";\n      case nn::Result::LEGACY_MODULE_NN_ENC:\n         return \"MODULE_NN_ENC\";\n      case nn::Result::LEGACY_MODULE_NN_PIA:\n         return \"MODULE_NN_PIA\";\n      case nn::Result::LEGACY_MODULE_APPLICATION:\n         return \"MODULE_APPLICATION\";\n      }\n   } else {\n      switch (result.module()) {\n      case nn::Result::MODULE_COMMON:\n         return \"MODULE_COMMON\";\n      case nn::Result::MODULE_NN_IPC:\n         return \"MODULE_NN_IPC\";\n      case nn::Result::MODULE_NN_BOSS:\n         return \"MODULE_NN_BOSS\";\n      case nn::Result::MODULE_NN_ACP:\n         return \"MODULE_NN_ACP\";\n      case nn::Result::MODULE_NN_IOS:\n         return \"MODULE_NN_IOS\";\n      case nn::Result::MODULE_NN_NIM:\n         return \"MODULE_NN_NIM\";\n      case nn::Result::MODULE_NN_PDM:\n         return \"MODULE_NN_PDM\";\n      case nn::Result::MODULE_NN_ACT:\n         return \"MODULE_NN_ACT\";\n      case nn::Result::MODULE_NN_NGC:\n         return \"MODULE_NN_NGC\";\n      case nn::Result::MODULE_NN_ECA:\n         return \"MODULE_NN_ECA\";\n      case nn::Result::MODULE_NN_NUP:\n         return \"MODULE_NN_NUP\";\n      case nn::Result::MODULE_NN_NDM:\n         return \"MODULE_NN_NDM\";\n      case nn::Result::MODULE_NN_FP:\n         return \"MODULE_NN_FP\";\n      case nn::Result::MODULE_NN_AC:\n         return \"MODULE_NN_AC\";\n      case nn::Result::MODULE_NN_CONNTEST:\n         return \"MODULE_NN_CONNTEST\";\n      case nn::Result::MODULE_NN_DRMAPP:\n         return \"MODULE_NN_DRMAPP\";\n      case nn::Result::MODULE_NN_TELNET:\n         return \"MODULE_NN_TELNET\";\n      case nn::Result::MODULE_NN_OLV:\n         return \"MODULE_NN_OLV\";\n      case nn::Result::MODULE_NN_VCTL:\n         return \"MODULE_NN_VCTL\";\n      case nn::Result::MODULE_NN_NEIA:\n         return \"MODULE_NN_NEIA\";\n      case nn::Result::MODULE_NN_SPM:\n         return \"MODULE_NN_SPM\";\n      case nn::Result::MODULE_NN_EMD:\n         return \"MODULE_NN_EMD\";\n      case nn::Result::MODULE_NN_EC:\n         return \"MODULE_NN_EC\";\n      case nn::Result::MODULE_NN_CIA:\n         return \"MODULE_NN_CIA\";\n      case nn::Result::MODULE_NN_SL:\n         return \"MODULE_NN_SL\";\n      case nn::Result::MODULE_NN_ECO:\n         return \"MODULE_NN_ECO\";\n      case nn::Result::MODULE_NN_TRIAL:\n         return \"MODULE_NN_TRIAL\";\n      case nn::Result::MODULE_NN_NFP:\n         return \"MODULE_NN_NFP\";\n      case nn::Result::MODULE_NN_TEST:\n         return \"MODULE_NN_TEST\";\n      }\n   }\n\n   return \"<unknown>\";\n}\n\nconst char *\nGetSummaryString(nn::Result result)\n{\n   if (!result.isLegacyRepresentation()) {\n      return \"SUMMARY_SUCCESS\";\n   }\n\n   switch (result.legacySummary()) {\n   case nn::Result::LEGACY_SUMMARY_SUCCESS:\n      return \"SUMMARY_SUCCESS\";\n   case nn::Result::LEGACY_SUMMARY_NOTHING_HAPPENED:\n      return \"SUMMARY_NOTHING_HAPPENED\";\n   case nn::Result::LEGACY_SUMMARY_WOULD_BLOCK:\n      return \"SUMMARY_WOULD_BLOCK\";\n   case nn::Result::LEGACY_SUMMARY_OUT_OF_RESOURCE:\n      return \"SUMMARY_OUT_OF_RESOURCE\";\n   case nn::Result::LEGACY_SUMMARY_NOT_FOUND:\n      return \"SUMMARY_NOT_FOUND\";\n   case nn::Result::LEGACY_SUMMARY_INVALID_STATE:\n      return \"SUMMARY_INVALID_STATE\";\n   case nn::Result::LEGACY_SUMMARY_NOT_SUPPORTED:\n      return \"SUMMARY_NOT_SUPPORTED\";\n   case nn::Result::LEGACY_SUMMARY_INVALID_ARGUMENT:\n      return \"SUMMARY_INVALID_ARGUMENT\";\n   case nn::Result::LEGACY_SUMMARY_WRONG_ARGUMENT:\n      return \"SUMMARY_WRONG_ARGUMENT\";\n   case nn::Result::LEGACY_SUMMARY_CANCELLED:\n      return \"SUMMARY_CANCELLED\";\n   case nn::Result::LEGACY_SUMMARY_STATUS_CHANGED:\n      return \"SUMMARY_STATUS_CHANGED\";\n   case nn::Result::LEGACY_SUMMARY_INTERNAL:\n      return \"SUMMARY_INTERNAL\";\n   }\n\n   return \"<unknown>\";\n}\n\nstatic const char *\nGetLegacyDescriptionString(nn::Result result)\n{\n   switch (result.description()) {\n   case nn::Result::LEGACY_DESCRIPTION_SUCCESS:\n      return \"DESCRIPTION_SUCCESS\";\n   case nn::Result::LEGACY_DESCRIPTION_TIMEOUT:\n      return \"DESCRIPTION_TIMEOUT\";\n   case nn::Result::LEGACY_DESCRIPTION_OUT_OF_RANGE:\n      return \"DESCRIPTION_OUT_OF_RANGE\";\n   case nn::Result::LEGACY_DESCRIPTION_ALREADY_EXISTS:\n      return \"DESCRIPTION_ALREADY_EXISTS\";\n   case nn::Result::LEGACY_DESCRIPTION_CANCEL_REQUESTED:\n      return \"DESCRIPTION_CANCEL_REQUESTED\";\n   case nn::Result::LEGACY_DESCRIPTION_NOT_FOUND:\n      return \"DESCRIPTION_NOT_FOUND\";\n   case nn::Result::LEGACY_DESCRIPTION_ALREADY_INITIALIZED:\n      return \"DESCRIPTION_ALREADY_INITIALIZED\";\n   case nn::Result::LEGACY_DESCRIPTION_NOT_INITIALIZED:\n      return \"DESCRIPTION_NOT_INITIALIZED\";\n   case nn::Result::LEGACY_DESCRIPTION_INVALID_HANDLE:\n      return \"DESCRIPTION_INVALID_HANDLE\";\n   case nn::Result::LEGACY_DESCRIPTION_INVALID_POINTER:\n      return \"DESCRIPTION_INVALID_POINTER\";\n   case nn::Result::LEGACY_DESCRIPTION_INVALID_ADDRESS:\n      return \"DESCRIPTION_INVALID_ADDRESS\";\n   case nn::Result::LEGACY_DESCRIPTION_NOT_IMPLEMENTED:\n      return \"DESCRIPTION_NOT_IMPLEMENTED\";\n   case nn::Result::LEGACY_DESCRIPTION_OUT_OF_MEMORY:\n      return \"DESCRIPTION_OUT_OF_MEMORY\";\n   case nn::Result::LEGACY_DESCRIPTION_MISALIGNED_SIZE:\n      return \"DESCRIPTION_MISALIGNED_SIZE\";\n   case nn::Result::LEGACY_DESCRIPTION_MISALIGNED_ADDRESS:\n      return \"DESCRIPTION_MISALIGNED_ADDRESS\";\n   case nn::Result::LEGACY_DESCRIPTION_BUSY:\n      return \"DESCRIPTION_BUSY\";\n   case nn::Result::LEGACY_DESCRIPTION_NO_DATA:\n      return \"DESCRIPTION_NO_DATA\";\n   case nn::Result::LEGACY_DESCRIPTION_INVALID_COMBINATION:\n      return \"DESCRIPTION_INVALID_COMBINATION\";\n   case nn::Result::LEGACY_DESCRIPTION_INVALID_ENUM_VALUE:\n      return \"DESCRIPTION_INVALID_ENUM_VALUE\";\n   case nn::Result::LEGACY_DESCRIPTION_INVALID_SIZE:\n      return \"DESCRIPTION_INVALID_SIZE\";\n   case nn::Result::LEGACY_DESCRIPTION_ALREADY_DONE:\n      return \"DESCRIPTION_ALREADY_DONE\";\n   case nn::Result::LEGACY_DESCRIPTION_NOT_AUTHORIZED:\n      return \"DESCRIPTION_NOT_AUTHORIZED\";\n   case nn::Result::LEGACY_DESCRIPTION_TOO_LARGE:\n      return \"DESCRIPTION_TOO_LARGE\";\n   case nn::Result::LEGACY_DESCRIPTION_INVALID_SELECTION:\n      return \"DESCRIPTION_INVALID_SELECTION\";\n   default:\n      return \"<unknown>\";\n   }\n}\n\nstatic const char *\nGetAcpDescriptionString(nn::Result result)\n{\n   if (result == acp::ResultInvalidParameter) {\n      return \"INVALID_PARAMETER\";\n   } else if (result == acp::ResultInvalidFile) {\n      return \"INVALID_FILE\";\n   } else if (result == acp::ResultInvalidXmlFile) {\n      return \"INVALID_XML_FILE\";\n   } else if (result == acp::ResultFileAccessMode) {\n      return \"FILE_ACCESS_MODE\";\n   } else if (result == acp::ResultInvalidNetworkTime) {\n      return \"INVALID_NETWORK_TIME\";\n   } else if (result == acp::ResultInvalid) {\n      return \"INVALID\";\n   }\n\n   if (result == acp::ResultFileNotFound) {\n      return \"FILE_NOT_FOUND\";\n   } else if (result == acp::ResultDirNotFound) {\n      return \"DIR_NOT_FOUND\";\n   } else if (result == acp::ResultDeviceNotFound) {\n      return \"DEVICE_NOT_FOUND\";\n   } else if (result == acp::ResultTitleNotFound) {\n      return \"TITLE_NOT_FOUND\";\n   } else if (result == acp::ResultApplicationNotFound) {\n      return \"APPLICATION_NOT_FOUND\";\n   } else if (result == acp::ResultSystemConfigNotFound) {\n      return \"SYSTEM_CONFIG_NOT_FOUND\";\n   } else if (result == acp::ResultXmlItemNotFound) {\n      return \"XML_ITEM_NOT_FOUND\";\n   } else if (result == acp::ResultNotFound) {\n      return \"NOT_FOUND\";\n   }\n\n   if (result == acp::ResultFileAlreadyExists) {\n      return \"FILE_ALREADY_EXISTS\";\n   } else if (result == acp::ResultDirAlreadyExists) {\n      return \"DIR_ALREADY_EXISTS\";\n   } else if (result == acp::ResultAlreadyExists) {\n      return \"ALREADY_EXISTS\";\n   }\n\n   if (result == acp::ResultAlreadyDone) {\n      return \"ALREADY_DONE\";\n   }\n\n   if (result == acp::ResultInvalidRegion) {\n      return \"INVALID_REGION\";\n   } else if (result == acp::ResultRestrictedRating) {\n      return \"RESTRICTED_RATING\";\n   } else if (result == acp::ResultNotPresentRating) {\n      return \"NOT_PRESENT_RATING\";\n   } else if (result == acp::ResultPendingRating) {\n      return \"PENDING_RATING\";\n   } else if (result == acp::ResultNetSettingRequired) {\n      return \"NET_SETTING_REQUIRED\";\n   } else if (result == acp::ResultNetAccountRequired) {\n      return \"NET_ACCOUNT_REQUIRED\";\n   } else if (result == acp::ResultNetAccountError) {\n      return \"NET_ACCOUNT_ERROR\";\n   } else if (result == acp::ResultBrowserRequired) {\n      return \"BROWSER_REQUIRED\";\n   } else if (result == acp::ResultOlvRequired) {\n      return \"OLV_REQUIRED\";\n   } else if (result == acp::ResultPincodeRequired) {\n      return \"PINCODE_REQUIRED\";\n   } else if (result == acp::ResultIncorrectPincode) {\n      return \"INCORRECT_PINCODE\";\n   } else if (result == acp::ResultInvalidLogo) {\n      return \"INVALID_LOGO\";\n   } else if (result == acp::ResultDemoExpiredNumber) {\n      return \"DEMO_EXPIRED_NUMBER\";\n   } else if (result == acp::ResultDrcRequired) {\n      return \"DRC_REQUIRED\";\n   } else if (result == acp::ResultAuthentication) {\n      return \"AUTHENTICATION\";\n   }\n\n   if (result == acp::ResultNoFilePermission) {\n      return \"NO_FILE_PERMISSION\";\n   } else if (result == acp::ResultNoDirPermission) {\n      return \"NO_DIR_PERMISSION\";\n   } else if (result == acp::ResultNoPermission) {\n      return \"NO_PERMISSION\";\n   }\n\n   if (result == acp::ResultUsbStorageNotReady) {\n      return \"USB_STORAGE_NOT_READY\";\n   } else if (result == acp::ResultBusy) {\n      return \"BUSY\";\n   }\n\n   if (result == acp::ResultCancelled) {\n      return \"CANCELLED\";\n   }\n\n   if (result == acp::ResultDeviceFull) {\n      return \"DEVICE_FULL\";\n   } else if (result == acp::ResultJournalFull) {\n      return \"JOURNAL_FULL\";\n   } else if (result == acp::ResultSystemMemory) {\n      return \"SYSTEM_MEMORY\";\n   } else if (result == acp::ResultFsResource) {\n      return \"FS_RESOURCE\";\n   } else if (result == acp::ResultIpcResource) {\n      return \"IPC_RESOURCE\";\n   } else if (result == acp::ResultResource) {\n      return \"RESOURCE\";\n   }\n\n   if (result == acp::ResultNotInitialised) {\n      return \"NOT_INITIALISED\";\n   }\n\n   if (result == acp::ResultAccountError) {\n      return \"ACCOUNT_ERROR\";\n   }\n\n   if (result == acp::ResultUnsupported) {\n      return \"UNSUPPORTED\";\n   }\n\n   if (result == acp::ResultDataCorrupted) {\n      return \"DATA_CORRUPTED\";\n   } else if (result == acp::ResultSlcDataCorrupted) {\n      return \"SLC_DATA_CORRUPTED\";\n   } else if (result == acp::ResultMlcDataCorrupted) {\n      return \"MLC_DATA_CORRUPTED\";\n   } else if (result == acp::ResultUsbDataCorrupted) {\n      return \"USB_DATA_CORRUPTED\";\n   } else if (result == acp::ResultDevice) {\n      return \"DEVICE\";\n   }\n\n   if (result == acp::ResultMediaNotReady) {\n      return \"MEDIA_NOT_READY\";\n   } else if (result == acp::ResultMediaBroken) {\n      return \"MEDIA_BROKEN\";\n   } else if (result == acp::ResultOddMediaNotReady) {\n      return \"ODD_MEDIA_NOT_READY\";\n   } else if (result == acp::ResultOddMediaBroken) {\n      return \"ODD_MEDIA_BROKEN\";\n   } else if (result == acp::ResultUsbMediaNotReady) {\n      return \"USB_MEDIA_NOT_READY\";\n   } else if (result == acp::ResultUsbMediaBroken) {\n      return \"USB_MEDIA_BROKEN\";\n   } else if (result == acp::ResultMediaWriteProtected) {\n      return \"MEDIA_WRITE_PROTECTED\";\n   } else if (result == acp::ResultUsbWriteProtected) {\n      return \"USB_WRITE_PROTECTED\";\n   } else if (result == acp::ResultMedia) {\n      return \"MEDIA\";\n   }\n\n   if (result == acp::ResultEncryptionError) {\n      return \"ENCRYPTION_ERROR\";\n   } else if (result == acp::ResultMii) {\n      return \"MII\";\n   }\n\n   if (result == acp::ResultFsaFatal) {\n      return \"FSA_FATAL\";\n   } else if (result == acp::ResultFsaAddClientFatal) {\n      return \"FSA_ADD_CLIENT_FATAL\";\n   } else if (result == acp::ResultMcpTitleFatal) {\n      return \"MCP_TITLE_FATAL\";\n   } else if (result == acp::ResultMcpPatchFatal) {\n      return \"MCP_PATCH_FATAL\";\n   } else if (result == acp::ResultMcpFatal) {\n      return \"MCP_FATAL\";\n   } else if (result == acp::ResultSaveFatal) {\n      return \"SAVE_FATAL\";\n   } else if (result == acp::ResultUcFatal) {\n      return \"UC_FATAL\";\n   } else if (result == acp::ResultFatal) {\n      return \"FATAL\";\n   }\n\n   return \"<unknown>\";\n}\n\nstatic const char *\nGetActDescriptionString(nn::Result result)\n{\n   if (result == act::ResultMailAddressNotConfirmed) {\n      return \"MAIL_ADDRESS_NOT_CONFIRMED\";\n   } else if (result == act::ResultLibraryError) {\n      return \"LIBRARY_ERROR\";\n   } else if (result == act::ResultNotInitialised) {\n      return \"NOT_INITIALISED\";\n   } else if (result == act::ResultAlreadyInitialised) {\n      return \"ALREADY_INITIALISED\";\n   } else if (result == act::ResultBusy) {\n      return \"BUSY\";\n   } else if (result == act::ResultNotImplemented) {\n      return \"NOT_IMPLEMENTED\";\n   } else if (result == act::ResultDeprecated) {\n      return \"DEPRECATED\";\n   } else if (result == act::ResultDevelopmentOnly) {\n      return \"DEVELOPMENT_ONLY\";\n   } else if (result == act::ResultInvalidArgument) {\n      return \"INVALID_ARGUMENT\";\n   } else if (result == act::ResultInvalidPointer) {\n      return \"INVALID_POINTER\";\n   } else if (result == act::ResultOutOfRange) {\n      return \"OUT_OF_RANGE\";\n   } else if (result == act::ResultInvalidSize) {\n      return \"INVALID_SIZE\";\n   } else if (result == act::ResultInvalidFormat) {\n      return \"INVALID_FORMAT\";\n   } else if (result == act::ResultInvalidHandle) {\n      return \"INVALID_HANDLE\";\n   } else if (result == act::ResultInvalidValue) {\n      return \"INVALID_VALUE\";\n   }\n\n   if (result == act::ResultInternalError) {\n      return \"INTERNAL_ERROR\";\n   } else if (result == act::ResultEndOfStream) {\n      return \"END_OF_STREAM\";\n   } else if (result == act::ResultFileError) {\n      return \"FILE_ERROR\";\n   } else if (result == act::ResultFileNotFound) {\n      return \"FILE_NOT_FOUND\";\n   } else if (result == act::ResultFileVersionMismatch) {\n      return \"FILE_VERSION_MISMATCH\";\n   } else if (result == act::ResultFileIoError) {\n      return \"FILE_IO_ERROR\";\n   } else if (result == act::ResultFileTypeMismatch) {\n      return \"FILE_TYPE_MISMATCH\";\n   } else if (result == act::ResultOutOfResource) {\n      return \"OUT_OF_RESOURCE\";\n   } else if (result == act::ResultShortOfBuffer) {\n      return \"SHORT_OF_BUFFER\";\n   } else if (result == act::ResultOutOfMemory) {\n      return \"OUT_OF_MEMORY\";\n   } else if (result == act::ResultOutOfGlobalHeap) {\n      return \"OUT_OF_GLOBAL_HEAP\";\n   } else if (result == act::ResultOutOfCrossProcessHeap) {\n      return \"OUT_OF_CROSS_PROCESS_HEAP\";\n   } else if (result == act::ResultOutOfProcessLocalHeap) {\n      return \"OUT_OF_PROCESS_LOCAL_HEAP\";\n   } else if (result == act::ResultOutOfMxmlHeap) {\n      return \"OUT_OF_MXML_HEAP\";\n   } else if (result == act::ResultUcError) {\n      return \"UC_ERROR\";\n   } else if (result == act::ResultUcReadSysConfigError) {\n      return \"UC_READ_SYS_CONFIG_ERROR\";\n   } else if (result == act::ResultMcpError) {\n      return \"MCP_ERROR\";\n   } else if (result == act::ResultMcpOpenError) {\n      return \"MCP_OPEN_ERROR\";\n   } else if (result == act::ResultMcpGetInfoError) {\n      return \"MCP_GET_INFO_ERROR\";\n   } else if (result == act::ResultIsoError) {\n      return \"ISO_ERROR\";\n   } else if (result == act::ResultIsoInitFailure) {\n      return \"ISO_INIT_FAILURE\";\n   } else if (result == act::ResultIsoGetCountryCodeFailure) {\n      return \"ISO_GET_COUNTRY_CODE_FAILURE\";\n   } else if (result == act::ResultIsoGetLanguageCodeFailure) {\n      return \"ISO_GET_LANGUAGE_CODE_FAILURE\";\n   } else if (result == act::ResultMxmlError) {\n      return \"MXML_ERROR\";\n   } else if (result == act::ResultIosError) {\n      return \"IOS_ERROR\";\n   } else if (result == act::ResultIosOpenError) {\n      return \"IOS_OPEN_ERROR\";\n   }\n\n   if (result == act::ResultAccountManagementError) {\n      return \"ACCOUNT_MANAGEMENT_ERROR\";\n   } else if (result == act::ResultAccountNotFound) {\n      return \"ACCOUNT_NOT_FOUND\";\n   } else if (result == act::ResultSlotsFull) {\n      return \"SLOTS_FULL\";\n   } else if (result == act::ResultAccountNotLoaded) {\n      return \"ACCOUNT_NOT_LOADED\";\n   } else if (result == act::ResultAccountAlreadyLoaded) {\n      return \"ACCOUNT_ALREADY_LOADED\";\n   } else if (result == act::ResultAccountLocked) {\n      return \"ACCOUNT_LOCKED\";\n   } else if (result == act::ResultNotNetworkAccount) {\n      return \"NOT_NETWORK_ACCOUNT\";\n   } else if (result == act::ResultNotLocalAccount) {\n      return \"NOT_LOCAL_ACCOUNT\";\n   } else if (result == act::ResultAccountNotCommitted) {\n      return \"ACCOUNT_NOT_COMMITTED\";\n   } else if (result == act::ResultNetworkClockInvalid) {\n      return \"NETWORK_CLOCK_INVALID\";\n   } else if (result == act::ResultAuthenticationError) {\n      return \"AUTHENTICATION_ERROR\";\n   }\n\n   if (result == act::ResultHttpError) {\n      return \"HTTP_ERROR\";\n   } else if (result == act::ResultHttpUnsupportedProtocol) {\n      return \"HTTP_UNSUPPORTED_PROTOCOL\";\n   } else if (result == act::ResultHttpFailedInit) {\n      return \"HTTP_FAILED_INIT\";\n   } else if (result == act::ResultHttpURLMalformat) {\n      return \"HTTP_URL_MALFORMAT\";\n   } else if (result == act::ResultHttpNotBuiltIn) {\n      return \"HTTP_NOT_BUILT_IN\";\n   } else if (result == act::ResultHttpCouldntResolveProxy) {\n      return \"HTTP_COULDNT_RESOLVE_PROXY\";\n   } else if (result == act::ResultHttpCouldntResolveHost) {\n      return \"HTTP_COULDNT_RESOLVE_HOST\";\n   } else if (result == act::ResultHttpCouldntConnect) {\n      return \"HTTP_COULDNT_CONNECT\";\n   } else if (result == act::ResultHttpFtpWeirdServerReply) {\n      return \"HTTP_FTP_WEIRD_SERVER_REPLY\";\n   } else if (result == act::ResultHttpRemoteAccessDenied) {\n      return \"HTTP_REMOTE_ACCESS_DENIED\";\n   } else if (result == act::ResultHttpObsolete10) {\n      return \"HTTP_OBSOLETE10\";\n   } else if (result == act::ResultHttpFtpWeirdPassReply) {\n      return \"HTTP_FTP_WEIRD_PASS_REPLY\";\n   } else if (result == act::ResultHttpObsolete12) {\n      return \"HTTP_OBSOLETE12\";\n   } else if (result == act::ResultHttpFtpWeirdPasvReply) {\n      return \"HTTP_FTP_WEIRD_PASV_REPLY\";\n   } else if (result == act::ResultHttpFtpWeird227Format) {\n      return \"HTTP_FTP_WEIRD_227_FORMAT\";\n   } else if (result == act::ResultHttpFtpCantGetHost) {\n      return \"HTTP_FTP_CANT_GET_HOST\";\n   } else if (result == act::ResultHttpObsolete16) {\n      return \"HTTP_OBSOLETE16\";\n   } else if (result == act::ResultHttpFtpCouldntSetType) {\n      return \"HTTP_FTP_COULDNT_SET_TYPE\";\n   } else if (result == act::ResultHttpPartialFile) {\n      return \"HTTP_PARTIAL_FILE\";\n   } else if (result == act::ResultHttpFtpCouldntRetrFile) {\n      return \"HTTP_FTP_COULDNT_RETR_FILE\";\n   } else if (result == act::ResultHttpObsolete20) {\n      return \"HTTP_OBSOLETE20\";\n   } else if (result == act::ResultHttpQuoteError) {\n      return \"HTTP_QUOTE_ERROR\";\n   } else if (result == act::ResultHttpHttpReturnedError) {\n      return \"HTTP_HTTP_RETURNED_ERROR\";\n   } else if (result == act::ResultHttpWriteError) {\n      return \"HTTP_WRITE_ERROR\";\n   } else if (result == act::ResultHttpObsolete24) {\n      return \"HTTP_OBSOLETE24\";\n   } else if (result == act::ResultHttpUploadFailed) {\n      return \"HTTP_UPLOAD_FAILED\";\n   } else if (result == act::ResultHttpReadError) {\n      return \"HTTP_READ_ERROR\";\n   } else if (result == act::ResultHttpOutOfMemory) {\n      return \"HTTP_OUT_OF_MEMORY\";\n   } else if (result == act::ResultHttpOperationTimedout) {\n      return \"HTTP_OPERATION_TIMEDOUT\";\n   } else if (result == act::ResultHttpObsolete29) {\n      return \"HTTP_OBSOLETE29\";\n   } else if (result == act::ResultHttpFtpPortFailed) {\n      return \"HTTP_FTP_PORT_FAILED\";\n   } else if (result == act::ResultHttpFtpCouldntUseRest) {\n      return \"HTTP_FTP_COULDNT_USE_REST\";\n   } else if (result == act::ResultHttpObsolete32) {\n      return \"HTTP_OBSOLETE32\";\n   } else if (result == act::ResultHttpRangeError) {\n      return \"HTTP_RANGE_ERROR\";\n   } else if (result == act::ResultHttpHttpPostError) {\n      return \"HTTP_HTTP_POST_ERROR\";\n   } else if (result == act::ResultHttpSslConnectError) {\n      return \"HTTP_SSL_CONNECT_ERROR\";\n   } else if (result == act::ResultHttpBadDownloadResume) {\n      return \"HTTP_BAD_DOWNLOAD_RESUME\";\n   } else if (result == act::ResultHttpFileCouldntReadFile) {\n      return \"HTTP_FILE_COULDNT_READ_FILE\";\n   } else if (result == act::ResultHttpLdapCannotBind) {\n      return \"HTTP_LDAP_CANNOT_BIND\";\n   } else if (result == act::ResultHttpLdapSearchFailed) {\n      return \"HTTP_LDAP_SEARCH_FAILED\";\n   } else if (result == act::ResultHttpObsolete40) {\n      return \"HTTP_OBSOLETE40\";\n   } else if (result == act::ResultHttpFunctionNotFound) {\n      return \"HTTP_FUNCTION_NOT_FOUND\";\n   } else if (result == act::ResultHttpAbortedByCallback) {\n      return \"HTTP_ABORTED_BY_CALLBACK\";\n   } else if (result == act::ResultHttpBadFunctionArgument) {\n      return \"HTTP_BAD_FUNCTION_ARGUMENT\";\n   } else if (result == act::ResultHttpObsolete44) {\n      return \"HTTP_OBSOLETE44\";\n   } else if (result == act::ResultHttpInterfaceFailed) {\n      return \"HTTP_INTERFACE_FAILED\";\n   } else if (result == act::ResultHttpObsolete46) {\n      return \"HTTP_OBSOLETE46\";\n   } else if (result == act::ResultHttpTooManyRedirects) {\n      return \"HTTP_TOO_MANY_REDIRECTS\";\n   } else if (result == act::ResultHttpUnknownOption) {\n      return \"HTTP_UNKNOWN_OPTION\";\n   } else if (result == act::ResultHttpTelnetOptionSyntax) {\n      return \"HTTP_TELNET_OPTION_SYNTAX\";\n   } else if (result == act::ResultHttpObsolete50) {\n      return \"HTTP_OBSOLETE50\";\n   } else if (result == act::ResultHttpPeerFailedVerification) {\n      return \"HTTP_PEER_FAILED_VERIFICATION\";\n   } else if (result == act::ResultHttpGotNothing) {\n      return \"HTTP_GOT_NOTHING\";\n   } else if (result == act::ResultHttpSslEngineNotfound) {\n      return \"HTTP_SSL_ENGINE_NOTFOUND\";\n   } else if (result == act::ResultHttpSslEngineSetfailed) {\n      return \"HTTP_SSL_ENGINE_SETFAILED\";\n   } else if (result == act::ResultHttpSendError) {\n      return \"HTTP_SEND_ERROR\";\n   } else if (result == act::ResultHttpRecvError) {\n      return \"HTTP_RECV_ERROR\";\n   } else if (result == act::ResultHttpObsolete57) {\n      return \"HTTP_OBSOLETE57\";\n   } else if (result == act::ResultHttpSslCertproblem) {\n      return \"HTTP_SSL_CERTPROBLEM\";\n   } else if (result == act::ResultHttpSslCipher) {\n      return \"HTTP_SSL_CIPHER\";\n   } else if (result == act::ResultHttpSslCacert) {\n      return \"HTTP_SSL_CACERT\";\n   } else if (result == act::ResultHttpBadContentEncoding) {\n      return \"HTTP_BAD_CONTENT_ENCODING\";\n   } else if (result == act::ResultHttpLdapInvalidURL) {\n      return \"HTTP_LDAP_INVALID_URL\";\n   } else if (result == act::ResultHttpFilesizeExceeded) {\n      return \"HTTP_FILESIZE_EXCEEDED\";\n   } else if (result == act::ResultHttpUseSslFailed) {\n      return \"HTTP_USE_SSL_FAILED\";\n   } else if (result == act::ResultHttpSendFailRewind) {\n      return \"HTTP_SEND_FAIL_REWIND\";\n   } else if (result == act::ResultHttpSslEngineInitfailed) {\n      return \"HTTP_SSL_ENGINE_INITFAILED\";\n   } else if (result == act::ResultHttpLoginDenied) {\n      return \"HTTP_LOGIN_DENIED\";\n   } else if (result == act::ResultHttpTftpNotfound) {\n      return \"HTTP_TFTP_NOTFOUND\";\n   } else if (result == act::ResultHttpTftpPerm) {\n      return \"HTTP_TFTP_PERM\";\n   } else if (result == act::ResultHttpRemoteDiskFull) {\n      return \"HTTP_REMOTE_DISK_FULL\";\n   } else if (result == act::ResultHttpTftpIllegal) {\n      return \"HTTP_TFTP_ILLEGAL\";\n   } else if (result == act::ResultHttpTftpUnknownid) {\n      return \"HTTP_TFTP_UNKNOWNID\";\n   } else if (result == act::ResultHttpRemoteFileExists) {\n      return \"HTTP_REMOTE_FILE_EXISTS\";\n   } else if (result == act::ResultHttpTftpNosuchuser) {\n      return \"HTTP_TFTP_NOSUCHUSER\";\n   } else if (result == act::ResultHttpConvFailed) {\n      return \"HTTP_CONV_FAILED\";\n   } else if (result == act::ResultHttpConvReqd) {\n      return \"HTTP_CONV_REQD\";\n   } else if (result == act::ResultHttpSslCacertBadfile) {\n      return \"HTTP_SSL_CACERT_BADFILE\";\n   } else if (result == act::ResultHttpRemoteFileNotFound) {\n      return \"HTTP_REMOTE_FILE_NOT_FOUND\";\n   } else if (result == act::ResultHttpSsh) {\n      return \"HTTP_SSH\";\n   } else if (result == act::ResultHttpSslShutdownFailed) {\n      return \"HTTP_SSL_SHUTDOWN_FAILED\";\n   } else if (result == act::ResultHttpAgain) {\n      return \"HTTP_AGAIN\";\n   } else if (result == act::ResultHttpSslCrlBadfile) {\n      return \"HTTP_SSL_CRL_BADFILE\";\n   } else if (result == act::ResultHttpSslIssuerError) {\n      return \"HTTP_SSL_ISSUER_ERROR\";\n   } else if (result == act::ResultHttpFtpPretFailed) {\n      return \"HTTP_FTP_PRET_FAILED\";\n   } else if (result == act::ResultHttpRtspCseqError) {\n      return \"HTTP_RTSP_CSEQ_ERROR\";\n   } else if (result == act::ResultHttpRtspSessionError) {\n      return \"HTTP_RTSP_SESSION_ERROR\";\n   } else if (result == act::ResultHttpFtpBadFileList) {\n      return \"HTTP_FTP_BAD_FILE_LIST\";\n   } else if (result == act::ResultHttpChunkFailed) {\n      return \"HTTP_CHUNK_FAILED\";\n   } else if (result == act::ResultHttpNsslNoCtx) {\n      return \"HTTP_NSSL_NO_CTX\";\n   }\n\n   if (result == act::ResultSoError) {\n      return \"SO_ERROR\";\n   } else if (result == act::ResultSoSelectError) {\n      return \"SO_SELECT_ERROR\";\n   } else if (result == act::ResultRequestError) {\n      return \"REQUEST_ERROR\";\n   } else if (result == act::ResultBadFormatParameter) {\n      return \"BAD_FORMAT_PARAMETER\";\n   } else if (result == act::ResultBadFormatRequest) {\n      return \"BAD_FORMAT_REQUEST\";\n   } else if (result == act::ResultRequestParameterMissing) {\n      return \"REQUEST_PARAMETER_MISSING\";\n   } else if (result == act::ResultWrongHttpMethod) {\n      return \"WRONG_HTTP_METHOD\";\n   } else if (result == act::ResultResponseError) {\n      return \"RESPONSE_ERROR\";\n   } else if (result == act::ResultBadFormatResponse) {\n      return \"BAD_FORMAT_RESPONSE\";\n   } else if (result == act::ResultResponseItemMissing) {\n      return \"RESPONSE_ITEM_MISSING\";\n   } else if (result == act::ResultResponseTooLarge) {\n      return \"RESPONSE_TOO_LARGE\";\n   } else if (result == act::ResultNotModified) {\n      return \"NOT_MODIFIED\";\n   } else if (result == act::ResultInvalidCommonParameter) {\n      return \"INVALID_COMMON_PARAMETER\";\n   } else if (result == act::ResultInvalidPlatformID) {\n      return \"INVALID_PLATFORM_ID\";\n   } else if (result == act::ResultUnauthorizedDevice) {\n      return \"UNAUTHORIZED_DEVICE\";\n   } else if (result == act::ResultInvalidSerialID) {\n      return \"INVALID_SERIAL_ID\";\n   } else if (result == act::ResultInvalidMacAddress) {\n      return \"INVALID_MAC_ADDRESS\";\n   } else if (result == act::ResultInvalidRegion) {\n      return \"INVALID_REGION\";\n   } else if (result == act::ResultInvalidCountry) {\n      return \"INVALID_COUNTRY\";\n   } else if (result == act::ResultInvalidLanguage) {\n      return \"INVALID_LANGUAGE\";\n   } else if (result == act::ResultUnauthorizedClient) {\n      return \"UNAUTHORIZED_CLIENT\";\n   } else if (result == act::ResultDeviceIDEmpty) {\n      return \"DEVICE_ID_EMPTY\";\n   } else if (result == act::ResultSerialIDEmpty) {\n      return \"SERIAL_ID_EMPTY\";\n   } else if (result == act::ResultPlatformIDEmpty) {\n      return \"PLATFORM_ID_EMPTY\";\n   } else if (result == act::ResultInvalidUniqueID) {\n      return \"INVALID_UNIQUE_ID\";\n   } else if (result == act::ResultInvalidClientID) {\n      return \"INVALID_CLIENT_ID\";\n   } else if (result == act::ResultInvalidClientKey) {\n      return \"INVALID_CLIENT_KEY\";\n   } else if (result == act::ResultInvalidNexClientID) {\n      return \"INVALID_NEX_CLIENT_ID\";\n   } else if (result == act::ResultInvalidGameServerID) {\n      return \"INVALID_GAME_SERVER_ID\";\n   } else if (result == act::ResultGameServerIDEnvironmentNotFound) {\n      return \"GAME_SERVER_ID_ENVIRONMENT_NOT_FOUND\";\n   } else if (result == act::ResultGameServerIDUniqueIDNotLinked) {\n      return \"GAME_SERVER_ID_UNIQUE_ID_NOT_LINKED\";\n   } else if (result == act::ResultClientIDUniqueIDNotLinked) {\n      return \"CLIENT_ID_UNIQUE_ID_NOT_LINKED\";\n   } else if (result == act::ResultDeviceMismatch) {\n      return \"DEVICE_MISMATCH\";\n   } else if (result == act::ResultCountryMismatch) {\n      return \"COUNTRY_MISMATCH\";\n   } else if (result == act::ResultEulaNotAccepted) {\n      return \"EULA_NOT_ACCEPTED\";\n   } else if (result == act::ResultUpdateRequired) {\n      return \"UPDATE_REQUIRED\";\n   } else if (result == act::ResultSystemUpdateRequired) {\n      return \"SYSTEM_UPDATE_REQUIRED\";\n   } else if (result == act::ResultApplicationUpdateRequired) {\n      return \"APPLICATION_UPDATE_REQUIRED\";\n   } else if (result == act::ResultUnauthorizedRequest) {\n      return \"UNAUTHORIZED_REQUEST\";\n   } else if (result == act::ResultRequestForbidden) {\n      return \"REQUEST_FORBIDDEN\";\n   } else if (result == act::ResultResourceNotFound) {\n      return \"RESOURCE_NOT_FOUND\";\n   } else if (result == act::ResultPidNotFound) {\n      return \"PID_NOT_FOUND\";\n   } else if (result == act::ResultNexAccountNotFound) {\n      return \"NEX_ACCOUNT_NOT_FOUND\";\n   } else if (result == act::ResultGenerateTokenFailure) {\n      return \"GENERATE_TOKEN_FAILURE\";\n   } else if (result == act::ResultRequestNotFound) {\n      return \"REQUEST_NOT_FOUND\";\n   } else if (result == act::ResultMasterPinNotFound) {\n      return \"MASTER_PIN_NOT_FOUND\";\n   } else if (result == act::ResultMailTextNotFound) {\n      return \"MAIL_TEXT_NOT_FOUND\";\n   } else if (result == act::ResultSendMailFailure) {\n      return \"SEND_MAIL_FAILURE\";\n   } else if (result == act::ResultApprovalIDNotFound) {\n      return \"APPROVAL_ID_NOT_FOUND\";\n   } else if (result == act::ResultInvalidEulaParameter) {\n      return \"INVALID_EULA_PARAMETER\";\n   } else if (result == act::ResultInvalidEulaCountry) {\n      return \"INVALID_EULA_COUNTRY\";\n   } else if (result == act::ResultInvalidEulaCountryAndVersion) {\n      return \"INVALID_EULA_COUNTRY_AND_VERSION\";\n   } else if (result == act::ResultEulaNotFound) {\n      return \"EULA_NOT_FOUND\";\n   } else if (result == act::ResultPhraseNotAcceptable) {\n      return \"PHRASE_NOT_ACCEPTABLE\";\n   } else if (result == act::ResultAccountIDAlreadyExists) {\n      return \"ACCOUNT_ID_ALREADY_EXISTS\";\n   } else if (result == act::ResultAccountIDNotAcceptable) {\n      return \"ACCOUNT_ID_NOT_ACCEPTABLE\";\n   } else if (result == act::ResultAccountPasswordNotAcceptable) {\n      return \"ACCOUNT_PASSWORD_NOT_ACCEPTABLE\";\n   } else if (result == act::ResultMiiNameNotAcceptable) {\n      return \"MII_NAME_NOT_ACCEPTABLE\";\n   } else if (result == act::ResultMailAddressNotAcceptable) {\n      return \"MAIL_ADDRESS_NOT_ACCEPTABLE\";\n   } else if (result == act::ResultAccountIDFormatInvalid) {\n      return \"ACCOUNT_ID_FORMAT_INVALID\";\n   } else if (result == act::ResultAccountIDPasswordSame) {\n      return \"ACCOUNT_ID_PASSWORD_SAME\";\n   } else if (result == act::ResultAccountIDCharNotAcceptable) {\n      return \"ACCOUNT_ID_CHAR_NOT_ACCEPTABLE\";\n   } else if (result == act::ResultAccountIDSuccessiveSymbol) {\n      return \"ACCOUNT_ID_SUCCESSIVE_SYMBOL\";\n   } else if (result == act::ResultAccountIDSymbolPositionNotAcceptable) {\n      return \"ACCOUNT_ID_SYMBOL_POSITION_NOT_ACCEPTABLE\";\n   } else if (result == act::ResultAccountIDTooManyDigit) {\n      return \"ACCOUNT_ID_TOO_MANY_DIGIT\";\n   } else if (result == act::ResultAccountPasswordCharNotAcceptable) {\n      return \"ACCOUNT_PASSWORD_CHAR_NOT_ACCEPTABLE\";\n   } else if (result == act::ResultAccountPasswordTooFewCharTypes) {\n      return \"ACCOUNT_PASSWORD_TOO_FEW_CHAR_TYPES\";\n   } else if (result == act::ResultAccountPasswordSuccessiveSameChar) {\n      return \"ACCOUNT_PASSWORD_SUCCESSIVE_SAME_CHAR\";\n   } else if (result == act::ResultMailAddressDomainNameNotAcceptable) {\n      return \"MAIL_ADDRESS_DOMAIN_NAME_NOT_ACCEPTABLE\";\n   } else if (result == act::ResultMailAddressDomainNameNotResolved) {\n      return \"MAIL_ADDRESS_DOMAIN_NAME_NOT_RESOLVED\";\n   } else if (result == act::ResultReachedAssociationLimit) {\n      return \"REACHED_ASSOCIATION_LIMIT\";\n   } else if (result == act::ResultReachedRegistrationLimit) {\n      return \"REACHED_REGISTRATION_LIMIT\";\n   } else if (result == act::ResultCoppaNotAccepted) {\n      return \"COPPA_NOT_ACCEPTED\";\n   } else if (result == act::ResultParentalControlsRequired) {\n      return \"PARENTAL_CONTROLS_REQUIRED\";\n   } else if (result == act::ResultMiiNotRegistered) {\n      return \"MII_NOT_REGISTERED\";\n   } else if (result == act::ResultDeviceEulaCountryMismatch) {\n      return \"DEVICE_EULA_COUNTRY_MISMATCH\";\n   } else if (result == act::ResultPendingMigration) {\n      return \"PENDING_MIGRATION\";\n   } else if (result == act::ResultWrongUserInput) {\n      return \"WRONG_USER_INPUT\";\n   } else if (result == act::ResultWrongAccountPassword) {\n      return \"WRONG_ACCOUNT_PASSWORD\";\n   } else if (result == act::ResultWrongMailAddress) {\n      return \"WRONG_MAIL_ADDRESS\";\n   } else if (result == act::ResultWrongAccountPasswordOrMailAddress) {\n      return \"WRONG_ACCOUNT_PASSWORD_OR_MAIL_ADDRESS\";\n   } else if (result == act::ResultWrongConfirmationCode) {\n      return \"WRONG_CONFIRMATION_CODE\";\n   } else if (result == act::ResultWrongBirthDateOrMailAddress) {\n      return \"WRONG_BIRTH_DATE_OR_MAIL_ADDRESS\";\n   } else if (result == act::ResultWrongAccountMail) {\n      return \"WRONG_ACCOUNT_MAIL\";\n   } else if (result == act::ResultAccountAlreadyDeleted) {\n      return \"ACCOUNT_ALREADY_DELETED\";\n   } else if (result == act::ResultAccountIDChanged) {\n      return \"ACCOUNT_ID_CHANGED\";\n   } else if (result == act::ResultAuthenticationLocked) {\n      return \"AUTHENTICATION_LOCKED\";\n   } else if (result == act::ResultDeviceInactive) {\n      return \"DEVICE_INACTIVE\";\n   } else if (result == act::ResultCoppaAgreementCanceled) {\n      return \"COPPA_AGREEMENT_CANCELED\";\n   } else if (result == act::ResultDomainAccountAlreadyExists) {\n      return \"DOMAIN_ACCOUNT_ALREADY_EXISTS\";\n   } else if (result == act::ResultAccountTokenExpired) {\n      return \"ACCOUNT_TOKEN_EXPIRED\";\n   } else if (result == act::ResultInvalidAccountToken) {\n      return \"INVALID_ACCOUNT_TOKEN\";\n   } else if (result == act::ResultAuthenticationRequired) {\n      return \"AUTHENTICATION_REQUIRED\";\n   } else if (result == act::ResultConfirmationCodeExpired) {\n      return \"CONFIRMATION_CODE_EXPIRED\";\n   } else if (result == act::ResultMailAddressNotValidated) {\n      return \"MAIL_ADDRESS_NOT_VALIDATED\";\n   } else if (result == act::ResultExcessiveMailSendRequest) {\n      return \"EXCESSIVE_MAIL_SEND_REQUEST\";\n   } else if (result == act::ResultCreditCardError) {\n      return \"CREDIT_CARD_ERROR\";\n   } else if (result == act::ResultCreditCardGeneralFailure) {\n      return \"CREDIT_CARD_GENERAL_FAILURE\";\n   } else if (result == act::ResultCreditCardDeclined) {\n      return \"CREDIT_CARD_DECLINED\";\n   } else if (result == act::ResultCreditCardBlacklisted) {\n      return \"CREDIT_CARD_BLACKLISTED\";\n   } else if (result == act::ResultInvalidCreditCardNumber) {\n      return \"INVALID_CREDIT_CARD_NUMBER\";\n   } else if (result == act::ResultInvalidCreditCardDate) {\n      return \"INVALID_CREDIT_CARD_DATE\";\n   } else if (result == act::ResultInvalidCreditCardPin) {\n      return \"INVALID_CREDIT_CARD_PIN\";\n   } else if (result == act::ResultInvalidPostalCode) {\n      return \"INVALID_POSTAL_CODE\";\n   } else if (result == act::ResultInvalidLocation) {\n      return \"INVALID_LOCATION\";\n   } else if (result == act::ResultCreditCardDateExpired) {\n      return \"CREDIT_CARD_DATE_EXPIRED\";\n   } else if (result == act::ResultCreditCardNumberWrong) {\n      return \"CREDIT_CARD_NUMBER_WRONG\";\n   } else if (result == act::ResultCreditCardPinWrong) {\n      return \"CREDIT_CARD_PIN_WRONG\";\n   }\n\n   if (result == act::ResultBanned) {\n      return \"BANNED\";\n   } else if (result == act::ResultBannedAccount) {\n      return \"BANNED_ACCOUNT\";\n   } else if (result == act::ResultBannedAccountAll) {\n      return \"BANNED_ACCOUNT_ALL\";\n   } else if (result == act::ResultBannedAccountInApplication) {\n      return \"BANNED_ACCOUNT_IN_APPLICATION\";\n   } else if (result == act::ResultBannedAccountInNexService) {\n      return \"BANNED_ACCOUNT_IN_NEX_SERVICE\";\n   } else if (result == act::ResultBannedAccountInIndependentService) {\n      return \"BANNED_ACCOUNT_IN_INDEPENDENT_SERVICE\";\n   } else if (result == act::ResultBannedDevice) {\n      return \"BANNED_DEVICE\";\n   } else if (result == act::ResultBannedDeviceAll) {\n      return \"BANNED_DEVICE_ALL\";\n   } else if (result == act::ResultBannedDeviceInApplication) {\n      return \"BANNED_DEVICE_IN_APPLICATION\";\n   } else if (result == act::ResultBannedDeviceInNexService) {\n      return \"BANNED_DEVICE_IN_NEX_SERVICE\";\n   } else if (result == act::ResultBannedDeviceInIndependentService) {\n      return \"BANNED_DEVICE_IN_INDEPENDENT_SERVICE\";\n   } else if (result == act::ResultBannedAccountTemporarily) {\n      return \"BANNED_ACCOUNT_TEMPORARILY\";\n   } else if (result == act::ResultBannedAccountAllTemporarily) {\n      return \"BANNED_ACCOUNT_ALL_TEMPORARILY\";\n   } else if (result == act::ResultBannedAccountInApplicationTemporarily) {\n      return \"BANNED_ACCOUNT_IN_APPLICATION_TEMPORARILY\";\n   } else if (result == act::ResultBannedAccountInNexServiceTemporarily) {\n      return \"BANNED_ACCOUNT_IN_NEX_SERVICE_TEMPORARILY\";\n   } else if (result == act::ResultBannedAccountInIndependentServiceTemporarily) {\n      return \"BANNED_ACCOUNT_IN_INDEPENDENT_SERVICE_TEMPORARILY\";\n   } else if (result == act::ResultBannedDeviceTemporarily) {\n      return \"BANNED_DEVICE_TEMPORARILY\";\n   } else if (result == act::ResultBannedDeviceAllTemporarily) {\n      return \"BANNED_DEVICE_ALL_TEMPORARILY\";\n   } else if (result == act::ResultBannedDeviceInApplicationTemporarily) {\n      return \"BANNED_DEVICE_IN_APPLICATION_TEMPORARILY\";\n   } else if (result == act::ResultBannedDeviceInNexServiceTemporarily) {\n      return \"BANNED_DEVICE_IN_NEX_SERVICE_TEMPORARILY\";\n   } else if (result == act::ResultBannedDeviceInIndependentServiceTemporarily) {\n      return \"BANNED_DEVICE_IN_INDEPENDENT_SERVICE_TEMPORARILY\";\n   }\n\n   if (result == act::ResultServiceNotProvided) {\n      return \"SERVICE_NOT_PROVIDED\";\n   } else if (result == act::ResultUnderMaintenance) {\n      return \"UNDER_MAINTENANCE\";\n   } else if (result == act::ResultServiceClosed) {\n      return \"SERVICE_CLOSED\";\n   } else if (result == act::ResultNintendoNetworkClosed) {\n      return \"NINTENDO_NETWORK_CLOSED\";\n   } else if (result == act::ResultNotProvidedCountry) {\n      return \"NOT_PROVIDED_COUNTRY\";\n   } else if (result == act::ResultRestrictionError) {\n      return \"RESTRICTION_ERROR\";\n   } else if (result == act::ResultRestrictedByAge) {\n      return \"RESTRICTED_BY_AGE\";\n   } else if (result == act::ResultRestrictedByParentalControls) {\n      return \"RESTRICTED_BY_PARENTAL_CONTROLS\";\n   } else if (result == act::ResultOnGameInternetCommunicationRestricted) {\n      return \"ON_GAME_INTERNET_COMMUNICATION_RESTRICTED\";\n   } else if (result == act::ResultInternalServerError) {\n      return \"INTERNAL_SERVER_ERROR\";\n   } else if (result == act::ResultUnknownServerError) {\n      return \"UNKNOWN_SERVER_ERROR\";\n   } else if (result == act::ResultUnauthenticatedAfterSalvage) {\n      return \"UNAUTHENTICATED_AFTER_SALVAGE\";\n   } else if (result == act::ResultAuthenticationFailureUnknown) {\n      return \"AUTHENTICATION_FAILURE_UNKNOWN\";\n   }\n\n   return \"<unknown>\";\n}\n\nconst char *\nGetDescriptionString(nn::Result result)\n{\n   if (result.isLegacyRepresentation()) {\n      return GetLegacyDescriptionString(result);\n   }\n\n   switch (result.module()) {\n   case nn::Result::MODULE_NN_ACP:\n      return GetAcpDescriptionString(result);\n   case nn::Result::MODULE_NN_ACT:\n      return GetActDescriptionString(result);\n   }\n\n   return \"<unknown>\";\n}\n\n} // namespace nn::dbg\n"
  },
  {
    "path": "src/libdecaf/src/nn/dbg/nn_dbg_result_string.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace nn::dbg\n{\n\nconst char *\nGetDescriptionString(nn::Result result);\n\nconst char *\nGetLevelString(nn::Result result);\n\nconst char *\nGetModuleString(nn::Result result);\n\nconst char *\nGetSummaryString(nn::Result result);\n\n} // namespace nn::dbg\n"
  },
  {
    "path": "src/libdecaf/src/nn/ffl/nn_ffl_miidata.h",
    "content": "#pragma once\n#include <array>\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n\nnamespace nn::ffl\n{\n\n#pragma pack(push, 1)\n\nenum FFLCreateIDFlags : uint8_t\n{\n   IsWiiUMii = 0x1 | 0x4,\n   IsTemporaryMii = 0x2,\n   IsNormalMii = 0x8,\n};\n\nstruct FFLCreateID\n{\n   uint32_t flags : 4;\n\n   //! Seconds since Jan 1st 2010\n   uint32_t timestamp : 28;\n\n   uint8_t deviceHash[6];\n};\nCHECK_OFFSET(FFLCreateID, 4, deviceHash);\nCHECK_SIZE(FFLCreateID, 10);\n\n// This structure is intentionally little-endian as the data\n//  is stored in a cross-platform manner for multiple devices.\nstruct FFLiMiiDataCore\n{\n   // 0x00\n   uint8_t birth_platform : 4;\n   uint8_t unk_0x00_b4 : 4;\n\n   // 0x01\n   uint8_t unk_0x01_b0 : 4;\n   uint8_t unk_0x01_b4 : 4;\n\n   // 0x02\n   uint8_t font_region : 4;\n   uint8_t region_move : 2;\n   uint8_t unk_0x02_b6 : 1;\n   uint8_t copyable : 1;\n\n   // 0x03\n   uint8_t mii_version;\n\n   // 0x4\n   uint64_t author_id;\n\n   // 0xC\n   FFLCreateID mii_id;\n\n   // 0x16\n   uint16_t unk_0x16;\n\n   // 0x18\n   uint16_t unk_0x18_b0 : 1;\n   uint16_t unk_0x18_b1 : 1;\n   uint16_t color : 4;\n   uint16_t birth_day : 5;\n   uint16_t birth_month : 4;\n   uint16_t gender : 1;\n\n   // 0x1A\n   char16_t mii_name[10];\n\n   // 0x2E\n   uint8_t size;\n\n   // 0x2F\n   uint8_t fatness;\n\n   // 0x30\n   uint8_t blush_type : 4;\n   uint8_t face_style : 4;\n\n   // 0x31\n   uint8_t face_color : 3;\n   uint8_t face_type : 4;\n   uint8_t local_only : 1;\n\n   // 0x32\n   uint8_t hair_mirrored : 5;\n   uint8_t hair_color : 3;\n\n   // 0x33\n   uint8_t hair_type;\n\n   // 0x34\n   uint32_t eye_thickness : 3;\n   uint32_t eye_scale : 4;\n   uint32_t eye_color : 3;\n   uint32_t eye_type : 6;\n   uint32_t eye_height : 7;\n   uint32_t eye_distance : 4;\n   uint32_t eye_rotation : 5;\n\n   // 0x38\n   uint32_t eyebrow_thickness : 4;\n   uint32_t eyebrow_scale : 4;\n   uint32_t eyebrow_color : 3;\n   uint32_t eyebrow_type : 5;\n   uint32_t eyebrow_height : 7;\n   uint32_t eyebrow_distance : 4;\n   uint32_t eyebrow_rotation : 5;\n\n   // 0x3c\n   uint32_t nose_height : 7;\n   uint32_t nose_scale : 4;\n   uint32_t nose_type : 5;\n   uint32_t mouth_thickness : 3;\n   uint32_t mouth_scale : 4;\n   uint32_t mouth_color : 3;\n   uint32_t mouth_type : 6;\n\n   // 0x40\n   uint32_t unk_0x40 : 8;\n   uint32_t mustache_type : 3;\n   uint32_t mouth_height : 5;\n   uint32_t mustache_height : 6;\n   uint32_t mustache_scale : 4;\n   uint32_t beard_color : 3;\n   uint32_t beard_type : 3;\n\n   // 0x44\n   uint16_t glass_height : 5;\n   uint16_t glass_scale : 4;\n   uint16_t glass_color : 3;\n   uint16_t glass_type : 4;\n\n   // 0x46\n   uint16_t unk_0x46_b0 : 1;\n   uint16_t mole_ypos : 5;\n   uint16_t mole_xpos : 5;\n   uint16_t mole_scale : 4;\n   uint16_t mole_enabled : 1;\n};\nCHECK_OFFSET(FFLiMiiDataCore, 0x03, mii_version);\nCHECK_OFFSET(FFLiMiiDataCore, 0x04, author_id);\nCHECK_OFFSET(FFLiMiiDataCore, 0x0C, mii_id);\nCHECK_OFFSET(FFLiMiiDataCore, 0x16, unk_0x16);\nCHECK_OFFSET(FFLiMiiDataCore, 0x1A, mii_name);\nCHECK_OFFSET(FFLiMiiDataCore, 0x2E, size);\nCHECK_OFFSET(FFLiMiiDataCore, 0x2F, fatness);\nCHECK_OFFSET(FFLiMiiDataCore, 0x33, hair_type);\nCHECK_SIZE(FFLiMiiDataCore, 0x48);\n\nstruct FFLiMiiDataOfficial : FFLiMiiDataCore\n{\n   char16_t creator_name[10];\n};\nCHECK_OFFSET(FFLiMiiDataOfficial, 0x48, creator_name);\nCHECK_SIZE(FFLiMiiDataOfficial, 0x5C);\n\nstruct FFLStoreData : FFLiMiiDataOfficial\n{\n   uint16_t unk_0x5C;\n   be2_val<uint16_t> checksum;\n};\nCHECK_OFFSET(FFLStoreData, 0x5C, unk_0x5C);\nCHECK_OFFSET(FFLStoreData, 0x5E, checksum);\nCHECK_SIZE(FFLStoreData, 0x60);\n\n#pragma pack(pop)\n\n} // namespace nn::ffl\n"
  },
  {
    "path": "src/libdecaf/src/nn/ios/nn_ios_error.cpp",
    "content": "#include \"nn_ios_error.h\"\n\nnamespace nn::ios\n{\n\nnn::Result\nconvertError(::ios::Error error)\n{\n   switch (error) {\n   case ::ios::Error::OK:\n      return ResultOK;\n   case ::ios::Error::Access:\n      return ResultAccess;\n   case ::ios::Error::Exists:\n      return ResultExists;\n   case ::ios::Error::Intr:\n      return ResultIntr;\n   case ::ios::Error::Invalid:\n      return ResultInvalid;\n   case ::ios::Error::Max:\n      return ResultMax;\n   case ::ios::Error::NoExists:\n      return ResultNoExists;\n   case ::ios::Error::QEmpty:\n      return ResultQEmpty;\n   case ::ios::Error::QFull:\n      return ResultQFull;\n   case ::ios::Error::Unknown:\n      return ResultUnknown;\n   case ::ios::Error::NotReady:\n      return ResultNotReady;\n   case ::ios::Error::InvalidObjType:\n      return ResultInvalidObjType;\n   case ::ios::Error::InvalidVersion:\n      return ResultInvalidVersion;\n   case ::ios::Error::InvalidSigner:\n      return ResultInvalidSigner;\n   case ::ios::Error::FailCheckValue:\n      return ResultFailCheckValue;\n   case ::ios::Error::FailInternal:\n      return ResultFailInternal;\n   case ::ios::Error::FailAlloc:\n      return ResultFailAlloc;\n   case ::ios::Error::InvalidSize:\n      return ResultInvalidSize;\n   case ::ios::Error::NoLink:\n      return ResultNoLink;\n   case ::ios::Error::ANFailed:\n      return ResultANFailed;\n   case ::ios::Error::MaxSemCount:\n      return ResultMaxSemCount;\n   case ::ios::Error::SemUnavailable:\n      return ResultSemUnavailable;\n   case ::ios::Error::InvalidHandle:\n      return ResultInvalidHandle;\n   case ::ios::Error::InvalidArg:\n      return ResultInvalidArg;\n   case ::ios::Error::NoResource:\n      return ResultNoResource;\n   case ::ios::Error::Busy:\n      return ResultBusy;\n   case ::ios::Error::Timeout:\n      return ResultTimeout;\n   case ::ios::Error::Alignment:\n      return ResultAlignment;\n   case ::ios::Error::BSP:\n      return ResultBSP;\n   case ::ios::Error::DataPending:\n      return ResultDataPending;\n   case ::ios::Error::Expired:\n      return ResultExpired;\n   case ::ios::Error::NoReadAccess:\n      return ResultNoReadAccess;\n   case ::ios::Error::NoWriteAccess:\n      return ResultNoWriteAccess;\n   case ::ios::Error::NoReadWriteAccess:\n      return ResultNoReadWriteAccess;\n   case ::ios::Error::ClientTxnLimit:\n      return ResultClientTxnLimit;\n   case ::ios::Error::StaleHandle:\n      return ResultStaleHandle;\n   default:\n      return ResultUnknownValue;\n   }\n}\n\n} // namespace nn::ios\n"
  },
  {
    "path": "src/libdecaf/src/nn/ios/nn_ios_error.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n#include \"ios/ios_enum.h\"\n\nnamespace nn::ios\n{\n\nstatic constexpr Result ResultOK {\n   Result::MODULE_NN_IOS, Result::LEVEL_SUCCESS, 0\n};\n\nstatic constexpr const auto ResultAccess =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x100, 0x180>();\n\nstatic constexpr const auto ResultExists =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x180, 0x200>();\n\nstatic constexpr const auto ResultIntr =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x200, 0x280>();\n\nstatic constexpr const auto ResultInvalid =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x280, 0x300>();\n\nstatic constexpr const auto ResultMax =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x300, 0x380>();\n\nstatic constexpr const auto ResultNoExists =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x380, 0x400>();\n\nstatic constexpr const auto ResultQEmpty =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x400, 0x480>();\n\nstatic constexpr const auto ResultQFull =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x480, 0x500>();\n\nstatic constexpr const auto ResultUnknown =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x500, 0x580>();\n\nstatic constexpr const auto ResultNotReady =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x580, 0x600>();\n\nstatic constexpr const auto ResultInvalidObjType =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x600, 0x680>();\n\nstatic constexpr const auto ResultInvalidVersion =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x680, 0x700>();\n\nstatic constexpr const auto ResultInvalidSigner =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x700, 0x780>();\n\nstatic constexpr const auto ResultFailCheckValue =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x780, 0x800>();\n\nstatic constexpr const auto ResultFailInternal =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x800, 0x880>();\n\nstatic constexpr const auto ResultFailAlloc =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x880, 0x900>();\n\nstatic constexpr const auto ResultInvalidSize =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x900, 0x980>();\n\nstatic constexpr const auto ResultNoLink =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x980, 0xA00>();\n\nstatic constexpr const auto ResultANFailed =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xA00, 0xA80>();\n\nstatic constexpr const auto ResultMaxSemCount =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xA80, 0xB00>();\n\nstatic constexpr const auto ResultSemUnavailable =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xB00, 0xB80>();\n\nstatic constexpr const auto ResultInvalidHandle =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xB80, 0xC00>();\n\nstatic constexpr const auto ResultInvalidArg =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xC00, 0xC80>();\n\nstatic constexpr const auto ResultNoResource =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xC80, 0xD00>();\n\nstatic constexpr const auto ResultBusy =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xD00, 0xD80>();\n\nstatic constexpr const auto ResultTimeout =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xD80, 0xE00>();\n\nstatic constexpr const auto ResultAlignment =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xE00, 0xE80>();\n\nstatic constexpr const auto ResultBSP =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xE80, 0xF00>();\n\nstatic constexpr const auto ResultDataPending =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xF00, 0xF80>();\n\nstatic constexpr const auto ResultExpired =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0xF80, 0x1000>();\n\nstatic constexpr const auto ResultNoReadAccess =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x1000, 0x1080>();\n\nstatic constexpr const auto ResultNoWriteAccess =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x1080, 0x1100>();\n\nstatic constexpr const auto ResultNoReadWriteAccess =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x1100, 0x1180>();\n\nstatic constexpr const auto ResultClientTxnLimit =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x1180, 0x1200>();\n\nstatic constexpr const auto ResultStaleHandle =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x1200, 0x1280>();\n\nstatic constexpr const auto ResultUnknownValue =\n   ResultRange<Result::MODULE_NN_IOS, Result::LEVEL_STATUS, 0x3180, 0x3180>();\n\nResult\nconvertError(::ios::Error error);\n\n} // namespace nn::ios\n"
  },
  {
    "path": "src/libdecaf/src/nn/ipc/nn_ipc_command.h",
    "content": "#pragma once\n#include <cstdint>\n#include <utility>\n\nnamespace nn::ipc\n{\n\nusing CommandId = int32_t;\nusing ServiceId = int32_t;\n\ntemplate<typename Service, int Id>\nstruct Command\n{\n   template<typename... ParameterTypes>\n   struct Parameters\n   {\n      static constexpr ServiceId service = Service::id;\n      static constexpr CommandId command = Id;\n\n      using parameters = std::tuple<ParameterTypes...>;\n      using response = std::tuple<>;\n\n      // ::Response is optional\n      template<typename... ResponseTypes>\n      struct Response\n      {\n         static constexpr ServiceId service = Service::id;\n         static constexpr CommandId command = Id;\n\n         using parameters = std::tuple<ParameterTypes...>;\n         using response = std::tuple<ResponseTypes...>;\n      };\n   };\n};\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/nn/ipc/nn_ipc_format.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace nn::ipc\n{\n\n/*\n * IOCTLV vec order:\n *  Vectors which are both input and output are treated as output vectors.\n *\n *  numVecIn = 1 + 2 * number of user output vectors\n *  numVecOut = 1 + 2 * number of user input vectors\n *\n *  vec[0] = response buffer\n *    ... user output vecs ...\n *  vec[numVecIn] = request buffer\n *    ... user input vecs ...\n */\n\nstruct RequestHeader\n{\n   be2_val<uint32_t> unk0x00;\n   be2_val<uint32_t> service;\n   be2_val<uint32_t> unk0x08;\n   be2_val<uint32_t> command;\n};\n\nstruct ResponseHeader\n{\n   be2_val<uint32_t> result;\n};\n\nstruct ManagedBufferParameter\n{\n   be2_val<uint32_t> alignedBufferSize;\n   be2_val<uint8_t> alignedBufferIndex;\n   be2_val<uint8_t> unalignedBufferIndex;\n   be2_val<uint8_t> unalignedBeforeBufferSize;\n   be2_val<uint8_t> unalignedAfterBufferSize;\n};\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/nn/ipc/nn_ipc_managedbuffer.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace nn::ipc\n{\n\nstruct ManagedBuffer\n{\n   ManagedBuffer(bool input, bool output) :\n      input(input),\n      output(output)\n   {\n   }\n\n   ManagedBuffer(virt_ptr<void> ptr,\n                 uint32_t size,\n                 bool input,\n                 bool output) :\n      ptr(ptr),\n      size(size),\n      input(input),\n      output(output)\n   {\n   }\n\n   uint32_t totalSize()\n   {\n      return unalignedBeforeBufferSize + alignedBufferSize + unalignedAfterBufferSize;\n   }\n\n   void writeOutput(phys_ptr<void> data, uint32_t writeSize)\n   {\n      writeOutput(data.get(), writeSize);\n   }\n\n   void writeOutput(const void *data, size_t writeSize)\n   {\n      auto writePtr = reinterpret_cast<const uint8_t *>(data);\n\n      if (unalignedBeforeBufferSize && writeSize) {\n         auto count = std::min<size_t>(unalignedBeforeBufferSize, writeSize);\n         std::memcpy(unalignedBeforeBuffer.get(), writePtr, count);\n         writePtr += count;\n         writeSize -= count;\n      }\n\n      if (alignedBufferSize && writeSize) {\n         auto count = std::min<size_t>(alignedBufferSize, writeSize);\n         std::memcpy(alignedBuffer.get(), writePtr, count);\n         writePtr += count;\n         writeSize -= count;\n      }\n\n      if (unalignedAfterBufferSize && writeSize) {\n         auto count = std::min<size_t>(unalignedAfterBufferSize, writeSize);\n         std::memcpy(unalignedAfterBuffer.get(), writePtr, count);\n         writePtr += count;\n         writeSize -= count;\n      }\n   }\n\n   // For serialisation:\n   virt_ptr<void> ptr = nullptr;\n   uint32_t size = 0u;\n\n   // For deserialisation:\n   phys_ptr<void> unalignedBeforeBuffer = nullptr;\n   uint32_t unalignedBeforeBufferSize = 0u;\n\n   phys_ptr<void> alignedBuffer = nullptr;\n   uint32_t alignedBufferSize = 0u;\n\n   phys_ptr<void> unalignedAfterBuffer = nullptr;\n   uint32_t unalignedAfterBufferSize = 0u;\n\n   // For both:\n   bool input;\n   bool output;\n};\n\ntemplate<typename Type>\nstruct InBuffer : ManagedBuffer\n{\n   InBuffer() :\n      ManagedBuffer(true, false)\n   {\n   }\n\n   InBuffer(virt_ptr<Type> ptr) :\n      ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type)), true, false)\n   {\n   }\n\n   InBuffer(virt_ptr<Type> ptr, uint32_t count) :\n      ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type) * count), true, false)\n   {\n   }\n};\n\ntemplate<>\nstruct InBuffer<void> : ManagedBuffer\n{\n   InBuffer() :\n      ManagedBuffer(true, false)\n   {\n   }\n\n   InBuffer(virt_ptr<void> ptr, uint32_t count) :\n      ManagedBuffer(ptr, count, true, false)\n   {\n   }\n};\n\ntemplate<typename Type>\nstruct InOutBuffer : ManagedBuffer\n{\n   InOutBuffer() :\n      ManagedBuffer(true, true)\n   {\n   }\n\n   InOutBuffer(virt_ptr<Type> ptr) :\n      ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type)), true, true)\n   {\n   }\n\n   InOutBuffer(virt_ptr<Type> ptr, uint32_t count) :\n      ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type) * count), true, true)\n   {\n   }\n};\n\ntemplate<>\nstruct InOutBuffer<void> : ManagedBuffer\n{\n   InOutBuffer() :\n      ManagedBuffer(true, true)\n   {\n   }\n\n   InOutBuffer(virt_ptr<void> ptr, uint32_t count) :\n      ManagedBuffer(ptr, count, true, true)\n   {\n   }\n};\n\ntemplate<typename Type>\nstruct OutBuffer : ManagedBuffer\n{\n   OutBuffer() :\n      ManagedBuffer(false, true)\n   {\n   }\n\n   OutBuffer(virt_ptr<Type> ptr) :\n      ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type)), false, true)\n   {\n   }\n\n   OutBuffer(virt_ptr<Type> ptr, uint32_t count) :\n      ManagedBuffer(ptr, static_cast<uint32_t>(sizeof(Type) * count), false, true)\n   {\n   }\n};\n\ntemplate<>\nstruct OutBuffer<void> : ManagedBuffer\n{\n   OutBuffer() :\n      ManagedBuffer(false, true)\n   {\n   }\n\n   OutBuffer(virt_ptr<void> ptr, uint32_t count) :\n      ManagedBuffer(ptr, count, false, true)\n   {\n   }\n};\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/nn/ipc/nn_ipc_result.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace nn::ipc\n{\n\nstatic constexpr Result ResultNotImplemented {\n   Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x3200\n};\n\nstatic constexpr Result ResultInvalidIpcID {\n   Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0xfa00\n};\n\nstatic constexpr Result ResultInvalidSessionID {\n   Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0xfa80\n};\n\nstatic constexpr Result ResultInvalidIpcHeader {\n   Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x6400\n};\n\nstatic constexpr Result ResultInvalidIpcHeaderFormat {\n   Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x9600\n};\n\nstatic constexpr Result ResultInvalidIpcHeaderSize {\n   Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0xc800\n};\n\nstatic constexpr Result ResultInvalidModuleID {\n   Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0xff00\n};\n\nstatic constexpr Result ResultInvalidMethodTag {\n   Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x10400\n};\n\nstatic constexpr Result ResultInvalidIpcBuffer {\n   Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x12c00\n};\n\nstatic constexpr Result ResultInvalidIpcBufferLength {\n   Result::MODULE_NN_IPC, Result::LEVEL_USAGE, 0x12c80\n};\n\nstatic constexpr Result ResultAccessDenied {\n   Result::MODULE_NN_IPC, Result::LEVEL_STATUS, 0x15e00\n};\n\nstatic constexpr const auto ResultCapabilityFailed =\n   ResultRange<Result::MODULE_NN_IPC, Result::LEVEL_FATAL, 0x15e80, 0x16d00>();\n\nstatic constexpr Result ResultDevelopmentOnly {\n   Result::MODULE_NN_IPC, Result::LEVEL_STATUS, 0x16d00\n};\n\nstatic constexpr Result ResultUnusualControlFlow {\n   Result::MODULE_NN_IPC, Result::LEVEL_STATUS, 0x1f400\n};\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/nn/ipc/nn_ipc_service.h",
    "content": "#pragma once\n#include \"nn_ipc_command.h\"\n#include <libcpu/be2_struct.h>\n\nnamespace nn::ipc\n{\n\nusing ServiceId = int;\nusing ServiceCommandHandler = void(*)(CommandId id);\n\ntemplate<int Id>\nstruct Service\n{\n   static constexpr ServiceId id = Id;\n};\n\n} // namespace nn::ipc\n"
  },
  {
    "path": "src/libdecaf/src/nn/nfp/nn_nfp_result.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace nn::nfp\n{\n\nstatic constexpr Result ResultSuccess {\n   Result::MODULE_NN_NFP, Result::LEVEL_SUCCESS, 128\n};\n\nstatic constexpr Result ResultInvalidPointer {\n   Result::MODULE_NN_NFP, Result::LEVEL_USAGE, 14208\n};\n\nstatic constexpr Result ResultIsBusy {\n   Result::MODULE_NN_NFP, Result::LEVEL_STATUS, 38528\n};\n\nstatic constexpr Result ResultInvalidState {\n   Result::MODULE_NN_NFP, Result::LEVEL_STATUS, 256000\n};\n\n} // namespace nn::nfp\n"
  },
  {
    "path": "src/libdecaf/src/nn/nn_result.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace nn\n{\n\nstruct Result\n{\n   enum Level : int32_t\n   {\n      LEVEL_SUCCESS  = 0,\n      LEVEL_FATAL    = -1,\n      LEVEL_USAGE    = -2,\n      LEVEL_STATUS   = -3,\n      LEVEL_END      = -7,\n   };\n\n   enum Module : int32_t\n   {\n      MODULE_COMMON        = 0,\n      MODULE_NN_IPC        = 1,\n      MODULE_NN_BOSS       = 2,\n      MODULE_NN_ACP        = 3,\n      MODULE_NN_IOS        = 4,\n      MODULE_NN_NIM        = 5,\n      MODULE_NN_PDM        = 6,\n      MODULE_NN_ACT        = 7,\n      MODULE_NN_NGC        = 8,\n      MODULE_NN_ECA        = 9,\n      MODULE_NN_NUP        = 10,\n      MODULE_NN_NDM        = 11,\n      MODULE_NN_FP         = 12,\n      MODULE_NN_AC         = 13,\n      MODULE_NN_CONNTEST   = 14,\n      MODULE_NN_DRMAPP     = 15,\n      MODULE_NN_TELNET     = 16,\n      MODULE_NN_OLV        = 17,\n      MODULE_NN_VCTL       = 18,\n      MODULE_NN_NEIA       = 19,\n      MODULE_NN_SPM        = 20,\n      MODULE_NN_EMD        = 21,\n      MODULE_NN_EC         = 22,\n      MODULE_NN_CIA        = 23,\n      MODULE_NN_SL         = 24,\n      MODULE_NN_ECO        = 25,\n      MODULE_NN_TRIAL      = 26,\n      MODULE_NN_NFP        = 27,\n      MODULE_NN_TEST       = 125,\n   };\n\n   enum LegacyLevel\n   {\n      LEGACY_LEVEL_INFO                   = 1,\n      LEGACY_LEVEL_RESET                  = -4,\n      LEGACY_LEVEL_REINIT                 = -5,\n      LEGACY_LEVEL_PERMANENT              = -6,\n      LEGACY_LEVEL_TEMPORARY              = -7,\n   };\n\n   enum LegacyCommonDescription : int32_t\n   {\n      LEGACY_DESCRIPTION_SUCCESS                 = 0,\n      LEGACY_DESCRIPTION_TIMEOUT                 = -2,\n      LEGACY_DESCRIPTION_OUT_OF_RANGE            = -3,\n      LEGACY_DESCRIPTION_ALREADY_EXISTS          = -4,\n      LEGACY_DESCRIPTION_CANCEL_REQUESTED        = -5,\n      LEGACY_DESCRIPTION_NOT_FOUND               = -6,\n      LEGACY_DESCRIPTION_ALREADY_INITIALIZED     = -7,\n      LEGACY_DESCRIPTION_NOT_INITIALIZED         = -8,\n      LEGACY_DESCRIPTION_INVALID_HANDLE          = -9,\n      LEGACY_DESCRIPTION_INVALID_POINTER         = -10,\n      LEGACY_DESCRIPTION_INVALID_ADDRESS         = -11,\n      LEGACY_DESCRIPTION_NOT_IMPLEMENTED         = -12,\n      LEGACY_DESCRIPTION_OUT_OF_MEMORY           = -13,\n      LEGACY_DESCRIPTION_MISALIGNED_SIZE         = -14,\n      LEGACY_DESCRIPTION_MISALIGNED_ADDRESS      = -15,\n      LEGACY_DESCRIPTION_BUSY                    = -16,\n      LEGACY_DESCRIPTION_NO_DATA                 = -17,\n      LEGACY_DESCRIPTION_INVALID_COMBINATION     = -18,\n      LEGACY_DESCRIPTION_INVALID_ENUM_VALUE      = -19,\n      LEGACY_DESCRIPTION_INVALID_SIZE            = -20,\n      LEGACY_DESCRIPTION_ALREADY_DONE            = -21,\n      LEGACY_DESCRIPTION_NOT_AUTHORIZED          = -22,\n      LEGACY_DESCRIPTION_TOO_LARGE               = -23,\n      LEGACY_DESCRIPTION_INVALID_SELECTION       = -24,\n   };\n\n   enum LegacyModule\n   {\n      LEGACY_MODULE_COMMON                = 0,\n      LEGACY_MODULE_NN_KERNEL             = 1,\n      LEGACY_MODULE_NN_UTIL               = 2,\n      LEGACY_MODULE_NN_FILE_SERVER        = 3,\n      LEGACY_MODULE_NN_LOADER_SERVER      = 4,\n      LEGACY_MODULE_NN_TCB                = 5,\n      LEGACY_MODULE_NN_OS                 = 6,\n      LEGACY_MODULE_NN_DBG                = 7,\n      LEGACY_MODULE_NN_DMNT               = 8,\n      LEGACY_MODULE_NN_PDN                = 9,\n      LEGACY_MODULE_NN_GX                 = 0xA,\n      LEGACY_MODULE_NN_I2C                = 0xB,\n      LEGACY_MODULE_NN_GPIO               = 0xC,\n      LEGACY_MODULE_NN_DD                 = 0xD,\n      LEGACY_MODULE_NN_CODEC              = 0xE,\n      LEGACY_MODULE_NN_SPI                = 0xF,\n      LEGACY_MODULE_NN_PXI                = 0x10,\n      LEGACY_MODULE_NN_FS                 = 0x11,\n      LEGACY_MODULE_NN_DI                 = 0x12,\n      LEGACY_MODULE_NN_HID                = 0x13,\n      LEGACY_MODULE_NN_CAMERA             = 0x14,\n      LEGACY_MODULE_NN_PI                 = 0x15,\n      LEGACY_MODULE_NN_PM                 = 0x16,\n      LEGACY_MODULE_NN_PMLOW              = 0x17,\n      LEGACY_MODULE_NN_FSI                = 0x18,\n      LEGACY_MODULE_NN_SRV                = 0x19,\n      LEGACY_MODULE_NN_NDM                = 0x1A,\n      LEGACY_MODULE_NN_NWM                = 0x1B,\n      LEGACY_MODULE_NN_SOCKET             = 0x1C,\n      LEGACY_MODULE_NN_LDR                = 0x1D,\n      LEGACY_MODULE_NN_ACC                = 0x1E,\n      LEGACY_MODULE_NN_ROMFS              = 0x1F,\n      LEGACY_MODULE_NN_AM                 = 0x20,\n      LEGACY_MODULE_NN_HIO                = 0x21,\n      LEGACY_MODULE_NN_UPDATER            = 0x22,\n      LEGACY_MODULE_NN_MIC                = 0x23,\n      LEGACY_MODULE_NN_FND                = 0x24,\n      LEGACY_MODULE_NN_MP                 = 0x25,\n      LEGACY_MODULE_NN_MPWL               = 0x26,\n      LEGACY_MODULE_NN_AC                 = 0x27,\n      LEGACY_MODULE_NN_HTTP               = 0x28,\n      LEGACY_MODULE_NN_DSP                = 0x29,\n      LEGACY_MODULE_NN_SND                = 0x2A,\n      LEGACY_MODULE_NN_DLP                = 0x2B,\n      LEGACY_MODULE_NN_HIOLOW             = 0x2C,\n      LEGACY_MODULE_NN_CSND               = 0x2D,\n      LEGACY_MODULE_NN_SSL                = 0x2E,\n      LEGACY_MODULE_NN_AMLOW              = 0x2F,\n      LEGACY_MODULE_NN_NEX                = 0x30,\n      LEGACY_MODULE_NN_FRIENDS            = 0x31,\n      LEGACY_MODULE_NN_RDT                = 0x32,\n      LEGACY_MODULE_NN_APPLET             = 0x33,\n      LEGACY_MODULE_NN_NIM                = 0x34,\n      LEGACY_MODULE_NN_PTM                = 0x35,\n      LEGACY_MODULE_NN_MIDI               = 0x36,\n      LEGACY_MODULE_NN_MC                 = 0x37,\n      LEGACY_MODULE_NN_SWC                = 0x38,\n      LEGACY_MODULE_NN_FATFS              = 0x39,\n      LEGACY_MODULE_NN_NGC                = 0x3A,\n      LEGACY_MODULE_NN_CARD               = 0x3B,\n      LEGACY_MODULE_NN_CARDNOR            = 0x3C,\n      LEGACY_MODULE_NN_SDMC               = 0x3D,\n      LEGACY_MODULE_NN_BOSS               = 0x3E,\n      LEGACY_MODULE_NN_DBM                = 0x3F,\n      LEGACY_MODULE_NN_CFG                = 0x40,\n      LEGACY_MODULE_NN_PS                 = 0x41,\n      LEGACY_MODULE_NN_CEC                = 0x42,\n      LEGACY_MODULE_NN_IR                 = 0x43,\n      LEGACY_MODULE_NN_UDS                = 0x44,\n      LEGACY_MODULE_NN_PL                 = 0x45,\n      LEGACY_MODULE_NN_CUP                = 0x46,\n      LEGACY_MODULE_NN_GYROSCOPE          = 0x47,\n      LEGACY_MODULE_NN_MCU                = 0x48,\n      LEGACY_MODULE_NN_NS                 = 0x49,\n      LEGACY_MODULE_NN_NEWS               = 0x4A,\n      LEGACY_MODULE_NN_RO                 = 0x4B,\n      LEGACY_MODULE_NN_GD                 = 0x4C,\n      LEGACY_MODULE_NN_CARDSPI            = 0x4D,\n      LEGACY_MODULE_NN_EC                 = 0x4E,\n      LEGACY_MODULE_NN_WEBBRS             = 0x4F,\n      LEGACY_MODULE_NN_TEST               = 0x50,\n      LEGACY_MODULE_NN_ENC                = 0x51,\n      LEGACY_MODULE_NN_PIA                = 0x52,\n      LEGACY_MODULE_APPLICATION           = 0x1FE,\n   };\n\n   enum LegacySummary\n   {\n      LEGACY_SUMMARY_SUCCESS              = 0,\n      LEGACY_SUMMARY_NOTHING_HAPPENED     = 1,\n      LEGACY_SUMMARY_WOULD_BLOCK          = 2,\n      LEGACY_SUMMARY_OUT_OF_RESOURCE      = 3,\n      LEGACY_SUMMARY_NOT_FOUND            = 4,\n      LEGACY_SUMMARY_INVALID_STATE        = 5,\n      LEGACY_SUMMARY_NOT_SUPPORTED        = 6,\n      LEGACY_SUMMARY_INVALID_ARGUMENT     = 7,\n      LEGACY_SUMMARY_WRONG_ARGUMENT       = 8,\n      LEGACY_SUMMARY_CANCELLED            = 9,\n      LEGACY_SUMMARY_STATUS_CHANGED       = 10,\n      LEGACY_SUMMARY_INTERNAL             = 11,\n   };\n\n   enum LegacySignature\n   {\n      LEGACY_SIGNATURE                    = 3,\n   };\n\n   constexpr Result() noexcept :\n      mCode(0)\n   {\n   }\n\n   constexpr Result(uint32_t code_) noexcept :\n      mCode(code_)\n   {\n   }\n\n   constexpr Result(Module module, Level level, int32_t description) noexcept :\n      mDescription(description),\n      mModule(module),\n      mLevel(level)\n   {\n   }\n\n   constexpr explicit operator uint32_t() const noexcept\n   {\n      return mCode;\n   }\n\n   constexpr explicit operator bool() const noexcept\n   {\n      return ok();\n   }\n\n   constexpr bool operator==(const Result &other) const noexcept\n   {\n      return module() == other.module() &&\n             description() == other.description();\n   }\n\n   constexpr bool ok() const noexcept\n   {\n      return (mCode & 0x80000000) == 0;\n   }\n\n   constexpr bool failed() const noexcept\n   {\n      return (mCode & 0x80000000) != 0;\n   }\n\n   constexpr bool isLegacyRepresentation() const noexcept\n   {\n      return mLegacy.signature == LEGACY_SIGNATURE;\n   }\n\n   constexpr uint32_t code() const noexcept\n   {\n      return mCode;\n   }\n\n   constexpr int description() const noexcept\n   {\n      if (isLegacyRepresentation()) {\n         return mLegacy.description;\n      }\n\n      return mDescription;\n   }\n\n   constexpr int level() const noexcept\n   {\n      if (isLegacyRepresentation()) {\n         return mLegacy.level;\n      }\n\n      return mLevel;\n   }\n\n   constexpr int module() const noexcept\n   {\n      if (isLegacyRepresentation()) {\n         return mLegacy.module;\n      }\n\n      return mModule;\n   }\n\n   constexpr int legacySummary() const noexcept\n   {\n      return mLegacy.summary;\n   }\n\nprivate:\n   union\n   {\n      uint32_t mCode;\n\n      struct\n      {\n         int32_t mDescription : 20;\n         int32_t mModule : 9;\n         int32_t mLevel : 3;\n      };\n\n      struct\n      {\n         int32_t description : 10;\n         int32_t summary : 4;\n         int32_t level : 4;\n         int32_t : 2;\n         int32_t module : 7;\n         int32_t signature : 2;\n         int32_t : 3;\n      } mLegacy;\n   };\n};\n\nstatic constexpr Result ResultSuccess { 0 };\n\ntemplate<Result::Module Module_, Result::Level Level_, int DescStart, int DescEnd>\nstruct ResultRange : Result\n{\n   constexpr ResultRange() :\n      Result(Module_, Level_, DescStart)\n   {\n   }\n\n   constexpr bool operator==(const Result &other) noexcept\n   {\n      return other.module() == Module_ &&\n             other.description() >= DescStart &&\n             other.description() < DescEnd;\n   }\n\n   constexpr friend bool operator==(const Result &other, const ResultRange &) noexcept\n   {\n      return other.module() == Module_ &&\n             other.description() >= DescStart &&\n             other.description() < DescEnd;\n   }\n};\n\n} // namespace nn\n"
  },
  {
    "path": "src/libdecaf/src/nn/olv/nn_olv_result.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace nn::olv\n{\n\nstatic constexpr Result ResultSuccess {\n   nn::Result::MODULE_NN_OLV, nn::Result::LEVEL_SUCCESS, 0x80\n};\n\nstatic constexpr Result ResultInvalidSize {\n   nn::Result::MODULE_NN_OLV, nn::Result::LEVEL_USAGE, 0x6580\n};\n\nstatic constexpr Result ResultInvalidPointer {\n   nn::Result::MODULE_NN_OLV, nn::Result::LEVEL_USAGE, 0x6600\n};\n\nstatic constexpr Result ResultNotOnline {\n   nn::Result::MODULE_NN_OLV, nn::Result::LEVEL_USAGE, 0x6780\n};\n\nstatic constexpr Result ResultNoData {\n   nn::Result::MODULE_NN_OLV, nn::Result::LEVEL_USAGE, 0x6800\n};\n\n} // namespace nn::olv\n"
  },
  {
    "path": "src/libdecaf/src/nn/pdm/nn_pdm_cosservice.h",
    "content": "#pragma once\n#include \"nn/ipc/nn_ipc_service.h\"\n\n#include <cstdint>\n\nnamespace nn::pdm::services\n{\n\nstruct CosService : ipc::Service<0>\n{\n   using GetPlayDiaryMaxLength =\n      ipc::Command<CosService, 0>\n      ::Parameters<>\n      ::Response<uint32_t>;\n\n   using GetPlayStatsMaxLength =\n      ipc::Command<CosService, 256>\n      ::Parameters<>\n      ::Response<uint32_t>;\n};\n\n} // namespace nn::pdm::services\n"
  },
  {
    "path": "src/libdecaf/src/nn/pdm/nn_pdm_result.h",
    "content": "#pragma once\n#include \"nn/nn_result.h\"\n\nnamespace nn::pdm\n{\n\nstatic constexpr Result ResultSuccess {\n   nn::Result::MODULE_NN_PDM, nn::Result::LEVEL_SUCCESS, 128\n};\n\n} // namespace nn::pdm\n"
  },
  {
    "path": "src/libdecaf/src/nn/spm/nn_spm_extendedstorageservice.h",
    "content": "#pragma once\n#include \"nn/ipc/nn_ipc_command.h\"\n#include \"nn/ipc/nn_ipc_service.h\"\n\n#include <cstdint>\n\nnamespace nn::spm::services\n{\n\n// Served from /dev/acp_main\nstruct ExtendedStorageService : ipc::Service<303>\n{\n   using SetAutoFatal =\n      ipc::Command<ExtendedStorageService, 6>\n         ::Parameters<uint8_t>\n         ::Response<>;\n};\n\n} // namespace nn::spm::services\n"
  },
  {
    "path": "src/libdecaf/src/traceiter.h",
    "content": "#pragma once\n#include \"trace.h\"\n\nclass TraceIterator\n{\npublic:\n   TraceIterator(const Tracer* tracer)\n      : mTracer(tracer), mCurTrace(nullptr), mPrevTrace(nullptr)\n   {\n   }\n\n   const Trace * next()\n   {\n\n   }\n\nprotected:\n   const Tracer *mTracer;\n   const Trace *mPrevTrace;\n   const Trace *mCurTrace;\n};"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_device.h",
    "content": "#pragma once\n#include \"vfs_directoryiterator.h\"\n#include \"vfs_error.h\"\n#include \"vfs_filehandle.h\"\n#include \"vfs_result.h\"\n#include \"vfs_path.h\"\n#include \"vfs_permissions.h\"\n#include \"vfs_status.h\"\n\n#include <memory>\n\nnamespace vfs\n{\n\nclass Device\n{\npublic:\n   enum Type\n   {\n      Host,\n      Virtual,\n      Overlay,\n      Link,\n   };\n\n   Device(Type type) :\n      mType(type)\n   {\n   }\n   virtual ~Device() = default;\n\n   virtual Result<std::shared_ptr<Device>> getLinkDevice(const User &user,\n                                                         const Path &path) = 0;\n   virtual Error makeFolder(const User &user, const Path &path) = 0;\n   virtual Error makeFolders(const User &user, const Path &path) = 0;\n   virtual Error mountDevice(const User &user, const Path &path,\n                             std::shared_ptr<Device> device) = 0;\n   virtual Error mountOverlayDevice(const User &user, OverlayPriority priority,\n                                    const Path &path,\n                                    std::shared_ptr<Device> device) = 0;\n   virtual Error unmountDevice(const User &user, const Path &path) = 0;\n   virtual Error unmountOverlayDevice(const User &user,\n                                      OverlayPriority priority,\n                                      const Path &path) = 0;\n   virtual Result<DirectoryIterator> openDirectory(const User &user,\n                                                   const Path &path) = 0;\n   virtual Result<std::unique_ptr<FileHandle>> openFile(const User &user,\n                                                        const Path &path,\n                                                        FileHandle::Mode mode) = 0;\n   virtual Error remove(const User &user, const Path &path) = 0;\n   virtual Error rename(const User &user, const Path &src, const Path &dst) = 0;\n   virtual Error setGroup(const User &user, const Path &path, GroupId group) = 0;\n   virtual Error setOwner(const User &user, const Path &path, OwnerId owner) = 0;\n   virtual Error setPermissions(const User &user, const Path &path, Permissions mode) = 0;\n   virtual Result<Status> status(const User &user, const Path &path) = 0;\n\n   Type type()\n   {\n      return mType;\n   }\n\nprivate:\n   Type mType;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_directoryiterator.h",
    "content": "#pragma once\n#include \"vfs_error.h\"\n#include \"vfs_result.h\"\n#include \"vfs_status.h\"\n\n#include <memory>\n\nnamespace vfs\n{\n\nstruct DirectoryIteratorImpl\n{\n   virtual ~DirectoryIteratorImpl() = default;\n   virtual Result<Status> readEntry() = 0;\n   virtual Error rewind() = 0;\n};\n\nclass DirectoryIterator\n{\npublic:\n   DirectoryIterator() = default;\n   DirectoryIterator(std::shared_ptr<DirectoryIteratorImpl> impl) :\n      mImpl(impl)\n   {\n   }\n\n   Result<Status> readEntry()\n   {\n      return mImpl->readEntry();\n   }\n\n   Error rewind()\n   {\n      return mImpl->rewind();\n   }\n\nprivate:\n   std::shared_ptr<DirectoryIteratorImpl> mImpl;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_error.h",
    "content": "#pragma once\n#include <fmt/format.h>\n\nnamespace vfs\n{\n\nenum class Error\n{\n   Success = 0,\n\n   AlreadyExists,\n   EndOfDirectory,\n   EndOfFile,\n   ExecutePermission,\n   GenericError,\n   InvalidPath,\n   InvalidSeekDirection,\n   InvalidSeekPosition,\n   InvalidTruncatePosition,\n   NotDirectory,\n   NotFile,\n   NotFound,\n   NotMountDevice,\n   NotOpen,\n   OperationNotSupported,\n   ReadOnly,\n   Permission,\n};\n\n} // namespace vfs\n\ntemplate <>\nstruct fmt::formatter<vfs::Error> :\n   fmt::formatter<std::string_view>\n{\n   template <typename FormatContext>\n   auto format(vfs::Error value, FormatContext& ctx) {\n      std::string_view name = \"unknown\";\n      switch (value) {\n      case vfs::Error::Success:\n         name = \"Success\";\n         break;\n      case vfs::Error::AlreadyExists:\n         name = \"AlreadyExists\";\n         break;\n      case vfs::Error::EndOfDirectory:\n         name = \"EndOfDirectory\";\n         break;\n      case vfs::Error::EndOfFile:\n         name = \"EndOfFile\";\n         break;\n      case vfs::Error::ExecutePermission:\n         name = \"ExecutePermission\";\n         break;\n      case vfs::Error::GenericError:\n         name = \"GenericError\";\n         break;\n      case vfs::Error::InvalidPath:\n         name = \"InvalidPath\";\n         break;\n      case vfs::Error::InvalidSeekDirection:\n         name = \"InvalidSeekDirection\";\n         break;\n      case vfs::Error::InvalidSeekPosition:\n         name = \"InvalidSeekPosition\";\n         break;\n      case vfs::Error::InvalidTruncatePosition:\n         name = \"InvalidTruncatePosition\";\n         break;\n      case vfs::Error::NotDirectory:\n         name = \"NotDirectory\";\n         break;\n      case vfs::Error::NotFile:\n         name = \"NotFile\";\n         break;\n      case vfs::Error::NotFound:\n         name = \"NotFound\";\n         break;\n      case vfs::Error::NotMountDevice:\n         name = \"NotMountDevice\";\n         break;\n      case vfs::Error::NotOpen:\n         name = \"NotOpen\";\n         break;\n      case vfs::Error::OperationNotSupported:\n         name = \"OperationNotSupported\";\n         break;\n      case vfs::Error::ReadOnly:\n         name = \"ReadOnly\";\n         break;\n      case vfs::Error::Permission:\n         name = \"Permission\";\n         break;\n      }\n      return fmt::formatter<string_view>::format(name, ctx);\n   }\n};\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_filehandle.h",
    "content": "#pragma once\n#include \"vfs_result.h\"\n\n#include <cstdint>\n\nnamespace vfs\n{\n\nclass FileHandle\n{\npublic:\n   enum Mode\n   {\n      Read     = 1 << 0,\n      Write    = 1 << 1,\n      Append   = 1 << 2,\n      Update   = 1 << 3,\n      Execute  = 1 << 4,\n   };\n\n   enum SeekDirection\n   {\n      SeekStart,\n      SeekCurrent,\n      SeekEnd,\n   };\n\n   virtual ~FileHandle() = default;\n\n   virtual Error close() = 0;\n   virtual Result<bool> eof() = 0;\n   virtual Error flush() = 0;\n   virtual Error seek(SeekDirection direction, int64_t position) = 0;\n   virtual Result<int64_t> size() = 0;\n   virtual Result<int64_t> tell() = 0;\n   virtual Result<int64_t> truncate() = 0;\n   virtual Result<int64_t> read(void *buffer, int64_t size, int64_t count) = 0;\n   virtual Result<int64_t> write(const void *buffer, int64_t size, int64_t count) = 0;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_host_device.cpp",
    "content": "#include \"vfs_host_device.h\"\n#include \"vfs_host_directoryiterator.h\"\n#include \"vfs_host_filehandle.h\"\n#include \"vfs_link_device.h\"\n#include \"vfs_virtual_device.h\"\n\n#include <algorithm>\n#include <common/platform.h>\n#include <system_error>\n#include <vector>\n\nnamespace vfs\n{\n\nHostDevice::HostDevice(std::filesystem::path path) :\n   Device(Device::Host),\n   mHostPath(std::move(path)),\n   mVirtualDevice(std::make_shared<VirtualDevice>())\n{\n}\n\nResult<std::shared_ptr<Device>>\nHostDevice::getLinkDevice(const User &user,\n                          const Path &path)\n{\n   return { std::make_shared<LinkDevice>(shared_from_this(), path) };\n}\n\nError\nHostDevice::makeFolder(const User &user,\n                       const Path &path)\n{\n   if (mVirtualDevice) {\n      mVirtualDevice->makeFolder(user, path);\n   }\n\n   if (!checkWritePermission(user, path)) {\n      return Error::Permission;\n   }\n\n   auto error = std::error_code { };\n   if (std::filesystem::create_directories(makeHostPath(path), error)) {\n      return Error::Success;\n   }\n\n   return translateError(error);\n}\n\nError\nHostDevice::makeFolders(const User &user,\n                        const Path &path)\n{\n   if (mVirtualDevice) {\n      mVirtualDevice->makeFolders(user, path);\n   }\n\n   if (!checkWritePermission(user, path)) {\n      return Error::Permission;\n   }\n\n   auto error = std::error_code { };\n   if (std::filesystem::create_directories(makeHostPath(path), error)) {\n      return Error::Success;\n   }\n\n   return translateError(error);\n}\n\nError\nHostDevice::mountDevice(const User &user,\n                        const Path &path,\n                        std::shared_ptr<Device> device)\n{\n   return mVirtualDevice->mountDevice(user, path, device);\n}\n\nError\nHostDevice::mountOverlayDevice(const User &user,\n                               OverlayPriority priority,\n                               const Path &path,\n                               std::shared_ptr<Device> device)\n{\n   return mVirtualDevice->mountOverlayDevice(user, priority, path, device);\n}\n\nError\nHostDevice::unmountDevice(const User &user,\n                          const Path &path)\n{\n   return mVirtualDevice->unmountDevice(user, path);\n}\n\nError\nHostDevice::unmountOverlayDevice(const User &user,\n                                 OverlayPriority priority,\n                                 const Path &path)\n{\n   return mVirtualDevice->unmountOverlayDevice(user, priority, path);\n}\n\nResult<DirectoryIterator>\nHostDevice::openDirectory(const User &user,\n                          const Path &path)\n{\n   if (!checkReadPermission(user, path)) {\n      return { Error::Permission };\n   }\n\n   auto ec = std::error_code { };\n   auto itr = std::filesystem::directory_iterator { makeHostPath(path), ec };\n   if (!ec) {\n      // Iterate through the whole directory building up a list of entries\n      // alphabetically sorted by name to ensure same behaviour across platforms\n      auto listing = std::vector<Status> {};\n\n      for (auto &hostEntry : itr) {\n         auto currentEntry = Status { };\n         currentEntry.name = hostEntry.path().filename().string();\n\n         if (auto size = hostEntry.file_size(ec); !ec) {\n            currentEntry.size = size;\n            currentEntry.flags = Status::HasSize;\n         }\n\n         if (hostEntry.is_directory()) {\n            currentEntry.flags = Status::IsDirectory;\n         }\n\n         if (auto result = lookupPermissions(currentEntry.name); result) {\n            currentEntry.group = result->group;\n            currentEntry.owner = result->owner;\n            currentEntry.permission = result->permission;\n            currentEntry.flags = Status::HasPermissions;\n         }\n\n         listing.insert(\n            std::upper_bound(listing.begin(), listing.end(), currentEntry,\n               [](const Status &lhs, const Status &rhs) {\n                  return lhs.name < rhs.name;\n               }),\n            std::move(currentEntry));\n      }\n\n      return { DirectoryIterator {\n         std::make_shared<HostDirectoryIterator>(this, std::move(listing)) } };\n   }\n\n   if (mVirtualDevice) {\n      auto result = mVirtualDevice->openDirectory(user, path);\n      if (result) {\n         return result;\n      }\n   }\n\n   return translateError(ec);\n}\n\nstatic std::string\ntranslateOpenMode(FileHandle::Mode mode)\n{\n   auto result = std::string { };\n   if (mode & FileHandle::Read) {\n      result += \"r\";\n   }\n\n   if (mode & FileHandle::Write) {\n      result += \"w\";\n   }\n\n   if (mode & FileHandle::Append) {\n      result += \"a\";\n   }\n\n   result += \"b\";\n\n   if (mode & FileHandle::Update) {\n      result += \"+\";\n   }\n\n   return result;\n}\n\nResult<std::unique_ptr<FileHandle>>\nHostDevice::openFile(const User &user,\n                     const Path &path,\n                     FileHandle::Mode mode)\n{\n   // Check file permissions\n   if ((mode & FileHandle::Read) || (mode & FileHandle::Update)) {\n      if (!checkReadPermission(user, path)) {\n         return { Error::Permission };\n      }\n   }\n\n   if ((mode & FileHandle::Write) || (mode & FileHandle::Update)) {\n      if (!checkWritePermission(user, path)) {\n         return { Error::Permission };\n      }\n   }\n\n   if (mode & FileHandle::Execute) {\n      if (!checkExecutePermission(user, path)) {\n         return { Error::ExecutePermission };\n      }\n   }\n\n   auto hostMode = translateOpenMode(mode);\n#ifdef PLATFORM_WINDOWS\n   auto handle = static_cast<FILE *>(nullptr);\n   fopen_s(&handle, makeHostPath(path).string().c_str(), hostMode.c_str());\n#else\n   auto handle = fopen(makeHostPath(path).string().c_str(), hostMode.c_str());\n#endif\n   if (handle) {\n      return { std::make_unique<HostFileHandle>(handle, mode) };\n   }\n\n   if (mVirtualDevice) {\n      auto result = mVirtualDevice->openFile(user, path, mode);\n      if (result) {\n         return result;\n      }\n   }\n\n   // TODO: Translate fopen errno\n   return { Error::NotFound };\n}\n\nError\nHostDevice::remove(const User &user,\n                   const Path &path)\n{\n   if (!checkWritePermission(user, path)) {\n      return Error::Permission;\n   }\n\n   auto ec = std::error_code { };\n   if (std::filesystem::remove(makeHostPath(path), ec)) {\n      return Error::Success;\n   }\n\n   if (mVirtualDevice) {\n      auto result = mVirtualDevice->remove(user, path);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return translateError(ec);\n}\n\nError\nHostDevice::rename(const User &user,\n                   const Path &src,\n                   const Path &dst)\n{\n   if (!checkReadPermission(user, src)) {\n      return Error::Permission;\n   }\n\n   if (!checkWritePermission(user, dst)) {\n      return Error::Permission;\n   }\n\n   auto ec = std::error_code { };\n   std::filesystem::rename(makeHostPath(src), makeHostPath(dst), ec);\n   if (!ec) {\n      return Error::Success;\n   }\n\n   if (mVirtualDevice) {\n      auto result = mVirtualDevice->rename(user, src, dst);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return translateError(ec);\n}\n\nError\nHostDevice::setGroup(const User &user,\n                     const Path &path,\n                     GroupId group)\n{\n   auto ec = std::error_code { };\n   if (std::filesystem::exists(makeHostPath(path), ec)) {\n      mPermissionsCache[path.path()].group = group;\n      return Error::Success;\n   }\n\n   if (mVirtualDevice) {\n      auto result = mVirtualDevice->setGroup(user, path, group);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return translateError(ec);\n}\n\nError\nHostDevice::setOwner(const User &user,\n                     const Path &path,\n                     OwnerId owner)\n{\n   auto ec = std::error_code { };\n   if (std::filesystem::exists(makeHostPath(path), ec)) {\n      mPermissionsCache[path.path()].owner = owner;\n      return Error::Success;\n   }\n\n   if (mVirtualDevice) {\n      auto result = mVirtualDevice->setOwner(user, path, owner);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return translateError(ec);\n}\n\nError\nHostDevice::setPermissions(const User &user,\n                           const Path &path,\n                           Permissions mode)\n{\n   auto ec = std::error_code { };\n   if (std::filesystem::exists(makeHostPath(path), ec)) {\n      mPermissionsCache[path.path()].permission = mode;\n      return Error::Success;\n   }\n\n   if (mVirtualDevice) {\n      auto result = mVirtualDevice->setPermissions(user, path, mode);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return translateError(ec);\n}\n\nResult<Status>\nHostDevice::status(const User &user,\n                   const Path &path)\n{\n   if (!checkReadPermission(user, path)) {\n      return { Error::Permission };\n   }\n\n   auto ec = std::error_code { };\n   auto hostPath = makeHostPath(path);\n   auto hostStatus = std::filesystem::status(hostPath, ec);\n   if (ec) {\n      auto result = mVirtualDevice->status(user, path);\n      if (result) {\n         return result;\n      }\n\n      return { translateError(ec) };\n   }\n\n   if (!std::filesystem::exists(hostStatus)) {\n      return { Error::NotFound };\n   }\n\n   auto status = Status { };\n   if (std::filesystem::is_directory(hostStatus)) {\n      status.flags |= Status::IsDirectory;\n   }\n\n   if (auto size = std::filesystem::file_size(hostPath, ec); !ec) {\n      status.size = size;\n      status.flags |= Status::HasSize;\n   }\n\n   if (auto result = lookupPermissions(path); result) {\n      auto &permissions = *result;\n      status.group = permissions.group;\n      status.owner = permissions.owner;\n      status.permission = permissions.permission;\n      status.flags |= Status::HasPermissions;\n   }\n\n   return { status };\n}\n\nResult<HostNodePermission>\nHostDevice::lookupPermissions(const Path &path) const\n{\n   auto itr = mPermissionsCache.find(path.path());\n   if (itr == mPermissionsCache.end()) {\n      return { Error::NotFound };\n   }\n\n   return { itr->second };\n}\n\nbool\nHostDevice::checkReadPermission(const User &user,\n                                const Path &path)\n{\n   auto itr = mPermissionsCache.find(path.path());\n   if (itr == mPermissionsCache.end()) {\n      // No permissions mapped for this file, assume valid.\n      return true;\n   }\n\n   auto &permissions = itr->second;\n   if (user.id == permissions.owner) {\n      if (permissions.permission & Permissions::OwnerRead) {\n         return true;\n      }\n   }\n\n   if (user.group == permissions.group) {\n      if (permissions.permission & Permissions::GroupRead) {\n         return true;\n      }\n   }\n\n   if (permissions.permission & Permissions::OtherRead) {\n      return true;\n   }\n\n   return false;\n}\n\nbool\nHostDevice::checkWritePermission(const User &user,\n                                 const Path &path)\n{\n   auto itr = mPermissionsCache.find(path.path());\n   if (itr == mPermissionsCache.end()) {\n      // No permissions mapped for this file, assume valid.\n      return true;\n   }\n\n   auto &permissions = itr->second;\n   if (user.id == permissions.owner) {\n      if (permissions.permission & Permissions::OwnerWrite) {\n         return true;\n      }\n   }\n\n   if (user.group == permissions.group) {\n      if (permissions.permission & Permissions::GroupWrite) {\n         return true;\n      }\n   }\n\n   if (permissions.permission & Permissions::OtherWrite) {\n      return true;\n   }\n\n   return false;\n}\n\nbool\nHostDevice::checkExecutePermission(const User &user,\n                                   const Path &path)\n{\n   auto itr = mPermissionsCache.find(path.path());\n   if (itr == mPermissionsCache.end()) {\n      // No permissions mapped for this file, assume valid.\n      return true;\n   }\n\n   auto &permissions = itr->second;\n   if (user.id == permissions.owner) {\n      if (permissions.permission & Permissions::OwnerExecute) {\n         return true;\n      }\n   }\n\n   if (user.group == permissions.group) {\n      if (permissions.permission & Permissions::GroupExecute) {\n         return true;\n      }\n   }\n\n   if (permissions.permission & Permissions::OtherExecute) {\n      return true;\n   }\n\n   return false;\n}\n\nstd::filesystem::path\nHostDevice::makeHostPath(const Path &guestPath) const\n{\n   return mHostPath / guestPath.path();\n}\n\nError\nHostDevice::translateError(const std::error_code &ec) const\n{\n   if (!ec) {\n      return Error::Success;\n   }\n\n   if (ec == std::errc::no_such_file_or_directory) {\n      return Error::NotFound;\n   } else if (ec == std::errc::file_exists) {\n      return Error::AlreadyExists;\n   } else if (ec == std::errc::is_a_directory) {\n      return Error::NotFile;\n   } else if (ec == std::errc::not_a_directory) {\n      return Error::NotDirectory;\n   } else if (ec == std::errc::operation_not_supported) {\n      return Error::OperationNotSupported;\n   } else if (ec == std::errc::permission_denied) {\n      return Error::Permission;\n   } else if (ec == std::errc::read_only_file_system) {\n      return Error::ReadOnly;\n   }\n\n   return Error::GenericError;\n}\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_host_device.h",
    "content": "#pragma once\n#include \"vfs_device.h\"\n\n#include <filesystem>\n#include <map>\n#include <memory>\n#include <string>\n#include <system_error>\n\nnamespace vfs\n{\n\nstruct HostNodePermission\n{\n   GroupId group;\n   OwnerId owner;\n   Permissions permission;\n};\n\nclass VirtualDevice;\n\nclass HostDevice : public Device, public std::enable_shared_from_this<HostDevice>\n{\npublic:\n   HostDevice(std::filesystem::path path);\n   ~HostDevice() override = default;\n\n   Result<std::shared_ptr<Device>>\n   getLinkDevice(const User &user, const Path &path) override;\n\n   Error\n   makeFolder(const User &user, const Path &path) override;\n\n   Error\n   makeFolders(const User &user, const Path &path) override;\n\n   Error\n   mountDevice(const User &user, const Path &path,\n               std::shared_ptr<Device> device) override;\n\n   Error\n   mountOverlayDevice(const User &user, OverlayPriority priority,\n                      const Path &path,\n                      std::shared_ptr<Device> device) override;\n\n   Error\n   unmountDevice(const User &user, const Path &path) override;\n\n   Error\n   unmountOverlayDevice(const User &user, OverlayPriority priority,\n                        const Path &path) override;\n\n   Result<DirectoryIterator>\n   openDirectory(const User &user, const Path &path) override;\n\n   Result<std::unique_ptr<FileHandle>>\n   openFile(const User &user, const Path &path,\n            FileHandle::Mode mode) override;\n\n   Error\n   remove(const User &user, const Path &path) override;\n\n   Error\n   rename(const User &user, const Path &src, const Path &dst) override;\n\n   Error\n   setGroup(const User &user, const Path &path, GroupId group) override;\n\n   Error\n   setOwner(const User &user, const Path &path, OwnerId owner) override;\n\n   Error\n   setPermissions(const User &user, const Path &path, Permissions mode) override;\n\n   Result<Status>\n   status(const User &user, const Path &path) override;\n\n   Result<HostNodePermission>\n   lookupPermissions(const Path &path) const;\n\nprivate:\n   bool checkReadPermission(const User &user, const Path &path);\n   bool checkWritePermission(const User &user, const Path &path);\n   bool checkExecutePermission(const User &user, const Path &path);\n\n   std::filesystem::path makeHostPath(const Path &guestPath) const;\n\n   Error translateError(const std::error_code &ec) const;\n   Error translateError(const std::system_error &ec) const;\n\nprivate:\n   std::filesystem::path mHostPath;\n   std::map<std::string, HostNodePermission> mPermissionsCache;\n\n   //! We want a virtual device backing this host device so we can do things\n   //! like mount other devices within a host device. For example this could\n   //! be useful if we have MLC / SLC on host device and we want to mount\n   //! installed games / patches within there when the user stores their data\n   //! external to these system paths.\n   std::shared_ptr<VirtualDevice> mVirtualDevice;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_host_directoryiterator.cpp",
    "content": "#include \"vfs_host_directoryiterator.h\"\n#include \"vfs_host_device.h\"\n\nnamespace vfs\n{\n\nHostDirectoryIterator::HostDirectoryIterator(HostDevice *hostDevice,\n                                             std::vector<Status> listing) :\n   mHostDevice(hostDevice),\n   mListing(std::move(listing)),\n   mIterator(mListing.begin())\n{\n}\n\nResult<Status>\nHostDirectoryIterator::readEntry()\n{\n   if (mIterator == mListing.end()) {\n      return { Error::EndOfDirectory };\n   }\n\n   auto currentEntry = *mIterator;\n   ++mIterator;\n   return { currentEntry };\n}\n\nError\nHostDirectoryIterator::rewind()\n{\n   mIterator = mListing.begin();\n   return Error::Success;\n}\n\n} // namespace vfs"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_host_directoryiterator.h",
    "content": "#pragma once\n#include \"vfs_directoryiterator.h\"\n\n#include <filesystem>\n#include <vector>\n\nnamespace vfs\n{\n\nclass HostDevice;\n\nclass HostDirectoryIterator : public DirectoryIteratorImpl\n{\npublic:\n   HostDirectoryIterator(HostDevice *hostDevice,\n                         std::vector<Status> listing);\n   ~HostDirectoryIterator() override = default;\n\n   Result<Status> readEntry() override;\n   Error rewind() override;\n\nprivate:\n   class HostDevice *mHostDevice;\n   std::vector<Status> mListing;\n   std::vector<Status>::iterator mIterator;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_host_filehandle.cpp",
    "content": "#include \"vfs_host_filehandle.h\"\n\n#include <common/platform.h>\n\n#ifdef PLATFORM_WINDOWS\n#include <io.h>\n#elif defined(PLATFORM_POSIX)\n#include <unistd.h>\n#include <sys/types.h>\n#endif\n\nnamespace vfs\n{\n\nHostFileHandle::HostFileHandle(FILE *handle, Mode mode) :\n   mHandle(handle),\n   mMode(mode)\n{\n}\n\nHostFileHandle::~HostFileHandle()\n{\n   close();\n}\n\nError\nHostFileHandle::close()\n{\n   if (mHandle) {\n      fclose(mHandle);\n      mHandle = nullptr;\n   }\n\n   return Error::Success;\n}\n\nResult<bool>\nHostFileHandle::eof()\n{\n   if (!mHandle) {\n      return { Error::NotOpen };\n   }\n\n   return { !!feof(mHandle) };\n}\n\nError\nHostFileHandle::flush()\n{\n   if (!mHandle) {\n      return Error::NotOpen;\n   }\n\n   fflush(mHandle);\n   return Error::Success;\n}\n\nError\nHostFileHandle::seek(SeekDirection direction, int64_t offset)\n{\n   if (!mHandle) {\n      return Error::NotOpen;\n   }\n\n   int seekDirection = SEEK_SET;\n   switch (direction) {\n   case SeekCurrent:\n      seekDirection = SEEK_CUR;\n      break;\n   case SeekEnd:\n      seekDirection = SEEK_END;\n      break;\n   case SeekStart:\n      seekDirection = SEEK_SET;\n      break;\n   default:\n      return Error::InvalidSeekDirection;\n   }\n\n\n#ifdef PLATFORM_WINDOWS\n   if (_fseeki64(mHandle, offset, seekDirection) != 0) {\n      // TODO: Translate ERRNO ?\n      return Error::GenericError;\n   }\n#elif defined(PLATFORM_POSIX)\n   if (fseeko(mHandle, offset, seekDirection) != 0) {\n      // TODO: Translate ERRNO ?\n      return Error::GenericError;\n   }\n#else\n   return Error::OperationNotSupported;\n#endif\n\n   return Error::Success;\n}\n\nResult<int64_t>\nHostFileHandle::size()\n{\n   if (!mHandle) {\n      return { Error::NotOpen };\n   }\n\n   auto position = tell();\n   if (!position) {\n      return { position.error() };\n   }\n\n   auto error = seek(SeekEnd, 0);\n   if (error != Error::Success) {\n      return { position.error() };\n   }\n\n   auto size = tell();\n   if (!size) {\n      return { size.error() };\n   }\n\n   seek(SeekStart, *position);\n   return size;\n}\n\nResult<int64_t>\nHostFileHandle::tell()\n{\n   if (!mHandle) {\n      return Error::NotOpen;\n   }\n\n#ifdef PLATFORM_WINDOWS\n   auto position = _ftelli64(mHandle);\n   if (position < 0) {\n      // TODO: Translate error?\n      return { Error::GenericError };\n   }\n#elif defined(PLATFORM_POSIX)\n   auto position = ftello(mHandle);\n   if (position < 0) {\n      // TODO: Translate error?\n      return { Error::GenericError };\n   }\n#else\n   return Error::OperationNotSupported;\n#endif\n\n   return { static_cast<int64_t>(position) };\n}\n\nResult<int64_t>\nHostFileHandle::truncate()\n{\n   if (!mHandle) {\n      return { Error::NotOpen };\n   }\n\n   // TODO: Check open mode for read only\n\n   auto length = tell();\n   if (!length) {\n      return length.error();\n   }\n\n#ifdef PLATFORM_WINDOWS\n   if (_chsize_s(_fileno(mHandle), static_cast<long long>(*length)) != 0) {\n      // TODO: Translate error?\n      return { Error::GenericError };\n   }\n#elif defined(PLATFORM_POSIX)\n   fflush(mHandle);\n   if (ftruncate(fileno(mHandle), static_cast<off_t>(*length)) != 0) {\n      // TODO: Translate error?\n      return { Error::GenericError };\n   }\n#else\n   return Error::OperationNotSupported;\n#endif\n\n   return length;\n}\n\nResult<int64_t>\nHostFileHandle::read(void *buffer, int64_t size, int64_t count)\n{\n   return { static_cast<int64_t>(fread(buffer, size, count, mHandle)) };\n}\n\nResult<int64_t>\nHostFileHandle::write(const void *buffer, int64_t size, int64_t count)\n{\n   if (!mHandle) {\n      return { Error::NotOpen };\n   }\n\n   // TODO: Check open mode for read only\n   return { static_cast<int64_t>(fwrite(buffer, size, count, mHandle)) };\n}\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_host_filehandle.h",
    "content": "#pragma once\n#include \"vfs_filehandle.h\"\n\n#include <cstdio>\n\nnamespace vfs\n{\n\nclass HostFileHandle : public FileHandle\n{\npublic:\n   HostFileHandle(FILE *handle, Mode mode);\n   ~HostFileHandle() override;\n\n   Error close() override;\n   Result<bool> eof() override;\n   Error flush() override;\n   Error seek(SeekDirection direction, int64_t offset) override;\n   Result<int64_t> size() override;\n   Result<int64_t> tell() override;\n   Result<int64_t> truncate() override;\n   Result<int64_t> read(void *buffer, int64_t size, int64_t count) override;\n   Result<int64_t> write(const void *buffer, int64_t size, int64_t count) override;\n\nprivate:\n   FILE *mHandle;\n   Mode mMode;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_link_device.cpp",
    "content": "#include \"vfs_link_device.h\"\n\nnamespace vfs\n{\n\nLinkDevice::LinkDevice(std::shared_ptr<Device> device,\n                       Path path) :\n   Device(Device::Link),\n   mDevice(device),\n   mPath(path)\n{\n}\n\nResult<std::shared_ptr<Device>>\nLinkDevice::getLinkDevice(const User &user,\n                          const Path &path)\n{\n   if (path.depth() == 0) {\n      return { shared_from_this() };\n   } else {\n      return mDevice->getLinkDevice(user, mPath / path);\n   }\n}\n\nError\nLinkDevice::makeFolder(const User &user,\n                       const Path &path)\n{\n   return mDevice->makeFolder(user, mPath / path);\n}\n\nError\nLinkDevice::makeFolders(const User &user,\n                        const Path &path)\n{\n   return mDevice->makeFolders(user, mPath / path);\n}\n\nError\nLinkDevice::mountDevice(const User &user,\n                        const Path &path,\n                        std::shared_ptr<Device> device)\n{\n   return mDevice->mountDevice(user, mPath / path, device);\n}\n\nError\nLinkDevice::mountOverlayDevice(const User &user,\n                               OverlayPriority priority,\n                               const Path &path,\n                               std::shared_ptr<Device> device)\n{\n   return mDevice->mountOverlayDevice(user, priority, mPath / path,\n                                      std::move(device));\n}\n\nError\nLinkDevice::unmountDevice(const User &user,\n                          const Path &path)\n{\n   return mDevice->unmountDevice(user, mPath / path);\n}\n\nError\nLinkDevice::unmountOverlayDevice(const User &user,\n                                 OverlayPriority priority,\n                                 const Path &path)\n{\n   return mDevice->unmountOverlayDevice(user, priority, mPath / path);\n}\n\nResult<DirectoryIterator>\nLinkDevice::openDirectory(const User &user,\n                          const Path &path)\n{\n   return mDevice->openDirectory(user, mPath / path);\n}\n\nResult<std::unique_ptr<FileHandle>>\nLinkDevice::openFile(const User &user,\n                     const Path &path,\n                     FileHandle::Mode mode)\n{\n   return mDevice->openFile(user, mPath / path, mode);\n}\n\nError\nLinkDevice::remove(const User &user,\n                   const Path &path)\n{\n   return mDevice->remove(user, mPath / path);\n}\n\nError\nLinkDevice::rename(const User &user,\n                   const Path &src,\n                   const Path &dst)\n{\n   return mDevice->rename(user, mPath / src, mPath / dst);\n}\n\nError\nLinkDevice::setGroup(const User &user,\n                     const Path &path,\n                     GroupId group)\n{\n   return mDevice->setGroup(user, mPath / path, group);\n}\n\nError\nLinkDevice::setOwner(const User &user,\n                     const Path &path,\n                     OwnerId owner)\n{\n   return mDevice->setOwner(user, mPath / path, owner);\n}\n\nError\nLinkDevice::setPermissions(const User &user,\n                           const Path &path,\n                           Permissions mode)\n{\n   return mDevice->setPermissions(user, mPath / path, mode);\n}\n\nResult<Status>\nLinkDevice::status(const User &user,\n                   const Path &path)\n{\n   return mDevice->status(user, mPath / path);\n}\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_link_device.h",
    "content": "#pragma once\n#include \"vfs_device.h\"\n\nnamespace vfs\n{\n\nclass LinkDevice : public Device, public std::enable_shared_from_this<LinkDevice>\n{\npublic:\n   LinkDevice(std::shared_ptr<Device> device, Path path);\n   ~LinkDevice() override = default;\n\n   Result<std::shared_ptr<Device>>\n   getLinkDevice(const User &user, const Path &path) override;\n\n   Error\n   makeFolder(const User &user, const Path &path) override;\n\n   Error\n   makeFolders(const User &user, const Path &path) override;\n\n   Error\n   mountDevice(const User &user, const Path &path,\n               std::shared_ptr<Device> device) override;\n\n   Error\n   mountOverlayDevice(const User &user, OverlayPriority priority,\n                      const Path &path,\n                      std::shared_ptr<Device> device) override;\n\n   Error\n   unmountDevice(const User &user, const Path &path) override;\n\n   Error\n   unmountOverlayDevice(const User &user,\n                        OverlayPriority priority,\n                        const Path &path) override;\n\n   Result<DirectoryIterator>\n   openDirectory(const User &user, const Path &path) override;\n\n   Result<std::unique_ptr<FileHandle>>\n   openFile(const User &user, const Path &path,\n            FileHandle::Mode mode) override;\n\n   Error\n   remove(const User &user, const Path &path) override;\n\n   Error\n   rename(const User &user, const Path &src, const Path &dst) override;\n\n   Error\n   setGroup(const User &user, const Path &path, GroupId group) override;\n\n   Error\n   setOwner(const User &user, const Path &path, OwnerId owner) override;\n\n   Error\n   setPermissions(const User &user, const Path &path, Permissions mode) override;\n\n   Result<Status>\n   status(const User &user, const Path &path) override;\n\nprivate:\n   std::shared_ptr<Device> mDevice;\n   Path mPath;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_overlay_device.cpp",
    "content": "#include \"vfs_overlay_device.h\"\n#include \"vfs_overlay_directoryiterator.h\"\n\n#include <algorithm>\n\nnamespace vfs\n{\n\nOverlayDevice::OverlayDevice() :\n   Device(Device::Overlay)\n{\n}\n\nResult<std::shared_ptr<Device>>\nOverlayDevice::getLinkDevice(const User &user,\n                             const Path &path)\n{\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->getLinkDevice(user, path);\n      if (result.error() != Error::NotFound) {\n         return result;\n      }\n   }\n\n   return { Error::NotFound };\n}\n\nError\nOverlayDevice::makeFolder(const User &user,\n                          const Path &path)\n{\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->makeFolder(user, path);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nError\nOverlayDevice::makeFolders(const User &user,\n                           const Path &path)\n{\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->makeFolders(user, path);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nError\nOverlayDevice::mountDevice(const User &user,\n                           const Path &path,\n                           std::shared_ptr<Device> device)\n{\n   if (path.depth() == 0) {\n      // Should be using mountOverlayDevice, not mountDevice...\n      return Error::OperationNotSupported;\n   }\n\n   for (auto &[basePriority, baseDevice] : mDevices) {\n      auto result = baseDevice->mountDevice(user, path, device);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nError\nOverlayDevice::mountOverlayDevice(const User &user,\n                                  OverlayPriority priority,\n                                  const Path &path,\n                                  std::shared_ptr<Device> device)\n{\n   if (path.depth() == 0) {\n      for (auto itr = mDevices.begin(); itr != mDevices.end(); ++itr) {\n         if (itr->first == priority) {\n            return Error::AlreadyExists;\n         }\n      }\n\n      mDevices.emplace_back(priority, std::move(device));\n      std::sort(mDevices.begin(), mDevices.end(),\n                [](const auto &lhs, const auto &rhs)\n                {\n                   return lhs.first > rhs.first;\n                });\n      return Error::Success;\n   }\n\n   for (auto &[basePriority, baseDevice] : mDevices) {\n      auto result = baseDevice->mountOverlayDevice(user, priority, path, device);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nError\nOverlayDevice::unmountDevice(const User &user,\n                             const Path &path)\n{\n   if (mDevices.empty()) {\n      return Error::NotFound;\n   }\n\n   if (path.depth() == 0) {\n      // Should be using unmountOverlayDevice, not mountDevice...\n      return Error::OperationNotSupported;\n   }\n\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->unmountDevice(user, path);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nError\nOverlayDevice::unmountOverlayDevice(const User &user,\n                                    OverlayPriority priority,\n                                    const Path &path)\n{\n   if (path.depth() == 0) {\n      for (auto itr = mDevices.begin(); itr != mDevices.end(); ++itr) {\n         if (itr->first == priority) {\n            mDevices.erase(itr);\n            return Error::Success;\n         }\n      }\n\n      return Error::NotFound;\n   }\n\n   for (auto &[basePriority, baseDevice] : mDevices) {\n      auto result = baseDevice->unmountOverlayDevice(user, priority, path);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nResult<DirectoryIterator>\nOverlayDevice::openDirectory(const User &user,\n                             const Path &path)\n{\n   auto error = Error { };\n   auto iterator = std::make_shared<OverlayDirectoryIterator>(user, path, this, error);\n   if (error != Error::Success) {\n      return { error };\n   }\n\n   return { DirectoryIterator { std::move(iterator) } };\n}\n\nResult<std::unique_ptr<FileHandle>>\nOverlayDevice::openFile(const User &user,\n                        const Path &path,\n                        FileHandle::Mode mode)\n{\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->openFile(user, path, mode);\n      if (result.error() != Error::NotFound) {\n         return result;\n      }\n   }\n\n   return { Error::NotFound };\n}\n\nError\nOverlayDevice::remove(const User &user,\n                      const Path &path)\n{\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->remove(user, path);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nError\nOverlayDevice::rename(const User &user,\n                      const Path &src,\n                      const Path &dst)\n{\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->rename(user, src, dst);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nError\nOverlayDevice::setGroup(const User &user,\n                        const Path &path,\n                        GroupId group)\n{\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->setGroup(user, path, group);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nError\nOverlayDevice::setOwner(const User &user,\n                        const Path &path,\n                        OwnerId owner)\n{\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->setOwner(user, path, owner);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nError\nOverlayDevice::setPermissions(const User &user,\n                              const Path &path,\n                              Permissions mode)\n{\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->setPermissions(user, path, mode);\n      if (result == Error::Success) {\n         return result;\n      }\n   }\n\n   return Error::NotFound;\n}\n\nResult<Status>\nOverlayDevice::status(const User &user,\n                      const Path &path)\n{\n   for (auto &[priority, device] : mDevices) {\n      auto result = device->status(user, path);\n      if (result.error() != Error::NotFound) {\n         return result;\n      }\n   }\n\n   return { Error::NotFound };\n}\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_overlay_device.h",
    "content": "#pragma once\n#include \"vfs_device.h\"\n#include \"vfs_permissions.h\"\n#include \"vfs_result.h\"\n\n#include <memory>\n#include <vector>\n#include <utility>\n\nnamespace vfs\n{\n\nclass OverlayDevice : public Device\n{\n   using device_list = std::vector<std::pair<OverlayPriority, std::shared_ptr<Device>>>;\n\npublic:\n   using iterator = device_list::iterator;\n   using const_iterator = device_list::const_iterator;\n\n   OverlayDevice();\n   ~OverlayDevice() override = default;\n\n   Result<std::shared_ptr<Device>>\n   getLinkDevice(const User &user, const Path &path) override;\n\n   Error\n   makeFolder(const User &user, const Path &path) override;\n\n   Error\n   makeFolders(const User &user, const Path &path) override;\n\n   Error\n   mountDevice(const User &user, const Path &path,\n               std::shared_ptr<Device> device) override;\n\n   Error\n   mountOverlayDevice(const User &user, OverlayPriority priority,\n                      const Path &path,\n                      std::shared_ptr<Device> device) override;\n\n   Error\n   unmountDevice(const User &user, const Path &path) override;\n\n   Error\n   unmountOverlayDevice(const User &user, OverlayPriority priority,\n                        const Path &path) override;\n\n   Result<DirectoryIterator>\n   openDirectory(const User &user, const Path &path) override;\n\n   Result<std::unique_ptr<FileHandle>>\n   openFile(const User &user, const Path &path,\n            FileHandle::Mode mode) override;\n\n   Error\n   remove(const User &user, const Path &path) override;\n\n   Error\n   rename(const User &user, const Path &src, const Path &dst) override;\n\n   Error\n   setGroup(const User &user, const Path &path, GroupId group) override;\n\n   Error\n   setOwner(const User &user, const Path &path, OwnerId owner) override;\n\n   Error\n   setPermissions(const User &user, const Path &path, Permissions mode) override;\n\n   Result<Status>\n   status(const User &user, const Path &path) override;\n\n   iterator begin()\n   {\n      return mDevices.begin();\n   }\n\n   iterator end()\n   {\n      return mDevices.end();\n   }\n\n   const_iterator begin() const\n   {\n      return mDevices.begin();\n   }\n\n   const_iterator end() const\n   {\n      return mDevices.end();\n   }\n\nprivate:\n   device_list mDevices;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_overlay_directoryiterator.cpp",
    "content": "#include \"vfs_overlay_directoryiterator.h\"\n\nnamespace vfs\n{\n\nOverlayDirectoryIterator::OverlayDirectoryIterator(User user,\n                                                   Path path,\n                                                   OverlayDevice *overlayDevice,\n                                                   Error &error) :\n   mUser(std::move(user)),\n   mPath(std::move(path)),\n   mDevice(overlayDevice)\n{\n   error = rewind();\n}\n\nResult<Status>\nOverlayDirectoryIterator::readEntry()\n{\n   while (mDeviceIterator != mDevice->end()) {\n      if (!mIterator.has_value()) {\n         auto result = mDeviceIterator->second->openDirectory(mUser, mPath);\n         if (!result) {\n            mDeviceIterator++;\n            continue;\n         }\n\n         mIterator = std::move(*result);\n      }\n\n      auto result = mIterator->readEntry();\n      if (result) {\n         return result;\n      }\n\n      if (result.error() == Error::EndOfDirectory) {\n         mIterator.reset();\n         mDeviceIterator++;\n         continue;\n      }\n   }\n\n   return Error::EndOfDirectory;\n}\n\nError\nOverlayDirectoryIterator::rewind()\n{\n   mDeviceIterator = mDevice->begin();\n   mIterator.reset();\n\n   // Find first device which can open directory\n   while (mDeviceIterator != mDevice->end()) {\n      auto result = mDeviceIterator->second->openDirectory(mUser, mPath);\n      if (!result) {\n         mDeviceIterator++;\n         continue;\n      }\n\n      mIterator = std::move(*result);\n      break;\n   }\n\n   return mIterator.has_value() ? Error::Success : Error::NotFound;\n}\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_overlay_directoryiterator.h",
    "content": "#pragma once\n#include \"vfs_directoryiterator.h\"\n#include \"vfs_overlay_device.h\"\n#include \"vfs_path.h\"\n\n#include <optional>\n\nnamespace vfs\n{\n\nclass OverlayDirectoryIterator : public DirectoryIteratorImpl\n{\npublic:\n   OverlayDirectoryIterator(User user,\n                            Path path,\n                            OverlayDevice *overlayDevice,\n                            Error &error);\n   ~OverlayDirectoryIterator() override = default;\n\n   Result<Status> readEntry() override;\n   Error rewind() override;\n\nprivate:\n   User mUser;\n   Path mPath;\n   OverlayDevice *mDevice;\n   OverlayDevice::iterator mDeviceIterator;\n   std::optional<DirectoryIterator> mIterator;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_path.cpp",
    "content": "#include \"vfs_path.h\"\n#include \"vfs_pathiterator.h\"\n\n#include <algorithm>\n#include <list>\n#include <string_view>\n\nnamespace vfs\n{\n\nstatic std::string normalisePath(std::string_view path);\n\nPath::Path(const std::string &path) :\n   mPath(normalisePath(std::string_view { path }))\n{\n}\n\nPath::Path(const char *path) :\n   mPath(normalisePath(std::string_view { path }))\n{\n}\n\nPath::Path(std::string_view path) :\n   mPath(normalisePath(path))\n{\n}\n\nPath::Path(PathIterator first, PathIterator last) :\n   mPath()\n{\n   auto size = static_cast<size_t>(std::distance(first.mPosition, last.mPosition));\n   if (size) {\n      mPath = normalisePath(std::string_view { &*first.mPosition, size });\n   }\n}\n\nvoid\nPath::clear()\n{\n   mPath.clear();\n}\n\nsize_t\nPath::depth() const\n{\n   if (mPath.empty()) {\n      return 0;\n   }\n\n   return 1 + std::count(mPath.begin(), mPath.end(), '/');\n}\n\nbool\nPath::empty() const\n{\n   return mPath.empty();\n}\n\nconst std::string &\nPath::path() const\n{\n   return mPath;\n}\n\nPathIterator\nPath::begin() const\n{\n   if (!mPath.empty()) {\n      auto firstBegin = mPath.begin();\n      auto firstEnd = std::find(mPath.begin(), mPath.end(), '/');\n\n      if (firstBegin == firstEnd) {\n         return PathIterator { this, mPath.begin(),\n                               std::string_view { &*firstBegin, 1 } };\n      } else {\n         auto size = static_cast<size_t>(firstEnd - firstBegin);\n         return PathIterator { this, mPath.begin(),\n                               std::string_view { &*firstBegin, size } };\n      }\n   }\n\n   return PathIterator { this, mPath.begin() };\n}\n\nPathIterator\nPath::end() const\n{\n   return PathIterator { this, mPath.end() };\n}\n\nPath\nPath::operator /(const Path &path) const\n{\n   return Path(mPath + \"/\" + path.mPath);\n}\n\nstd::string\nnormalisePath(std::string_view path)\n{\n   // 1) If the path is empty, stop\n   if (path.empty()) {\n      return {};\n   }\n\n   // 2) Replace each directory-separator (which may consist of multiple\n   //    slashes) with a single path::preferred_separator.\n   auto expanded = std::list<std::string_view> { };\n   for (auto itr = path.begin(); itr != path.end(); ) {\n      if (*itr == '/') {\n         if (expanded.empty() || !expanded.back().empty()) {\n            expanded.emplace_back();\n         }\n\n         ++itr;\n      } else {\n         auto end = std::find(itr + 1, path.end(), '/');\n         expanded.emplace_back(&*itr, static_cast<size_t>(end - itr));\n         itr = end;\n      }\n   }\n\n   // 3) Replace each slash character in the root-name with\n   //    path::preferred_separator.\n   // Nothing to do for us.\n\n   // 4) Remove each dot and any immediately following directory-separator.\n   for (auto itr = expanded.begin(); itr != expanded.end(); ) {\n      if (*itr == \".\") {\n         // Erase dot filename.\n         itr = expanded.erase(itr);\n\n         if (itr != expanded.end()) {\n            // Erase immediately following directory-separator.\n            itr = expanded.erase(itr);\n         }\n      } else {\n         ++itr;\n      }\n   }\n\n   // 5) Remove each non-dot-dot filename immediately followed by a\n   //    directory-separator and a dot-dot, along with any immediately\n   //    following directory-separator.\n   for (auto itr = expanded.begin(); itr != expanded.end(); ) {\n      auto start = itr;\n      ++itr;\n\n      if (*start == \"..\" &&\n            start != expanded.begin() &&\n            --start != expanded.begin() &&\n            *--start != \"..\") {\n         if (itr != expanded.end()) {\n            // dot-dot filename has following directory-separator\n            ++itr;\n         }\n\n         expanded.erase(start, itr);\n      }\n   }\n\n   // 6) If there is root-directory, remove all dot-dots and any\n   //    directory-separators immediately following them.\n   if (!expanded.empty() && expanded.front().empty()) {\n      for (auto itr = expanded.begin(); itr != expanded.end(); ) {\n         if (*itr == \"..\") {\n            // Erase dot-dot filename.\n            itr = expanded.erase(itr);\n\n            if (itr != expanded.end()) {\n               // Erase immediately following directory-separator.\n               itr = expanded.erase(itr);\n            }\n         } else {\n            break;\n         }\n      }\n   }\n\n   // 7) If the last filename is dot-dot, remove any trailing directory-separator.\n   if (expanded.size() >= 2 &&\n         expanded.back().empty() &&\n         *prev(expanded.end(), 2) == \"..\") {\n      expanded.pop_back();\n   }\n\n   // Remove trailing separator\n   while (expanded.size() > 1 && expanded.back().empty()) {\n      expanded.pop_back();\n   }\n\n   // Flatten into normalised path\n   std::string normalised;\n\n   for (auto itr = expanded.begin(); itr != expanded.end(); ++itr) {\n      if (itr->empty()) {\n         normalised.push_back('/');\n      } else {\n         normalised.append(*itr);\n      }\n   }\n\n   // 8) If the path is empty, add a dot (normal form of ./ is .)\n   if (normalised.empty()) {\n      normalised = \".\";\n   }\n\n   return normalised;\n}\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_path.h",
    "content": "#pragma once\n#include <string>\n\nnamespace vfs\n{\n\nclass PathIterator;\n\nclass Path\n{\n   friend class PathIterator;\n\npublic:\n   Path() = default;\n   Path(const std::string &path);\n   Path(const char *path);\n   Path(std::string_view path);\n   Path(PathIterator first, PathIterator last);\n\n   void clear();\n   size_t depth() const;\n   bool empty() const;\n   const std::string &path() const;\n\n   PathIterator begin() const;\n   PathIterator end() const;\n\n   Path operator /(const Path &path) const;\n\nprivate:\n   std::string mPath;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_pathiterator.cpp",
    "content": "#include \"vfs_path.h\"\n#include \"vfs_pathiterator.h\"\n\n#include <algorithm>\n\nnamespace vfs\n{\n\nPathIterator::PathIterator(const Path *path,\n                           std::string::const_iterator position) :\n   mPath(path),\n   mPosition(position),\n   mElement()\n{\n}\n\nPathIterator::PathIterator(const Path *path,\n                           std::string::const_iterator position,\n                           std::string_view element) :\n   mPath(path),\n   mPosition(position),\n   mElement(element)\n{\n}\n\nPathIterator::reference PathIterator::operator *() const\n{\n   return mElement;\n}\n\nconst Path *PathIterator::operator ->() const\n{\n   return &mElement;\n}\n\nPathIterator &PathIterator::operator++()\n{\n   auto pathEnd = mPath->mPath.end();\n   mPosition += mElement.mPath.size();\n\n   if (mPosition == pathEnd) {\n      mElement.clear();\n      return *this;\n   } else {\n      if (*mPosition == '/') {\n         mPosition++;\n      }\n\n      auto elementStart = mPosition;\n      auto elementEnd = std::find(mPosition, pathEnd, '/');\n      mElement.mPath.assign(elementStart, elementEnd);\n   }\n\n   return *this;\n}\n\nPathIterator\nPathIterator::operator++(int)\n{\n   auto prev = *this;\n   ++*this;\n   return prev;\n}\n\nPathIterator &\nPathIterator::operator--()\n{\n   auto pathBegin = mPath->mPath.begin();\n   auto pathEnd = mPath->mPath.end();\n   auto elementEnd = (mPosition == pathEnd) ? (pathEnd) : (mPosition - 1);\n   mPosition = elementEnd - 1;\n\n   while (mPosition > pathBegin) {\n      if (*mPosition == '/') {\n         ++mPosition;\n         break;\n      }\n\n      --mPosition;\n   }\n\n   mElement.mPath.assign(mPosition, elementEnd);\n   return *this;\n}\n\nPathIterator\nPathIterator::operator--(int)\n{\n   auto prev = *this;\n   --*this;\n   return prev;\n}\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_pathiterator.h",
    "content": "#pragma once\n#include \"vfs_path.h\"\n\n#include <cstddef>\n#include <iterator>\n\nnamespace vfs\n{\n\nclass PathIterator\n{\n   friend class Path;\n\npublic:\n   using iterator_category = std::bidirectional_iterator_tag;\n   using value_type = Path;\n   using difference_type = std::ptrdiff_t;\n   using pointer = const Path *;\n   using reference = const Path &;\n\n   PathIterator() = default;\n   PathIterator(const Path *path,\n                std::string::const_iterator position);\n   PathIterator(const Path *path,\n                std::string::const_iterator position,\n                std::string_view element);\n\n   PathIterator(const PathIterator &) = default;\n   PathIterator(PathIterator &&) = default;\n   PathIterator &operator=(const PathIterator &) = default;\n   PathIterator &operator=(PathIterator &&) = default;\n\n   reference operator *() const;\n   const Path *operator ->() const;\n\n   PathIterator &operator++();\n   PathIterator operator++(int);\n\n   PathIterator &operator--();\n   PathIterator operator--(int);\n\n   friend bool operator ==(const PathIterator &lhs, const PathIterator &rhs)\n   {\n      return lhs.mPosition == rhs.mPosition;\n   }\n\n   friend bool operator !=(const PathIterator &lhs, const PathIterator &rhs)\n   {\n      return lhs.mPosition != rhs.mPosition;\n   }\n\nprivate:\n   const Path *mPath = nullptr;\n   std::string::const_iterator mPosition;\n   Path mElement;\n};\n\nPathIterator begin(Path &path);\nPathIterator end(Path &path);\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_permissions.h",
    "content": "#pragma once\n\nnamespace vfs\n{\n\nusing GroupId = unsigned int;\nusing OwnerId = unsigned int;\nusing OverlayPriority = int;\n\nenum Permissions\n{\n   NoPermissions = 0,\n\n   OtherExecute = 0x001,\n   OtherWrite = 0x002,\n   OtherRead = 0x004,\n\n   GroupExecute = 0x010,\n   GroupWrite = 0x020,\n   GroupRead = 0x040,\n\n   OwnerExecute = 0x100,\n   OwnerWrite = 0x200,\n   OwnerRead = 0x400,\n};\n\ninline Permissions operator |(Permissions lhs, Permissions rhs)\n{\n   return static_cast<Permissions>(static_cast<int>(lhs) | static_cast<int>(rhs));\n}\n\ninline Permissions operator &(Permissions lhs, Permissions rhs)\n{\n   return static_cast<Permissions>(static_cast<int>(lhs) & static_cast<int>(rhs));\n}\n\nstruct User\n{\n   OwnerId id = 0;\n   GroupId group = 0;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_result.h",
    "content": "#pragma once\n#include \"vfs_error.h\"\n#include <utility>\n\nnamespace vfs\n{\n\ntemplate<typename ValueType>\nclass Result\n{\npublic:\n   Result(ValueType value) :\n      mError(Error::Success),\n      mValue(std::move(value))\n   {\n   }\n\n   Result(Error error) :\n      mError(error)\n   {\n   }\n\n   Error error() const\n   {\n      return mError;\n   }\n\n   explicit operator bool() const\n   {\n      return mError == Error::Success;\n   }\n\n   ValueType &operator *()\n   {\n      return mValue;\n   }\n\n   const ValueType &operator *() const\n   {\n      return mValue;\n   }\n\n   ValueType *operator ->()\n   {\n      return &mValue;\n   }\n\n   const ValueType *operator ->() const\n   {\n      return &mValue;\n   }\n\nprivate:\n   Error mError;\n   ValueType mValue;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_status.h",
    "content": "#pragma once\n#include \"vfs_permissions.h\"\n\n#include <cstdint>\n#include <string>\n\nnamespace vfs\n{\n\nstruct Status\n{\n   enum Flags\n   {\n      None           = 0,\n      HasSize        = 1 << 0,\n      HasPermissions = 1 << 1,\n      IsDirectory    = 1 << 2,\n   };\n\n   std::string name;\n   GroupId group = 0;\n   OwnerId owner = 0;\n   Permissions permission = Permissions::NoPermissions;\n   std::uintmax_t size = 0;\n   int flags = Flags::None;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_virtual_device.cpp",
    "content": "#include \"vfs_virtual_device.h\"\n#include \"vfs_virtual_directory.h\"\n#include \"vfs_virtual_directoryiterator.h\"\n#include \"vfs_virtual_file.h\"\n#include \"vfs_virtual_filehandle.h\"\n#include \"vfs_virtual_mounteddevice.h\"\n#include \"vfs_link_device.h\"\n#include \"vfs_overlay_device.h\"\n#include \"vfs_pathiterator.h\"\n\n#include <cassert>\n\nnamespace vfs\n{\n\nVirtualDevice::VirtualDevice(std::string rootDeviceName) :\n   Device(Device::Virtual),\n   mRootDeviceName(std::move(rootDeviceName)),\n   mRoot(std::make_shared<VirtualDirectory>())\n{\n}\n\nResult<std::shared_ptr<Device>>\nVirtualDevice::getLinkDevice(const User &user,\n                             const Path &path)\n{\n   // TODO: This can be optimised to get a link device from the deepest path\n   return { std::make_shared<LinkDevice>(shared_from_this(), path) };\n}\n\nError\nVirtualDevice::makeFolder(const User &user,\n                          const Path &path)\n{\n   auto [node, relativePath] = findDeepest(user, path);\n   if (relativePath.empty()) {\n      return Error::AlreadyExists;\n   }\n\n   if (node->type == VirtualNode::MountedDevice) {\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n      return mountedDevice->device->makeFolder(user, relativePath);\n   }\n\n   if (node->type == VirtualNode::File) {\n      return Error::NotDirectory;\n   }\n\n   if (relativePath.depth() > 1) {\n      return Error::NotFound;\n   }\n\n   assert(node->type == VirtualNode::Directory);\n   auto parentDirectory = static_cast<VirtualDirectory *>(node.get());\n   parentDirectory->children.emplace(relativePath.path(),\n                                     std::make_shared<VirtualDirectory>());\n   return Error::Success;\n}\n\nError\nVirtualDevice::makeFolders(const User &user,\n                           const Path &path)\n{\n   auto [node, relativePath] = findDeepest(user, path);\n   if (relativePath.empty()) {\n      return Error::AlreadyExists;\n   }\n\n   if (node->type == VirtualNode::MountedDevice) {\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n      return mountedDevice->device->makeFolder(user, relativePath);\n   }\n\n   if (node->type == VirtualNode::File) {\n      return Error::NotDirectory;\n   }\n\n   auto parentDirectory = static_cast<VirtualDirectory *>(node.get());\n   for (auto &childPath : relativePath) {\n      auto childDirectory = new VirtualDirectory();\n      parentDirectory->children.emplace(childPath.path(), childDirectory);\n      parentDirectory = childDirectory;\n   }\n\n   return Error::Success;\n}\n\nError\nVirtualDevice::mountDevice(const User &user,\n                           const Path &path,\n                           std::shared_ptr<Device> device)\n{\n   auto [node, relativePath] = findDeepest(user, path);\n   if (relativePath.depth() == 0) {\n      return Error::AlreadyExists;\n   }\n\n   if (node->type == VirtualNode::MountedDevice) {\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n      return mountedDevice->device->mountDevice(user, relativePath, device);\n   }\n\n   if (relativePath.depth() > 1) {\n      return Error::NotFound;\n   }\n\n   if (node->type == VirtualNode::File) {\n      return Error::NotDirectory;\n   }\n\n   assert(node->type == VirtualNode::Directory);\n   auto parentDirectory = static_cast<VirtualDirectory *>(node.get());\n   parentDirectory->children.emplace(\n      relativePath.path(),\n      std::make_shared<VirtualMountedDevice>(std::move(device)));\n   return Error::Success;\n}\n\nError\nVirtualDevice::mountOverlayDevice(const User &user,\n                                  OverlayPriority priority,\n                                  const Path &path,\n                                  std::shared_ptr<Device> device)\n{\n   auto[node, relativePath] = findDeepest(user, path);\n   if (node->type == VirtualNode::MountedDevice) {\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n\n      if (relativePath.depth() > 0) {\n         return mountedDevice->device->mountOverlayDevice(user, priority,\n                                                          relativePath,\n                                                          device);\n      }\n\n      if (mountedDevice->device->type() != Device::Overlay) {\n         // Remount the original device under an overlay device with priority 1\n         auto overlayDevice = std::make_shared<OverlayDevice>();\n         overlayDevice->mountOverlayDevice(user, 1, {},\n                                           std::move(mountedDevice->device));\n         mountedDevice->device = std::move(overlayDevice);\n      }\n\n      auto overlayDevice = static_cast<OverlayDevice *>(mountedDevice->device.get());\n      return overlayDevice->mountOverlayDevice(user, priority, {},\n                                               std::move(device));\n   }\n\n   if (relativePath.depth() > 1) {\n      return Error::NotFound;\n   }\n\n   if (node->type == VirtualNode::File) {\n      return Error::NotDirectory;\n   }\n\n   assert(node->type == VirtualNode::Directory);\n   auto parentDirectory = static_cast<VirtualDirectory *>(node.get());\n   auto overlayDevice = std::make_shared<OverlayDevice>();\n   overlayDevice->mountOverlayDevice(user, priority, {}, std::move(device));\n   parentDirectory->children.emplace(relativePath.path(),\n      std::make_shared<VirtualMountedDevice>(std::move(overlayDevice)));\n   return Error::Success;\n}\n\nError\nVirtualDevice::unmountDevice(const User &user,\n                             const Path &path)\n{\n   auto [parentNode, relativePath] = findDeepest(user, path, 1);\n   if (parentNode->type == VirtualNode::MountedDevice) {\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(parentNode.get());\n      return mountedDevice->device->unmountDevice(user, relativePath);\n   }\n\n   if (relativePath.depth() > 1) {\n      return Error::NotFound;\n   }\n\n   if (parentNode->type != VirtualNode::Directory) {\n      return Error::NotDirectory;\n   }\n\n   // Remove device from parent\n   auto parentDirectory = static_cast<VirtualDirectory *>(parentNode.get());\n   parentDirectory->children.erase(relativePath.path());\n   return Error::Success;\n}\n\nError\nVirtualDevice::unmountOverlayDevice(const User &user,\n                                    vfs::OverlayPriority priority,\n                                    const Path &path)\n{\n   auto [node, relativePath] = findDeepest(user, path);\n   if (node->type != VirtualNode::MountedDevice) {\n      return Error::NotMountDevice;\n   }\n\n   auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n   return mountedDevice->device->unmountOverlayDevice(user, priority, relativePath);\n}\n\nResult<DirectoryIterator>\nVirtualDevice::openDirectory(const User &user,\n                             const Path &path)\n{\n   auto [node, relativePath] = findDeepest(user, path);\n   if (node->type == VirtualNode::MountedDevice) {\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n      return mountedDevice->device->openDirectory(user, relativePath);\n   }\n\n   if (relativePath.depth() > 0) {\n      return Error::NotFound;\n   }\n\n   if (node->type != VirtualNode::Directory) {\n      return Error::NotDirectory;\n   }\n\n   auto dir = std::static_pointer_cast<VirtualDirectory>(node);\n   return { DirectoryIterator {\n      std::make_shared<VirtualDirectoryIterator>(std::move(dir)) } };\n}\n\nResult<std::unique_ptr<FileHandle>>\nVirtualDevice::openFile(const User &user,\n                        const Path &path,\n                        FileHandle::Mode mode)\n{\n   auto [node, relativePath] = findDeepest(user, path);\n   if (node->type == VirtualNode::MountedDevice) {\n      if (relativePath.depth() == 0) {\n         return Error::NotFile;\n      }\n\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n      return mountedDevice->device->openFile(user, relativePath, mode);\n   }\n\n   if (relativePath.depth() > 0) {\n      return Error::NotFound;\n   }\n\n   if (node->type != VirtualNode::File) {\n      return Error::NotFile;\n   }\n\n   auto file = std::static_pointer_cast<VirtualFile>(node);\n   return { std::make_unique<VirtualFileHandle>(std::move(file), mode) };\n}\n\nError\nVirtualDevice::remove(const User &user,\n                      const Path &path)\n{\n   auto [parentNode, relativePath] = findDeepest(user, path, 1);\n   if (parentNode->type == VirtualNode::MountedDevice) {\n      // Forward remove into mounted device\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(parentNode.get());\n      return mountedDevice->device->remove(user, relativePath);\n   }\n\n   if (relativePath.depth() > 1) {\n      return Error::NotFound;\n   }\n\n   if (parentNode->type != VirtualNode::Directory) {\n      // Can only erase from a directory\n      return Error::NotDirectory;\n   }\n\n   // Remove device from parent\n   auto parentDirectory = static_cast<VirtualDirectory *>(parentNode.get());\n   parentDirectory->children.erase(relativePath.path());\n   return Error::Success;\n}\n\nError\nVirtualDevice::rename(const User &user,\n                      const Path &src,\n                      const Path &dst)\n{\n   auto [srcParent, srcRelativePath] = findDeepest(user, src, 1);\n   auto [dstParent, dstRelativePath] = findDeepest(user, dst, 1);\n\n   if (srcParent->type == VirtualNode::MountedDevice ||\n       dstParent->type == VirtualNode::MountedDevice) {\n      if (srcParent != dstParent) {\n         // Cannot move across different mounted devices.\n         return Error::OperationNotSupported;\n      }\n\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(srcParent.get());\n      return mountedDevice->device->rename(user, srcRelativePath,\n                                           dstRelativePath);\n   }\n\n   if (srcRelativePath.depth() > 1 || dstRelativePath.depth() > 1) {\n      return Error::NotFound;\n   }\n\n   if (srcParent->type != VirtualNode::Directory ||\n       dstParent->type != VirtualNode::Directory) {\n      // Can only move items between directories..\n      return Error::NotDirectory;\n   }\n\n   // Move node from src parent to dst parent\n   auto srcParentDirectory = static_cast<VirtualDirectory *>(srcParent.get());\n   auto dstParentDirectory = static_cast<VirtualDirectory *>(dstParent.get());\n\n   dstParentDirectory->children[dstRelativePath.path()] =\n      std::move(srcParentDirectory->children[srcRelativePath.path()]);\n   srcParentDirectory->children.erase(srcRelativePath.path());\n   return Error::OperationNotSupported;\n}\n\nError\nVirtualDevice::setGroup(const User &user,\n                        const Path &path,\n                        GroupId group)\n{\n   auto [node, relativePath] = findDeepest(user, path);\n   if (node->type == VirtualNode::MountedDevice) {\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n      return mountedDevice->device->setGroup(user, relativePath, group);\n   }\n\n   if (relativePath.depth() > 0) {\n      return Error::NotFound;\n   }\n\n   node->group = group;\n   return Error::Success;\n}\n\nError\nVirtualDevice::setOwner(const User &user,\n                        const Path &path,\n                        OwnerId owner)\n{\n   auto [node, relativePath] = findDeepest(user, path);\n   if (node->type == VirtualNode::MountedDevice) {\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n      return mountedDevice->device->setOwner(user, relativePath, owner);\n   }\n\n   if (relativePath.depth() > 0) {\n      return Error::NotFound;\n   }\n\n   node->owner = owner;\n   return Error::Success;\n}\n\nError\nVirtualDevice::setPermissions(const User &user,\n                              const Path &path,\n                              Permissions mode)\n{\n   auto [node, relativePath] = findDeepest(user, path);\n   if (node->type == VirtualNode::MountedDevice) {\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n      return mountedDevice->device->setPermissions(user, relativePath, mode);\n   }\n\n   if (relativePath.depth() > 0) {\n      return Error::NotFound;\n   }\n\n   node->permission = mode;\n   return Error::Success;\n}\n\nResult<Status>\nVirtualDevice::status(const User &user, const Path &path)\n{\n   auto [node, relativePath] = findDeepest(user, path);\n   if (node->type == VirtualNode::MountedDevice) {\n      auto mountedDevice = static_cast<VirtualMountedDevice *>(node.get());\n      return mountedDevice->device->status(user, relativePath);\n   }\n\n   if (relativePath.depth() > 0) {\n      return Error::NotFound;\n   }\n\n   auto status = Status { };\n   status.name = std::prev(std::end(path))->path();\n\n   if (node->type == VirtualNode::Directory) {\n      status.flags |= Status::IsDirectory;\n   }\n\n   if (node->type == VirtualNode::File) {\n      auto file = static_cast<VirtualFile *>(node.get());\n      status.size = file->data.size();\n   }\n\n   status.group = node->group;\n   status.owner = node->owner;\n   status.permission = node->permission;\n   status.flags |= Status::HasPermissions;\n   return status;\n}\n\nstd::pair<std::shared_ptr<VirtualNode>, Path>\nVirtualDevice::findDeepest(const User &user,\n                           Path path,\n                           int parentLevel)\n{\n   if (path.empty()) {\n      return { mRoot, {} };\n   }\n\n   if (!mRootDeviceName.empty()) {\n      auto pathRoot = std::begin(path);\n      if (pathRoot->path() != mRootDeviceName) {\n         return { nullptr, {} };\n      }\n\n      path = Path { std::next(pathRoot), std::end(path) };\n      if (path.empty()) {\n         return { mRoot, {} };\n      }\n   }\n\n   auto dir = mRoot;\n   auto itr = path.begin();\n   auto last = std::prev(path.end(), parentLevel);\n   for (; itr != last; ++itr) {\n      auto name = itr->path();\n      auto childItr = dir->children.find(itr->path());\n      if (childItr == dir->children.end()) {\n         // We have reached as far as we could.\n         break;\n      }\n\n      auto child = childItr->second;\n      if (child->type == VirtualNode::Directory) {\n         // Iterate through directory\n         dir = std::static_pointer_cast<VirtualDirectory>(child);\n      } else if (child->type == VirtualNode::MountedDevice ||\n                  child->type == VirtualNode::File) {\n         // Cannot iterate through device or a file\n         return { child, Path { ++itr, path.end() } };\n      }\n   }\n\n   return { dir, Path { itr, path.end() } };\n}\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_virtual_device.h",
    "content": "#pragma once\n#include \"vfs_device.h\"\n#include \"vfs_virtual_directory.h\"\n\n#include <memory>\n\nnamespace vfs\n{\n\nclass VirtualDevice : public Device, public std::enable_shared_from_this<VirtualDevice>\n{\npublic:\n   VirtualDevice(std::string rootDeviceName = {});\n   ~VirtualDevice() override = default;\n\n   Result<std::shared_ptr<Device>>\n   getLinkDevice(const User &user, const Path &path) override;\n\n   Error\n   makeFolder(const User &user, const Path &path) override;\n\n   Error\n   makeFolders(const User &user, const Path &path) override;\n\n   Error\n   mountDevice(const User &user, const Path &path,\n               std::shared_ptr<Device> device) override;\n\n   Error\n   mountOverlayDevice(const User &user, OverlayPriority priority,\n                      const Path &path,\n                      std::shared_ptr<Device> device) override;\n\n   Error\n   unmountDevice(const User &user, const Path &path) override;\n\n   Error\n   unmountOverlayDevice(const User &user, OverlayPriority priority,\n                        const Path &path) override;\n\n   Result<DirectoryIterator>\n   openDirectory(const User &user, const Path &path) override;\n\n   Result<std::unique_ptr<FileHandle>>\n   openFile(const User &user, const Path &path,\n            FileHandle::Mode mode) override;\n\n   Error\n   remove(const User &user, const Path &path) override;\n\n   Error\n   rename(const User &user, const Path &src, const Path &dst) override;\n\n   Error\n   setGroup(const User &user, const Path &path, GroupId group) override;\n\n   Error\n   setOwner(const User &user, const Path &path, OwnerId owner) override;\n\n   Error\n   setPermissions(const User &user, const Path &path, Permissions mode) override;\n\n   Result<Status>\n   status(const User &user, const Path &path) override;\n\nprivate:\n   std::pair<std::shared_ptr<VirtualNode>, Path>\n   findDeepest(const User &user, Path path, int parentLevel = 0);\n\nprivate:\n   std::string mRootDeviceName;\n   std::shared_ptr<VirtualDirectory> mRoot;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_virtual_directory.h",
    "content": "#pragma once\n#include \"vfs_virtual_node.h\"\n\n#include <map>\n#include <memory>\n#include <string>\n\nnamespace vfs\n{\n\nstruct VirtualDirectory : public VirtualNode\n{\n   using child_map = std::map<std::string, std::shared_ptr<VirtualNode>>;\n   using iterator = child_map::iterator;\n   using const_iterator = child_map::const_iterator;\n\n   VirtualDirectory() :\n      VirtualNode(VirtualNode::Directory)\n   {\n   }\n\n   iterator begin()\n   {\n      return children.begin();\n   }\n\n   iterator end()\n   {\n      return children.end();\n   }\n\n   child_map children;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_virtual_directoryiterator.cpp",
    "content": "#include \"vfs_virtual_directory.h\"\n#include \"vfs_virtual_directoryiterator.h\"\n#include \"vfs_virtual_file.h\"\n\n#include <system_error>\n\nnamespace vfs\n{\n\nVirtualDirectoryIterator::VirtualDirectoryIterator(std::shared_ptr<VirtualDirectory> directory) :\n   mDirectory(std::move(directory)),\n   mIterator(mDirectory->begin())\n{\n}\n\nResult<Status>\nVirtualDirectoryIterator::readEntry()\n{\n   if (mIterator == mDirectory->end()) {\n      return Error::EndOfDirectory;\n   }\n\n   auto ec = std::error_code { };\n   auto entry = Status{ };\n   entry.name = mIterator->first;\n\n   auto node = mIterator->second.get();\n   if (node->type == VirtualNode::File) {\n      auto file = static_cast<VirtualFile *>(node);\n      entry.size = file->data.size();\n      entry.flags = Status::HasSize;\n   }\n\n   if (node->type == VirtualNode::Directory) {\n      entry.flags = Status::IsDirectory;\n   }\n\n   entry.group = node->group;\n   entry.owner = node->owner;\n   entry.permission = node->permission;\n   entry.flags = Status::HasPermissions;\n\n   ++mIterator;\n   return entry;\n}\n\nError\nVirtualDirectoryIterator::rewind()\n{\n   mIterator = mDirectory->begin();\n   return Error::Success;\n}\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_virtual_directoryiterator.h",
    "content": "#pragma once\n#include \"vfs_directoryiterator.h\"\n#include \"vfs_virtual_directory.h\"\n\n#include <memory>\n\nnamespace vfs\n{\n\nclass VirtualDirectoryIterator : public DirectoryIteratorImpl\n{\npublic:\n   VirtualDirectoryIterator(std::shared_ptr<VirtualDirectory> directory);\n   ~VirtualDirectoryIterator() override = default;\n\n   Result<Status> readEntry() override;\n   Error rewind() override;\n\nprivate:\n   std::shared_ptr<VirtualDirectory> mDirectory;\n   VirtualDirectory::iterator mIterator;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_virtual_file.h",
    "content": "#pragma once\n#include \"vfs_virtual_node.h\"\n\n#include <cstdint>\n#include <vector>\n\nnamespace vfs\n{\n\nstruct VirtualFile : public VirtualNode\n{\n   VirtualFile() :\n      VirtualNode(VirtualNode::File)\n   {\n   }\n   ~VirtualFile() override = default;\n\n   std::vector<uint8_t> data;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_virtual_filehandle.cpp",
    "content": "#include \"vfs_virtual_file.h\"\n#include \"vfs_virtual_filehandle.h\"\n\n#include <algorithm>\n#include <cstring>\n\nnamespace vfs\n{\n\nVirtualFileHandle::VirtualFileHandle(std::shared_ptr<VirtualFile> file,\n                                     Mode mode) :\n   mFile(std::move(file)),\n   mPosition(0),\n   mMode(mode)\n{\n}\n\nVirtualFileHandle::~VirtualFileHandle()\n{\n   close();\n}\n\nError\nVirtualFileHandle::close()\n{\n   mFile.reset();\n   return Error::Success;\n}\n\nResult<bool>\nVirtualFileHandle::eof()\n{\n   if (!mFile) {\n      return { Error::NotOpen };\n   }\n\n   return mPosition >= static_cast<int64_t>(mFile->data.size());\n}\n\nError\nVirtualFileHandle::flush()\n{\n   if (!mFile) {\n      return Error::NotOpen;\n   }\n\n   return Error::Success;\n}\n\nError\nVirtualFileHandle::seek(SeekDirection direction,\n                        int64_t offset)\n{\n   if (!mFile) {\n      return Error::NotOpen;\n   }\n\n   auto oldPosition = mPosition;\n   switch (direction) {\n   case SeekCurrent:\n      mPosition += offset;\n      break;\n   case SeekEnd:\n      mPosition = static_cast<int64_t>(mFile->data.size()) + offset;\n      break;\n   case SeekStart:\n      mPosition = offset;\n      break;\n   default:\n      return Error::InvalidSeekDirection;\n   }\n\n   if (mPosition < 0) {\n      // Cannot seek before start of file!\n      mPosition = oldPosition;\n      return Error::InvalidSeekPosition;\n   }\n\n   return Error::Success;\n}\n\nResult<int64_t>\nVirtualFileHandle::size()\n{\n   if (!mFile) {\n      return { Error::NotOpen };\n   }\n\n   return { static_cast<int64_t>(mFile->data.size()) };\n}\n\nResult<int64_t>\nVirtualFileHandle::tell()\n{\n   if (!mFile) {\n      return { Error::NotOpen };\n   }\n\n   return { mPosition };\n}\n\nResult<int64_t>\nVirtualFileHandle::truncate()\n{\n   if (!mFile) {\n      return { Error::NotOpen };\n   }\n\n   mFile->data.resize(static_cast<size_t>(mPosition));\n   return { mPosition };\n}\n\nResult<int64_t>\nVirtualFileHandle::read(void *buffer,\n                        int64_t size,\n                        int64_t count)\n{\n   if (!mFile) {\n      return { Error::NotOpen };\n   }\n\n   if (mPosition >= static_cast<int64_t>(mFile->data.size())) {\n      return { Error::EndOfFile };\n   }\n\n   auto groupsRemaining = (static_cast<int64_t>(mFile->data.size()) - mPosition) / size;\n   count = std::min(count, groupsRemaining);\n   if (count == 0) {\n      return { 0 };\n   }\n\n   std::memcpy(buffer, mFile->data.data() + mPosition, size * count);\n   mPosition += size * count;\n   return { count };\n}\n\nResult<int64_t>\nVirtualFileHandle::write(const void *buffer,\n                         int64_t size,\n                         int64_t count)\n{\n   if (!mFile) {\n      return { Error::NotOpen };\n   }\n\n   if (mMode & Append) {\n      mPosition = static_cast<int64_t>(mFile->data.size());\n   }\n\n   auto endPosition = mPosition + size * count;\n   if (endPosition > static_cast<int64_t>(mFile->data.size())) {\n      mFile->data.resize(static_cast<size_t>(endPosition));\n   }\n\n   std::memcpy(mFile->data.data() + mPosition, buffer, size * count);\n   mPosition += size * count;\n   return { count };\n}\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_virtual_filehandle.h",
    "content": "#pragma once\n#include \"vfs_filehandle.h\"\n\n#include <memory>\n\nnamespace vfs\n{\n\nstruct VirtualFile;\n\nclass VirtualFileHandle : public FileHandle\n{\npublic:\n   VirtualFileHandle(std::shared_ptr<VirtualFile> file,\n                     Mode mode);\n   ~VirtualFileHandle() override;\n\n   Error close() override;\n   Result<bool> eof() override;\n   Error flush() override;\n   Error seek(SeekDirection direction, int64_t offset) override;\n   Result<int64_t> size() override;\n   Result<int64_t> tell() override;\n   Result<int64_t> truncate() override;\n   Result<int64_t> read(void *buffer, int64_t size, int64_t count) override;\n   Result<int64_t> write(const void *buffer, int64_t size, int64_t count) override;\n\nprivate:\n   std::shared_ptr<VirtualFile> mFile;\n   int64_t mPosition;\n   Mode mMode;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_virtual_mounteddevice.h",
    "content": "#pragma once\n#include \"vfs_virtual_node.h\"\n\n#include <memory>\n\nnamespace vfs\n{\n\nclass Device;\n\nstruct VirtualMountedDevice : public VirtualNode\n{\n   VirtualMountedDevice(std::shared_ptr<Device> device) :\n      VirtualNode(VirtualNode::MountedDevice),\n      device(std::move(device))\n   {\n   }\n   ~VirtualMountedDevice() override = default;\n\n   std::shared_ptr<Device> device;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libdecaf/src/vfs/vfs_virtual_node.h",
    "content": "#pragma once\n#include \"vfs_permissions.h\"\n\nnamespace vfs\n{\n\nstruct VirtualNode\n{\n   enum Type\n   {\n      File,\n      Directory,\n      MountedDevice,\n   };\n\n   VirtualNode(Type type) :\n      type(type)\n   {\n   }\n   virtual ~VirtualNode() = default;\n\n   Type type;\n   GroupId group = 0;\n   OwnerId owner = 0;\n   Permissions permission = Permissions::OtherWrite | Permissions::OtherRead;\n};\n\n} // namespace vfs\n"
  },
  {
    "path": "src/libgfd/CMakeLists.txt",
    "content": "project(libgfd)\n\ninclude_directories(\".\")\ninclude_directories(\"src\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nadd_library(libgfd STATIC ${SOURCE_FILES} ${HEADER_FILES})\nGroupSources(\"Source Files\" src)\n\ntarget_link_libraries(libgfd\n    common)\n"
  },
  {
    "path": "src/libgfd/gfd.h",
    "content": "#pragma once\n#include \"gfd_enum.h\"\n#include \"gfd_gx2.h\"\n\n#include <cstdint>\n#include <stdexcept>\n#include <vector>\n\nnamespace gfd\n{\n\nstruct GFDFileHeader\n{\n   static constexpr uint32_t Magic = 0x47667832;\n   static constexpr uint32_t HeaderSize = 8 * 4;\n   uint32_t magic;\n   uint32_t headerSize;\n   uint32_t majorVersion;\n   uint32_t minorVersion;\n   uint32_t gpuVersion;\n   uint32_t align;\n   uint32_t unk1;\n   uint32_t unk2;\n};\n\nstruct GFDBlockHeader\n{\n   static constexpr uint32_t Magic = 0x424C4B7B;\n   static constexpr uint32_t HeaderSize = 8 * 4;\n   uint32_t magic;\n   uint32_t headerSize;\n   uint32_t majorVersion;\n   uint32_t minorVersion;\n   GFDBlockType type;\n   uint32_t dataSize;\n   uint32_t id;\n   uint32_t index;\n};\n\nstruct GFDRelocationHeader\n{\n   static constexpr uint32_t Magic = 0x7D424C4B;\n   static constexpr uint32_t HeaderSize = 10 * 4;\n   uint32_t magic;\n   uint32_t headerSize;\n   uint32_t unk1;\n   uint32_t dataSize;\n   uint32_t dataOffset;\n   uint32_t textSize;\n   uint32_t textOffset;\n   uint32_t patchBase;\n   uint32_t patchCount;\n   uint32_t patchOffset;\n};\n\nstruct GFDBlockRelocations\n{\n   GFDRelocationHeader header;\n   std::vector<uint32_t> patches;\n};\n\nstruct GFDBlock\n{\n   GFDBlockHeader header;\n   std::vector<uint8_t> data;\n};\n\nstruct GFDFile\n{\n   std::vector<GFDVertexShader> vertexShaders;\n   std::vector<GFDPixelShader> pixelShaders;\n   std::vector<GFDGeometryShader> geometryShaders;\n   std::vector<GFDTexture> textures;\n};\n\nstatic constexpr uint32_t GFDFileMajorVersion = 7u;\nstatic constexpr uint32_t GFDFileMinorVersion = 1u;\nstatic constexpr uint32_t GFDFileGpuVersion = 2u;\nstatic constexpr uint32_t GFDBlockMajorVersion = 1u;\nstatic constexpr uint32_t GFDPatchMask = 0xFFF00000u;\nstatic constexpr uint32_t GFDPatchData = 0xD0600000u;\nstatic constexpr uint32_t GFDPatchText = 0xCA700000u;\n\nstruct GFDReadException : public std::runtime_error\n{\npublic:\n   GFDReadException(const std::string &msg) :\n      std::runtime_error(msg)\n   {\n   }\n};\n\nbool\nreadFile(GFDFile &file,\n         const std::string &path);\n\nbool\nwriteFile(const GFDFile &file,\n          const std::string &path,\n          bool align = false);\n\n} // namespace gfd\n"
  },
  {
    "path": "src/libgfd/gfd_enum.h",
    "content": "#ifndef GFD_ENUM_H\n#define GFD_ENUM_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(gfd)\n\nENUM_BEG(GFDBlockType, uint32_t)\n   ENUM_VALUE(EndOfFile,                  1)\n   ENUM_VALUE(Padding,                    2)\n   ENUM_VALUE(VertexShaderHeader,         3)\n   ENUM_VALUE(VertexShaderProgram,        5)\n   ENUM_VALUE(PixelShaderHeader,          6)\n   ENUM_VALUE(PixelShaderProgram,         7)\n   ENUM_VALUE(GeometryShaderHeader,       8)\n   ENUM_VALUE(GeometryShaderProgram,      9)\n   ENUM_VALUE(GeometryShaderCopyProgram,  10)\n   ENUM_VALUE(TextureHeader,              11)\n   ENUM_VALUE(TextureImage,               12)\n   ENUM_VALUE(TextureMipmap,              13)\n   ENUM_VALUE(ComputeShaderHeader,        14)\n   ENUM_VALUE(ComputeShaderProgram,       15)\nENUM_END(GFDBlockType)\n\nENUM_NAMESPACE_EXIT(gfd)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef GFD_ENUM_H\n"
  },
  {
    "path": "src/libgfd/gfd_gx2.h",
    "content": "#pragma once\n#include <array>\n#include <cstdint>\n#include <libdecaf/src/cafe/libraries/gx2/gx2_enum.h>\n#include <libgpu/latte/latte_registers.h>\n#include <string>\n#include <vector>\n\nnamespace gfd\n{\n\nusing cafe::gx2::GX2AAMode;\nusing cafe::gx2::GX2FetchShaderType;\nusing cafe::gx2::GX2RResourceFlags;\nusing cafe::gx2::GX2RResourceFlags;\nusing cafe::gx2::GX2SamplerVarType;\nusing cafe::gx2::GX2ShaderMode;\nusing cafe::gx2::GX2ShaderVarType;\nusing cafe::gx2::GX2SurfaceDim;\nusing cafe::gx2::GX2SurfaceFormat;\nusing cafe::gx2::GX2SurfaceUse;\nusing cafe::gx2::GX2TileMode;\n\nstruct GFDSurface\n{\n   GX2SurfaceDim dim;\n   uint32_t width;\n   uint32_t height;\n   uint32_t depth;\n   uint32_t mipLevels;\n   GX2SurfaceFormat format;\n   GX2AAMode aa;\n\n   union\n   {\n      GX2SurfaceUse use;\n      GX2RResourceFlags resourceFlags;\n   };\n\n   std::vector<uint8_t> image;\n   std::vector<uint8_t> mipmap;\n   GX2TileMode tileMode;\n   uint32_t swizzle;\n   uint32_t alignment;\n   uint32_t pitch;\n   std::array<uint32_t, 13> mipLevelOffset;\n};\n\nstruct GFDTexture\n{\n   GFDSurface surface;\n   uint32_t viewFirstMip;\n   uint32_t viewNumMips;\n   uint32_t viewFirstSlice;\n   uint32_t viewNumSlices;\n   uint32_t compMap;\n\n   struct\n   {\n      latte::SQ_TEX_RESOURCE_WORD0_N word0;\n      latte::SQ_TEX_RESOURCE_WORD1_N word1;\n      latte::SQ_TEX_RESOURCE_WORD4_N word4;\n      latte::SQ_TEX_RESOURCE_WORD5_N word5;\n      latte::SQ_TEX_RESOURCE_WORD6_N word6;\n   } regs;\n};\n\nstruct GFDFetchShader\n{\n   GX2FetchShaderType type;\n\n   struct\n   {\n      latte::SQ_PGM_RESOURCES_FS sq_pgm_resources_fs;\n   } regs;\n\n   uint32_t size;\n   uint8_t *data;\n   uint32_t attribCount;\n   uint32_t numDivisors;\n   uint32_t divisors[2];\n};\n\nstruct GFDUniformVar\n{\n   std::string name;\n   GX2ShaderVarType type;\n   uint32_t count;\n   uint32_t offset;\n   int32_t block;\n};\n\nstruct GFDUniformInitialValue\n{\n   std::array<float, 4> value;\n   uint32_t offset;\n};\n\nstruct GFDUniformBlock\n{\n   std::string name;\n   uint32_t offset;\n   uint32_t size;\n};\n\nstruct GFDAttribVar\n{\n   std::string name;\n   GX2ShaderVarType type;\n   uint32_t count;\n   uint32_t location;\n};\n\nstruct GFDSamplerVar\n{\n   std::string name;\n   GX2SamplerVarType type;\n   uint32_t location;\n};\n\nstruct GFDLoopVar\n{\n   uint32_t offset;\n   uint32_t value;\n};\n\nstruct GFDRBuffer\n{\n   GX2RResourceFlags flags;\n   uint32_t elemSize;\n   uint32_t elemCount;\n   std::vector<uint8_t> buffer;\n};\n\nstruct GFDVertexShader\n{\n   struct\n   {\n      latte::SQ_PGM_RESOURCES_VS sq_pgm_resources_vs;\n      latte::VGT_PRIMITIVEID_EN vgt_primitiveid_en;\n      latte::SPI_VS_OUT_CONFIG spi_vs_out_config;\n      uint32_t num_spi_vs_out_id;\n      std::array<latte::SPI_VS_OUT_ID_N, 10> spi_vs_out_id;\n      latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl;\n      latte::SQ_VTX_SEMANTIC_CLEAR sq_vtx_semantic_clear;\n      uint32_t num_sq_vtx_semantic;\n      std::array<latte::SQ_VTX_SEMANTIC_N, 32> sq_vtx_semantic;\n      latte::VGT_STRMOUT_BUFFER_EN vgt_strmout_buffer_en;\n      latte::VGT_VERTEX_REUSE_BLOCK_CNTL vgt_vertex_reuse_block_cntl;\n      latte::VGT_HOS_REUSE_DEPTH vgt_hos_reuse_depth;\n   } regs;\n\n   std::vector<uint8_t> data;\n   GX2ShaderMode mode;\n   std::vector<GFDUniformBlock> uniformBlocks;\n   std::vector<GFDUniformVar> uniformVars;\n   std::vector<GFDUniformInitialValue> initialValues;\n   std::vector<GFDLoopVar> loopVars;\n   std::vector<GFDSamplerVar> samplerVars;\n   std::vector<GFDAttribVar> attribVars;\n   uint32_t ringItemSize;\n   bool hasStreamOut;\n   std::array<uint32_t, 4> streamOutStride;\n   GFDRBuffer gx2rData;\n};\n\nstruct GFDPixelShader\n{\n   struct\n   {\n      latte::SQ_PGM_RESOURCES_PS sq_pgm_resources_ps;\n      latte::SQ_PGM_EXPORTS_PS sq_pgm_exports_ps;\n      latte::SPI_PS_IN_CONTROL_0 spi_ps_in_control_0;\n      latte::SPI_PS_IN_CONTROL_1 spi_ps_in_control_1;\n      uint32_t num_spi_ps_input_cntl;\n      std::array<latte::SPI_PS_INPUT_CNTL_N, 32> spi_ps_input_cntls;\n      latte::CB_SHADER_MASK cb_shader_mask;\n      latte::CB_SHADER_CONTROL cb_shader_control;\n      latte::DB_SHADER_CONTROL db_shader_control;\n      latte::SPI_INPUT_Z spi_input_z;\n   } regs;\n\n   std::vector<uint8_t> data;\n   GX2ShaderMode mode;\n   std::vector<GFDUniformBlock> uniformBlocks;\n   std::vector<GFDUniformVar> uniformVars;\n   std::vector<GFDUniformInitialValue> initialValues;\n   std::vector<GFDLoopVar> loopVars;\n   std::vector<GFDSamplerVar> samplerVars;\n   GFDRBuffer gx2rData;\n};\n\nstruct GFDGeometryShader\n{\n   struct\n   {\n      latte::SQ_PGM_RESOURCES_GS sq_pgm_resources_gs;\n      latte::VGT_GS_OUT_PRIM_TYPE vgt_gs_out_prim_type;\n      latte::VGT_GS_MODE vgt_gs_mode;\n      latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl;\n      latte::SQ_PGM_RESOURCES_VS sq_pgm_resources_vs;\n      latte::SQ_GS_VERT_ITEMSIZE sq_gs_vert_itemsize;\n      latte::SPI_VS_OUT_CONFIG spi_vs_out_config;\n      uint32_t num_spi_vs_out_id;\n      std::array<latte::SPI_VS_OUT_ID_N, 10> spi_vs_out_id;\n      latte::VGT_STRMOUT_BUFFER_EN vgt_strmout_buffer_en;\n   } regs;\n\n   std::vector<uint8_t> data;\n   std::vector<uint8_t> vertexShaderData;\n   GX2ShaderMode mode;\n   std::vector<GFDUniformBlock> uniformBlocks;\n   std::vector<GFDUniformVar> uniformVars;\n   std::vector<GFDUniformInitialValue> initialValues;\n   std::vector<GFDLoopVar> loopVars;\n   std::vector<GFDSamplerVar> samplerVars;\n   uint32_t ringItemSize;\n   bool hasStreamOut;\n   std::array<uint32_t, 4> streamOutStride;\n   GFDRBuffer gx2rData;\n   GFDRBuffer gx2rVertexShaderData;\n};\n\n} // namespace gfd\n"
  },
  {
    "path": "src/libgfd/src/gfd_read.cpp",
    "content": "#include \"gfd.h\"\n\n#include <common/byte_swap.h>\n#include <fstream>\n#include <fmt/core.h>\n#include <string>\n\nnamespace gfd\n{\n\nstruct MemoryFile\n{\n   bool eof()\n   {\n      return pos >= data.size();\n   }\n\n   uint32_t pos = 0;\n   std::vector<uint8_t> data;\n};\n\nstatic bool\nopenFile(const std::string &path,\n         MemoryFile & file)\n{\n   std::ifstream fh { path, std::ifstream::binary };\n   if (!fh.is_open()) {\n      return false;\n   }\n\n   fh.seekg(0, std::istream::end);\n   file.data.resize(fh.tellg());\n   fh.seekg(0);\n   fh.read(reinterpret_cast<char *>(file.data.data()), file.data.size());\n   file.pos = 0;\n   return true;\n}\n\ntemplate<typename Type>\ninline Type\nread(MemoryFile &fh)\n{\n   if (fh.pos + sizeof(Type) > fh.data.size()) {\n      throw GFDReadException { \"Tried to read past end of file\" };\n   }\n\n   auto value = byte_swap(*reinterpret_cast<Type *>(fh.data.data() + fh.pos));\n   fh.pos += sizeof(Type);\n   return value;\n}\n\ninline void\nreadBinary(MemoryFile &fh,\n           std::vector<uint8_t> &value,\n           uint32_t size)\n{\n   if (fh.pos + size > fh.data.size()) {\n      throw GFDReadException { \"Tried to read past end of file\" };\n   }\n\n   value.resize(size);\n   std::memcpy(value.data(), fh.data.data() + fh.pos, size);\n   fh.pos += size;\n}\n\nstatic bool\nreadFileHeader(MemoryFile &fh,\n               GFDFileHeader &header)\n{\n   header.magic = read<uint32_t>(fh);\n   if (header.magic != GFDFileHeader::Magic) {\n      throw GFDReadException { fmt::format(\"Unexpected file magic {:X}\", header.magic) };\n   }\n\n   header.headerSize = read<uint32_t>(fh);\n   if (header.headerSize != GFDFileHeader::HeaderSize) {\n      throw GFDReadException { fmt::format(\"Unexpected file header size {}\",\n                                           header.headerSize) };\n   }\n\n   header.majorVersion = read<uint32_t>(fh);\n   if (header.majorVersion != GFDFileMajorVersion) {\n      throw GFDReadException { fmt::format(\"Unsupported file version {}.{}.{}\",\n                                           header.majorVersion, header.minorVersion, header.gpuVersion) };\n   }\n\n   header.minorVersion = read<uint32_t>(fh);\n   header.gpuVersion = read<uint32_t>(fh);\n   header.align = read<uint32_t>(fh);\n   header.unk1 = read<uint32_t>(fh);\n   header.unk2 = read<uint32_t>(fh);\n   return true;\n}\n\nstatic bool\nreadBlockHeader(MemoryFile &fh,\n                GFDBlockHeader &header)\n{\n   if (fh.eof()) {\n      return false;\n   }\n\n   header.magic = read<uint32_t>(fh);\n   if (header.magic != GFDBlockHeader::Magic) {\n      throw GFDReadException { fmt::format(\"Unexpected block magic {:X}\", header.magic) };\n   }\n\n   header.headerSize = read<uint32_t>(fh);\n   if (header.headerSize != GFDBlockHeader::HeaderSize) {\n      throw GFDReadException { fmt::format(\"Unexpected block header size {:X}\", header.headerSize) };\n   }\n\n   header.majorVersion = read<uint32_t>(fh);\n   header.minorVersion = read<uint32_t>(fh);\n   header.type = static_cast<GFDBlockType>(read<uint32_t>(fh));\n   header.dataSize = read<uint32_t>(fh);\n   header.id = read<uint32_t>(fh);\n   header.index = read<uint32_t>(fh);\n   return true;\n}\n\nstatic bool\nreadRelocationHeader(MemoryFile &fh,\n                     GFDRelocationHeader &header)\n{\n   header.magic = read<uint32_t>(fh);\n   if (header.magic != GFDRelocationHeader::Magic) {\n      return false;\n   }\n\n   header.headerSize = read<uint32_t>(fh);\n   if (header.headerSize != GFDRelocationHeader::HeaderSize) {\n      throw GFDReadException { fmt::format(\"Unexpected relocation header size {:X}\", header.headerSize) };\n   }\n\n   header.unk1 = read<uint32_t>(fh);\n   header.dataSize = read<uint32_t>(fh);\n   header.dataOffset = read<uint32_t>(fh);\n   header.textSize = read<uint32_t>(fh);\n   header.textOffset = read<uint32_t>(fh);\n   header.patchBase = read<uint32_t>(fh);\n   header.patchCount = read<uint32_t>(fh);\n   header.patchOffset = read<uint32_t>(fh);\n   return true;\n}\n\nstatic bool\nreadBlockRelocations(MemoryFile &fh,\n                     uint32_t blockBase,\n                     uint32_t blockSize,\n                     GFDBlockRelocations &block)\n{\n   fh.pos = blockBase + blockSize - GFDRelocationHeader::HeaderSize;\n\n   if (!readRelocationHeader(fh, block.header)) {\n      return false;\n   }\n\n   fh.pos = blockBase + (block.header.patchOffset & ~GFDPatchMask);\n\n   for (auto i = 0u; i < block.header.patchCount; ++i) {\n      block.patches.push_back(read<uint32_t>(fh));\n   }\n\n   return true;\n}\n\nstatic bool\ncheckRelocation(const GFDBlockRelocations &relocations,\n                uint32_t pos)\n{\n   for (auto &patch : relocations.patches) {\n      if (pos == (patch & ~GFDPatchMask)) {\n         return true;\n      }\n   }\n\n   return false;\n}\n\nstatic std::string\nreadString(MemoryFile &fh,\n           uint32_t blockBase,\n           const GFDBlockRelocations &relocations)\n{\n   auto offset = read<uint32_t>(fh) & ~GFDPatchMask;\n   if (!checkRelocation(relocations, fh.pos - 4 - blockBase)) {\n      return { };\n   }\n\n   return reinterpret_cast<const char *>(fh.data.data() + blockBase + offset);\n}\n\nstatic bool\nreadUniformBlocks(MemoryFile &fh,\n                  uint32_t blockBase,\n                  const GFDBlockRelocations &relocations,\n                  std::vector<GFDUniformBlock> &uniformBlocks)\n{\n   auto pos = fh.pos;\n   auto count = read<uint32_t>(fh);\n   auto offset = read<uint32_t>(fh) & ~GFDPatchMask;\n\n   if (!count) {\n      return true;\n   }\n\n   decaf_check(offset);\n   decaf_check(checkRelocation(relocations, pos + 4 - blockBase));\n\n   fh.pos = blockBase + offset;\n   uniformBlocks.resize(count);\n\n   for (auto &block : uniformBlocks) {\n      block.name = readString(fh, blockBase, relocations);\n      block.offset = read<uint32_t>(fh);\n      block.size = read<uint32_t>(fh);\n   }\n\n   fh.pos = pos + 8;\n   return true;\n}\n\nstatic bool\nreadUniformVars(MemoryFile &fh,\n                uint32_t blockBase,\n                const GFDBlockRelocations &relocations,\n                std::vector<GFDUniformVar> &uniformVars)\n{\n   auto pos = fh.pos;\n   auto count = read<uint32_t>(fh);\n   auto offset = read<uint32_t>(fh) & ~GFDPatchMask;\n\n   if (!count) {\n      return true;\n   }\n\n   decaf_check(offset);\n   decaf_check(checkRelocation(relocations, pos + 4 - blockBase));\n\n   fh.pos = blockBase + offset;\n   uniformVars.resize(count);\n\n   for (auto &var : uniformVars) {\n      var.name = readString(fh, blockBase, relocations);\n      var.type = static_cast<GX2ShaderVarType>(read<uint32_t>(fh));\n      var.count = read<uint32_t>(fh);\n      var.offset = read<uint32_t>(fh);\n      var.block = read<int32_t>(fh);\n   }\n\n   fh.pos = pos + 8;\n   return true;\n}\n\nstatic bool\nreadInitialValues(MemoryFile &fh,\n                  uint32_t blockBase,\n                  const GFDBlockRelocations &relocations,\n                  std::vector<GFDUniformInitialValue> &initialValues)\n{\n   auto pos = fh.pos;\n   auto count = read<uint32_t>(fh);\n   auto offset = read<uint32_t>(fh) & ~GFDPatchMask;\n\n   if (!count) {\n      return true;\n   }\n\n   decaf_check(offset);\n   decaf_check(checkRelocation(relocations, pos + 4 - blockBase));\n\n   fh.pos = blockBase + offset;\n   initialValues.resize(count);\n\n   for (auto &var : initialValues) {\n      for (auto &value : var.value) {\n         value = read<float>(fh);\n      }\n\n      var.offset = read<uint32_t>(fh);\n   }\n\n   fh.pos = pos + 8;\n   return true;\n}\n\nstatic bool\nreadLoopVars(MemoryFile &fh,\n             uint32_t blockBase,\n             const GFDBlockRelocations &relocations,\n             std::vector<GFDLoopVar> &loopVars)\n{\n   auto pos = fh.pos;\n   auto count = read<uint32_t>(fh);\n   auto offset = read<uint32_t>(fh) & ~GFDPatchMask;\n\n   if (!count) {\n      return true;\n   }\n\n   decaf_check(offset);\n   decaf_check(checkRelocation(relocations, pos + 4 - blockBase));\n\n   fh.pos = blockBase + offset;\n   loopVars.resize(count);\n\n   for (auto &var : loopVars) {\n      var.offset = read<uint32_t>(fh);\n      var.value = read<uint32_t>(fh);\n   }\n\n   fh.pos = pos + 8;\n   return true;\n}\n\nstatic bool\nreadSamplerVars(MemoryFile &fh,\n                uint32_t blockBase,\n                const GFDBlockRelocations &relocations,\n                std::vector<GFDSamplerVar> &samplerVars)\n{\n   auto pos = fh.pos;\n   auto count = read<uint32_t>(fh);\n   auto offset = read<uint32_t>(fh) & ~GFDPatchMask;\n\n   if (!count) {\n      return true;\n   }\n\n   decaf_check(offset);\n   decaf_check(checkRelocation(relocations, pos + 4 - blockBase));\n\n   fh.pos = blockBase + offset;\n   samplerVars.resize(count);\n\n   for (auto &var : samplerVars) {\n      var.name = readString(fh, blockBase, relocations);\n      var.type = static_cast<GX2SamplerVarType>(read<uint32_t>(fh));\n      var.location = read<uint32_t>(fh);\n   }\n\n   fh.pos = pos + 8;\n   return true;\n}\n\nstatic bool\nreadAttribVars(MemoryFile &fh,\n               uint32_t blockBase,\n               const GFDBlockRelocations &relocations,\n               std::vector<GFDAttribVar> &attribVars)\n{\n   auto pos = fh.pos;\n   auto count = read<uint32_t>(fh);\n   auto offset = read<uint32_t>(fh) & ~GFDPatchMask;\n\n   if (!count) {\n      return true;\n   }\n\n   decaf_check(offset);\n   decaf_check(checkRelocation(relocations, pos + 4 - blockBase));\n\n   fh.pos = blockBase + offset;\n   attribVars.resize(count);\n\n   for (auto &var : attribVars) {\n      var.name = readString(fh, blockBase, relocations);\n      var.type = static_cast<GX2ShaderVarType>(read<uint32_t>(fh));\n      var.count = read<uint32_t>(fh);\n      var.location = read<uint32_t>(fh);\n   }\n\n   fh.pos = pos + 8;\n   return true;\n}\n\nstatic bool\nreadGx2rBuffer(MemoryFile &fh,\n               GFDRBuffer &gx2r)\n{\n   gx2r.flags = static_cast<GX2RResourceFlags>(read<uint32_t>(fh));\n   gx2r.elemSize = read<uint32_t>(fh);\n   gx2r.elemCount = read<uint32_t>(fh);\n   decaf_check(read<uint32_t>(fh) == 0);\n   return true;\n}\n\nstatic bool\nreadVertexShaderHeader(MemoryFile &fh,\n                       GFDBlockHeader &block,\n                       GFDVertexShader &vsh)\n{\n   GFDBlockRelocations relocations;\n   auto blockBase = fh.pos;\n\n   if (!readBlockRelocations(fh, blockBase, block.dataSize, relocations)) {\n      return false;\n   }\n\n   fh.pos = blockBase;\n\n   if (block.majorVersion == 1) {\n      vsh.regs.sq_pgm_resources_vs = latte::SQ_PGM_RESOURCES_VS::get(read<uint32_t>(fh));\n      vsh.regs.vgt_primitiveid_en = latte::VGT_PRIMITIVEID_EN::get(read<uint32_t>(fh));\n      vsh.regs.spi_vs_out_config = latte::SPI_VS_OUT_CONFIG::get(read<uint32_t>(fh));\n      vsh.regs.num_spi_vs_out_id = read<uint32_t>(fh);\n\n      for (auto i = 0u; i < vsh.regs.spi_vs_out_id.size(); ++i) {\n         vsh.regs.spi_vs_out_id[i] = latte::SPI_VS_OUT_ID_N::get(read<uint32_t>(fh));\n      }\n\n      vsh.regs.pa_cl_vs_out_cntl = latte::PA_CL_VS_OUT_CNTL::get(read<uint32_t>(fh));\n      vsh.regs.sq_vtx_semantic_clear = latte::SQ_VTX_SEMANTIC_CLEAR::get(read<uint32_t>(fh));\n      vsh.regs.num_sq_vtx_semantic = read<uint32_t>(fh);\n\n      for (auto i = 0u; i < vsh.regs.sq_vtx_semantic.size(); ++i) {\n         vsh.regs.sq_vtx_semantic[i] = latte::SQ_VTX_SEMANTIC_N::get(read<uint32_t>(fh));\n      }\n\n      vsh.regs.vgt_strmout_buffer_en = latte::VGT_STRMOUT_BUFFER_EN::get(read<uint32_t>(fh));\n      vsh.regs.vgt_vertex_reuse_block_cntl = latte::VGT_VERTEX_REUSE_BLOCK_CNTL::get(read<uint32_t>(fh));\n      vsh.regs.vgt_hos_reuse_depth = latte::VGT_HOS_REUSE_DEPTH::get(read<uint32_t>(fh));\n      decaf_check(fh.pos - blockBase == 0xD0);\n   } else {\n      throw GFDReadException { fmt::format(\"Unsupported VertexShaderHeader version {}.{}\",\n                                           block.majorVersion, block.minorVersion) };\n   }\n\n   vsh.data.reserve(read<uint32_t>(fh));\n   decaf_check(read<uint32_t>(fh) == 0); // vsh.data\n   vsh.mode = static_cast<GX2ShaderMode>(read<uint32_t>(fh));\n\n   readUniformBlocks(fh, blockBase, relocations, vsh.uniformBlocks);\n   readUniformVars(fh, blockBase, relocations, vsh.uniformVars);\n   readInitialValues(fh, blockBase, relocations, vsh.initialValues);\n   readLoopVars(fh, blockBase, relocations, vsh.loopVars);\n   readSamplerVars(fh, blockBase, relocations, vsh.samplerVars);\n   readAttribVars(fh, blockBase, relocations, vsh.attribVars);\n\n   vsh.ringItemSize = read<uint32_t>(fh);\n   vsh.hasStreamOut = read<uint32_t>(fh) ? true : false;\n\n   for (auto &stride : vsh.streamOutStride) {\n      stride = read<uint32_t>(fh);\n   }\n\n   readGx2rBuffer(fh, vsh.gx2rData);\n\n   if (block.majorVersion == 1) {\n      decaf_check(fh.pos - blockBase == 0x134);\n   }\n\n   fh.pos = blockBase + block.dataSize;\n   return true;\n}\n\nstatic bool\nreadVertexShaderProgram(MemoryFile &fh,\n                        GFDBlockHeader &block,\n                        GFDVertexShader &vsh)\n{\n   if (block.dataSize != vsh.data.capacity()) {\n      throw GFDReadException { fmt::format(\"VertexShaderProgram block.dataSize {} != vsh.size {}\",\n                                           block.dataSize, vsh.data.capacity()) };\n   }\n\n   readBinary(fh, vsh.data, block.dataSize);\n   return true;\n}\n\nstatic bool\nreadPixelShaderHeader(MemoryFile &fh,\n                      GFDBlockHeader &block,\n                      GFDPixelShader &psh)\n{\n   GFDBlockRelocations relocations;\n   auto blockBase = fh.pos;\n\n   if (!readBlockRelocations(fh, blockBase, block.dataSize, relocations)) {\n      return false;\n   }\n\n   if (block.majorVersion != 0 && block.majorVersion != 1) {\n      throw GFDReadException { fmt::format(\"Unsupported PixelShaderHeader version {}.{}\",\n                                           block.majorVersion, block.minorVersion) };\n   }\n\n   fh.pos = blockBase;\n\n   psh.regs.sq_pgm_resources_ps = latte::SQ_PGM_RESOURCES_PS::get(read<uint32_t>(fh));\n   psh.regs.sq_pgm_exports_ps = latte::SQ_PGM_EXPORTS_PS::get(read<uint32_t>(fh));\n   psh.regs.spi_ps_in_control_0 = latte::SPI_PS_IN_CONTROL_0::get(read<uint32_t>(fh));\n   psh.regs.spi_ps_in_control_1 = latte::SPI_PS_IN_CONTROL_1::get(read<uint32_t>(fh));\n   psh.regs.num_spi_ps_input_cntl = read<uint32_t>(fh);\n\n   for (auto &spi_ps_input_cntl : psh.regs.spi_ps_input_cntls) {\n      spi_ps_input_cntl = latte::SPI_PS_INPUT_CNTL_N::get(read<uint32_t>(fh));\n   }\n\n   psh.regs.cb_shader_mask = latte::CB_SHADER_MASK::get(read<uint32_t>(fh));\n   psh.regs.cb_shader_control = latte::CB_SHADER_CONTROL::get(read<uint32_t>(fh));\n   psh.regs.db_shader_control = latte::DB_SHADER_CONTROL::get(read<uint32_t>(fh));\n   psh.regs.spi_input_z = latte::SPI_INPUT_Z::get(read<uint32_t>(fh));\n\n   psh.data.reserve(read<uint32_t>(fh));\n   decaf_check(read<uint32_t>(fh) == 0); // psh.data\n   psh.mode = static_cast<GX2ShaderMode>(read<uint32_t>(fh));\n\n   readUniformBlocks(fh, blockBase, relocations, psh.uniformBlocks);\n   readUniformVars(fh, blockBase, relocations, psh.uniformVars);\n   readInitialValues(fh, blockBase, relocations, psh.initialValues);\n   readLoopVars(fh, blockBase, relocations, psh.loopVars);\n   readSamplerVars(fh, blockBase, relocations, psh.samplerVars);\n\n   if (block.majorVersion == 0) {\n      std::memset(&psh.gx2rData, 0, sizeof(GFDRBuffer));\n      decaf_check(fh.pos - blockBase == 0xD8);\n   } else if (block.majorVersion == 1) {\n      readGx2rBuffer(fh, psh.gx2rData);\n      decaf_check(fh.pos - blockBase == 0xE8);\n   }\n\n   fh.pos = blockBase + block.dataSize;\n   return true;\n}\n\nstatic bool\nreadPixelShaderProgram(MemoryFile &fh,\n                       GFDBlockHeader &block,\n                       GFDPixelShader &psh)\n{\n   if (block.dataSize != psh.data.capacity()) {\n      throw GFDReadException { fmt::format(\"PixelShaderProgram block.dataSize {} != vsh.size {}\",\n                                           block.dataSize, psh.data.capacity()) };\n   }\n\n   readBinary(fh, psh.data, block.dataSize);\n   return true;\n}\n\nstatic bool\nreadGeometryShaderHeader(MemoryFile &fh,\n                         GFDBlockHeader &block,\n                         GFDGeometryShader &gsh)\n{\n   GFDBlockRelocations relocations;\n   auto blockBase = fh.pos;\n\n   if (!readBlockRelocations(fh, blockBase, block.dataSize, relocations)) {\n      return false;\n   }\n\n   if (block.majorVersion != 1) {\n      throw GFDReadException { fmt::format(\"Unsupported GeometryShaderHeader version {}.{}\",\n                                           block.majorVersion, block.minorVersion) };\n   }\n\n   fh.pos = blockBase;\n\n   gsh.regs.sq_pgm_resources_gs = latte::SQ_PGM_RESOURCES_GS::get(read<uint32_t>(fh));\n   gsh.regs.vgt_gs_out_prim_type = latte::VGT_GS_OUT_PRIM_TYPE::get(read<uint32_t>(fh));\n   gsh.regs.vgt_gs_mode = latte::VGT_GS_MODE::get(read<uint32_t>(fh));\n   gsh.regs.pa_cl_vs_out_cntl = latte::PA_CL_VS_OUT_CNTL::get(read<uint32_t>(fh));\n   gsh.regs.sq_pgm_resources_vs = latte::SQ_PGM_RESOURCES_VS::get(read<uint32_t>(fh));\n   gsh.regs.sq_gs_vert_itemsize = latte::SQ_GS_VERT_ITEMSIZE::get(read<uint32_t>(fh));\n   gsh.regs.spi_vs_out_config = latte::SPI_VS_OUT_CONFIG::get(read<uint32_t>(fh));\n   gsh.regs.num_spi_vs_out_id = read<uint32_t>(fh);\n\n   for (auto &spi_vs_out_id : gsh.regs.spi_vs_out_id) {\n      spi_vs_out_id = latte::SPI_VS_OUT_ID_N::get(read<uint32_t>(fh));\n   }\n\n   gsh.regs.vgt_strmout_buffer_en = latte::VGT_STRMOUT_BUFFER_EN::get(read<uint32_t>(fh));\n\n   gsh.data.reserve(read<uint32_t>(fh));\n   decaf_check(read<uint32_t>(fh) == 0); // psh.data\n\n   gsh.vertexShaderData.reserve(read<uint32_t>(fh));\n   decaf_check(read<uint32_t>(fh) == 0); // psh.vertexShaderData\n\n   gsh.mode = static_cast<GX2ShaderMode>(read<uint32_t>(fh));\n   readUniformBlocks(fh, blockBase, relocations, gsh.uniformBlocks);\n   readUniformVars(fh, blockBase, relocations, gsh.uniformVars);\n   readInitialValues(fh, blockBase, relocations, gsh.initialValues);\n   readLoopVars(fh, blockBase, relocations, gsh.loopVars);\n   readSamplerVars(fh, blockBase, relocations, gsh.samplerVars);\n\n   gsh.ringItemSize = read<uint32_t>(fh);\n   gsh.hasStreamOut = read<uint32_t>(fh) ? true : false;\n\n   for (auto &stride : gsh.streamOutStride) {\n      stride = read<uint32_t>(fh);\n   }\n\n   readGx2rBuffer(fh, gsh.gx2rData);\n   readGx2rBuffer(fh, gsh.gx2rVertexShaderData);\n\n   if (block.majorVersion == 1) {\n      decaf_check(fh.pos - blockBase == 0xC0);\n   }\n\n   fh.pos = blockBase + block.dataSize;\n   return true;\n}\n\nstatic bool\nreadGeometryShaderProgram(MemoryFile &fh,\n                          GFDBlockHeader &block,\n                          GFDGeometryShader &gsh)\n{\n   if (block.dataSize != gsh.data.capacity()) {\n      throw GFDReadException { fmt::format(\"GeometryShaderProgram block.dataSize {} != gsh.size {}\",\n                                           block.dataSize, gsh.data.capacity()) };\n   }\n\n   readBinary(fh, gsh.data, block.dataSize);\n   return true;\n}\n\nstatic bool\nreadGeometryShaderCopyProgram(MemoryFile &fh,\n                              GFDBlockHeader &block,\n                              GFDGeometryShader &gsh)\n{\n   if (block.dataSize != gsh.vertexShaderData.capacity()) {\n      throw GFDReadException { fmt::format(\"GeometryShaderCopyProgram block.dataSize {} != gsh.vertexShaderSize {}\",\n                                           block.dataSize, gsh.vertexShaderData.capacity()) };\n   }\n\n   readBinary(fh, gsh.vertexShaderData, block.dataSize);\n   return true;\n}\n\nstatic bool\nreadTextureHeader(MemoryFile &fh,\n                  GFDBlockHeader &block,\n                  GFDTexture &tex)\n{\n   auto blockBase = fh.pos;\n\n   if (block.majorVersion != 1) {\n      throw GFDReadException { fmt::format(\"Unsupported TextureHeader version {}.{}\",\n                                           block.majorVersion, block.minorVersion) };\n   }\n\n   tex.surface.dim = static_cast<GX2SurfaceDim>(read<uint32_t>(fh));\n   tex.surface.width = read<uint32_t>(fh);\n   tex.surface.height = read<uint32_t>(fh);\n   tex.surface.depth = read<uint32_t>(fh);\n   tex.surface.mipLevels = read<uint32_t>(fh);\n   tex.surface.format = static_cast<GX2SurfaceFormat>(read<uint32_t>(fh));\n   tex.surface.aa = static_cast<GX2AAMode>(read<uint32_t>(fh));\n   tex.surface.use = static_cast<GX2SurfaceUse>(read<uint32_t>(fh));\n\n   tex.surface.image.reserve(read<uint32_t>(fh));\n   decaf_check(read<uint32_t>(fh) == 0); // tex.surface.image\n\n   tex.surface.mipmap.reserve(read<uint32_t>(fh));\n   decaf_check(read<uint32_t>(fh) == 0); // tex.surface.mipmap\n\n   tex.surface.tileMode = static_cast<GX2TileMode>(read<uint32_t>(fh));\n   tex.surface.swizzle = read<uint32_t>(fh);\n   tex.surface.alignment = read<uint32_t>(fh);\n   tex.surface.pitch = read<uint32_t>(fh);\n\n   for (auto &mipLevelOffset : tex.surface.mipLevelOffset) {\n      mipLevelOffset = read<uint32_t>(fh);\n   }\n\n   tex.viewFirstMip = read<uint32_t>(fh);\n   tex.viewNumMips = read<uint32_t>(fh);\n   tex.viewFirstSlice = read<uint32_t>(fh);\n   tex.viewNumSlices = read<uint32_t>(fh);\n   tex.compMap = read<uint32_t>(fh);\n\n   tex.regs.word0 = latte::SQ_TEX_RESOURCE_WORD0_N::get(read<uint32_t>(fh));\n   tex.regs.word1 = latte::SQ_TEX_RESOURCE_WORD1_N::get(read<uint32_t>(fh));\n   tex.regs.word4 = latte::SQ_TEX_RESOURCE_WORD4_N::get(read<uint32_t>(fh));\n   tex.regs.word5 = latte::SQ_TEX_RESOURCE_WORD5_N::get(read<uint32_t>(fh));\n   tex.regs.word6 = latte::SQ_TEX_RESOURCE_WORD6_N::get(read<uint32_t>(fh));\n\n   if (block.majorVersion == 1) {\n      decaf_check(fh.pos - blockBase == 0x9c);\n   }\n\n   fh.pos = blockBase + block.dataSize;\n   return true;\n}\n\nstatic bool\nreadTextureImage(MemoryFile &fh,\n                 GFDBlockHeader &block,\n                 GFDTexture &tex)\n{\n   if (block.dataSize != tex.surface.image.capacity()) {\n      throw GFDReadException { fmt::format(\"TextureImage block.dataSize {} != tex.surface.imageSize {}\",\n                                           block.dataSize, tex.surface.image.capacity()) };\n   }\n\n   readBinary(fh, tex.surface.image, block.dataSize);\n   return true;\n}\n\nstatic bool\nreadTextureMipmap(MemoryFile &fh,\n                  GFDBlockHeader &block,\n                  GFDTexture &tex)\n{\n   if (block.dataSize != tex.surface.mipmap.capacity()) {\n      throw GFDReadException { fmt::format(\"TextureMipmap block.dataSize {} != tex.surface.mipmapSize {}\",\n                                           block.dataSize, tex.surface.mipmap.capacity()) };\n   }\n\n   readBinary(fh, tex.surface.mipmap, block.dataSize);\n   return true;\n}\n\nbool\nreadFile(GFDFile &file,\n         const std::string &path)\n{\n   MemoryFile fh;\n   GFDBlock block;\n   GFDFileHeader header;\n\n   if (!openFile(path, fh)) {\n      return false;\n   }\n\n   if (!readFileHeader(fh, header)) {\n      return false;\n   }\n\n   while (readBlockHeader(fh, block.header)) {\n      auto pos = fh.pos;\n\n      if (block.header.type == GFDBlockType::EndOfFile) {\n         break;\n      }\n\n      switch (block.header.type) {\n      case GFDBlockType::VertexShaderHeader:\n      {\n         file.vertexShaders.emplace_back();\n         readVertexShaderHeader(fh, block.header, file.vertexShaders.back());\n         break;\n      }\n      case GFDBlockType::VertexShaderProgram:\n      {\n         decaf_check(file.vertexShaders.size());\n         readVertexShaderProgram(fh, block.header, file.vertexShaders.back());\n         break;\n      }\n      case GFDBlockType::PixelShaderHeader:\n      {\n         file.pixelShaders.emplace_back();\n         readPixelShaderHeader(fh, block.header, file.pixelShaders.back());\n         break;\n      }\n      case GFDBlockType::PixelShaderProgram:\n      {\n         decaf_check(file.pixelShaders.size());\n         readPixelShaderProgram(fh, block.header, file.pixelShaders.back());\n         break;\n      }\n      case GFDBlockType::GeometryShaderHeader:\n      {\n         file.geometryShaders.emplace_back();\n         readGeometryShaderHeader(fh, block.header, file.geometryShaders.back());\n         break;\n      }\n      case GFDBlockType::GeometryShaderProgram:\n      {\n         decaf_check(file.geometryShaders.size());\n         readGeometryShaderProgram(fh, block.header, file.geometryShaders.back());\n         break;\n      }\n      case GFDBlockType::GeometryShaderCopyProgram:\n      {\n         decaf_check(file.geometryShaders.size());\n         readGeometryShaderCopyProgram(fh, block.header, file.geometryShaders.back());\n         break;\n      }\n      case GFDBlockType::TextureHeader:\n      {\n         file.textures.emplace_back();\n         readTextureHeader(fh, block.header, file.textures.back());\n         break;\n      }\n      case GFDBlockType::TextureImage:\n      {\n         decaf_check(file.textures.size());\n         readTextureImage(fh, block.header, file.textures.back());\n         break;\n      }\n      case GFDBlockType::TextureMipmap:\n      {\n         decaf_check(file.textures.size());\n         readTextureMipmap(fh, block.header, file.textures.back());\n         break;\n      }\n      case GFDBlockType::ComputeShaderHeader:\n      case GFDBlockType::ComputeShaderProgram:\n      case GFDBlockType::Padding:\n      default:\n         fh.pos += block.header.dataSize;\n      }\n\n      decaf_check(fh.pos == pos + block.header.dataSize);\n   }\n\n   return true;\n}\n\n} // namespace gfd\n"
  },
  {
    "path": "src/libgfd/src/gfd_write.cpp",
    "content": "#include \"gfd.h\"\n#include <common/align.h>\n#include <fstream>\n\nnamespace gfd\n{\n\nstruct DataPatch\n{\n   size_t offset;\n   size_t target;\n};\n\nstruct TextPatch\n{\n   size_t offset;\n   size_t target;\n   const char *text;\n};\n\nusing MemoryFile = std::vector<uint8_t>;\n\ntemplate<typename Type>\ninline void\nwriteAt(MemoryFile &fh,\n        size_t pos,\n        Type value)\n{\n   *reinterpret_cast<Type *>(fh.data() + pos) = byte_swap(value);\n}\n\ntemplate<typename Type>\ninline void\nwrite(MemoryFile &fh,\n      Type value)\n{\n   auto pos = fh.size();\n   fh.resize(pos + sizeof(Type));\n   *reinterpret_cast<Type *>(fh.data() + pos) = byte_swap(value);\n}\n\ninline void\nwriteNullTerminatedString(MemoryFile &fh,\n                          const char *str)\n{\n   auto pos = fh.size();\n   auto len = strlen(str) + 1;\n   fh.resize(align_up(pos + len, 4));\n   std::memcpy(fh.data() + pos, str, len);\n}\n\ninline void\nwriteBinary(MemoryFile &fh,\n            const std::vector<uint8_t> &data)\n{\n   auto pos = fh.size();\n   auto len = data.size();\n   fh.resize(pos + len);\n   std::memcpy(fh.data() + pos, data.data(), len);\n}\n\nstatic bool\nwriteGX2RBuffer(MemoryFile &fh,\n                const GFDRBuffer &buffer)\n{\n   write<uint32_t>(fh, static_cast<uint32_t>(buffer.flags));\n   write<uint32_t>(fh, buffer.elemSize);\n   write<uint32_t>(fh, buffer.elemCount);\n   write<uint32_t>(fh, 0); // buffer.buffer\n   return true;\n}\n\nstatic bool\nwriteUniformBlocksData(std::vector<uint8_t> &fh,\n                       DataPatch &patch,\n                       std::vector<DataPatch> &dataPatches,\n                       std::vector<TextPatch> &textPatches,\n                       const std::vector<GFDUniformBlock> &uniformBlocks)\n{\n   if (uniformBlocks.size() > 0) {\n      patch.target = fh.size();\n      dataPatches.push_back(patch);\n\n      for (auto &block : uniformBlocks) {\n         textPatches.push_back(TextPatch { fh.size(), 0, block.name.c_str() });\n         write<uint32_t>(fh, 0); // block.name\n         write<uint32_t>(fh, block.offset);\n         write<uint32_t>(fh, block.size);\n      }\n   }\n\n   return true;\n}\n\nstatic bool\nwriteUniformVarsData(std::vector<uint8_t> &fh,\n                     DataPatch &patch,\n                     std::vector<DataPatch> &dataPatches,\n                     std::vector<TextPatch> &textPatches,\n                     const std::vector<GFDUniformVar> &uniformVars)\n{\n   if (uniformVars.size() > 0) {\n      patch.target = fh.size();\n      dataPatches.push_back(patch);\n\n      for (auto &var : uniformVars) {\n         textPatches.push_back(TextPatch { fh.size(), 0, var.name.c_str() });\n         write<uint32_t>(fh, 0); // var.name\n         write<uint32_t>(fh, static_cast<uint32_t>(var.type));\n         write<uint32_t>(fh, var.count);\n         write<uint32_t>(fh, var.offset);\n         write<int32_t>(fh, var.block);\n      }\n   }\n\n   return true;\n}\n\nstatic bool\nwriteInitialValuesData(std::vector<uint8_t> &fh,\n                       DataPatch &patch,\n                       std::vector<DataPatch> &dataPatches,\n                       std::vector<TextPatch> &textPatches,\n                       const std::vector<GFDUniformInitialValue> &initialValues)\n{\n   if (initialValues.size() > 0) {\n      patch.target = fh.size();\n      dataPatches.push_back(patch);\n\n      for (auto &var : initialValues) {\n         for (auto &value : var.value) {\n            write<float>(fh, value);\n         }\n\n         write<uint32_t>(fh, var.offset);\n      }\n   }\n\n   return true;\n}\n\nstatic bool\nwriteLoopVarsData(std::vector<uint8_t> &fh,\n                  DataPatch &patch,\n                  std::vector<DataPatch> &dataPatches,\n                  std::vector<TextPatch> &textPatches,\n                  const std::vector<GFDLoopVar> &loopVars)\n{\n   if (loopVars.size() > 0) {\n      patch.target = fh.size();\n      dataPatches.push_back(patch);\n\n      for (auto &var : loopVars) {\n         write<uint32_t>(fh, var.offset);\n         write<uint32_t>(fh, var.value);\n      }\n   }\n\n   return true;\n}\n\nstatic bool\nwriteSamplerVarsData(std::vector<uint8_t> &fh,\n                     DataPatch &patch,\n                     std::vector<DataPatch> &dataPatches,\n                     std::vector<TextPatch> &textPatches,\n                     const std::vector<GFDSamplerVar> &samplerVars)\n{\n   if (samplerVars.size() > 0) {\n      patch.target = fh.size();\n      dataPatches.push_back(patch);\n\n      for (auto &var : samplerVars) {\n         textPatches.push_back(TextPatch { fh.size(), 0, var.name.c_str() });\n         write<uint32_t>(fh, 0); // var.name\n         write<uint32_t>(fh, static_cast<uint32_t>(var.type));\n         write<uint32_t>(fh, var.location);\n      }\n   }\n\n   return true;\n}\n\nstatic bool\nwriteAttribVarsData(std::vector<uint8_t> &fh,\n                    DataPatch &patch,\n                    std::vector<DataPatch> &dataPatches,\n                    std::vector<TextPatch> &textPatches,\n                    const std::vector<GFDAttribVar> &attribVars)\n{\n   if (attribVars.size() > 0) {\n      patch.target = fh.size();\n      dataPatches.push_back(patch);\n\n      for (auto &var : attribVars) {\n         textPatches.push_back(TextPatch { fh.size(), 0, var.name.c_str() });\n         write<uint32_t>(fh, 0); // var.name\n         write<uint32_t>(fh, static_cast<uint32_t>(var.type));\n         write<uint32_t>(fh, var.count);\n         write<uint32_t>(fh, var.location);\n      }\n   }\n\n   return true;\n}\n\nstatic bool\nwriteRelocationData(std::vector<uint8_t> &fh,\n                    std::vector<DataPatch> &dataPatches,\n                    std::vector<TextPatch> &textPatches)\n{\n   // Now write text\n   auto textOffset = fh.size();\n\n   for (auto &patch : textPatches) {\n      patch.target = fh.size();\n      writeNullTerminatedString(fh, patch.text);\n   }\n\n   auto textSize = fh.size() - textOffset;\n\n   auto patchOffset = fh.size();\n   auto dataSize = patchOffset;\n   auto dataOffset = 0;\n\n   // Now write patches\n   for (auto &patch : dataPatches) {\n      writeAt<uint32_t>(fh, patch.offset, static_cast<uint32_t>(patch.target) | GFDPatchData);\n      write<uint32_t>(fh, static_cast<uint32_t>(patch.offset) | GFDPatchData);\n   }\n\n   for (auto &patch : textPatches) {\n      writeAt<uint32_t>(fh, patch.offset, static_cast<uint32_t>(patch.target) | GFDPatchText);\n      write<uint32_t>(fh, static_cast<uint32_t>(patch.offset) | GFDPatchText);\n   }\n\n   // Write relocation header\n   write<uint32_t>(fh, GFDRelocationHeader::Magic);\n   write<uint32_t>(fh, GFDRelocationHeader::HeaderSize);\n   write<uint32_t>(fh, 0); // unk1\n   write<uint32_t>(fh, static_cast<uint32_t>(dataSize));\n   write<uint32_t>(fh, static_cast<uint32_t>(dataOffset) | GFDPatchData);\n   write<uint32_t>(fh, static_cast<uint32_t>(textSize));\n   write<uint32_t>(fh, static_cast<uint32_t>(textOffset) | GFDPatchData);\n   write<uint32_t>(fh, 0); // patchBase\n   write<uint32_t>(fh, static_cast<uint32_t>(dataPatches.size() + textPatches.size()));\n   write<uint32_t>(fh, static_cast<uint32_t>(patchOffset) | GFDPatchData);\n   return true;\n}\n\nstatic bool\nwriteVertexShader(std::vector<uint8_t> &fh,\n                  const GFDVertexShader &vsh)\n{\n   std::vector<uint8_t> text;\n   std::vector<DataPatch> dataPatches;\n   std::vector<TextPatch> textPatches;\n   decaf_check(fh.size() == 0);\n\n   write<uint32_t>(fh, vsh.regs.sq_pgm_resources_vs.value);\n   write<uint32_t>(fh, vsh.regs.vgt_primitiveid_en.value);\n   write<uint32_t>(fh, vsh.regs.spi_vs_out_config.value);\n   write<uint32_t>(fh, vsh.regs.num_spi_vs_out_id);\n\n   for (auto &spi_vs_out_id : vsh.regs.spi_vs_out_id) {\n      write<uint32_t>(fh, spi_vs_out_id.value);\n   }\n\n   write<uint32_t>(fh, vsh.regs.pa_cl_vs_out_cntl.value);\n   write<uint32_t>(fh, vsh.regs.sq_vtx_semantic_clear.value);\n   write<uint32_t>(fh, vsh.regs.num_sq_vtx_semantic);\n\n   for (auto &sq_vtx_semantic : vsh.regs.sq_vtx_semantic) {\n      write<uint32_t>(fh, sq_vtx_semantic.value);\n   }\n\n   write<uint32_t>(fh, vsh.regs.vgt_strmout_buffer_en.value);\n   write<uint32_t>(fh, vsh.regs.vgt_vertex_reuse_block_cntl.value);\n   write<uint32_t>(fh, vsh.regs.vgt_hos_reuse_depth.value);\n\n   write<uint32_t>(fh, static_cast<uint32_t>(vsh.data.size()));\n   write<uint32_t>(fh, 0); // vsh.data\n   write<uint32_t>(fh, static_cast<uint32_t>(vsh.mode));\n\n   auto uniformBlocksPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(vsh.uniformBlocks.size()));\n   write<uint32_t>(fh, 0); // vsh.uniformBlocks.data\n\n   auto uniformVarsPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(vsh.uniformVars.size()));\n   write<uint32_t>(fh, 0); // vsh.uniformVars.data\n\n   auto initialValuesPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(vsh.initialValues.size()));\n   write<uint32_t>(fh, 0); // vsh.initialValues.data\n\n   auto loopVarsPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(vsh.loopVars.size()));\n   write<uint32_t>(fh, 0); // vsh.loopVars.data\n\n   auto samplerVarsPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(vsh.samplerVars.size()));\n   write<uint32_t>(fh, 0); // vsh.samplerVars.data\n\n   auto attribVarsPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(vsh.attribVars.size()));\n   write<uint32_t>(fh, 0); // vsh.attribVars.data\n\n   write<uint32_t>(fh, vsh.ringItemSize);\n   write<uint32_t>(fh, vsh.hasStreamOut ? 1 : 0);\n\n   for (auto &stride : vsh.streamOutStride) {\n      write<uint32_t>(fh, stride);\n   }\n\n   writeGX2RBuffer(fh, vsh.gx2rData);\n   decaf_check(fh.size() == 0x134);\n\n   // Now write relocated data\n   writeUniformBlocksData(fh, uniformBlocksPatch, dataPatches, textPatches, vsh.uniformBlocks);\n   writeUniformVarsData(fh, uniformVarsPatch, dataPatches, textPatches, vsh.uniformVars);\n   writeInitialValuesData(fh, initialValuesPatch, dataPatches, textPatches, vsh.initialValues);\n   writeLoopVarsData(fh, loopVarsPatch, dataPatches, textPatches, vsh.loopVars);\n   writeSamplerVarsData(fh, samplerVarsPatch, dataPatches, textPatches, vsh.samplerVars);\n   writeAttribVarsData(fh, attribVarsPatch, dataPatches, textPatches, vsh.attribVars);\n   writeRelocationData(fh, dataPatches, textPatches);\n   return true;\n}\n\nstatic bool\nwritePixelShader(std::vector<uint8_t> &fh,\n                 const GFDPixelShader &psh)\n{\n   std::vector<uint8_t> text;\n   std::vector<DataPatch> dataPatches;\n   std::vector<TextPatch> textPatches;\n   decaf_check(fh.size() == 0);\n\n   write<uint32_t>(fh, psh.regs.sq_pgm_resources_ps.value);\n   write<uint32_t>(fh, psh.regs.sq_pgm_exports_ps.value);\n   write<uint32_t>(fh, psh.regs.spi_ps_in_control_0.value);\n   write<uint32_t>(fh, psh.regs.spi_ps_in_control_1.value);\n   write<uint32_t>(fh, psh.regs.num_spi_ps_input_cntl);\n\n   for (auto &spi_ps_input_cntl : psh.regs.spi_ps_input_cntls) {\n      write<uint32_t>(fh, spi_ps_input_cntl.value);\n   }\n\n   write<uint32_t>(fh, psh.regs.cb_shader_mask.value);\n   write<uint32_t>(fh, psh.regs.cb_shader_control.value);\n   write<uint32_t>(fh, psh.regs.db_shader_control.value);\n   write<uint32_t>(fh, psh.regs.spi_input_z.value);\n\n   write<uint32_t>(fh, static_cast<uint32_t>(psh.data.size()));\n   write<uint32_t>(fh, 0); // psh.data\n   write<uint32_t>(fh, static_cast<uint32_t>(psh.mode));\n\n   auto uniformBlocksPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(psh.uniformBlocks.size()));\n   write<uint32_t>(fh, 0); // vsh.uniformBlocks.data\n\n   auto uniformVarsPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(psh.uniformVars.size()));\n   write<uint32_t>(fh, 0); // vsh.uniformVars.data\n\n   auto initialValuesPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(psh.initialValues.size()));\n   write<uint32_t>(fh, 0); // vsh.initialValues.data\n\n   auto loopVarsPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(psh.loopVars.size()));\n   write<uint32_t>(fh, 0); // vsh.loopVars.data\n\n   auto samplerVarsPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(psh.samplerVars.size()));\n   write<uint32_t>(fh, 0); // vsh.samplerVars.data\n\n   writeGX2RBuffer(fh, psh.gx2rData);\n   decaf_check(fh.size() == 0xE8);\n\n   // Now write relocated data\n   writeUniformBlocksData(fh, uniformBlocksPatch, dataPatches, textPatches, psh.uniformBlocks);\n   writeUniformVarsData(fh, uniformVarsPatch, dataPatches, textPatches, psh.uniformVars);\n   writeInitialValuesData(fh, initialValuesPatch, dataPatches, textPatches, psh.initialValues);\n   writeLoopVarsData(fh, loopVarsPatch, dataPatches, textPatches, psh.loopVars);\n   writeSamplerVarsData(fh, samplerVarsPatch, dataPatches, textPatches, psh.samplerVars);\n   writeRelocationData(fh, dataPatches, textPatches);\n   return true;\n}\n\nstatic bool\nwriteGeometryShader(std::vector<uint8_t> &fh,\n                    const GFDGeometryShader &gsh)\n{\n   std::vector<uint8_t> text;\n   std::vector<DataPatch> dataPatches;\n   std::vector<TextPatch> textPatches;\n   decaf_check(fh.size() == 0);\n\n   write<uint32_t>(fh, gsh.regs.sq_pgm_resources_gs.value);\n   write<uint32_t>(fh, gsh.regs.vgt_gs_out_prim_type.value);\n   write<uint32_t>(fh, gsh.regs.vgt_gs_mode.value);\n   write<uint32_t>(fh, gsh.regs.pa_cl_vs_out_cntl.value);\n   write<uint32_t>(fh, gsh.regs.sq_pgm_resources_vs.value);\n   write<uint32_t>(fh, gsh.regs.sq_gs_vert_itemsize.value);\n   write<uint32_t>(fh, gsh.regs.spi_vs_out_config.value);\n   write<uint32_t>(fh, gsh.regs.num_spi_vs_out_id);\n\n   for (auto &spi_vs_out_id : gsh.regs.spi_vs_out_id) {\n      write<uint32_t>(fh, spi_vs_out_id.value);\n   }\n\n   write<uint32_t>(fh, gsh.regs.vgt_strmout_buffer_en.value);\n\n   write<uint32_t>(fh, static_cast<uint32_t>(gsh.data.size()));\n   write<uint32_t>(fh, 0); // gsh.data\n\n   write<uint32_t>(fh, static_cast<uint32_t>(gsh.vertexShaderData.size()));\n   write<uint32_t>(fh, 0); // gsh.vertexShaderData\n\n   write<uint32_t>(fh, static_cast<uint32_t>(gsh.mode));\n\n   auto uniformBlocksPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(gsh.uniformBlocks.size()));\n   write<uint32_t>(fh, 0); // vsh.uniformBlocks.data\n\n   auto uniformVarsPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(gsh.uniformVars.size()));\n   write<uint32_t>(fh, 0); // vsh.uniformVars.data\n\n   auto initialValuesPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(gsh.initialValues.size()));\n   write<uint32_t>(fh, 0); // vsh.initialValues.data\n\n   auto loopVarsPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(gsh.loopVars.size()));\n   write<uint32_t>(fh, 0); // vsh.loopVars.data\n\n   auto samplerVarsPatch = DataPatch { fh.size() + 4, 0 };\n   write<uint32_t>(fh, static_cast<uint32_t>(gsh.samplerVars.size()));\n   write<uint32_t>(fh, 0); // vsh.samplerVars.data\n\n   write<uint32_t>(fh, gsh.ringItemSize);\n   write<uint32_t>(fh, gsh.hasStreamOut ? 1 : 0);\n\n   for (auto &stride : gsh.streamOutStride) {\n      write<uint32_t>(fh, stride);\n   }\n\n   writeGX2RBuffer(fh, gsh.gx2rData);\n   writeGX2RBuffer(fh, gsh.gx2rVertexShaderData);\n   decaf_check(fh.size() == 0xC0);\n\n   // Now write relocated data\n   writeUniformBlocksData(fh, uniformBlocksPatch, dataPatches, textPatches, gsh.uniformBlocks);\n   writeUniformVarsData(fh, uniformVarsPatch, dataPatches, textPatches, gsh.uniformVars);\n   writeInitialValuesData(fh, initialValuesPatch, dataPatches, textPatches, gsh.initialValues);\n   writeLoopVarsData(fh, loopVarsPatch, dataPatches, textPatches, gsh.loopVars);\n   writeSamplerVarsData(fh, samplerVarsPatch, dataPatches, textPatches, gsh.samplerVars);\n   writeRelocationData(fh, dataPatches, textPatches);\n   return true;\n}\n\nstatic bool\nwriteTexture(MemoryFile &fh,\n             const GFDTexture &texture)\n{\n   write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.dim));\n   write<uint32_t>(fh, texture.surface.width);\n   write<uint32_t>(fh, texture.surface.height);\n   write<uint32_t>(fh, texture.surface.depth);\n   write<uint32_t>(fh, texture.surface.mipLevels);\n   write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.format));\n   write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.aa));\n   write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.use));\n\n   write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.image.size()));\n   write<uint32_t>(fh, 0); // texture.surface.image\n\n   write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.mipmap.size()));\n   write<uint32_t>(fh, 0); // texture.surface.mipmap\n\n   write<uint32_t>(fh, static_cast<uint32_t>(texture.surface.tileMode));\n   write<uint32_t>(fh, texture.surface.swizzle);\n   write<uint32_t>(fh, texture.surface.alignment);\n   write<uint32_t>(fh, texture.surface.pitch);\n\n   for (auto &mipLevelOffset : texture.surface.mipLevelOffset) {\n      write<uint32_t>(fh, mipLevelOffset);\n   }\n\n   write<uint32_t>(fh, texture.viewFirstMip);\n   write<uint32_t>(fh, texture.viewNumMips);\n   write<uint32_t>(fh, texture.viewFirstSlice);\n   write<uint32_t>(fh, texture.viewNumSlices);\n   write<uint32_t>(fh, texture.compMap);\n\n   write<uint32_t>(fh, texture.regs.word0.value);\n   write<uint32_t>(fh, texture.regs.word1.value);\n   write<uint32_t>(fh, texture.regs.word4.value);\n   write<uint32_t>(fh, texture.regs.word5.value);\n   write<uint32_t>(fh, texture.regs.word6.value);\n   decaf_check(fh.size() == 0x9C);\n   return true;\n}\n\nstatic bool\nwriteBlock(MemoryFile &fh,\n           const GFDBlockHeader &header,\n           const std::vector<uint8_t> &data)\n{\n   write<uint32_t>(fh, GFDBlockHeader::Magic);\n   write<uint32_t>(fh, GFDBlockHeader::HeaderSize);\n   write<uint32_t>(fh, header.majorVersion);\n   write<uint32_t>(fh, header.minorVersion);\n   write<uint32_t>(fh, static_cast<uint32_t>(header.type));\n   write<uint32_t>(fh, static_cast<uint32_t>(data.size()));\n   write<uint32_t>(fh, header.id);\n   write<uint32_t>(fh, header.index);\n   writeBinary(fh, data);\n   return true;\n}\n\nstatic bool\nalignNextBlock(MemoryFile &fh,\n               uint32_t &blockID)\n{\n   auto paddingSize = (0x200 - ((fh.size() + sizeof(GFDBlockHeader)) & 0x1FF)) & 0x1FF;\n\n   if (paddingSize) {\n      if (paddingSize < sizeof(GFDBlockHeader)) {\n         paddingSize += 0x200;\n      }\n\n      paddingSize -= sizeof(GFDBlockHeader);\n\n      GFDBlockHeader paddingHeader;\n      paddingHeader.majorVersion = GFDBlockMajorVersion;\n      paddingHeader.minorVersion = 0;\n      paddingHeader.type = GFDBlockType::Padding;\n      paddingHeader.id = blockID++;\n      paddingHeader.index = 0;\n\n      writeBlock(fh, paddingHeader, std::vector<uint8_t>(paddingSize, 0));\n   }\n\n   return true;\n}\n\nbool\nwriteFile(const GFDFile &file,\n          const std::string &path,\n          bool align)\n{\n   MemoryFile fh;\n   auto blockID = uint32_t { 0 };\n\n   // Write File Header\n   write<uint32_t>(fh, GFDFileHeader::Magic);\n   write<uint32_t>(fh, GFDFileHeader::HeaderSize);\n   write<uint32_t>(fh, GFDFileMajorVersion);\n   write<uint32_t>(fh, GFDFileMinorVersion);\n   write<uint32_t>(fh, GFDFileGpuVersion);\n   write<uint32_t>(fh, align ? 1 : 0); // align\n   write<uint32_t>(fh, 0); // unk1\n   write<uint32_t>(fh, 0); // unk2\n\n   // Write vertex shaders\n   for (auto i = 0u; i < file.vertexShaders.size(); ++i) {\n      std::vector<uint8_t> vertexShaderHeader;\n      writeVertexShader(vertexShaderHeader, file.vertexShaders[i]);\n\n      GFDBlockHeader vshHeader;\n      vshHeader.majorVersion = GFDBlockMajorVersion;\n      vshHeader.minorVersion = 0;\n      vshHeader.type = GFDBlockType::VertexShaderHeader;\n      vshHeader.id = blockID++;\n      vshHeader.index = i;\n      writeBlock(fh, vshHeader, vertexShaderHeader);\n\n      if (align) {\n         alignNextBlock(fh, blockID);\n      }\n\n      GFDBlockHeader dataHeader;\n      dataHeader.majorVersion = GFDBlockMajorVersion;\n      dataHeader.minorVersion = 0;\n      dataHeader.type = GFDBlockType::VertexShaderProgram;\n      dataHeader.id = blockID++;\n      dataHeader.index = i;\n      writeBlock(fh, dataHeader, file.vertexShaders[i].data);\n   }\n\n   // Write pixel shaders\n   for (auto i = 0u; i < file.pixelShaders.size(); ++i) {\n      std::vector<uint8_t> pixelShaderHeader;\n      writePixelShader(pixelShaderHeader, file.pixelShaders[i]);\n\n      GFDBlockHeader pshHeader;\n      pshHeader.majorVersion = GFDBlockMajorVersion;\n      pshHeader.minorVersion = 0;\n      pshHeader.type = GFDBlockType::PixelShaderHeader;\n      pshHeader.id = blockID++;\n      pshHeader.index = i;\n      writeBlock(fh, pshHeader, pixelShaderHeader);\n\n      if (align) {\n         alignNextBlock(fh, blockID);\n      }\n\n      GFDBlockHeader dataHeader;\n      dataHeader.majorVersion = GFDBlockMajorVersion;\n      dataHeader.minorVersion = 0;\n      dataHeader.type = GFDBlockType::PixelShaderProgram;\n      dataHeader.id = blockID++;\n      dataHeader.index = i;\n      writeBlock(fh, dataHeader, file.pixelShaders[i].data);\n   }\n\n   // Write geometry shaders\n   for (auto i = 0u; i < file.geometryShaders.size(); ++i) {\n      std::vector<uint8_t> geometryShaderHeader;\n      writeGeometryShader(geometryShaderHeader, file.geometryShaders[i]);\n\n      GFDBlockHeader gshHeader;\n      gshHeader.majorVersion = GFDBlockMajorVersion;\n      gshHeader.minorVersion = 0;\n      gshHeader.type = GFDBlockType::GeometryShaderHeader;\n      gshHeader.id = blockID++;\n      gshHeader.index = i;\n      writeBlock(fh, gshHeader, geometryShaderHeader);\n\n      if (align) {\n         alignNextBlock(fh, blockID);\n      }\n\n      if (file.geometryShaders[i].data.size()) {\n         GFDBlockHeader dataHeader;\n         dataHeader.majorVersion = GFDBlockMajorVersion;\n         dataHeader.minorVersion = 0;\n         dataHeader.type = GFDBlockType::GeometryShaderProgram;\n         dataHeader.id = blockID++;\n         dataHeader.index = i;\n         writeBlock(fh, dataHeader, file.geometryShaders[i].data);\n      }\n\n      if (align) {\n         alignNextBlock(fh, blockID);\n      }\n\n      if (file.geometryShaders[i].vertexShaderData.size()) {\n         GFDBlockHeader dataHeader;\n         dataHeader.majorVersion = GFDBlockMajorVersion;\n         dataHeader.minorVersion = 0;\n         dataHeader.type = GFDBlockType::GeometryShaderCopyProgram;\n         dataHeader.id = blockID++;\n         dataHeader.index = i;\n         writeBlock(fh, dataHeader, file.geometryShaders[i].vertexShaderData);\n      }\n   }\n\n   // Write textures\n   for (auto i = 0u; i < file.textures.size(); ++i) {\n      std::vector<uint8_t> textureHeader;\n      writeTexture(textureHeader, file.textures[i]);\n\n      GFDBlockHeader texHeader;\n      texHeader.majorVersion = GFDBlockMajorVersion;\n      texHeader.minorVersion = 0;\n      texHeader.type = GFDBlockType::TextureHeader;\n      texHeader.id = blockID++;\n      texHeader.index = i;\n      writeBlock(fh, texHeader, textureHeader);\n\n      if (align) {\n         alignNextBlock(fh, blockID);\n      }\n\n      GFDBlockHeader imageHeader;\n      imageHeader.majorVersion = GFDBlockMajorVersion;\n      imageHeader.minorVersion = 0;\n      imageHeader.type = GFDBlockType::TextureImage;\n      imageHeader.id = blockID++;\n      imageHeader.index = i;\n      writeBlock(fh, imageHeader, file.textures[i].surface.image);\n\n      if (align) {\n         alignNextBlock(fh, blockID);\n      }\n\n      if (file.textures[i].surface.mipmap.size()) {\n         GFDBlockHeader mipmapHeader;\n         mipmapHeader.majorVersion = GFDBlockMajorVersion;\n         mipmapHeader.minorVersion = 0;\n         mipmapHeader.type = GFDBlockType::TextureImage;\n         mipmapHeader.id = blockID++;\n         mipmapHeader.index = i;\n         writeBlock(fh, mipmapHeader, file.textures[i].surface.mipmap);\n      }\n   }\n\n   // Write EOF\n   GFDBlockHeader eofHeader;\n   eofHeader.majorVersion = GFDBlockMajorVersion;\n   eofHeader.minorVersion = 0;\n   eofHeader.type = GFDBlockType::EndOfFile;\n   eofHeader.id = blockID++;\n   eofHeader.index = 0;\n   writeBlock(fh, eofHeader, {});\n\n   std::ofstream out { path, std::ofstream::binary };\n   out.write(reinterpret_cast<const char *>(fh.data()), fh.size());\n   return true;\n}\n\n} // namespace gfd\n"
  },
  {
    "path": "src/libgpu/CMakeLists.txt",
    "content": "project(libgpu)\n\ninclude_directories(\".\")\ninclude_directories(\"src\")\ninclude_directories(\"vulkan_shaders\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\nfile(GLOB_RECURSE INLINE_FILES *.inl)\nfile(GLOB_RECURSE VULKANSHADER_FILES vulkan_shaders/*)\n\nif(DECAF_VULKAN)\n    find_program(GLSLANG_VALIDATOR NAMES glslangValidator\n                HINTS \"${Vulkan_INCLUDE_DIR}/../Bin\")\n    find_program(SPIRV_OPT NAMES spirv-opt\n                HINTS \"${Vulkan_INCLUDE_DIR}/../Bin\")\n\n    set(GENERATED_BASEPATH \"${PROJECT_BINARY_DIR}/generated\")\n    set(SHDRBIN_BASEPATH \"${GENERATED_BASEPATH}/vulkan_shaders_bin\")\n\n    include_directories(${GENERATED_BASEPATH})\n\n    if(DECAF_PLATFORM_COCOA)\n        set(SHADER_DEFINES \"-DDECAF_MVK_COMPAT\")\n    endif()\n\n    macro(compile_vulkan_shader OUTFILE INFILE)\n        set(BIN2C \"${CMAKE_SOURCE_DIR}/libraries/bin2c.cmake\")\n        set(INPATH \"${PROJECT_SOURCE_DIR}/vulkan_shaders/${INFILE}\")\n        set(OUTPATH \"${SHDRBIN_BASEPATH}/${OUTFILE}\")\n        add_custom_command(\n            OUTPUT \"${OUTPATH}.h\" \"${OUTPATH}.cpp\"\n            COMMAND ${CMAKE_COMMAND} -E make_directory \"${SHDRBIN_BASEPATH}\"\n            COMMAND ${GLSLANG_VALIDATOR} -V ${SHADER_DEFINES} -o \"${OUTPATH}\" \"${INPATH}\"\n            COMMAND ${CMAKE_COMMAND} ARGS \"-DOUTPUT_C=${OUTPATH}.cpp\" \"-DOUTPUT_H=${OUTPATH}.h\" \"-DINPUT_FILES=${OUTPATH}\" -P \"${BIN2C}\"\n            DEPENDS \"${INPATH}\" \"${BIN2C}\"\n            VERBATIM)\n        list(APPEND VK_BIN_FILES \"${OUTPATH}.h\")\n        list(APPEND VK_BIN_FILES \"${OUTPATH}.cpp\")\n        set_source_files_properties(\"${OUTPATH}.cpp\" PROPERTIES GENERATED TRUE)\n        set_source_files_properties(\"${OUTPATH}.h\" PROPERTIES GENERATED TRUE)\n    endmacro()\n\n    # compile the main retiling shader itself\n    compile_vulkan_shader(\"gpu7_tiling.comp.spv\" \"gpu7_tiling.comp.glsl\")\n    add_custom_target(libgpu-shaders DEPENDS ${VK_BIN_FILES})\nendif()\n\nadd_library(libgpu STATIC ${SOURCE_FILES} ${HEADER_FILES} ${INLINE_FILES} ${VULKANSHADER_FILES} ${VK_BIN_FILES})\nGroupSources(\"Header Files/latte\" latte)\nGroupSources(\"Source Files\" src)\nGroupSources(\"Vulkan Shader Files\" vulkan_shaders)\n\n# We have to do this manually as the files don't exist until the first\n# build occurs, which is after the solution is generated.\nif(DECAF_VULKAN)\n    if(MSVC)\n        add_dependencies(libgpu libgpu-shaders)\n        source_group(\"Generated Files\" FILES ${VK_BIN_FILES})\n    endif()\nendif()\n\ntarget_link_libraries(libgpu\n    common\n    libcpu\n    addrlib)\n\nif(DECAF_PLATFORM_XLIB)\n    target_link_libraries(libgpu ${X11_LIBRARIES})\nendif()\n\nif(DECAF_PLATFORM_XCB)\n    target_link_libraries(libgpu ${XCB_LIBRARIES})\n\n    if(X11_Xau_FOUND)\n        target_link_libraries(libgpu ${X11_Xau_LIB})\n    endif()\n\n    if(X11_Xdmcp_FOUND)\n        target_link_libraries(libgpu ${X11_Xdmcp_LIB})\n    endif()\nendif()\n\nif(DECAF_PLATFORM_WAYLAND)\n    target_link_libraries(libgpu ${WAYLAND_CLIENT_LIBRARIES})\nendif()\n\nif(DECAF_VULKAN)\n    target_link_libraries(libgpu vulkan SPIRV)\nendif()\n\nif(DECAF_PCH)\n    target_precompile_headers(libgpu\n      PRIVATE\n        <common/pch.h>\n        \"latte/latte_registers.h\"\n        \"latte/latte_pm4.h\"\n        \"latte/latte_pm4_commands.h\"\n    )\n\n    if(DECAF_VULKAN)\n        target_precompile_headers(libgpu\n          PRIVATE\n            <common/vulkan_hpp.h>\n            \"src/vulkan/vk_mem_alloc_decaf.h\"\n        )\n    endif()\n\n    AutoGroupPCHFiles()\nendif()\n"
  },
  {
    "path": "src/libgpu/gpu.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace gpu\n{\n\nusing FlipCallbackFn = void(*)();\n\nvoid\nsetFlipCallback(FlipCallbackFn callback);\n\n} // namespace gpu\n"
  },
  {
    "path": "src/libgpu/gpu7_displaylayout.h",
    "content": "#pragma once\n#include <array>\n\nnamespace gpu7\n{\n\nstruct DisplayLayout\n{\n   struct Display\n   {\n      bool visible;\n      float x;\n      float y;\n      float width;\n      float height;\n   };\n\n   Display tv;\n   Display drc;\n   std::array<float, 4> backgroundColour;\n};\n\nstruct DisplayTouchEvent\n{\n   enum Screen\n   {\n      None,\n      Tv,\n      Drc1,\n      Drc2,\n   };\n\n   Screen screen = None;\n   float x = 0;\n   float y = 0;\n};\n\nvoid\nupdateDisplayLayout(DisplayLayout &layout,\n                    float windowWidth,\n                    float windowHeight);\n\nDisplayTouchEvent\ntranslateDisplayTouch(DisplayLayout &layout,\n                      float x,\n                      float y);\n\nstatic inline DisplayLayout\ngetDisplayLayout(float width, float height)\n{\n   DisplayLayout layout;\n   updateDisplayLayout(layout, width, height);\n   return layout;\n}\n\n} // namespace gpu7\n"
  },
  {
    "path": "src/libgpu/gpu7_tiling.h",
    "content": "#pragma once\n#include <array>\n\nnamespace gpu7::tiling\n{\n\n#include <common/enum_start.inl>\n\nENUM_BEG(DataFormat, uint32_t)\n   ENUM_VALUE(Invalid,                    0)\n   ENUM_VALUE(FMT_8,                      1)\n   ENUM_VALUE(FMT_4_4,                    2)\n   ENUM_VALUE(FMT_3_3_2,                  3)\n   ENUM_VALUE(FMT_16,                     5)\n   ENUM_VALUE(FMT_16_FLOAT,               6)\n   ENUM_VALUE(FMT_8_8,                    7)\n   ENUM_VALUE(FMT_5_6_5,                  8)\n   ENUM_VALUE(FMT_6_5_5,                  9)\n   ENUM_VALUE(FMT_1_5_5_5,                10)\n   ENUM_VALUE(FMT_4_4_4_4,                11)\n   ENUM_VALUE(FMT_5_5_5_1,                12)\n   ENUM_VALUE(FMT_32,                     13)\n   ENUM_VALUE(FMT_32_FLOAT,               14)\n   ENUM_VALUE(FMT_16_16,                  15)\n   ENUM_VALUE(FMT_16_16_FLOAT,            16)\n   ENUM_VALUE(FMT_8_24,                   17)\n   ENUM_VALUE(FMT_8_24_FLOAT,             18)\n   ENUM_VALUE(FMT_24_8,                   19)\n   ENUM_VALUE(FMT_24_8_FLOAT,             20)\n   ENUM_VALUE(FMT_10_11_11,               21)\n   ENUM_VALUE(FMT_10_11_11_FLOAT,         22)\n   ENUM_VALUE(FMT_11_11_10,               23)\n   ENUM_VALUE(FMT_11_11_10_FLOAT,         24)\n   ENUM_VALUE(FMT_2_10_10_10,             25)\n   ENUM_VALUE(FMT_8_8_8_8,                26)\n   ENUM_VALUE(FMT_10_10_10_2,             27)\n   ENUM_VALUE(FMT_X24_8_32_FLOAT,         28)\n   ENUM_VALUE(FMT_32_32,                  29)\n   ENUM_VALUE(FMT_32_32_FLOAT,            30)\n   ENUM_VALUE(FMT_16_16_16_16,            31)\n   ENUM_VALUE(FMT_16_16_16_16_FLOAT,      32)\n   ENUM_VALUE(FMT_32_32_32_32,            34)\n   ENUM_VALUE(FMT_32_32_32_32_FLOAT,      35)\n   ENUM_VALUE(FMT_1,                      37)\n   ENUM_VALUE(FMT_GB_GR,                  39)\n   ENUM_VALUE(FMT_BG_RG,                  40)\n   ENUM_VALUE(FMT_32_AS_8,                41)\n   ENUM_VALUE(FMT_32_AS_8_8,              42)\n   ENUM_VALUE(FMT_5_9_9_9_SHAREDEXP,      43)\n   ENUM_VALUE(FMT_8_8_8,                  44)\n   ENUM_VALUE(FMT_16_16_16,               45)\n   ENUM_VALUE(FMT_16_16_16_FLOAT,         46)\n   ENUM_VALUE(FMT_32_32_32,               47)\n   ENUM_VALUE(FMT_32_32_32_FLOAT,         48)\n   ENUM_VALUE(FMT_BC1,                    49)\n   ENUM_VALUE(FMT_BC2,                    50)\n   ENUM_VALUE(FMT_BC3,                    51)\n   ENUM_VALUE(FMT_BC4,                    52)\n   ENUM_VALUE(FMT_BC5,                    53)\n   ENUM_VALUE(FMT_APC0,                   54)\n   ENUM_VALUE(FMT_APC1,                   55)\n   ENUM_VALUE(FMT_APC2,                   56)\n   ENUM_VALUE(FMT_APC3,                   57)\n   ENUM_VALUE(FMT_APC4,                   58)\n   ENUM_VALUE(FMT_APC5,                   59)\n   ENUM_VALUE(FMT_APC6,                   60)\n   ENUM_VALUE(FMT_APC7,                   61)\n   ENUM_VALUE(FMT_CTX1,                   62)\nENUM_END(DataFormat)\n\n// TODO(brett19): Use this...\nENUM_BEG(TileMode, uint32_t)\n   ENUM_VALUE(LinearGeneral,        0x00)\n   ENUM_VALUE(LinearAligned,        0x01)\n   ENUM_VALUE(Micro1DTiledThin1,    0x02)\n   ENUM_VALUE(Micro1DTiledThick,    0x03)\n   ENUM_VALUE(Macro2DTiledThin1,    0x04)\n   ENUM_VALUE(Macro2DTiledThin2,    0x05)\n   ENUM_VALUE(Macro2DTiledThin4,    0x06)\n   ENUM_VALUE(Macro2DTiledThick,    0x07)\n   ENUM_VALUE(Macro2BTiledThin1,    0x08)\n   ENUM_VALUE(Macro2BTiledThin2,    0x09)\n   ENUM_VALUE(Macro2BTiledThin4,    0x0A)\n   ENUM_VALUE(Macro2BTiledThick,    0x0B)\n   ENUM_VALUE(Macro3DTiledThin1,    0x0C)\n   ENUM_VALUE(Macro3DTiledThick,    0x0D)\n   ENUM_VALUE(Macro3BTiledThin1,    0x0E)\n   ENUM_VALUE(Macro3BTiledThick,    0x0F)\n   ENUM_VALUE(LinearSpecial,        0x10)\nENUM_END(TileMode)\n\nENUM_BEG(SurfaceDim, uint32_t)\n   ENUM_VALUE(Texture1D,               0)\n   ENUM_VALUE(Texture2D,               1)\n   ENUM_VALUE(Texture3D,               2)\n   ENUM_VALUE(TextureCube,             3)\n   ENUM_VALUE(Texture1DArray,          4)\n   ENUM_VALUE(Texture2DArray,          5)\n   ENUM_VALUE(Texture2DMSAA,           6)\n   ENUM_VALUE(Texture2DMSAAArray,      7)\nENUM_END(SurfaceDim)\n\nFLAGS_BEG(SurfaceUse, uint32_t)\n   FLAGS_VALUE(None,             0)\n   FLAGS_VALUE(Texture,          1 << 0)\n   FLAGS_VALUE(ColorBuffer,      1 << 1)\n   FLAGS_VALUE(DepthBuffer,      1 << 2)\n   FLAGS_VALUE(ScanBuffer,       1 << 3)\nFLAGS_END(SurfaceUse)\n\n#include <common/enum_end.inl>\n\nstatic constexpr auto MicroTileWidth = 8;\nstatic constexpr auto MicroTileHeight = 8;\nstatic constexpr auto NumPipes = 2;\nstatic constexpr auto NumBanks = 4;\n\nstatic constexpr auto PipeInterleaveBytes = 256;\nstatic constexpr auto NumGroupBits = 8;\nstatic constexpr auto NumPipeBits = 1;\nstatic constexpr auto NumBankBits = 2;\nstatic constexpr auto GroupMask = (1 << NumGroupBits) - 1;\n\nstatic constexpr auto RowSize = 2048;\nstatic constexpr auto SwapSize = 256;\nstatic constexpr auto SampleSplitSize = 2048;\nstatic constexpr auto BankSwapBytes = 256;\n\nstatic constexpr int MacroTileWidth[] = {\n   /* LinearGeneral = */   1,\n   /* LinearAligned = */   1,\n   /* Tiled1DThin1 = */    1,\n   /* Tiled1DThick = */    1,\n   /* Tiled2DThin1 = */    NumBanks,\n   /* Tiled2DThin2 = */    NumBanks / 2,\n   /* Tiled2DThin4 = */    NumBanks / 4,\n   /* Tiled2DThick = */    NumBanks,\n   /* Tiled2BThin1 = */    NumBanks,\n   /* Tiled2BThin2 = */    NumBanks / 2,\n   /* Tiled2BThin4 = */    NumBanks / 4,\n   /* Tiled2BThick = */    NumBanks,\n   /* Tiled3DThin1 = */    NumBanks,\n   /* Tiled3DThick = */    NumBanks,\n   /* Tiled3BThin1 = */    NumBanks,\n   /* Tiled3BThick = */    NumBanks,\n};\n\nstatic constexpr int MacroTileHeight[] = {\n   /* LinearGeneral = */   1,\n   /* LinearAligned = */   1,\n   /* Tiled1DThin1 = */    1,\n   /* Tiled1DThick = */    1,\n   /* Tiled2DThin1 = */    NumPipes,\n   /* Tiled2DThin2 = */    NumPipes * 2,\n   /* Tiled2DThin4 = */    NumPipes * 4,\n   /* Tiled2DThick = */    NumPipes,\n   /* Tiled2BThin1 = */    NumPipes,\n   /* Tiled2BThin2 = */    NumPipes * 2,\n   /* Tiled2BThin4 = */    NumPipes * 4,\n   /* Tiled2BThick = */    NumPipes,\n   /* Tiled3DThin1 = */    NumPipes,\n   /* Tiled3DThick = */    NumPipes,\n   /* Tiled3BThin1 = */    NumPipes,\n   /* Tiled3BThick = */    NumPipes,\n};\n\nstatic constexpr int MicroTileThickness[] = {\n   /* LinearGeneral = */   1,\n   /* LinearAligned = */   1,\n   /* Tiled1DThin1 = */    1,\n   /* Tiled1DThick = */    4,\n   /* Tiled2DThin1 = */    1,\n   /* Tiled2DThin2 = */    1,\n   /* Tiled2DThin4 = */    1,\n   /* Tiled2DThick = */    4,\n   /* Tiled2BThin1 = */    1,\n   /* Tiled2BThin2 = */    1,\n   /* Tiled2BThin4 = */    1,\n   /* Tiled2BThick = */    4,\n   /* Tiled3DThin1 = */    1,\n   /* Tiled3DThick = */    4,\n   /* Tiled3BThin1 = */    1,\n   /* Tiled3BThick = */    4,\n};\n\nstatic constexpr bool TileModeIsMacro[] = {\n   /* LinearGeneral = */   false,\n   /* LinearAligned = */   false,\n   /* Tiled1DThin1 = */    false,\n   /* Tiled1DThick = */    false,\n   /* Tiled2DThin1 = */    true,\n   /* Tiled2DThin2 = */    true,\n   /* Tiled2DThin4 = */    true,\n   /* Tiled2DThick = */    true,\n   /* Tiled2BThin1 = */    true,\n   /* Tiled2BThin2 = */    true,\n   /* Tiled2BThin4 = */    true,\n   /* Tiled2BThick = */    true,\n   /* Tiled3DThin1 = */    true,\n   /* Tiled3DThick = */    true,\n   /* Tiled3BThin1 = */    true,\n   /* Tiled3BThick = */    true,\n};\n\nstatic constexpr bool TileModeIsMacro3X[] = {\n   /* LinearGeneral = */   false,\n   /* LinearAligned = */   false,\n   /* Tiled1DThin1 = */    false,\n   /* Tiled1DThick = */    false,\n   /* Tiled2DThin1 = */    false,\n   /* Tiled2DThin2 = */    false,\n   /* Tiled2DThin4 = */    false,\n   /* Tiled2DThick = */    false,\n   /* Tiled2BThin1 = */    false,\n   /* Tiled2BThin2 = */    false,\n   /* Tiled2BThin4 = */    false,\n   /* Tiled2BThick = */    false,\n   /* Tiled3DThin1 = */    true,\n   /* Tiled3DThick = */    true,\n   /* Tiled3BThin1 = */    true,\n   /* Tiled3BThick = */    true,\n};\n\nstatic constexpr bool TileModeIsBankSwapped[] = {\n   /* LinearGeneral = */   false,\n   /* LinearAligned = */   false,\n   /* Tiled1DThin1 = */    false,\n   /* Tiled1DThick = */    false,\n   /* Tiled2DThin1 = */    false,\n   /* Tiled2DThin2 = */    false,\n   /* Tiled2DThin4 = */    false,\n   /* Tiled2DThick = */    false,\n   /* Tiled2BThin1 = */    true,\n   /* Tiled2BThin2 = */    true,\n   /* Tiled2BThin4 = */    true,\n   /* Tiled2BThick = */    true,\n   /* Tiled3DThin1 = */    false,\n   /* Tiled3DThick = */    false,\n   /* Tiled3BThin1 = */    true,\n   /* Tiled3BThick = */    true,\n};\n\nstatic constexpr int\ngetMacroTileWidth(TileMode tileMode)\n{\n   return MacroTileWidth[static_cast<size_t>(tileMode)];\n}\n\nstatic constexpr int\ngetMacroTileHeight(TileMode tileMode)\n{\n   return MacroTileHeight[static_cast<size_t>(tileMode)];\n}\n\nstatic constexpr int\ngetMicroTileThickness(TileMode tileMode)\n{\n   return MicroTileThickness[static_cast<size_t>(tileMode)];\n}\n\nstatic constexpr int\ngetTileModeIsMacro(TileMode tileMode)\n{\n   return TileModeIsMacro[static_cast<size_t>(tileMode)];\n}\n\nstatic constexpr int\ngetTileModeIs3X(TileMode tileMode)\n{\n   return TileModeIsMacro3X[static_cast<size_t>(tileMode)];\n}\n\nstatic constexpr int\ngetTileModeIsBankSwapped(TileMode tileMode)\n{\n   return TileModeIsBankSwapped[static_cast<size_t>(tileMode)];\n}\n\nstruct SurfaceDescription\n{\n   TileMode tileMode = TileMode::LinearGeneral;\n   DataFormat format = DataFormat::Invalid;\n   uint32_t bpp = 0u;\n   uint32_t numSamples = 0u;\n   uint32_t width = 0u;\n   uint32_t height = 0u;\n   uint32_t numSlices = 0u;\n   uint32_t numFrags = 0u;\n   uint32_t numLevels = 0u;\n   uint32_t bankSwizzle = 0u;\n   uint32_t pipeSwizzle = 0u;\n   SurfaceUse use = SurfaceUse::None;\n   SurfaceDim dim = SurfaceDim::Texture1D;\n};\n\nstruct SurfaceInfo\n{\n   TileMode tileMode;\n   SurfaceUse use;\n   uint32_t bpp;\n\n   uint32_t pitch;\n   uint32_t height;\n   uint32_t depth;\n   uint32_t surfSize;\n   uint32_t sliceSize;\n   uint32_t baseAlign;\n   uint32_t pitchAlign;\n   uint32_t heightAlign;\n   uint32_t depthAlign;\n   uint32_t bankSwizzle;\n   uint32_t pipeSwizzle;\n};\n\nstruct RetileInfo\n{\n   // Input values\n   TileMode tileMode;\n   uint32_t bitsPerElement;\n   bool isDepth;\n\n   // Some helpful stuff\n   uint32_t thinSliceBytes;\n\n   // Used for both micro and macro tiling\n   bool isTiled;\n   bool isMacroTiled;\n   uint32_t macroTileWidth;\n   uint32_t macroTileHeight;\n   uint32_t microTileThickness;\n   uint32_t thickMicroTileBytes;\n   uint32_t numTilesPerRow;\n   uint32_t numTilesPerSlice;\n\n   // Used only for macro tiling\n   uint32_t bankSwizzle;\n   uint32_t pipeSwizzle;\n   uint32_t bankSwapWidth;\n};\n\nSurfaceInfo\ncomputeSurfaceInfo(const SurfaceDescription &surface,\n                   int mipLevel);\n\nsize_t\ncomputeUnpitchedImageSize(const SurfaceDescription &desc);\n\nsize_t\ncomputeUnpitchedMipMapSize(const SurfaceDescription &desc);\n\nvoid\nunpitchImage(const SurfaceDescription &desc,\n             const void *pitched,\n             void *unpitched);\n\nvoid\nunpitchMipMap(const SurfaceDescription &desc,\n              const void *pitched,\n              void *unpitched);\n\nRetileInfo\ncomputeRetileInfo(const SurfaceInfo &info);\n\n} // namespace gpu7::tiling\n"
  },
  {
    "path": "src/libgpu/gpu7_tiling_cpu.h",
    "content": "#pragma once\n#include \"gpu7_tiling.h\"\n\nnamespace gpu7::tiling::cpu\n{\n\nvoid\nuntile(const RetileInfo& desc,\n       uint8_t* untiled,\n       uint8_t* tiled,\n       uint32_t firstSlice,\n       uint32_t numSlices);\n\nvoid\ntile(const RetileInfo& desc,\n     uint8_t* untiled,\n     uint8_t* tiled,\n     uint32_t firstSlice,\n     uint32_t numSlices);\n\n}  // namespace gpu7::tiling::cpu\n"
  },
  {
    "path": "src/libgpu/gpu7_tiling_vulkan.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n#include \"gpu7_tiling.h\"\n\n#include <common/vulkan_hpp.h>\n#include <map>\n\nnamespace gpu7::tiling::vulkan\n{\n\ntypedef void * RetileHandle;\n\nclass Retiler\n{\npublic:\n   Retiler()\n   {\n   }\n\n   void\n   initialise(vk::Device device);\n\n   RetileHandle\n   tile(const RetileInfo& retileInfo,\n        vk::CommandBuffer &commandBuffer,\n        vk::Buffer dstBuffer, uint32_t dstOffset,\n        vk::Buffer srcBuffer, uint32_t srcOffset,\n        uint32_t firstSlice, uint32_t numSlices)\n   {\n      return retile(false,\n                    retileInfo,\n                    commandBuffer,\n                    dstBuffer, dstOffset,\n                    srcBuffer, srcOffset,\n                    firstSlice, numSlices);\n   }\n\n   RetileHandle\n   untile(const RetileInfo& retileInfo,\n          vk::CommandBuffer &commandBuffer,\n          vk::Buffer dstBuffer, uint32_t dstOffset,\n          vk::Buffer srcBuffer, uint32_t srcOffset,\n          uint32_t firstSlice, uint32_t numSlices)\n   {\n      return retile(true,\n                    retileInfo,\n                    commandBuffer,\n                    srcBuffer, srcOffset,\n                    dstBuffer, dstOffset,\n                    firstSlice, numSlices);\n   }\n\n   void\n   releaseHandle(RetileHandle handle)\n   {\n      releaseHandle(reinterpret_cast<HandleImpl*>(handle));\n   }\n\nprivate:\n   struct HandleImpl\n   {\n      vk::DescriptorPool descriptorPool;\n      vk::DescriptorSet descriptorSet;\n   };\n\n   HandleImpl *\n   allocateHandle();\n\n   void\n   releaseHandle(HandleImpl *handle);\n\n   RetileHandle\n   retile(bool wantsUntile,\n          const RetileInfo& retileInfo,\n          vk::CommandBuffer &commandBuffer,\n          vk::Buffer tiledBuffer, uint32_t tiledOffset,\n          vk::Buffer untiledBuffer, uint32_t untiledOffset,\n          uint32_t firstSlice, uint32_t numSlices);\n\n   void\n   retile(bool wantsUntile,\n          const RetileInfo& retileInfo,\n          vk::DescriptorSet& descriptorSet,\n          vk::CommandBuffer& commandBuffer,\n          vk::Buffer tiledBuffer, uint32_t tiledOffset,\n          vk::Buffer untiledBuffer, uint32_t untiledOffset,\n          uint32_t firstSlice, uint32_t numSlices);\n\nprotected:\n   vk::Device mDevice;\n   vk::PipelineLayout mPipelineLayout;\n   vk::ShaderModule mShader;\n   std::map<uint32_t, vk::Pipeline> mPipelines;\n   vk::DescriptorSetLayout mDescriptorSetLayout;\n   std::vector<HandleImpl *> mHandlesPool;\n\n};\n\n}\n\n#endif // DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/gpu_config.h",
    "content": "#pragma once\n#include <array>\n#include <cstdint>\n#include <memory>\n#include <vector>\n\nnamespace gpu\n{\n\nstruct DebugSettings\n{\n   //! Enable debugging\n   bool debug_enabled = false;\n\n   //! Dump shaders\n   bool dump_shaders = false;\n\n   //! Only dump shader binaries\n   bool dump_shader_binaries_only = false;\n};\n\nstruct DisplaySettings\n{\n   enum Backend\n   {\n      Null,\n      // Previously OpenGL = 1\n      Vulkan = 2,\n   };\n\n   enum ScreenMode\n   {\n      Windowed,\n      Fullscreen,\n   };\n\n   enum ViewMode\n   {\n      Split,\n      TV,\n      Gamepad1,\n      Gamepad2,\n   };\n\n   Backend backend = Backend::Vulkan;\n   std::array<int, 3> backgroundColour = { 153, 51, 51 };\n   bool maintainAspectRatio = true;\n   ScreenMode screenMode = ScreenMode::Windowed;\n   double splitSeperation = 5.0;\n   ViewMode viewMode = ViewMode::Split;\n};\n\nstruct Settings\n{\n   DebugSettings debug;\n   DisplaySettings display;\n};\n\nstd::shared_ptr<const Settings> config();\nvoid setConfig(const Settings &settings);\n\n} // namespace gpu\n"
  },
  {
    "path": "src/libgpu/gpu_graphicsdriver.h",
    "content": "#pragma once\n#include <fmt/format.h>\n#include <libcpu/be2_struct.h>\n\n#include <cstdint>\n#include <string>\n#include <string_view>\n\nnamespace gpu\n{\n\nenum class GraphicsDriverType\n{\n   Null,\n   // Previously OpenGL = 1\n   Vulkan = 2,\n};\n\nenum class WindowSystemType\n{\n   Headless,\n   Windows,\n   Cocoa,\n   X11,\n   Xcb,\n   Wayland,\n};\n\nstruct WindowSystemInfo\n{\n   WindowSystemType type = WindowSystemType::Headless;\n   void *displayConnection = nullptr;\n   void *renderSurface = nullptr;\n   double renderSurfaceScale = 1.0;\n};\n\nstruct GraphicsDriverDebugInfo\n{\n   GraphicsDriverType type = GraphicsDriverType::Null;\n   double averageFps = 0.0f;\n   double averageFrameTimeMS = 0.0f;\n};\n\nclass GraphicsDriver\n{\npublic:\n   virtual ~GraphicsDriver() = default;\n\n   virtual void setWindowSystemInfo(const WindowSystemInfo &wsi) = 0;\n   virtual void windowHandleChanged(void *handle) = 0;\n   virtual void windowSizeChanged(int width, int height) = 0;\n\n   virtual void run() = 0;\n   virtual void runUntilFlip() = 0;\n   virtual void stop() = 0;\n\n   virtual GraphicsDriverType type() = 0;\n   virtual GraphicsDriverDebugInfo *getDebugInfo() = 0;\n\n   // Called for stores to emulated physical RAM, such as via DCFlushRange().\n   //  May be called from any CPU core!\n   virtual void notifyCpuFlush(phys_addr address,\n                               uint32_t size) = 0;\n\n   // Called when the emulated CPU is about to read from emulated physical RAM,\n   //  such as after DCInvalidateRange().  May be called from any CPU core!\n   virtual void notifyGpuFlush(phys_addr address,\n                               uint32_t size) = 0;\n\n   // Begin a frame capture, frames will be dumped to outPrefix0.tga -> outPrefixN.tga\n   virtual bool\n   startFrameCapture(const std::string &outPrefix,\n                     bool captureTV,\n                     bool captureDRC)\n   {\n      return false;\n   }\n\n   virtual size_t\n   stopFrameCapture()\n   {\n      return 0;\n   }\n};\n\nGraphicsDriver *\ncreateGraphicsDriver();\n\nGraphicsDriver *\ncreateGraphicsDriver(GraphicsDriverType type);\n\n} // namespace gpu\n\ntemplate <>\nstruct fmt::formatter<gpu::WindowSystemType> :\n   fmt::formatter<std::string_view>\n{\n   template <typename FormatContext>\n   auto format(gpu::WindowSystemType value, FormatContext& ctx) {\n      std::string_view name = \"unknown\";\n      switch (value) {\n      case gpu::WindowSystemType::Headless:\n         name = \"Headless\";\n         break;\n      case gpu::WindowSystemType::Windows:\n         name = \"Windows\";\n         break;\n      case gpu::WindowSystemType::Cocoa:\n         name = \"Cocoa\";\n         break;\n      case gpu::WindowSystemType::X11:\n         name = \"X11\";\n         break;\n      case gpu::WindowSystemType::Xcb:\n         name = \"Xcb\";\n         break;\n      case gpu::WindowSystemType::Wayland:\n         name = \"Wayland\";\n         break;\n      }\n      return fmt::formatter<string_view>::format(name, ctx);\n   }\n};\n"
  },
  {
    "path": "src/libgpu/gpu_ih.h",
    "content": "#pragma once\n#include \"latte/latte_enum_cp.h\"\n#include \"latte/latte_registers_cp.h\"\n\n#include <cstdint>\n#include <gsl/gsl-lite.hpp>\n\nnamespace gpu::ih\n{\n\nstruct Entry\n{\n   uint32_t word0;\n   uint32_t word1;\n   uint32_t word2;\n   uint32_t word3;\n};\n\nusing Entries = gsl::span<const Entry>;\nusing InterruptCallbackFn = void(*) ();\n\nvoid\nwrite(const Entries &entries);\n\ninline void\nwrite(const Entry &entry)\n{\n   write({ &entry, 1 });\n}\n\nEntries\nread();\n\nvoid\nsetInterruptCallback(InterruptCallbackFn callback);\n\nvoid\nenable(latte::CP_INT_CNTL cntl);\n\nvoid\ndisable(latte::CP_INT_CNTL cntl);\n\n} // namespace gpu::ih\n"
  },
  {
    "path": "src/libgpu/gpu_memory.h",
    "content": "#pragma once\n#include <libcpu/be2_struct.h>\n\nnamespace gpu::internal\n{\n\ntemplate<typename Type = void>\nType *\ntranslateAddress(phys_addr address)\n{\n   return cpu::internal::translate<Type>(address);\n}\n\n} // namespace gpu::internal\n"
  },
  {
    "path": "src/libgpu/gpu_ringbuffer.h",
    "content": "#pragma once\n#include <cstdint>\n#include <gsl/gsl-lite.hpp>\n\nnamespace gpu::ringbuffer\n{\n\nusing Buffer = gsl::span<uint32_t>;\n\nvoid\nwrite(const Buffer &buffer);\n\nBuffer\nread();\n\nbool\nwait();\n\nvoid\nwake();\n\n} // namespace gpu::ringbuffer\n"
  },
  {
    "path": "src/libgpu/gpu_tiling.h",
    "content": "#pragma once\n#include \"latte/latte_enum_sq.h\"\n#include <addrlib/addrinterface.h>\n\nnamespace gpu\n{\n\nADDR_HANDLE\ngetAddrLibHandle();\n\nvoid\nalignTiling(latte::SQ_TILE_MODE& tileMode,\n            latte::SQ_DATA_FORMAT& format,\n            uint32_t& swizzle,\n            uint32_t& pitch,\n            uint32_t& width,\n            uint32_t& height,\n            uint32_t& depth,\n            uint32_t& aa,\n            bool& isDepth,\n            uint32_t& bpp);\n\nbool\ncopySurfacePixels(uint8_t *dstBasePtr,\n                  uint32_t dstWidth,\n                  uint32_t dstHeight,\n                  ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &dstAddrInput,\n                  uint8_t *srcBasePtr,\n                  uint32_t srcWidth,\n                  uint32_t srcHeight,\n                  ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &srcAddrInput);\n\nbool\nconvertFromTiled(uint8_t *output,\n                 uint32_t outputPitch,\n                 uint8_t *input,\n                 latte::SQ_TILE_MODE tileMode,\n                 uint32_t swizzle,\n                 uint32_t pitch,\n                 uint32_t width,\n                 uint32_t height,\n                 uint32_t depth,\n                 uint32_t aa,\n                 bool isDepth,\n                 uint32_t bpp,\n                 uint32_t beginSlice = 0,\n                 uint32_t endSlice = 0);\n\nbool\nconvertToTiled(uint8_t *output,\n               uint8_t *input,\n               uint32_t inputPitch,\n               latte::SQ_TILE_MODE tileMode,\n               uint32_t swizzle,\n               uint32_t pitch,\n               uint32_t width,\n               uint32_t height,\n               uint32_t depth,\n               uint32_t aa,\n               bool isDepth,\n               uint32_t bpp,\n               uint32_t beginSlice = 0,\n               uint32_t endSlice = 0);\n\n} // namespace gpu\n"
  },
  {
    "path": "src/libgpu/gpu_vulkandriver.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n#include \"gpu_graphicsdriver.h\"\n#include <common/vulkan_hpp.h>\n\nnamespace gpu\n{\n\nstruct VulkanDriverDebugInfo : GraphicsDriverDebugInfo\n{\n   VulkanDriverDebugInfo()\n   {\n      type = GraphicsDriverType::Vulkan;\n   }\n\n   uint64_t numVertexShaders = 0;\n   uint64_t numGeometryShaders = 0;\n   uint64_t numPixelShaders = 0;\n   uint64_t numRenderPasses = 0;\n   uint64_t numPipelines = 0;\n   uint64_t numSamplers = 0;\n   uint64_t numSurfaces = 0;\n   uint64_t numDataBuffers = 0;\n};\n\n} // namespace gpu\n\n#endif // DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/latte/latte_constants.h",
    "content": "#pragma once\n\nnamespace latte\n{\n\nstatic const unsigned\nMaxAttribBuffers = 16u;\n\nstatic const unsigned\nMaxRenderTargets = 8u;\n\nstatic const unsigned\nMaxSamplers = 16u;\n\nstatic const unsigned\nMaxStreamOutBuffers = 4u;\n\nstatic const unsigned\nMaxTextures = 16u;\n\nstatic const unsigned\nMaxUniformRegisters = 256u;\n\nstatic const unsigned\nMaxUniformBlocks = 16u;\n\nstatic const unsigned\nMaxUniformBlockSize = 65536u;\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_contextstate.h",
    "content": "#pragma once\n#include \"latte_registers.h\"\n\n#include <common/bitfield.h>\n#include <libcpu/be2_struct.h>\n#include <cstdint>\n\n#pragma pack(push, 1)\n\nnamespace latte\n{\n\nBITFIELD_BEG(CONTEXT_CONTROL_ENABLE, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, ENABLE_CONFIG_REG);\n   BITFIELD_ENTRY(1, 1, bool, ENABLE_CONTEXT_REG);\n   BITFIELD_ENTRY(2, 1, bool, ENABLE_ALU_CONST);\n   BITFIELD_ENTRY(3, 1, bool, ENABLE_BOOL_CONST);\n   BITFIELD_ENTRY(4, 1, bool, ENABLE_LOOP_CONST);\n   BITFIELD_ENTRY(5, 1, bool, ENABLE_RESOURCE);\n   BITFIELD_ENTRY(6, 1, bool, ENABLE_SAMPLER);\n   BITFIELD_ENTRY(7, 1, bool, ENABLE_CTL_CONST);\n   BITFIELD_ENTRY(31, 1, bool, ENABLE_ORDINAL);\nBITFIELD_END\n\nstruct ShadowState\n{\n   CONTEXT_CONTROL_ENABLE LOAD_CONTROL = CONTEXT_CONTROL_ENABLE::get(0);\n   CONTEXT_CONTROL_ENABLE SHADOW_ENABLE = CONTEXT_CONTROL_ENABLE::get(0);\n   phys_ptr<uint32_t> CONFIG_REG_BASE = nullptr;\n   phys_ptr<uint32_t> CONTEXT_REG_BASE = nullptr;\n   phys_ptr<uint32_t> ALU_CONST_BASE = nullptr;\n   phys_ptr<uint32_t> BOOL_CONST_BASE = nullptr;\n   phys_ptr<uint32_t> LOOP_CONST_BASE = nullptr;\n   phys_ptr<uint32_t> RESOURCE_CONST_BASE = nullptr;\n   phys_ptr<uint32_t> SAMPLER_CONST_BASE = nullptr;\n   phys_ptr<uint32_t> CTL_CONST_BASE = nullptr;\n};\n\n} // namespace latte\n\n#pragma pack(pop)\n"
  },
  {
    "path": "src/libgpu/latte/latte_disassembler.h",
    "content": "#pragma once\n\n#include <gsl/gsl-lite.hpp>\n\nnamespace latte\n{\n\nstd::string\ndisassemble(const gsl::span<const uint8_t> &binary, bool isSubroutine = false);\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_as_string.cpp",
    "content": "#include \"latte_enum_as_string.h\"\n\n#undef LATTE_ENUM_CB_H\n#undef LATTE_ENUM_COMMON_H\n#undef LATTE_ENUM_DB_H\n#undef LATTE_ENUM_PA_H\n#undef LATTE_ENUM_PM4_H\n#undef LATTE_ENUM_SPI_H\n#undef LATTE_ENUM_SQ_H\n#undef LATTE_ENUM_VGT_H\n\n#include <common/enum_string_define.inl>\n#include \"latte_enum_cb.h\"\n\n#include <common/enum_string_define.inl>\n#include \"latte_enum_common.h\"\n\n#include <common/enum_string_define.inl>\n#include \"latte_enum_db.h\"\n\n#include <common/enum_string_define.inl>\n#include \"latte_enum_pa.h\"\n\n#include <common/enum_string_define.inl>\n#include \"latte_enum_pm4.h\"\n\n#include <common/enum_string_define.inl>\n#include \"latte_enum_spi.h\"\n\n// TODO: Fix collision in SQ_RES_OFFSET\n// #include <common/enum_string_define.inl>\n// #include \"latte_enum_sq.h\"\n\n#include <common/enum_string_define.inl>\n#include \"latte_enum_vgt.h\"\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_as_string.h",
    "content": "#pragma once\n#include <cstdint>\n#include <string>\n#include \"latte_enum_cb.h\"\n#include \"latte_enum_common.h\"\n#include \"latte_enum_db.h\"\n#include \"latte_enum_pa.h\"\n#include \"latte_enum_pm4.h\"\n#include \"latte_enum_spi.h\"\n#include \"latte_enum_sq.h\"\n#include \"latte_enum_vgt.h\"\n\n#undef LATTE_ENUM_CB_H\n#undef LATTE_ENUM_COMMON_H\n#undef LATTE_ENUM_DB_H\n#undef LATTE_ENUM_PA_H\n#undef LATTE_ENUM_PM4_H\n#undef LATTE_ENUM_SPI_H\n#undef LATTE_ENUM_SQ_H\n#undef LATTE_ENUM_VGT_H\n\n#include <common/enum_string_declare.inl>\n#include \"latte_enum_cb.h\"\n\n#include <common/enum_string_declare.inl>\n#include \"latte_enum_common.h\"\n\n#include <common/enum_string_declare.inl>\n#include \"latte_enum_db.h\"\n\n#include <common/enum_string_declare.inl>\n#include \"latte_enum_pa.h\"\n\n#include <common/enum_string_declare.inl>\n#include \"latte_enum_pm4.h\"\n\n#include <common/enum_string_declare.inl>\n#include \"latte_enum_spi.h\"\n\n// TODO: Fix collision in SQ_RES_OFFSET\n// #include <common/enum_string_declare.inl>\n// #include \"latte_enum_sq.h\"\n\n#include <common/enum_string_declare.inl>\n#include \"latte_enum_vgt.h\"\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_cb.h",
    "content": "#ifndef LATTE_ENUM_CB_H\n#define LATTE_ENUM_CB_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(latte)\n\nENUM_BEG(CB_BLEND_FUNC, uint32_t)\n   ENUM_VALUE(ZERO,                       0)\n   ENUM_VALUE(ONE,                        1)\n   ENUM_VALUE(SRC_COLOR,                  2)\n   ENUM_VALUE(ONE_MINUS_SRC_COLOR,        3)\n   ENUM_VALUE(SRC_ALPHA,                  4)\n   ENUM_VALUE(ONE_MINUS_SRC_ALPHA,        5)\n   ENUM_VALUE(DST_ALPHA,                  6)\n   ENUM_VALUE(ONE_MINUS_DST_ALPHA,        7)\n   ENUM_VALUE(DST_COLOR,                  8)\n   ENUM_VALUE(ONE_MINUS_DST_COLOR,        9)\n   ENUM_VALUE(SRC_ALPHA_SATURATE,         10)\n   ENUM_VALUE(BOTH_SRC_ALPHA,             11)\n   ENUM_VALUE(BOTH_INV_SRC_ALPHA,         12)\n   ENUM_VALUE(CONSTANT_COLOR,             13)\n   ENUM_VALUE(ONE_MINUS_CONSTANT_COLOR,   14)\n   ENUM_VALUE(SRC1_COLOR,                 15)\n   ENUM_VALUE(ONE_MINUS_SRC1_COLOR,       16)\n   ENUM_VALUE(SRC1_ALPHA,                 17)\n   ENUM_VALUE(ONE_MINUS_SRC1_ALPHA,       18)\n   ENUM_VALUE(CONSTANT_ALPHA,             19)\n   ENUM_VALUE(ONE_MINUS_CONSTANT_ALPHA,   20)\nENUM_END(CB_BLEND_FUNC)\n\nENUM_BEG(CB_CLRCMP_DRAW, uint32_t)\n   ENUM_VALUE(ALWAYS,                     0)\n   ENUM_VALUE(NEVER,                      1)\n   ENUM_VALUE(ON_NEQ,                     4)\n   ENUM_VALUE(ON_EQ,                      5)\nENUM_END(CB_CLRCMP_DRAW)\n\nENUM_BEG(CB_CLRCMP_SEL, uint32_t)\n   ENUM_VALUE(DST,                        0)\n   ENUM_VALUE(SRC,                        1)\n   ENUM_VALUE(AND,                        2)\nENUM_END(CB_CLRCMP_SEL)\n\nENUM_BEG(CB_COMB_FUNC, uint32_t)\n   ENUM_VALUE(DST_PLUS_SRC,               0)\n   ENUM_VALUE(SRC_MINUS_DST,              1)\n   ENUM_VALUE(MIN_DST_SRC,                2)\n   ENUM_VALUE(MAX_DST_SRC,                3)\n   ENUM_VALUE(DST_MINUS_SRC,              4)\nENUM_END(CB_COMB_FUNC)\n\nENUM_BEG(CB_ENDIAN, uint32_t)\n   ENUM_VALUE(NONE,                       0)\n   ENUM_VALUE(SWAP_8IN16,                 1)\n   ENUM_VALUE(SWAP_8IN32,                 2)\n   ENUM_VALUE(SWAP_8IN64,                 3)\nENUM_END(CB_ENDIAN)\n\nENUM_BEG(CB_FORMAT, uint32_t)\n   ENUM_VALUE(COLOR_INVALID,              0)\n   ENUM_VALUE(COLOR_8,                    1)\n   ENUM_VALUE(COLOR_4_4,                  2)\n   ENUM_VALUE(COLOR_3_3_2,                3)\n   ENUM_VALUE(COLOR_16,                   5)\n   ENUM_VALUE(COLOR_16_FLOAT,             6)\n   ENUM_VALUE(COLOR_8_8,                  7)\n   ENUM_VALUE(COLOR_5_6_5,                8)\n   ENUM_VALUE(COLOR_6_5_5,                9)\n   ENUM_VALUE(COLOR_1_5_5_5,              10)\n   ENUM_VALUE(COLOR_4_4_4_4,              11)\n   ENUM_VALUE(COLOR_5_5_5_1,              12)\n   ENUM_VALUE(COLOR_32,                   13)\n   ENUM_VALUE(COLOR_32_FLOAT,             14)\n   ENUM_VALUE(COLOR_16_16,                15)\n   ENUM_VALUE(COLOR_16_16_FLOAT,          16)\n   ENUM_VALUE(COLOR_8_24,                 17)\n   ENUM_VALUE(COLOR_8_24_FLOAT,           18)\n   ENUM_VALUE(COLOR_24_8,                 19)\n   ENUM_VALUE(COLOR_24_8_FLOAT,           20)\n   ENUM_VALUE(COLOR_10_11_11,             21)\n   ENUM_VALUE(COLOR_10_11_11_FLOAT,       22)\n   ENUM_VALUE(COLOR_11_11_10,             23)\n   ENUM_VALUE(COLOR_11_11_10_FLOAT,       24)\n   ENUM_VALUE(COLOR_2_10_10_10,           25)\n   ENUM_VALUE(COLOR_8_8_8_8,              26)\n   ENUM_VALUE(COLOR_10_10_10_2,           27)\n   ENUM_VALUE(COLOR_X24_8_32_FLOAT,       28)\n   ENUM_VALUE(COLOR_32_32,                29)\n   ENUM_VALUE(COLOR_32_32_FLOAT,          30)\n   ENUM_VALUE(COLOR_16_16_16_16,          31)\n   ENUM_VALUE(COLOR_16_16_16_16_FLOAT,    32)\n   ENUM_VALUE(COLOR_32_32_32_32,          34)\n   ENUM_VALUE(COLOR_32_32_32_32_FLOAT,    35)\nENUM_END(CB_FORMAT)\n\nENUM_BEG(CB_NUMBER_TYPE, uint32_t)\n   ENUM_VALUE(UNORM,                      0)\n   ENUM_VALUE(SNORM,                      1)\n   ENUM_VALUE(USCALED,                    2)\n   ENUM_VALUE(SSCALED,                    3)\n   ENUM_VALUE(UINT,                       4)\n   ENUM_VALUE(SINT,                       5)\n   ENUM_VALUE(SRGB,                       6)\n   ENUM_VALUE(FLOAT,                      7)\nENUM_END(CB_NUMBER_TYPE)\n\nENUM_BEG(CB_ROUND_MODE, uint32_t)\n   ENUM_VALUE(BY_HALF,                    0)\n   ENUM_VALUE(TRUNCATE,                   1)\nENUM_END(CB_ROUND_MODE)\n\nENUM_BEG(CB_SOURCE_FORMAT, uint32_t)\n   ENUM_VALUE(EXPORT_FULL,                0)\n   ENUM_VALUE(EXPORT_NORM,                1)\nENUM_END(CB_SOURCE_FORMAT)\n\nENUM_BEG(CB_SPECIAL_OP, uint32_t)\n   ENUM_VALUE(NORMAL,                     0)\n   ENUM_VALUE(DISABLE,                    1)\n   ENUM_VALUE(FAST_CLEAR,                 2)\n   ENUM_VALUE(FORCE_CLEAR,                3)\n   ENUM_VALUE(EXPAND_COLOR,               4)\n   ENUM_VALUE(EXPAND_TEXTURE,             5)\n   ENUM_VALUE(EXPAND_SAMPLES,             6)\n   ENUM_VALUE(RESOLVE_BOX,                7)\nENUM_END(CB_SPECIAL_OP)\n\nENUM_BEG(CB_COMP_SWAP, uint32_t)\n   ENUM_VALUE(STD,                        0)\n   ENUM_VALUE(ALT,                        1)\n   ENUM_VALUE(STD_REV,                    2)\n   ENUM_VALUE(ALT_REV,                    3)\nENUM_END(CB_COMP_SWAP)\n\nENUM_BEG(CB_TILE_MODE, uint32_t)\n   ENUM_VALUE(DISABLE,                    0)\n   ENUM_VALUE(CLEAR_ENABLE,               1)\n   ENUM_VALUE(FRAG_ENABLE,                2)\nENUM_END(CB_TILE_MODE)\n\nENUM_NAMESPACE_EXIT(latte)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef LATTE_ENUM_CB_H\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_common.h",
    "content": "#ifndef LATTE_ENUM_COMMON_H\n#define LATTE_ENUM_COMMON_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(latte)\n\nENUM_BEG(BUFFER_ARRAY_MODE, uint32_t)\n   ENUM_VALUE(LINEAR_GENERAL,             0)\n   ENUM_VALUE(LINEAR_ALIGNED,             1)\n   ENUM_VALUE(TILED_2D_THIN1,             4)\nENUM_END(BUFFER_ARRAY_MODE)\n\nENUM_BEG(BUFFER_READ_SIZE, uint32_t)\n   ENUM_VALUE(READ_256_BITS,              0)\n   ENUM_VALUE(READ_512_BITS,              1)\nENUM_END(BUFFER_READ_SIZE)\n\nENUM_BEG(REF_FUNC, uint32_t)\n   ENUM_VALUE(NEVER,                      0)\n   ENUM_VALUE(LESS,                       1)\n   ENUM_VALUE(EQUAL,                      2)\n   ENUM_VALUE(LESS_EQUAL,                 3)\n   ENUM_VALUE(GREATER,                    4)\n   ENUM_VALUE(NOT_EQUAL,                  5)\n   ENUM_VALUE(GREATER_EQUAL,              6)\n   ENUM_VALUE(ALWAYS,                     7)\nENUM_END(REF_FUNC)\n\nENUM_NAMESPACE_EXIT(latte)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef LATTE_ENUM_COMMON_H\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_cp.h",
    "content": "#ifndef LATTE_ENUM_CP_H\n#define LATTE_ENUM_CP_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(latte)\n\n// Interrupt types taken from linux/drivers/gpu/drm/radeon and\n// avm.rpl dc.rpl tcl.rpl uvd.rpl\nENUM_BEG(CP_INT_SRC_ID, uint32_t)\n   ENUM_VALUE(D1_VSYNC,                         1)\n   ENUM_VALUE(D1_TRIGA,                         2)\n   ENUM_VALUE(D2_VSYNC,                         5)\n   ENUM_VALUE(D2_TRIGA,                         6)\n   ENUM_VALUE(D1_VUPD,                          8)\n   ENUM_VALUE(D1_PFLIP,                         9)\n   ENUM_VALUE(D2_VUPD,                          10)\n   ENUM_VALUE(D2_PFLIP,                         11)\n   ENUM_VALUE(HPD_DAC_HOTPLUG,                  19)\n   ENUM_VALUE(HDMI,                             21)\n   ENUM_VALUE(DVOCAP,                           22)\n   ENUM_VALUE(UVD,                              124)\n   ENUM_VALUE(CP_RB,                            176)\n   ENUM_VALUE(CP_IB1,                           177)\n   ENUM_VALUE(CP_IB2,                           178)\n   ENUM_VALUE(CP_RESERVED_BITS,                 180)\n   ENUM_VALUE(CP_EOP_EVENT,                     181)\n   ENUM_VALUE(SCRATCH,                          182)\n   ENUM_VALUE(CP_BAD_OPCODE,                    183)\n   ENUM_VALUE(CP_CTX_EMPTY,                     187)\n   ENUM_VALUE(CP_CTX_BUSY,                      188)\n   ENUM_VALUE(UNKNOWN_192,                      192)\n   ENUM_VALUE(DMA_TRAP_EVENT,                   224)\n   ENUM_VALUE(DMA_SEM_INCOMPLETE,               225)\n   ENUM_VALUE(DMA_SEM_WAIT,                     226)\n   ENUM_VALUE(THERMAL_LOW_TO_HIGH,              230)\n   ENUM_VALUE(THERMAL_HIGH_TO_LOW,              231)\n   ENUM_VALUE(GUI_IDLE,                         233)\n   ENUM_VALUE(DMA_CTX_EMPTY,                    243)\nENUM_END(CP_INT_SRC_ID)\n\nENUM_NAMESPACE_EXIT(latte)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef LATTE_ENUM_CP_H\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_db.h",
    "content": "#ifndef LATTE_ENUM_DB_H\n#define LATTE_ENUM_DB_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(latte)\n\nENUM_BEG(DB_FORMAT, uint32_t)\n   ENUM_VALUE(DEPTH_INVALID,              0)\n   ENUM_VALUE(DEPTH_16,                   1)\n   ENUM_VALUE(DEPTH_X8_24,                2)\n   ENUM_VALUE(DEPTH_8_24,                 3)\n   ENUM_VALUE(DEPTH_X8_24_FLOAT,          4)\n   ENUM_VALUE(DEPTH_8_24_FLOAT,           5)\n   ENUM_VALUE(DEPTH_32_FLOAT,             6)\n   ENUM_VALUE(DEPTH_X24_8_32_FLOAT,       7)\nENUM_END(DB_FORMAT)\n\nENUM_BEG(DB_FORCE, uint32_t)\n   ENUM_VALUE(OFF,                        0)\n   ENUM_VALUE(ENABLE,                     1)\n   ENUM_VALUE(DISABLE,                    2)\nENUM_END(DB_FORCE)\n\nENUM_BEG(DB_STENCIL_FUNC, uint32_t)\n   ENUM_VALUE(KEEP,                       0)\n   ENUM_VALUE(ZERO,                       1)\n   ENUM_VALUE(REPLACE,                    2)\n   ENUM_VALUE(INCR_CLAMP,                 3)\n   ENUM_VALUE(DECR_CLAMP,                 4)\n   ENUM_VALUE(INVERT,                     5)\n   ENUM_VALUE(INCR_WRAP,                  6)\n   ENUM_VALUE(DECR_WRAP,                  7)\nENUM_END(DB_STENCIL_FUNC)\n\nENUM_BEG(DB_Z_EXPORT, uint32_t)\n   ENUM_VALUE(ANY_Z,                      0)\n   ENUM_VALUE(LESS_THAN_Z,                1)\n   ENUM_VALUE(GREATER_THAN_Z,             2)\nENUM_END(DB_Z_EXPORT)\n\nENUM_BEG(DB_Z_ORDER, uint32_t)\n   ENUM_VALUE(LATE_Z,                     0)\n   ENUM_VALUE(EARLY_Z_THEN_LATE_Z,        1)\n   ENUM_VALUE(RE_Z,                       2)\n   ENUM_VALUE(EARLY_Z_THEN_RE_Z,          3)\nENUM_END(DB_Z_ORDER)\n\nENUM_NAMESPACE_EXIT(latte)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef LATTE_ENUM_DB_H\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_pa.h",
    "content": "#ifndef LATTE_ENUM_PA_H\n#define LATTE_ENUM_PA_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(latte)\n\nENUM_BEG(PA_FACE, uint32_t)\n   ENUM_VALUE(CCW,                        0)\n   ENUM_VALUE(CW,                         1)\nENUM_END(PA_FACE)\n\nENUM_BEG(PA_PTYPE, uint32_t)\n   ENUM_VALUE(POINTS, 0)\n   ENUM_VALUE(LINES, 1)\n   ENUM_VALUE(TRIANGLES, 2)\nENUM_END(PA_PTYPE)\n\nENUM_BEG(PA_PS_UCP_MODE, uint32_t)\n   ENUM_VALUE(CULL_DISTANCE,              0)\n   ENUM_VALUE(CULL_RADIUS,                1)\n   ENUM_VALUE(CULL_RADIUS_EXPAND,         2)\n   ENUM_VALUE(CULL_EXPAND,                3)\nENUM_END(PA_PS_UCP_MODE)\n\nENUM_BEG(PA_SU_VTX_CNTL_PIX_CENTER, uint32_t)\n   ENUM_VALUE(D3D,                        0)\n   ENUM_VALUE(OGL,                        1)\nENUM_END(PA_SU_VTX_CNTL_PIX_CENTER)\n\nENUM_BEG(PA_SU_VTX_CNTL_ROUND_MODE, uint32_t)\n   ENUM_VALUE(TRUNCATE,                   0)\n   ENUM_VALUE(NEAREST,                    1)\n   ENUM_VALUE(TO_EVEN,                    2)\n   ENUM_VALUE(TO_ODD,                     3)\nENUM_END(PA_SU_VTX_CNTL_ROUND_MODE)\n\nENUM_BEG(PA_SU_VTX_CNTL_QUANT_MODE, uint32_t)\n   ENUM_VALUE(QUANT_1_16TH,               0)\n   ENUM_VALUE(QUANT_1_8TH,                1)\n   ENUM_VALUE(QUANT_1_4TH,                2)\n   ENUM_VALUE(QUANT_1_2ND,                3)\n   ENUM_VALUE(QUANT_1,                    4)\n   ENUM_VALUE(QUANT_1_256TH,              5)\nENUM_END(PA_SU_VTX_CNTL_QUANT_MODE)\n\nENUM_NAMESPACE_EXIT(latte)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef LATTE_ENUM_PA_H\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_pm4.h",
    "content": "#ifndef LATTE_ENUM_PM4_H\n#define LATTE_ENUM_PM4_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(latte)\nENUM_NAMESPACE_ENTER(pm4)\n\nENUM_BEG(PacketType, uint32_t)\n   ENUM_VALUE(Type0,                         0x00)\n   ENUM_VALUE(Type1,                         0x01)\n   ENUM_VALUE(Type2,                         0x02)\n   ENUM_VALUE(Type3,                         0x03)\nENUM_END(PacketType)\n\nENUM_BEG(IT_OPCODE, uint32_t)\n   ENUM_VALUE(DECAF_COPY_COLOR_TO_SCAN,      0x01)\n   ENUM_VALUE(DECAF_SWAP_BUFFERS,            0x02)\n   ENUM_VALUE(DECAF_CLEAR_COLOR,             0x03)\n   ENUM_VALUE(DECAF_CLEAR_DEPTH_STENCIL,     0x04)\n   ENUM_VALUE(DECAF_CAP_SYNC_REGISTERS,      0x05)\n   ENUM_VALUE(DECAF_SET_BUFFER,              0x06)\n   ENUM_VALUE(DECAF_COPY_SURFACE,            0x07)\n   ENUM_VALUE(DECAF_EXPAND_COLORBUFFER,      0x08)\n   ENUM_VALUE(DECAF_OSSCREEN_FLIP,           0x09)\n\n   ENUM_VALUE(NOP,                           0x10)\n   ENUM_VALUE(INDIRECT_BUFFER_END,           0x17)\n   ENUM_VALUE(NEXTCHAR,                      0x19)\n   ENUM_VALUE(PLY_NEXTSCAN,                  0x1D)\n   ENUM_VALUE(SET_SCISSORS,                  0x1E)\n   ENUM_VALUE(OCCLUSION_QUERY,               0x1F)\n   ENUM_VALUE(SET_PREDICATION,               0x20)\n   ENUM_VALUE(REG_RMW,                       0x21)\n   ENUM_VALUE(COND_EXEC,                     0x22)\n   ENUM_VALUE(PRED_EXEC,                     0x23)\n   ENUM_VALUE(START_3D_CMDBUF,               0x24)\n   ENUM_VALUE(START_2D_CMDBUF,               0x25)\n   ENUM_VALUE(INDEX_BASE,                    0x26)\n   ENUM_VALUE(DRAW_INDEX_2,                  0x27)\n   ENUM_VALUE(CONTEXT_CTL,                   0x28)\n   ENUM_VALUE(DRAW_INDEX_OFFSET,             0x29)\n   ENUM_VALUE(INDEX_TYPE,                    0x2A)\n   ENUM_VALUE(DRAW_INDEX,                    0x2B)\n   ENUM_VALUE(LOAD_PALETTE,                  0x2C)\n   ENUM_VALUE(DRAW_INDEX_AUTO,               0x2D)\n   ENUM_VALUE(DRAW_INDEX_IMMD,               0x2E)\n   ENUM_VALUE(NUM_INSTANCES,                 0x2F)\n   ENUM_VALUE(DRAW_INDEX_MULTI_AUTO,         0x30)\n   ENUM_VALUE(INDIRECT_BUFFER_PRIV,          0x32)\n   ENUM_VALUE(STRMOUT_BUFFER_UPDATE,         0x34)\n   ENUM_VALUE(DRAW_INDEX_OFFSET_2,           0x35)\n   ENUM_VALUE(DRAW_INDEX_MULTI_ELEMENT,      0x36)\n   ENUM_VALUE(INDIRECT_BUFFER_MP,            0x38)\n   ENUM_VALUE(MEM_SEMAPHORE,                 0x39)\n   ENUM_VALUE(MPEG_INDEX,                    0x3A)\n   ENUM_VALUE(COPY_DW,                       0x3B)\n   ENUM_VALUE(WAIT_REG_MEM,                  0x3C)\n   ENUM_VALUE(MEM_WRITE,                     0x3D)\n   ENUM_VALUE(PER_FRAME,                     0x3E)\n   ENUM_VALUE(INDIRECT_BUFFER,               0x3F)\n   ENUM_VALUE(CP_DMA,                        0x41)\n   ENUM_VALUE(PFP_SYNC_ME,                   0x42)\n   ENUM_VALUE(SURFACE_SYNC,                  0x43)\n   ENUM_VALUE(ME_INITIALIZE,                 0x44)\n   ENUM_VALUE(COND_WRITE,                    0x45)\n   ENUM_VALUE(EVENT_WRITE,                   0x46)\n   ENUM_VALUE(EVENT_WRITE_EOP,               0x47)\n   ENUM_VALUE(LOAD_SURFACE_PROBE,            0x48)\n   ENUM_VALUE(SURFACE_PROBE,                 0x49)\n   ENUM_VALUE(PREAMBLE_CNTL,                 0x4A)\n   ENUM_VALUE(RB_OFFSET,                     0x4B)\n   ENUM_VALUE(GFX_CNTX_UPDATE,               0x52)\n   ENUM_VALUE(BLK_CNTX_UPDATE,               0x53)\n   ENUM_VALUE(IB_OFFSET,                     0x54)\n   ENUM_VALUE(INCR_UPDT_STATE,               0x55)\n   ENUM_VALUE(INCR_UPDT_CONST,               0x56)\n   ENUM_VALUE(ONE_REG_WRITE,                 0x57)\n   ENUM_VALUE(LOAD_CONFIG_REG,               0x60)\n   ENUM_VALUE(LOAD_CONTEXT_REG,              0x61)\n   ENUM_VALUE(LOAD_ALU_CONST,                0x62)\n   ENUM_VALUE(LOAD_BOOL_CONST,               0x63)\n   ENUM_VALUE(LOAD_LOOP_CONST,               0x64)\n   ENUM_VALUE(LOAD_RESOURCE,                 0x65)\n   ENUM_VALUE(LOAD_SAMPLER,                  0x66)\n   ENUM_VALUE(LOAD_CTL_CONST,                0x67)\n   ENUM_VALUE(SET_CONFIG_REG,                0x68)\n   ENUM_VALUE(SET_CONTEXT_REG,               0x69)\n   ENUM_VALUE(SET_ALU_CONST,                 0x6A)\n   ENUM_VALUE(SET_BOOL_CONST,                0x6B)\n   ENUM_VALUE(SET_LOOP_CONST,                0x6C)\n   ENUM_VALUE(SET_RESOURCE,                  0x6D)\n   ENUM_VALUE(SET_SAMPLER,                   0x6E)\n   ENUM_VALUE(SET_CTL_CONST,                 0x6F)\n   ENUM_VALUE(SET_RESOURCE_OFFSET,           0x70)\n   ENUM_VALUE(STRMOUT_BASE_UPDATE,           0x72)\n   ENUM_VALUE(SURFACE_BASE_UPDATE,           0x73)\n   ENUM_VALUE(SET_ALL_CONTEXTS,              0x74)\n   ENUM_VALUE(INDIRECT_BUFFER_BASE,          0x78)\n   ENUM_VALUE(EXECUTE_IB2,                   0x79)\n   ENUM_VALUE(PFP_REG_WR,                    0x7B)\n   ENUM_VALUE(FORWARD_HEADER,                0x7C)\n   ENUM_VALUE(PAINT,                         0x91)\n   ENUM_VALUE(BITBLT,                        0x92)\n   ENUM_VALUE(HOSTDATA_BLT,                  0x94)\n   ENUM_VALUE(POLYLINE,                      0x95)\n   ENUM_VALUE(POLYSCANLINES,                 0x98)\n   ENUM_VALUE(PAINT_MULTI,                   0x9A)\n   ENUM_VALUE(BITBLT_MULTI,                  0x9B)\n   ENUM_VALUE(TRANS_BITBLT,                  0x9C)\n   ENUM_VALUE(DRAW_2D_DIRTY_AREA,            0xFF)\nENUM_END(IT_OPCODE)\n\nENUM_NAMESPACE_EXIT(pm4)\nENUM_NAMESPACE_EXIT(latte)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef LATTE_ENUM_PM4_H\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_spi.h",
    "content": "#ifndef LATTE_ENUM_SPI_H\n#define LATTE_ENUM_SPI_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(latte)\n\nENUM_BEG(SPI_BARYC_CNTL, uint32_t)\n   ENUM_VALUE(CENTROIDS_ONLY,             0)\n   ENUM_VALUE(CENTERS_ONLY,               1)\n   ENUM_VALUE(CENTROIDS_AND_CENTERS,      2)\nENUM_END(SPI_BARYC_CNTL)\n\nENUM_BEG(SPI_FOG_FUNC, uint32_t)\n   ENUM_VALUE(NONE,                       0)\n   ENUM_VALUE(EXP,                        1)\n   ENUM_VALUE(EXP2,                       2)\n   ENUM_VALUE(LINEAR,                     3)\nENUM_END(SPI_FOG_FUNC)\n\nENUM_BEG(SPI_FOG_SRC_SEL, uint32_t)\n   ENUM_VALUE(SEL_Z,                      0)\n   ENUM_VALUE(SEL_W,                      1)\nENUM_END(SPI_FOG_SRC_SEL)\n\nENUM_BEG(SPI_PNT_SPRITE_SEL, uint32_t)\n   ENUM_VALUE(SEL_0,                      0)\n   ENUM_VALUE(SEL_1,                      1)\n   ENUM_VALUE(SEL_S,                      2)\n   ENUM_VALUE(SEL_T,                      3)\n   ENUM_VALUE(SEL_NONE,                   4)\nENUM_END(SPI_PNT_SPRITE_SEL)\n\nENUM_NAMESPACE_EXIT(latte)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef LATTE_ENUM_SPI_H\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_sq.h",
    "content": "#ifndef LATTE_ENUM_SQ_H\n#define LATTE_ENUM_SQ_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(latte)\n\nENUM_BEG(SQ_ALU_ENCODING, uint32_t)\nENUM_VALUE(OP2, 0)\n// OP3 is not a specific value, only that it the encoding != OP2.  This\n//  is because ALU_ENCODING is actually multiple bits, where 0 means OP2.\n//ENUM_VALUE(OP3,                      NOT_ZERO)\nENUM_END(SQ_ALU_ENCODING)\n\nENUM_BEG(SQ_ALU_OMOD, uint32_t)\n   ENUM_VALUE(OFF,                        0)\n   ENUM_VALUE(M2,                         1)\n   ENUM_VALUE(M4,                         2)\n   ENUM_VALUE(D2,                         3)\nENUM_END(SQ_ALU_OMOD)\n\nENUM_BEG(SQ_ALU_SRC, uint32_t)\n   ENUM_VALUE(REGISTER_FIRST,             0)\n   ENUM_VALUE(REGISTER_TEMP_FIRST,        120)\n   ENUM_VALUE(REGISTER_LAST,              127)\n   ENUM_VALUE(KCACHE_BANK0_FIRST,         128)\n   ENUM_VALUE(KCACHE_BANK0_LAST,          159)\n   ENUM_VALUE(KCACHE_BANK1_FIRST,         160)\n   ENUM_VALUE(KCACHE_BANK1_LAST,          191)\n   ENUM_VALUE(LDS_OQ_A,                   219)\n   ENUM_VALUE(LDS_OQ_B,                   220)\n   ENUM_VALUE(LDS_OQ_A_POP,               221)\n   ENUM_VALUE(LDS_OQ_B_POP,               222)\n   ENUM_VALUE(LDS_DIRECT_A,               223)\n   ENUM_VALUE(LDS_DIRECT_B,               224)\n   ENUM_VALUE(TIME_HI,                    227)\n   ENUM_VALUE(TIME_LO,                    228)\n   ENUM_VALUE(MASK_HI,                    229)\n   ENUM_VALUE(MASK_LO,                    230)\n   ENUM_VALUE(HW_WAVE_ID,                 231)\n   ENUM_VALUE(SIMD_ID,                    232)\n   ENUM_VALUE(SE_ID,                      233)\n   ENUM_VALUE(HW_THREADGRP_ID,            234)\n   ENUM_VALUE(WAVE_ID_IN_GRP,             235)\n   ENUM_VALUE(NUM_THREADGRP_WAVES,        236)\n   ENUM_VALUE(HW_ALU_ODD,                 237)\n   ENUM_VALUE(LOOP_IDX,                   238)\n   ENUM_VALUE(PARAM_BASE_ADDR,            240)\n   ENUM_VALUE(NEW_PRIM_MASK,              241)\n   ENUM_VALUE(PRIM_MASK_HI,               242)\n   ENUM_VALUE(PRIM_MASK_LO,               243)\n   ENUM_VALUE(IMM_1_DBL_L,                244)\n   ENUM_VALUE(IMM_1_DBL_M,                245)\n   ENUM_VALUE(IMM_0_5_DBL_L,              246)\n   ENUM_VALUE(IMM_0_5_DBL_M,              247)\n   ENUM_VALUE(IMM_0,                      248)\n   ENUM_VALUE(IMM_1,                      249)\n   ENUM_VALUE(IMM_1_INT,                  250)\n   ENUM_VALUE(IMM_M_1_INT,                251)\n   ENUM_VALUE(IMM_0_5,                    252)\n   ENUM_VALUE(LITERAL,                    253)\n   ENUM_VALUE(PV,                         254)\n   ENUM_VALUE(PS,                         255)\n   ENUM_VALUE(CONST_FILE_FIRST,           256)\n   ENUM_VALUE(CONST_FILE_LAST,            511)\nENUM_END(SQ_ALU_SRC)\n\nENUM_BEG(SQ_ALU_VEC_BANK_SWIZZLE, uint32_t)\n   ENUM_VALUE(VEC_012,                    0)\n   ENUM_VALUE(VEC_021,                    1)\n   ENUM_VALUE(VEC_120,                    2)\n   ENUM_VALUE(VEC_102,                    3)\n   ENUM_VALUE(VEC_201,                    4)\n   ENUM_VALUE(VEC_210,                    5)\nENUM_END(SQ_ALU_VEC_BANK_SWIZZLE)\n\nENUM_BEG(SQ_ALU_SCL_BANK_SWIZZLE, uint32_t)\n   ENUM_VALUE(SCL_210,                    0)\n   ENUM_VALUE(SCL_122,                    1)\n   ENUM_VALUE(SCL_212,                    2)\n   ENUM_VALUE(SCL_221,                    3)\nENUM_END(SQ_ALU_SCL_BANK_SWIZZLE)\n\nENUM_BEG(SQ_CF_COND, uint32_t)\n   ENUM_VALUE(ACTIVE,                     0)\n   ENUM_VALUE(ALWAYS_FALSE,               1)\n   ENUM_VALUE(CF_BOOL,                    2)\n   ENUM_VALUE(CF_NOT_BOOL,                3)\nENUM_END(SQ_CF_COND)\n\nENUM_BEG(SQ_DATA_FORMAT, uint32_t)\n   ENUM_VALUE(FMT_INVALID,                0)\n   ENUM_VALUE(FMT_8,                      1)\n   ENUM_VALUE(FMT_4_4,                    2)\n   ENUM_VALUE(FMT_3_3_2,                  3)\n   ENUM_VALUE(FMT_16,                     5)\n   ENUM_VALUE(FMT_16_FLOAT,               6)\n   ENUM_VALUE(FMT_8_8,                    7)\n   ENUM_VALUE(FMT_5_6_5,                  8)\n   ENUM_VALUE(FMT_6_5_5,                  9)\n   ENUM_VALUE(FMT_1_5_5_5,                10)\n   ENUM_VALUE(FMT_4_4_4_4,                11)\n   ENUM_VALUE(FMT_5_5_5_1,                12)\n   ENUM_VALUE(FMT_32,                     13)\n   ENUM_VALUE(FMT_32_FLOAT,               14)\n   ENUM_VALUE(FMT_16_16,                  15)\n   ENUM_VALUE(FMT_16_16_FLOAT,            16)\n   ENUM_VALUE(FMT_8_24,                   17)\n   ENUM_VALUE(FMT_8_24_FLOAT,             18)\n   ENUM_VALUE(FMT_24_8,                   19)\n   ENUM_VALUE(FMT_24_8_FLOAT,             20)\n   ENUM_VALUE(FMT_10_11_11,               21)\n   ENUM_VALUE(FMT_10_11_11_FLOAT,         22)\n   ENUM_VALUE(FMT_11_11_10,               23)\n   ENUM_VALUE(FMT_11_11_10_FLOAT,         24)\n   ENUM_VALUE(FMT_2_10_10_10,             25)\n   ENUM_VALUE(FMT_8_8_8_8,                26)\n   ENUM_VALUE(FMT_10_10_10_2,             27)\n   ENUM_VALUE(FMT_X24_8_32_FLOAT,         28)\n   ENUM_VALUE(FMT_32_32,                  29)\n   ENUM_VALUE(FMT_32_32_FLOAT,            30)\n   ENUM_VALUE(FMT_16_16_16_16,            31)\n   ENUM_VALUE(FMT_16_16_16_16_FLOAT,      32)\n   ENUM_VALUE(FMT_32_32_32_32,            34)\n   ENUM_VALUE(FMT_32_32_32_32_FLOAT,      35)\n   ENUM_VALUE(FMT_1,                      37)\n   ENUM_VALUE(FMT_GB_GR,                  39)\n   ENUM_VALUE(FMT_BG_RG,                  40)\n   ENUM_VALUE(FMT_32_AS_8,                41)\n   ENUM_VALUE(FMT_32_AS_8_8,              42)\n   ENUM_VALUE(FMT_5_9_9_9_SHAREDEXP,      43)\n   ENUM_VALUE(FMT_8_8_8,                  44)\n   ENUM_VALUE(FMT_16_16_16,               45)\n   ENUM_VALUE(FMT_16_16_16_FLOAT,         46)\n   ENUM_VALUE(FMT_32_32_32,               47)\n   ENUM_VALUE(FMT_32_32_32_FLOAT,         48)\n   ENUM_VALUE(FMT_BC1,                    49)\n   ENUM_VALUE(FMT_BC2,                    50)\n   ENUM_VALUE(FMT_BC3,                    51)\n   ENUM_VALUE(FMT_BC4,                    52)\n   ENUM_VALUE(FMT_BC5,                    53)\n   ENUM_VALUE(FMT_APC0,                   54)\n   ENUM_VALUE(FMT_APC1,                   55)\n   ENUM_VALUE(FMT_APC2,                   56)\n   ENUM_VALUE(FMT_APC3,                   57)\n   ENUM_VALUE(FMT_APC4,                   58)\n   ENUM_VALUE(FMT_APC5,                   59)\n   ENUM_VALUE(FMT_APC6,                   60)\n   ENUM_VALUE(FMT_APC7,                   61)\n   ENUM_VALUE(FMT_CTX1,                   62)\n   ENUM_VALUE(FMT_MASK,                   0x3F)\nENUM_END(SQ_DATA_FORMAT)\n\nENUM_BEG(SQ_EXPORT_TYPE, uint32_t)\n   ENUM_VALUE(PIXEL,                      0)\n   ENUM_VALUE(POS,                        1)\n   ENUM_VALUE(PARAM,                      2)\nENUM_END(SQ_EXPORT_TYPE)\n\nENUM_BEG(SQ_MEM_EXPORT_TYPE, uint32_t)\n   ENUM_VALUE(WRITE,                      0)\n   ENUM_VALUE(WRITE_IND,                  1)\n   ENUM_VALUE(READ,                       2)\n   ENUM_VALUE(READ_IND,                   3)\nENUM_END(SQ_MEM_EXPORT_TYPE)\n\nENUM_BEG(SQ_CF_KCACHE_MODE, uint32_t)\n   ENUM_VALUE(NOP,                        0)\n   ENUM_VALUE(LOCK_1,                     1)\n   ENUM_VALUE(LOCK_2,                     2)\n   ENUM_VALUE(LOCK_LOOP_INDEX,            3)\nENUM_END(SQ_CF_KCACHE_MODE)\n\nENUM_BEG(SQ_CHAN, uint32_t)\n   ENUM_VALUE(X,                          0)\n   ENUM_VALUE(Y,                          1)\n   ENUM_VALUE(Z,                          2)\n   ENUM_VALUE(W,                          3)\n   ENUM_VALUE(T,                          4)\nENUM_END(SQ_CHAN)\n\nENUM_BEG(SQ_ENDIAN, uint32_t)\n   ENUM_VALUE(NONE,                       0)\n   ENUM_VALUE(SWAP_8IN16,                 1)\n   ENUM_VALUE(SWAP_8IN32,                 2)\n   ENUM_VALUE(AUTO,                       3)\nENUM_END(SQ_ENDIAN)\n\nENUM_BEG(SQ_FORMAT_COMP, uint32_t)\n   ENUM_VALUE(UNSIGNED,                   0)\n   ENUM_VALUE(SIGNED,                     1)\nENUM_END(SQ_FORMAT_COMP)\n\nENUM_BEG(SQ_INDEX_MODE, uint32_t)\n   ENUM_VALUE(AR_X,                       0)\n   ENUM_VALUE(AR_Y,                       1)\n   ENUM_VALUE(AR_Z,                       2)\n   ENUM_VALUE(AR_W,                       3)\n   ENUM_VALUE(LOOP,                       4)\nENUM_END(SQ_INDEX_MODE)\n\nENUM_BEG(SQ_NUM_FORMAT, uint32_t)\n   ENUM_VALUE(NORM,                       0)\n   ENUM_VALUE(INT,                        1)\n   ENUM_VALUE(SCALED,                     2)\nENUM_END(SQ_NUM_FORMAT)\n\nENUM_BEG(SQ_PRED_SEL, uint32_t)\n   ENUM_VALUE(OFF,                        0)\n   ENUM_VALUE(ZERO,                       2)\n   ENUM_VALUE(ONE,                        3)\nENUM_END(SQ_PRED_SEL)\n\nENUM_BEG(SQ_SRF_MODE, uint32_t)\n   ENUM_VALUE(ZERO_CLAMP_MINUS_ONE,       0)\n   ENUM_VALUE(NO_ZERO,                    1)\nENUM_END(SQ_SRF_MODE)\n\nENUM_BEG(SQ_REL, uint32_t)\n   ENUM_VALUE(ABS,                        0)\n   ENUM_VALUE(REL,                        1)\nENUM_END(SQ_REL)\n\nENUM_BEG(SQ_RES_OFFSET, uint32_t)\n   ENUM_VALUE(PS_TEX_RESOURCE_0,          0x0)\n   ENUM_VALUE(PS_BUF_RESOURCE_0,          0x80)\n\n   ENUM_VALUE(VS_TEX_RESOURCE_0,          0xA0)\n   ENUM_VALUE(VS_BUF_RESOURCE_0,          0x120)\n   ENUM_VALUE(VS_GSOUT_RESOURCE,          0x13F)\n   ENUM_VALUE(VS_ATTRIB_RESOURCE_0,       0x140)\n\n   ENUM_VALUE(GS_TEX_RESOURCE_0,          0x150)\n   ENUM_VALUE(GS_BUF_RESOURCE_0,          0x1D0)\n   ENUM_VALUE(GS_GSIN_RESOURCE,           0x1EF)\nENUM_END(SQ_RES_OFFSET)\n\nENUM_BEG(SQ_SEL, uint32_t)\n   ENUM_VALUE(SEL_X,                      0)\n   ENUM_VALUE(SEL_Y,                      1)\n   ENUM_VALUE(SEL_Z,                      2)\n   ENUM_VALUE(SEL_W,                      3)\n   ENUM_VALUE(SEL_0,                      4)\n   ENUM_VALUE(SEL_1,                      5)\n   ENUM_VALUE(SEL_MASK,                   7)\nENUM_END(SQ_SEL)\n\nENUM_BEG(SQ_TEX_COORD_TYPE, uint32_t)\n   ENUM_VALUE(UNNORMALIZED,               0)\n   ENUM_VALUE(NORMALIZED,                 1)\nENUM_END(SQ_TEX_COORD_TYPE)\n\nENUM_BEG(SQ_TEX_DIM, uint32_t)\n   ENUM_VALUE(DIM_1D,                     0)\n   ENUM_VALUE(DIM_2D,                     1)\n   ENUM_VALUE(DIM_3D,                     2)\n   ENUM_VALUE(DIM_CUBEMAP,                3)\n   ENUM_VALUE(DIM_1D_ARRAY,               4)\n   ENUM_VALUE(DIM_2D_ARRAY,               5)\n   ENUM_VALUE(DIM_2D_MSAA,                6)\n   ENUM_VALUE(DIM_2D_ARRAY_MSAA,          7)\nENUM_END(SQ_TEX_DIM)\n\nENUM_BEG(SQ_TEX_VTX_TYPE, uint32_t)\n   ENUM_VALUE(INVALID_TEXTURE,            0)\n   ENUM_VALUE(INVALID_BUFFER,             1)\n   ENUM_VALUE(VALID_TEXTURE,              2)\n   ENUM_VALUE(VALID_BUFFER,               3)\nENUM_END(SQ_TEX_VTX_TYPE)\n\nENUM_BEG(SQ_TILE_TYPE, uint32_t)\n   ENUM_VALUE(DEFAULT,                    0)\n   ENUM_VALUE(DEPTH,                      1)\nENUM_END(SQ_TILE_TYPE)\n\nENUM_BEG(SQ_TILE_MODE, uint32_t)\n   ENUM_VALUE(DEFAULT,                    0)\n   ENUM_VALUE(LINEAR_ALIGNED,             1)\n   ENUM_VALUE(TILED_1D_THIN1,             2)\n   ENUM_VALUE(TILED_1D_THICK,             3)\n   ENUM_VALUE(TILED_2D_THIN1,             4)\n   ENUM_VALUE(TILED_2D_THIN2,             5)\n   ENUM_VALUE(TILED_2D_THIN4,             6)\n   ENUM_VALUE(TILED_2D_THICK,             7)\n   ENUM_VALUE(TILED_2B_THIN1,             8)\n   ENUM_VALUE(TILED_2B_THIN2,             9)\n   ENUM_VALUE(TILED_2B_THIN4,             10)\n   ENUM_VALUE(TILED_2B_THICK,             11)\n   ENUM_VALUE(TILED_3D_THIN1,             12)\n   ENUM_VALUE(TILED_3D_THICK,             13)\n   ENUM_VALUE(TILED_3B_THIN1,             14)\n   ENUM_VALUE(TILED_3B_THICK,             15)\nENUM_END(SQ_TILE_MODE)\n\nENUM_BEG(SQ_VTX_CLAMP, uint32_t)\n   ENUM_VALUE(TO_ZERO,                    0)\n   ENUM_VALUE(TO_NAN,                     1)\nENUM_END(SQ_VTX_CLAMP)\n\nENUM_BEG(SQ_VTX_FETCH_TYPE, uint32_t)\n   ENUM_VALUE(VERTEX_DATA,                0)\n   ENUM_VALUE(INSTANCE_DATA,              1)\n   ENUM_VALUE(NO_INDEX_OFFSET,            2)\nENUM_END(SQ_VTX_FETCH_TYPE)\n\nENUM_BEG(SQ_TEX_CLAMP, uint32_t)\n   ENUM_VALUE(WRAP,                       0)\n   ENUM_VALUE(MIRROR,                     1)\n   ENUM_VALUE(CLAMP_LAST_TEXEL,           2)\n   ENUM_VALUE(MIRROR_ONCE_LAST_TEXEL,     3)\n   ENUM_VALUE(CLAMP_HALF_BORDER,          4)\n   ENUM_VALUE(MIRROR_ONCE_HALF_BORDER,    5)\n   ENUM_VALUE(CLAMP_BORDER,               6)\n   ENUM_VALUE(MIRROR_ONCE_BORDER,         7)\nENUM_END(SQ_TEX_CLAMP)\n\nENUM_BEG(SQ_TEX_ANISO, uint32_t)\n   ENUM_VALUE(ANISO_1_TO_1,               0)\n   ENUM_VALUE(ANISO_2_TO_1,               1)\n   ENUM_VALUE(ANISO_4_TO_1,               2)\n   ENUM_VALUE(ANISO_8_TO_1,               3)\n   ENUM_VALUE(ANISO_16_TO_1,              4)\nENUM_END(SQ_TEX_ANISO)\n\nENUM_BEG(SQ_TEX_BORDER_COLOR, uint32_t)\n   ENUM_VALUE(TRANS_BLACK,                0)\n   ENUM_VALUE(OPAQUE_BLACK,               1)\n   ENUM_VALUE(OPAQUE_WHITE,               2)\n   ENUM_VALUE(REGISTER,                   3)\nENUM_END(SQ_TEX_BORDER_COLOR)\n\nENUM_BEG(SQ_TEX_CHROMA_KEY, uint32_t)\n   ENUM_VALUE(DISABLED,                   0)\n   ENUM_VALUE(KILL,                       1)\n   ENUM_VALUE(BLEND,                      2)\nENUM_END(SQ_TEX_CHROMA_KEY)\n\nENUM_BEG(SQ_TEX_MPEG_CLAMP, uint32_t)\n   ENUM_VALUE(OFF,                        0)\n   ENUM_VALUE(CLAMP_9,                    1)\n   ENUM_VALUE(CLAMP_10,                   2)\nENUM_END(SQ_TEX_MPEG_CLAMP)\n\nENUM_BEG(SQ_TEX_ROUNDING_MODE, uint32_t)\n   ENUM_VALUE(TRUNCATE,                   0)\n   ENUM_VALUE(NEAREST_EVEN,               1)\nENUM_END(SQ_TEX_ROUNDING_MODE)\n\nENUM_BEG(SQ_TEX_XY_FILTER, uint32_t)\n   ENUM_VALUE(POINT,                      0)\n   ENUM_VALUE(BILINEAR,                   1)\n   ENUM_VALUE(BICUBIC,                    2)\nENUM_END(SQ_TEX_XY_FILTER)\n\nENUM_BEG(SQ_TEX_Z_FILTER, uint32_t)\n   ENUM_VALUE(NONE,                       0)\n   ENUM_VALUE(POINT,                      1)\n   ENUM_VALUE(LINEAR,                     2)\nENUM_END(SQ_TEX_Z_FILTER)\n\nENUM_NAMESPACE_EXIT(latte)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef LATTE_ENUM_SQ_H\n"
  },
  {
    "path": "src/libgpu/latte/latte_enum_vgt.h",
    "content": "#ifndef LATTE_ENUM_VGT_H\n#define LATTE_ENUM_VGT_H\n\n#include <common/enum_start.inl>\n\nENUM_NAMESPACE_ENTER(latte)\n\nENUM_BEG(VGT_DI_MAJOR_MODE, uint32_t)\n   ENUM_VALUE(MODE0,                            0)\n   ENUM_VALUE(MODE1,                            1)\nENUM_END(VGT_DI_MAJOR_MODE)\n\nENUM_BEG(VGT_DI_PRIMITIVE_TYPE, uint32_t)\n   ENUM_VALUE(NONE,                             0)\n   ENUM_VALUE(POINTLIST,                        1)\n   ENUM_VALUE(LINELIST,                         2)\n   ENUM_VALUE(LINESTRIP,                        3)\n   ENUM_VALUE(TRILIST,                          4)\n   ENUM_VALUE(TRIFAN,                           5)\n   ENUM_VALUE(TRISTRIP,                         6)\n   ENUM_VALUE(UNUSED_0,                         7)\n   ENUM_VALUE(UNUSED_1,                         8)\n   ENUM_VALUE(UNUSED_2,                         9)\n   ENUM_VALUE(LINELIST_ADJ,                     10)\n   ENUM_VALUE(LINESTRIP_ADJ,                    11)\n   ENUM_VALUE(TRILIST_ADJ,                      12)\n   ENUM_VALUE(TRISTRIP_ADJ,                     13)\n   ENUM_VALUE(UNUSED_3,                         14)\n   ENUM_VALUE(UNUSED_4,                         15)\n   ENUM_VALUE(TRI_WITH_WFLAGS,                  16)\n   ENUM_VALUE(RECTLIST,                         17)\n   ENUM_VALUE(LINELOOP,                         18)\n   ENUM_VALUE(QUADLIST,                         19)\n   ENUM_VALUE(QUADSTRIP,                        20)\n   ENUM_VALUE(POLYGON,                          21)\n   ENUM_VALUE(COPY_RECT_LIST_2D_V0,             22)\n   ENUM_VALUE(COPY_RECT_LIST_2D_V1,             23)\n   ENUM_VALUE(COPY_RECT_LIST_2D_V2,             24)\n   ENUM_VALUE(COPY_RECT_LIST_2D_V3,             25)\n   ENUM_VALUE(FILL_RECT_LIST_2D,                26)\n   ENUM_VALUE(LINE_STRIP_2D,                    27)\n   ENUM_VALUE(TRI_STRIP_2D,                     28)\nENUM_END(VGT_DI_PRIMITIVE_TYPE)\n\nENUM_BEG(VGT_DI_SRC_SEL, uint32_t)\n   ENUM_VALUE(DMA,                              0)\n   ENUM_VALUE(IMMEDIATE,                        1)\n   ENUM_VALUE(AUTO_INDEX,                       2)\n   ENUM_VALUE(RESERVED,                         3)\nENUM_END(VGT_DI_SRC_SEL)\n\nENUM_BEG(VGT_DMA_SWAP, uint32_t)\n   ENUM_VALUE(NONE,                             0)\n   ENUM_VALUE(SWAP_16_BIT,                      1)\n   ENUM_VALUE(SWAP_32_BIT,                      2)\n   ENUM_VALUE(SWAP_WORD,                        3)\nENUM_END(VGT_DMA_SWAP)\n\nENUM_BEG(VGT_GS_CUT_MODE, uint32_t)\n   ENUM_VALUE(CUT_1024,                         0)\n   ENUM_VALUE(CUT_512,                          1)\n   ENUM_VALUE(CUT_256,                          2)\n   ENUM_VALUE(CUT_128,                          3)\nENUM_END(VGT_GS_CUT_MODE)\n\nENUM_BEG(VGT_GS_ENABLE_MODE, uint32_t)\n   ENUM_VALUE(OFF,                              0)\n   ENUM_VALUE(SCENARIO_A,                       1)\n   ENUM_VALUE(SCENARIO_B,                       2)\n   ENUM_VALUE(SCENARIO_G,                       3)\nENUM_END(VGT_GS_ENABLE_MODE)\n\nENUM_BEG(VGT_EVENT_TYPE, uint32_t)\n   ENUM_VALUE(CACHE_FLUSH_TS,                   4)\n   ENUM_VALUE(CONTEXT_DONE,                     5)\n   ENUM_VALUE(CACHE_FLUSH,                      6)\n   ENUM_VALUE(VIZQUERY_START,                   7)\n   ENUM_VALUE(VIZQUERY_END,                     8)\n   ENUM_VALUE(SC_WAIT_WC,                       9)\n   ENUM_VALUE(MPASS_PS_CP_REFETCH,              10)\n   ENUM_VALUE(MPASS_PS_RST_START,               11)\n   ENUM_VALUE(MPASS_PS_INCR_START,              12)\n   ENUM_VALUE(RST_PIX_CNT,                      13)\n   ENUM_VALUE(RST_VTX_CNT,                      14)\n   ENUM_VALUE(VS_PARTIAL_FLUSH,                 15)\n   ENUM_VALUE(PS_PARTIAL_FLUSH,                 16)\n   ENUM_VALUE(CACHE_FLUSH_AND_INV_TS_EVENT,     20)\n   ENUM_VALUE(ZPASS_DONE,                       21)\n   ENUM_VALUE(CACHE_FLUSH_AND_INV_EVENT,        22)\n   ENUM_VALUE(PERFCOUNTER_START,                23)\n   ENUM_VALUE(PERFCOUNTER_STOP,                 24)\n   ENUM_VALUE(PIPELINESTAT_START,               25)\n   ENUM_VALUE(PIPELINESTAT_STOP,                26)\n   ENUM_VALUE(PERFCOUNTER_SAMPLE,               27)\n   ENUM_VALUE(FLUSH_ES_OUTPUT,                  28)\n   ENUM_VALUE(FLUSH_GS_OUTPUT,                  29)\n   ENUM_VALUE(SAMPLE_PIPELINESTAT,              30)\n   ENUM_VALUE(SO_VGTSTREAMOUT_FLUSH,            31)\n   ENUM_VALUE(SAMPLE_STREAMOUTSTATS,            32)\n   ENUM_VALUE(RESET_VTX_CNT,                    33)\n   ENUM_VALUE(BLOCK_CONTEXT_DONE,               34)\n   ENUM_VALUE(VGT_FLUSH,                        36)\n   ENUM_VALUE(SQ_NON_EVENT,                     38)\n   ENUM_VALUE(SC_SEND_DB_VPZ,                   39)\n   ENUM_VALUE(BOTTOM_OF_PIPE_TS,                40)\n   ENUM_VALUE(FLUSH_SX_TS,                      41)\n   ENUM_VALUE(DB_CACHE_FLUSH_AND_INV,           42)\n   ENUM_VALUE(FLUSH_AND_INV_DB_DATA_TS,         43)\n   ENUM_VALUE(FLUSH_AND_INV_DB_META,            44)\n   ENUM_VALUE(FLUSH_AND_INV_CB_DATA_TS,         45)\n   ENUM_VALUE(FLUSH_AND_INV_CB_META,            46)\nENUM_END(VGT_EVENT_TYPE)\n\nENUM_BEG(VGT_EVENT_INDEX, uint32_t)\n   ENUM_VALUE(GENERIC,                          0)\n   ENUM_VALUE(ZPASS_DONE,                       1)\n   ENUM_VALUE(SAMPLE_PIPELINESTAT,              2)\n   ENUM_VALUE(SAMPLE_STREAMOUTSTAT,             3)\n   ENUM_VALUE(PARTIAL_FLUSH,                    4)\n   ENUM_VALUE(TS,                               5)\n   ENUM_VALUE(CACHE_FLUSH,                      7)\nENUM_END(VGT_EVENT_INDEX)\n\nENUM_BEG(VGT_GS_OUT_PRIMITIVE_TYPE, uint32_t)\n   ENUM_VALUE(POINTLIST,                        0)\n   ENUM_VALUE(LINESTRIP,                        1)\n   ENUM_VALUE(TRISTRIP,                         2)\nENUM_END(VGT_GS_OUT_PRIMITIVE_TYPE)\n\nENUM_BEG(VGT_INDEX_TYPE, uint32_t)\n   ENUM_VALUE(INDEX_16,                         0)\n   ENUM_VALUE(INDEX_32,                         1)\nENUM_END(VGT_INDEX_TYPE)\n\nENUM_BEG(VGT_OUTPUT_PATH_SELECT, uint32_t)\n   ENUM_VALUE(VTX_REUSE,                        0)\n   ENUM_VALUE(TESS_EN,                          1)\n   ENUM_VALUE(PASSTHRU,                         2)\n   ENUM_VALUE(GS_BLOCK,                         3)\nENUM_END(VGT_OUTPUT_PATH_SELECT)\n\nENUM_NAMESPACE_EXIT(latte)\n\n#include <common/enum_end.inl>\n\n#endif // ifdef LATTE_ENUM_VGT_H\n"
  },
  {
    "path": "src/libgpu/latte/latte_formats.h",
    "content": "#pragma once\n#include <array>\n#include <libgpu/latte/latte_enum_cb.h>\n#include <libgpu/latte/latte_enum_db.h>\n#include <libgpu/latte/latte_enum_sq.h>\n#include <libgpu/latte/latte_enum_common.h>\n#include <string>\n\nnamespace latte\n{\n\nenum SurfaceFormat : uint32_t\n{\n   Invalid           = 0x0000, // FMT_INVALID\n   R8Unorm           = 0x0001, // FMT_8\n   R8Uint            = 0x0101, // FMT_8\n   R8Snorm           = 0x0201, // FMT_8\n   R8Sint            = 0x0301, // FMT_8\n   R4G4Unorm         = 0x0002, // FMT_4_4\n   R16Unorm          = 0x0005, // FMT_16\n   R16Uint           = 0x0105, // FMT_16\n   R16Snorm          = 0x0205, // FMT_16\n   R16Sint           = 0x0305, // FMT_16\n   R16Float          = 0x0806, // FMT_16_FLOAT\n   R8G8Unorm         = 0x0007, // FMT_8_8\n   R8G8Uint          = 0x0107, // FMT_8_8\n   R8G8Snorm         = 0x0207, // FMT_8_8\n   R8G8Sint          = 0x0307, // FMT_8_8\n   R5G6B5Unorm       = 0x0008, // FMT_5_6_5\n   R5G5B5A1Unorm     = 0x000a, // FMT_1_5_5_5\n   R4G4B4A4Unorm     = 0x000b, // FMT_4_4_4_4\n   A1B5G5R5Unorm     = 0x000c, // FMT_5_5_5_1\n   R32Uint           = 0x010d, // FMT_32\n   R32Sint           = 0x030d, // FMT_32\n   R32Float          = 0x080e, // FMT_32_FLOAT\n   R16G16Unorm       = 0x000f, // FMT_16_16\n   R16G16Uint        = 0x010f, // FMT_16_16\n   R16G16Snorm       = 0x020f, // FMT_16_16\n   R16G16Sint        = 0x030f, // FMT_16_16\n   R16G16Float       = 0x0810, // FMT_16_16_FLOAT\n   D24UnormS8Uint    = 0x0011, // FMT_8_24\n   X24G8Uint         = 0x0111, // FMT_8_24\n   R11G11B10Float    = 0x0816, // FMT_10_11_11_FLOAT\n   R10G10B10A2Unorm  = 0x0019, // FMT_2_10_10_10\n   R10G10B10A2Uint   = 0x0119, // FMT_2_10_10_10\n   R10G10B10A2Snorm  = 0x0219, // FMT_2_10_10_10\n   R10G10B10A2Sint   = 0x0319, // FMT_2_10_10_10\n   R8G8B8A8Unorm     = 0x001a, // FMT_8_8_8_8\n   R8G8B8A8Uint      = 0x011a, // FMT_8_8_8_8\n   R8G8B8A8Snorm     = 0x021a, // FMT_8_8_8_8\n   R8G8B8A8Sint      = 0x031a, // FMT_8_8_8_8\n   R8G8B8A8Srgb      = 0x041a, // FMT_8_8_8_8\n   A2B10G10R10Unorm  = 0x001b, // FMT_10_10_10_2\n   A2B10G10R10Uint   = 0x011b, // FMT_10_10_10_2\n   D32FloatS8UintX24 = 0x081c, // FMT_X24_8_32_FLOAT\n   D32G8UintX24      = 0x011c, // FMT_X24_8_32_FLOAT\n   R32G32Uint        = 0x011d, // FMT_32_32\n   R32G32Sint        = 0x031d, // FMT_32_32\n   R32G32Float       = 0x081e, // FMT_32_32_FLOAT\n   R16G16B16A16Unorm = 0x001f, // FMT_16_16_16_16\n   R16G16B16A16Uint  = 0x011f, // FMT_16_16_16_16\n   R16G16B16A16Snorm = 0x021f, // FMT_16_16_16_16\n   R16G16B16A16Sint  = 0x031f, // FMT_16_16_16_16\n   R16G16B16A16Float = 0x0820, // FMT_16_16_16_16_FLOAT\n   R32G32B32A32Uint  = 0x0122, // FMT_32_32_32_32\n   R32G32B32A32Sint  = 0x0322, // FMT_32_32_32_32\n   R32G32B32A32Float = 0x0823, // FMT_32_32_32_32_FLOAT\n   BC1Unorm          = 0x0031, // FMT_BC1\n   BC1Srgb           = 0x0431, // FMT_BC1\n   BC2Unorm          = 0x0032, // FMT_BC2\n   BC2Srgb           = 0x0432, // FMT_BC2\n   BC3Unorm          = 0x0033, // FMT_BC3\n   BC3Srgb           = 0x0433, // FMT_BC3\n   BC4Unorm          = 0x0034, // FMT_BC4\n   BC4Snorm          = 0x0234, // FMT_BC4\n   BC5Unorm          = 0x0035, // FMT_BC5\n   BC5Snorm          = 0x0235, // FMT_BC5\n   NV12              = 0x0081, // FMT_NV12\n};\n\nenum class DataFormatMetaType : uint32_t\n{\n   None,\n   UINT,\n   FLOAT\n};\n\nstruct DataFormatMetaElem\n{\n   uint32_t index;\n   uint32_t start;\n   uint32_t length;\n};\n\nstruct DataFormatMeta\n{\n   uint32_t inputWidth;\n   uint32_t inputCount;\n   DataFormatMetaType type;\n   DataFormatMetaElem elems[4];\n};\n\nSurfaceFormat\ngetSurfaceFormat(latte::SQ_DATA_FORMAT dataFormat,\n                 latte::SQ_NUM_FORMAT numFormat,\n                 latte::SQ_FORMAT_COMP formatComp,\n                 bool forceDegamma);\n\nlatte::SQ_DATA_FORMAT\ngetSurfaceFormatDataFormat(latte::SurfaceFormat format);\n\nSurfaceFormat\ngetColorBufferSurfaceFormat(latte::CB_FORMAT format, latte::CB_NUMBER_TYPE numberType);\n\nSurfaceFormat\ngetDepthBufferSurfaceFormat(latte::DB_FORMAT format);\n\nuint32_t\ngetDataFormatBitsPerElement(latte::SQ_DATA_FORMAT format);\n\nbool\ngetDataFormatIsCompressed(latte::SQ_DATA_FORMAT format);\n\nstd::string\ngetDataFormatName(latte::SQ_DATA_FORMAT format);\n\nDataFormatMeta\ngetDataFormatMeta(latte::SQ_DATA_FORMAT format);\n\nuint32_t\ngetDataFormatComponents(latte::SQ_DATA_FORMAT format);\n\nuint32_t\ngetDataFormatComponentBits(latte::SQ_DATA_FORMAT format);\n\nbool\ngetDataFormatIsFloat(latte::SQ_DATA_FORMAT format);\n\nuint32_t\ngetTexDimDimensions(latte::SQ_TEX_DIM dim);\n\nlatte::SQ_TILE_MODE\ngetArrayModeTileMode(latte::BUFFER_ARRAY_MODE mode);\n\n} // namespace gpu\n"
  },
  {
    "path": "src/libgpu/latte/latte_instructions.h",
    "content": "#pragma once\n#include \"latte_enum_sq.h\"\n\n#include <cstdint>\n#include <common/bitfield.h>\n#include <common/fixed.h>\n#include <common/structsize.h>\n\nnamespace latte\n{\n\n#pragma pack(push, 1)\n\nenum SQ_CF_INST : uint32_t\n{\n#define CF_INST(name, value) SQ_CF_INST_##name = value,\n#include \"latte_instructions_def.inl\"\n#undef CF_INST\n   SQ_CF_INST_INVALID = 0xFFFFFFFF,\n};\n\nenum SQ_CF_EXP_INST : uint32_t\n{\n#define EXP_INST(name, value) SQ_CF_INST_##name = value,\n#include \"latte_instructions_def.inl\"\n#undef EXP_INST\n   SQ_CF_EXP_INST_INVALID = 0xFFFFFFFF,\n};\n\nenum SQ_CF_ALU_INST : uint32_t\n{\n#define ALU_INST(name, value) SQ_CF_INST_##name = value,\n#include \"latte_instructions_def.inl\"\n#undef ALU_INST\n   SQ_CF_ALU_INST_INVALID = 0xFFFFFFFF,\n};\n\nenum SQ_OP2_INST : uint32_t\n{\n#define ALU_OP2(name, value, srcs, flags) SQ_OP2_INST_##name = value,\n#include \"latte_instructions_def.inl\"\n#undef ALU_OP2\n   SQ_OP2_INST_INVALID = 0xFFFFFFFF,\n};\n\nenum SQ_OP3_INST : uint32_t\n{\n#define ALU_OP3(name, value, srcs, flags) SQ_OP3_INST_##name = value,\n#include \"latte_instructions_def.inl\"\n#undef ALU_OP3\n   SQ_OP3_INST_INVALID = 0xFFFFFFFF,\n};\n\nenum SQ_TEX_INST : uint32_t\n{\n#define TEX_INST(name, value) SQ_TEX_INST_##name = value,\n#include \"latte_instructions_def.inl\"\n#undef TEX_INST\n   SQ_TEX_INST_INVALID = 0xFFFFFFFF,\n};\n\nenum SQ_VTX_INST : uint32_t\n{\n#define VTX_INST(name, value) SQ_VTX_INST_##name = value,\n#include \"latte_instructions_def.inl\"\n#undef VTX_INST\n   SQ_VTX_INST_INVALID = 0xFFFFFFFF,\n};\n\nenum SQ_CF_INST_TYPE : uint32_t\n{\n   SQ_CF_INST_TYPE_NORMAL        = 0,\n   SQ_CF_INST_TYPE_EXPORT        = 1,\n   SQ_CF_INST_TYPE_ALU           = 2,\n   SQ_CF_INST_TYPE_ALU_EXTENDED  = 3,\n};\n\nenum SQ_ALU_FLAGS : uint32_t\n{\n   SQ_ALU_FLAG_NONE              = 0,\n   SQ_ALU_FLAG_VECTOR            = (1 << 0),\n   SQ_ALU_FLAG_TRANSCENDENTAL    = (1 << 1),\n   SQ_ALU_FLAG_REDUCTION         = (1 << 2),\n   SQ_ALU_FLAG_PRED_SET          = (1 << 3),\n   SQ_ALU_FLAG_INT_IN            = (1 << 4),\n   SQ_ALU_FLAG_INT_OUT           = (1 << 5),\n   SQ_ALU_FLAG_UINT_IN           = (1 << 6),\n   SQ_ALU_FLAG_UINT_OUT          = (1 << 7),\n};\n\n// Control flow instruction word 0\nBITFIELD_BEG(SQ_CF_WORD0, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, ADDR);\nBITFIELD_END\n\n// Control flow instruction word 1\nBITFIELD_BEG(SQ_CF_WORD1, uint32_t)\n   BITFIELD_ENTRY(0, 3, uint32_t, POP_COUNT);\n   BITFIELD_ENTRY(3, 5, uint32_t, CF_CONST);\n   BITFIELD_ENTRY(8, 2, SQ_CF_COND, COND);\n   BITFIELD_ENTRY(10, 3, uint32_t, COUNT);\n   BITFIELD_ENTRY(13, 6, uint32_t, CALL_COUNT);\n   BITFIELD_ENTRY(19, 1, uint32_t, COUNT_3);\n   BITFIELD_ENTRY(21, 1, bool, END_OF_PROGRAM);\n   BITFIELD_ENTRY(22, 1, bool, VALID_PIXEL_MODE);\n   BITFIELD_ENTRY(23, 7, SQ_CF_INST, CF_INST);\n   BITFIELD_ENTRY(28, 2, SQ_CF_INST_TYPE, CF_INST_TYPE);\n   BITFIELD_ENTRY(30, 1, bool, WHOLE_QUAD_MODE);\n   BITFIELD_ENTRY(31, 1, bool, BARRIER);\nBITFIELD_END\n\n// Control flow ALU clause instruction word 0\nBITFIELD_BEG(SQ_CF_ALU_WORD0, uint32_t)\n   BITFIELD_ENTRY(0, 22, uint32_t, ADDR);\n   BITFIELD_ENTRY(22, 4, uint32_t, KCACHE_BANK0);\n   BITFIELD_ENTRY(26, 4, uint32_t, KCACHE_BANK1);\n   BITFIELD_ENTRY(30, 2, SQ_CF_KCACHE_MODE, KCACHE_MODE0);\nBITFIELD_END\n\n// Control flow ALU clause instruction word 1\nBITFIELD_BEG(SQ_CF_ALU_WORD1, uint32_t)\n   BITFIELD_ENTRY(0, 2, SQ_CF_KCACHE_MODE, KCACHE_MODE1);\n   BITFIELD_ENTRY(2, 8, uint32_t, KCACHE_ADDR0);\n   BITFIELD_ENTRY(10, 8, uint32_t, KCACHE_ADDR1);\n   BITFIELD_ENTRY(18, 7, uint32_t, COUNT);\n   BITFIELD_ENTRY(25, 1, bool, ALT_CONST);\n   BITFIELD_ENTRY(26, 4, SQ_CF_ALU_INST, CF_INST);\n   BITFIELD_ENTRY(30, 1, bool, WHOLE_QUAD_MODE);\n   BITFIELD_ENTRY(31, 1, bool, BARRIER);\nBITFIELD_END\n\n// ALU instruction word 0\nBITFIELD_BEG(SQ_ALU_WORD0, uint32_t)\n   BITFIELD_ENTRY(0, 9, SQ_ALU_SRC, SRC0_SEL);\n   BITFIELD_ENTRY(9, 1, SQ_REL, SRC0_REL);\n   BITFIELD_ENTRY(10, 2, SQ_CHAN, SRC0_CHAN);\n   BITFIELD_ENTRY(12, 1, bool, SRC0_NEG);\n   BITFIELD_ENTRY(13, 9, SQ_ALU_SRC, SRC1_SEL);\n   BITFIELD_ENTRY(22, 1, SQ_REL, SRC1_REL);\n   BITFIELD_ENTRY(23, 2, SQ_CHAN, SRC1_CHAN);\n   BITFIELD_ENTRY(25, 1, bool, SRC1_NEG);\n   BITFIELD_ENTRY(26, 3, SQ_INDEX_MODE, INDEX_MODE);\n   BITFIELD_ENTRY(29, 2, SQ_PRED_SEL, PRED_SEL);\n   BITFIELD_ENTRY(31, 1, bool, LAST);\nBITFIELD_END\n\n// ALU instruction word 1\nBITFIELD_BEG(SQ_ALU_WORD1, uint32_t)\n   BITFIELD_ENTRY(15, 3, SQ_ALU_ENCODING, ENCODING);\n   BITFIELD_ENTRY(18, 3, SQ_ALU_VEC_BANK_SWIZZLE, BANK_SWIZZLE);\n   BITFIELD_ENTRY(21, 7, uint32_t, DST_GPR);\n   BITFIELD_ENTRY(28, 1, SQ_REL, DST_REL);\n   BITFIELD_ENTRY(29, 2, SQ_CHAN, DST_CHAN);\n   BITFIELD_ENTRY(31, 1, bool, CLAMP);\nBITFIELD_END\n\n// ALU instruction word 1. This subencoding is used for instructions taking 0-2 operands.\nBITFIELD_BEG(SQ_ALU_WORD1_OP2, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, SRC0_ABS);\n   BITFIELD_ENTRY(1, 1, bool, SRC1_ABS);\n   BITFIELD_ENTRY(2, 1, bool, UPDATE_EXECUTE_MASK);\n   BITFIELD_ENTRY(3, 1, bool, UPDATE_PRED);\n   BITFIELD_ENTRY(4, 1, bool, WRITE_MASK);\n   BITFIELD_ENTRY(5, 2, SQ_ALU_OMOD, OMOD);\n   BITFIELD_ENTRY(7, 11, SQ_OP2_INST, ALU_INST);\nBITFIELD_END\n\n// ALU instruction word 1. This subencoding is used for instructions taking 3 operands.\nBITFIELD_BEG(SQ_ALU_WORD1_OP3, uint32_t)\n   BITFIELD_ENTRY(0, 9, SQ_ALU_SRC, SRC2_SEL);\n   BITFIELD_ENTRY(9, 1, SQ_REL, SRC2_REL);\n   BITFIELD_ENTRY(10, 2, SQ_CHAN, SRC2_CHAN);\n   BITFIELD_ENTRY(12, 1, bool, SRC2_NEG);\n   BITFIELD_ENTRY(13, 5, SQ_OP3_INST, ALU_INST);\nBITFIELD_END\n\n// Word 0 of the control flow instruction for alloc/export.\nBITFIELD_BEG(SQ_CF_ALLOC_EXPORT_WORD0, uint32_t)\n   BITFIELD_ENTRY(0, 13, uint32_t, ARRAY_BASE);\n   BITFIELD_ENTRY(13, 2, SQ_EXPORT_TYPE, TYPE);\n   BITFIELD_ENTRY(15, 7, uint32_t, RW_GPR);\n   BITFIELD_ENTRY(22, 1, SQ_REL, RW_REL);\n   BITFIELD_ENTRY(23, 7, uint32_t, INDEX_GPR);\n   BITFIELD_ENTRY(30, 2, uint32_t, ELEM_SIZE);\nBITFIELD_END\n\n// Word 1 of the control flow instruction\nBITFIELD_BEG(SQ_CF_ALLOC_EXPORT_WORD1, uint32_t)\n   BITFIELD_ENTRY(17, 4, uint32_t, BURST_COUNT);\n   BITFIELD_ENTRY(21, 1, bool, END_OF_PROGRAM);\n   BITFIELD_ENTRY(22, 1, bool, VALID_PIXEL_MODE);\n   BITFIELD_ENTRY(23, 7, SQ_CF_EXP_INST, CF_INST);\n   BITFIELD_ENTRY(30, 1, bool, WHOLE_QUAD_MODE);\n   BITFIELD_ENTRY(31, 1, bool, BARRIER);\nBITFIELD_END\n\n// Word 1 of the control flow instruction. This subencoding is used by alloc/exports\n// for all input / outputs to scratch / ring / stream / reduction buffers.\nBITFIELD_BEG(SQ_CF_ALLOC_EXPORT_WORD1_BUF, uint32_t)\n   BITFIELD_ENTRY(0, 12, uint32_t, ARRAY_SIZE);\n   BITFIELD_ENTRY(12, 4, uint32_t, COMP_MASK);\nBITFIELD_END\n\n// Word 1 of the control flow instruction. This subencoding is used by\n// alloc/exports for PIXEL, POS, and PARAM.\nBITFIELD_BEG(SQ_CF_ALLOC_EXPORT_WORD1_SWIZ, uint32_t)\n   BITFIELD_ENTRY(0, 3, SQ_SEL, SEL_X);\n   BITFIELD_ENTRY(3, 3, SQ_SEL, SEL_Y);\n   BITFIELD_ENTRY(6, 3, SQ_SEL, SEL_Z);\n   BITFIELD_ENTRY(9, 3, SQ_SEL, SEL_W);\nBITFIELD_END\n\n// Texture fetch clause instruction word 0\nBITFIELD_BEG(SQ_TEX_WORD0, uint32_t)\n   BITFIELD_ENTRY(0, 5, SQ_TEX_INST, TEX_INST);\n   BITFIELD_ENTRY(5, 1, bool, BC_FRAC_MODE);\n   BITFIELD_ENTRY(7, 1, bool, FETCH_WHOLE_QUAD);\n   BITFIELD_ENTRY(8, 8, uint32_t, RESOURCE_ID);\n   BITFIELD_ENTRY(16, 7, uint32_t, SRC_GPR);\n   BITFIELD_ENTRY(23, 1, SQ_REL, SRC_REL);\n   BITFIELD_ENTRY(24, 1, bool, ALT_CONST);\nBITFIELD_END\n\n// Texture fetch clause instruction word 1\nBITFIELD_BEG(SQ_TEX_WORD1, uint32_t)\n   BITFIELD_ENTRY(0, 7, uint32_t, DST_GPR);\n   BITFIELD_ENTRY(7, 1, SQ_REL, DST_REL);\n   BITFIELD_ENTRY(9, 3, SQ_SEL, DST_SEL_X);\n   BITFIELD_ENTRY(12, 3, SQ_SEL, DST_SEL_Y);\n   BITFIELD_ENTRY(15, 3, SQ_SEL, DST_SEL_Z);\n   BITFIELD_ENTRY(18, 3, SQ_SEL, DST_SEL_W);\n   BITFIELD_ENTRY(21, 7, sfixed_1_3_3_t, LOD_BIAS);\n   BITFIELD_ENTRY(28, 1, SQ_TEX_COORD_TYPE, COORD_TYPE_X);\n   BITFIELD_ENTRY(29, 1, SQ_TEX_COORD_TYPE, COORD_TYPE_Y);\n   BITFIELD_ENTRY(30, 1, SQ_TEX_COORD_TYPE, COORD_TYPE_Z);\n   BITFIELD_ENTRY(31, 1, SQ_TEX_COORD_TYPE, COORD_TYPE_W);\nBITFIELD_END\n\n// Texture fetch clause instruction word 2\nBITFIELD_BEG(SQ_TEX_WORD2, uint32_t)\n   BITFIELD_ENTRY(0, 5, sfixed_1_3_1_t, OFFSET_X);\n   BITFIELD_ENTRY(5, 5, sfixed_1_3_1_t, OFFSET_Y);\n   BITFIELD_ENTRY(10, 5, sfixed_1_3_1_t, OFFSET_Z);\n   BITFIELD_ENTRY(15, 5, uint32_t, SAMPLER_ID);\n   BITFIELD_ENTRY(20, 3, SQ_SEL, SRC_SEL_X);\n   BITFIELD_ENTRY(23, 3, SQ_SEL, SRC_SEL_Y);\n   BITFIELD_ENTRY(26, 3, SQ_SEL, SRC_SEL_Z);\n   BITFIELD_ENTRY(29, 3, SQ_SEL, SRC_SEL_W);\nBITFIELD_END\n\n// Vertex fetch clause instruction word 0.\nBITFIELD_BEG(SQ_VTX_WORD0, uint32_t)\n   BITFIELD_ENTRY(0, 5, SQ_VTX_INST, VTX_INST);\n   BITFIELD_ENTRY(5, 2, SQ_VTX_FETCH_TYPE, FETCH_TYPE);\n   BITFIELD_ENTRY(7, 1, uint32_t, FETCH_WHOLE_QUAD);\n   BITFIELD_ENTRY(8, 8, uint32_t, BUFFER_ID);\n   BITFIELD_ENTRY(16, 7, uint32_t, SRC_GPR);\n   BITFIELD_ENTRY(23, 1, SQ_REL, SRC_REL);\n   BITFIELD_ENTRY(24, 2, SQ_SEL, SRC_SEL_X);\n   BITFIELD_ENTRY(26, 6, uint32_t, MEGA_FETCH_COUNT);\nBITFIELD_END\n\n// Vertex fetch clause instruction word 1\nBITFIELD_BEG(SQ_VTX_WORD1_SEM, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, SEMANTIC_ID);\nBITFIELD_END\n\nBITFIELD_BEG(SQ_VTX_WORD1_GPR, uint32_t)\n   BITFIELD_ENTRY(0, 7, uint32_t, DST_GPR);\n   BITFIELD_ENTRY(7, 1, SQ_REL, DST_REL);\nBITFIELD_END\n\nBITFIELD_BEG(SQ_VTX_WORD1, uint32_t)\n   BITFIELD_ENTRY(9, 3, SQ_SEL, DST_SEL_X);\n   BITFIELD_ENTRY(12, 3, SQ_SEL, DST_SEL_Y);\n   BITFIELD_ENTRY(15, 3, SQ_SEL, DST_SEL_Z);\n   BITFIELD_ENTRY(18, 3, SQ_SEL, DST_SEL_W);\n   BITFIELD_ENTRY(21, 1, bool, USE_CONST_FIELDS);\n   BITFIELD_ENTRY(22, 6, SQ_DATA_FORMAT, DATA_FORMAT);\n   BITFIELD_ENTRY(28, 2, SQ_NUM_FORMAT, NUM_FORMAT_ALL);\n   BITFIELD_ENTRY(30, 1, SQ_FORMAT_COMP, FORMAT_COMP_ALL);\n   BITFIELD_ENTRY(31, 1, SQ_SRF_MODE, SRF_MODE_ALL);\nBITFIELD_END\n\n// Vertex fetch clause instruction word 2\nBITFIELD_BEG(SQ_VTX_WORD2, uint32_t)\n   BITFIELD_ENTRY(0, 16, uint32_t, OFFSET);\n   BITFIELD_ENTRY(16, 2, SQ_ENDIAN, ENDIAN_SWAP);\n   BITFIELD_ENTRY(18, 1, bool, CONST_BUF_NO_STRIDE);\n   BITFIELD_ENTRY(19, 1, bool, MEGA_FETCH);\n   BITFIELD_ENTRY(20, 1, bool, ALT_CONST);\nBITFIELD_END\n\nstruct ControlFlowInst\n{\n   union\n   {\n      struct\n      {\n         SQ_CF_WORD0 word0;\n         SQ_CF_WORD1 word1;\n      };\n\n      struct\n      {\n         SQ_CF_ALU_WORD0 word0;\n         SQ_CF_ALU_WORD1 word1;\n      } alu;\n\n      struct\n      {\n         SQ_CF_ALLOC_EXPORT_WORD0 word0;\n\n         union\n         {\n            SQ_CF_ALLOC_EXPORT_WORD1 word1;\n            SQ_CF_ALLOC_EXPORT_WORD1_BUF buf;\n            SQ_CF_ALLOC_EXPORT_WORD1_SWIZ swiz;\n         };\n      } exp;\n   };\n};\nCHECK_SIZE(ControlFlowInst, 8);\n\nstruct AluInst\n{\n   SQ_ALU_WORD0 word0;\n\n   union\n   {\n      SQ_ALU_WORD1 word1;\n      SQ_ALU_WORD1_OP2 op2;\n      SQ_ALU_WORD1_OP3 op3;\n   };\n};\nCHECK_SIZE(AluInst, 8);\n\nstruct VertexFetchInst\n{\n   SQ_VTX_WORD0 word0;\n\n   union\n   {\n      SQ_VTX_WORD1 word1;\n      SQ_VTX_WORD1_GPR gpr;\n      SQ_VTX_WORD1_SEM sem;\n   };\n\n   SQ_VTX_WORD2 word2;\n   uint32_t padding;\n};\nCHECK_SIZE(VertexFetchInst, 16);\n\nstruct TextureFetchInst\n{\n   SQ_TEX_WORD0 word0;\n   SQ_TEX_WORD1 word1;\n   SQ_TEX_WORD2 word2;\n   uint32_t padding;\n};\nCHECK_SIZE(TextureFetchInst, 16);\n\n#pragma pack(pop)\n\nconst char *getInstructionName(SQ_CF_INST id);\nconst char *getInstructionName(SQ_CF_EXP_INST id);\nconst char *getInstructionName(SQ_CF_ALU_INST id);\nconst char *getInstructionName(SQ_OP2_INST id);\nconst char *getInstructionName(SQ_OP3_INST id);\nconst char *getInstructionName(SQ_TEX_INST id);\nconst char *getInstructionName(SQ_VTX_INST id);\n\nuint32_t getInstructionNumSrcs(SQ_OP2_INST id);\nuint32_t getInstructionNumSrcs(SQ_OP3_INST id);\n\nSQ_ALU_FLAGS getInstructionFlags(SQ_OP2_INST id);\nSQ_ALU_FLAGS getInstructionFlags(SQ_OP3_INST id);\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_instructions_def.inl",
    "content": "/**\n * Reference documentation:\n * R600/R700/Evergreen Assembly Language Format\n * http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/R600-R700-Evergreen_Assembly_Language_Format.pdf\n *\n * ATI R700-Family ISA\n * http://developer.amd.com/wordpress/media/2012/10/R700-Family_Instruction_Set_Architecture.pdf\n *\n * ATI Evergreen-Family ISA\n * http://developer.amd.com/wordpress/media/2012/10/AMD_Evergreen-Family_Instruction_Set_Architecture.pdf\n *\n * AMD HD 6900 ISA\n * http://developer.amd.com/wordpress/media/2012/10/AMD_HD_6900_Series_Instruction_Set_Architecture.pdf\n */\n\n#ifndef CF_INST\n#define CF_INST(name, value)\n#endif\n\n#ifndef EXP_INST\n#define EXP_INST(name, value)\n#endif\n\n#ifndef ALU_INST\n#define ALU_INST(name, value)\n#endif\n\n#ifndef ALU_OP2\n#define ALU_OP2(name, value, srcs, flags)\n#endif\n\n#ifndef ALU_OP3\n#define ALU_OP3(name, value, srcs, flags)\n#endif\n\n#ifndef TEX_INST\n#define TEX_INST(name, value)\n#endif\n\n#ifndef VTX_INST\n#define VTX_INST(name, value)\n#endif\n\n#ifndef MEM_INST\n#define MEM_INST(name, value)\n#endif\n\n#ifndef ALU_REDUC\n#define ALU_REDUC Opcode::Reduction\n#endif\n\n#ifndef ALU_VEC\n#define ALU_VEC Opcode::Vector\n#endif\n\n#ifndef ALU_TRANS\n#define ALU_TRANS Opcode::Transcendental\n#endif\n\n#ifndef ALU_PRED_SET\n#define ALU_PRED_SET Opcode::PredSet\n#endif\n\n#ifndef ALU_INT\n#define ALU_INT Opcode::IntIn | Opcode::IntOut\n#endif\n\n#ifndef ALU_UINT\n#define ALU_UINT Opcode::UintIn | Opcode::UintOut\n#endif\n\n#ifndef ALU_INT_IN\n#define ALU_INT_IN Opcode::IntIn\n#endif\n\n#ifndef ALU_UINT_IN\n#define ALU_UINT_IN Opcode::UintIn\n#endif\n\n#ifndef ALU_INT_OUT\n#define ALU_INT_OUT Opcode::IntOut\n#endif\n\n#ifndef ALU_UINT_OUT\n#define ALU_UINT_OUT Opcode::UintOut\n#endif\n\n\n// CF\nCF_INST(NOP,                     0x00000000)\nCF_INST(TEX,                     0x00000001)\nCF_INST(VTX,                     0x00000002)\nCF_INST(VTX_TC,                  0x00000003)\nCF_INST(LOOP_START,              0x00000004)\nCF_INST(LOOP_END,                0x00000005)\nCF_INST(LOOP_START_DX10,         0x00000006)\nCF_INST(LOOP_START_NO_AL,        0x00000007)\nCF_INST(LOOP_CONTINUE,           0x00000008)\nCF_INST(LOOP_BREAK,              0x00000009)\nCF_INST(JUMP,                    0x0000000A)\nCF_INST(PUSH,                    0x0000000B)\nCF_INST(PUSH_ELSE,               0x0000000C)\nCF_INST(ELSE,                    0x0000000D)\nCF_INST(POP,                     0x0000000E)\nCF_INST(POP_JUMP,                0x0000000F)\nCF_INST(POP_PUSH,                0x00000010)\nCF_INST(POP_PUSH_ELSE,           0x00000011)\nCF_INST(CALL,                    0x00000012)\nCF_INST(CALL_FS,                 0x00000013)\nCF_INST(RETURN,                  0x00000014)\nCF_INST(EMIT_VERTEX,             0x00000015)\nCF_INST(EMIT_CUT_VERTEX,         0x00000016)\nCF_INST(CUT_VERTEX,              0x00000017)\nCF_INST(KILL,                    0x00000018)\nCF_INST(END_PROGRAM,             0x00000019)\nCF_INST(WAIT_ACK,                0x0000001A)\nCF_INST(TEX_ACK,                 0x0000001B)\nCF_INST(VTX_ACK,                 0x0000001C)\nCF_INST(VTX_TC_ACK,              0x0000001D)\n/* I don't think Wii U has these.\nCF_INST(TC,                      0x0000001E)\nCF_INST(VC,                      0x0000001F)\nCF_INST(GDS,                     0x00000020)\nCF_INST(TC_ACK,                  0x00000021)\nCF_INST(VC_ACK,                  0x00000022)\nCF_INST(JUMPTABLE,               0x00000023)\nCF_INST(GLOBAL_WAVE_SYNC,        0x00000024)\nCF_INST(HALT,                    0x00000025)\nCF_INST(END,                     0x00000026)\nCF_INST(LDS_DEALLOC,             0x00000027)\nCF_INST(PUSH_WQM,                0x00000028)\nCF_INST(POP_WQM,                 0x00000029)\nCF_INST(ELSE_WQM,                0x0000002A)\nCF_INST(JUMP_ANY,                0x0000002B)\nCF_INST(REACTIVATE,              0x0000002C)\nCF_INST(REACTIVATE_WQM,          0x0000002D)\nCF_INST(INTERRUPT,               0x0000002E)\nCF_INST(INTERRUPT_AND_SLEEP,     0x0000002F)\nCF_INST(SET_PRIORITY,            0x00000030)\n*/\n\n// EXP\nEXP_INST(MEM_STREAM0,                  0x00000020)\nEXP_INST(MEM_STREAM1,                  0x00000021)\nEXP_INST(MEM_STREAM2,                  0x00000022)\nEXP_INST(MEM_STREAM3,                  0x00000023)\nEXP_INST(MEM_SCRATCH,                  0x00000024)\nEXP_INST(MEM_REDUCTION,                0x00000025)\nEXP_INST(MEM_RING,                     0x00000026)\nEXP_INST(EXP,                          0x00000027)\nEXP_INST(EXP_DONE,                     0x00000028)\nEXP_INST(MEM_EXPORT,                   0x0000003A)\n/* I don't think Wii U has these\nEXP_INST(MEM_STREAM0_BUF0,             0x00000040)\nEXP_INST(MEM_STREAM0_BUF1,             0x00000041)\nEXP_INST(MEM_STREAM0_BUF2,             0x00000042)\nEXP_INST(MEM_STREAM0_BUF3,             0x00000043)\nEXP_INST(MEM_STREAM1_BUF0,             0x00000044)\nEXP_INST(MEM_STREAM1_BUF1,             0x00000045)\nEXP_INST(MEM_STREAM1_BUF2,             0x00000046)\nEXP_INST(MEM_STREAM1_BUF3,             0x00000047)\nEXP_INST(MEM_STREAM2_BUF0,             0x00000048)\nEXP_INST(MEM_STREAM2_BUF1,             0x00000049)\nEXP_INST(MEM_STREAM2_BUF2,             0x0000004A)\nEXP_INST(MEM_STREAM2_BUF3,             0x0000004B)\nEXP_INST(MEM_STREAM3_BUF0,             0x0000004C)\nEXP_INST(MEM_STREAM3_BUF1,             0x0000004D)\nEXP_INST(MEM_STREAM3_BUF2,             0x0000004E)\nEXP_INST(MEM_STREAM3_BUF3,             0x0000004F)\nEXP_INST(MEM_RAT,                      0x00000056)\nEXP_INST(MEM_RAT_CACHELESS,            0x00000057)\nEXP_INST(MEM_RING1,                    0x00000058)\nEXP_INST(MEM_RING2,                    0x00000059)\nEXP_INST(MEM_RING3,                    0x0000005A)\nEXP_INST(MEM_EXPORT_COMBINED,          0x0000005B)\nEXP_INST(MEM_RAT_COMBINED_CACHELESS,   0x0000005C)\nEXP_INST(MEM_RAT_COMBINED,             0x0000005D)\nEXP_INST(EXP_DONE_END_IS_NEXT,         0x0000005E)\n*/\n\n// ALU\nALU_INST(ALU,                    0x00000008)\nALU_INST(ALU_PUSH_BEFORE,        0x00000009)\nALU_INST(ALU_POP_AFTER,          0x0000000A)\nALU_INST(ALU_POP2_AFTER,         0x0000000B)\nALU_INST(ALU_EXT,                0x0000000C)\nALU_INST(ALU_CONTINUE,           0x0000000D)\nALU_INST(ALU_BREAK,              0x0000000E)\nALU_INST(ALU_ELSE_AFTER,         0x0000000F)\n\n// ALU OP2\nALU_OP2(ADD,                     0x00000000, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(MUL,                     0x00000001, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(MUL_IEEE,                0x00000002, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(MAX,                     0x00000003, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(MIN,                     0x00000004, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(MAX_DX10,                0x00000005, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(MIN_DX10,                0x00000006, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(FREXP_64,                0x00000007, 1, ALU_VEC | ALU_REDUC)\nALU_OP2(SETE,                    0x00000008, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(SETGT,                   0x00000009, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(SETGE,                   0x0000000A, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(SETNE,                   0x0000000B, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(SETE_DX10,               0x0000000C, 2, ALU_VEC | ALU_TRANS | ALU_INT_OUT)\nALU_OP2(SETGT_DX10,              0x0000000D, 2, ALU_VEC | ALU_TRANS | ALU_INT_OUT)\nALU_OP2(SETGE_DX10,              0x0000000E, 2, ALU_VEC | ALU_TRANS | ALU_INT_OUT)\nALU_OP2(SETNE_DX10,              0x0000000F, 2, ALU_VEC | ALU_TRANS | ALU_INT_OUT)\nALU_OP2(FRACT,                   0x00000010, 1, ALU_VEC | ALU_TRANS)\nALU_OP2(TRUNC,                   0x00000011, 1, ALU_VEC | ALU_TRANS)\nALU_OP2(CEIL,                    0x00000012, 1, ALU_VEC | ALU_TRANS)\nALU_OP2(RNDNE,                   0x00000013, 1, ALU_VEC | ALU_TRANS)\nALU_OP2(FLOOR,                   0x00000014, 1, ALU_VEC | ALU_TRANS)\nALU_OP2(MOVA,                    0x00000015, 1, ALU_VEC | ALU_INT_OUT)\nALU_OP2(MOVA_FLOOR,              0x00000016, 1, ALU_VEC | ALU_INT_OUT)\nALU_OP2(ADD_64,                  0x00000017, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(MOVA_INT,                0x00000018, 1, ALU_VEC | ALU_INT)\nALU_OP2(MOV,                     0x00000019, 1, ALU_VEC | ALU_TRANS)\nALU_OP2(NOP,                     0x0000001A, 0, ALU_VEC | ALU_TRANS)\nALU_OP2(MUL_64,                  0x0000001B, 2, ALU_VEC | ALU_REDUC)\nALU_OP2(FLT64_TO_FLT32,          0x0000001C, 1, ALU_VEC | ALU_REDUC)\nALU_OP2(FLT32_TO_FLT64,          0x0000001D, 1, ALU_VEC | ALU_REDUC)\nALU_OP2(PRED_SETGT_UINT,         0x0000001E, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_UINT)\nALU_OP2(PRED_SETGE_UINT,         0x0000001F, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_UINT)\nALU_OP2(PRED_SETE,               0x00000020, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SETGT,              0x00000021, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SETGE,              0x00000022, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SETNE,              0x00000023, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SET_INV,            0x00000024, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SET_POP,            0x00000025, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SET_CLR,            0x00000026, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SET_RESTORE,        0x00000027, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SETE_PUSH,          0x00000028, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SETGT_PUSH,         0x00000029, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SETGE_PUSH,         0x0000002A, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(PRED_SETNE_PUSH,         0x0000002B, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET)\nALU_OP2(KILLE,                   0x0000002C, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(KILLGT,                  0x0000002D, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(KILLGE,                  0x0000002E, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(KILLNE,                  0x0000002F, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(AND_INT,                 0x00000030, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(OR_INT,                  0x00000031, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(XOR_INT,                 0x00000032, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(NOT_INT,                 0x00000033, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(ADD_INT,                 0x00000034, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(SUB_INT,                 0x00000035, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(MAX_INT,                 0x00000036, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(MIN_INT,                 0x00000037, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(MAX_UINT,                0x00000038, 2, ALU_VEC | ALU_TRANS | ALU_UINT)\nALU_OP2(MIN_UINT,                0x00000039, 2, ALU_VEC | ALU_TRANS | ALU_UINT)\nALU_OP2(SETE_INT,                0x0000003A, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(SETGT_INT,               0x0000003B, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(SETGE_INT,               0x0000003C, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(SETNE_INT,               0x0000003D, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(SETGT_UINT,              0x0000003E, 2, ALU_VEC | ALU_TRANS | ALU_UINT)\nALU_OP2(SETGE_UINT,              0x0000003F, 2, ALU_VEC | ALU_TRANS | ALU_UINT)\nALU_OP2(KILLGT_UINT,             0x00000040, 2, ALU_VEC | ALU_TRANS | ALU_UINT)\nALU_OP2(KILLGE_UINT,             0x00000041, 2, ALU_VEC | ALU_TRANS | ALU_UINT)\nALU_OP2(PRED_SETE_INT,           0x00000042, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT)\nALU_OP2(PRED_SETGT_INT,          0x00000043, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT)\nALU_OP2(PRED_SETGE_INT,          0x00000044, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT)\nALU_OP2(PRED_SETNE_INT,          0x00000045, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT)\nALU_OP2(KILLE_INT,               0x00000046, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(KILLGT_INT,              0x00000047, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(KILLGE_INT,              0x00000048, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(KILLNE_INT,              0x00000049, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(PRED_SETE_PUSH_INT,      0x0000004A, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT)\nALU_OP2(PRED_SETGT_PUSH_INT,     0x0000004B, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT)\nALU_OP2(PRED_SETGE_PUSH_INT,     0x0000004C, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT)\nALU_OP2(PRED_SETNE_PUSH_INT,     0x0000004D, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT)\nALU_OP2(PRED_SETLT_PUSH_INT,     0x0000004E, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT)\nALU_OP2(PRED_SETLE_PUSH_INT,     0x0000004F, 2, ALU_VEC | ALU_TRANS | ALU_PRED_SET | ALU_INT)\nALU_OP2(DOT4,                    0x00000050, 2, ALU_VEC | ALU_REDUC)\nALU_OP2(DOT4_IEEE,               0x00000051, 2, ALU_VEC | ALU_REDUC)\nALU_OP2(CUBE,                    0x00000052, 2, ALU_VEC | ALU_REDUC)\nALU_OP2(MAX4,                    0x00000053, 2, ALU_VEC | ALU_REDUC)\nALU_OP2(GROUP_BARRIER,           0x00000054, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(GROUP_SEQ_BEGIN,         0x00000055, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(GROUP_SEQ_END,           0x00000056, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(SET_MODE,                0x00000057, 0, ALU_VEC | ALU_TRANS)\nALU_OP2(SET_CF_IDX0,             0x00000058, 0, ALU_VEC | ALU_TRANS)\nALU_OP2(SET_CF_IDX1,             0x00000059, 0, ALU_VEC | ALU_TRANS)\nALU_OP2(SET_LDS_SIZE,            0x0000005A, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(MUL_INT24,               0x0000005B, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(MULHI_INT24,             0x0000005C, 2, ALU_VEC | ALU_TRANS)\nALU_OP2(MOVA_GPR_INT,            0x00000060, 2, ALU_VEC | ALU_INT)\nALU_OP2(EXP_IEEE,                0x00000061, 1, ALU_TRANS)\nALU_OP2(LOG_CLAMPED,             0x00000062, 1, ALU_TRANS)\nALU_OP2(LOG_IEEE,                0x00000063, 1, ALU_TRANS)\nALU_OP2(RECIP_CLAMPED,           0x00000064, 1, ALU_TRANS)\nALU_OP2(RECIP_FF,                0x00000065, 1, ALU_TRANS)\nALU_OP2(RECIP_IEEE,              0x00000066, 1, ALU_TRANS)\nALU_OP2(RECIPSQRT_CLAMPED,       0x00000067, 1, ALU_TRANS)\nALU_OP2(RECIPSQRT_FF,            0x00000068, 1, ALU_TRANS)\nALU_OP2(RECIPSQRT_IEEE,          0x00000069, 1, ALU_TRANS)\nALU_OP2(SQRT_IEEE,               0x0000006A, 1, ALU_TRANS)\nALU_OP2(FLT_TO_INT,              0x0000006B, 1, ALU_TRANS | ALU_INT_OUT)\nALU_OP2(INT_TO_FLT,              0x0000006C, 1, ALU_TRANS | ALU_INT_IN)\nALU_OP2(UINT_TO_FLT,             0x0000006D, 1, ALU_TRANS | ALU_UINT_IN)\nALU_OP2(SIN,                     0x0000006E, 1, ALU_TRANS)\nALU_OP2(COS,                     0x0000006F, 1, ALU_TRANS)\nALU_OP2(ASHR_INT,                0x00000070, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(LSHR_INT,                0x00000071, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(LSHL_INT,                0x00000072, 2, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP2(MULLO_INT,               0x00000073, 2, ALU_TRANS | ALU_INT)\nALU_OP2(MULHI_INT,               0x00000074, 2, ALU_TRANS | ALU_INT)\nALU_OP2(MULLO_UINT,              0x00000075, 2, ALU_TRANS | ALU_UINT)\nALU_OP2(MULHI_UINT,              0x00000076, 2, ALU_TRANS | ALU_UINT)\nALU_OP2(RECIP_INT,               0x00000077, 1, ALU_TRANS | ALU_INT)\nALU_OP2(RECIP_UINT,              0x00000078, 1, ALU_TRANS | ALU_UINT)\nALU_OP2(FLT_TO_UINT,             0x00000079, 1, ALU_TRANS | ALU_UINT_OUT)\nALU_OP2(LDEXP_64,                0x0000007A, 2, ALU_VEC | ALU_REDUC)\nALU_OP2(FRACT_64,                0x0000007B, 1, ALU_VEC | ALU_REDUC)\nALU_OP2(PRED_SETGT_64,           0x0000007C, 2, ALU_VEC | ALU_REDUC | ALU_PRED_SET)\nALU_OP2(PRED_SETE_64,            0x0000007D, 2, ALU_VEC | ALU_REDUC | ALU_PRED_SET)\nALU_OP2(PRED_SETGE_64,           0x0000007E, 2, ALU_VEC | ALU_REDUC | ALU_PRED_SET)\n\n// ALU OP3\nALU_OP3(BFE_UINT,                0x00000004, 3, ALU_VEC | ALU_TRANS | ALU_UINT)\nALU_OP3(BFE_INT,                 0x00000005, 3, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP3(BFI_INT,                 0x00000006, 3, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP3(FMA,                     0x00000007, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(MULADD_64,               0x00000008, 3, ALU_VEC | ALU_REDUC)\nALU_OP3(MULADD_64_M2,            0x00000009, 3, ALU_VEC | ALU_REDUC)\nALU_OP3(MULADD_64_M4,            0x0000000A, 3, ALU_VEC | ALU_REDUC)\nALU_OP3(MULADD_64_D2,            0x0000000B, 3, ALU_VEC | ALU_REDUC)\nALU_OP3(MUL_LIT,                 0x0000000C, 3, ALU_TRANS)\nALU_OP3(MUL_LIT_M2,              0x0000000D, 3, ALU_TRANS)\nALU_OP3(MUL_LIT_M4,              0x0000000E, 3, ALU_TRANS)\nALU_OP3(MUL_LIT_D2,              0x0000000F, 3, ALU_TRANS)\nALU_OP3(MULADD,                  0x00000010, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(MULADD_M2,               0x00000011, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(MULADD_M4,               0x00000012, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(MULADD_D2,               0x00000013, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(MULADD_IEEE,             0x00000014, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(MULADD_IEEE_M2,          0x00000015, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(MULADD_IEEE_M4,          0x00000016, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(MULADD_IEEE_D2,          0x00000017, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(CNDE,                    0x00000018, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(CNDGT,                   0x00000019, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(CNDGE,                   0x0000001A, 3, ALU_VEC | ALU_TRANS)\nALU_OP3(CNDE_INT,                0x0000001C, 3, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP3(CNDGT_INT,               0x0000001D, 3, ALU_VEC | ALU_TRANS | ALU_INT)\nALU_OP3(CNDGE_INT,               0x0000001E, 3, ALU_VEC | ALU_TRANS | ALU_INT)\n\n// TEX\nTEX_INST(VTX_FETCH,              0x00000000)\nTEX_INST(VTX_SEMANTIC,           0x00000001)\nTEX_INST(MEM,                    0x00000002)\nTEX_INST(LD,                     0x00000003)\nTEX_INST(GET_TEXTURE_INFO,       0x00000004)\nTEX_INST(GET_SAMPLE_INFO,        0x00000005)\nTEX_INST(GET_COMP_TEX_LOD,       0x00000006)\nTEX_INST(GET_GRADIENTS_H,        0x00000007)\nTEX_INST(GET_GRADIENTS_V,        0x00000008)\nTEX_INST(GET_LERP,               0x00000009)\nTEX_INST(KEEP_GRADIENTS,         0x0000000A)\nTEX_INST(SET_GRADIENTS_H,        0x0000000B)\nTEX_INST(SET_GRADIENTS_V,        0x0000000C)\nTEX_INST(PASS,                   0x0000000D)\nTEX_INST(SET_CUBEMAP_INDEX,      0x0000000E)\nTEX_INST(FETCH4,                 0x0000000F)\nTEX_INST(SAMPLE,                 0x00000010)\nTEX_INST(SAMPLE_L,               0x00000011)\nTEX_INST(SAMPLE_LB,              0x00000012)\nTEX_INST(SAMPLE_LZ,              0x00000013)\nTEX_INST(SAMPLE_G,               0x00000014)\nTEX_INST(SAMPLE_G_L,             0x00000015)\nTEX_INST(SAMPLE_G_LB,            0x00000016)\nTEX_INST(SAMPLE_G_LZ,            0x00000017)\nTEX_INST(SAMPLE_C,               0x00000018)\nTEX_INST(SAMPLE_C_L,             0x00000019)\nTEX_INST(SAMPLE_C_LB,            0x0000001A)\nTEX_INST(SAMPLE_C_LZ,            0x0000001B)\nTEX_INST(SAMPLE_C_G,             0x0000001C)\nTEX_INST(SAMPLE_C_G_L,           0x0000001D)\nTEX_INST(SAMPLE_C_G_LB,          0x0000001E)\nTEX_INST(SAMPLE_C_G_LZ,          0x0000001F)\nTEX_INST(SET_TEXTURE_OFFSETS,    0x00000020)\nTEX_INST(GATHER4,                0x00000021)\nTEX_INST(GATHER4_O,              0x00000022)\nTEX_INST(GATHER4_C,              0x00000023)\nTEX_INST(GATHER4_C_O,            0x00000024)\nTEX_INST(GET_BUFFER_RESINFO,     0x00000025)\n\n// VTX\nVTX_INST(FETCH,                  0x00000000)\nVTX_INST(SEMANTIC,               0x00000001)\nVTX_INST(BUFINFO,                0x0000000E)\n\n// MEM\nMEM_INST(RD_SCRATCH,             0x00000000)\nMEM_INST(RD_REDUC,               0x00000001)\nMEM_INST(RD_SCATTER,             0x00000002)\nMEM_INST(LOCAL_DS_WRITE,         0x00000004)\nMEM_INST(LOCAL_DS_READ,          0x00000005)\nMEM_INST(DS_GLOBAL_WRITE,        0x00000006)\nMEM_INST(DS_GLOBAL_READ,         0x00000007)\nMEM_INST(MEM_GDS,                0x00000008)\nMEM_INST(TF_WRITE,               0x00000009)\n\n#undef CF_INST\n#undef EXP_INST\n#undef ALU_INST\n#undef ALU_OP2\n#undef ALU_OP3\n#undef TEX_INST\n#undef VTX_INST\n#undef MEM_INST\n\n/*\n#undef ALU_REDUC\n#undef ALU_VEC\n#undef ALU_TRANS\n#undef ALU_PRED_SET\n#undef ALU_INT\n#undef ALU_UINT\n#undef ALU_INT_IN\n#undef ALU_UINT_IN\n#undef ALU_INT_OUT\n#undef ALU_UINT_OUT\n*/\n"
  },
  {
    "path": "src/libgpu/latte/latte_pm4.h",
    "content": "#pragma once\n#include \"latte_enum_pm4.h\"\n\n#include <common/bitfield.h>\n#include <cstdint>\n\nnamespace latte\n{\n\nnamespace pm4\n{\n\n#pragma pack(push, 1)\n\nBITFIELD_BEG(Header, uint32_t)\n   BITFIELD_ENTRY(30, 2, PacketType, type);\nBITFIELD_END\n\nBITFIELD_BEG(HeaderType0, uint32_t)\n   BITFIELD_ENTRY(0, 16, uint32_t, baseIndex);\n   BITFIELD_ENTRY(16, 14, uint32_t, count);\n   BITFIELD_ENTRY(30, 2, PacketType, type);\nBITFIELD_END\n\nBITFIELD_BEG(HeaderType2, uint32_t)\n   BITFIELD_ENTRY(30, 2, PacketType, type);\nBITFIELD_END\n\nBITFIELD_BEG(HeaderType3, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, predicate);\n   BITFIELD_ENTRY(8, 8, IT_OPCODE, opcode);\n   BITFIELD_ENTRY(16, 14, uint32_t, size);\n   BITFIELD_ENTRY(30, 2, PacketType, type);\nBITFIELD_END\n\n#pragma pack(pop)\n\n} // namespace pm4\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_pm4_commands.h",
    "content": "#pragma once\n#include \"latte_enum_pm4.h\"\n#include \"latte_pm4.h\"\n#include \"latte_registers.h\"\n\n#include <common/bitfield.h>\n#include <libcpu/be2_struct.h>\n#include <cstdint>\n#include <gsl/gsl-lite.hpp>\n\n#pragma pack(push, 1)\n\nnamespace latte\n{\n\nnamespace pm4\n{\n\nenum ScanTarget : uint32_t\n{\n   TV = 1,\n   DRC = 4\n};\n\nstruct DecafSwapBuffers\n{\n   static const auto Opcode = IT_OPCODE::DECAF_SWAP_BUFFERS;\n\n   uint32_t dummy;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(dummy);\n   }\n};\n\nstruct DecafCopyColorToScan\n{\n   static const auto Opcode = IT_OPCODE::DECAF_COPY_COLOR_TO_SCAN;\n\n   ScanTarget scanTarget;\n   latte::CB_COLORN_BASE cb_color_base;\n   latte::CB_COLORN_FRAG cb_color_frag;\n   uint32_t width;\n   uint32_t height;\n   latte::CB_COLORN_SIZE cb_color_size;\n   latte::CB_COLORN_INFO cb_color_info;\n   latte::CB_COLORN_VIEW cb_color_view;\n   latte::CB_COLORN_MASK cb_color_mask;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(scanTarget);\n      se(cb_color_base.value);\n      se(cb_color_frag.value);\n      se(width);\n      se(height);\n      se(cb_color_size.value);\n      se(cb_color_info.value);\n      se(cb_color_view.value);\n      se(cb_color_mask.value);\n   }\n};\n\nstruct DecafClearColor\n{\n   static const auto Opcode = IT_OPCODE::DECAF_CLEAR_COLOR;\n\n   float red;\n   float green;\n   float blue;\n   float alpha;\n   latte::CB_COLORN_BASE cb_color_base;\n   latte::CB_COLORN_FRAG cb_color_frag;\n   latte::CB_COLORN_SIZE cb_color_size;\n   latte::CB_COLORN_INFO cb_color_info;\n   latte::CB_COLORN_VIEW cb_color_view;\n   latte::CB_COLORN_MASK cb_color_mask;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(red);\n      se(green);\n      se(blue);\n      se(alpha);\n      se(cb_color_base.value);\n      se(cb_color_frag.value);\n      se(cb_color_size.value);\n      se(cb_color_info.value);\n      se(cb_color_view.value);\n      se(cb_color_mask.value);\n   }\n};\n\nstruct DecafClearDepthStencil\n{\n   static const auto Opcode = IT_OPCODE::DECAF_CLEAR_DEPTH_STENCIL;\n\n   uint32_t flags;\n   latte::DB_DEPTH_BASE db_depth_base;\n   latte::DB_DEPTH_HTILE_DATA_BASE db_depth_htile_data_base;\n   latte::DB_DEPTH_INFO db_depth_info;\n   latte::DB_DEPTH_SIZE db_depth_size;\n   latte::DB_DEPTH_VIEW db_depth_view;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(flags);\n      se(db_depth_base.value);\n      se(db_depth_htile_data_base.value);\n      se(db_depth_info.value);\n      se(db_depth_size.value);\n      se(db_depth_view.value);\n   }\n};\n\nstruct DecafSetBuffer\n{\n   static const auto Opcode = IT_OPCODE::DECAF_SET_BUFFER;\n\n   ScanTarget scanTarget;\n   phys_addr buffer;\n   uint32_t numBuffers;\n   uint32_t width;\n   uint32_t height;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(scanTarget);\n      se(buffer);\n      se(numBuffers);\n      se(width);\n      se(height);\n   }\n};\n\nstruct DecafOSScreenFlip\n{\n   static const auto Opcode = IT_OPCODE::DECAF_OSSCREEN_FLIP;\n\n   uint32_t screen;\n   phys_addr buffer;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(screen);\n      se(buffer);\n   }\n};\n\nstruct DecafCopySurface\n{\n   static const auto Opcode = IT_OPCODE::DECAF_COPY_SURFACE;\n\n   phys_addr dstImage;\n   phys_addr dstMipmaps;\n   uint32_t dstLevel;\n   uint32_t dstSlice;\n   uint32_t dstPitch;\n   uint32_t dstWidth;\n   uint32_t dstHeight;\n   uint32_t dstDepth;\n   uint32_t dstSamples;\n   latte::SQ_TEX_DIM dstDim;\n   latte::SQ_DATA_FORMAT dstFormat;\n   latte::SQ_NUM_FORMAT dstNumFormat;\n   latte::SQ_FORMAT_COMP dstFormatComp;\n   uint32_t dstForceDegamma;\n   latte::SQ_TILE_TYPE dstTileType;\n   latte::SQ_TILE_MODE dstTileMode;\n\n   phys_addr srcImage;\n   phys_addr srcMipmaps;\n   uint32_t srcLevel;\n   uint32_t srcSlice;\n   uint32_t srcPitch;\n   uint32_t srcWidth;\n   uint32_t srcHeight;\n   uint32_t srcDepth;\n   uint32_t srcSamples;\n   latte::SQ_TEX_DIM srcDim;\n   latte::SQ_DATA_FORMAT srcFormat;\n   latte::SQ_NUM_FORMAT srcNumFormat;\n   latte::SQ_FORMAT_COMP srcFormatComp;\n   uint32_t srcForceDegamma;\n   latte::SQ_TILE_TYPE srcTileType;\n   latte::SQ_TILE_MODE srcTileMode;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(dstImage);\n      se(dstMipmaps);\n      se(dstLevel);\n      se(dstSlice);\n      se(dstPitch);\n      se(dstWidth);\n      se(dstHeight);\n      se(dstDepth);\n      se(dstSamples);\n      se(dstDim);\n      se(dstFormat);\n      se(dstNumFormat);\n      se(dstFormatComp);\n      se(dstForceDegamma);\n      se(dstTileType);\n      se(dstTileMode);\n\n      se(srcImage);\n      se(srcMipmaps);\n      se(srcLevel);\n      se(srcSlice);\n      se(srcPitch);\n      se(srcWidth);\n      se(srcHeight);\n      se(srcDepth);\n      se(srcSamples);\n      se(srcDim);\n      se(srcFormat);\n      se(srcNumFormat);\n      se(srcFormatComp);\n      se(srcForceDegamma);\n      se(srcTileType);\n      se(srcTileMode);\n   }\n};\n\nstruct DecafExpandColorBuffer\n{\n   static const auto Opcode = IT_OPCODE::DECAF_EXPAND_COLORBUFFER;\n\n   phys_addr dstImage;\n   phys_addr dstMipmaps;\n   uint32_t dstLevel;\n   uint32_t dstSlice;\n   uint32_t dstPitch;\n   uint32_t dstWidth;\n   uint32_t dstHeight;\n   uint32_t dstDepth;\n   uint32_t dstSamples;\n   latte::SQ_TEX_DIM dstDim;\n   latte::SQ_DATA_FORMAT dstFormat;\n   latte::SQ_NUM_FORMAT dstNumFormat;\n   latte::SQ_FORMAT_COMP dstFormatComp;\n   uint32_t dstForceDegamma;\n   latte::SQ_TILE_TYPE dstTileType;\n   latte::SQ_TILE_MODE dstTileMode;\n\n   phys_addr srcImage;\n   phys_addr srcFmask;\n   phys_addr srcMipmaps;\n   uint32_t srcLevel;\n   uint32_t srcSlice;\n   uint32_t srcPitch;\n   uint32_t srcWidth;\n   uint32_t srcHeight;\n   uint32_t srcDepth;\n   uint32_t srcSamples;\n   latte::SQ_TEX_DIM srcDim;\n   latte::SQ_DATA_FORMAT srcFormat;\n   latte::SQ_NUM_FORMAT srcNumFormat;\n   latte::SQ_FORMAT_COMP srcFormatComp;\n   uint32_t srcForceDegamma;\n   latte::SQ_TILE_TYPE srcTileType;\n   latte::SQ_TILE_MODE srcTileMode;\n\n   uint32_t numSlices;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(dstImage);\n      se(dstMipmaps);\n      se(dstLevel);\n      se(dstSlice);\n      se(dstPitch);\n      se(dstWidth);\n      se(dstHeight);\n      se(dstDepth);\n      se(dstSamples);\n      se(dstDim);\n      se(dstFormat);\n      se(dstNumFormat);\n      se(dstFormatComp);\n      se(dstForceDegamma);\n      se(dstTileType);\n      se(dstTileMode);\n\n      se(srcImage);\n      se(srcFmask);\n      se(srcMipmaps);\n      se(srcLevel);\n      se(srcSlice);\n      se(srcPitch);\n      se(srcWidth);\n      se(srcHeight);\n      se(srcDepth);\n      se(srcSamples);\n      se(srcDim);\n      se(srcFormat);\n      se(srcNumFormat);\n      se(srcFormatComp);\n      se(srcForceDegamma);\n      se(srcTileType);\n      se(srcTileMode);\n\n      se(numSlices);\n   }\n};\n\nenum COPY_DW_SEL : uint32_t\n{\n   COPY_DW_SEL_REGISTER = 0,\n   COPY_DW_SEL_MEMORY = 1,\n};\n\nBITFIELD_BEG(COPY_DW_SELECT, uint32_t)\n   BITFIELD_ENTRY(0, 1, COPY_DW_SEL, SRC);\n   BITFIELD_ENTRY(1, 1, COPY_DW_SEL, DST);\nBITFIELD_END\n\nstruct CopyDw\n{\n   static const auto Opcode = IT_OPCODE::COPY_DW;\n\n   COPY_DW_SELECT select;\n\n   // Memory address or RegisterIndex/4 based  on select.src\n   phys_addr srcLo;\n   uint32_t srcHi;\n\n   // Memory address or RegisterIndex/4 based on select.dst\n   latte::Register dstLo;\n   uint32_t dstHi;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(select.value);\n      se(srcLo);\n      se(srcHi);\n      se.REG_OFFSET(dstLo, static_cast<latte::Register>(0));\n      se(dstHi);\n   }\n};\n\nstruct DrawIndexAuto\n{\n   static const auto Opcode = IT_OPCODE::DRAW_INDEX_AUTO;\n\n   uint32_t count;\n   latte::VGT_DRAW_INITIATOR drawInitiator;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(count);\n      se(drawInitiator.value);\n   }\n};\n\nstruct DrawIndex2\n{\n   static const auto Opcode = IT_OPCODE::DRAW_INDEX_2;\n\n   uint32_t maxIndices;                      // VGT_DMA_MAX_SIZE\n   phys_addr addr;                           // VGT_DMA_BASE\n   uint32_t count;                           // VGT_DMA_SIZE\n   latte::VGT_DRAW_INITIATOR drawInitiator;  // VGT_DRAW_INITIATOR\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(maxIndices);\n      se(addr);\n      se(unusedAddrHi);\n      se(count);\n      se(drawInitiator.value);\n   }\n};\n\nstruct DrawIndexImmd\n{\n   static const auto Opcode = IT_OPCODE::DRAW_INDEX_IMMD;\n\n   uint32_t count;                           // VGT_DMA_SIZE\n   latte::VGT_DRAW_INITIATOR drawInitiator;  // VGT_DRAW_INITIATOR\n   gsl::span<uint32_t> indices;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(count);\n      se(drawInitiator);\n      se(indices);\n   }\n};\n\n// This structure should only be used to WRITE\nstruct DrawIndexImmdBE\n{\n   static const auto Opcode = IT_OPCODE::DRAW_INDEX_IMMD;\n\n   uint32_t count;                           // VGT_DMA_SIZE\n   latte::VGT_DRAW_INITIATOR drawInitiator;  // VGT_DRAW_INITIATOR\n   gsl::span<be2_val<uint32_t>> indices;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(count);\n      se(drawInitiator);\n      se(indices);\n   }\n};\n\n// This structure should only be used to WRITE\nstruct DrawIndexImmdBE16\n{\n   static const auto Opcode = IT_OPCODE::DRAW_INDEX_IMMD;\n\n   uint32_t count;                           // VGT_DMA_SIZE\n   latte::VGT_DRAW_INITIATOR drawInitiator;  // VGT_DRAW_INITIATOR\n   gsl::span<be2_val<uint32_t>> indices;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(count);\n      se(drawInitiator);\n\n      // Swap around the two 16 bit indices\n      for (auto i = 0u; i < indices.size(); ++i) {\n         auto index = static_cast<uint32_t>(indices[i]);\n         index = static_cast<uint32_t>((index >> 16) | (index << 16));\n         se(index);\n      }\n   }\n};\n\nstruct IndexType\n{\n   static const auto Opcode = IT_OPCODE::INDEX_TYPE;\n\n   latte::VGT_DMA_INDEX_TYPE type; // VGT_DMA_INDEX_TYPE\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(type.value);\n   }\n};\n\nstruct NumInstances\n{\n   static const auto Opcode = IT_OPCODE::NUM_INSTANCES;\n\n   uint32_t count; // VGT_DMA_NUM_INSTANCES\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(count);\n   }\n};\n\nstruct SetAluConsts\n{\n   static const auto Opcode = IT_OPCODE::SET_ALU_CONST;\n\n   latte::Register id;\n   gsl::span<uint32_t> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::AluConstRegisterBase);\n      se(values);\n   }\n};\n\nstruct SetAluConstsBE\n{\n   static const auto Opcode = IT_OPCODE::SET_ALU_CONST;\n\n   latte::Register id;\n   gsl::span<be2_val<uint32_t>> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::AluConstRegisterBase);\n      se(values);\n   }\n};\n\nstruct SetConfigReg\n{\n   static const auto Opcode = IT_OPCODE::SET_CONFIG_REG;\n\n   latte::Register id;\n   uint32_t value;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::ConfigRegisterBase);\n      se(value);\n   }\n};\n\nstruct SetConfigRegs\n{\n   static const auto Opcode = IT_OPCODE::SET_CONFIG_REG;\n\n   latte::Register id;\n   gsl::span<uint32_t> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::ConfigRegisterBase);\n      se(values);\n   }\n};\n\nstruct SetContextReg\n{\n   static const auto Opcode = IT_OPCODE::SET_CONTEXT_REG;\n\n   latte::Register id;\n   uint32_t value;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::ContextRegisterBase);\n      se(value);\n   }\n};\n\nstruct SetContextRegs\n{\n   static const auto Opcode = IT_OPCODE::SET_CONTEXT_REG;\n\n   latte::Register id;\n   gsl::span<uint32_t> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::ContextRegisterBase);\n      se(values);\n   }\n};\n\nstruct SetAllContextsReg\n{\n   static const auto Opcode = IT_OPCODE::SET_ALL_CONTEXTS;\n\n   latte::Register id;\n   uint32_t value;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::ContextRegisterBase);\n      se(value);\n   }\n};\n\nstruct SetControlConstant\n{\n   static const auto Opcode = IT_OPCODE::SET_CTL_CONST;\n\n   latte::Register id;\n   uint32_t value;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::ControlRegisterBase);\n      se(value);\n   }\n};\n\nstruct SetControlConstants\n{\n   static const auto Opcode = IT_OPCODE::SET_CTL_CONST;\n\n   latte::Register id;\n   gsl::span<uint32_t> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::ControlRegisterBase);\n      se(values);\n   }\n};\n\nstruct SetLoopConst\n{\n   static const auto Opcode = IT_OPCODE::SET_LOOP_CONST;\n\n   latte::Register id;\n   uint32_t value;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::LoopConstRegisterBase);\n      se(value);\n   }\n};\n\nstruct SetLoopConsts\n{\n   static const auto Opcode = IT_OPCODE::SET_LOOP_CONST;\n\n   latte::Register id;\n   gsl::span<uint32_t> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::LoopConstRegisterBase);\n      se(values);\n   }\n};\n\nstruct SetSamplerAttrib\n{\n   static const auto Opcode = IT_OPCODE::SET_SAMPLER;\n\n   uint32_t id;\n   latte::SQ_TEX_SAMPLER_WORD0_N word0;\n   latte::SQ_TEX_SAMPLER_WORD1_N word1;\n   latte::SQ_TEX_SAMPLER_WORD2_N word2;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.CONST_OFFSET(id);\n      se(word0.value);\n      se(word1.value);\n      se(word2.value);\n   }\n};\n\nstruct SetSamplers\n{\n   static const auto Opcode = IT_OPCODE::SET_SAMPLER;\n\n   latte::Register id;\n   gsl::span<uint32_t> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.REG_OFFSET(id, latte::Register::SamplerRegisterBase);\n      se(values);\n   }\n};\n\nstruct SetVtxResource\n{\n   static const auto Opcode = IT_OPCODE::SET_RESOURCE;\n\n   uint32_t id;\n   phys_addr baseAddress;\n   latte::SQ_VTX_CONSTANT_WORD1_N word1;\n   latte::SQ_VTX_CONSTANT_WORD2_N word2;\n   latte::SQ_VTX_CONSTANT_WORD3_N word3;\n   latte::SQ_VTX_CONSTANT_WORD6_N word6;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedWord4 = 0xABCD1234;\n      uint32_t unusedWord5 = 0xABCD1234;\n\n      se.CONST_OFFSET(id);\n      se(baseAddress);\n      se(word1.value);\n      se(word2.value);\n      se(word3.value);\n      se(unusedWord4);\n      se(unusedWord5);\n      se(word6.value);\n   }\n};\n\nstruct SetTexResource\n{\n   static const auto Opcode = IT_OPCODE::SET_RESOURCE;\n\n   uint32_t id;\n   latte::SQ_TEX_RESOURCE_WORD0_N word0;\n   latte::SQ_TEX_RESOURCE_WORD1_N word1;\n   latte::SQ_TEX_RESOURCE_WORD2_N word2;\n   latte::SQ_TEX_RESOURCE_WORD3_N word3;\n   latte::SQ_TEX_RESOURCE_WORD4_N word4;\n   latte::SQ_TEX_RESOURCE_WORD5_N word5;\n   latte::SQ_TEX_RESOURCE_WORD6_N word6;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.CONST_OFFSET(id);\n      se(word0.value);\n      se(word1.value);\n      se(word2.value);\n      se(word3.value);\n      se(word4.value);\n      se(word5.value);\n      se(word6.value);\n   }\n};\n\nstruct SetResources\n{\n   static const auto Opcode = IT_OPCODE::SET_RESOURCE;\n\n   uint32_t id;\n   gsl::span<uint32_t> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se.CONST_OFFSET(id);\n      se(values);\n   }\n};\n\nstruct IndirectBufferCall\n{\n   static const auto Opcode = IT_OPCODE::INDIRECT_BUFFER;\n   phys_addr addr;\n   uint32_t size;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(addr);\n      se(unusedAddrHi);\n      se(size);\n   }\n};\n\nstruct IndirectBufferCallPriv\n{\n   static const auto Opcode = IT_OPCODE::INDIRECT_BUFFER_PRIV;\n   phys_addr addr;\n   uint32_t size;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(addr);\n      se(unusedAddrHi);\n      se(size);\n   }\n};\n\nstruct LoadConfigReg\n{\n   static const auto Opcode = IT_OPCODE::LOAD_CONFIG_REG;\n   phys_addr addr;\n   gsl::span<std::pair<uint32_t, uint32_t>> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(addr);\n      se(unusedAddrHi);\n      se(values);\n   }\n};\n\nstruct LoadContextReg\n{\n   static const auto Opcode = IT_OPCODE::LOAD_CONTEXT_REG;\n   phys_addr addr;\n   gsl::span<std::pair<uint32_t, uint32_t>> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(addr);\n      se(unusedAddrHi);\n      se(values);\n   }\n};\n\nstruct LoadAluConst\n{\n   static const auto Opcode = IT_OPCODE::LOAD_ALU_CONST;\n   phys_addr addr;\n   gsl::span<std::pair<uint32_t, uint32_t>> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(addr);\n      se(unusedAddrHi);\n      se(values);\n   }\n};\n\nstruct LoadBoolConst\n{\n   static const auto Opcode = IT_OPCODE::LOAD_BOOL_CONST;\n   phys_addr addr;\n   gsl::span<std::pair<uint32_t, uint32_t>> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(addr);\n      se(unusedAddrHi);\n      se(values);\n   }\n};\n\nstruct LoadLoopConst\n{\n   static const auto Opcode = IT_OPCODE::LOAD_LOOP_CONST;\n   phys_addr addr;\n   gsl::span<std::pair<uint32_t, uint32_t>> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(addr);\n      se(unusedAddrHi);\n      se(values);\n   }\n};\n\nstruct LoadResource\n{\n   static const auto Opcode = IT_OPCODE::LOAD_RESOURCE;\n   phys_addr addr;\n   gsl::span<std::pair<uint32_t, uint32_t>> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(addr);\n      se(unusedAddrHi);\n      se(values);\n   }\n};\n\nstruct LoadSampler\n{\n   static const auto Opcode = IT_OPCODE::LOAD_SAMPLER;\n   phys_addr addr;\n   gsl::span<std::pair<uint32_t, uint32_t>> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(addr);\n      se(unusedAddrHi);\n      se(values);\n   }\n};\n\nstruct LoadControlConst\n{\n   static const auto Opcode = IT_OPCODE::LOAD_CTL_CONST;\n   phys_addr addr;\n   gsl::span<std::pair<uint32_t, uint32_t>> values;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      uint32_t unusedAddrHi = 0;\n\n      se(addr);\n      se(unusedAddrHi);\n      se(values);\n   }\n};\n\nBITFIELD_BEG(MW_ADDR_LO, uint32_t)\n   BITFIELD_ENTRY(0, 2, latte::CB_ENDIAN, ENDIAN_SWAP);\n   BITFIELD_ENTRY(2, 30, uint32_t, ADDR_LO);\nBITFIELD_END\n\nenum MW_CNTR_SEL : uint32_t\n{\n   MW_WRITE_DATA = 0,\n   MW_WRITE_CLOCK = 1,\n};\n\nBITFIELD_BEG(MW_ADDR_HI, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, ADDR_HI);\n   BITFIELD_ENTRY(16, 1, MW_CNTR_SEL, CNTR_SEL);\n   BITFIELD_ENTRY(17, 1, bool, WR_CONFIRM);\n   BITFIELD_ENTRY(18, 1, bool, DATA32);\nBITFIELD_END\n\nstruct MemWrite\n{\n   static const auto Opcode = IT_OPCODE::MEM_WRITE;\n   MW_ADDR_LO addrLo;\n   MW_ADDR_HI addrHi;\n   uint32_t dataLo;\n   uint32_t dataHi;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(addrLo.value);\n      se(addrHi.value);\n      se(dataLo);\n      se(dataHi);\n   }\n};\n\nBITFIELD_BEG(EW_ADDR_LO, uint32_t)\n   BITFIELD_ENTRY(0, 2, latte::CB_ENDIAN, ENDIAN_SWAP);\n   BITFIELD_ENTRY(2, 30, uint32_t, ADDR_LO);\nBITFIELD_END\n\nBITFIELD_BEG(EW_ADDR_HI, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, ADDR_HI);\nBITFIELD_END\n\nstruct EventWrite\n{\n   static const auto Opcode = IT_OPCODE::EVENT_WRITE;\n   latte::VGT_EVENT_INITIATOR eventInitiator;\n   EW_ADDR_LO addrLo;\n   EW_ADDR_HI addrHi;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(eventInitiator.value);\n\n      if (eventInitiator.EVENT_INDEX() == latte::VGT_EVENT_INDEX::ZPASS_DONE ||\n          eventInitiator.EVENT_INDEX() == latte::VGT_EVENT_INDEX::SAMPLE_PIPELINESTAT ||\n          eventInitiator.EVENT_INDEX() == latte::VGT_EVENT_INDEX::SAMPLE_STREAMOUTSTAT) {\n         se(addrLo.value);\n         se(addrHi.value);\n      }\n   }\n};\n\nenum EWP_DATA_SEL : uint32_t\n{\n   EWP_DATA_DISCARD = 0,\n   EWP_DATA_32 = 1,\n   EWP_DATA_64 = 2,\n   EWP_DATA_CLOCK = 3,\n};\n\nenum EWP_INT_SEL : uint32_t\n{\n   EWP_INT_NONE = 0,\n   EWP_INT_ONLY = 1,\n   EWP_INT_WRITE_CONFIRM = 2,\n};\n\nBITFIELD_BEG(EWP_ADDR_HI, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, ADDR_HI);\n   BITFIELD_ENTRY(24, 2, EWP_INT_SEL, INT_SEL);\n   BITFIELD_ENTRY(29, 3, EWP_DATA_SEL, DATA_SEL);\nBITFIELD_END\n\nstruct EventWriteEOP\n{\n   static const auto Opcode = IT_OPCODE::EVENT_WRITE_EOP;\n   latte::VGT_EVENT_INITIATOR eventInitiator;\n   EW_ADDR_LO addrLo;\n   EWP_ADDR_HI addrHi;\n   uint32_t dataLo;\n   uint32_t dataHi;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(eventInitiator.value);\n      se(addrLo.value);\n      se(addrHi.value);\n      se(dataLo);\n      se(dataHi);\n   }\n};\n\nstruct PfpSyncMe\n{\n   static const auto Opcode = IT_OPCODE::PFP_SYNC_ME;\n   uint32_t dummy;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(dummy);\n   }\n};\n\nstruct StreamOutBaseUpdate\n{\n   static const auto Opcode = IT_OPCODE::STRMOUT_BASE_UPDATE;\n   uint32_t index;\n   uint32_t addr;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(index);\n      se(addr);\n   }\n};\n\nenum STRMOUT_OFFSET_SOURCE : uint32_t\n{\n   STRMOUT_OFFSET_FROM_PACKET = 0,\n   STRMOUT_OFFSET_FROM_VGT_FILLED_SIZE = 1,\n   STRMOUT_OFFSET_FROM_MEM = 2,\n   STRMOUT_OFFSET_NONE = 3,\n};\n\nBITFIELD_BEG(SBU_CONTROL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, STORE_BUFFER_FILLED_SIZE);\n   BITFIELD_ENTRY(1, 2, STRMOUT_OFFSET_SOURCE, OFFSET_SOURCE);\n   BITFIELD_ENTRY(8, 2, uint8_t, SELECT_BUFFER);\nBITFIELD_END\n\nstruct StreamOutBufferUpdate\n{\n   static const auto Opcode = IT_OPCODE::STRMOUT_BUFFER_UPDATE;\n   SBU_CONTROL control;\n   phys_addr dstLo;  // Store target for STORE_BUFFER_FILLED_SIZE\n   uint32_t dstHi;\n   phys_addr srcLo;  // Offset for STRMOUT_OFFSET_FROM_PACKET;\n   uint32_t srcHi;  //   address of offset for STRMOUT_OFFSET_FROM_MEM\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(control.value);\n      se(dstLo);\n      se(dstHi);\n      se(srcLo);\n      se(srcHi);\n   }\n};\n\nstruct Nop\n{\n   static const auto Opcode = IT_OPCODE::NOP;\n\n   uint32_t unk;\n   gsl::span<uint32_t> strWords;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(unk);\n      se(strWords);\n   }\n};\n\nstruct NopBE\n{\n   static const auto Opcode = IT_OPCODE::NOP;\n\n   uint32_t unk;\n   gsl::span<be2_val<uint32_t>> strWords;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(unk);\n      se(strWords);\n   }\n};\n\nstruct SurfaceSync\n{\n   static const auto Opcode = IT_OPCODE::SURFACE_SYNC;\n\n   latte::CP_COHER_CNTL cp_coher_cntl;\n   uint32_t size;\n   uint32_t addr;\n   uint32_t pollInterval;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(cp_coher_cntl.value);\n      se(size);\n      se(addr);\n      se(pollInterval);\n   }\n};\n\nenum SP_PRED_OP\n{\n   SP_PRED_OP_CLEAR = 0,\n   SP_PRED_OP_ZPASS = 1,\n   SP_PRED_OP_PRIMCOUNT = 2,\n};\n\nBITFIELD_BEG(SET_PRED, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, ADDR_HI);\n   BITFIELD_ENTRY(8, 1, bool, PREDICATE);\n   BITFIELD_ENTRY(12, 1, bool, HINT);\n   BITFIELD_ENTRY(16, 3, SP_PRED_OP, PRED_OP);\n   BITFIELD_ENTRY(31, 1, bool, CONTINUE);\nBITFIELD_END\n\nstruct SetPredication\n{\n   static const auto Opcode = IT_OPCODE::SET_PREDICATION;\n\n   uint32_t addrLo;\n   SET_PRED set_pred;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(addrLo);\n      se(set_pred.value);\n   }\n};\n\nstruct ContextControl\n{\n   static const auto Opcode = IT_OPCODE::CONTEXT_CTL;\n\n   latte::CONTEXT_CONTROL_ENABLE LOAD_CONTROL;\n   latte::CONTEXT_CONTROL_ENABLE SHADOW_ENABLE;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(LOAD_CONTROL.value);\n      se(SHADOW_ENABLE.value);\n   }\n};\n\nenum WRM_ENGINE : uint32_t\n{\n   ENGINE_ME = 0,\n   ENGINE_PFP = 1,\n};\n\nenum WRM_FUNCTION : uint32_t\n{\n   FUNCTION_ALWAYS = 0,\n   FUNCTION_LESS_THAN = 1,\n   FUNCTION_LESS_THAN_EQUAL = 2,\n   FUNCTION_EQUAL = 3,\n   FUNCTION_NOT_EQUAL = 4,\n   FUNCTION_GREATER_THAN_EQUAL = 5,\n   FUNCTION_GREATER_THAN = 6,\n};\n\nenum WRM_MEM_SPACE : uint32_t\n{\n   MEM_SPACE_REGISTER = 0,\n   MEM_SPACE_MEMORY = 1,\n};\n\nBITFIELD_BEG(MEM_SPACE_FUNCTION, uint32_t)\n   BITFIELD_ENTRY(0, 3, WRM_FUNCTION, FUNCTION);\n   BITFIELD_ENTRY(4, 1, WRM_MEM_SPACE, MEM_SPACE);\n   BITFIELD_ENTRY(8, 1, WRM_ENGINE, ENGINE);\nBITFIELD_END\n\nBITFIELD_BEG(WRM_ADDR_LO, uint32_t)\n   BITFIELD_ENTRY(0, 2, latte::CB_ENDIAN, ENDIAN_SWAP);\n   BITFIELD_ENTRY(2, 30, uint32_t, ADDR_LO);\nBITFIELD_END\n\nBITFIELD_BEG(WRM_ADDR_HI, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, ADDR_HI);\nBITFIELD_END\n\nstruct WaitReg\n{\n   static constexpr auto Opcode = IT_OPCODE::WAIT_REG_MEM;\n\n   MEM_SPACE_FUNCTION memSpaceFunction;\n   latte::Register addrLo;\n   WRM_ADDR_HI addrHi;\n   uint32_t reference;\n   uint32_t mask;\n   uint32_t pollInterval;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(memSpaceFunction.value);\n      se.REG_OFFSET(addrLo, static_cast<latte::Register>(0));\n      se(addrHi.value);\n      se(reference);\n      se(mask);\n      se(pollInterval);\n   }\n};\n\nstruct WaitMem\n{\n   static constexpr auto Opcode = IT_OPCODE::WAIT_REG_MEM;\n\n   MEM_SPACE_FUNCTION memSpaceFunction;\n   WRM_ADDR_LO addrLo;\n   WRM_ADDR_HI addrHi;\n   uint32_t reference;\n   uint32_t mask;\n   uint32_t pollInterval;\n\n   template<typename Serialiser>\n   void serialise(Serialiser &se)\n   {\n      se(memSpaceFunction.value);\n      se(addrLo);\n      se(addrHi.value);\n      se(reference);\n      se(mask);\n      se(pollInterval);\n   }\n};\n\n} // namespace pm4\n\n} // namespace latte\n\n#pragma pack(pop)\n"
  },
  {
    "path": "src/libgpu/latte/latte_pm4_reader.h",
    "content": "#pragma once\n#include \"latte_pm4.h\"\n#include \"latte_registers.h\"\n\n#include <common/decaf_assert.h>\n#include <libcpu/mem.h>\n#include <gsl/gsl-lite.hpp>\n\nnamespace latte\n{\n\nnamespace pm4\n{\n\n/**\n * Note: packet reader assumes the buffer has already been byte swapped to\n * little endian before reading. This is because it returns pointers to the\n * buffer, and we have to be able to return swapped memory. We cannot do an\n * in place swap because that could modify the game's memory.\n */\nclass PacketReader\n{\npublic:\n   PacketReader(gsl::span<uint32_t> data) :\n      mBuffer(data)\n   {\n   }\n\n   // Read one word\n   PacketReader &operator()(uint32_t &value)\n   {\n      checkSize(1);\n      value = mBuffer[mPosition++];\n      return *this;\n   }\n\n   // Read one float\n   PacketReader &operator()(float &value)\n   {\n      checkSize(1);\n      value = bit_cast<float>(mBuffer[mPosition++]);\n      return *this;\n   }\n\n   // Read one uint32_t sized datatype\n   template <typename Type>\n   PacketReader &operator()(Type &value)\n   {\n      static_assert(sizeof(Type) == sizeof(uint32_t), \"Invalid type size\");\n      checkSize(1);\n      value = bit_cast<Type>(mBuffer[mPosition++]);\n      return *this;\n   }\n\n   // Read the rest of the entire packet\n   template<typename Type>\n   PacketReader &operator()(gsl::span<Type> &values)\n   {\n      if (mBuffer.size() - mPosition == 0) {\n         values = {};\n      } else {\n         values = gsl::make_span(reinterpret_cast<Type*>(&mBuffer[mPosition]),\n                                 ((mBuffer.size() - mPosition) * sizeof(uint32_t)) / sizeof(Type));\n      }\n\n      mPosition = mBuffer.size();\n      return *this;\n   }\n\n   // Read one word as a REG_OFFSET\n   PacketReader &REG_OFFSET(latte::Register &value, latte::Register base)\n   {\n      checkSize(1);\n      value = static_cast<latte::Register>(((mBuffer[mPosition++] & 0xFFFF) * 4) + (uint32_t)base);\n      return *this;\n   }\n\n   // Read one word as a CONST_OFFSET\n   PacketReader &CONST_OFFSET(uint32_t &value)\n   {\n      checkSize(1);\n      value = mBuffer[mPosition++] & 0xFFFF;\n      return *this;\n   }\n\n   // Read one word as a size (N - 1)\n   template<typename Type>\n   PacketReader &size(Type &value)\n   {\n      checkSize(1);\n      value = static_cast<Type>(mBuffer[mPosition++] + 1);\n      return *this;\n   }\n\nprivate:\n   void checkSize(size_t sizeToRead)\n   {\n      if (mPosition + sizeToRead > mBuffer.size()) {\n         decaf_abort(\"Read past end of packet\");\n      }\n   }\n\nprivate:\n   size_t mPosition = 0;\n   gsl::span<uint32_t> mBuffer;\n};\n\ntemplate<typename Type>\nType read(PacketReader &reader)\n{\n   Type result;\n   result.serialise(reader);\n   return result;\n}\n\n} // namespace pm4\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_pm4_sizer.h",
    "content": "#pragma once\n#include \"latte_registers.h\"\n#include <gsl/gsl-lite.hpp>\n\nnamespace latte\n{\n\nnamespace pm4\n{\n\nclass PacketSizer\n{\npublic:\n   PacketSizer() :\n      mPayloadSize(0)\n   {\n   }\n\n   // Read one uint32_t sized datatype\n   template <typename Type>\n   PacketSizer &operator()(Type value)\n   {\n      static_assert(sizeof(Type) == sizeof(uint32_t), \"Invalid type size\");\n      mPayloadSize++;\n      return *this;\n   }\n\n   // Write a pointer as one word\n   template<typename Type>\n   PacketSizer &operator()(Type *value)\n   {\n      mPayloadSize++;\n      return *this;\n   }\n\n   // Write a list of words\n   template<typename Type>\n   PacketSizer &operator()(const gsl::span<Type> &values)\n   {\n      auto dataSize = gsl::narrow_cast<uint32_t>(((values.size() * sizeof(Type)) + 3) / 4);\n      mPayloadSize += dataSize;\n      return *this;\n   }\n\n   // Write one word as a REG_OFFSET\n   PacketSizer &REG_OFFSET(latte::Register value, latte::Register base)\n   {\n      mPayloadSize++;\n      return *this;\n   }\n\n   // Write one word as a CONST_OFFSET\n   PacketSizer &CONST_OFFSET(uint32_t value)\n   {\n      mPayloadSize++;\n      return *this;\n   }\n\n   // Write one word as a size (N - 1)\n   template<typename Type>\n   PacketSizer &size(Type value)\n   {\n      mPayloadSize++;\n      return *this;\n   }\n\n   uint32_t getSize() const\n   {\n      return mPayloadSize;\n   }\n\nprotected:\n   uint32_t mPayloadSize;\n};\n\n} // namespace pm4\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_pm4_writer.h",
    "content": "#pragma once\n#include \"latte_registers.h\"\n#include \"latte_pm4_commands.h\"\n\n#include <libcpu/mmu.h>\n#include <gsl/gsl-lite.hpp>\n\nnamespace latte::pm4\n{\n\nclass PacketWriter\n{\npublic:\n   PacketWriter(uint32_t *buffer,\n                uint32_t &outSize,\n                IT_OPCODE op,\n                uint32_t totalSize) :\n      mBuffer(buffer),\n      mCurSize(outSize)\n   {\n      mTotalSize = totalSize;\n      mSaveSize = mCurSize;\n\n      auto header = latte::pm4::HeaderType3::get(0)\n         .type(latte::pm4::PacketType::Type3)\n         .opcode(op)\n         .size(mTotalSize - 2);\n\n      mBuffer[mCurSize++] = byte_swap(header.value);\n   }\n\n   ~PacketWriter()\n   {\n      decaf_check(mCurSize - mSaveSize == mTotalSize);\n   }\n\n   // Write one word\n   PacketWriter &operator()(uint32_t value)\n   {\n      mBuffer[mCurSize++] = byte_swap(value);\n      return *this;\n   }\n\n   // Write one float\n   PacketWriter &operator()(float value)\n   {\n      mBuffer[mCurSize++] = byte_swap(bit_cast<uint32_t>(value));\n      return *this;\n   }\n\n   // Read one uint32_t sized datatype\n   template <typename Type>\n   PacketWriter &operator()(Type value)\n   {\n      static_assert(sizeof(Type) == sizeof(uint32_t), \"Invalid type size\");\n      mBuffer[mCurSize++] = byte_swap(bit_cast<uint32_t>(value));\n      return *this;\n   }\n\n   // Write a list of words\n   template<typename Type>\n   PacketWriter &operator()(const gsl::span<Type> &values)\n   {\n      auto dataSize = gsl::narrow_cast<uint32_t>(((values.size() * sizeof(Type)) + 3) / 4);\n      std::memcpy(mBuffer + mCurSize, values.data(), dataSize * sizeof(uint32_t));\n\n      // We do the byte_swap here separately as Type may not be uint32_t sized\n      for (auto i = 0u; i < dataSize; ++i) {\n         mBuffer[mCurSize + i] = byte_swap(mBuffer[mCurSize + i]);\n      }\n\n      mCurSize += dataSize;\n      return *this;\n   }\n\n   // Write a list of already swapped words\n   template<typename Type>\n   PacketWriter &operator()(const gsl::span<be2_val<Type>> &values)\n   {\n      auto dataSize = gsl::narrow_cast<uint32_t>(((values.size() * sizeof(Type)) + 3) / 4);\n      std::memcpy(mBuffer + mCurSize, values.data(), dataSize * sizeof(uint32_t));\n      mCurSize += dataSize;\n      return *this;\n   }\n\n   // Write one word as a REG_OFFSET\n   PacketWriter &REG_OFFSET(latte::Register value, latte::Register base)\n   {\n      auto offset = static_cast<uint32_t>(value) - static_cast<uint32_t>(base);\n      mBuffer[mCurSize++] = byte_swap(offset / 4);\n      return *this;\n   }\n\n   // Write one word as a CONST_OFFSET\n   PacketWriter &CONST_OFFSET(uint32_t value)\n   {\n      mBuffer[mCurSize++] = byte_swap(value);\n      return *this;\n   }\n\n   // Write one word as a size (N - 1)\n   template<typename Type>\n   PacketWriter &size(Type value)\n   {\n      mBuffer[mCurSize++] = byte_swap(static_cast<uint32_t>(value) - 1);\n      return *this;\n   }\n\nprivate:\n   uint32_t *mBuffer;\n   uint32_t &mCurSize;\n\n   uint32_t mTotalSize;\n   uint32_t mSaveSize;\n};\n\n} // namespace namespace latte::pm4\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers.h",
    "content": "#pragma once\n#include \"latte_contextstate.h\"\n#include \"latte_registers_cb.h\"\n#include \"latte_registers_cp.h\"\n#include \"latte_registers_db.h\"\n#include \"latte_registers_pa.h\"\n#include \"latte_registers_spi.h\"\n#include \"latte_registers_sq.h\"\n#include \"latte_registers_sx.h\"\n#include \"latte_registers_ta.h\"\n#include \"latte_registers_td.h\"\n#include \"latte_registers_vgt.h\"\n\n#include <cstdint>\n\nnamespace latte\n{\n\nstatic const uint32_t MicroTileWidth = 8;\nstatic const uint32_t MicroTileHeight = 8;\n\nnamespace Register_\n{\n\nenum Value : uint32_t\n{\n   IH_RB_BASE                       = 0x03E04,\n   IH_RB_RPTR                       = 0x03E08,\n   IH_RB_WPTR                       = 0x03E0C,\n   IH_STATUS                        = 0x03E20,\n\n   // Config Registers\n   ConfigRegisterBase               = 0x08000,\n   CP_COHER_STATUS                  = 0x085FC,\n   CP_BUSY_STAT                     = 0x0867C,\n   CP_STAT                          = 0x08680,\n   CP_RB_RPTR                       = 0x08700,\n   CP_IB1_BASE_LO                   = 0x08730,\n   CP_IB1_BASE_HI                   = 0x08734,\n   CP_IB1_BASE_SIZE                 = 0x08738,\n   CP_IB2_BASE_LO                   = 0x0873C,\n   CP_IB2_BASE_HI                   = 0x08740,\n   CP_IB2_BASE_SIZE                 = 0x08744,\n   CP_PERFMON_CNTL                  = 0x087FC,\n   VGT_GS_PER_ES                    = 0x088C8,\n   VGT_ES_PER_GS                    = 0x088CC,\n   VGT_GS_VERTEX_REUSE              = 0x088D4,\n   VGT_GS_PER_VS                    = 0x088E8,\n   VGT_PRIMITIVE_TYPE               = 0x08958,\n   VGT_INDEX_TYPE                   = 0x0895C,\n   VGT_NUM_INDICES                  = 0x08970,\n   SQ_CONFIG                        = 0x08C00,\n   SQ_GPR_RESOURCE_MGMT_1           = 0x08C04,\n   SQ_GPR_RESOURCE_MGMT_2           = 0x08C08,\n   SQ_THREAD_RESOURCE_MGMT          = 0x08C0C,\n   SQ_STACK_RESOURCE_MGMT_1         = 0x08C10,\n   SQ_STACK_RESOURCE_MGMT_2         = 0x08C14,\n   SQ_ESGS_RING_BASE                = 0x08C40,\n   SQ_ESGS_RING_SIZE                = 0x08C44,\n   SQ_GSVS_RING_BASE                = 0x08C48,\n   SQ_GSVS_RING_SIZE                = 0x08C4C,\n   SQ_ESTMP_RING_BASE               = 0x08C50,\n   SQ_ESTMP_RING_SIZE               = 0x08C54,\n   SQ_GSTMP_RING_BASE               = 0x08C58,\n   SQ_GSTMP_RING_SIZE               = 0x08C5C,\n   SQ_VSTMP_RING_BASE               = 0x08C60,\n   SQ_VSTMP_RING_SIZE               = 0x08C64,\n   SQ_PSTMP_RING_BASE               = 0x08C68,\n   SQ_PSTMP_RING_SIZE               = 0x08C6C,\n   SQ_FBUF_RING_BASE                = 0x08C70,\n   SQ_FBUF_RING_SIZE                = 0x08C74,\n   SQ_REDUC_RING_BASE               = 0x08C78,\n   SQ_REDUC_RING_SIZE               = 0x08C7C,\n   SPI_CONFIG_CNTL_1                = 0x0913C,\n   TA_CNTL_AUX                      = 0x09508,\n   TD_PS_SAMPLER_BORDER0_RED        = 0x0A400,\n   TD_PS_SAMPLER_BORDER0_GREEN      = 0x0A404,\n   TD_PS_SAMPLER_BORDER0_BLUE       = 0x0A408,\n   TD_PS_SAMPLER_BORDER0_ALPHA      = 0x0A40C,\n   // ...\n   TD_PS_SAMPLER_BORDER17_RED       = 0x0A510,\n   TD_PS_SAMPLER_BORDER17_GREEN     = 0x0A514,\n   TD_PS_SAMPLER_BORDER17_BLUE      = 0x0A518,\n   TD_PS_SAMPLER_BORDER17_ALPHA     = 0x0A51C,\n   TD_VS_SAMPLER_BORDER0_RED        = 0x0A600,\n   TD_VS_SAMPLER_BORDER0_GREEN      = 0x0A604,\n   TD_VS_SAMPLER_BORDER0_BLUE       = 0x0A608,\n   TD_VS_SAMPLER_BORDER0_ALPHA      = 0x0A60C,\n   // ...\n   TD_VS_SAMPLER_BORDER17_RED       = 0x0A710,\n   TD_VS_SAMPLER_BORDER17_GREEN     = 0x0A714,\n   TD_VS_SAMPLER_BORDER17_BLUE      = 0x0A718,\n   TD_VS_SAMPLER_BORDER17_ALPHA     = 0x0A71C,\n   TD_GS_SAMPLER_BORDER0_RED        = 0x0A900,\n   TD_GS_SAMPLER_BORDER0_GREEN      = 0x0A904,\n   TD_GS_SAMPLER_BORDER0_BLUE       = 0x0A908,\n   TD_GS_SAMPLER_BORDER0_ALPHA      = 0x0A90C,\n   // ...\n   TD_GS_SAMPLER_BORDER17_RED       = 0x0AA10,\n   TD_GS_SAMPLER_BORDER17_GREEN     = 0x0AA14,\n   TD_GS_SAMPLER_BORDER17_BLUE      = 0x0AA18,\n   TD_GS_SAMPLER_BORDER17_ALPHA     = 0x0AA1C,\n   ConfigRegisterEnd                = 0x0AC00,\n\n   CP_RB_BASE                       = 0x0C100,\n   CP_RB_CNTL                       = 0x0C104,\n   CP_INT_CNTL                      = 0x0C124,\n   CP_INT_STATUS                    = 0x0C128,\n\n   // Context Registers\n   ContextRegisterBase              = 0x28000,\n   DB_DEPTH_SIZE                    = 0x28000,\n   DB_DEPTH_VIEW                    = 0x28004,\n   DB_DEPTH_BASE                    = 0x2800C,\n   DB_DEPTH_INFO                    = 0x28010,\n   DB_DEPTH_HTILE_DATA_BASE         = 0x28014,\n   DB_STENCIL_CLEAR                 = 0x28028,\n   DB_DEPTH_CLEAR                   = 0x2802C,\n   PA_SC_SCREEN_SCISSOR_TL          = 0x28030,\n   PA_SC_SCREEN_SCISSOR_BR          = 0x28034,\n   CB_COLOR0_BASE                   = 0x28040,\n   CB_COLOR1_BASE                   = 0x28044,\n   CB_COLOR2_BASE                   = 0x28048,\n   CB_COLOR3_BASE                   = 0x2804C,\n   CB_COLOR4_BASE                   = 0x28050,\n   CB_COLOR5_BASE                   = 0x28054,\n   CB_COLOR6_BASE                   = 0x28058,\n   CB_COLOR7_BASE                   = 0x2805C,\n   CB_COLOR0_SIZE                   = 0x28060,\n   CB_COLOR1_SIZE                   = 0x28064,\n   CB_COLOR2_SIZE                   = 0x28068,\n   CB_COLOR3_SIZE                   = 0x2806C,\n   CB_COLOR4_SIZE                   = 0x28070,\n   CB_COLOR5_SIZE                   = 0x28074,\n   CB_COLOR6_SIZE                   = 0x28078,\n   CB_COLOR7_SIZE                   = 0x2807C,\n   CB_COLOR0_VIEW                   = 0x28080,\n   CB_COLOR1_VIEW                   = 0x28084,\n   CB_COLOR2_VIEW                   = 0x28088,\n   CB_COLOR3_VIEW                   = 0x2808C,\n   CB_COLOR4_VIEW                   = 0x28090,\n   CB_COLOR5_VIEW                   = 0x28094,\n   CB_COLOR6_VIEW                   = 0x28098,\n   CB_COLOR7_VIEW                   = 0x2809C,\n   CB_COLOR0_INFO                   = 0x280A0,\n   CB_COLOR1_INFO                   = 0x280A4,\n   CB_COLOR2_INFO                   = 0x280A8,\n   CB_COLOR3_INFO                   = 0x280AC,\n   CB_COLOR4_INFO                   = 0x280B0,\n   CB_COLOR5_INFO                   = 0x280B4,\n   CB_COLOR6_INFO                   = 0x280B8,\n   CB_COLOR7_INFO                   = 0x280BC,\n   CB_COLOR0_TILE                   = 0x280C0,\n   CB_COLOR1_TILE                   = 0x280C4,\n   CB_COLOR2_TILE                   = 0x280C8,\n   CB_COLOR3_TILE                   = 0x280CC,\n   CB_COLOR4_TILE                   = 0x280D0,\n   CB_COLOR5_TILE                   = 0x280D4,\n   CB_COLOR6_TILE                   = 0x280D8,\n   CB_COLOR7_TILE                   = 0x280DC,\n   CB_COLOR0_FRAG                   = 0x280E0,\n   CB_COLOR1_FRAG                   = 0x280E4,\n   CB_COLOR2_FRAG                   = 0x280E8,\n   CB_COLOR3_FRAG                   = 0x280EC,\n   CB_COLOR4_FRAG                   = 0x280F0,\n   CB_COLOR5_FRAG                   = 0x280F4,\n   CB_COLOR6_FRAG                   = 0x280F8,\n   CB_COLOR7_FRAG                   = 0x280FC,\n   CB_COLOR0_MASK                   = 0x28100,\n   CB_COLOR1_MASK                   = 0x28104,\n   CB_COLOR2_MASK                   = 0x28108,\n   CB_COLOR3_MASK                   = 0x2810C,\n   CB_COLOR4_MASK                   = 0x28110,\n   CB_COLOR5_MASK                   = 0x28114,\n   CB_COLOR6_MASK                   = 0x28118,\n   CB_COLOR7_MASK                   = 0x2811C,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_0    = 0x28140,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_1    = 0x28144,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_2    = 0x28148,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_3    = 0x2814C,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_4    = 0x28150,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_5    = 0x28154,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_6    = 0x28158,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_7    = 0x2815C,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_8    = 0x28160,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_9    = 0x28164,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_10   = 0x28168,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_11   = 0x2816C,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_12   = 0x28170,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_13   = 0x28174,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_14   = 0x28178,\n   SQ_ALU_CONST_BUFFER_SIZE_PS_15   = 0x2817C,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_0    = 0x28180,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_1    = 0x28184,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_2    = 0x28188,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_3    = 0x2818C,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_4    = 0x28190,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_5    = 0x28194,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_6    = 0x28198,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_7    = 0x2819C,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_8    = 0x281A0,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_9    = 0x281A4,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_10   = 0x281A8,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_11   = 0x281AC,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_12   = 0x281B0,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_13   = 0x281B4,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_14   = 0x281B8,\n   SQ_ALU_CONST_BUFFER_SIZE_VS_15   = 0x281BC,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_0    = 0x281C0,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_1    = 0x281C4,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_2    = 0x281C8,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_3    = 0x281CC,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_4    = 0x281D0,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_5    = 0x281D4,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_6    = 0x281D8,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_7    = 0x281DC,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_8    = 0x281E0,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_9    = 0x281E4,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_10   = 0x281E8,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_11   = 0x281EC,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_12   = 0x281F0,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_13   = 0x281F4,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_14   = 0x281F8,\n   SQ_ALU_CONST_BUFFER_SIZE_GS_15   = 0x281FC,\n   PA_SC_WINDOW_OFFSET              = 0x28200,\n   PA_SC_WINDOW_SCISSOR_TL          = 0x28204,\n   PA_SC_WINDOW_SCISSOR_BR          = 0x28208,\n   PA_SC_CLIPRECT_RULE              = 0x2820C,\n   CB_TARGET_MASK                   = 0x28238,\n   CB_SHADER_MASK                   = 0x2823C,\n   PA_SC_GENERIC_SCISSOR_TL         = 0x28240,\n   PA_SC_GENERIC_SCISSOR_BR         = 0x28244,\n   PA_SC_VPORT_SCISSOR_0_TL         = 0x28250,\n   PA_SC_VPORT_SCISSOR_0_BR         = 0x28254,\n   PA_SC_VPORT_ZMIN_0               = 0x282D0,\n   PA_SC_VPORT_ZMAX_0               = 0x282D4,\n   PA_SC_VPORT_ZMIN_1               = 0x282D8,\n   PA_SC_VPORT_ZMAX_1               = 0x282DC,\n   PA_SC_VPORT_ZMIN_2               = 0x282E0,\n   PA_SC_VPORT_ZMAX_2               = 0x282E4,\n   PA_SC_VPORT_ZMIN_3               = 0x282E8,\n   PA_SC_VPORT_ZMAX_3               = 0x282EC,\n   PA_SC_VPORT_ZMIN_4               = 0x282F0,\n   PA_SC_VPORT_ZMAX_4               = 0x282F4,\n   PA_SC_VPORT_ZMIN_5               = 0x282F8,\n   PA_SC_VPORT_ZMAX_5               = 0x282FC,\n   PA_SC_VPORT_ZMIN_6               = 0x28300,\n   PA_SC_VPORT_ZMAX_6               = 0x28304,\n   PA_SC_VPORT_ZMIN_7               = 0x28308,\n   PA_SC_VPORT_ZMAX_7               = 0x2830C,\n   PA_SC_VPORT_ZMIN_8               = 0x28310,\n   PA_SC_VPORT_ZMAX_8               = 0x28314,\n   PA_SC_VPORT_ZMIN_9               = 0x28318,\n   PA_SC_VPORT_ZMAX_9               = 0x2831C,\n   PA_SC_VPORT_ZMIN_10              = 0x28320,\n   PA_SC_VPORT_ZMAX_10              = 0x28324,\n   PA_SC_VPORT_ZMIN_11              = 0x28328,\n   PA_SC_VPORT_ZMAX_11              = 0x2832C,\n   PA_SC_VPORT_ZMIN_12              = 0x28330,\n   PA_SC_VPORT_ZMAX_12              = 0x28334,\n   PA_SC_VPORT_ZMIN_13              = 0x28338,\n   PA_SC_VPORT_ZMAX_13              = 0x2833C,\n   PA_SC_VPORT_ZMIN_14              = 0x28340,\n   PA_SC_VPORT_ZMAX_14              = 0x28344,\n   PA_SC_VPORT_ZMIN_15              = 0x28348,\n   PA_SC_VPORT_ZMAX_15              = 0x2834C,\n   SQ_VTX_SEMANTIC_0                = 0x28380,\n   SQ_VTX_SEMANTIC_1                = 0x28384,\n   SQ_VTX_SEMANTIC_2                = 0x28388,\n   SQ_VTX_SEMANTIC_3                = 0x2838C,\n   SQ_VTX_SEMANTIC_4                = 0x28390,\n   SQ_VTX_SEMANTIC_5                = 0x28394,\n   SQ_VTX_SEMANTIC_6                = 0x28398,\n   SQ_VTX_SEMANTIC_7                = 0x2839C,\n   SQ_VTX_SEMANTIC_8                = 0x283A0,\n   SQ_VTX_SEMANTIC_9                = 0x283A4,\n   SQ_VTX_SEMANTIC_10               = 0x283A8,\n   SQ_VTX_SEMANTIC_11               = 0x283AC,\n   SQ_VTX_SEMANTIC_12               = 0x283B0,\n   SQ_VTX_SEMANTIC_13               = 0x283B4,\n   SQ_VTX_SEMANTIC_14               = 0x283B8,\n   SQ_VTX_SEMANTIC_15               = 0x283BC,\n   SQ_VTX_SEMANTIC_16               = 0x283C0,\n   SQ_VTX_SEMANTIC_17               = 0x283C4,\n   SQ_VTX_SEMANTIC_18               = 0x283C8,\n   SQ_VTX_SEMANTIC_19               = 0x283CC,\n   SQ_VTX_SEMANTIC_20               = 0x283D0,\n   SQ_VTX_SEMANTIC_21               = 0x283D4,\n   SQ_VTX_SEMANTIC_22               = 0x283D8,\n   SQ_VTX_SEMANTIC_23               = 0x283DC,\n   SQ_VTX_SEMANTIC_24               = 0x283E0,\n   SQ_VTX_SEMANTIC_25               = 0x283E4,\n   SQ_VTX_SEMANTIC_26               = 0x283E8,\n   SQ_VTX_SEMANTIC_27               = 0x283EC,\n   SQ_VTX_SEMANTIC_28               = 0x283F0,\n   SQ_VTX_SEMANTIC_29               = 0x283F4,\n   SQ_VTX_SEMANTIC_30               = 0x283F8,\n   SQ_VTX_SEMANTIC_31               = 0x283FC,\n   VGT_MAX_VTX_INDX                 = 0x28400,\n   VGT_MIN_VTX_INDX                 = 0x28404,\n   VGT_INDX_OFFSET                  = 0x28408,\n   VGT_MULTI_PRIM_IB_RESET_INDX     = 0x2840C,\n   SX_ALPHA_TEST_CONTROL            = 0x28410,\n   CB_BLEND_RED                     = 0x28414,\n   CB_BLEND_GREEN                   = 0x28418,\n   CB_BLEND_BLUE                    = 0x2841C,\n   CB_BLEND_ALPHA                   = 0x28420,\n   DB_STENCILREFMASK                = 0x28430,\n   DB_STENCILREFMASK_BF             = 0x28434,\n   SX_ALPHA_REF                     = 0x28438,\n   PA_CL_VPORT_XSCALE_0             = 0x2843C,\n   PA_CL_VPORT_XOFFSET_0            = 0x28440,\n   PA_CL_VPORT_YSCALE_0             = 0x28444,\n   PA_CL_VPORT_YOFFSET_0            = 0x28448,\n   PA_CL_VPORT_ZSCALE_0             = 0x2844C,\n   PA_CL_VPORT_ZOFFSET_0            = 0x28450,\n   // ...\n   PA_CL_VPORT_XSCALE_15            = 0x285A4,\n   PA_CL_VPORT_XOFFSET_15           = 0x285A8,\n   PA_CL_VPORT_YSCALE_15            = 0x285AC,\n   PA_CL_VPORT_YOFFSET_15           = 0x285B0,\n   PA_CL_VPORT_ZSCALE_15            = 0x285B4,\n   PA_CL_VPORT_ZOFFSET_15           = 0x285B8,\n   SPI_VS_OUT_ID_0                  = 0x28614,\n   SPI_VS_OUT_ID_1                  = 0x28618,\n   SPI_VS_OUT_ID_2                  = 0x2861C,\n   SPI_VS_OUT_ID_3                  = 0x28620,\n   SPI_VS_OUT_ID_4                  = 0x28624,\n   SPI_VS_OUT_ID_5                  = 0x28628,\n   SPI_VS_OUT_ID_6                  = 0x2862C,\n   SPI_VS_OUT_ID_7                  = 0x28630,\n   SPI_VS_OUT_ID_8                  = 0x28634,\n   SPI_VS_OUT_ID_9                  = 0x28638,\n   SPI_PS_INPUT_CNTL_0              = 0x28644,\n   SPI_PS_INPUT_CNTL_1              = 0x28648,\n   SPI_PS_INPUT_CNTL_2              = 0x2864C,\n   SPI_PS_INPUT_CNTL_3              = 0x28650,\n   SPI_PS_INPUT_CNTL_4              = 0x28654,\n   SPI_PS_INPUT_CNTL_5              = 0x28658,\n   SPI_PS_INPUT_CNTL_6              = 0x2865C,\n   SPI_PS_INPUT_CNTL_7              = 0x28660,\n   SPI_PS_INPUT_CNTL_8              = 0x28664,\n   SPI_PS_INPUT_CNTL_9              = 0x28668,\n   SPI_PS_INPUT_CNTL_10             = 0x2866C,\n   SPI_PS_INPUT_CNTL_11             = 0x28670,\n   SPI_PS_INPUT_CNTL_12             = 0x28674,\n   SPI_PS_INPUT_CNTL_13             = 0x28678,\n   SPI_PS_INPUT_CNTL_14             = 0x2867C,\n   SPI_PS_INPUT_CNTL_15             = 0x28680,\n   SPI_PS_INPUT_CNTL_16             = 0x28684,\n   SPI_PS_INPUT_CNTL_17             = 0x28688,\n   SPI_PS_INPUT_CNTL_18             = 0x2868C,\n   SPI_PS_INPUT_CNTL_19             = 0x28690,\n   SPI_PS_INPUT_CNTL_20             = 0x28694,\n   SPI_PS_INPUT_CNTL_21             = 0x28698,\n   SPI_PS_INPUT_CNTL_22             = 0x2869C,\n   SPI_PS_INPUT_CNTL_23             = 0x286A0,\n   SPI_PS_INPUT_CNTL_24             = 0x286A4,\n   SPI_PS_INPUT_CNTL_25             = 0x286A8,\n   SPI_PS_INPUT_CNTL_26             = 0x286AC,\n   SPI_PS_INPUT_CNTL_27             = 0x286B0,\n   SPI_PS_INPUT_CNTL_28             = 0x286B4,\n   SPI_PS_INPUT_CNTL_29             = 0x286B8,\n   SPI_PS_INPUT_CNTL_30             = 0x286BC,\n   SPI_PS_INPUT_CNTL_31             = 0x286C0,\n   SPI_VS_OUT_CONFIG                = 0x286C4,\n   SPI_PS_IN_CONTROL_0              = 0x286CC,\n   SPI_PS_IN_CONTROL_1              = 0x286D0,\n   SPI_INTERP_CONTROL_0             = 0x286D4,\n   SPI_INPUT_Z                      = 0x286D8,\n   SPI_FOG_CNTL                     = 0x286DC,\n   SPI_FOG_FUNC_SCALE               = 0x286E0,\n   SPI_FOG_FUNC_BIAS                = 0x286E4,\n   CB_BLEND0_CONTROL                = 0x28780,\n   CB_BLEND1_CONTROL                = 0x28784,\n   CB_BLEND2_CONTROL                = 0x28788,\n   CB_BLEND3_CONTROL                = 0x2878C,\n   CB_BLEND4_CONTROL                = 0x28790,\n   CB_BLEND5_CONTROL                = 0x28794,\n   CB_BLEND6_CONTROL                = 0x28798,\n   CB_BLEND7_CONTROL                = 0x2879C,\n   CB_SHADER_CONTROL                = 0x287A0,\n   VGT_DRAW_INITIATOR               = 0x287F0,\n   VGT_DMA_BASE_HI                  = 0x287E4,\n   VGT_DMA_BASE                     = 0x287E8,\n   DB_DEPTH_CONTROL                 = 0x28800,\n   CB_BLEND_CONTROL                 = 0x28804,\n   CB_COLOR_CONTROL                 = 0x28808,\n   DB_SHADER_CONTROL                = 0x2880C,\n   PA_CL_CLIP_CNTL                  = 0x28810,\n   PA_SU_SC_MODE_CNTL               = 0x28814,\n   PA_CL_VTE_CNTL                   = 0x28818,\n   PA_CL_VS_OUT_CNTL                = 0x2881C,\n   PA_CL_NANINF_CNTL                = 0x28820,\n   SQ_PGM_START_PS                  = 0x28840,\n   SQ_PGM_SIZE_PS                   = 0x28844,\n   SQ_PGM_RESOURCES_PS              = 0x28850,\n   SQ_PGM_EXPORTS_PS                = 0x28854,\n   SQ_PGM_START_VS                  = 0x28858,\n   SQ_PGM_SIZE_VS                   = 0x2885C,\n   SQ_PGM_RESOURCES_VS              = 0x28868,\n   SQ_PGM_START_GS                  = 0x2886C,\n   SQ_PGM_SIZE_GS                   = 0x28870,\n   SQ_PGM_RESOURCES_GS              = 0x2887C,\n   SQ_PGM_START_ES                  = 0x28880,\n   SQ_PGM_SIZE_ES                   = 0x28884,\n   SQ_PGM_RESOURCES_ES              = 0x28890,\n   SQ_PGM_START_FS                  = 0x28894,\n   SQ_PGM_SIZE_FS                   = 0x28898,\n   SQ_PGM_RESOURCES_FS              = 0x288A4,\n   SQ_ESGS_RING_ITEMSIZE            = 0x288A8,\n   SQ_GSVS_RING_ITEMSIZE            = 0x288AC,\n   SQ_ESTMP_RING_ITEMSIZE           = 0x288B0,\n   SQ_GSTMP_RING_ITEMSIZE           = 0x288B4,\n   SQ_VSTMP_RING_ITEMSIZE           = 0x288B8,\n   SQ_PSTMP_RING_ITEMSIZE           = 0x288BC,\n   SQ_FBUF_RING_ITEMSIZE            = 0x288C0,\n   SQ_REDUC_RING_ITEMSIZE           = 0x288C4,\n   SQ_GS_VERT_ITEMSIZE              = 0x288C8,\n   SQ_PGM_CF_OFFSET_PS              = 0x288CC,\n   SQ_PGM_CF_OFFSET_VS              = 0x288D0,\n   SQ_PGM_CF_OFFSET_GS              = 0x288D4,\n   SQ_PGM_CF_OFFSET_ES              = 0x288D8,\n   SQ_PGM_CF_OFFSET_FS              = 0x288DC,\n   SQ_VTX_SEMANTIC_CLEAR            = 0x288E0,\n   SQ_ALU_CONST_CACHE_PS_0          = 0x28940,\n   SQ_ALU_CONST_CACHE_PS_1          = 0x28944,\n   SQ_ALU_CONST_CACHE_PS_2          = 0x28948,\n   SQ_ALU_CONST_CACHE_PS_3          = 0x2894C,\n   SQ_ALU_CONST_CACHE_PS_4          = 0x28950,\n   SQ_ALU_CONST_CACHE_PS_5          = 0x28954,\n   SQ_ALU_CONST_CACHE_PS_6          = 0x28958,\n   SQ_ALU_CONST_CACHE_PS_7          = 0x2895C,\n   SQ_ALU_CONST_CACHE_PS_8          = 0x28960,\n   SQ_ALU_CONST_CACHE_PS_9          = 0x28964,\n   SQ_ALU_CONST_CACHE_PS_10         = 0x28968,\n   SQ_ALU_CONST_CACHE_PS_11         = 0x2896C,\n   SQ_ALU_CONST_CACHE_PS_12         = 0x28970,\n   SQ_ALU_CONST_CACHE_PS_13         = 0x28974,\n   SQ_ALU_CONST_CACHE_PS_14         = 0x28978,\n   SQ_ALU_CONST_CACHE_PS_15         = 0x2897C,\n   SQ_ALU_CONST_CACHE_VS_0          = 0x28980,\n   SQ_ALU_CONST_CACHE_VS_1          = 0x28984,\n   SQ_ALU_CONST_CACHE_VS_2          = 0x28988,\n   SQ_ALU_CONST_CACHE_VS_3          = 0x2898C,\n   SQ_ALU_CONST_CACHE_VS_4          = 0x28990,\n   SQ_ALU_CONST_CACHE_VS_5          = 0x28994,\n   SQ_ALU_CONST_CACHE_VS_6          = 0x28998,\n   SQ_ALU_CONST_CACHE_VS_7          = 0x2899C,\n   SQ_ALU_CONST_CACHE_VS_8          = 0x289A0,\n   SQ_ALU_CONST_CACHE_VS_9          = 0x289A4,\n   SQ_ALU_CONST_CACHE_VS_10         = 0x289A8,\n   SQ_ALU_CONST_CACHE_VS_11         = 0x289AC,\n   SQ_ALU_CONST_CACHE_VS_12         = 0x289B0,\n   SQ_ALU_CONST_CACHE_VS_13         = 0x289B4,\n   SQ_ALU_CONST_CACHE_VS_14         = 0x289B8,\n   SQ_ALU_CONST_CACHE_VS_15         = 0x289BC,\n   SQ_ALU_CONST_CACHE_GS_0          = 0x289C0,\n   SQ_ALU_CONST_CACHE_GS_1          = 0x289C4,\n   SQ_ALU_CONST_CACHE_GS_2          = 0x289C8,\n   SQ_ALU_CONST_CACHE_GS_3          = 0x289CC,\n   SQ_ALU_CONST_CACHE_GS_4          = 0x289D0,\n   SQ_ALU_CONST_CACHE_GS_5          = 0x289D4,\n   SQ_ALU_CONST_CACHE_GS_6          = 0x289D8,\n   SQ_ALU_CONST_CACHE_GS_7          = 0x289DC,\n   SQ_ALU_CONST_CACHE_GS_8          = 0x289E0,\n   SQ_ALU_CONST_CACHE_GS_9          = 0x289E4,\n   SQ_ALU_CONST_CACHE_GS_10         = 0x289E8,\n   SQ_ALU_CONST_CACHE_GS_11         = 0x289EC,\n   SQ_ALU_CONST_CACHE_GS_12         = 0x289F0,\n   SQ_ALU_CONST_CACHE_GS_13         = 0x289F4,\n   SQ_ALU_CONST_CACHE_GS_14         = 0x289F8,\n   SQ_ALU_CONST_CACHE_GS_15         = 0x289FC,\n   PA_SU_POINT_SIZE                 = 0x28A00,\n   PA_SU_POINT_MINMAX               = 0x28A04,\n   PA_SU_LINE_CNTL                  = 0x28A08,\n   PA_SC_LINE_STIPPLE               = 0x28A0C,\n   VGT_OUTPUT_PATH_CNTL             = 0x28A10,\n   VGT_HOS_MAX_TESS_LEVEL           = 0x28A18,\n   VGT_HOS_MIN_TESS_LEVEL           = 0x28A1C,\n   VGT_HOS_REUSE_DEPTH              = 0x28A20,\n   VGT_GS_MODE                      = 0x28A40,\n   PA_SC_MPASS_PS_CNTL              = 0x28A48,\n   PA_SC_MODE_CNTL                  = 0x28A4C,\n   VGT_GS_OUT_PRIM_TYPE             = 0x28A6C,\n   VGT_DMA_SIZE                     = 0x28A74,\n   VGT_DMA_MAX_SIZE                 = 0x28A78,\n   VGT_DMA_INDEX_TYPE               = 0x28A7C,\n   VGT_PRIMITIVEID_EN               = 0x28A84,\n   VGT_DMA_NUM_INSTANCES            = 0x28A88,\n   VGT_MULTI_PRIM_IB_RESET_EN       = 0x28A94,\n   VGT_INSTANCE_STEP_RATE_0         = 0x28AA0,\n   VGT_INSTANCE_STEP_RATE_1         = 0x28AA4,\n   VGT_STRMOUT_EN                   = 0x28AB0,\n   VGT_REUSE_OFF                    = 0x28AB4,\n   VGT_VTX_CNT_EN                   = 0x28AB8,\n   VGT_STRMOUT_BUFFER_SIZE_0        = 0x28AD0,\n   VGT_STRMOUT_VTX_STRIDE_0         = 0x28AD4,\n   VGT_STRMOUT_BUFFER_BASE_0        = 0x28AD8,\n   VGT_STRMOUT_BUFFER_OFFSET_0      = 0x28ADC,\n   VGT_STRMOUT_BUFFER_SIZE_1        = 0x28AE0,\n   VGT_STRMOUT_VTX_STRIDE_1         = 0x28AE4,\n   VGT_STRMOUT_BUFFER_BASE_1        = 0x28AE8,\n   VGT_STRMOUT_BUFFER_OFFSET_1      = 0x28AEC,\n   VGT_STRMOUT_BUFFER_SIZE_2        = 0x28AF0,\n   VGT_STRMOUT_VTX_STRIDE_2         = 0x28AF4,\n   VGT_STRMOUT_BUFFER_BASE_2        = 0x28AF8,\n   VGT_STRMOUT_BUFFER_OFFSET_2      = 0x28AFC,\n   VGT_STRMOUT_BUFFER_SIZE_3        = 0x28B00,\n   VGT_STRMOUT_VTX_STRIDE_3         = 0x28B04,\n   VGT_STRMOUT_BUFFER_BASE_3        = 0x28B08,\n   VGT_STRMOUT_BUFFER_OFFSET_3      = 0x28B0C,\n   VGT_STRMOUT_BASE_OFFSET_0        = 0x28B10,\n   VGT_STRMOUT_BASE_OFFSET_1        = 0x28B14,\n   VGT_STRMOUT_BASE_OFFSET_2        = 0x28B18,\n   VGT_STRMOUT_BASE_OFFSET_3        = 0x28B1C,\n   VGT_STRMOUT_BUFFER_EN            = 0x28B20,\n   VGT_STRMOUT_DRAW_OPAQUE_OFFSET   = 0x28B28,\n   VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE = 0x28B2C,\n   VGT_STRMOUT_DRAW_OPAQUE_VERTEX_STRIDE = 0x28B30,\n   PA_SC_LINE_CNTL                  = 0x28C00,\n   PA_SU_VTX_CNTL                   = 0x28C08,\n   PA_CL_GB_VERT_CLIP_ADJ           = 0x28C0C,\n   PA_CL_GB_VERT_DISC_ADJ           = 0x28C10,\n   PA_CL_GB_HORZ_CLIP_ADJ           = 0x28C14,\n   PA_CL_GB_HORZ_DISC_ADJ           = 0x28C18,\n   CB_CLRCMP_CONTROL                = 0x28C30,\n   CB_CLRCMP_SRC                    = 0x28C34,\n   CB_CLRCMP_DST                    = 0x28C38,\n   CB_CLRCMP_MSK                    = 0x28C3C,\n   PA_SC_AA_MASK                    = 0x28C48,\n   VGT_VERTEX_REUSE_BLOCK_CNTL      = 0x28C58,\n   VGT_OUT_DEALLOC_CNTL             = 0x28C5C,\n   DB_RENDER_CONTROL                = 0x28D0C,\n   DB_RENDER_OVERRIDE               = 0x28D10,\n   DB_HTILE_SURFACE                 = 0x28D24,\n   DB_SRESULTS_COMPARE_STATE0       = 0x28D28,\n   DB_SRESULTS_COMPARE_STATE1       = 0x28D2C,\n   DB_PRELOAD_CONTROL               = 0x28D30,\n   DB_PREFETCH_LIMIT                = 0x28D34,\n   DB_ALPHA_TO_MASK                 = 0x28D44,\n   PA_SU_POLY_OFFSET_DB_FMT_CNTL    = 0x28DF8,\n   PA_SU_POLY_OFFSET_CLAMP          = 0x28DFC,\n   PA_SU_POLY_OFFSET_FRONT_SCALE    = 0x28E00,\n   PA_SU_POLY_OFFSET_FRONT_OFFSET   = 0x28E04,\n   PA_SU_POLY_OFFSET_BACK_SCALE     = 0x28E08,\n   PA_SU_POLY_OFFSET_BACK_OFFSET    = 0x28E0C,\n   PA_CL_POINT_X_RAD                = 0x28E10,\n   PA_CL_POINT_Y_RAD                = 0x28E14,\n   PA_CL_POINT_POINT_SIZE           = 0x28E18,\n   PA_CL_POINT_POINT_CULL_RAD       = 0x28E1C,\n   PA_CL_UCP_0_X                    = 0x28E20,\n   PA_CL_UCP_0_Y                    = 0x28E24,\n   PA_CL_UCP_0_Z                    = 0x28E28,\n   PA_CL_UCP_0_W                    = 0x28E2C,\n   // ...\n   PA_CL_UCP_5_X                    = 0x28E70,\n   PA_CL_UCP_5_Y                    = 0x28E74,\n   PA_CL_UCP_5_Z                    = 0x28E78,\n   PA_CL_UCP_5_W                    = 0x28E7C,\n   ContextRegisterEnd               = 0x29000,\n\n   // Alu Const Registers\n   AluConstRegisterBase             = 0x30000,\n   SQ_ALU_CONSTANT0_0               = 0x30000,\n   SQ_ALU_CONSTANT1_0               = 0x30004,\n   SQ_ALU_CONSTANT2_0               = 0x30008,\n   SQ_ALU_CONSTANT3_0               = 0x3000C,\n   // ...\n   SQ_ALU_CONSTANT0_256             = 0x31000,\n   SQ_ALU_CONSTANT1_256             = 0x31004,\n   SQ_ALU_CONSTANT2_256             = 0x31008,\n   SQ_ALU_CONSTANT3_256             = 0x3100C,\n   AluConstRegisterEnd              = 0x32000,\n\n   // Resource Registers\n   ResourceRegisterBase             = 0x38000,\n   SQ_RESOURCE_WORD0_0              = 0x38000,\n   SQ_RESOURCE_WORD1_0              = 0x38004,\n   SQ_RESOURCE_WORD2_0              = 0x38008,\n   SQ_RESOURCE_WORD3_0              = 0x3800C,\n   SQ_RESOURCE_WORD4_0              = 0x38010,\n   SQ_RESOURCE_WORD5_0              = 0x38014,\n   SQ_RESOURCE_WORD6_0              = 0x38018,\n   ResourceRegisterEnd              = 0x3B678,\n\n   // Sampler Registers\n   SamplerRegisterBase              = 0x3C000,\n   SQ_TEX_SAMPLER_WORD0_0           = 0x3C000,\n   SQ_TEX_SAMPLER_WORD1_0           = 0x3C004,\n   SQ_TEX_SAMPLER_WORD2_0           = 0x3C008,\n   SamplerRegisterEnd               = 0x3C288,\n\n   // Control Registers\n   ControlRegisterBase              = 0x3CFF0,\n   SQ_VTX_BASE_VTX_LOC              = 0x3CFF0,\n   SQ_VTX_START_INST_LOC            = 0x3CFF4,\n   ControlRegisterEnd               = 0x3CFF8, // TODO: Find real ControlRegisterEnd\n\n   // Loop Const Registers\n   LoopConstRegisterBase            = 0x3E200,\n   SQ_LOOP_CONST_PS_0               = 0x3E200,\n   // ...\n   SQ_LOOP_CONST_PS_31              = 0x3E27C,\n   SQ_LOOP_CONST_VS_0               = 0x3E280,\n   // ...\n   SQ_LOOP_CONST_VS_31              = 0x3E2FC,\n   SQ_LOOP_CONST_GS_0               = 0x3E300,\n   // ...\n   SQ_LOOP_CONST_GS_31              = 0x3E37C,\n   LoopConstRegisterEnd             = 0x3E380,\n\n   // Bool Const Registers\n   BoolConstRegisterBase            = 0x3E380,\n   BoolConstRegisterEnd             = 0x3E384, // TODO: Find real BoolConstRegisterEnd\n};\n}\n\nusing Register = Register_::Value;\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers_cb.h",
    "content": "#pragma once\n#include \"latte_enum_cb.h\"\n#include \"latte_enum_common.h\"\n\n#include <cstdint>\n#include <common/bitfield.h>\n\nnamespace latte\n{\n\n// Blend function used for all render targets\nBITFIELD_BEG(CB_BLENDN_CONTROL, uint32_t)\n   BITFIELD_ENTRY(0, 5, CB_BLEND_FUNC, COLOR_SRCBLEND)\n   BITFIELD_ENTRY(5, 3, CB_COMB_FUNC, COLOR_COMB_FCN)\n   BITFIELD_ENTRY(8, 5, CB_BLEND_FUNC, COLOR_DESTBLEND)\n   BITFIELD_ENTRY(13, 1, bool, OPACITY_WEIGHT)\n   BITFIELD_ENTRY(16, 5, CB_BLEND_FUNC, ALPHA_SRCBLEND)\n   BITFIELD_ENTRY(21, 3, CB_COMB_FUNC, ALPHA_COMB_FCN)\n   BITFIELD_ENTRY(24, 5, CB_BLEND_FUNC, ALPHA_DESTBLEND)\n   BITFIELD_ENTRY(29, 1, bool, SEPARATE_ALPHA_BLEND)\nBITFIELD_END\n\nBITFIELD_BEG(CB_BLEND_RED, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BLEND_RED)\nBITFIELD_END\n\nBITFIELD_BEG(CB_BLEND_GREEN, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BLEND_GREEN)\nBITFIELD_END\n\nBITFIELD_BEG(CB_BLEND_BLUE, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BLEND_BLUE)\nBITFIELD_END\n\nBITFIELD_BEG(CB_BLEND_ALPHA, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BLEND_ALPHA)\nBITFIELD_END\n\n// This register controls color keying, which masks individual pixel writes based on comparing the\n// source(pre - ROP) color and / or the dest(frame buffer) color to comparison values, after masking both by CLRCMP_MSK\nBITFIELD_BEG(CB_CLRCMP_CONTROL, uint32_t)\n   BITFIELD_ENTRY(0, 3, CB_CLRCMP_DRAW, CLRCMP_FCN_SRC)\n   BITFIELD_ENTRY(8, 3, CB_CLRCMP_DRAW, CLRCMP_FCN_DST)\n   BITFIELD_ENTRY(24, 2, CB_CLRCMP_SEL, CLRCMP_FCN_SEL)\nBITFIELD_END\n\nBITFIELD_BEG(CB_CLRCMP_SRC, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, CLRCMP_SRC)\nBITFIELD_END\n\nBITFIELD_BEG(CB_CLRCMP_DST, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, CLRCMP_DST)\nBITFIELD_END\n\nBITFIELD_BEG(CB_CLRCMP_MSK, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, CLRCMP_MSK)\nBITFIELD_END\n\nBITFIELD_BEG(CB_COLORN_BASE, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, BASE_256B)\nBITFIELD_END\n\nBITFIELD_BEG(CB_COLORN_TILE, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, BASE_256B)\nBITFIELD_END\n\nBITFIELD_BEG(CB_COLORN_FRAG, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, BASE_256B)\nBITFIELD_END\n\nBITFIELD_BEG(CB_COLORN_SIZE, uint32_t)\n   BITFIELD_ENTRY(0, 10, uint32_t, PITCH_TILE_MAX)\n   BITFIELD_ENTRY(10, 20, uint32_t, SLICE_TILE_MAX)\nBITFIELD_END\n\nBITFIELD_BEG(CB_COLORN_INFO, uint32_t)\n   BITFIELD_ENTRY(0, 2, CB_ENDIAN, ENDIAN)\n   BITFIELD_ENTRY(2, 6, CB_FORMAT, FORMAT)\n   BITFIELD_ENTRY(8, 4, BUFFER_ARRAY_MODE, ARRAY_MODE)\n   BITFIELD_ENTRY(12, 3, CB_NUMBER_TYPE, NUMBER_TYPE)\n   BITFIELD_ENTRY(15, 1, BUFFER_READ_SIZE, READ_SIZE)\n   BITFIELD_ENTRY(16, 2, CB_COMP_SWAP, COMP_SWAP)\n   BITFIELD_ENTRY(18, 2, CB_TILE_MODE, TILE_MODE)\n   BITFIELD_ENTRY(20, 1, bool, BLEND_CLAMP)\n   BITFIELD_ENTRY(21, 1, bool, CLEAR_COLOR)\n   BITFIELD_ENTRY(22, 1, bool, BLEND_BYPASS)\n   BITFIELD_ENTRY(23, 1, bool, BLEND_FLOAT32)\n   BITFIELD_ENTRY(24, 1, bool, SIMPLE_FLOAT)\n   BITFIELD_ENTRY(25, 1, CB_ROUND_MODE, ROUND_MODE)\n   BITFIELD_ENTRY(26, 1, bool, TILE_COMPACT)\n   BITFIELD_ENTRY(27, 1, CB_SOURCE_FORMAT, SOURCE_FORMAT)\nBITFIELD_END\n\n// Selects slice index range for render target\nBITFIELD_BEG(CB_COLORN_VIEW, uint32_t)\n   BITFIELD_ENTRY(0, 11, uint32_t, SLICE_START)\n   BITFIELD_ENTRY(13, 11, uint32_t, SLICE_MAX)\nBITFIELD_END\n\nBITFIELD_BEG(CB_COLORN_MASK, uint32_t)\n   BITFIELD_ENTRY(0, 12, uint32_t, CMASK_BLOCK_MAX)\n   BITFIELD_ENTRY(12, 20, uint32_t, FMASK_TILE_MAX)\nBITFIELD_END\n\nBITFIELD_BEG(CB_COLOR_CONTROL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, FOG_ENABLE)\n   BITFIELD_ENTRY(1, 1, bool, MULTIWRITE_ENABLE)\n   BITFIELD_ENTRY(2, 1, bool, DITHER_ENABLE)\n   BITFIELD_ENTRY(3, 1, bool, DEGAMMA_ENABLE)\n   BITFIELD_ENTRY(4, 3, CB_SPECIAL_OP, SPECIAL_OP)\n   BITFIELD_ENTRY(7, 1, bool, PER_MRT_BLEND)\n   BITFIELD_ENTRY(8, 8, uint8_t, TARGET_BLEND_ENABLE)\n   BITFIELD_ENTRY(16, 8, uint8_t, ROP3)\nBITFIELD_END\n\nBITFIELD_BEG(CB_SHADER_CONTROL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, RT0_ENABLE)\n   BITFIELD_ENTRY(1, 1, bool, RT1_ENABLE)\n   BITFIELD_ENTRY(2, 1, bool, RT2_ENABLE)\n   BITFIELD_ENTRY(3, 1, bool, RT3_ENABLE)\n   BITFIELD_ENTRY(4, 1, bool, RT4_ENABLE)\n   BITFIELD_ENTRY(5, 1, bool, RT5_ENABLE)\n   BITFIELD_ENTRY(6, 1, bool, RT6_ENABLE)\n   BITFIELD_ENTRY(7, 1, bool, RT7_ENABLE)\nBITFIELD_END\n\n// Contains color component mask fields for the colors output by the shader\nBITFIELD_BEG(CB_SHADER_MASK, uint32_t)\n   BITFIELD_ENTRY(0, 4, uint32_t, OUTPUT0_ENABLE)\n   BITFIELD_ENTRY(4, 4, uint32_t, OUTPUT1_ENABLE)\n   BITFIELD_ENTRY(8, 4, uint32_t, OUTPUT2_ENABLE)\n   BITFIELD_ENTRY(12, 4, uint32_t, OUTPUT3_ENABLE)\n   BITFIELD_ENTRY(16, 4, uint32_t, OUTPUT4_ENABLE)\n   BITFIELD_ENTRY(20, 4, uint32_t, OUTPUT5_ENABLE)\n   BITFIELD_ENTRY(24, 4, uint32_t, OUTPUT6_ENABLE)\n   BITFIELD_ENTRY(28, 4, uint32_t, OUTPUT7_ENABLE)\nBITFIELD_END\n\n// Contains color component mask fields for writing the render targets. Red, green, blue, and alpha\n// are components 0, 1, 2, and 3 in the pixel shader and are enabled by bits 0, 1, 2, and 3 in each field\nBITFIELD_BEG(CB_TARGET_MASK, uint32_t)\n   BITFIELD_ENTRY(0, 4, uint8_t, TARGET0_ENABLE)\n   BITFIELD_ENTRY(4, 4, uint8_t, TARGET1_ENABLE)\n   BITFIELD_ENTRY(8, 4, uint8_t, TARGET2_ENABLE)\n   BITFIELD_ENTRY(12, 4, uint8_t, TARGET3_ENABLE)\n   BITFIELD_ENTRY(16, 4, uint8_t, TARGET4_ENABLE)\n   BITFIELD_ENTRY(20, 4, uint8_t, TARGET5_ENABLE)\n   BITFIELD_ENTRY(24, 4, uint8_t, TARGET6_ENABLE)\n   BITFIELD_ENTRY(28, 4, uint8_t, TARGET7_ENABLE)\nBITFIELD_END\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers_cp.h",
    "content": "#pragma once\n#include \"latte_enum_cb.h\"\n\n#include <common/bitfield.h>\n#include <cstdint>\n\nnamespace latte\n{\n\n// Interrupt Control\nBITFIELD_BEG(CP_INT_CNTL, uint32_t)\n   BITFIELD_ENTRY(17, 1, bool, UNK17_INT_ENABLE)\n   BITFIELD_ENTRY(19, 1, bool, CNTX_BUSY_INT_ENABLE)\n   BITFIELD_ENTRY(20, 1, bool, CNTX_EMPTY_INT_ENABLE)\n   BITFIELD_ENTRY(24, 1, bool, BAD_OPCODE_EXCEPTION)\n   BITFIELD_ENTRY(25, 1, bool, SCRATCH_INT_ENABLE)\n   BITFIELD_ENTRY(26, 1, bool, TIME_STAMP_INT_ENABLE)\n   BITFIELD_ENTRY(27, 1, bool, RESERVED_BITS_EXCEPTION)\n   BITFIELD_ENTRY(29, 1, bool, IB2_INT_ENABLE)\n   BITFIELD_ENTRY(30, 1, bool, IB1_INT_ENABLE)\n   BITFIELD_ENTRY(31, 1, bool, RB_INT_ENABLE)\nBITFIELD_END\n\n// Interrupt Status\nBITFIELD_BEG(CP_INT_STATUS, uint32_t)\n   BITFIELD_ENTRY(25, 1, bool, SCRATCH_INT_STAT)\n   BITFIELD_ENTRY(26, 1, bool, TIME_STAMP_INT_STAT)\n   BITFIELD_ENTRY(29, 1, bool, IB2_INT_STAT)\n   BITFIELD_ENTRY(30, 1, bool, IB1_INT_STAT)\n   BITFIELD_ENTRY(31, 1, bool, RB_INT_STAT)\nBITFIELD_END\n\n// Ring Buffer Control\nBITFIELD_BEG(CP_RB_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, RB_BUFSZ)\n   BITFIELD_ENTRY(8, 8, uint8_t, RB_BLKSZ)\n   BITFIELD_ENTRY(27, 1, bool, RB_NO_UPDATE)\n   BITFIELD_ENTRY(16, 2, CB_ENDIAN, BUF_SWAP)\n   BITFIELD_ENTRY(31, 1, bool, RB_RPTR_WR_ENA)\nBITFIELD_END\n\n// Coherence Control\nBITFIELD_BEG(CP_COHER_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, DEST_BASE_0_ENA)\n   BITFIELD_ENTRY(1, 1, bool, DEST_BASE_1_ENA)\n   BITFIELD_ENTRY(2, 1, bool, SO0_DEST_BASE_ENA)\n   BITFIELD_ENTRY(3, 1, bool, SO1_DEST_BASE_ENA)\n   BITFIELD_ENTRY(4, 1, bool, SO2_DEST_BASE_ENA)\n   BITFIELD_ENTRY(5, 1, bool, SO3_DEST_BASE_ENA)\n   BITFIELD_ENTRY(6, 1, bool, CB0_DEST_BASE_ENA)\n   BITFIELD_ENTRY(7, 1, bool, CB1_DEST_BASE_ENA)\n   BITFIELD_ENTRY(8, 1, bool, CB2_DEST_BASE_ENA)\n   BITFIELD_ENTRY(9, 1, bool, CB3_DEST_BASE_ENA)\n   BITFIELD_ENTRY(10, 1, bool, CB4_DEST_BASE_ENA)\n   BITFIELD_ENTRY(11, 1, bool, CB5_DEST_BASE_ENA)\n   BITFIELD_ENTRY(12, 1, bool, CB6_DEST_BASE_ENA)\n   BITFIELD_ENTRY(13, 1, bool, CB7_DEST_BASE_ENA)\n   BITFIELD_ENTRY(14, 1, bool, DB_DEST_BASE_ENA)\n   BITFIELD_ENTRY(20, 1, bool, FULL_CACHE_ENA)\n   BITFIELD_ENTRY(23, 1, bool, TC_ACTION_ENA)\n   BITFIELD_ENTRY(24, 1, bool, VC_ACTION_ENA)\n   BITFIELD_ENTRY(25, 1, bool, CB_ACTION_ENA)\n   BITFIELD_ENTRY(26, 1, bool, DB_ACTION_ENA)\n   BITFIELD_ENTRY(27, 1, bool, SH_ACTION_ENA)\n   BITFIELD_ENTRY(28, 1, bool, SX_ACTION_ENA)\n   BITFIELD_ENTRY(31, 1, bool, ENGINE_ME)\nBITFIELD_END\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers_db.h",
    "content": "#pragma once\n#include \"latte_enum_common.h\"\n#include \"latte_enum_db.h\"\n\n#include <common/bitfield.h>\n#include <cstdint>\n\nnamespace latte\n{\n\nBITFIELD_BEG(DB_ALPHA_TO_MASK, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, ALPHA_TO_MASK_ENABLE)\n   BITFIELD_ENTRY(8, 2, uint32_t, ALPHA_TO_MASK_OFFSET0)\n   BITFIELD_ENTRY(10, 2, uint32_t, ALPHA_TO_MASK_OFFSET1)\n   BITFIELD_ENTRY(12, 2, uint32_t, ALPHA_TO_MASK_OFFSET2)\n   BITFIELD_ENTRY(14, 2, uint32_t, ALPHA_TO_MASK_OFFSET3)\n   BITFIELD_ENTRY(16, 1, bool, OFFSET_ROUND)\nBITFIELD_END\n\nBITFIELD_BEG(DB_DEPTH_BASE, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, BASE_256B)\nBITFIELD_END\n\nBITFIELD_BEG(DB_DEPTH_HTILE_DATA_BASE, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, BASE_256B)\nBITFIELD_END\n\nBITFIELD_BEG(DB_DEPTH_INFO, uint32_t)\n   BITFIELD_ENTRY(0, 3, DB_FORMAT, FORMAT)\n   BITFIELD_ENTRY(3, 1, BUFFER_READ_SIZE, READ_SIZE)\n   BITFIELD_ENTRY(15, 4, BUFFER_ARRAY_MODE, ARRAY_MODE)\n   BITFIELD_ENTRY(25, 1, bool, TILE_SURFACE_ENABLE)\n   BITFIELD_ENTRY(26, 1, bool, TILE_COMPACT)\n   BITFIELD_ENTRY(31, 1, bool, ZRANGE_PRECISION)\nBITFIELD_END\n\nBITFIELD_BEG(DB_DEPTH_SIZE, uint32_t)\n   BITFIELD_ENTRY(0, 10, uint32_t, PITCH_TILE_MAX)\n   BITFIELD_ENTRY(10, 20, uint32_t, SLICE_TILE_MAX)\nBITFIELD_END\n\nBITFIELD_BEG(DB_DEPTH_VIEW, uint32_t)\n   BITFIELD_ENTRY(0, 11, uint32_t, SLICE_START)\n   BITFIELD_ENTRY(13, 11, uint32_t, SLICE_MAX)\nBITFIELD_END\n\nBITFIELD_BEG(DB_HTILE_SURFACE, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, HTILE_WIDTH)\n   BITFIELD_ENTRY(1, 1, bool, HTILE_HEIGHT)\n   BITFIELD_ENTRY(2, 1, bool, LINEAR)\n   BITFIELD_ENTRY(3, 1, bool, FULL_CACHE)\n   BITFIELD_ENTRY(4, 1, bool, HTILE_USES_PRELOAD_WIN)\n   BITFIELD_ENTRY(5, 1, bool, PRELOAD)\n   BITFIELD_ENTRY(6, 6, uint32_t, PREFETCH_WIDTH)\n   BITFIELD_ENTRY(12, 6, uint32_t, PREFETCH_HEIGHT)\nBITFIELD_END\n\nBITFIELD_BEG(DB_DEPTH_CLEAR, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, DEPTH_CLEAR)\nBITFIELD_END\n\n// This register controls depth and stencil tests.\nBITFIELD_BEG(DB_DEPTH_CONTROL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, STENCIL_ENABLE)\n   BITFIELD_ENTRY(1, 1, bool, Z_ENABLE)\n   BITFIELD_ENTRY(2, 1, bool, Z_WRITE_ENABLE)\n   BITFIELD_ENTRY(4, 3, REF_FUNC, ZFUNC)\n   BITFIELD_ENTRY(7, 1, bool, BACKFACE_ENABLE)\n   BITFIELD_ENTRY(8, 3, REF_FUNC, STENCILFUNC)\n   BITFIELD_ENTRY(11, 3, DB_STENCIL_FUNC, STENCILFAIL)\n   BITFIELD_ENTRY(14, 3, DB_STENCIL_FUNC, STENCILZPASS)\n   BITFIELD_ENTRY(17, 3, DB_STENCIL_FUNC, STENCILZFAIL)\n   BITFIELD_ENTRY(20, 3, REF_FUNC, STENCILFUNC_BF)\n   BITFIELD_ENTRY(23, 3, DB_STENCIL_FUNC, STENCILFAIL_BF)\n   BITFIELD_ENTRY(26, 3, DB_STENCIL_FUNC, STENCILZPASS_BF)\n   BITFIELD_ENTRY(29, 3, DB_STENCIL_FUNC, STENCILZFAIL_BF)\nBITFIELD_END\n\nBITFIELD_BEG(DB_RENDER_CONTROL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, DEPTH_CLEAR_ENABLE)\n   BITFIELD_ENTRY(1, 1, bool, STENCIL_CLEAR_ENABLE)\n   BITFIELD_ENTRY(2, 1, bool, DEPTH_COPY)\n   BITFIELD_ENTRY(3, 1, bool, STENCIL_COPY)\n   BITFIELD_ENTRY(4, 1, bool, RESUMMARIZE_ENABLE)\n   BITFIELD_ENTRY(5, 1, bool, STENCIL_COMPRESS_DISABLE)\n   BITFIELD_ENTRY(6, 1, bool, DEPTH_COMPRESS_DISABLE)\n   BITFIELD_ENTRY(7, 1, bool, COPY_CENTROID)\n   BITFIELD_ENTRY(8, 3, uint8_t, COPY_SAMPLE)\n   BITFIELD_ENTRY(11, 1, bool, ZPASS_INCREMENT_DISABLE)\n   BITFIELD_ENTRY(12, 1, bool, COLOR_DISABLE)\n   BITFIELD_ENTRY(13, 2, DB_Z_EXPORT, CONSERVATIVE_Z_EXPORT)\n   BITFIELD_ENTRY(15, 1, bool, PERFECT_ZPASS_COUNTS)\nBITFIELD_END\n\nBITFIELD_BEG(DB_RENDER_OVERRIDE, uint32_t)\n   BITFIELD_ENTRY(0, 2, DB_FORCE, FORCE_HIZ_ENABLE)\n   BITFIELD_ENTRY(2, 2, DB_FORCE, FORCE_HIS_ENABLE0)\n   BITFIELD_ENTRY(4, 2, DB_FORCE, FORCE_HIS_ENABLE1)\n   BITFIELD_ENTRY(6, 1, bool, FORCE_SHADER_Z_ORDER)\n   BITFIELD_ENTRY(7, 1, bool, FAST_Z_DISABLE)\n   BITFIELD_ENTRY(8, 1, bool, FAST_STENCIL_DISABLE)\n   BITFIELD_ENTRY(9, 1, bool, NOOP_CULL_DISABLE)\n   BITFIELD_ENTRY(10, 1, bool, FORCE_COLOR_KILL)\n   BITFIELD_ENTRY(11, 1, bool, FORCE_Z_READ)\n   BITFIELD_ENTRY(12, 1, bool, FORCE_STENCIL_READ)\n   BITFIELD_ENTRY(13, 1, DB_FORCE, FORCE_FULL_Z_RANGE)\n   BITFIELD_ENTRY(15, 1, bool, FORCE_QC_SMASK_CONFLICT)\n   BITFIELD_ENTRY(16, 1, bool, DISABLE_VIEWPORT_CLAMP)\n   BITFIELD_ENTRY(17, 1, bool, IGNORE_SC_ZRANGE)\nBITFIELD_END\n\nBITFIELD_BEG(DB_STENCILREFMASK, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, STENCILREF)\n   BITFIELD_ENTRY(8, 8, uint8_t, STENCILMASK)\n   BITFIELD_ENTRY(16, 8, uint8_t, STENCILWRITEMASK)\nBITFIELD_END\n\nBITFIELD_BEG(DB_STENCILREFMASK_BF, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, STENCILREF_BF)\n   BITFIELD_ENTRY(8, 8, uint8_t, STENCILMASK_BF)\n   BITFIELD_ENTRY(16, 8, uint8_t, STENCILWRITEMASK_BF)\nBITFIELD_END\n\nBITFIELD_BEG(DB_PREFETCH_LIMIT, uint32_t)\n   BITFIELD_ENTRY(0, 10, uint32_t, DEPTH_HEIGHT_TILE_MAX)\nBITFIELD_END\n\nBITFIELD_BEG(DB_PRELOAD_CONTROL, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, START_X)\n   BITFIELD_ENTRY(8, 8, uint8_t, START_Y)\n   BITFIELD_ENTRY(16, 8, uint8_t, MAX_X)\n   BITFIELD_ENTRY(24, 8, uint8_t, MAX_Y)\nBITFIELD_END\n\nBITFIELD_BEG(DB_SHADER_CONTROL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, Z_EXPORT_ENABLE)\n   BITFIELD_ENTRY(1, 1, bool, STENCIL_REF_EXPORT_ENABLE)\n   BITFIELD_ENTRY(4, 2, DB_Z_ORDER, Z_ORDER)\n   BITFIELD_ENTRY(6, 1, bool, KILL_ENABLE)\n   BITFIELD_ENTRY(7, 1, bool, COVERAGE_TO_MASK_ENABLE)\n   BITFIELD_ENTRY(8, 1, bool, MASK_EXPORT_ENABLE)\n   BITFIELD_ENTRY(9, 1, bool, DUAL_EXPORT_ENABLE)\n   BITFIELD_ENTRY(10, 1, bool, EXEC_ON_HIER_FAIL)\n   BITFIELD_ENTRY(11, 1, bool, EXEC_ON_NOOP)\n   BITFIELD_ENTRY(12, 1, bool, ALPHA_TO_MASK_DISABLE)\nBITFIELD_END\n\nBITFIELD_BEG(DB_SRESULTS_COMPARE_STATE0, uint32_t)\n   BITFIELD_ENTRY(0, 3, REF_FUNC, COMPAREFUNC0)\n   BITFIELD_ENTRY(4, 8, uint8_t, COMPAREVALUE0)\n   BITFIELD_ENTRY(12, 8, uint8_t, COMPAREMASK0)\n   BITFIELD_ENTRY(24, 1, bool, ENABLE0)\nBITFIELD_END\n\nBITFIELD_BEG(DB_SRESULTS_COMPARE_STATE1, uint32_t)\n   BITFIELD_ENTRY(0, 3, REF_FUNC, COMPAREFUNC1)\n   BITFIELD_ENTRY(4, 8, uint8_t, COMPAREVALUE1)\n   BITFIELD_ENTRY(12, 8, uint8_t, COMPAREMASK1)\n   BITFIELD_ENTRY(24, 1, bool, ENABLE1)\nBITFIELD_END\n\nBITFIELD_BEG(DB_STENCIL_CLEAR, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, CLEAR)\n   BITFIELD_ENTRY(16, 8, uint32_t, MIN)\nBITFIELD_END\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers_pa.h",
    "content": "#pragma once\n#include \"latte_enum_spi.h\"\n#include \"latte_enum_pa.h\"\n\n#include <common/bitfield.h>\n#include <cstdint>\n\nnamespace latte\n{\n\n// Clipper Control Bits\nBITFIELD_BEG(PA_CL_CLIP_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, UCP_ENA_0)\n   BITFIELD_ENTRY(1, 1, bool, UCP_ENA_1)\n   BITFIELD_ENTRY(2, 1, bool, UCP_ENA_2)\n   BITFIELD_ENTRY(3, 1, bool, UCP_ENA_3)\n   BITFIELD_ENTRY(4, 1, bool, UCP_ENA_4)\n   BITFIELD_ENTRY(5, 1, bool, UCP_ENA_5)\n   BITFIELD_ENTRY(13, 1, bool, PS_UCP_Y_SCALE_NEG)\n   BITFIELD_ENTRY(14, 2, PA_PS_UCP_MODE, PS_UCP_MODE)\n   BITFIELD_ENTRY(16, 1, bool, CLIP_DISABLE)\n   BITFIELD_ENTRY(17, 1, bool, UCP_CULL_ONLY_ENA)\n   BITFIELD_ENTRY(18, 1, bool, BOUNDARY_EDGE_FLAG_ENA)\n   BITFIELD_ENTRY(19, 1, bool, DX_CLIP_SPACE_DEF)\n   BITFIELD_ENTRY(20, 1, bool, DIS_CLIP_ERR_DETECT)\n   BITFIELD_ENTRY(21, 1, bool, VTX_KILL_OR)\n   BITFIELD_ENTRY(22, 1, bool, RASTERISER_DISABLE)\n   BITFIELD_ENTRY(24, 1, bool, DX_LINEAR_ATTR_CLIP_ENA)\n   BITFIELD_ENTRY(25, 1, bool, VTE_VPORT_PROVOKE_DISABLE)\n   BITFIELD_ENTRY(26, 1, bool, ZCLIP_NEAR_DISABLE)\n   BITFIELD_ENTRY(27, 1, bool, ZCLIP_FAR_DISABLE)\nBITFIELD_END\n\n// Horizontal Guard Band Clip Adjust Register\nBITFIELD_BEG(PA_CL_GB_HORZ_CLIP_ADJ, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, DATA_REGISTER)\nBITFIELD_END\n\n// Horizontal Guard Band Discard Adjust Register\nBITFIELD_BEG(PA_CL_GB_HORZ_DISC_ADJ, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, DATA_REGISTER)\nBITFIELD_END\n\n// Vertical Guard Band Clip Adjust Register\nBITFIELD_BEG(PA_CL_GB_VERT_CLIP_ADJ, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, DATA_REGISTER)\nBITFIELD_END\n\n// Vertical Guard Band Discard Adjust Register\nBITFIELD_BEG(PA_CL_GB_VERT_DISC_ADJ, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, DATA_REGISTER)\nBITFIELD_END\n\nBITFIELD_BEG(PA_CL_NANINF_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, VTE_XY_INF_DISCARD)\n   BITFIELD_ENTRY(1, 1, bool, VTE_Z_INF_DISCARD)\n   BITFIELD_ENTRY(2, 1, bool, VTE_W_INF_DISCARD)\n   BITFIELD_ENTRY(3, 1, bool, VTE_0XNANINF_IS_0)\n   BITFIELD_ENTRY(4, 1, bool, VTE_XY_NAN_RETAIN)\n   BITFIELD_ENTRY(5, 1, bool, VTE_Z_NAN_RETAIN)\n   BITFIELD_ENTRY(6, 1, bool, VTE_W_NAN_RETAIN)\n   BITFIELD_ENTRY(7, 1, bool, VTE_W_RECIP_NAN_IS_0)\n   BITFIELD_ENTRY(8, 1, bool, VS_XY_NAN_TO_INF)\n   BITFIELD_ENTRY(9, 1, bool, VS_XY_INF_RETAIN)\n   BITFIELD_ENTRY(10, 1, bool, VS_Z_NAN_TO_INF)\n   BITFIELD_ENTRY(11, 1, bool, VS_Z_INF_RETAIN)\n   BITFIELD_ENTRY(12, 1, bool, VS_W_NAN_TO_INF)\n   BITFIELD_ENTRY(13, 1, bool, VS_W_INF_RETAIN)\n   BITFIELD_ENTRY(14, 1, bool, VS_CLIP_DIST_INF_DISCARD)\n   BITFIELD_ENTRY(20, 1, bool, VTE_NO_OUTPUT_NEG_0)\nBITFIELD_END\n\n// Point Sprite X Radius Expansion\nBITFIELD_BEG(PA_CL_POINT_X_RAD, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, DATA_REGISTER)\nBITFIELD_END\n\n// Point Sprite Y Radius Expansion\nBITFIELD_BEG(PA_CL_POINT_Y_RAD, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, DATA_REGISTER)\nBITFIELD_END\n\n// Point Sprite Constant Size\nBITFIELD_BEG(PA_CL_POINT_SIZE, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, DATA_REGISTER)\nBITFIELD_END\n\n// Point Sprite Culling Radius Expansion\nBITFIELD_BEG(PA_CL_POINT_CULL_RAD, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, DATA_REGISTER)\nBITFIELD_END\n\n// Viewport Transform X Scale Factor\nBITFIELD_BEG(PA_CL_VPORT_XSCALE_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, VPORT_XSCALE)\nBITFIELD_END\n\n// Viewport Transform X Offset\nBITFIELD_BEG(PA_CL_VPORT_XOFFSET_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, VPORT_XOFFSET)\nBITFIELD_END\n\n// Viewport Transform Y Scale Factor\nBITFIELD_BEG(PA_CL_VPORT_YSCALE_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, VPORT_YSCALE)\nBITFIELD_END\n\n// Viewport Transform Y Offset\nBITFIELD_BEG(PA_CL_VPORT_YOFFSET_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, VPORT_YOFFSET)\nBITFIELD_END\n\n// Viewport Transform Z Scale Factor\nBITFIELD_BEG(PA_CL_VPORT_ZSCALE_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, VPORT_ZSCALE)\nBITFIELD_END\n\n// Viewport Transform Z Offset\nBITFIELD_BEG(PA_CL_VPORT_ZOFFSET_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, VPORT_ZOFFSET)\nBITFIELD_END\n\n// Vertex Shader Output Control\nBITFIELD_BEG(PA_CL_VS_OUT_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, CLIP_DIST_ENA_0)\n   BITFIELD_ENTRY(1, 1, bool, CLIP_DIST_ENA_1)\n   BITFIELD_ENTRY(2, 1, bool, CLIP_DIST_ENA_2)\n   BITFIELD_ENTRY(3, 1, bool, CLIP_DIST_ENA_3)\n   BITFIELD_ENTRY(4, 1, bool, CLIP_DIST_ENA_4)\n   BITFIELD_ENTRY(5, 1, bool, CLIP_DIST_ENA_5)\n   BITFIELD_ENTRY(6, 1, bool, CLIP_DIST_ENA_6)\n   BITFIELD_ENTRY(7, 1, bool, CLIP_DIST_ENA_7)\n   BITFIELD_ENTRY(8, 1, bool, CULL_DIST_ENA_0)\n   BITFIELD_ENTRY(9, 1, bool, CULL_DIST_ENA_1)\n   BITFIELD_ENTRY(10, 1, bool, CULL_DIST_ENA_2)\n   BITFIELD_ENTRY(11, 1, bool, CULL_DIST_ENA_3)\n   BITFIELD_ENTRY(12, 1, bool, CULL_DIST_ENA_4)\n   BITFIELD_ENTRY(13, 1, bool, CULL_DIST_ENA_5)\n   BITFIELD_ENTRY(14, 1, bool, CULL_DIST_ENA_6)\n   BITFIELD_ENTRY(15, 1, bool, CULL_DIST_ENA_7)\n   BITFIELD_ENTRY(16, 1, bool, USE_VTX_POINT_SIZE)\n   BITFIELD_ENTRY(17, 1, bool, USE_VTX_EDGE_FLAG)\n   BITFIELD_ENTRY(18, 1, bool, USE_VTX_RENDER_TARGET_INDX)\n   BITFIELD_ENTRY(19, 1, bool, USE_VTX_VIEWPORT_INDX)\n   BITFIELD_ENTRY(20, 1, bool, USE_VTX_KILL_FLAG)\n   BITFIELD_ENTRY(21, 1, bool, VS_OUT_MISC_VEC_ENA)\n   BITFIELD_ENTRY(22, 1, bool, VS_OUT_CCDIST0_VEC_ENA)\n   BITFIELD_ENTRY(23, 1, bool, VS_OUT_CCDIST1_VEC_ENA)\n   BITFIELD_ENTRY(24, 1, bool, VS_OUT_MISC_SIDE_BUS_ENA)\n   BITFIELD_ENTRY(25, 1, bool, USE_VTX_GS_CUT_FLAG)\nBITFIELD_END\n\n// Viewport Transform Engine Control\nBITFIELD_BEG(PA_CL_VTE_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, VPORT_X_SCALE_ENA)\n   BITFIELD_ENTRY(1, 1, bool, VPORT_X_OFFSET_ENA)\n   BITFIELD_ENTRY(2, 1, bool, VPORT_Y_SCALE_ENA)\n   BITFIELD_ENTRY(3, 1, bool, VPORT_Y_OFFSET_ENA)\n   BITFIELD_ENTRY(4, 1, bool, VPORT_Z_SCALE_ENA)\n   BITFIELD_ENTRY(5, 1, bool, VPORT_Z_OFFSET_ENA)\n   BITFIELD_ENTRY(8, 1, bool, VTX_XY_FMT)\n   BITFIELD_ENTRY(9, 1, bool, VTX_Z_FMT)\n   BITFIELD_ENTRY(10, 1, bool, VTX_W0_FMT)\n   BITFIELD_ENTRY(11, 1, bool, PERFCOUNTER_REF)\nBITFIELD_END\n\n// Multisample AA Mask\nBITFIELD_BEG(PA_SC_AA_MASK, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, AA_MASK_ULC)\n   BITFIELD_ENTRY(8, 8, uint8_t, AA_MASK_URC)\n   BITFIELD_ENTRY(16, 8, uint8_t, AA_MASK_LLC)\n   BITFIELD_ENTRY(24, 8, uint8_t, AA_MASK_LRC)\nBITFIELD_END\n\n// OpenGL Clip boolean function\nBITFIELD_BEG(PA_SC_CLIPRECT_RULE, uint32_t)\n   BITFIELD_ENTRY(0, 16, uint16_t, CLIP_RULE)\nBITFIELD_END\n\n// Line Drawing Control\nBITFIELD_BEG(PA_SC_LINE_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, BRES_CNTL)\n   BITFIELD_ENTRY(8, 1, bool, USE_BRES_CNTL)\n   BITFIELD_ENTRY(9, 1, bool, EXPAND_LINE_WIDTH)\n   BITFIELD_ENTRY(10, 1, bool, LAST_PIXEL)\nBITFIELD_END\n\n// Line Stipple Control\nBITFIELD_BEG(PA_SC_LINE_STIPPLE, uint32_t)\n   BITFIELD_ENTRY(0, 16, uint16_t, LINE_PATTERN)\n   BITFIELD_ENTRY(16, 8, uint8_t, REPEAT_COUNT)\n   BITFIELD_ENTRY(28, 1, bool, PATTERN_BIT_ORDER)\n   BITFIELD_ENTRY(29, 2, uint8_t, AUTO_RESET_CNTL)\nBITFIELD_END\n\n// SC Mode Control Register for Various Enables\nBITFIELD_BEG(PA_SC_MODE_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, MSAA_ENABLE)\n   BITFIELD_ENTRY(1, 1, bool, CLIPRECT_ENABLE)\n   BITFIELD_ENTRY(2, 1, bool, LINE_STIPPLE_ENABLE)\n   BITFIELD_ENTRY(3, 1, bool, MULTI_CHIP_PRIM_DISCARD_ENABLE)\n   BITFIELD_ENTRY(4, 1, bool, WALK_ORDER_ENABLE)\n   BITFIELD_ENTRY(5, 1, bool, HALVE_DETAIL_SAMPLE_PERF)\n   BITFIELD_ENTRY(6, 1, bool, WALK_SIZE)\n   BITFIELD_ENTRY(7, 1, bool, WALK_ALIGNMENT)\n   BITFIELD_ENTRY(8, 1, bool, WALK_ALIGN8_PRIM_FITS_ST)\n   BITFIELD_ENTRY(9, 1, bool, TILE_COVER_NO_SCISSOR)\n   BITFIELD_ENTRY(10, 1, bool, KILL_PIX_POST_HI_Z)\n   BITFIELD_ENTRY(11, 1, bool, KILL_PIX_POST_DETAIL_MASK)\n   BITFIELD_ENTRY(12, 1, bool, MULTI_CHIP_SUPERTILE_ENABLE)\n   BITFIELD_ENTRY(13, 1, bool, TILE_COVER_DISABLE)\n   BITFIELD_ENTRY(14, 1, bool, FORCE_EOV_CNTDWN_ENABLE)\n   BITFIELD_ENTRY(15, 1, bool, FORCE_EOV_TILE_ENABLE)\n   BITFIELD_ENTRY(16, 1, bool, FORCE_EOV_REZ_ENABLE)\n   BITFIELD_ENTRY(17, 1, bool, PS_ITER_SAMPLE)\nBITFIELD_END\n\n// Multi-Pass Pixel Shader Control Register\nBITFIELD_BEG(PA_SC_MPASS_PS_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 20, uint32_t, MPASS_PIX_VEC_PER_PASS)\n   BITFIELD_ENTRY(31, 1, bool, MPASS_PS_ENA)\nBITFIELD_END\n\n// Screen Scissor rectangle specification\nBITFIELD_BEG(PA_SC_SCREEN_SCISSOR_BR, uint32_t)\n   BITFIELD_ENTRY(0, 14, uint32_t, BR_X)\n   BITFIELD_ENTRY(16, 14, uint32_t, BR_Y)\nBITFIELD_END\n\n// Screen Scissor rectangle specification\nBITFIELD_BEG(PA_SC_SCREEN_SCISSOR_TL, uint32_t)\n   BITFIELD_ENTRY(0, 14, uint32_t, TL_X)\n   BITFIELD_ENTRY(16, 14, uint32_t, TL_Y)\nBITFIELD_END\n\n// Generic Scissor rectangle specification\nBITFIELD_BEG(PA_SC_GENERIC_SCISSOR_BR, uint32_t)\n   BITFIELD_ENTRY(0, 14, uint32_t, BR_X)\n   BITFIELD_ENTRY(16, 14, uint32_t, BR_Y)\nBITFIELD_END\n\n// Generic Scissor rectangle specification\nBITFIELD_BEG(PA_SC_GENERIC_SCISSOR_TL, uint32_t)\n   BITFIELD_ENTRY(0, 14, uint32_t, TL_X)\n   BITFIELD_ENTRY(16, 14, uint32_t, TL_Y)\n   BITFIELD_ENTRY(31, 1, bool, WINDOW_OFFSET_DISABLE)\nBITFIELD_END\n\n// WGF ViewportId Scissor rectangle specification (0-15).\nBITFIELD_BEG(PA_SC_VPORT_SCISSOR_0_BR, uint32_t)\n   BITFIELD_ENTRY(0, 14, uint32_t, BR_X)\n   BITFIELD_ENTRY(16, 14, uint32_t, BR_Y)\nBITFIELD_END\n\n// WGF ViewportId Scissor rectangle specification (0-15).\nBITFIELD_BEG(PA_SC_VPORT_SCISSOR_0_TL, uint32_t)\n   BITFIELD_ENTRY(0, 14, uint32_t, TL_X)\n   BITFIELD_ENTRY(16, 14, uint32_t, TL_Y)\n   BITFIELD_ENTRY(31, 1, bool, WINDOW_OFFSET_DISABLE)\nBITFIELD_END\n\n// Viewport Transform Z Min Clamp\nBITFIELD_BEG(PA_SC_VPORT_ZMIN_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, VPORT_ZMIN)\nBITFIELD_END\n\n// Viewport Transform Z Max Clamp\nBITFIELD_BEG(PA_SC_VPORT_ZMAX_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, VPORT_ZMAX)\nBITFIELD_END\n\n// Offset from screen coords to window coords.\nBITFIELD_BEG(PA_SC_WINDOW_OFFSET, uint32_t)\n   BITFIELD_ENTRY(0, 14, uint32_t, WINDOW_X_OFFSET)\n   BITFIELD_ENTRY(16, 14, uint32_t, WINDOW_Y_OFFSET)\nBITFIELD_END\n\n// Window Scissor rectangle specification.\nBITFIELD_BEG(PA_SC_WINDOW_SCISSOR_TL, uint32_t)\n   BITFIELD_ENTRY(0, 14, uint32_t, TL_X)\n   BITFIELD_ENTRY(16, 14, uint32_t, TL_Y)\n   BITFIELD_ENTRY(31, 1, bool, WINDOW_OFFSET_DISABLE)\nBITFIELD_END\n\n// Window Scissor rectangle specification.\nBITFIELD_BEG(PA_SC_WINDOW_SCISSOR_BR, uint32_t)\n   BITFIELD_ENTRY(0, 14, uint32_t, BR_X)\n   BITFIELD_ENTRY(16, 14, uint32_t, BR_Y)\nBITFIELD_END\n\n// Line control\nBITFIELD_BEG(PA_SU_LINE_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 16, uint32_t, WIDTH) // 16.0 fixed\nBITFIELD_END\n\n// Specifies maximum and minimum point & sprite sizes for per vertex size specification\nBITFIELD_BEG(PA_SU_POINT_MINMAX, uint32_t)\n   BITFIELD_ENTRY(0, 16, uint32_t, MIN_SIZE) // 12.4 fixed\n   BITFIELD_ENTRY(16, 16, uint32_t, MAX_SIZE) // 12.4 fixed\nBITFIELD_END\n\n// Dimensions for Points\nBITFIELD_BEG(PA_SU_POINT_SIZE, uint32_t)\n   BITFIELD_ENTRY(0, 16, uint32_t, HEIGHT) // 12.4 fixed\n   BITFIELD_ENTRY(16, 16, uint32_t, WIDTH) // 12.4 fixed\nBITFIELD_END\n\n// Clamp Value for Polygon Offset\nBITFIELD_BEG(PA_SU_POLY_OFFSET_CLAMP, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, CLAMP)\nBITFIELD_END\n\n// Back-Facing Polygon Offset Scale\nBITFIELD_BEG(PA_SU_POLY_OFFSET_BACK_SCALE, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, SCALE)\nBITFIELD_END\n\n// Back-Facing Polygon Offset Scale\nBITFIELD_BEG(PA_SU_POLY_OFFSET_BACK_OFFSET, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, OFFSET)\nBITFIELD_END\n\n// Front-Facing Polygon Offset Scale\nBITFIELD_BEG(PA_SU_POLY_OFFSET_FRONT_SCALE, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, SCALE)\nBITFIELD_END\n\n// Front-Facing Polygon Offset Scale\nBITFIELD_BEG(PA_SU_POLY_OFFSET_FRONT_OFFSET, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, OFFSET)\nBITFIELD_END\n\n// SU/SC Controls for Facedness Culling, Polymode, Polygon Offset, and various Enables\nBITFIELD_BEG(PA_SU_SC_MODE_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, CULL_FRONT)\n   BITFIELD_ENTRY(1, 1, bool, CULL_BACK)\n   BITFIELD_ENTRY(2, 1, PA_FACE, FACE)\n   BITFIELD_ENTRY(3, 2, uint32_t, POLY_MODE)\n   BITFIELD_ENTRY(5, 3, PA_PTYPE, POLYMODE_FRONT_PTYPE)\n   BITFIELD_ENTRY(8, 3, PA_PTYPE, POLYMODE_BACK_PTYPE)\n   BITFIELD_ENTRY(11, 1, bool, POLY_OFFSET_FRONT_ENABLE)\n   BITFIELD_ENTRY(12, 1, bool, POLY_OFFSET_BACK_ENABLE)\n   BITFIELD_ENTRY(13, 1, bool, POLY_OFFSET_PARA_ENABLE)\n   BITFIELD_ENTRY(16, 1, bool, VTX_WINDOW_OFFSET_ENABLE)\n   BITFIELD_ENTRY(19, 1, bool, PROVOKING_VTX_LAST)\n   BITFIELD_ENTRY(20, 1, bool, PERSP_CORR_DIS)\n   BITFIELD_ENTRY(21, 1, bool, MULTI_PRIM_IB_ENA)\nBITFIELD_END\n\n// Polygon Offset Depth Buffer Format Control\nBITFIELD_BEG(PA_SU_POLY_OFFSET_DB_FMT_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, POLY_OFFSET_NEG_NUM_DB_BITS)\n   BITFIELD_ENTRY(8, 1, bool, POLY_OFFSET_DB_IS_FLOAT_FMT)\nBITFIELD_END\n\n// Miscellaneous SU Control\nBITFIELD_BEG(PA_SU_VTX_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 1, PA_SU_VTX_CNTL_PIX_CENTER, PIX_CENTER)\n   BITFIELD_ENTRY(1, 2, PA_SU_VTX_CNTL_ROUND_MODE, ROUND_MODE)\n   BITFIELD_ENTRY(3, 3, PA_SU_VTX_CNTL_QUANT_MODE, QUANT_MODE)\nBITFIELD_END\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers_spi.h",
    "content": "#pragma once\n#include \"latte_enum_spi.h\"\n\n#include <common/bitfield.h>\n#include <cstdint>\n\nnamespace latte\n{\n\nBITFIELD_BEG(SPI_CONFIG_CNTL_1, uint32_t)\n   BITFIELD_ENTRY(0, 4, uint8_t, VTX_DONE_DELAY)\n   BITFIELD_ENTRY(4, 1, bool, INTERP_ONE_PRIM_PER_ROW)\nBITFIELD_END\n\nBITFIELD_BEG(SPI_FOG_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, PASS_FOG_THROUGH_PS)\n   BITFIELD_ENTRY(1, 2, SPI_FOG_FUNC, PIXEL_FOG_FUNC)\n   BITFIELD_ENTRY(3, 1, SPI_FOG_SRC_SEL, PIXEL_FOG_SRC_SEL)\n   BITFIELD_ENTRY(4, 1, bool, VS_FOG_CLAMP_DISABLE)\nBITFIELD_END\n\nBITFIELD_BEG(SPI_INTERP_CONTROL_0, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, FLAT_SHADE_ENA)\n   BITFIELD_ENTRY(1, 1, bool, PNT_SPRITE_ENA)\n   BITFIELD_ENTRY(2, 3, SPI_PNT_SPRITE_SEL, PNT_SPRITE_OVRD_X)\n   BITFIELD_ENTRY(5, 3, SPI_PNT_SPRITE_SEL, PNT_SPRITE_OVRD_Y)\n   BITFIELD_ENTRY(8, 3, SPI_PNT_SPRITE_SEL, PNT_SPRITE_OVRD_Z)\n   BITFIELD_ENTRY(11, 3, SPI_PNT_SPRITE_SEL, PNT_SPRITE_OVRD_W)\n   BITFIELD_ENTRY(14, 1, bool, PNT_SPRITE_TOP_1)\nBITFIELD_END\n\nBITFIELD_BEG(SPI_INPUT_Z, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, PROVIDE_Z_TO_SPI)\nBITFIELD_END\n\n// Interpolator control settings\nBITFIELD_BEG(SPI_PS_IN_CONTROL_0, uint32_t)\n   BITFIELD_ENTRY(0, 6, uint32_t, NUM_INTERP)\n   BITFIELD_ENTRY(8, 1, bool, POSITION_ENA)\n   BITFIELD_ENTRY(9, 1, bool, POSITION_CENTROID)\n   BITFIELD_ENTRY(10, 5, uint32_t, POSITION_ADDR)\n   BITFIELD_ENTRY(15, 4, uint32_t, PARAM_GEN)\n   BITFIELD_ENTRY(19, 7, uint32_t, PARAM_GEN_ADDR)\n   BITFIELD_ENTRY(26, 2, SPI_BARYC_CNTL, BARYC_SAMPLE_CNTL)\n   BITFIELD_ENTRY(28, 1, bool, PERSP_GRADIENT_ENA)\n   BITFIELD_ENTRY(29, 1, bool, LINEAR_GRADIENT_ENA)\n   BITFIELD_ENTRY(30, 1, bool, POSITION_SAMPLE)\n   BITFIELD_ENTRY(31, 1, bool, BARYC_AT_SAMPLE_ENA)\nBITFIELD_END\n\n// Interpolator control settings\nBITFIELD_BEG(SPI_PS_IN_CONTROL_1, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, GEN_INDEX_PIX)\n   BITFIELD_ENTRY(1, 7, uint32_t, GEN_INDEX_PIX_ADDR)\n   BITFIELD_ENTRY(8, 1, bool, FRONT_FACE_ENA)\n   BITFIELD_ENTRY(9, 2, uint32_t, FRONT_FACE_CHAN)\n   BITFIELD_ENTRY(11, 1, bool, FRONT_FACE_ALL_BITS)\n   BITFIELD_ENTRY(12, 5, uint32_t, FRONT_FACE_ADDR)\n   BITFIELD_ENTRY(17, 7, uint32_t, FOG_ADDR)\n   BITFIELD_ENTRY(24, 1, bool, FIXED_PT_POSITION_ENA)\n   BITFIELD_ENTRY(25, 5, uint32_t, FIXED_PT_POSITION_ADDR)\n   BITFIELD_ENTRY(30, 1, bool, POSITION_ULC)\nBITFIELD_END\n\n// PS interpolator setttings for parameter N\nBITFIELD_BEG(SPI_PS_INPUT_CNTL_N, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, SEMANTIC)\n   BITFIELD_ENTRY(8, 2, uint32_t, DEFAULT_VAL)\n   BITFIELD_ENTRY(10, 1, bool, FLAT_SHADE)\n   BITFIELD_ENTRY(11, 1, bool, SEL_CENTROID)\n   BITFIELD_ENTRY(12, 1, bool, SEL_LINEAR)\n   BITFIELD_ENTRY(13, 4, uint32_t, CYL_WRAP)\n   BITFIELD_ENTRY(17, 1, bool, PT_SPRITE_TEX)\n   BITFIELD_ENTRY(18, 1, bool, SEL_SAMPLE)\nBITFIELD_END\n\n// Vertex Shader output configuration\nBITFIELD_BEG(SPI_VS_OUT_CONFIG, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, VS_PER_COMPONENT)\n   BITFIELD_ENTRY(1, 5, uint32_t, VS_EXPORT_COUNT)\n   BITFIELD_ENTRY(8, 1, bool, VS_EXPORTS_FOG)\n   BITFIELD_ENTRY(9, 5, uint32_t, VS_OUT_FOG_VEC_ADDR)\nBITFIELD_END\n\n// Vertex Shader output semantic mapping\nBITFIELD_BEG(SPI_VS_OUT_ID_N, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint8_t, SEMANTIC_0)\n   BITFIELD_ENTRY(8, 8, uint8_t, SEMANTIC_1)\n   BITFIELD_ENTRY(16, 8, uint8_t, SEMANTIC_2)\n   BITFIELD_ENTRY(24, 8, uint8_t, SEMANTIC_3)\nBITFIELD_END\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers_sq.h",
    "content": "#pragma once\n#include \"latte_enum_common.h\"\n#include \"latte_enum_sq.h\"\n\n#include <common/bitfield.h>\n#include <common/fixed.h>\n#include <cstdint>\n\nnamespace latte\n{\n\n// ALU Constant store data for use in DX9 mode (DX10 mode uses the constant-cache\n// instead and this constant-file is not available).\n// Constants 0-225 are reserved for pixel shader\n// Constants 256-511 are reserved for vertex shader\nBITFIELD_BEG(SQ_ALU_CONSTANT0_0, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, X)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_ALU_CONSTANT1_0, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, Y)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_ALU_CONSTANT2_0, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, Z)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_ALU_CONSTANT3_0, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, W)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_CONFIG, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, VC_ENABLE)\n   BITFIELD_ENTRY(1, 1, bool, EXPORT_SRC_C)\n   BITFIELD_ENTRY(2, 1, bool, DX9_CONSTS)\n   BITFIELD_ENTRY(3, 1, bool, ALU_INST_PREFER_VECTOR)\n   BITFIELD_ENTRY(4, 1, bool, DX10_CLAMP)\n   BITFIELD_ENTRY(5, 1, bool, ALU_PREFER_ONE_WATERFALL)\n   BITFIELD_ENTRY(6, 1, bool, ALU_MAX_ONE_WATERFALL)\n   BITFIELD_ENTRY(8, 2, uint32_t, CLAUSE_SEQ_PRIO)\n   BITFIELD_ENTRY(10, 1, bool, NO_GPR_CLAMP)\n   BITFIELD_ENTRY(11, 1, bool, EN_TEX_SKEW)\n   BITFIELD_ENTRY(24, 2, uint32_t, PS_PRIO)\n   BITFIELD_ENTRY(26, 2, uint32_t, VS_PRIO)\n   BITFIELD_ENTRY(28, 2, uint32_t, GS_PRIO)\n   BITFIELD_ENTRY(30, 2, uint32_t, ES_PRIO)\nBITFIELD_END\n\n// Space allocated to a single GS output vertex in GS Temp Buffer. This defines the\n// size of a single vertex output by the GS.Multiple vertices can be output so long\n// as the total output size does not exceed SQ_GSVS_RING_ITEMSIZE.\nBITFIELD_BEG(SQ_GS_VERT_ITEMSIZE, uint32_t)\n   BITFIELD_ENTRY(0, 15, uint32_t, ITEMSIZE)\nBITFIELD_END\n\n// Defines how GPR space is divided among the 4 thread types.\nBITFIELD_BEG(SQ_GPR_RESOURCE_MGMT_1, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, NUM_PS_GPRS)\n   BITFIELD_ENTRY(16, 8, uint32_t, NUM_VS_GPRS)\n   BITFIELD_ENTRY(27, 1, bool, DYN_GPR_ENABLE)\n   BITFIELD_ENTRY(28, 4, uint32_t, NUM_CLAUSE_TEMP_GPRS)\nBITFIELD_END\n\n// Defines how GPR space is divided among the 4 thread types.\nBITFIELD_BEG(SQ_GPR_RESOURCE_MGMT_2, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, NUM_GS_GPRS)\n   BITFIELD_ENTRY(16, 8, uint32_t, NUM_ES_GPRS)\nBITFIELD_END\n\n// Used for SQ_CF_INST_LOOP and SQ_CF_INST_LOOP_NO_AL\nBITFIELD_BEG(SQ_LOOP_CONST_DX9_0, uint32_t)\n   BITFIELD_ENTRY(0, 12, uint32_t, COUNT)\n   BITFIELD_ENTRY(12, 12, uint32_t, INIT)\n   BITFIELD_ENTRY(24, 8, uint32_t, INC)\nBITFIELD_END\n\n// Used for SQ_CF_INST_LOOP_DX10\nBITFIELD_BEG(SQ_LOOP_CONST_DX10_0, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, COUNT)\nBITFIELD_END\n\n// Defines how thread stack space is divided among the thread types\nBITFIELD_BEG(SQ_STACK_RESOURCE_MGMT_1, uint32_t)\n   BITFIELD_ENTRY(0, 12, uint32_t, NUM_PS_STACK_ENTRIES)\n   BITFIELD_ENTRY(16, 12, uint32_t, NUM_VS_STACK_ENTRIES)\nBITFIELD_END\n\n// Defines how thread stack space is divided among the thread types\nBITFIELD_BEG(SQ_STACK_RESOURCE_MGMT_2, uint32_t)\n   BITFIELD_ENTRY(0, 12, uint32_t, NUM_GS_STACK_ENTRIES)\n   BITFIELD_ENTRY(16, 12, uint32_t, NUM_ES_STACK_ENTRIES)\nBITFIELD_END\n\n// Defines how thread space is divided among the thread types\nBITFIELD_BEG(SQ_THREAD_RESOURCE_MGMT, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, NUM_PS_THREADS)\n   BITFIELD_ENTRY(8, 8, uint32_t, NUM_VS_THREADS)\n   BITFIELD_ENTRY(16, 8, uint32_t, NUM_GS_THREADS)\n   BITFIELD_ENTRY(24, 8, uint32_t, NUM_ES_THREADS)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_VTX_CONSTANT_WORD0_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, BASE_ADDRESS)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_VTX_CONSTANT_WORD1_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, SIZE)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_VTX_CONSTANT_WORD2_N, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, BASE_ADDRESS_HI)\n   BITFIELD_ENTRY(8, 11, uint32_t, STRIDE)\n   BITFIELD_ENTRY(19, 1, SQ_VTX_CLAMP, CLAMP_X)\n   BITFIELD_ENTRY(20, 6, SQ_DATA_FORMAT, DATA_FORMAT)\n   BITFIELD_ENTRY(26, 2, SQ_NUM_FORMAT, NUM_FORMAT_ALL)\n   BITFIELD_ENTRY(28, 1, SQ_FORMAT_COMP, FORMAT_COMP_ALL)\n   BITFIELD_ENTRY(29, 1, SQ_SRF_MODE, SRF_MODE_ALL)\n   BITFIELD_ENTRY(30, 2, SQ_ENDIAN, ENDIAN_SWAP)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_VTX_CONSTANT_WORD3_N, uint32_t)\n   BITFIELD_ENTRY(0, 2, uint32_t, MEM_REQUEST_SIZE)\n   BITFIELD_ENTRY(2, 1, bool, UNCACHED)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_VTX_CONSTANT_WORD6_N, uint32_t)\n   BITFIELD_ENTRY(30, 2, SQ_TEX_VTX_TYPE, TYPE)\nBITFIELD_END\n\n// Vertex fetch base location\nBITFIELD_BEG(SQ_VTX_BASE_VTX_LOC, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, OFFSET)\nBITFIELD_END\n\n// Vertex fetch instance offset\nBITFIELD_BEG(SQ_VTX_START_INST_LOC, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, OFFSET)\nBITFIELD_END\n\n// Resource requirements to run the GS program\nBITFIELD_BEG(SQ_PGM_RESOURCES_GS, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, NUM_GPRS)\n   BITFIELD_ENTRY(8, 8, uint32_t, STACK_SIZE)\n   BITFIELD_ENTRY(21, 1, bool, DX10_CLAMP)\n   BITFIELD_ENTRY(22, 1, bool, PRIME_CACHE_PGM_EN)\n   BITFIELD_ENTRY(23, 1, bool, PRIME_CACHE_ON_DRAW)\n   BITFIELD_ENTRY(24, 3, uint32_t, FETCH_CACHE_LINES)\n   BITFIELD_ENTRY(28, 1, bool, UNCACHED_FIRST_INST)\n   BITFIELD_ENTRY(29, 1, bool, PRIME_CACHE_ENABLE)\n   BITFIELD_ENTRY(30, 1, bool, PRIME_CACHE_ON_CONST)\nBITFIELD_END\n\n// Resource requirements to run the Vertex Shader program\nBITFIELD_BEG(SQ_PGM_RESOURCES_VS, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, NUM_GPRS)\n   BITFIELD_ENTRY(8, 8, uint32_t, STACK_SIZE)\n   BITFIELD_ENTRY(21, 1, bool, DX10_CLAMP)\n   BITFIELD_ENTRY(22, 1, bool, PRIME_CACHE_PGM_EN)\n   BITFIELD_ENTRY(23, 1, bool, PRIME_CACHE_ON_DRAW)\n   BITFIELD_ENTRY(24, 3, uint32_t, FETCH_CACHE_LINES)\n   BITFIELD_ENTRY(28, 1, bool, UNCACHED_FIRST_INST)\n   BITFIELD_ENTRY(29, 1, bool, PRIME_CACHE_ENABLE)\n   BITFIELD_ENTRY(30, 1, bool, PRIME_CACHE_ON_CONST)\nBITFIELD_END\n\n// Resource requirements to run the Pixel Shader program\nBITFIELD_BEG(SQ_PGM_RESOURCES_PS, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, NUM_GPRS)\n   BITFIELD_ENTRY(8, 8, uint32_t, STACK_SIZE)\n   BITFIELD_ENTRY(21, 1, bool, DX10_CLAMP)\n   BITFIELD_ENTRY(22, 1, bool, PRIME_CACHE_PGM_EN)\n   BITFIELD_ENTRY(23, 1, bool, PRIME_CACHE_ON_DRAW)\n   BITFIELD_ENTRY(24, 3, uint32_t, FETCH_CACHE_LINES)\n   BITFIELD_ENTRY(28, 1, bool, UNCACHED_FIRST_INST)\n   BITFIELD_ENTRY(29, 1, bool, PRIME_CACHE_ENABLE)\n   BITFIELD_ENTRY(30, 1, bool, PRIME_CACHE_ON_CONST)\n   BITFIELD_ENTRY(31, 1, bool, CLAMP_CONSTS)\nBITFIELD_END\n\n// Resource requirements to run the Fetch Shader program\nBITFIELD_BEG(SQ_PGM_RESOURCES_FS, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, NUM_GPRS)\n   BITFIELD_ENTRY(8, 8, uint32_t, STACK_SIZE)\n   BITFIELD_ENTRY(21, 1, bool, DX10_CLAMP)\nBITFIELD_END\n\n// Memory address of the (256-byte aligned) first CF instruction of the shader code for the fetch shader(FS)\nBITFIELD_BEG(SQ_PGM_START_FS, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, PGM_START)\nBITFIELD_END\n\n// Size >> 3\nBITFIELD_BEG(SQ_PGM_SIZE_FS, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, PGM_SIZE)\nBITFIELD_END\n\n// Offset >> 3\nBITFIELD_BEG(SQ_PGM_CF_OFFSET_FS, uint32_t)\nBITFIELD_ENTRY(0, 32, uint32_t, PGM_OFFSET)\nBITFIELD_END\n\n// Memory address of the (256-byte aligned) first CF instruction of the shader code for the fetch shader(FS)\nBITFIELD_BEG(SQ_PGM_START_ES, uint32_t)\nBITFIELD_ENTRY(0, 32, uint32_t, PGM_START)\nBITFIELD_END\n\n// Size >> 3\nBITFIELD_BEG(SQ_PGM_SIZE_ES, uint32_t)\nBITFIELD_ENTRY(0, 32, uint32_t, PGM_SIZE)\nBITFIELD_END\n\n// Offset >> 3\nBITFIELD_BEG(SQ_PGM_CF_OFFSET_ES, uint32_t)\nBITFIELD_ENTRY(0, 32, uint32_t, PGM_OFFSET)\nBITFIELD_END\n\n// Memory address of the (256-byte aligned) first CF instruction of the shader code for the geometry shader(GS)\nBITFIELD_BEG(SQ_PGM_START_GS, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, PGM_START)\nBITFIELD_END\n\n// Size >> 3\nBITFIELD_BEG(SQ_PGM_SIZE_GS, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, PGM_SIZE)\nBITFIELD_END\n\n// Offset >> 3\nBITFIELD_BEG(SQ_PGM_CF_OFFSET_GS, uint32_t)\nBITFIELD_ENTRY(0, 32, uint32_t, PGM_OFFSET)\nBITFIELD_END\n\n// Memory address of the (256-byte aligned) first CF instruction of the shader code for the fetch shader(VS)\nBITFIELD_BEG(SQ_PGM_START_VS, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, PGM_START)\nBITFIELD_END\n\n// Size >> 3\nBITFIELD_BEG(SQ_PGM_SIZE_VS, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, PGM_SIZE)\nBITFIELD_END\n\n// Offset >> 3\nBITFIELD_BEG(SQ_PGM_CF_OFFSET_VS, uint32_t)\nBITFIELD_ENTRY(0, 32, uint32_t, PGM_OFFSET)\nBITFIELD_END\n\n// Memory address of the (256-byte aligned) first CF instruction of the shader code for the fetch shader(PS)\nBITFIELD_BEG(SQ_PGM_START_PS, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, PGM_START)\nBITFIELD_END\n\n// Size >> 3\nBITFIELD_BEG(SQ_PGM_SIZE_PS, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, PGM_SIZE)\nBITFIELD_END\n\n// Offset >> 3\nBITFIELD_BEG(SQ_PGM_CF_OFFSET_PS, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, PGM_OFFSET)\nBITFIELD_END\n\n// Defines the exports from the Pixel Shader Program.\nBITFIELD_BEG(SQ_PGM_EXPORTS_PS, uint32_t)\n   BITFIELD_ENTRY(0, 5, uint32_t, EXPORT_MODE)\nBITFIELD_END\n\n// This register is used to clear the contents of the vertex semantic table.\n// Entries can be cleared independently -- each has one bit in this register to clear or leave alone.\nBITFIELD_BEG(SQ_VTX_SEMANTIC_CLEAR, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, CLEAR)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_VTX_SEMANTIC_N, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, SEMANTIC_ID)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_TEX_RESOURCE_WORD0_N, uint32_t)\n   BITFIELD_ENTRY(0, 3, SQ_TEX_DIM, DIM)\n   BITFIELD_ENTRY(3, 4, SQ_TILE_MODE, TILE_MODE)\n   BITFIELD_ENTRY(7, 1, SQ_TILE_TYPE, TILE_TYPE)\n   BITFIELD_ENTRY(8, 11, uint32_t, PITCH)\n   BITFIELD_ENTRY(19, 13, uint32_t, TEX_WIDTH)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_TEX_RESOURCE_WORD1_N, uint32_t)\n   BITFIELD_ENTRY(0, 13, uint32_t, TEX_HEIGHT)\n   BITFIELD_ENTRY(13, 13, uint32_t, TEX_DEPTH)\n   BITFIELD_ENTRY(26, 6, SQ_DATA_FORMAT, DATA_FORMAT)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_TEX_RESOURCE_WORD2_N, uint32_t)\n   BITFIELD_ENTRY(0, 3, uint32_t, SWIZZLE)\n   BITFIELD_ENTRY(0, 32, uint32_t, BASE_ADDRESS)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_TEX_RESOURCE_WORD3_N, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, MIP_ADDRESS)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_TEX_RESOURCE_WORD4_N, uint32_t)\n   BITFIELD_ENTRY(0, 2, SQ_FORMAT_COMP, FORMAT_COMP_X)\n   BITFIELD_ENTRY(2, 2, SQ_FORMAT_COMP, FORMAT_COMP_Y)\n   BITFIELD_ENTRY(4, 2, SQ_FORMAT_COMP, FORMAT_COMP_Z)\n   BITFIELD_ENTRY(6, 2, SQ_FORMAT_COMP, FORMAT_COMP_W)\n   BITFIELD_ENTRY(8, 2, SQ_NUM_FORMAT, NUM_FORMAT_ALL)\n   BITFIELD_ENTRY(10, 1, SQ_SRF_MODE, SRF_MODE_ALL)\n   BITFIELD_ENTRY(11, 1, bool, FORCE_DEGAMMA)\n   BITFIELD_ENTRY(12, 2, SQ_ENDIAN, ENDIAN_SWAP)\n   BITFIELD_ENTRY(14, 2, uint32_t, REQUEST_SIZE)\n   BITFIELD_ENTRY(16, 3, SQ_SEL, DST_SEL_X)\n   BITFIELD_ENTRY(19, 3, SQ_SEL, DST_SEL_Y)\n   BITFIELD_ENTRY(22, 3, SQ_SEL, DST_SEL_Z)\n   BITFIELD_ENTRY(25, 3, SQ_SEL, DST_SEL_W)\n   BITFIELD_ENTRY(28, 4, uint32_t, BASE_LEVEL)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_TEX_RESOURCE_WORD5_N, uint32_t)\n   BITFIELD_ENTRY(0, 4, uint32_t, LAST_LEVEL)\n   BITFIELD_ENTRY(4, 13, uint32_t, BASE_ARRAY)\n   BITFIELD_ENTRY(17, 13, uint32_t, LAST_ARRAY)\n   BITFIELD_ENTRY(30, 2, uint32_t, YUV_CONV)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_TEX_RESOURCE_WORD6_N, uint32_t)\n   BITFIELD_ENTRY(0, 2, SQ_TEX_MPEG_CLAMP, MPEG_CLAMP)\n   BITFIELD_ENTRY(2, 3, uint32_t, MAX_ANISO_RATIO)\n   BITFIELD_ENTRY(5, 3, uint32_t, PERF_MODULATION)\n   BITFIELD_ENTRY(8, 1, bool, INTERLACED)\n   BITFIELD_ENTRY(9, 4, uint32_t, ADVIS_FAULT_LOD)\n   BITFIELD_ENTRY(13, 6, uint32_t, ADVIS_CLAMP_LOD)\n   BITFIELD_ENTRY(30, 2, SQ_TEX_VTX_TYPE, TYPE)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_TEX_SAMPLER_WORD0_N, uint32_t)\n   BITFIELD_ENTRY(0, 3, SQ_TEX_CLAMP, CLAMP_X)\n   BITFIELD_ENTRY(3, 3, SQ_TEX_CLAMP, CLAMP_Y)\n   BITFIELD_ENTRY(6, 3, SQ_TEX_CLAMP, CLAMP_Z)\n   BITFIELD_ENTRY(9, 3, SQ_TEX_XY_FILTER, XY_MAG_FILTER)\n   BITFIELD_ENTRY(12, 3, SQ_TEX_XY_FILTER, XY_MIN_FILTER)\n   BITFIELD_ENTRY(15, 2, SQ_TEX_Z_FILTER, Z_FILTER)\n   BITFIELD_ENTRY(17, 2, SQ_TEX_Z_FILTER, MIP_FILTER)\n   BITFIELD_ENTRY(19, 3, SQ_TEX_ANISO, MAX_ANISO_RATIO)\n   BITFIELD_ENTRY(22, 2, SQ_TEX_BORDER_COLOR, BORDER_COLOR_TYPE)\n   BITFIELD_ENTRY(24, 1, bool, POINT_SAMPLING_CLAMP)\n   BITFIELD_ENTRY(25, 1, bool, TEX_ARRAY_OVERRIDE)\n   BITFIELD_ENTRY(26, 3, REF_FUNC, DEPTH_COMPARE_FUNCTION)\n   BITFIELD_ENTRY(29, 2, SQ_TEX_CHROMA_KEY, CHROMA_KEY)\n   BITFIELD_ENTRY(31, 1, bool, LOD_USES_MINOR_AXIS)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_TEX_SAMPLER_WORD1_N, uint32_t)\n   BITFIELD_ENTRY(0, 10, ufixed_4_6_t, MIN_LOD)\n   BITFIELD_ENTRY(10, 10, ufixed_4_6_t, MAX_LOD)\n   BITFIELD_ENTRY(20, 12, sfixed_1_5_6_t, LOD_BIAS)\nBITFIELD_END\n\nBITFIELD_BEG(SQ_TEX_SAMPLER_WORD2_N, uint32_t)\n   BITFIELD_ENTRY(0, 12, uint32_t, LOD_BIAS_SEC)\n   BITFIELD_ENTRY(12, 1, bool, MC_COORD_TRUNCATE)\n   BITFIELD_ENTRY(13, 1, bool, FORCE_DEGAMMA)\n   BITFIELD_ENTRY(14, 1, bool, HIGH_PRECISION_FILTER)\n   BITFIELD_ENTRY(15, 3, uint32_t, PERF_MIP)\n   BITFIELD_ENTRY(18, 2, uint32_t, PERF_Z)\n   BITFIELD_ENTRY(20, 6, ufixed_1_5_t, ANISO_BIAS)\n   BITFIELD_ENTRY(26, 1, bool, FETCH_4)\n   BITFIELD_ENTRY(27, 1, bool, SAMPLE_IS_PCF)\n   BITFIELD_ENTRY(28, 1, SQ_TEX_ROUNDING_MODE, TRUNCATE_COORD)\n   BITFIELD_ENTRY(29, 1, bool, DISABLE_CUBE_WRAP)\n   BITFIELD_ENTRY(31, 1, bool, TYPE)\nBITFIELD_END\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers_sx.h",
    "content": "#pragma once\n#include \"latte_enum_common.h\"\n\n#include <common/bitfield.h>\n#include <cstdint>\n\nnamespace latte\n{\n\nBITFIELD_BEG(SX_ALPHA_TEST_CONTROL, uint32_t)\n   BITFIELD_ENTRY(0, 3, REF_FUNC, ALPHA_FUNC)\n   BITFIELD_ENTRY(3, 1, bool, ALPHA_TEST_ENABLE)\n   BITFIELD_ENTRY(8, 1, bool, ALPHA_TEST_BYPASS)\nBITFIELD_END\n\nBITFIELD_BEG(SX_ALPHA_REF, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, ALPHA_REF)\nBITFIELD_END\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers_ta.h",
    "content": "#pragma once\n#include <common/bitfield.h>\n#include <cstdint>\n\nnamespace latte\n{\n\n// Texture Addresser Common Control\nBITFIELD_BEG(TA_CNTL_AUX, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, DISABLE_CUBE_WRAP)\n   BITFIELD_ENTRY(1, 1, bool, UNK0)\n   BITFIELD_ENTRY(24, 1, bool, SYNC_GRADIENT)\n   BITFIELD_ENTRY(25, 1, bool, SYNC_WALKER)\n   BITFIELD_ENTRY(26, 1, bool, SYNC_ALIGNER)\n   BITFIELD_ENTRY(31, 1, bool, BILINEAR_PRECISION)\nBITFIELD_END\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers_td.h",
    "content": "#pragma once\n#include <common/bitfield.h>\n#include <cstdint>\n\nnamespace latte\n{\n\nBITFIELD_BEG(TD_PS_SAMPLER_BORDERN_RED, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_RED)\nBITFIELD_END\n\nBITFIELD_BEG(TD_PS_SAMPLER_BORDERN_GREEN, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_GREEN)\nBITFIELD_END\n\nBITFIELD_BEG(TD_PS_SAMPLER_BORDERN_BLUE, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_BLUE)\nBITFIELD_END\n\nBITFIELD_BEG(TD_PS_SAMPLER_BORDERN_ALPHA, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_ALPHA)\nBITFIELD_END\n\nBITFIELD_BEG(TD_VS_SAMPLER_BORDERN_RED, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_RED)\nBITFIELD_END\n\nBITFIELD_BEG(TD_VS_SAMPLER_BORDERN_GREEN, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_GREEN)\nBITFIELD_END\n\nBITFIELD_BEG(TD_VS_SAMPLER_BORDERN_BLUE, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_BLUE)\nBITFIELD_END\n\nBITFIELD_BEG(TD_VS_SAMPLER_BORDERN_ALPHA, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_ALPHA)\nBITFIELD_END\n\nBITFIELD_BEG(TD_GS_SAMPLER_BORDERN_RED, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_RED)\nBITFIELD_END\n\nBITFIELD_BEG(TD_GS_SAMPLER_BORDERN_GREEN, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_GREEN)\nBITFIELD_END\n\nBITFIELD_BEG(TD_GS_SAMPLER_BORDERN_BLUE, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_BLUE)\nBITFIELD_END\n\nBITFIELD_BEG(TD_GS_SAMPLER_BORDERN_ALPHA, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, BORDER_ALPHA)\nBITFIELD_END\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/latte/latte_registers_vgt.h",
    "content": "#pragma once\n#include \"latte_enum_vgt.h\"\n\n#include <common/bitfield.h>\n#include <cstdint>\n\nnamespace latte\n{\n\n// Draw Inititiator\nBITFIELD_BEG(VGT_DRAW_INITIATOR, uint32_t)\n   BITFIELD_ENTRY(0, 2, VGT_DI_SRC_SEL, SOURCE_SELECT)\n   BITFIELD_ENTRY(2, 2, VGT_DI_MAJOR_MODE, MAJOR_MODE)\n   BITFIELD_ENTRY(4, 1, bool, SPRITE_EN_R6XX)\n   BITFIELD_ENTRY(5, 1, bool, NOT_EOP)\n   BITFIELD_ENTRY(6, 1, bool, USE_OPAQUE)\nBITFIELD_END\n\n// VGT Non-DMA Index Type\nBITFIELD_BEG(VGT_NODMA_INDEX_TYPE, uint32_t)\nBITFIELD_ENTRY(0, 2, VGT_INDEX_TYPE, INDEX_TYPE)\nBITFIELD_END\n\n// VGT DMA Base Address\nBITFIELD_BEG(VGT_DMA_BASE, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, BASE_ADDR)\nBITFIELD_END\n\n// VGT DMA Base Address : upper 8-bits of 40 bit address\nBITFIELD_BEG(VGT_DMA_BASE_HI, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, BASE_ADDR)\nBITFIELD_END\n\n// VGT DMA Index Type and Mode\nBITFIELD_BEG(VGT_DMA_INDEX_TYPE, uint32_t)\n   BITFIELD_ENTRY(0, 2, VGT_INDEX_TYPE, INDEX_TYPE)\n   BITFIELD_ENTRY(2, 2, VGT_DMA_SWAP, SWAP_MODE)\nBITFIELD_END\n\n// VGT DMA Maximum Size\nBITFIELD_BEG(VGT_DMA_MAX_SIZE, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, MAX_SIZE)\nBITFIELD_END\n\n// VGT DMA Number of Instances\nBITFIELD_BEG(VGT_DMA_NUM_INSTANCES, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, NUM_INSTANCES)\nBITFIELD_END\n\n// VGT DMA Size\nBITFIELD_BEG(VGT_DMA_SIZE, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, NUM_INDICES)\nBITFIELD_END\n\n// Maximum ES vertices per GS thread\nBITFIELD_BEG(VGT_ES_PER_GS, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, ES_PER_GS)\nBITFIELD_END\n\n// Event Initiator\nBITFIELD_BEG(VGT_EVENT_INITIATOR, uint32_t)\n   BITFIELD_ENTRY(0, 6, VGT_EVENT_TYPE, EVENT_TYPE)\n   BITFIELD_ENTRY(8, 4, VGT_EVENT_INDEX, EVENT_INDEX)\n   BITFIELD_ENTRY(19, 8, uint32_t, ADDRESS_HI)\n   BITFIELD_ENTRY(27, 1, uint32_t, EXTENDED_EVENT)\nBITFIELD_END\n\n// VGT GS Enable Mode\nBITFIELD_BEG(VGT_GS_MODE, uint32_t)\n   BITFIELD_ENTRY(0, 2, VGT_GS_ENABLE_MODE, MODE)\n   BITFIELD_ENTRY(2, 1, bool, ES_PASSTHRU)\n   BITFIELD_ENTRY(3, 2, VGT_GS_CUT_MODE, CUT_MODE)\n   BITFIELD_ENTRY(8, 1, bool, MODE_HI)\n   BITFIELD_ENTRY(11, 1, bool, GS_C_PACK_EN)\n   BITFIELD_ENTRY(14, 1, bool, COMPUTE_MODE)\n   BITFIELD_ENTRY(15, 1, bool, FAST_COMPUTE_MODE)\n   BITFIELD_ENTRY(16, 1, bool, ELEMENT_INFO_EN)\n   BITFIELD_ENTRY(17, 1, bool, PARTIAL_THD_AT_EOI)\nBITFIELD_END\n\n// VGT GS output primitive type\nBITFIELD_BEG(VGT_GS_OUT_PRIM_TYPE, uint32_t)\n   BITFIELD_ENTRY(0, 6, VGT_GS_OUT_PRIMITIVE_TYPE, PRIM_TYPE)\nBITFIELD_END\n\n// Maximum GS prims per ES thread\nBITFIELD_BEG(VGT_GS_PER_ES, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, GS_PER_ES)\nBITFIELD_END\n\n// Maximum GS prims per VS thread\nBITFIELD_BEG(VGT_GS_PER_VS, uint32_t)\n   BITFIELD_ENTRY(0, 4, uint8_t, GS_PER_VS)\nBITFIELD_END\n\n// Reuseability for GS path, it is nothing to do with number of good simd\nBITFIELD_BEG(VGT_GS_VERTEX_REUSE, uint32_t)\n   BITFIELD_ENTRY(0, 5, uint8_t, VERT_REUSE)\nBITFIELD_END\n\nBITFIELD_BEG(VGT_HOS_REUSE_DEPTH, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, REUSE_DEPTH)\nBITFIELD_END\n\n// For continuous and discrete tessellation modes, this register contains the tessellation level.\n// For adaptive tessellation, this register contains the maximum tessellation level.\nBITFIELD_BEG(VGT_HOS_MAX_TESS_LEVEL, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, MAX_TESS)\nBITFIELD_END\n\n// For continuous and discrete tessellation modes, this register is not applicable.\n// For adaptive tessellation, this register contains the minimum tessellation level.\nBITFIELD_BEG(VGT_HOS_MIN_TESS_LEVEL, uint32_t)\n   BITFIELD_ENTRY(0, 32, float, MIN_TESS)\nBITFIELD_END\n\n// For components that are that are specified to be indices (see the VGT_GROUP_VECT_0_FMT_CNTL register), this register is the offset value.\nBITFIELD_BEG(VGT_INDX_OFFSET, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, INDX_OFFSET)\nBITFIELD_END\n\n// For components that are that are specified to be indices (see the VGT_GROUP_VECT_0_FMT_CNTL register), this register is the maximum clamp value.\nBITFIELD_BEG(VGT_MAX_VTX_INDX, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, MAX_INDX)\nBITFIELD_END\n\n// For components that are that are specified to be indices (see the VGT_GROUP_VECT_0_FMT_CNTL register), this register is the minimum clamp value.\nBITFIELD_BEG(VGT_MIN_VTX_INDX, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, MIN_INDX)\nBITFIELD_END\n\n// This register enabling reseting of prim based on reset index\nBITFIELD_BEG(VGT_MULTI_PRIM_IB_RESET_EN, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, RESET_EN)\nBITFIELD_END\n\n// This register defines the index which resets primitive sets when MULTI_PRIM_IB is enabled.\nBITFIELD_BEG(VGT_MULTI_PRIM_IB_RESET_INDX, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, RESET_INDX)\nBITFIELD_END\n\n// VGT Number of Indices\nBITFIELD_BEG(VGT_NUM_INDICES, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, NUM_INDICES)\nBITFIELD_END\n\n// This register controls, within a process vector, when the previous process vector is de-allocated.\nBITFIELD_BEG(VGT_OUT_DEALLOC_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 7, uint32_t, DEALLOC_DIST)\nBITFIELD_END\n\n// This register selects which backend path will be used by the VGT block.\nBITFIELD_BEG(VGT_OUTPUT_PATH_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 2, VGT_OUTPUT_PATH_SELECT, PATH_SELECT)\nBITFIELD_END\n\n// Primitive ID generation is enabled\nBITFIELD_BEG(VGT_PRIMITIVEID_EN, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, PRIMITIVEID_EN)\nBITFIELD_END\n\n// VGT Primitive Type\nBITFIELD_BEG(VGT_PRIMITIVE_TYPE, uint32_t)\n   BITFIELD_ENTRY(0, 6, VGT_DI_PRIMITIVE_TYPE, PRIM_TYPE)\nBITFIELD_END\n\n// VGT reuse is off. This will expand strip primitives to list primitives\nBITFIELD_BEG(VGT_REUSE_OFF, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, REUSE_OFF)\nBITFIELD_END\n\n// This register enables streaming out\nBITFIELD_BEG(VGT_STRMOUT_EN, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, STREAMOUT)\nBITFIELD_END\n\n// Stream out enable bits.\nBITFIELD_BEG(VGT_STRMOUT_BUFFER_EN, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, BUFFER_0_EN)\n   BITFIELD_ENTRY(1, 1, bool, BUFFER_1_EN)\n   BITFIELD_ENTRY(2, 1, bool, BUFFER_2_EN)\n   BITFIELD_ENTRY(3, 1, bool, BUFFER_3_EN)\nBITFIELD_END\n\n// Draw opaque offset.\nBITFIELD_BEG(VGT_STRMOUT_DRAW_OPAQUE_OFFSET, uint32_t)\n   BITFIELD_ENTRY(0, 32, uint32_t, OFFSET)\nBITFIELD_END\n\n// This register controls the behavior of the Vertex Reuse block at the backend of the VGT.\nBITFIELD_BEG(VGT_VERTEX_REUSE_BLOCK_CNTL, uint32_t)\n   BITFIELD_ENTRY(0, 8, uint32_t, VTX_REUSE_DEPTH)\nBITFIELD_END\n\n// Auto-index generation is on.\nBITFIELD_BEG(VGT_VTX_CNT_EN, uint32_t)\n   BITFIELD_ENTRY(0, 1, bool, VTX_CNT_EN)\nBITFIELD_END\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/gpu7_displaylayout.cpp",
    "content": "#include \"gpu7_displaylayout.h\"\n#include \"gpu_config.h\"\n\n#include <algorithm>\n#include <cmath>\n\nnamespace gpu7\n{\n\nvoid\nupdateDisplayLayout(DisplayLayout &layout,\n                    float windowWidth,\n                    float windowHeight)\n{\n   // Sizes for calculating aspect ratio\n   constexpr auto TvWidth = 1280.0f;\n   constexpr auto TvHeight = 720.0f;\n   constexpr auto DrcWidth = 854.0f;\n   constexpr auto DrcHeight = 480.0f;\n   constexpr auto SplitCombinedWidth = std::max(TvWidth, DrcWidth);\n   constexpr auto SplitCombinedHeight = TvHeight + DrcHeight;\n\n   const auto &displaySettings = gpu::config()->display;\n   const auto splitScreenSeparation = static_cast<float>(displaySettings.splitSeperation);\n\n   auto maintainAspectRatio =\n      [&](DisplayLayout::Display &display,\n          float width, float height,\n          float referenceWidth, float referenceHeight)\n      {\n         const auto widthRatio = static_cast<float>(width) / referenceWidth;\n         const auto heightRatio = static_cast<float>(height) / referenceHeight;\n         const auto aspectRatio = std::min(widthRatio, heightRatio);\n         display.width = aspectRatio * referenceWidth;\n         display.height = aspectRatio * referenceHeight;\n      };\n\n   if (displaySettings.viewMode == gpu::DisplaySettings::TV) {\n      layout.tv.visible = true;\n      layout.drc.visible = false;\n\n      if (displaySettings.maintainAspectRatio) {\n         maintainAspectRatio(layout.tv, windowWidth, windowHeight, TvWidth, TvHeight);\n         layout.tv.x = (windowWidth - layout.tv.width) / 2.0f;\n         layout.tv.y = (windowHeight - layout.tv.height) / 2.0f;\n      } else {\n         layout.tv.x = 0.0f;\n         layout.tv.y = 0.0f;\n         layout.tv.width = windowWidth;\n         layout.tv.height = windowHeight;\n      }\n   } else if (displaySettings.viewMode == gpu::DisplaySettings::Gamepad1) {\n      layout.tv.visible = false;\n      layout.drc.visible = true;\n\n      if (displaySettings.maintainAspectRatio) {\n         maintainAspectRatio(layout.drc, windowWidth, windowHeight, DrcWidth, DrcHeight);\n         layout.drc.x = (windowWidth - layout.drc.width) / 2.0f;\n         layout.drc.y = (windowHeight - layout.drc.height) / 2.0f;\n      } else {\n         layout.drc.x = 0.0f;\n         layout.drc.y = 0.0f;\n         layout.drc.width = windowWidth;\n         layout.drc.height = windowHeight;\n      }\n   } else if (displaySettings.viewMode == gpu::DisplaySettings::Split) {\n      layout.tv.visible = true;\n      layout.drc.visible = true;\n\n      if (displaySettings.maintainAspectRatio) {\n         auto combined = DisplayLayout::Display { };\n         maintainAspectRatio(combined, windowWidth, windowHeight,\n                             SplitCombinedWidth,\n                             SplitCombinedHeight + splitScreenSeparation);\n\n         layout.tv.width = combined.width * (TvWidth / SplitCombinedWidth);\n         layout.tv.height = combined.height * (TvHeight / SplitCombinedHeight) - (splitScreenSeparation / 2.0f);\n         layout.tv.x = (windowWidth - layout.tv.width) / 2.0f;\n         layout.tv.y = (windowHeight - combined.height) / 2.0f;\n\n         layout.drc.width = combined.width * (DrcWidth / SplitCombinedWidth);\n         layout.drc.height = combined.height * (DrcHeight / SplitCombinedHeight) - (splitScreenSeparation / 2.0f);\n         layout.drc.x = (windowWidth - layout.drc.width) / 2.0f;\n         layout.drc.y = layout.tv.y + layout.tv.height + splitScreenSeparation;\n      } else {\n         layout.tv.x = 0.0f;\n         layout.tv.y = 0.0f;\n         layout.tv.width = windowWidth;\n         layout.tv.height = windowHeight * (TvHeight / SplitCombinedHeight);\n\n         layout.drc.x = 0.0f;\n         layout.drc.y = layout.tv.height;\n         layout.drc.width = windowWidth;\n         layout.drc.height = windowHeight * (DrcHeight / SplitCombinedHeight);\n      }\n   }\n\n   layout.backgroundColour[0] = displaySettings.backgroundColour[0] / 255.0f;\n   layout.backgroundColour[1] = displaySettings.backgroundColour[1] / 255.0f;\n   layout.backgroundColour[2] = displaySettings.backgroundColour[2] / 255.0f;\n   layout.backgroundColour[3] = 1.0f;\n\n   // TODO: Only do if SRGB\n   layout.backgroundColour[0] = std::pow(layout.backgroundColour[0], 2.2f);\n   layout.backgroundColour[1] = std::pow(layout.backgroundColour[1], 2.2f);\n   layout.backgroundColour[2] = std::pow(layout.backgroundColour[2], 2.2f);\n}\n\n\nDisplayTouchEvent\ntranslateDisplayTouch(DisplayLayout &layout,\n                      float x,\n                      float y)\n{\n   if (layout.tv.visible &&\n       x >= layout.tv.x &&\n       x  < layout.tv.x + layout.tv.width &&\n       y >= layout.tv.y &&\n       y  < layout.tv.y + layout.tv.height) {\n      return {\n         DisplayTouchEvent::Tv,\n         (x - layout.tv.x) / layout.tv.width,\n         (y - layout.tv.y) / layout.tv.height,\n      };\n   }\n\n   if (layout.drc.visible &&\n       x >= layout.drc.x &&\n       x  < layout.drc.x + layout.drc.width &&\n       y >= layout.drc.y &&\n       y  < layout.drc.y + layout.drc.height) {\n      return {\n         DisplayTouchEvent::Drc1,\n         (x - layout.drc.x) / layout.drc.width,\n         (y - layout.drc.y) / layout.drc.height,\n      };\n   }\n\n   return { DisplayTouchEvent::None, 0.0f, 0.0f };\n}\n\n} // namespace gpu7\n"
  },
  {
    "path": "src/libgpu/src/gpu7_tiling.cpp",
    "content": "#include \"gpu7_tiling.h\"\n\n#include <algorithm>\n#include <cstdint>\n#include <cstring>\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <addrlib/addrinterface.h>\n\nnamespace gpu7::tiling\n{\n\nint\ncomputeSurfaceBankSwappedWidth(TileMode tileMode,\n                               uint32_t bpp,\n                               uint32_t numSamples,\n                               uint32_t pitch)\n{\n   const auto bytesPerSample = 8 * bpp;\n   const auto samplesPerTile = SampleSplitSize / bytesPerSample;\n   auto bankSwapWidth = uint32_t { 0 };\n   auto slicesPerTile = uint32_t { 1 };\n\n   if (samplesPerTile) {\n      slicesPerTile = std::max<uint32_t>(1u, numSamples / samplesPerTile);\n   }\n\n   if (getMicroTileThickness(tileMode) > 1) {\n      numSamples = 4;\n   }\n\n   if (tileMode == TileMode::Macro2BTiledThin1 ||\n       tileMode == TileMode::Macro2BTiledThin2 ||\n       tileMode == TileMode::Macro2BTiledThin4 ||\n       tileMode == TileMode::Macro2BTiledThick ||\n       tileMode == TileMode::Macro3BTiledThin1 ||\n       tileMode == TileMode::Macro3BTiledThick) {\n      const auto swapTiles = std::max<uint32_t>(1u, (SwapSize >> 1) / bpp);\n      const auto swapWidth = swapTiles * 8 * NumBanks;\n\n      const auto macroTileHeight = getMacroTileHeight(tileMode);\n      const auto heightBytes = numSamples * macroTileHeight * bpp / slicesPerTile;\n      const auto swapMax = NumPipes * NumBanks * RowSize / heightBytes;\n\n      const auto bytesPerTileSlice = numSamples * bytesPerSample / slicesPerTile;\n      const auto swapMin = PipeInterleaveBytes * 8 * NumBanks / bytesPerTileSlice;\n\n      bankSwapWidth = std::min(swapMax, std::max(swapMin, swapWidth));\n\n      while (bankSwapWidth >= 2 * pitch) {\n         bankSwapWidth >>= 1;\n      }\n   }\n\n   return bankSwapWidth;\n}\n\nstatic void *\naddrLibAlloc(const ADDR_ALLOCSYSMEM_INPUT *pInput)\n{\n   return std::malloc(pInput->sizeInBytes);\n}\n\nstatic ADDR_E_RETURNCODE\naddrLibFree(const ADDR_FREESYSMEM_INPUT *pInput)\n{\n   std::free(pInput->pVirtAddr);\n   return ADDR_OK;\n}\n\nstatic ADDR_HANDLE\ngetAddrLibHandle()\n{\n   static ADDR_HANDLE handle = nullptr;\n   if (!handle) {\n      auto input = ADDR_CREATE_INPUT { };\n      input.size = sizeof(ADDR_CREATE_INPUT);\n      input.chipEngine = CIASICIDGFXENGINE_R600;\n      input.chipFamily = 0x51;\n      input.chipRevision = 71;\n      input.createFlags.fillSizeFields = 1;\n      input.regValue.gbAddrConfig = 0x44902;\n      input.callbacks.allocSysMem = &addrLibAlloc;\n      input.callbacks.freeSysMem = &addrLibFree;\n\n      auto output = ADDR_CREATE_OUTPUT { };\n      output.size = sizeof(ADDR_CREATE_OUTPUT);\n\n      if (AddrCreate(&input, &output) == ADDR_OK) {\n         handle = output.hLib;\n      }\n   }\n\n   return handle;\n}\n\nSurfaceInfo\ncomputeSurfaceInfo(const SurfaceDescription &surface,\n                   int mipLevel)\n{\n   auto output = ADDR_COMPUTE_SURFACE_INFO_OUTPUT { };\n   output.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT);\n\n   auto input = ADDR_COMPUTE_SURFACE_INFO_INPUT { };\n   input.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT);\n   input.tileMode = static_cast<AddrTileMode>(surface.tileMode);\n   input.format = static_cast<AddrFormat>(surface.format);\n   input.bpp = surface.bpp;\n   input.numSamples = surface.numSamples;\n   input.numFrags = surface.numFrags;\n   input.mipLevel = mipLevel;\n   input.slice = 0;\n   input.numSlices = surface.numSlices;\n\n   input.width = std::max(surface.width >> mipLevel, 1u);\n   input.height = std::max(surface.height >> mipLevel, 1u);\n   input.flags.inputBaseMap = mipLevel == 0 ? 1 : 0;\n\n   if (surface.use & SurfaceUse::ScanBuffer) {\n      input.flags.display = 1;\n   }\n\n   if (surface.use & SurfaceUse::DepthBuffer) {\n      input.flags.depth = 1;\n   }\n\n   if (surface.dim == SurfaceDim::Texture3D) {\n      input.flags.volume = 1;\n      input.numSlices = std::max(surface.numSlices >> mipLevel, 1u);\n   }\n\n   if (surface.dim == SurfaceDim::TextureCube) {\n      input.flags.cube = 1;\n   }\n\n   auto handle = getAddrLibHandle();\n   decaf_check(handle);\n   decaf_check(AddrComputeSurfaceInfo(handle, &input, &output) == ADDR_OK);\n\n   if (surface.dim == SurfaceDim::Texture3D) {\n      output.sliceSize /= output.depth;\n   }\n\n   auto result = SurfaceInfo { };\n   result.tileMode = static_cast<TileMode>(output.tileMode);\n   result.use = surface.use;\n   result.bpp = output.bpp;\n   result.pitch = output.pitch;\n   result.height = output.height;\n   result.depth = output.depth;\n   result.surfSize = static_cast<uint32_t>(output.surfSize);\n   result.sliceSize = output.sliceSize;\n   result.baseAlign = output.baseAlign;\n   result.pitchAlign = output.pitchAlign;\n   result.heightAlign = output.heightAlign;\n   result.depthAlign = output.depthAlign;\n   result.bankSwizzle = surface.bankSwizzle;\n   result.pipeSwizzle = surface.pipeSwizzle;\n   return result;\n}\n\n\nvoid\nunpitchImage(const SurfaceDescription &desc,\n             const void *pitched,\n             void *unpitched)\n{\n   const auto info = computeSurfaceInfo(desc, 0);\n   const auto bytesPerElem = info.bpp / 8;\n   const auto unpitchedSliceSize = desc.width * desc.height * bytesPerElem;\n\n   for (auto slice = 0u; slice < desc.numSlices; ++slice) {\n      auto src = reinterpret_cast<const uint8_t*>(pitched)\n                 + info.sliceSize * slice;\n      auto dst = reinterpret_cast<uint8_t*>(unpitched)\n                 + unpitchedSliceSize * slice;\n\n      for (auto y = 0u; y < desc.height; ++y) {\n         std::memcpy(dst, src, desc.width * bytesPerElem);\n         src += info.pitch * bytesPerElem;\n         dst += desc.width * bytesPerElem;\n      }\n   }\n}\n\nsize_t\ncomputeUnpitchedImageSize(const SurfaceDescription &desc)\n{\n   return desc.width * desc.height * (desc.bpp / 8) * desc.numSlices;\n}\n\nsize_t\ncomputeUnpitchedMipMapSize(const SurfaceDescription &desc)\n{\n   auto size = size_t { 0 };\n\n   for (auto level = 1u; level < desc.numLevels; ++level) {\n      const auto width = desc.width >> level;\n      const auto height = desc.height >> level;\n      auto numSlices = desc.numSlices;\n      if (desc.dim == SurfaceDim::Texture3D) {\n         numSlices >>= level;\n      }\n\n      size += width * height * (desc.bpp / 8) * numSlices;\n   }\n\n   return size;\n}\n\nvoid\nunpitchMipMap(const SurfaceDescription &desc,\n              const void *pitched,\n              void *unpitched)\n{\n   auto srcMipOffset = size_t { 0 };\n   auto dstMipOffset = size_t { 0 };\n\n   for (auto level = 1u; level < desc.numLevels; ++level) {\n      const auto info = computeSurfaceInfo(desc, level);\n      const auto bytesPerElem = info.bpp / 8;\n      const auto width = desc.width >> level;\n      const auto height = desc.height >> level;\n      const auto unpitchedSliceSize = width * height * bytesPerElem;\n      auto numSlices = desc.numSlices;\n      if (desc.dim == SurfaceDim::Texture3D) {\n         numSlices >>= level;\n      }\n\n      srcMipOffset = align_up(srcMipOffset, info.baseAlign);\n\n      for (auto slice = 0u; slice < numSlices; ++slice) {\n         auto src = reinterpret_cast<const uint8_t*>(pitched)\n                    + srcMipOffset + info.sliceSize * slice;\n         auto dst = reinterpret_cast<uint8_t*>(unpitched)\n                    + dstMipOffset + unpitchedSliceSize * slice;\n\n         for (auto y = 0u; y < height; ++y) {\n            std::memcpy(dst, src, width * bytesPerElem);\n            src += info.pitch * bytesPerElem;\n            dst += width * bytesPerElem;\n         }\n      }\n\n      srcMipOffset += info.surfSize;\n      dstMipOffset += unpitchedSliceSize * numSlices;\n   }\n}\n\nRetileInfo\ncomputeLinearRetileInfo(const SurfaceInfo &info)\n{\n   const auto bytesPerElement = info.bpp / 8;\n   const auto pitch = info.pitch;\n   const auto height = info.height;\n\n   const auto thinSliceBytes =\n      pitch * height * bytesPerElement;\n\n   RetileInfo out;\n   out.tileMode = static_cast<TileMode>(info.tileMode);\n   out.bitsPerElement = info.bpp;\n   out.isDepth = !!(info.use & gpu7::tiling::SurfaceUse::DepthBuffer);\n   out.thinSliceBytes = thinSliceBytes;\n   out.isTiled = false;\n   out.isMacroTiled = false;\n   out.macroTileWidth = 0;\n   out.macroTileHeight = 0;\n   out.microTileThickness = 0;\n   out.thickMicroTileBytes = 0;\n   out.numTilesPerRow = 0;\n   out.numTilesPerSlice = 0;\n   out.bankSwizzle = 0;\n   out.pipeSwizzle = 0;\n   out.bankSwapWidth = 0;\n   return out;\n}\n\nRetileInfo\ncomputeMicroRetileInfo(const SurfaceInfo &info)\n{\n   const auto bytesPerElement = info.bpp / 8;\n   const auto pitch = info.pitch;\n   const auto height = info.height;\n\n   const auto microTileThickness = getMicroTileThickness(info.tileMode);\n   const auto microTileBytes =\n      MicroTileWidth * MicroTileHeight * microTileThickness * bytesPerElement;\n\n   const auto microTilesPerRow = pitch / MicroTileWidth;\n   const auto microTilesNumRows = height / MicroTileHeight;\n   const auto microTilesPerSlice = microTilesPerRow * microTilesNumRows;\n\n   const auto thinSliceBytes =\n      pitch * height * bytesPerElement;\n\n   RetileInfo out;\n   out.tileMode = static_cast<TileMode>(info.tileMode);\n   out.bitsPerElement = info.bpp;\n   out.isDepth = !!(info.use & gpu7::tiling::SurfaceUse::DepthBuffer);\n   out.thinSliceBytes = thinSliceBytes;\n   out.isTiled = true;\n   out.isMacroTiled = false;\n   out.macroTileWidth = 1;\n   out.macroTileHeight = 1;\n   out.microTileThickness = microTileThickness;\n   out.thickMicroTileBytes = microTileBytes;\n   out.numTilesPerRow = microTilesPerRow;\n   out.numTilesPerSlice = microTilesPerSlice;\n   out.bankSwizzle = 0;\n   out.pipeSwizzle = 0;\n   out.bankSwapWidth = 0;\n   return out;\n}\n\nRetileInfo\ncomputeMacroRetileInfo(const SurfaceInfo &info)\n{\n   const auto bytesPerElement = info.bpp / 8;\n   const auto pitch = info.pitch;\n   const auto height = info.height;\n\n   const auto macroTileWidth = getMacroTileWidth(info.tileMode);\n   const auto macroTileHeight = getMacroTileHeight(info.tileMode);\n   const auto microTileThickness = getMicroTileThickness(info.tileMode);\n\n   const auto microTileBytes =\n      MicroTileWidth * MicroTileHeight * microTileThickness * bytesPerElement;\n\n   const auto microTilesPerRow = pitch / MicroTileWidth;\n   const auto microTilesNumRows = height / MicroTileHeight;\n   const auto microTilesPerSlice = microTilesPerRow * microTilesNumRows;\n\n   auto bankSwapWidth = computeSurfaceBankSwappedWidth(\n      info.tileMode, info.bpp, 1, info.pitch);\n\n   const auto thinSliceBytes =\n      pitch * height * bytesPerElement;\n\n   RetileInfo out;\n   out.tileMode = static_cast<TileMode>(info.tileMode);\n   out.bitsPerElement = info.bpp;\n   out.isDepth = !!(info.use & gpu7::tiling::SurfaceUse::DepthBuffer);\n   out.thinSliceBytes = thinSliceBytes;\n   out.isTiled = true;\n   out.isMacroTiled = true;\n   out.macroTileWidth = macroTileWidth;\n   out.macroTileHeight = macroTileHeight;\n   out.microTileThickness = microTileThickness;\n   out.thickMicroTileBytes = microTileBytes;\n   out.numTilesPerRow = microTilesPerRow;\n   out.numTilesPerSlice = microTilesPerSlice;\n   out.bankSwizzle = info.bankSwizzle;\n   out.pipeSwizzle = info.pipeSwizzle;\n   out.bankSwapWidth = bankSwapWidth;\n   return out;\n}\n\nRetileInfo\ncomputeRetileInfo(const SurfaceInfo &info)\n{\n   switch (info.tileMode) {\n   case TileMode::LinearGeneral:\n   case TileMode::LinearAligned:\n      return computeLinearRetileInfo(info);\n   case TileMode::Micro1DTiledThin1:\n   case TileMode::Micro1DTiledThick:\n      return computeMicroRetileInfo(info);\n   case TileMode::Macro2DTiledThin1:\n   case TileMode::Macro2DTiledThin2:\n   case TileMode::Macro2DTiledThin4:\n   case TileMode::Macro2DTiledThick:\n   case TileMode::Macro2BTiledThin1:\n   case TileMode::Macro2BTiledThin2:\n   case TileMode::Macro2BTiledThin4:\n   case TileMode::Macro2BTiledThick:\n   case TileMode::Macro3DTiledThin1:\n   case TileMode::Macro3DTiledThick:\n   case TileMode::Macro3BTiledThin1:\n   case TileMode::Macro3BTiledThick:\n      return computeMacroRetileInfo(info);\n   default:\n      decaf_abort(\"Invalid tile mode\");\n   }\n}\n\n} // namespace gpu7::tiling\n"
  },
  {
    "path": "src/libgpu/src/gpu7_tiling_cpu.cpp",
    "content": "#include \"gpu7_tiling_cpu.h\"\n\n#include <common/align.h>\n#include <common/decaf_assert.h>\n\n#include <cstring>\n\nnamespace gpu7::tiling::cpu\n{\n\ntemplate<\n   bool IsUntiling,\n   uint32_t MicroTileThickness,\n   uint32_t MacroTileWidth,\n   uint32_t MacroTileHeight,\n   bool IsMacro3X,\n   bool IsBankSwapped,\n   uint32_t BitsPerElement,\n   bool IsDepth\n>\nstruct RetileCore\n{\n   static constexpr bool IsMacroTiling = (MacroTileWidth > 1 || MacroTileHeight > 1);\n   static constexpr uint32_t BytesPerElement = BitsPerElement / 8;\n   static constexpr uint32_t MicroTileBytes = MicroTileWidth * MicroTileHeight * MicroTileThickness * BytesPerElement;\n   static constexpr uint32_t MacroTileBytes = MacroTileWidth * MacroTileHeight * MicroTileBytes;\n\n   template<uint32_t bytesPerElem>\n   static inline void\n   copyElems(uint8_t *untiled, uint8_t *tiled, size_t numElems)\n   {\n      if (IsUntiling) {\n         std::memcpy(untiled, tiled, bytesPerElem * numElems);\n      } else {\n         std::memcpy(tiled, untiled, bytesPerElem * numElems);\n      }\n   }\n\n   static inline void\n   retileMicro8(uint8_t *tiled,\n                uint8_t *untiled,\n                uint32_t untiledStride)\n   {\n      static constexpr auto tiledStride = MicroTileWidth;\n      static constexpr auto rowElems = MicroTileWidth / 8;\n\n      for (int y = 0; y < MicroTileHeight; y += 4) {\n         auto untiledRow0 = untiled + 0 * untiledStride;\n         auto untiledRow1 = untiled + 1 * untiledStride;\n         auto untiledRow2 = untiled + 2 * untiledStride;\n         auto untiledRow3 = untiled + 3 * untiledStride;\n\n         auto tiledRow0 = tiled + 0 * tiledStride;\n         auto tiledRow1 = tiled + 1 * tiledStride;\n         auto tiledRow2 = tiled + 2 * tiledStride;\n         auto tiledRow3 = tiled + 3 * tiledStride;\n\n         copyElems<8>(untiledRow0, tiledRow0, rowElems);\n         copyElems<8>(untiledRow1, tiledRow2, rowElems);\n         copyElems<8>(untiledRow2, tiledRow1, rowElems);\n         copyElems<8>(untiledRow3, tiledRow3, rowElems);\n\n         untiled += 4 * untiledStride;\n         tiled += 4 * tiledStride;\n      }\n   }\n\n   static inline void\n   retileMicro16(uint8_t *tiled,\n                 uint8_t *untiled,\n                 uint32_t untiledStride)\n   {\n      static constexpr auto tiledStride = MicroTileWidth * 2;\n      static constexpr auto rowElems = MicroTileWidth * 2 / 16;\n\n      for (int y = 0; y < MicroTileHeight; ++y) {\n         copyElems<16>(untiled, tiled, rowElems);\n\n         untiled += untiledStride;\n         tiled += tiledStride;\n      }\n   }\n\n   static inline void\n   retileMicro32(uint8_t *tiled,\n                 uint8_t *untiled,\n                 uint32_t untiledStride)\n   {\n      static constexpr auto tiledStride = MicroTileWidth * 4;\n      static constexpr auto groupElems = 4 * 4 / 16;\n\n      for (int y = 0; y < MicroTileHeight; y += 2) {\n         auto untiledRow1 = untiled + 0 * untiledStride;\n         auto untiledRow2 = untiled + 1 * untiledStride;\n\n         auto tiledRow1 = tiled + 0 * tiledStride;\n         auto tiledRow2 = tiled + 1 * tiledStride;\n\n         copyElems<16>(untiledRow1 + 0, tiledRow1 + 0, groupElems);\n         copyElems<16>(untiledRow1 + 16, tiledRow2 + 0, groupElems);\n\n         copyElems<16>(untiledRow2 + 0, tiledRow1 + 16, groupElems);\n         copyElems<16>(untiledRow2 + 16, tiledRow2 + 16, groupElems);\n\n         tiled += tiledStride * 2;\n         untiled += untiledStride * 2;\n      }\n   }\n\n   static inline void\n   retileMicro64(uint8_t *tiled,\n                 uint8_t *untiled,\n                 uint32_t untiledStride)\n   {\n      static constexpr auto tiledStride = MicroTileWidth * 8;\n      static constexpr auto groupElems = 2 * (64 / 8) / 16;\n\n      for (int y = 0; y < MicroTileHeight; y += 2) {\n         if constexpr (IsMacroTiling) {\n            if (y == 4) {\n               // At y == 4 we hit the next group (at element offset 256)\n               tiled -= tiledStride * y;\n               tiled += 0x100 << (NumBankBits + NumPipeBits);\n            }\n         }\n\n         auto untiledRow1 = untiled + 0 * untiledStride;\n         auto untiledRow2 = untiled + 1 * untiledStride;\n\n         auto tiledRow1 = tiled + 0 * tiledStride;\n         auto tiledRow2 = tiled + 1 * tiledStride;\n\n         copyElems<16>(untiledRow1 + 0, tiledRow1 + 0, groupElems);\n         copyElems<16>(untiledRow2 + 0, tiledRow1 + 16, groupElems);\n\n         copyElems<16>(untiledRow1 + 16, tiledRow1 + 32, groupElems);\n         copyElems<16>(untiledRow2 + 16, tiledRow1 + 48, groupElems);\n\n         copyElems<16>(untiledRow1 + 32, tiledRow2 + 0, groupElems);\n         copyElems<16>(untiledRow2 + 32, tiledRow2 + 16, groupElems);\n\n         copyElems<16>(untiledRow1 + 48, tiledRow2 + 32, groupElems);\n         copyElems<16>(untiledRow2 + 48, tiledRow2 + 48, groupElems);\n\n         tiled += tiledStride * 2;\n         untiled += untiledStride * 2;\n      }\n   }\n\n   static inline void\n   retileMicro128(uint8_t *tiled,\n                  uint8_t *untiled,\n                  uint32_t untiledStride)\n   {\n      static constexpr auto tiledStride = MicroTileWidth * 16;\n      static constexpr auto groupBytes = 16;\n      static constexpr auto groupElems = 16 / 16;\n\n      for (int y = 0; y < MicroTileHeight; y += 2) {\n         auto untiledRow1 = untiled + 0 * untiledStride;\n         auto untiledRow2 = untiled + 1 * untiledStride;\n\n         auto tiledRow1 = tiled + 0 * tiledStride;\n         auto tiledRow2 = tiled + 1 * tiledStride;\n\n         copyElems<16>(untiledRow1 + 0 * groupBytes, tiledRow1 + 0 * groupBytes, groupElems);\n         copyElems<16>(untiledRow1 + 1 * groupBytes, tiledRow1 + 2 * groupBytes, groupElems);\n         copyElems<16>(untiledRow2 + 0 * groupBytes, tiledRow1 + 1 * groupBytes, groupElems);\n         copyElems<16>(untiledRow2 + 1 * groupBytes, tiledRow1 + 3 * groupBytes, groupElems);\n\n         copyElems<16>(untiledRow1 + 2 * groupBytes, tiledRow1 + 4 * groupBytes, groupElems);\n         copyElems<16>(untiledRow1 + 3 * groupBytes, tiledRow1 + 6 * groupBytes, groupElems);\n         copyElems<16>(untiledRow2 + 2 * groupBytes, tiledRow1 + 5 * groupBytes, groupElems);\n         copyElems<16>(untiledRow2 + 3 * groupBytes, tiledRow1 + 7 * groupBytes, groupElems);\n\n         copyElems<16>(untiledRow1 + 4 * groupBytes, tiledRow2 + 0 * groupBytes, groupElems);\n         copyElems<16>(untiledRow1 + 5 * groupBytes, tiledRow2 + 2 * groupBytes, groupElems);\n         copyElems<16>(untiledRow2 + 4 * groupBytes, tiledRow2 + 1 * groupBytes, groupElems);\n         copyElems<16>(untiledRow2 + 5 * groupBytes, tiledRow2 + 3 * groupBytes, groupElems);\n\n         copyElems<16>(untiledRow1 + 6 * groupBytes, tiledRow2 + 4 * groupBytes, groupElems);\n         copyElems<16>(untiledRow1 + 7 * groupBytes, tiledRow2 + 6 * groupBytes, groupElems);\n         copyElems<16>(untiledRow2 + 6 * groupBytes, tiledRow2 + 5 * groupBytes, groupElems);\n         copyElems<16>(untiledRow2 + 7 * groupBytes, tiledRow2 + 7 * groupBytes, groupElems);\n\n         if (IsMacroTiling) {\n            tiled += 0x100 << (NumBankBits + NumPipeBits);\n         } else {\n            tiled += tiledStride * 2;\n         }\n\n         untiled += untiledStride * 2;\n      }\n   }\n\n   static inline void\n   copyDepthXYGroup(uint8_t *tiled,\n                    uint8_t *untiled,\n                    uint32_t untiledStride,\n                    uint32_t tX, uint32_t tY,\n                    uint32_t uX, uint32_t uY)\n   {\n      static constexpr auto groupBytes = 2 * BytesPerElement;\n      static constexpr auto groupElems = 2 * BytesPerElement / 4;\n      static constexpr auto tiledStride = MicroTileWidth * BytesPerElement;\n\n      copyElems<4>(untiled + uY * untiledStride + uX * groupBytes, tiled + tY * tiledStride + tX * groupBytes, groupElems);\n   }\n\n   static inline void\n   retileMicroDepth(uint8_t *tiled,\n                    uint8_t *untiled,\n                    uint32_t untiledStride)\n   {\n      for (int y = 0; y < MicroTileHeight; y += 4) {\n         copyDepthXYGroup(tiled, untiled, untiledStride, 0, y + 0, 0, y + 0);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 1, y + 0, 0, y + 1);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 2, y + 0, 1, y + 0);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 3, y + 0, 1, y + 1);\n\n         copyDepthXYGroup(tiled, untiled, untiledStride, 0, y + 1, 0, y + 2);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 1, y + 1, 0, y + 3);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 2, y + 1, 1, y + 2);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 3, y + 1, 1, y + 3);\n\n         copyDepthXYGroup(tiled, untiled, untiledStride, 0, y + 2, 2, y + 0);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 1, y + 2, 2, y + 1);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 2, y + 2, 3, y + 0);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 3, y + 2, 3, y + 1);\n\n         copyDepthXYGroup(tiled, untiled, untiledStride, 0, y + 3, 2, y + 2);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 1, y + 3, 2, y + 3);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 2, y + 3, 3, y + 2);\n         copyDepthXYGroup(tiled, untiled, untiledStride, 3, y + 3, 3, y + 3);\n      }\n   }\n\n   struct Params\n   {\n      uint32_t firstSliceIndex;\n\n      // Micro tiling parameters\n      uint32_t numTilesPerRow;\n      uint32_t numTilesPerSlice;\n      uint32_t thinMicroTileBytes;\n      uint32_t thickSliceBytes;\n\n      // Macro tiling parameters\n      uint32_t bankSwizzle;\n      uint32_t pipeSwizzle;\n      uint32_t bankSwapWidth;\n   };\n\n   /*\n   We always execute in a problem-space which starts at a thick-slice\n   boundary.  The tiled pointer will point to the start of that boundary\n   and the untiled pointer will point to the start of a specific slice\n   within it (if untiling on a not-thick-slice-aligned boundary).\n   */\n\n   static inline void\n   retileMicro(const Params& params,\n               uint32_t tileIndex,\n               uint8_t *untiled,\n               uint8_t *tiled)\n   {\n      const uint32_t thinSliceBytes = params.thickSliceBytes / MicroTileThickness;\n      const uint32_t untiledStride = params.numTilesPerRow * MicroTileWidth * BytesPerElement;\n      const uint32_t thickMicroTileBytes = params.thinMicroTileBytes * MicroTileThickness;\n\n      const uint32_t dispatchSliceIndex = tileIndex / params.numTilesPerSlice;\n      const uint32_t sliceTileIndex = tileIndex % params.numTilesPerSlice;\n\n      // Find the global slice index we are currently at.\n      const uint32_t srcSliceIndex = params.firstSliceIndex + dispatchSliceIndex;\n\n      // We need to identify where inside the current thick slice we are.\n      const uint32_t localSliceIndex = srcSliceIndex % MicroTileThickness;\n\n      // Calculate the offset to our untiled data starting from the thick slice\n      const uint32_t srcTileY = sliceTileIndex / params.numTilesPerRow;\n      const uint32_t srcTileX = sliceTileIndex % params.numTilesPerRow;\n\n      uint32_t untiledOffset =\n         (localSliceIndex * thinSliceBytes) +\n         (srcTileX * MicroTileWidth * BytesPerElement) +\n         (srcTileY * params.numTilesPerRow * params.thinMicroTileBytes);\n\n      // Calculate the offset to our tiled data starting from the thick slice\n      uint32_t tiledOffset =\n         (localSliceIndex * params.thinMicroTileBytes) +\n         sliceTileIndex * thickMicroTileBytes;\n\n      // In the case that we are using thick micro tiles, we need to advance our\n      // offsets to the current thick slice boundary that we are at.\n      const uint32_t firstThickSliceIndex = params.firstSliceIndex / MicroTileThickness;\n      const uint32_t thickSliceIndex = srcSliceIndex / MicroTileThickness;\n      const uint32_t thickSliceOffset = (thickSliceIndex - firstThickSliceIndex) * params.thickSliceBytes;\n      tiledOffset += thickSliceOffset;\n      untiledOffset += thickSliceOffset;\n\n      // The untiled pointers are offset forward by the local slice index already,\n      // we need to back it up since our calculations above consider it.\n      const uint32_t firstThinSliceIndex = params.firstSliceIndex % MicroTileThickness;\n      untiledOffset -= firstThinSliceIndex * thinSliceBytes;\n\n      // Update our pointers based on the calculated offset.\n      tiled = tiled + tiledOffset;\n      untiled = untiled + untiledOffset;\n\n      if constexpr (IsDepth) {\n         retileMicroDepth(tiled, untiled, untiledStride);\n      } else {\n         if constexpr (BitsPerElement == 8) {\n            retileMicro8(tiled, untiled, untiledStride);\n         } else if constexpr (BitsPerElement == 16) {\n            retileMicro16(tiled, untiled, untiledStride);\n         } else if constexpr (BitsPerElement == 32) {\n            retileMicro32(tiled, untiled, untiledStride);\n         } else if constexpr (BitsPerElement == 64) {\n            retileMicro64(tiled, untiled, untiledStride);\n         } else if constexpr (BitsPerElement == 128) {\n            retileMicro128(tiled, untiled, untiledStride);\n         }\n      }\n   }\n\n   static inline void\n   retileMacro(const Params& params,\n               uint32_t tileIndex,\n               uint8_t *untiled,\n               uint8_t *tiled)\n   {\n      const uint32_t thinSliceBytes = params.thickSliceBytes / MicroTileThickness;\n      const uint32_t untiledStride = params.numTilesPerRow * MicroTileWidth * BytesPerElement;\n\n      const uint32_t dispatchSliceIndex = tileIndex / params.numTilesPerSlice;\n      const uint32_t sliceTileIndex = tileIndex % params.numTilesPerSlice;\n\n      // Find the global slice index we are currently at.\n      const uint32_t srcSliceIndex = params.firstSliceIndex + dispatchSliceIndex;\n\n      // We need to identify where inside the current thick slice we are.\n      const uint32_t localSliceIndex = srcSliceIndex % MicroTileThickness;\n\n      // Calculate the thickSliceIndex\n      const uint32_t thickSliceIndex = srcSliceIndex / MicroTileThickness;\n\n      // Calculate our tile positions\n      const uint32_t microTilesPerMacro = MacroTileWidth * MacroTileHeight;\n      const uint32_t macroTilesPerRow = params.numTilesPerRow / MacroTileWidth;\n      const uint32_t microTilesPerMacroRow = microTilesPerMacro * macroTilesPerRow;\n\n      const uint32_t srcMacroTileY = sliceTileIndex / microTilesPerMacroRow;\n      const uint32_t macroRowTileIndex = sliceTileIndex % microTilesPerMacroRow;\n\n      const uint32_t srcMacroTileX = macroRowTileIndex / microTilesPerMacro;\n      const uint32_t microTileIndex = macroRowTileIndex % microTilesPerMacro;\n\n      const uint32_t srcMicroTileY = microTileIndex / MacroTileWidth;\n      const uint32_t srcMicroTileX = microTileIndex % MacroTileWidth;\n\n      const uint32_t srcTileX = srcMacroTileX * MacroTileWidth + srcMicroTileX;\n      const uint32_t srcTileY = srcMacroTileY * MacroTileHeight + srcMicroTileY;\n\n      // Figure out what our untiled offset shall be\n      uint32_t untiledOffset =\n         (localSliceIndex * thinSliceBytes) +\n         (srcTileX * MicroTileWidth * BytesPerElement) +\n         (srcTileY * MicroTileHeight * untiledStride);\n\n      // Calculate the offset to our untiled data starting from the thick slice\n      const uint32_t macroTileIndex = (srcMacroTileY * macroTilesPerRow) + srcMacroTileX;\n      const uint32_t macroTileOffset = macroTileIndex * MacroTileBytes;\n      const uint32_t tiledBaseOffset =\n         (macroTileOffset >> (NumBankBits + NumPipeBits)) +\n         (localSliceIndex * params.thinMicroTileBytes);\n\n      const uint32_t offsetHigh = (tiledBaseOffset & ~GroupMask) << (NumBankBits + NumPipeBits);\n      const uint32_t offsetLow = tiledBaseOffset & GroupMask;\n\n      // Calculate our bank/pipe/sample rotations and swaps\n      uint32_t bankSliceRotation = 0;\n      uint32_t pipeSliceRotation = 0;\n      if (!IsMacro3X) {\n         // 2_ format\n         bankSliceRotation = ((NumBanks >> 1) - 1)* thickSliceIndex;\n      } else {\n         // 3_ format\n         bankSliceRotation = thickSliceIndex / NumPipes;\n         pipeSliceRotation = thickSliceIndex;\n      }\n\n      uint32_t bankSwapRotation = 0;\n      if (IsBankSwapped) {\n         const uint32_t bankSwapOrder[] = { 0, 1, 3, 2 };\n         const uint32_t swapIndex = ((srcMacroTileX * MicroTileWidth * MacroTileWidth) / params.bankSwapWidth);\n         bankSwapRotation = bankSwapOrder[swapIndex % NumBanks];\n      }\n\n      uint32_t bank = 0;\n      bank |= ((srcTileX >> 0) & 1) ^ ((srcTileY >> 2) & 1);\n      bank |= (((srcTileX >> 1) & 1) ^ ((srcTileY >> 1) & 1)) << 1;\n      bank ^= (params.bankSwizzle + bankSliceRotation) & (NumBanks - 1);\n      bank ^= bankSwapRotation;\n\n      uint32_t pipe = 0;\n      pipe |= ((srcTileX >> 0) & 1) ^ ((srcTileY >> 0) & 1);\n      pipe ^= (params.pipeSwizzle + pipeSliceRotation) & (NumPipes - 1);\n\n      uint32_t tiledOffset =\n         (bank << (NumGroupBits + NumPipeBits)) |\n         (pipe << NumGroupBits) |\n         offsetLow | offsetHigh;\n\n      // In the case that we are using thick micro tiles, we need to advance our\n      // offsets to the current thick slice boundary that we are at.\n      const uint32_t firstThickSliceIndex = params.firstSliceIndex / MicroTileThickness;\n      const uint32_t thickSliceOffset = (thickSliceIndex - firstThickSliceIndex) * params.thickSliceBytes;\n      tiledOffset += thickSliceOffset;\n      untiledOffset += thickSliceOffset;\n\n      // The untiled pointers are offset forward by the local slice index already,\n      // we need to back it up since our calculations above consider it.\n      const uint32_t firstThinSliceIndex = params.firstSliceIndex % MicroTileThickness;\n      untiledOffset -= firstThinSliceIndex * thinSliceBytes;\n\n      // Update our pointers based on the calculated offset.\n      tiled = tiled + tiledOffset;\n      untiled = untiled + untiledOffset;\n\n      if constexpr (IsDepth) {\n         retileMicroDepth(tiled, untiled, untiledStride);\n      } else {\n         if constexpr (BitsPerElement == 8) {\n            retileMicro8(tiled, untiled, untiledStride);\n         } else if constexpr (BitsPerElement == 16) {\n            retileMicro16(tiled, untiled, untiledStride);\n         } else if constexpr (BitsPerElement == 32) {\n            retileMicro32(tiled, untiled, untiledStride);\n         } else if constexpr (BitsPerElement == 64) {\n            retileMicro64(tiled, untiled, untiledStride);\n         } else if constexpr (BitsPerElement == 128) {\n            retileMicro128(tiled, untiled, untiledStride);\n         }\n      }\n   }\n\n   static inline void\n   retile(const Params& params,\n          uint32_t tileIndex,\n          uint8_t *untiled,\n          uint8_t *tiled)\n   {\n      if constexpr (IsMacroTiling) {\n         retileMacro(params, tileIndex, untiled, tiled);\n      } else {\n         retileMicro(params, tileIndex, untiled, tiled);\n      }\n   }\n};\n\ntemplate<bool IsUntiling,\n   TileMode RetileMode,\n   uint32_t BitsPerElement,\n   bool IsDepth>\n   static inline void\nretileTiledSurface3(const RetileInfo& info,\n                    uint8_t *untiled,\n                    uint8_t *tiled,\n                    uint32_t firstSlice,\n                    uint32_t numSlices)\n{\n   using Retiler = RetileCore<\n      IsUntiling,\n      getMicroTileThickness(RetileMode),\n      getMacroTileWidth(RetileMode),\n      getMacroTileHeight(RetileMode),\n      getTileModeIs3X(RetileMode),\n      getTileModeIsBankSwapped(RetileMode),\n      BitsPerElement,\n      IsDepth>;\n\n   typename Retiler::Params params;\n   params.firstSliceIndex = firstSlice;\n\n   params.numTilesPerRow = info.numTilesPerRow;\n   params.numTilesPerSlice = info.numTilesPerSlice;\n   params.thinMicroTileBytes = info.thickMicroTileBytes / info.microTileThickness;\n   params.thickSliceBytes = info.thinSliceBytes * info.microTileThickness;\n\n   params.bankSwizzle = info.bankSwizzle;\n   params.pipeSwizzle = info.pipeSwizzle;\n   params.bankSwapWidth = info.bankSwapWidth;\n\n   uint32_t numTiles = numSlices * info.numTilesPerSlice;\n   for (auto tileIndex = 0u; tileIndex < numTiles; ++tileIndex) {\n      Retiler::retile(params, tileIndex, untiled, tiled);\n   }\n}\n\ntemplate<bool IsUntiling, TileMode RetileMode>\nstatic inline void\nretileTiledSurface2(const RetileInfo& info,\n                    uint8_t *untiled,\n                    uint8_t *tiled,\n                    uint32_t firstSlice,\n                    uint32_t numSlices)\n{\n   if (!info.isDepth) {\n      if (info.bitsPerElement == 8) {\n         retileTiledSurface3<IsUntiling, RetileMode, 8, false>(info, untiled, tiled, firstSlice, numSlices);\n      } else if (info.bitsPerElement == 16) {\n         retileTiledSurface3<IsUntiling, RetileMode, 16, false>(info, untiled, tiled, firstSlice, numSlices);\n      } else if (info.bitsPerElement == 32) {\n         retileTiledSurface3<IsUntiling, RetileMode, 32, false>(info, untiled, tiled, firstSlice, numSlices);\n      } else if (info.bitsPerElement == 64) {\n         retileTiledSurface3<IsUntiling, RetileMode, 64, false>(info, untiled, tiled, firstSlice, numSlices);\n      } else if (info.bitsPerElement == 128) {\n         retileTiledSurface3<IsUntiling, RetileMode, 128, false>(info, untiled, tiled, firstSlice, numSlices);\n      } else {\n         decaf_abort(\"Invalid color surface bpp\");\n      }\n   } else {\n      if (info.bitsPerElement == 16) {\n         retileTiledSurface3<IsUntiling, RetileMode, 16, true>(info, untiled, tiled, firstSlice, numSlices);\n      } else if (info.bitsPerElement == 32) {\n         retileTiledSurface3<IsUntiling, RetileMode, 32, true>(info, untiled, tiled, firstSlice, numSlices);\n      } else if (info.bitsPerElement == 64) {\n         retileTiledSurface3<IsUntiling, RetileMode, 64, true>(info, untiled, tiled, firstSlice, numSlices);\n      } else {\n         decaf_abort(\"Invalid depth surface bpp\");\n      }\n   }\n}\n\ntemplate<bool IsUntiling>\nstatic inline void\nretileTiledSurface(const RetileInfo& info,\n                   uint8_t *untiled,\n                   uint8_t *tiled,\n                   uint32_t firstSlice,\n                   uint32_t numSlices)\n{\n   switch (info.tileMode) {\n   case TileMode::Micro1DTiledThin1:\n      retileTiledSurface2<IsUntiling, TileMode::Micro1DTiledThin1>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Micro1DTiledThick:\n      retileTiledSurface2<IsUntiling, TileMode::Micro1DTiledThick>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro2DTiledThin1:\n      retileTiledSurface2<IsUntiling, TileMode::Macro2DTiledThin1>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro2DTiledThin2:\n      retileTiledSurface2<IsUntiling, TileMode::Macro2DTiledThin2>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro2DTiledThin4:\n      retileTiledSurface2<IsUntiling, TileMode::Macro2DTiledThin4>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro2DTiledThick:\n      retileTiledSurface2<IsUntiling, TileMode::Macro2DTiledThick>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro2BTiledThin1:\n      retileTiledSurface2<IsUntiling, TileMode::Macro2BTiledThin1>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro2BTiledThin2:\n      retileTiledSurface2<IsUntiling, TileMode::Macro2BTiledThin2>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro2BTiledThin4:\n      retileTiledSurface2<IsUntiling, TileMode::Macro2BTiledThin4>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro2BTiledThick:\n      retileTiledSurface2<IsUntiling, TileMode::Macro2BTiledThick>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro3DTiledThin1:\n      retileTiledSurface2<IsUntiling, TileMode::Macro3DTiledThin1>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro3DTiledThick:\n      retileTiledSurface2<IsUntiling, TileMode::Macro3DTiledThick>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro3BTiledThin1:\n      retileTiledSurface2<IsUntiling, TileMode::Macro3BTiledThin1>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   case TileMode::Macro3BTiledThick:\n      retileTiledSurface2<IsUntiling, TileMode::Macro3BTiledThick>(info, untiled, tiled, firstSlice, numSlices);\n      break;\n   default:\n      decaf_abort(\"Unexpected tiled tile mode\");\n   }\n}\n\ntemplate<bool IsUntiling>\nstatic void\nretileLinearSurface(const RetileInfo& info,\n                    uint8_t *untiled,\n                    uint8_t *tiled,\n                    uint32_t firstSlice,\n                    uint32_t numSlices)\n{\n   // These functions assume that the tiled side is aligned to a thick slice\n   // boundary, since this is linear, we just pull that pointer forward.\n   uint32_t thinSliceAdjust = firstSlice % info.microTileThickness;\n   uint32_t thinMicroTileBytes = info.thickMicroTileBytes / info.microTileThickness;\n   tiled += thinSliceAdjust * info.numTilesPerSlice * thinMicroTileBytes;\n\n   // Calculate the total number of tiles to copy.\n   uint32_t totalTiles = numSlices * info.numTilesPerSlice;\n\n   // Copy all the bytes from one place to the other.\n   uint32_t totalBytes = totalTiles * thinMicroTileBytes;\n   if (IsUntiling) {\n      std::memcpy(untiled, tiled, totalBytes);\n   } else {\n      std::memcpy(tiled, untiled, totalBytes);\n   }\n}\n\ntemplate<bool IsUntiling>\nstatic inline void\nretile(const RetileInfo& info,\n       uint8_t *untiled,\n       uint8_t *tiled,\n       uint32_t firstSlice,\n       uint32_t numSlices)\n{\n   if (info.tileMode == TileMode::LinearGeneral ||\n       info.tileMode == TileMode::LinearAligned) {\n      return retileLinearSurface<IsUntiling>(info, untiled, tiled, firstSlice, numSlices);\n   }\n\n   retileTiledSurface<IsUntiling>(info, untiled, tiled, firstSlice, numSlices);\n}\n\nvoid\nuntile(const RetileInfo& info,\n       uint8_t *untiled,\n       uint8_t *tiled,\n       uint32_t firstSlice,\n       uint32_t numSlices)\n{\n   retile<true>(info, untiled, tiled, firstSlice, numSlices);\n}\n\nvoid\ntile(const RetileInfo& info,\n     uint8_t *untiled,\n     uint8_t *tiled,\n     uint32_t firstSlice,\n     uint32_t numSlices)\n{\n   retile<false>(info, untiled, tiled, firstSlice, numSlices);\n}\n\n} // namespace gpu::tiling::cpu\n"
  },
  {
    "path": "src/libgpu/src/gpu7_tiling_vulkan.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"gpu7_tiling_vulkan.h\"\n\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <vulkan_shaders_bin/gpu7_tiling.comp.spv.h>\n\nnamespace gpu7::tiling::vulkan\n{\n\nstruct GeneralPushConstants\n{\n   uint32_t firstSliceIndex;\n   uint32_t maxTiles;\n};\n\nstruct MicroTilePushConstants : GeneralPushConstants\n{\n   uint32_t numTilesPerRow;\n   uint32_t numTilesPerSlice;\n   uint32_t thinMicroTileBytes;\n   uint32_t thickSliceBytes;\n};\n\nstruct MacroTilePushConstants : MicroTilePushConstants\n{\n   uint32_t bankSwizzle;\n   uint32_t pipeSwizzle;\n   uint32_t bankSwapWidth;\n};\n\nstruct TileShaderSpecialisation\n{\n   uint32_t isUntiling;\n   uint32_t microTileThickness;\n   uint32_t macroTileWidth;\n   uint32_t macroTileHeight;\n   uint32_t isMacro3X;\n   uint32_t isBankSwapped;\n   uint32_t bpp;\n   uint32_t isDepth;\n   uint32_t subGroupSize;\n};\n\nstruct BppDepthInfo\n{\n   uint32_t bpp;\n   bool isDepth;\n};\n\nstd::array<BppDepthInfo, 8> ValidBppDepths = { {\n   { 8, false }, { 16, false }, { 32, false }, { 64, false }, { 128, false },\n   { 16, true }, { 32, true }, { 64, true } } };\n\nstruct TileModeInfo\n{\n   TileMode tileMode;\n   uint32_t microTileThickness;\n   uint32_t macroTileWidth;\n   uint32_t macroTileHeight;\n   bool isMacro3X;\n   bool isBankSwapped;\n};\n\nstd::array<TileModeInfo, 14> ValidTileConfigs = { {\n   { TileMode::Micro1DTiledThin1, 1, 1, 1, false, false },\n   { TileMode::Micro1DTiledThick, 4, 1, 1, false, false },\n   { TileMode::Macro2DTiledThin1, 1, 4, 2, false, false },\n   { TileMode::Macro2DTiledThin2, 1, 2, 4, false, false },\n   { TileMode::Macro2DTiledThin4, 1, 1, 8, false, false },\n   { TileMode::Macro2DTiledThick, 4, 4, 2, false, false },\n   { TileMode::Macro2BTiledThin1, 1, 4, 2, false, true },\n   { TileMode::Macro2BTiledThin2, 1, 2, 4, false, true },\n   { TileMode::Macro2BTiledThin4, 1, 1, 8, false, true },\n   { TileMode::Macro2BTiledThick, 4, 4, 2, false, true },\n   { TileMode::Macro3DTiledThin1, 1, 4, 2, true, false },\n   { TileMode::Macro3DTiledThick, 4, 4, 2, true, false },\n   { TileMode::Macro3BTiledThin1, 1, 4, 2, true, true },\n   { TileMode::Macro3BTiledThick, 4, 4, 2, true, true },\n} };\n\n// TODO: This should be based on the GPU in use\nstatic const uint32_t GpuSubGroupSize = 32;\n\nstatic inline uint32_t\ngetRetileSpecKey(uint32_t bpp, bool isDepth, TileMode tileMode, bool isUntiling)\n{\n   uint32_t fmtKey = bpp + (isDepth ? 100 : 0);\n   uint32_t tileModeKey = static_cast<uint32_t>(tileMode);\n   uint32_t isUntilingKey = isUntiling ? 1 : 0;\n\n   uint32_t key = 0;\n   key |= (fmtKey << 0) & 0x0000FFFF;\n   key |= (tileModeKey << 16) & 0x0FFF0000;\n   key |= (isUntilingKey << 28) & 0xF0000000;\n   return key;\n}\n\nvoid\nRetiler::initialise(vk::Device device)\n{\n   mDevice = device;\n\n   std::array<vk::DescriptorSetLayoutBinding, 2> descriptorSetLayoutBinding = {};\n\n   descriptorSetLayoutBinding[0].binding = 0;\n   descriptorSetLayoutBinding[0].descriptorType = vk::DescriptorType::eStorageBuffer;\n   descriptorSetLayoutBinding[0].descriptorCount = 1;\n   descriptorSetLayoutBinding[0].stageFlags = vk::ShaderStageFlagBits::eCompute;\n\n   descriptorSetLayoutBinding[1].binding = 1;\n   descriptorSetLayoutBinding[1].descriptorType = vk::DescriptorType::eStorageBuffer;\n   descriptorSetLayoutBinding[1].descriptorCount = 1;\n   descriptorSetLayoutBinding[1].stageFlags = vk::ShaderStageFlagBits::eCompute;\n\n   vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {};\n   descriptorSetLayoutCreateInfo.bindingCount = static_cast<uint32_t>(descriptorSetLayoutBinding.size());\n   descriptorSetLayoutCreateInfo.pBindings = descriptorSetLayoutBinding.data();\n\n   mDescriptorSetLayout = mDevice.createDescriptorSetLayout(descriptorSetLayoutCreateInfo);\n\n   // We create a push constant block that is the size of the largest push\n   // constants block we could use, saves us from having multiple layouts.\n   vk::PushConstantRange pushConstant = {};\n   pushConstant.stageFlags = vk::ShaderStageFlagBits::eCompute;\n   pushConstant.offset = 0;\n   pushConstant.size = sizeof(MacroTilePushConstants);\n\n   vk::PipelineLayoutCreateInfo pipelineLayoutDesc;\n   pipelineLayoutDesc.setLayoutCount = 1;\n   pipelineLayoutDesc.pSetLayouts = &mDescriptorSetLayout;\n   pipelineLayoutDesc.pushConstantRangeCount = 1;\n   pipelineLayoutDesc.pPushConstantRanges = &pushConstant;\n   mPipelineLayout = mDevice.createPipelineLayout(pipelineLayoutDesc);\n\n   vk::ShaderModuleCreateInfo shaderDesc;\n   shaderDesc.pCode = reinterpret_cast<const uint32_t*>(gpu7_tiling_comp_spv);\n   shaderDesc.codeSize = gpu7_tiling_comp_spv_size;\n   mShader = mDevice.createShaderModule(shaderDesc);\n\n   static const std::array<vk::SpecializationMapEntry, 9> specEntries = { {\n      { 0, offsetof(TileShaderSpecialisation, isUntiling), sizeof(uint32_t) },\n      { 1, offsetof(TileShaderSpecialisation, microTileThickness), sizeof(uint32_t) },\n      { 2, offsetof(TileShaderSpecialisation, macroTileWidth), sizeof(uint32_t) },\n      { 3, offsetof(TileShaderSpecialisation, macroTileHeight), sizeof(uint32_t) },\n      { 4, offsetof(TileShaderSpecialisation, isMacro3X), sizeof(uint32_t) },\n      { 5, offsetof(TileShaderSpecialisation, isBankSwapped), sizeof(uint32_t) },\n      { 6, offsetof(TileShaderSpecialisation, bpp), sizeof(uint32_t) },\n      { 7, offsetof(TileShaderSpecialisation, isDepth), sizeof(uint32_t) },\n      { 8, offsetof(TileShaderSpecialisation, subGroupSize), sizeof(uint32_t) }\n   } };\n\n   for (auto tileConfig : ValidTileConfigs) {\n      for (auto bppDepth : ValidBppDepths) {\n         for (auto tileOrUntile : { true, false }) {\n            uint32_t specKey = getRetileSpecKey(bppDepth.bpp,\n                                                bppDepth.isDepth,\n                                                tileConfig.tileMode,\n                                                tileOrUntile);\n\n            TileShaderSpecialisation specValues;\n            specValues.isUntiling = tileOrUntile ? 1 : 0;\n            specValues.microTileThickness = tileConfig.microTileThickness;\n            specValues.macroTileWidth = tileConfig.macroTileWidth;\n            specValues.macroTileHeight = tileConfig.macroTileHeight;\n            specValues.isMacro3X = tileConfig.isMacro3X ? 1 : 0;\n            specValues.isBankSwapped = tileConfig.isBankSwapped ? 1 : 0;\n            specValues.bpp = bppDepth.bpp;\n            specValues.isDepth = bppDepth.isDepth ? 1 : 0;\n            specValues.subGroupSize = GpuSubGroupSize;\n\n            vk::SpecializationInfo specInfo;\n            specInfo.mapEntryCount = static_cast<uint32_t>(specEntries.size());\n            specInfo.pMapEntries = specEntries.data();\n            specInfo.dataSize = sizeof(TileShaderSpecialisation);\n            specInfo.pData = &specValues;\n\n            vk::PipelineShaderStageCreateInfo shaderStageDesc = {};\n            shaderStageDesc.stage = vk::ShaderStageFlagBits::eCompute;\n            shaderStageDesc.module = mShader;\n            shaderStageDesc.pName = \"main\";\n            shaderStageDesc.pSpecializationInfo = &specInfo;\n\n            vk::ComputePipelineCreateInfo pipelineDesc = {};\n            pipelineDesc.stage = shaderStageDesc;\n            pipelineDesc.layout = mPipelineLayout;\n\n            auto pipeline = mDevice.createComputePipeline(vk::PipelineCache(), pipelineDesc);\n            mPipelines.insert({ specKey, pipeline.value });\n         }\n      }\n   }\n}\n\n/*\nWhen retiling THICK tile modes, this algorithm assumes that you've aligned the tiled\nbuffer to the edge of a group of 4 slices and the untiled buffer directly to the slice.\n*/\nvoid\nRetiler::retile(bool wantsUntile,\n                const RetileInfo& retileInfo,\n                vk::DescriptorSet& descriptorSet,\n                vk::CommandBuffer& commandBuffer,\n                vk::Buffer tiledBuffer, uint32_t tiledOffset,\n                vk::Buffer untiledBuffer, uint32_t untiledOffset,\n                uint32_t firstSlice, uint32_t numSlices)\n{\n   // Would be odd to dispatch a retile when we are not tiled...\n   decaf_check(retileInfo.isTiled);\n\n   // Calcualate the spec key for this retiler configuration\n   // Due to know known issues with depth, we instead retile it normally.\n   auto specKey = getRetileSpecKey(retileInfo.bitsPerElement,\n                                   retileInfo.isDepth,\n                                   retileInfo.tileMode,\n                                   wantsUntile);\n\n   // Find the specific pipeline for this configuration\n   auto pipelineIter = mPipelines.find(specKey);\n   if (pipelineIter == mPipelines.end()) {\n      decaf_abort(\"Attempted to retile an unsupported surface configuration\");\n   }\n\n   // Bind the pipeline for execution\n   commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipelineIter->second);\n\n   // Calculate the sizing for our retiling space.  Note that we have to make sure\n   // we are correctly aligning the location when the user is doing partial groups\n   // of slices on thickness>1\n   auto alignedFirstSlice = align_down(firstSlice, retileInfo.microTileThickness);\n   auto alignedLastSlice = align_up(firstSlice + numSlices, retileInfo.microTileThickness);\n   auto alignedNumSlices = alignedLastSlice - alignedFirstSlice;\n   uint32_t tiledSize = alignedNumSlices * retileInfo.thinSliceBytes;\n   uint32_t untiledSize = numSlices * retileInfo.thinSliceBytes;\n\n   std::array<vk::DescriptorBufferInfo, 2> descriptorBufferDescs;\n   descriptorBufferDescs[0].buffer = tiledBuffer;\n   descriptorBufferDescs[0].offset = tiledOffset;\n   descriptorBufferDescs[0].range = tiledSize;\n   descriptorBufferDescs[1].buffer = untiledBuffer;\n   descriptorBufferDescs[1].offset = untiledOffset;\n   descriptorBufferDescs[1].range = untiledSize;\n\n   vk::WriteDescriptorSet setWriteDesc;\n   setWriteDesc.dstSet = descriptorSet;\n   setWriteDesc.dstBinding = 0;\n   setWriteDesc.descriptorCount = static_cast<uint32_t>(descriptorBufferDescs.size());\n   setWriteDesc.descriptorType = vk::DescriptorType::eStorageBuffer;\n   setWriteDesc.pBufferInfo = descriptorBufferDescs.data();\n   mDevice.updateDescriptorSets({ setWriteDesc }, {});\n\n   // Bind our new descriptor set\n   commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, mPipelineLayout, 0, { descriptorSet }, {});\n\n   // Calculate the number of tiles we need to process!\n   uint32_t numDispatchTiles = numSlices * retileInfo.numTilesPerSlice;\n\n   // Setup our arguments\n   if (!retileInfo.isMacroTiled) {\n      MicroTilePushConstants pushConstants;\n      pushConstants.firstSliceIndex = firstSlice;\n      pushConstants.maxTiles = numDispatchTiles;\n\n      pushConstants.numTilesPerRow = retileInfo.numTilesPerRow;\n      pushConstants.numTilesPerSlice = retileInfo.numTilesPerSlice;\n      pushConstants.thinMicroTileBytes = retileInfo.thickMicroTileBytes / retileInfo.microTileThickness;\n      pushConstants.thickSliceBytes = retileInfo.thinSliceBytes * retileInfo.microTileThickness;\n\n      commandBuffer.pushConstants<MicroTilePushConstants>(\n         mPipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, { pushConstants });\n   } else {\n      MacroTilePushConstants pushConstants;\n      pushConstants.firstSliceIndex = firstSlice;\n      pushConstants.maxTiles = numDispatchTiles;\n\n      pushConstants.numTilesPerRow = retileInfo.numTilesPerRow;\n      pushConstants.numTilesPerSlice = retileInfo.numTilesPerSlice;\n      pushConstants.thinMicroTileBytes = retileInfo.thickMicroTileBytes / retileInfo.microTileThickness;\n      pushConstants.thickSliceBytes = retileInfo.thinSliceBytes * retileInfo.microTileThickness;\n\n      pushConstants.bankSwizzle = retileInfo.bankSwizzle;\n      pushConstants.pipeSwizzle = retileInfo.pipeSwizzle;\n      pushConstants.bankSwapWidth = retileInfo.bankSwapWidth;\n\n      commandBuffer.pushConstants<MacroTilePushConstants>(\n         mPipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, { pushConstants });\n   }\n\n   // Calculate the number of groups we need to dispatch!\n   uint32_t numDispatchGroups =\n      align_up(numDispatchTiles, GpuSubGroupSize) / GpuSubGroupSize;\n\n   // Actually dispatch the work to the GPU\n   commandBuffer.dispatch(numDispatchGroups, 1, 1);\n}\n\nRetileHandle\nRetiler::retile(bool wantsUntile,\n                const RetileInfo& retileInfo,\n                   vk::CommandBuffer& commandBuffer,\n                   vk::Buffer dstBuffer, uint32_t dstOffset,\n                   vk::Buffer srcBuffer, uint32_t srcOffset,\n                uint32_t firstSlice, uint32_t numSlices)\n{\n   auto handle = allocateHandle();\n\n   vk::DescriptorSetAllocateInfo allocInfo;\n   allocInfo.descriptorSetCount = 1;\n   allocInfo.pSetLayouts = &mDescriptorSetLayout;\n   allocInfo.descriptorPool = handle->descriptorPool;\n   auto descriptorSets = mDevice.allocateDescriptorSets(allocInfo);\n\n   retile(wantsUntile,\n          retileInfo,\n          descriptorSets[0],\n          commandBuffer,\n          dstBuffer, dstOffset,\n          srcBuffer, srcOffset,\n          firstSlice, numSlices);\n\n   return handle;\n}\n\nRetiler::HandleImpl*\nRetiler::allocateHandle()\n{\n   Retiler::HandleImpl* handle = nullptr;\n\n   if (!mHandlesPool.empty()) {\n      handle = mHandlesPool.back();\n      mHandlesPool.pop_back();\n   }\n\n   if (!handle) {\n      std::vector<vk::DescriptorPoolSize> descriptorPoolSizes = {\n         vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 2 * 13),\n      };\n\n      vk::DescriptorPoolCreateInfo descriptorPoolInfo;\n      descriptorPoolInfo.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size());\n      descriptorPoolInfo.pPoolSizes = descriptorPoolSizes.data();\n      descriptorPoolInfo.maxSets = static_cast<uint32_t>(13);\n      auto descriptorPool = mDevice.createDescriptorPool(descriptorPoolInfo);\n\n      handle = new Retiler::HandleImpl();\n      handle->descriptorPool = descriptorPool;\n   }\n\n   return handle;\n}\n\nvoid\nRetiler::releaseHandle(Retiler::HandleImpl* handle)\n{\n   mDevice.resetDescriptorPool(handle->descriptorPool);\n   mHandlesPool.push_back(handle);\n}\n\n}\n\n#endif // DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/gpu_clock.h",
    "content": "#pragma once\n#include <chrono>\n\nnamespace gpu::clock\n{\n\nusing Time = int64_t;\n\ninline Time\nnow()\n{\n   return std::chrono::steady_clock::now().time_since_epoch().count();\n}\n\n} // namespace gpu::clock\n"
  },
  {
    "path": "src/libgpu/src/gpu_configstorage.cpp",
    "content": "#include \"gpu_config.h\"\n#include \"gpu_configstorage.h\"\n\n#include <common/configstorage.h>\n\nnamespace gpu\n{\n\nstatic ConfigStorage<Settings> sSettings;\n\nstd::shared_ptr<const Settings>\nconfig()\n{\n   return sSettings.get();\n}\n\nvoid\nsetConfig(const Settings &settings)\n{\n   sSettings.set(std::make_shared<Settings>(settings));\n}\n\nvoid\nregisterConfigChangeListener(ConfigStorage<Settings>::ChangeListener listener)\n{\n   sSettings.addListener(listener);\n}\n\n} // namespace gpu\n"
  },
  {
    "path": "src/libgpu/src/gpu_configstorage.h",
    "content": "#pragma once\n#include \"gpu_config.h\"\n#include <common/configstorage.h>\n\nnamespace gpu\n{\n\nvoid\nregisterConfigChangeListener(ConfigStorage<Settings>::ChangeListener listener);\n\n} // namespace gpu\n"
  },
  {
    "path": "src/libgpu/src/gpu_event.cpp",
    "content": "#include \"gpu.h\"\n#include \"gpu_event.h\"\n\nnamespace gpu\n{\n\nstatic FlipCallbackFn sFlipCallbackFn = nullptr;\n\nvoid\nsetFlipCallback(FlipCallbackFn callback)\n{\n   sFlipCallbackFn = callback;\n}\n\nvoid\nonFlip()\n{\n   if (sFlipCallbackFn) {\n      sFlipCallbackFn();\n   }\n}\n\n} // namespace gpu\n"
  },
  {
    "path": "src/libgpu/src/gpu_event.h",
    "content": "#pragma once\n#include <cstdint>\n\nnamespace gpu\n{\n\nvoid\nonFlip();\n\n} // namespace gpu\n"
  },
  {
    "path": "src/libgpu/src/gpu_graphicsdriver.cpp",
    "content": "#include \"gpu_graphicsdriver.h\"\n#include \"gpu_config.h\"\n\n#include \"null/null_driver.h\"\n#include \"vulkan/vulkan_driver.h\"\n\nnamespace gpu\n{\n\nGraphicsDriver *\ncreateGraphicsDriver()\n{\n   switch (config()->display.backend) {\n   case DisplaySettings::Null:\n      return createGraphicsDriver(GraphicsDriverType::Null);\n   case DisplaySettings::Vulkan:\n      return createGraphicsDriver(GraphicsDriverType::Vulkan);\n   default:\n      return nullptr;\n   }\n}\n\nGraphicsDriver *\ncreateGraphicsDriver(GraphicsDriverType type)\n{\n   switch (type) {\n   case GraphicsDriverType::Null:\n      return new null::Driver{};\n#ifdef DECAF_VULKAN\n   case GraphicsDriverType::Vulkan:\n      return new vulkan::Driver{};\n#endif\n   default:\n      return nullptr;\n   }\n}\n\n} // namespace gpu\n"
  },
  {
    "path": "src/libgpu/src/gpu_ih.cpp",
    "content": "#include \"gpu_ih.h\"\n\n#include <atomic>\n#include <condition_variable>\n#include <mutex>\n#include <vector>\n\nnamespace gpu::ih\n{\n\nstatic std::vector<Entry>\nmWriteVector;\n\nstatic std::vector<Entry>\nmReadVector;\n\nstatic std::mutex\nsMutex;\n\nstatic InterruptCallbackFn\nsInterruptCallback = nullptr;\n\nstatic std::atomic<uint32_t>\nsInterruptControl { 0u };\n\nvoid\nwrite(const Entries &entries)\n{\n   auto generateInterrupt = false;\n\n   {\n      std::unique_lock<std::mutex> lock { sMutex };\n      generateInterrupt = mWriteVector.empty();\n      mWriteVector.insert(mWriteVector.end(), entries.begin(), entries.end());\n   }\n\n   if (generateInterrupt && sInterruptCallback) {\n      sInterruptCallback();\n   }\n}\n\nEntries\nread()\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   mReadVector.clear();\n   mReadVector.swap(mWriteVector);\n   return mReadVector;\n}\n\n/**\n * Set callback to be called when a GPU interrupt is triggered.\n */\nvoid\nsetInterruptCallback(InterruptCallbackFn callback)\n{\n   sInterruptCallback = callback;\n}\n\nvoid\nenable(latte::CP_INT_CNTL cntl)\n{\n   sInterruptControl |= cntl.value;\n}\n\nvoid\ndisable(latte::CP_INT_CNTL cntl)\n{\n   sInterruptControl &= ~cntl.value;\n}\n\n} // namespace gpu::ih\n"
  },
  {
    "path": "src/libgpu/src/gpu_ringbuffer.cpp",
    "content": "#include \"gpu_ringbuffer.h\"\n\n#include <condition_variable>\n#include <mutex>\n#include <vector>\n\nnamespace gpu::ringbuffer\n{\n\nstatic std::vector<uint32_t>\nmWriteVector;\n\nstatic std::vector<uint32_t>\nmReadVector;\n\nstatic std::mutex\nsMutex;\n\nstatic std::condition_variable\nsConditionVariable;\n\nstatic bool\nsPendingWake = false;\n\nvoid\nwrite(const Buffer &items)\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   mWriteVector.insert(mWriteVector.end(), items.begin(), items.end());\n   sConditionVariable.notify_all();\n}\n\nBuffer\nread()\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   mReadVector.clear();\n   mReadVector.swap(mWriteVector);\n   return mReadVector;\n}\n\nbool\nwait()\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   if (mWriteVector.empty() && !sPendingWake) {\n      sConditionVariable.wait(lock);\n   }\n\n   sPendingWake = false;\n\n   return !mWriteVector.empty();\n}\n\nvoid\nwake()\n{\n   std::unique_lock<std::mutex> lock { sMutex };\n   sPendingWake = true;\n   sConditionVariable.notify_all();\n}\n\n} // namespace gpu::ringbuffer\n"
  },
  {
    "path": "src/libgpu/src/gpu_tiling.cpp",
    "content": "#include \"gpu_tiling.h\"\n\n#include <common/decaf_assert.h>\n#include <cstdlib>\n#include <cstring>\n\nnamespace gpu\n{\n\nstatic ADDR_HANDLE\ngAddrLibHandle = nullptr;\n\nstatic void *\nallocSysMem(const ADDR_ALLOCSYSMEM_INPUT *pInput)\n{\n   return std::malloc(pInput->sizeInBytes);\n}\n\nstatic ADDR_E_RETURNCODE\nfreeSysMem(const ADDR_FREESYSMEM_INPUT *pInput)\n{\n   std::free(pInput->pVirtAddr);\n   return ADDR_OK;\n}\n\nbool\ninitAddrLib()\n{\n   ADDR_CREATE_INPUT input;\n   ADDR_CREATE_OUTPUT output;\n   std::memset(&input, 0, sizeof(input));\n   std::memset(&output, 0, sizeof(output));\n\n   input.size = sizeof(ADDR_CREATE_INPUT);\n   output.size = sizeof(ADDR_CREATE_OUTPUT);\n\n   input.chipEngine = CIASICIDGFXENGINE_R600;\n   input.chipFamily = 0x51;\n   input.chipRevision = 71;\n   input.createFlags.fillSizeFields = 1;\n   input.regValue.gbAddrConfig = 0x44902;\n\n   input.callbacks.allocSysMem = &allocSysMem;\n   input.callbacks.freeSysMem = &freeSysMem;\n\n   auto result = AddrCreate(&input, &output);\n\n   if (result != ADDR_OK) {\n      return false;\n   }\n\n   gAddrLibHandle = output.hLib;\n   return true;\n}\n\nADDR_HANDLE\ngetAddrLibHandle()\n{\n   if (!gAddrLibHandle) {\n      initAddrLib();\n   }\n\n   return gAddrLibHandle;\n}\n\nstatic inline void\ncalcSurfaceBankPipeSwizzle(uint32_t swizzle, uint32_t *bankSwizzle, uint32_t *pipeSwizzle)\n{\n   auto handle = getAddrLibHandle();\n\n   ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT input;\n   ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT output;\n\n   std::memset(&input, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT));\n   std::memset(&output, 0, sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT));\n\n   input.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_INPUT);\n   output.size = sizeof(ADDR_EXTRACT_BANKPIPE_SWIZZLE_OUTPUT);\n\n   input.base256b = (swizzle >> 8) & 0xFF;\n   AddrExtractBankPipeSwizzle(handle, &input, &output);\n\n   *bankSwizzle = output.bankSwizzle;\n   *pipeSwizzle = output.pipeSwizzle;\n}\n\nvoid\nalignTiling(latte::SQ_TILE_MODE& tileMode,\n            latte::SQ_DATA_FORMAT& format,\n            uint32_t& swizzle,\n            uint32_t& pitch,\n            uint32_t& width,\n            uint32_t& height,\n            uint32_t& depth,\n            uint32_t& aa,\n            bool& isDepth,\n            uint32_t& bpp)\n{\n   auto handle = getAddrLibHandle();\n\n   // We only partially complete this, knowing that we only need\n   // some information which does not depend on it...\n   ADDR_COMPUTE_SURFACE_INFO_INPUT input;\n   memset(&input, 0, sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT));\n   input.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT);\n   input.tileMode = static_cast<AddrTileMode>(tileMode);\n   input.format = static_cast<AddrFormat>(format);\n   input.bpp = bpp;\n   input.width = pitch;\n   input.height = height;\n   input.numSlices = depth;\n   input.numSamples = 1;\n   input.numFrags = 0;\n   input.slice = 0;\n   input.mipLevel = 0;\n\n   ADDR_COMPUTE_SURFACE_INFO_OUTPUT output;\n   std::memset(&output, 0, sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT));\n   output.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT);\n\n   auto result = AddrComputeSurfaceInfo(handle, &input, &output);\n   decaf_check(result == ADDR_OK);\n\n   pitch = output.pitch;\n   height = output.height;\n}\n\nbool\ncopySurfacePixels(uint8_t *dstBasePtr,\n   uint32_t dstWidth,\n   uint32_t dstHeight,\n   ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &dstAddrInput,\n   uint8_t *srcBasePtr,\n   uint32_t srcWidth,\n   uint32_t srcHeight,\n   ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &srcAddrInput)\n{\n   auto handle = getAddrLibHandle();\n\n   decaf_check(srcAddrInput.bpp == dstAddrInput.bpp);\n   auto bpp = dstAddrInput.bpp;\n\n   auto srcAddrOutput = ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT { 0 };\n   srcAddrOutput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT);\n\n   auto dstAddrOutput = ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT { 0 };\n   dstAddrOutput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT);\n\n   for (auto y = 0u; y < dstHeight; ++y) {\n      for (auto x = 0u; x < dstWidth; ++x) {\n         srcAddrInput.x = srcWidth * x / dstWidth;\n         srcAddrInput.y = srcHeight * y / dstHeight;\n         AddrComputeSurfaceAddrFromCoord(handle, &srcAddrInput, &srcAddrOutput);\n\n         dstAddrInput.x = x;\n         dstAddrInput.y = y;\n         AddrComputeSurfaceAddrFromCoord(handle, &dstAddrInput, &dstAddrOutput);\n\n         auto src = &srcBasePtr[srcAddrOutput.addr];\n         auto dst = &dstBasePtr[dstAddrOutput.addr];\n         std::memcpy(dst, src, bpp / 8);\n      }\n   }\n\n   return true;\n}\n\nbool\nconvertFromTiled(\n   uint8_t *output,\n   uint32_t outputPitch,\n   uint8_t *input,\n   latte::SQ_TILE_MODE tileMode,\n   uint32_t swizzle,\n   uint32_t pitch,\n   uint32_t width,\n   uint32_t height,\n   uint32_t depth,\n   uint32_t aa,\n   bool isDepth,\n   uint32_t bpp,\n   uint32_t beginSlice,\n   uint32_t endSlice)\n{\n   if (endSlice == 0) {\n      endSlice = depth;\n   }\n\n   decaf_check(beginSlice >= 0);\n   decaf_check(endSlice > 0);\n   decaf_check(endSlice <= depth);\n\n   ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT srcAddrInput;\n   std::memset(&srcAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT));\n   srcAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT);\n   srcAddrInput.bpp = bpp;\n   srcAddrInput.pitch = pitch;\n   srcAddrInput.height = height;\n   srcAddrInput.numSlices = depth;\n   srcAddrInput.numSamples = 1 << aa;\n   srcAddrInput.tileMode = static_cast<AddrTileMode>(tileMode);\n   srcAddrInput.isDepth = isDepth;\n   srcAddrInput.tileBase = 0;\n   srcAddrInput.compBits = 0;\n   srcAddrInput.numFrags = 0;\n   calcSurfaceBankPipeSwizzle(swizzle,\n      &srcAddrInput.bankSwizzle,\n      &srcAddrInput.pipeSwizzle);\n\n   // Setup dst\n   ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT dstAddrInput;\n   std::memset(&dstAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT));\n   dstAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT);\n   dstAddrInput.bpp = bpp;\n   dstAddrInput.pitch = outputPitch;\n   dstAddrInput.height = height;\n   dstAddrInput.numSlices = depth;\n   dstAddrInput.numSamples = 1;\n   dstAddrInput.tileMode = AddrTileMode::ADDR_TM_LINEAR_GENERAL;\n   dstAddrInput.isDepth = isDepth;\n   dstAddrInput.tileBase = 0;\n   dstAddrInput.compBits = 0;\n   dstAddrInput.numFrags = 0;\n   dstAddrInput.bankSwizzle = 0;\n   dstAddrInput.pipeSwizzle = 0;\n\n   // Untiling always takes sample 0\n   srcAddrInput.sample = 0;\n   dstAddrInput.sample = 0;\n\n   // Untile all of the slices of this surface\n   for (uint32_t slice = beginSlice; slice < endSlice; ++slice) {\n      srcAddrInput.slice = slice;\n      dstAddrInput.slice = slice;\n\n      copySurfacePixels(\n         output, width, height, dstAddrInput,\n         input, width, height, srcAddrInput);\n   }\n\n   return true;\n}\n\nbool\nconvertToTiled(\n   uint8_t *output,\n   uint8_t *input,\n   uint32_t inputPitch,\n   latte::SQ_TILE_MODE tileMode,\n   uint32_t swizzle,\n   uint32_t pitch,\n   uint32_t width,\n   uint32_t height,\n   uint32_t depth,\n   uint32_t aa,\n   bool isDepth,\n   uint32_t bpp,\n   uint32_t beginSlice,\n   uint32_t endSlice)\n{\n   if (endSlice == 0) {\n      endSlice = depth;\n   }\n\n   decaf_check(beginSlice >= 0);\n   decaf_check(endSlice > 0);\n   decaf_check(endSlice <= depth);\n\n   ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT srcAddrInput;\n   std::memset(&srcAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT));\n   srcAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT);\n   srcAddrInput.bpp = bpp;\n   srcAddrInput.pitch = inputPitch;\n   srcAddrInput.height = height;\n   srcAddrInput.numSlices = depth;\n   srcAddrInput.numSamples = 1;\n   srcAddrInput.tileMode = AddrTileMode::ADDR_TM_LINEAR_GENERAL;\n   srcAddrInput.isDepth = isDepth;\n   srcAddrInput.tileBase = 0;\n   srcAddrInput.compBits = 0;\n   srcAddrInput.numFrags = 0;\n   srcAddrInput.bankSwizzle = 0;\n   srcAddrInput.pipeSwizzle = 0;\n\n   // Setup dst\n   ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT dstAddrInput;\n   std::memset(&dstAddrInput, 0, sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT));\n   dstAddrInput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT);\n   dstAddrInput.bpp = bpp;\n   dstAddrInput.pitch = pitch;\n   dstAddrInput.height = height;\n   dstAddrInput.numSlices = depth;\n   dstAddrInput.numSamples = 1 << aa;\n   dstAddrInput.tileMode = static_cast<AddrTileMode>(tileMode);\n   dstAddrInput.isDepth = isDepth;\n   dstAddrInput.tileBase = 0;\n   dstAddrInput.compBits = 0;\n   dstAddrInput.numFrags = 0;\n   calcSurfaceBankPipeSwizzle(swizzle,\n                              &srcAddrInput.bankSwizzle,\n                              &srcAddrInput.pipeSwizzle);\n\n   // Untiling always takes sample 0\n   srcAddrInput.sample = 0;\n   dstAddrInput.sample = 0;\n\n   // Untile all of the slices of this surface\n   for (uint32_t slice = beginSlice; slice < endSlice; ++slice) {\n      srcAddrInput.slice = slice;\n      dstAddrInput.slice = slice;\n\n      copySurfacePixels(\n         output, width, height, dstAddrInput,\n         input, width, height, srcAddrInput);\n   }\n\n   return true;\n}\n\n} // namespace gpu\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_decoders.h",
    "content": "#pragma once\n#include \"latte/latte_instructions.h\"\n\n#include <common/align.h>\n#include <common/decaf_assert.h>\n#include <algorithm>\n#include <fmt/core.h>\n#include <gsl/gsl-lite.hpp>\n#include <stdexcept>\n\nnamespace latte\n{\n\nstatic constexpr auto InvalidGprIdx = static_cast<uint32_t>(-1);\n\ninline bool\nisTranscendentalOnly(SQ_ALU_FLAGS flags)\n{\n   if (flags & SQ_ALU_FLAG_VECTOR) {\n      return false;\n   }\n\n   if (flags & SQ_ALU_FLAG_TRANSCENDENTAL) {\n      return true;\n   }\n\n   return false;\n}\n\ninline bool\nisVectorOnly(SQ_ALU_FLAGS flags)\n{\n   if (flags & SQ_ALU_FLAG_TRANSCENDENTAL) {\n      return false;\n   }\n\n   if (flags & SQ_ALU_FLAG_VECTOR) {\n      return true;\n   }\n\n   return false;\n}\n\n\ninline SQ_ALU_FLAGS\ngetInstructionFlags(const AluInst &inst)\n{\n   if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n      return getInstructionFlags(inst.op2.ALU_INST());\n   } else {\n      return getInstructionFlags(inst.op3.ALU_INST());\n   }\n}\n\ninline uint32_t\ngetInstructionNumSrcs(const AluInst &inst)\n{\n   if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n      return getInstructionNumSrcs(inst.op2.ALU_INST());\n   } else {\n      return getInstructionNumSrcs(inst.op3.ALU_INST());\n   }\n}\n\ninline bool\nisTranscendentalOnlyInst(const AluInst &inst)\n{\n   auto flags = getInstructionFlags(inst);\n   return isTranscendentalOnly(flags);\n}\n\ninline bool\nisVectorOnlyInst(const AluInst &inst)\n{\n   auto flags = getInstructionFlags(inst);\n   return isVectorOnly(flags);\n}\n\ninline bool\nisReductionInst(const AluInst &inst)\n{\n   auto flags = getInstructionFlags(inst);\n   return !!(flags & SQ_ALU_FLAG_REDUCTION);\n}\n\nstruct AluGroup\n{\n   AluGroup(const latte::AluInst *group)\n   {\n      auto instructionCount = 0u;\n      auto literalCount = 0u;\n\n      for (instructionCount = 1u; instructionCount <= 5u; ++instructionCount) {\n         auto &inst = group[instructionCount - 1];\n         auto srcCount = 0u;\n\n         if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n            srcCount = getInstructionNumSrcs(inst.op2.ALU_INST());\n         } else {\n            srcCount = getInstructionNumSrcs(inst.op3.ALU_INST());\n         }\n\n         if (srcCount > 0 && inst.word0.SRC0_SEL() == SQ_ALU_SRC::LITERAL) {\n            literalCount = std::max<unsigned>(literalCount, 1u + inst.word0.SRC0_CHAN());\n         }\n\n         if (srcCount > 1 && inst.word0.SRC1_SEL() == SQ_ALU_SRC::LITERAL) {\n            literalCount = std::max<unsigned>(literalCount, 1u + inst.word0.SRC1_CHAN());\n         }\n\n         if (srcCount > 2 && inst.op3.SRC2_SEL() == SQ_ALU_SRC::LITERAL) {\n            literalCount = std::max<unsigned>(literalCount, 1u + inst.op3.SRC2_CHAN());\n         }\n\n         if (inst.word0.LAST()) {\n            break;\n         }\n      }\n\n      instructions = gsl::make_span(group, instructionCount);\n      literals = gsl::make_span(reinterpret_cast<const uint32_t *>(group + instructionCount), literalCount);\n   }\n\n   size_t getNextSlot(size_t slot)\n   {\n      slot += instructions.size();\n      slot += (literals.size() + 1) / 2;\n      return slot;\n   }\n\n   gsl::span<const latte::AluInst> instructions;\n   gsl::span<const uint32_t> literals;\n};\n\nstruct AluGroupUnits\n{\n   SQ_CHAN addInstructionUnit(const latte::AluInst &inst)\n   {\n      SQ_ALU_FLAGS flags;\n      SQ_CHAN unit = inst.word1.DST_CHAN();\n\n      if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n         flags = getInstructionFlags(inst.op2.ALU_INST());\n      } else {\n         flags = getInstructionFlags(inst.op3.ALU_INST());\n      }\n\n      if (isTranscendentalOnly(flags)) {\n         unit = SQ_CHAN::T;\n      } else if (isVectorOnly(flags)) {\n         unit = unit;\n      } else if (units[unit]) {\n         unit = SQ_CHAN::T;\n      }\n\n      decaf_assert(!units[unit], fmt::format(\"Clause instruction unit collision for unit {}\", unit));\n      units[unit] = true;\n      return unit;\n   }\n\n   bool units[5] = { false, false, false, false, false };\n};\n\nstruct AluInstructionGroup\n{\n   std::array<const AluInst*, 5> units;\n   gsl::span<const uint32_t> literals;\n};\n\nclass AluClauseParser\n{\npublic:\n   AluClauseParser(gsl::span<const AluInst> slots, bool aluInstPreferVector)\n      : mSlots(slots), mAluInstPreferVector(aluInstPreferVector), mIndex(0)\n   {\n   }\n\n   AluInstructionGroup\n      readOneGroup()\n   {\n      AluInstructionGroup group = { 0 };\n      auto literalCount = 0;\n\n      while (true) {\n         auto &inst = mSlots[mIndex++];\n\n         // Read some internal data about the instruction\n         uint32_t srcCount = getInstructionNumSrcs(inst);\n\n         // Calculate the number of literals used by this instruction\n         if (srcCount > 0 && inst.word0.SRC0_SEL() == SQ_ALU_SRC::LITERAL) {\n            literalCount = std::max<uint32_t>(literalCount, 1u + inst.word0.SRC0_CHAN());\n         }\n\n         if (srcCount > 1 && inst.word0.SRC1_SEL() == SQ_ALU_SRC::LITERAL) {\n            literalCount = std::max<uint32_t>(literalCount, 1u + inst.word0.SRC1_CHAN());\n         }\n\n         if (srcCount > 2 && inst.op3.SRC2_SEL() == SQ_ALU_SRC::LITERAL) {\n            literalCount = std::max<uint32_t>(literalCount, 1u + inst.op3.SRC2_CHAN());\n         }\n\n         // This logic comes straight from the reference pages.\n         auto elem = inst.word1.DST_CHAN();\n         bool isLast = inst.word0.LAST();\n         bool isTrans;\n\n         if (isTranscendentalOnlyInst(inst)) {\n            isTrans = true;\n         } else if (isVectorOnlyInst(inst)) {\n            isTrans = false;\n         } else if (group.units[elem] || (!mAluInstPreferVector && isLast)) {\n            isTrans = true;\n         } else {\n            isTrans = false;\n         }\n\n         if (isTrans) {\n            decaf_check(!group.units[SQ_CHAN::T]);\n            group.units[SQ_CHAN::T] = &inst;\n         } else {\n            decaf_check(!group.units[elem]);\n            group.units[elem] = &inst;\n         }\n\n         // If this is marked as the last instruction, we are done\n         if (isLast) {\n            break;\n         }\n      }\n\n      // We decode the literals using subspan to allow GSL to perform proper\n      //  span bounds checking for us!\n      if (literalCount > 0) {\n         auto literalSlotCount = align_up(literalCount, 2) / 2;\n         auto literalSpan = mSlots.subspan(mIndex, literalSlotCount);\n         group.literals = gsl::make_span(reinterpret_cast<const uint32_t*>(&literalSpan[0]), literalCount);\n         mIndex += literalSlotCount;\n      }\n\n      // We place NOP data into all the ALU units that are not used.  This\n      //  permits us to simplify the logical handling to avoid NULL checks.\n      static const uint32_t NopInstData[] = { 0x00000000, 0x00000d00 };\n      static const auto NopInstPtr = reinterpret_cast<const AluInst *>(NopInstData);\n\n      for (auto i = 0; i < 5; ++i) {\n         if (!group.units[i]) {\n            group.units[i] = NopInstPtr;\n         }\n      }\n\n      return group;\n   }\n\n   bool\n      isEndOfClause()\n   {\n      decaf_check(mIndex <= mSlots.size());\n      return mIndex >= mSlots.size();\n   }\n\nprivate:\n   bool mAluInstPreferVector;\n   size_t mIndex;\n   gsl::span<const AluInst> mSlots;\n\n};\n\nenum class GprIndexMode : uint32_t\n{\n   None,\n   AR_X,\n   AL\n};\n\nenum class CfileIndexMode : uint32_t\n{\n   None,\n   AR_X,\n   AR_Y,\n   AR_Z,\n   AR_W,\n   AL\n};\n\nenum class CbufferIndexMode : uint32_t\n{\n   None,\n   AL\n};\n\nenum class VarRefType : uint32_t\n{\n   UNKNOWN,\n   FLOAT,\n   INT,\n   UINT\n};\n\nstruct GprRef\n{\n   uint32_t number;\n   GprIndexMode indexMode;\n\n   inline void next()\n   {\n      number++;\n   }\n};\n\nstruct CfileRef\n{\n   uint32_t index;\n   CfileIndexMode indexMode;\n};\n\nstruct CbufferRef\n{\n   uint32_t bufferId;\n   uint32_t index;\n   CbufferIndexMode indexMode;\n};\n\nstruct GprMaskRef\n{\n   GprRef gpr;\n   std::array<SQ_SEL, 4> mask;\n};\n\nstruct GprSelRef\n{\n   GprRef gpr;\n   SQ_SEL sel;\n};\n\nstruct GprChanRef\n{\n   GprRef gpr;\n   SQ_CHAN chan;\n};\n\nstruct CfileChanRef\n{\n   CfileRef cfile;\n   SQ_CHAN chan;\n};\n\nstruct CbufferChanRef\n{\n   CbufferRef cbuffer;\n   SQ_CHAN chan;\n};\n\nstruct PrevValRef\n{\n   SQ_CHAN unit;\n};\n\nstruct ValueRef\n{\n   union\n   {\n      int32_t intValue;\n      uint32_t uintValue;\n      float floatValue;\n   };\n};\n\nstruct ExportRef\n{\n   enum class Type : uint32_t\n   {\n      Position,\n      Param,\n      Pixel,\n      PixelWithFog,\n      ComputedZ,\n      Stream0Write,\n      Stream1Write,\n      Stream2Write,\n      Stream3Write,\n      VsGsRingWrite,\n      GsDcRingWrite\n   };\n\n   Type type;\n   uint32_t dataStride;\n   uint32_t elemCount;\n   uint32_t arrayBase;\n   uint32_t arraySize;\n   uint32_t indexGpr;\n\n   inline void next()\n   {\n      // Normal exports have a specific behaviour that they increment\n      // by one and have a elemCount of 1, even though they write vec4's\n      if (type < Type::Stream0Write) {\n         decaf_check(elemCount == 1);\n      }\n\n      arrayBase += elemCount;\n   }\n};\n\nstruct ExportMaskRef\n{\n   ExportRef output;\n   std::array<SQ_SEL, 4> mask;\n};\n\nstruct SrcVarRef\n{\n   enum class Type : uint32_t\n   {\n      GPR,\n      CBUFFER,\n      CFILE,\n      PREVRES,\n      VALUE\n   };\n\n   Type type;\n   union\n   {\n      GprChanRef gprChan;\n      CbufferChanRef cbufferChan;\n      CfileChanRef cfileChan;\n      PrevValRef prevres;\n      ValueRef value;\n   };\n   VarRefType valueType;\n   bool isAbsolute;\n   bool isNegated;\n};\n\ninline GprRef\nmakeGprRef(uint32_t gprNum, SQ_REL rel = SQ_REL::ABS, SQ_INDEX_MODE indexMode = SQ_INDEX_MODE::AR_X)\n{\n   GprRef gpr;\n\n   gpr.number = gprNum;\n\n   if (rel == SQ_REL::REL) {\n      switch (indexMode) {\n      case SQ_INDEX_MODE::AR_X:\n      case SQ_INDEX_MODE::AR_Y:\n      case SQ_INDEX_MODE::AR_Z:\n      case SQ_INDEX_MODE::AR_W:\n         gpr.indexMode = GprIndexMode::AR_X;\n         break;\n      case SQ_INDEX_MODE::LOOP:\n         gpr.indexMode = GprIndexMode::AL;\n         break;\n      default:\n         decaf_abort(\"Unexpected GPR index mode\");\n      }\n   } else {\n      gpr.indexMode = GprIndexMode::None;\n   }\n\n   return gpr;\n}\n\ninline CfileRef\nmakeCfileRef(uint32_t offset, SQ_REL rel, SQ_INDEX_MODE indexMode)\n{\n   CfileRef cfile;\n\n   cfile.index = offset;\n\n   if (rel == SQ_REL::REL) {\n      switch (indexMode) {\n      case SQ_INDEX_MODE::AR_X:\n         cfile.indexMode = CfileIndexMode::AR_X;\n         break;\n      case SQ_INDEX_MODE::AR_Y:\n         cfile.indexMode = CfileIndexMode::AR_Y;\n         break;\n      case SQ_INDEX_MODE::AR_Z:\n         cfile.indexMode = CfileIndexMode::AR_Z;\n         break;\n      case SQ_INDEX_MODE::AR_W:\n         cfile.indexMode = CfileIndexMode::AR_W;\n         break;\n      case SQ_INDEX_MODE::LOOP:\n         cfile.indexMode = CfileIndexMode::AL;\n         break;\n      default:\n         decaf_abort(\"Unexpected GPR index mode\");\n      }\n   } else {\n      cfile.indexMode = CfileIndexMode::None;\n   }\n\n   return cfile;\n}\n\ninline CbufferRef\nmakeCbufferRef(uint32_t offset, SQ_CF_KCACHE_MODE mode, uint32_t bank, uint32_t addr)\n{\n   CbufferRef cbuffer;\n\n   uint32_t lockedCount;\n   CbufferIndexMode cbufferIndexMode;\n   switch (mode) {\n   case SQ_CF_KCACHE_MODE::NOP:\n      lockedCount = 0;\n      cbufferIndexMode = CbufferIndexMode::None;\n      break;\n   case SQ_CF_KCACHE_MODE::LOCK_1:\n      lockedCount = 16;\n      cbufferIndexMode = CbufferIndexMode::None;\n      break;\n   case SQ_CF_KCACHE_MODE::LOCK_2:\n      lockedCount = 32;\n      cbufferIndexMode = CbufferIndexMode::None;\n      break;\n   case SQ_CF_KCACHE_MODE::LOCK_LOOP_INDEX:\n      lockedCount = 32;\n      cbufferIndexMode = CbufferIndexMode::AL;\n      break;\n   default:\n      decaf_abort(\"Unexpected KCACHE_MODE\");\n   }\n\n   decaf_check(offset < lockedCount);\n   cbuffer.bufferId = bank;\n   cbuffer.index = addr * 16 + offset;\n   cbuffer.indexMode = cbufferIndexMode;\n\n   return cbuffer;\n}\n\ninline SrcVarRef\nmakeSrcVar(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_ALU_SRC selId, SQ_CHAN chan, SQ_REL rel, bool abs, bool neg, SQ_INDEX_MODE indexMode, VarRefType type)\n{\n   SrcVarRef out;\n\n   out.isAbsolute = abs;\n   out.isNegated = neg;\n   out.valueType = type;\n\n   if (selId >= 0 && selId < 128) {\n      out.type = SrcVarRef::Type::GPR;\n      out.gprChan.gpr = makeGprRef(selId, rel, indexMode);\n      out.gprChan.chan = chan;\n   } else if (selId >= 128 && selId < 160) {\n      // Cannot use relative indexing to a Cbuffer, instead you have to do it\n      //  at the lock level, as below.\n      decaf_check(rel == SQ_REL::ABS);\n\n      out.type = SrcVarRef::Type::CBUFFER;\n      out.cbufferChan.cbuffer = makeCbufferRef(selId - 128,\n                                               cf.alu.word0.KCACHE_MODE0(), cf.alu.word0.KCACHE_BANK0(), cf.alu.word1.KCACHE_ADDR0());\n      out.cbufferChan.chan = chan;\n   } else if (selId >= 160 && selId < 192) {\n      // Cannot use relative indexing to a Cbuffer, instead you have to do it\n      //  at the lock level, as below.\n      decaf_check(rel == SQ_REL::ABS);\n\n      out.type = SrcVarRef::Type::CBUFFER;\n      out.cbufferChan.cbuffer = makeCbufferRef(selId - 160,\n                                               cf.alu.word1.KCACHE_MODE1(), cf.alu.word0.KCACHE_BANK1(), cf.alu.word1.KCACHE_ADDR1());\n      out.cbufferChan.chan = chan;\n   } else if (selId >= 256 && selId < 512) {\n      out.type = SrcVarRef::Type::CFILE;\n      out.cfileChan.cfile = makeCfileRef(selId - 256, rel, indexMode);\n      out.cfileChan.chan = chan;\n   } else {\n      switch (selId) {\n      case latte::SQ_ALU_SRC::LDS_OQ_A:\n         decaf_abort(\"Unsupported ALU SRC: LDS_OQ_A\");\n      case latte::SQ_ALU_SRC::LDS_OQ_B:\n         decaf_abort(\"Unsupported ALU SRC: LDS_OQ_B\");\n      case latte::SQ_ALU_SRC::LDS_OQ_A_POP:\n         decaf_abort(\"Unsupported ALU SRC: LDS_OQ_A_POP\");\n      case latte::SQ_ALU_SRC::LDS_OQ_B_POP:\n         decaf_abort(\"Unsupported ALU SRC: LDS_OQ_B_POP\");\n      case latte::SQ_ALU_SRC::LDS_DIRECT_A:\n         decaf_abort(\"Unsupported ALU SRC: LDS_DIRECT_A\");\n      case latte::SQ_ALU_SRC::LDS_DIRECT_B:\n         decaf_abort(\"Unsupported ALU SRC: LDS_DIRECT_B\");\n      case latte::SQ_ALU_SRC::TIME_HI:\n         decaf_abort(\"Unsupported ALU SRC: TIME_HI\");\n      case latte::SQ_ALU_SRC::TIME_LO:\n         decaf_abort(\"Unsupported ALU SRC: TIME_LO\");\n      case latte::SQ_ALU_SRC::MASK_HI:\n         decaf_abort(\"Unsupported ALU SRC: MASK_HI\");\n      case latte::SQ_ALU_SRC::MASK_LO:\n         decaf_abort(\"Unsupported ALU SRC: MASK_LO\");\n      case latte::SQ_ALU_SRC::HW_WAVE_ID:\n         decaf_abort(\"Unsupported ALU SRC: HW_WAVE_ID\");\n      case latte::SQ_ALU_SRC::SIMD_ID:\n         decaf_abort(\"Unsupported ALU SRC: SIMD_ID\");\n      case latte::SQ_ALU_SRC::SE_ID:\n         decaf_abort(\"Unsupported ALU SRC: SE_ID\");\n      case latte::SQ_ALU_SRC::HW_THREADGRP_ID:\n         decaf_abort(\"Unsupported ALU SRC: HW_THREADGRP_ID\");\n      case latte::SQ_ALU_SRC::WAVE_ID_IN_GRP:\n         decaf_abort(\"Unsupported ALU SRC: WAVE_ID_IN_GRP\");\n      case latte::SQ_ALU_SRC::NUM_THREADGRP_WAVES:\n         decaf_abort(\"Unsupported ALU SRC: NUM_THREADGRP_WAVES\");\n      case latte::SQ_ALU_SRC::HW_ALU_ODD:\n         decaf_abort(\"Unsupported ALU SRC: HW_ALU_ODD\");\n      case latte::SQ_ALU_SRC::LOOP_IDX:\n         decaf_abort(\"Unsupported ALU SRC: LOOP_IDX\");\n      case latte::SQ_ALU_SRC::PARAM_BASE_ADDR:\n         decaf_abort(\"Unsupported ALU SRC: PARAM_BASE_ADDR\");\n      case latte::SQ_ALU_SRC::NEW_PRIM_MASK:\n         decaf_abort(\"Unsupported ALU SRC: NEW_PRIM_MASK\");\n      case latte::SQ_ALU_SRC::PRIM_MASK_HI:\n         decaf_abort(\"Unsupported ALU SRC: PRIM_MASK_HI\");\n      case latte::SQ_ALU_SRC::PRIM_MASK_LO:\n         decaf_abort(\"Unsupported ALU SRC: PRIM_MASK_LO\");\n      case latte::SQ_ALU_SRC::IMM_1_DBL_L:\n         decaf_abort(\"Unsupported ALU SRC: IMM_1_DBL_L\");\n      case latte::SQ_ALU_SRC::IMM_1_DBL_M:\n         decaf_abort(\"Unsupported ALU SRC: IMM_1_DBL_M\");\n      case latte::SQ_ALU_SRC::IMM_0_5_DBL_L:\n         decaf_abort(\"Unsupported ALU SRC: IMM_0_5_DBL_L\");\n      case latte::SQ_ALU_SRC::IMM_0_5_DBL_M:\n         decaf_abort(\"Unsupported ALU SRC: IMM_0_5_DBL_M\");\n      case latte::SQ_ALU_SRC::IMM_0:\n         out.type = SrcVarRef::Type::VALUE;\n         out.value.floatValue = 0.0f;\n         break;\n      case latte::SQ_ALU_SRC::IMM_1:\n         out.type = SrcVarRef::Type::VALUE;\n         out.value.floatValue = 1.0f;\n         break;\n      case latte::SQ_ALU_SRC::IMM_1_INT:\n         out.type = SrcVarRef::Type::VALUE;\n         out.value.uintValue = 1;\n         break;\n      case latte::SQ_ALU_SRC::IMM_M_1_INT:\n         out.type = SrcVarRef::Type::VALUE;\n         out.value.intValue = -1;\n         break;\n      case latte::SQ_ALU_SRC::IMM_0_5:\n         out.type = SrcVarRef::Type::VALUE;\n         out.value.floatValue = 0.5f;\n         break;\n      case latte::SQ_ALU_SRC::LITERAL:\n         out.type = SrcVarRef::Type::VALUE;\n         out.value.uintValue = group.literals[chan];\n         break;\n      case latte::SQ_ALU_SRC::PV:\n         out.type = SrcVarRef::Type::PREVRES;\n         out.prevres.unit = chan;\n         break;\n      case latte::SQ_ALU_SRC::PS:\n         out.type = SrcVarRef::Type::PREVRES;\n         out.prevres.unit = SQ_CHAN::T;\n         break;\n      default:\n         decaf_abort(fmt::format(\"Unexpected ALU SRC encountered: {}\", selId));\n      }\n   }\n\n   return out;\n}\n\ninline SrcVarRef\nmakeSrcVar(const ControlFlowInst &cf, const AluInstructionGroup &group, const AluInst &inst, uint32_t srcIndex, VarRefType valueType = VarRefType::UNKNOWN)\n{\n   auto instFlags = getInstructionFlags(inst);\n   if (valueType == VarRefType::UNKNOWN) {\n      // TODO: Rewrite GLSL generator to input the expected type information.\n      // This would allow us to remove the UNKNOWN type and just use that inputs.\n      valueType = VarRefType::FLOAT;\n      if (instFlags & SQ_ALU_FLAG_INT_IN) {\n         valueType = VarRefType::INT;\n      } else if (instFlags & SQ_ALU_FLAG_UINT_IN) {\n         valueType = VarRefType::UINT;\n      }\n   } else {\n      if (instFlags & (SQ_ALU_FLAG_INT_IN | SQ_ALU_FLAG_UINT_IN)) {\n         decaf_check(valueType == VarRefType::INT || valueType == VarRefType::UINT);\n      } else {\n         decaf_check(valueType == VarRefType::FLOAT);\n      }\n   }\n\n   if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n      switch (srcIndex) {\n      case 0:\n         return makeSrcVar(cf, group,\n                           inst.word0.SRC0_SEL(),\n                           inst.word0.SRC0_CHAN(),\n                           inst.word0.SRC0_REL(),\n                           inst.op2.SRC0_ABS(),\n                           inst.word0.SRC0_NEG(),\n                           inst.word0.INDEX_MODE(),\n                           valueType);\n      case 1:\n         return makeSrcVar(cf, group,\n                           inst.word0.SRC1_SEL(),\n                           inst.word0.SRC1_CHAN(),\n                           inst.word0.SRC1_REL(),\n                           inst.op2.SRC1_ABS(),\n                           inst.word0.SRC1_NEG(),\n                           inst.word0.INDEX_MODE(),\n                           valueType);\n      default:\n         decaf_abort(\"Invalid source var index\");\n      }\n   } else {\n      switch (srcIndex) {\n      case 0:\n         return makeSrcVar(cf, group,\n                           inst.word0.SRC0_SEL(),\n                           inst.word0.SRC0_CHAN(),\n                           inst.word0.SRC0_REL(),\n                           false,\n                           inst.word0.SRC0_NEG(),\n                           inst.word0.INDEX_MODE(),\n                           valueType);\n      case 1:\n         return makeSrcVar(cf, group,\n                           inst.word0.SRC1_SEL(),\n                           inst.word0.SRC1_CHAN(),\n                           inst.word0.SRC1_REL(),\n                           false,\n                           inst.word0.SRC1_NEG(),\n                           inst.word0.INDEX_MODE(),\n                           valueType);\n      case 2:\n         return makeSrcVar(cf, group,\n                           inst.op3.SRC2_SEL(),\n                           inst.op3.SRC2_CHAN(),\n                           inst.op3.SRC2_REL(),\n                           false,\n                           inst.op3.SRC2_NEG(),\n                           inst.word0.INDEX_MODE(),\n                           valueType);\n      default:\n         decaf_abort(\"Invalid source var index\");\n      }\n   }\n}\n\ninline uint32_t\ncondenseSwizzleMask(std::array<SQ_SEL, 4> &source, std::array<SQ_CHAN, 4> &dest, uint32_t numSwizzle)\n{\n   uint32_t numSwizzleOut = 0;\n\n   for (auto i = 0u; i < numSwizzle; ++i) {\n      if (source[i] != SQ_SEL::SEL_MASK) {\n         source[numSwizzleOut] = source[i];\n         dest[numSwizzleOut] = static_cast<SQ_CHAN>(i);\n         numSwizzleOut++;\n      }\n   }\n\n   return numSwizzleOut;\n}\n\ninline bool\nsimplifySwizzle(std::array<SQ_CHAN, 4> &out, const std::array<SQ_SEL, 4> &source, uint32_t numSwizzle)\n{\n   for (auto i = 0u; i < numSwizzle; ++i) {\n      switch (source[i]) {\n      case SQ_SEL::SEL_X:\n         out[i] = SQ_CHAN::X;\n         break;\n      case SQ_SEL::SEL_Y:\n         out[i] = SQ_CHAN::Y;\n         break;\n      case SQ_SEL::SEL_Z:\n         out[i] = SQ_CHAN::Z;\n         break;\n      case SQ_SEL::SEL_W:\n         out[i] = SQ_CHAN::W;\n         break;\n      default:\n         return false;\n      }\n   }\n\n   return true;\n}\n\ninline bool\nisSwizzleFullyMasked(const std::array<SQ_SEL, 4> &dest)\n{\n   for (auto i = 0u; i < 4; ++i) {\n      if (dest[i] != SQ_SEL::SEL_MASK) {\n         return false;\n      }\n   }\n\n   return true;\n}\n\ninline bool\nisSwizzleFullyUnmasked(const std::array<SQ_SEL, 4> &dest)\n{\n   for (auto i = 0u; i < 4; ++i) {\n      if (dest[i] == SQ_SEL::SEL_MASK) {\n         return false;\n      }\n   }\n\n   return true;\n}\n\ninline ExportRef\nmakeExportRef(SQ_EXPORT_TYPE type, uint32_t arrayBase)\n{\n   ExportRef out;\n   out.dataStride = 0;\n   out.arraySize = 1;\n   out.elemCount = 1;\n   out.indexGpr = InvalidGprIdx;\n\n   if (type == SQ_EXPORT_TYPE::POS) {\n      if (60 <= arrayBase && arrayBase <= 63) {\n         out.type = ExportRef::Type::Position;\n         out.arrayBase = arrayBase - 60;\n      } else {\n         decaf_abort(\"Unexpected POS EXPORT index\");\n      }\n   } else if (type == SQ_EXPORT_TYPE::PARAM) {\n      if (0 <= arrayBase && arrayBase <= 31) {\n         out.type = ExportRef::Type::Param;\n         out.arrayBase = arrayBase;\n      } else {\n         decaf_abort(\"Unexpected PARAM EXPORT index\");\n      }\n   } else if (type == SQ_EXPORT_TYPE::PIXEL) {\n      if (0 <= arrayBase && arrayBase <= 7) {\n         out.type = ExportRef::Type::Pixel;\n         out.arrayBase = arrayBase;\n      } else if (16 <= arrayBase && arrayBase <= 23) {\n         out.type = ExportRef::Type::PixelWithFog;\n         out.arrayBase = arrayBase - 16;\n      } else if (arrayBase == 61) {\n         out.type = ExportRef::Type::ComputedZ;\n         out.arrayBase = 0;\n      } else {\n         decaf_abort(\"Unexpected PIXEL EXPORT index\");\n      }\n   } else {\n      decaf_abort(\"Unexpected shader EXPORT type\");\n   }\n\n   return out;\n}\n\ninline ExportRef\n_makeGenericMemExportRef(ExportRef::Type refType, SQ_EXPORT_TYPE type,\n                         uint32_t indexGpr, uint32_t dataStride,\n                         uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount)\n{\n   ExportRef out;\n   out.type = refType;\n   out.dataStride = dataStride;\n   out.elemCount = elemCount;\n   out.arrayBase = arrayBase;\n   out.arraySize = arraySize;\n   out.indexGpr = InvalidGprIdx;\n\n   switch(static_cast<SQ_MEM_EXPORT_TYPE>(type)) {\n   case SQ_MEM_EXPORT_TYPE::WRITE:\n      // This is handled implicitly\n      break;\n   case SQ_MEM_EXPORT_TYPE::WRITE_IND:\n      out.indexGpr = indexGpr;\n      break;\n   default:\n      decaf_abort(\"Unexpected shader MEM EXPORT type\");\n   }\n\n   return out;\n}\n\ninline ExportRef\nmakeStreamExportRef(SQ_EXPORT_TYPE type, uint32_t indexGpr, uint32_t streamIdx, uint32_t streamStride, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount)\n{\n   ExportRef::Type writeType;\n   if (streamIdx == 0) {\n      writeType = ExportRef::Type::Stream0Write;\n   } else if (streamIdx == 1) {\n      writeType = ExportRef::Type::Stream1Write;\n   } else if (streamIdx == 2) {\n      writeType = ExportRef::Type::Stream2Write;\n   } else if (streamIdx == 3) {\n      writeType = ExportRef::Type::Stream3Write;\n   } else {\n      decaf_abort(\"Unexpected stream index for stream memory export\");\n   }\n\n   return _makeGenericMemExportRef(writeType, type, indexGpr, streamStride, arrayBase, arraySize, elemCount);\n}\n\ninline ExportRef\nmakeVsGsRingExportRef(SQ_EXPORT_TYPE type, uint32_t indexGpr, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount)\n{\n   return _makeGenericMemExportRef(ExportRef::Type::VsGsRingWrite, type, indexGpr, 0, arrayBase, arraySize, elemCount);\n}\n\ninline ExportRef\nmakeGsDcRingExportRef(SQ_EXPORT_TYPE type, uint32_t indexGpr, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount)\n{\n   return _makeGenericMemExportRef(ExportRef::Type::GsDcRingWrite, type, indexGpr, 0, arrayBase, arraySize, elemCount);\n}\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_disassembler.cpp",
    "content": "#include \"latte/latte_disassembler.h\"\n#include \"latte/latte_disassembler_state.h\"\n\n#include \"latte/latte_instructions.h\"\n\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n#include <gsl/gsl-lite.hpp>\n#include <iterator>\n\nnamespace latte\n{\n\nnamespace disassembler\n{\n\nstatic const std::string\nSingleIndent = \"  \";\n\nvoid\nincreaseIndent(State &state)\n{\n   state.indent += SingleIndent;\n}\n\nvoid\ndecreaseIndent(State &state)\n{\n   if (state.indent.size() >= SingleIndent.size()) {\n      state.indent.resize(state.indent.size() - SingleIndent.size());\n   } else {\n      decaf_abort(\"Invalid decrease indent\");\n   }\n}\n\nvoid\ndisassembleCondition(fmt::memory_buffer &out, const ControlFlowInst &inst)\n{\n   if (inst.word1.COND()) {\n      fmt::format_to(std::back_inserter(out), \" CND(\");\n\n      switch (inst.word1.COND()) {\n      case SQ_CF_COND::ALWAYS_FALSE:\n         fmt::format_to(std::back_inserter(out), \"FALSE\");\n         break;\n      case SQ_CF_COND::CF_BOOL:\n         fmt::format_to(std::back_inserter(out), \"BOOL\");\n         break;\n      case SQ_CF_COND::CF_NOT_BOOL:\n         fmt::format_to(std::back_inserter(out), \"NOT_BOOL\");\n         break;\n      }\n\n      fmt::format_to(std::back_inserter(out), \") CF_CONST({})\", inst.word1.CF_CONST());\n   }\n}\n\nstatic void\ndisassembleLoop(fmt::memory_buffer &out, const ControlFlowInst &inst)\n{\n   disassembleCondition(out, inst);\n\n   switch (inst.word1.CF_INST()) {\n   case SQ_CF_INST_LOOP_START:\n   case SQ_CF_INST_LOOP_END:\n      if (!inst.word1.COND()) {\n         // If .COND is set - disassembleCondition will print CF_CONST\n         fmt::format_to(std::back_inserter(out), \" CF_CONST({})\", inst.word1.CF_CONST());\n      }\n   }\n\n   switch (inst.word1.CF_INST()) {\n   case SQ_CF_INST_LOOP_START:\n   case SQ_CF_INST_LOOP_START_DX10:\n   case SQ_CF_INST_LOOP_START_NO_AL:\n      fmt::format_to(std::back_inserter(out), \" FAIL_JUMP_ADDR({})\", inst.word0.ADDR());\n      break;\n   case SQ_CF_INST_LOOP_CONTINUE:\n   case SQ_CF_INST_LOOP_BREAK:\n      fmt::format_to(std::back_inserter(out), \" ADDR({})\", inst.word0.ADDR());\n      break;\n   case SQ_CF_INST_LOOP_END:\n      fmt::format_to(std::back_inserter(out), \" PASS_JUMP_ADDR({})\", inst.word0.ADDR());\n      break;\n   default:\n      fmt::format_to(std::back_inserter(out), \" UNKNOWN_LOOP_CF_INST\");\n   }\n\n   if (inst.word1.POP_COUNT()) {\n      fmt::format_to(std::back_inserter(out), \" POP_CNT({})\", inst.word1.POP_COUNT());\n   }\n\n   if (inst.word1.VALID_PIXEL_MODE()) {\n      fmt::format_to(std::back_inserter(out), \" VALID_PIX\");\n   }\n\n   if (!inst.word1.BARRIER()) {\n      fmt::format_to(std::back_inserter(out), \" NO_BARRIER\");\n   }\n}\n\nstatic void\ndisassembleJump(fmt::memory_buffer &out, const ControlFlowInst &inst)\n{\n   auto id = inst.word1.CF_INST();\n\n   if (id == SQ_CF_INST_CALL && inst.word1.CALL_COUNT()) {\n      fmt::format_to(std::back_inserter(out), \" CALL_COUNT({})\", inst.word1.CALL_COUNT());\n   }\n\n   disassembleCondition(out, inst);\n\n   if (inst.word1.POP_COUNT()) {\n      fmt::format_to(std::back_inserter(out), \" POP_CNT({})\", inst.word1.POP_COUNT());\n   }\n\n   if (id == SQ_CF_INST_CALL || id == SQ_CF_INST_ELSE || id == SQ_CF_INST_JUMP) {\n      fmt::format_to(std::back_inserter(out), \" ADDR({})\", inst.word0.ADDR());\n   }\n\n   if (inst.word1.VALID_PIXEL_MODE()) {\n      fmt::format_to(std::back_inserter(out), \" VALID_PIX\");\n   }\n\n   if (!inst.word1.BARRIER()) {\n      fmt::format_to(std::back_inserter(out), \" NO_BARRIER\");\n   }\n}\n\nvoid\ndisassembleCF(fmt::memory_buffer &out, const ControlFlowInst &inst)\n{\n   auto id = inst.word1.CF_INST();\n   auto name = getInstructionName(id);\n   fmt::format_to(std::back_inserter(out), \"{}\", name);\n\n   switch (id) {\n   case SQ_CF_INST_TEX:\n      disassembleCfTEX(out, inst);\n      break;\n   case SQ_CF_INST_VTX:\n   case SQ_CF_INST_VTX_TC:\n      disassembleCfVTX(out, inst);\n      break;\n   case SQ_CF_INST_LOOP_START:\n   case SQ_CF_INST_LOOP_START_DX10:\n   case SQ_CF_INST_LOOP_START_NO_AL:\n   case SQ_CF_INST_LOOP_END:\n   case SQ_CF_INST_LOOP_CONTINUE:\n   case SQ_CF_INST_LOOP_BREAK:\n      disassembleLoop(out, inst);\n      break;\n   case SQ_CF_INST_JUMP:\n   case SQ_CF_INST_ELSE:\n   case SQ_CF_INST_CALL:\n   case SQ_CF_INST_CALL_FS:\n   case SQ_CF_INST_RETURN:\n   case SQ_CF_INST_POP_JUMP:\n      disassembleJump(out, inst);\n      break;\n   case SQ_CF_INST_EMIT_VERTEX:\n   case SQ_CF_INST_EMIT_CUT_VERTEX:\n   case SQ_CF_INST_CUT_VERTEX:\n      if (!inst.word1.BARRIER()) {\n         fmt::format_to(std::back_inserter(out), \" NO_BARRIER\");\n      }\n      break;\n   case SQ_CF_INST_PUSH:\n   case SQ_CF_INST_PUSH_ELSE:\n   case SQ_CF_INST_KILL:\n      disassembleCondition(out, inst);\n      // switch case pass through\n   case SQ_CF_INST_POP:\n   case SQ_CF_INST_POP_PUSH:\n   case SQ_CF_INST_POP_PUSH_ELSE:\n      if (inst.word1.POP_COUNT()) {\n         fmt::format_to(std::back_inserter(out), \" POP_CNT({})\", inst.word1.POP_COUNT());\n      }\n\n      if (inst.word1.VALID_PIXEL_MODE()) {\n         fmt::format_to(std::back_inserter(out), \" VALID_PIX\");\n      }\n      break;\n   case SQ_CF_INST_END_PROGRAM:\n   case SQ_CF_INST_NOP:\n      break;\n   case SQ_CF_INST_WAIT_ACK:\n   case SQ_CF_INST_TEX_ACK:\n   case SQ_CF_INST_VTX_ACK:\n   case SQ_CF_INST_VTX_TC_ACK:\n   default:\n      fmt::format_to(std::back_inserter(out), \" UNK_FORMAT\");\n      break;\n   }\n}\n\nstatic void\ndisassembleNormal(State &state, const ControlFlowInst &inst)\n{\n   auto id = inst.word1.CF_INST();\n   auto name = getInstructionName(id);\n\n   switch (id) {\n   case SQ_CF_INST_WAIT_ACK:\n   case SQ_CF_INST_TEX_ACK:\n   case SQ_CF_INST_VTX_ACK:\n   case SQ_CF_INST_VTX_TC_ACK:\n      decaf_abort(fmt::format(\"Unable to decode instruction {} {}\", id, name));\n   }\n\n   // Decode instruction clause\n   fmt::format_to(state.out, \"{}{:02} \", state.indent, state.cfPC);\n   disassembleCF(state.out, inst);\n   fmt::format_to(state.out, \"\\n\");\n\n   switch (id) {\n   case SQ_CF_INST_LOOP_START:\n   case SQ_CF_INST_LOOP_START_DX10:\n   case SQ_CF_INST_LOOP_START_NO_AL:\n      increaseIndent(state);\n      break;\n   case SQ_CF_INST_LOOP_END:\n      decreaseIndent(state);\n      break;\n   case SQ_CF_INST_TEX:\n      disassembleTEXClause(state, inst);\n      break;\n   case SQ_CF_INST_VTX:\n   case SQ_CF_INST_VTX_TC:\n      disassembleVtxClause(state, inst);\n      break;\n   }\n}\n\n} // namespace disassembler\n\nstd::string\ndisassemble(const gsl::span<const uint8_t> &binary,\n            bool isSubroutine)\n{\n   disassembler::State state;\n   state.binary = binary;\n   state.cfPC = 0;\n   state.groupPC = 0;\n\n   for (auto i = 0; i < binary.size(); i += sizeof(ControlFlowInst)) {\n      auto cf = *reinterpret_cast<const ControlFlowInst *>(binary.data() + i);\n      // cf.word1.CF_INST();\n      auto type = cf.word1.CF_INST_TYPE();\n\n      switch (type) {\n      case SQ_CF_INST_TYPE_NORMAL:\n         disassembler::disassembleNormal(state, cf);\n         break;\n      case SQ_CF_INST_TYPE_EXPORT:\n         disassembler::disassembleExport(state, cf);\n         break;\n      case SQ_CF_INST_TYPE_ALU:\n      case SQ_CF_INST_TYPE_ALU_EXTENDED:\n         disassembler::disassembleControlFlowALU(state, cf);\n         break;\n      default:\n         decaf_abort(fmt::format(\"Invalid top level instruction type {}\", type));\n      }\n\n      if (cf.word1.CF_INST() == SQ_CF_INST_RETURN && isSubroutine) {\n         break;\n      }\n\n      if (cf.word1.CF_INST_TYPE() == SQ_CF_INST_TYPE_NORMAL\n       || cf.word1.CF_INST_TYPE() == SQ_CF_INST_TYPE_EXPORT) {\n         if (cf.word1.END_OF_PROGRAM()) {\n            fmt::format_to(state.out, \"\\nEND_OF_PROGRAM\\n\");\n            break;\n         }\n      }\n\n      state.cfPC++;\n      fmt::format_to(state.out, \"\\n\");\n   }\n\n   return to_string(state.out);\n}\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_disassembler_alu.cpp",
    "content": "#include \"latte/latte_disassembler_state.h\"\n#include \"latte_decoders.h\"\n\n#include <common/bit_cast.h>\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <fmt/core.h>\n#include <iterator>\n\nnamespace latte\n{\n\nnamespace disassembler\n{\n\nstatic void\ndisassembleKcache(fmt::memory_buffer &out,\n                  uint32_t id,\n                  SQ_CF_KCACHE_MODE mode,\n                  uint32_t bank,\n                  uint32_t addr)\n{\n   switch (mode) {\n   case SQ_CF_KCACHE_MODE::NOP:\n      break;\n   case SQ_CF_KCACHE_MODE::LOCK_1:\n      fmt::format_to(std::back_inserter(out), \" KCACHE{}(CB{}:{}-{})\",\n                     id, bank, 16 * addr, 16 * addr + 15);\n      break;\n   case SQ_CF_KCACHE_MODE::LOCK_2:\n      fmt::format_to(std::back_inserter(out), \" KCACHE{}(CB{}:{}-{})\",\n                     id, bank, 16 * addr, 16 * addr + 31);\n      break;\n   case SQ_CF_KCACHE_MODE::LOCK_LOOP_INDEX:\n      fmt::format_to(std::back_inserter(out), \" KCACHE{}(CB{}:AL+{}-AL+{})\",\n                     id, bank, 16 * addr, 16 * addr + 31);\n      break;\n   }\n}\n\nstatic void\ndisassembleAluSource(fmt::memory_buffer &out,\n                     const latte::ControlFlowInst &parent,\n                     size_t groupPC,\n                     SQ_INDEX_MODE indexMode,\n                     uint32_t sel,\n                     SQ_REL rel,\n                     SQ_CHAN chan,\n                     uint32_t literalValue,\n                     bool negate,\n                     bool absolute)\n{\n   bool useChannel = true;\n\n   if (negate) {\n      fmt::format_to(std::back_inserter(out), \"-\");\n   }\n\n   if (absolute) {\n      fmt::format_to(std::back_inserter(out), \"|\");\n   }\n\n   if (sel >= SQ_ALU_SRC::KCACHE_BANK0_FIRST && sel <= SQ_ALU_SRC::KCACHE_BANK0_LAST) {\n      auto id = sel - SQ_ALU_SRC::KCACHE_BANK0_FIRST;\n      fmt::format_to(std::back_inserter(out), \"KC0[{}]\", id);\n   } else if (sel >= SQ_ALU_SRC::KCACHE_BANK1_FIRST && sel <= SQ_ALU_SRC::KCACHE_BANK1_LAST) {\n      auto id = sel - SQ_ALU_SRC::KCACHE_BANK1_FIRST;\n      fmt::format_to(std::back_inserter(out), \"KC1[{}]\", id);\n   } else if (sel >= SQ_ALU_SRC::REGISTER_FIRST && sel <= SQ_ALU_SRC::REGISTER_LAST) {\n      fmt::format_to(std::back_inserter(out), \"R{}\", sel - SQ_ALU_SRC::REGISTER_FIRST);\n   } else if (sel >= SQ_ALU_SRC::CONST_FILE_FIRST && sel <= SQ_ALU_SRC::CONST_FILE_LAST) {\n      fmt::format_to(std::back_inserter(out), \"C{}\", sel - SQ_ALU_SRC::CONST_FILE_FIRST);\n   } else {\n      useChannel = false;\n\n      switch (sel) {\n      case SQ_ALU_SRC::LDS_OQ_A:\n         fmt::format_to(std::back_inserter(out), \"LDS_OQ_A\");\n         break;\n      case SQ_ALU_SRC::LDS_OQ_B:\n         fmt::format_to(std::back_inserter(out), \"LDS_OQ_B\");\n         break;\n      case SQ_ALU_SRC::LDS_OQ_A_POP:\n         fmt::format_to(std::back_inserter(out), \"LDS_OQ_A_POP\");\n         break;\n      case SQ_ALU_SRC::LDS_OQ_B_POP:\n         fmt::format_to(std::back_inserter(out), \"LDS_OQ_B_POP\");\n         break;\n      case SQ_ALU_SRC::LDS_DIRECT_A:\n         fmt::format_to(std::back_inserter(out), \"LDS_DIRECT_A\");\n         break;\n      case SQ_ALU_SRC::LDS_DIRECT_B:\n         fmt::format_to(std::back_inserter(out), \"LDS_DIRECT_B\");\n         break;\n      case SQ_ALU_SRC::TIME_HI:\n         fmt::format_to(std::back_inserter(out), \"TIME_HI\");\n         break;\n      case SQ_ALU_SRC::TIME_LO:\n         fmt::format_to(std::back_inserter(out), \"TIME_LO\");\n         break;\n      case SQ_ALU_SRC::MASK_HI:\n         fmt::format_to(std::back_inserter(out), \"MASK_HI\");\n         break;\n      case SQ_ALU_SRC::MASK_LO:\n         fmt::format_to(std::back_inserter(out), \"MASK_LO\");\n         break;\n      case SQ_ALU_SRC::HW_WAVE_ID:\n         fmt::format_to(std::back_inserter(out), \"HW_WAVE_ID\");\n         break;\n      case SQ_ALU_SRC::SIMD_ID:\n         fmt::format_to(std::back_inserter(out), \"SIMD_ID\");\n         break;\n      case SQ_ALU_SRC::SE_ID:\n         fmt::format_to(std::back_inserter(out), \"SE_ID\");\n         break;\n      case SQ_ALU_SRC::HW_THREADGRP_ID:\n         fmt::format_to(std::back_inserter(out), \"HW_THREADGRP_ID\");\n         break;\n      case SQ_ALU_SRC::WAVE_ID_IN_GRP:\n         fmt::format_to(std::back_inserter(out), \"WAVE_ID_IN_GRP\");\n         break;\n      case SQ_ALU_SRC::NUM_THREADGRP_WAVES:\n         fmt::format_to(std::back_inserter(out), \"NUM_THREADGRP_WAVES\");\n         break;\n      case SQ_ALU_SRC::HW_ALU_ODD:\n         fmt::format_to(std::back_inserter(out), \"HW_ALU_ODD\");\n         break;\n      case SQ_ALU_SRC::LOOP_IDX:\n         fmt::format_to(std::back_inserter(out), \"AL\");\n         break;\n      case SQ_ALU_SRC::PARAM_BASE_ADDR:\n         fmt::format_to(std::back_inserter(out), \"PARAM_BASE_ADDR\");\n         break;\n      case SQ_ALU_SRC::NEW_PRIM_MASK:\n         fmt::format_to(std::back_inserter(out), \"NEW_PRIM_MASK\");\n         break;\n      case SQ_ALU_SRC::PRIM_MASK_HI:\n         fmt::format_to(std::back_inserter(out), \"PRIM_MASK_HI\");\n         break;\n      case SQ_ALU_SRC::PRIM_MASK_LO:\n         fmt::format_to(std::back_inserter(out), \"PRIM_MASK_LO\");\n         break;\n      case SQ_ALU_SRC::IMM_1_DBL_L:\n         fmt::format_to(std::back_inserter(out), \"1.0_L\");\n         break;\n      case SQ_ALU_SRC::IMM_1_DBL_M:\n         fmt::format_to(std::back_inserter(out), \"1.0_M\");\n         break;\n      case SQ_ALU_SRC::IMM_0_5_DBL_L:\n         fmt::format_to(std::back_inserter(out), \"0.5_L\");\n         break;\n      case SQ_ALU_SRC::IMM_0_5_DBL_M:\n         fmt::format_to(std::back_inserter(out), \"0.5_M\");\n         break;\n      case SQ_ALU_SRC::IMM_0:\n         fmt::format_to(std::back_inserter(out), \"0.0f\");\n         break;\n      case SQ_ALU_SRC::IMM_1:\n         fmt::format_to(std::back_inserter(out), \"1.0f\");\n         break;\n      case SQ_ALU_SRC::IMM_1_INT:\n         fmt::format_to(std::back_inserter(out), \"1\");\n         break;\n      case SQ_ALU_SRC::IMM_M_1_INT:\n         fmt::format_to(std::back_inserter(out), \"-1\");\n         break;\n      case SQ_ALU_SRC::IMM_0_5:\n         fmt::format_to(std::back_inserter(out), \"0.5f\");\n         break;\n      case SQ_ALU_SRC::LITERAL:\n         fmt::format_to(std::back_inserter(out), \"(0x{:08X}, {})\", literalValue, bit_cast<float>(literalValue));\n         break;\n      case SQ_ALU_SRC::PV:\n         fmt::format_to(std::back_inserter(out), \"PV{}\", groupPC - 1);\n         useChannel = true;\n         break;\n      case SQ_ALU_SRC::PS:\n         fmt::format_to(std::back_inserter(out), \"PS{}\", groupPC - 1);\n         break;\n      default:\n         fmt::format_to(std::back_inserter(out), \"UNKNOWN\");\n      }\n   }\n\n   if (rel) {\n      switch (indexMode) {\n      case SQ_INDEX_MODE::AR_X:\n         fmt::format_to(std::back_inserter(out), \"[AR.x]\");\n         break;\n      case SQ_INDEX_MODE::AR_Y:\n         fmt::format_to(std::back_inserter(out), \"[AR.y]\");\n         break;\n      case SQ_INDEX_MODE::AR_Z:\n         fmt::format_to(std::back_inserter(out), \"[AR.z]\");\n         break;\n      case SQ_INDEX_MODE::AR_W:\n         fmt::format_to(std::back_inserter(out), \"[AR.w]\");\n         break;\n      case SQ_INDEX_MODE::LOOP:\n         fmt::format_to(std::back_inserter(out), \"[AL]\");\n         break;\n      default:\n         fmt::format_to(std::back_inserter(out), \"[UNKNOWN]\");\n      }\n   }\n\n   if (useChannel) {\n      switch (chan) {\n      case SQ_CHAN::X:\n         fmt::format_to(std::back_inserter(out), \".x\");\n         break;\n      case SQ_CHAN::Y:\n         fmt::format_to(std::back_inserter(out), \".y\");\n         break;\n      case SQ_CHAN::Z:\n         fmt::format_to(std::back_inserter(out), \".z\");\n         break;\n      case SQ_CHAN::W:\n         fmt::format_to(std::back_inserter(out), \".w\");\n         break;\n      default:\n         fmt::format_to(std::back_inserter(out), \".UNKNOWN\");\n         break;\n      }\n   }\n\n   if (absolute) {\n      fmt::format_to(std::back_inserter(out), \"|\");\n   }\n}\n\nvoid\ndisassembleAluInstruction(fmt::memory_buffer &out,\n                          const ControlFlowInst &parent,\n                          const AluInst &inst,\n                          size_t groupPC,\n                          SQ_CHAN unit,\n                          const gsl::span<const uint32_t> &literals,\n                          int namePad)\n{\n   std::string name;\n   SQ_ALU_FLAGS flags;\n   auto srcCount = 0u;\n\n   if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n      name = getInstructionName(inst.op2.ALU_INST());\n      flags = getInstructionFlags(inst.op2.ALU_INST());\n      srcCount = getInstructionNumSrcs(inst.op2.ALU_INST());\n   } else {\n      name = getInstructionName(inst.op3.ALU_INST());\n      flags = getInstructionFlags(inst.op3.ALU_INST());\n      srcCount = getInstructionNumSrcs(inst.op3.ALU_INST());\n   }\n\n   if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2 && !inst.op2.UPDATE_EXECUTE_MASK()) {\n      switch (inst.op2.OMOD()) {\n      case SQ_ALU_OMOD::OFF:\n         break;\n      case SQ_ALU_OMOD::D2:\n         name += \"/2\";\n         break;\n      case SQ_ALU_OMOD::M2:\n         name += \"*2\";\n         break;\n      case SQ_ALU_OMOD::M4:\n         name += \"*4\";\n         break;\n      default:\n         decaf_abort(fmt::format(\"Unexpected OMOD {}\", inst.op2.OMOD()));\n      }\n   }\n\n   fmt::format_to(std::back_inserter(out), \"{: <{}} \", name, namePad);\n\n   auto writeMask = true;\n\n   if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n      writeMask = inst.op2.WRITE_MASK();\n   }\n\n   if (!writeMask) {\n      fmt::format_to(std::back_inserter(out), \"____\");\n   } else {\n      disassembleAluSource(out,\n                           parent,\n                           groupPC,\n                           inst.word0.INDEX_MODE(),\n                           inst.word1.DST_GPR(),\n                           inst.word1.DST_REL(),\n                           inst.word1.DST_CHAN(),\n                           0,\n                           false,\n                           false);\n   }\n\n   if (srcCount > 0) {\n      auto literal = 0u;\n      auto abs = false;\n\n      if (inst.word0.SRC0_SEL() == SQ_ALU_SRC::LITERAL) {\n         literal = literals[inst.word0.SRC0_CHAN()];\n      }\n\n      if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n         abs = !!inst.op2.SRC0_ABS();\n      }\n\n      fmt::format_to(std::back_inserter(out), \", \");\n      disassembleAluSource(out,\n                           parent,\n                           groupPC,\n                           inst.word0.INDEX_MODE(),\n                           inst.word0.SRC0_SEL(),\n                           inst.word0.SRC0_REL(),\n                           inst.word0.SRC0_CHAN(),\n                           literal,\n                           inst.word0.SRC0_NEG(),\n                           abs);\n   }\n\n   if (srcCount > 1) {\n      auto literal = 0u;\n      auto abs = false;\n\n      if (inst.word0.SRC1_SEL() == SQ_ALU_SRC::LITERAL) {\n         literal = literals[inst.word0.SRC1_CHAN()];\n      }\n\n      if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n         abs = !!inst.op2.SRC1_ABS();\n      }\n\n      fmt::format_to(std::back_inserter(out), \", \");\n      disassembleAluSource(out,\n                           parent,\n                           groupPC,\n                           inst.word0.INDEX_MODE(),\n                           inst.word0.SRC1_SEL(),\n                           inst.word0.SRC1_REL(),\n                           inst.word0.SRC1_CHAN(),\n                           literal,\n                           inst.word0.SRC1_NEG(),\n                           abs);\n   }\n\n   if (srcCount > 2) {\n      auto literal = 0u;\n\n      if (inst.op3.SRC2_SEL() == SQ_ALU_SRC::LITERAL) {\n         literal = literals[inst.op3.SRC2_CHAN()];\n      }\n\n      fmt::format_to(std::back_inserter(out), \", \");\n      disassembleAluSource(out,\n                           parent,\n                           groupPC,\n                           inst.word0.INDEX_MODE(),\n                           inst.op3.SRC2_SEL(),\n                           inst.op3.SRC2_REL(),\n                           inst.op3.SRC2_CHAN(),\n                           literal,\n                           inst.op3.SRC2_NEG(),\n                           false);\n   }\n\n   if (inst.word1.CLAMP()) {\n      fmt::format_to(std::back_inserter(out), \" CLAMP\");\n   }\n\n   if (isTranscendentalOnly(flags)) {\n      switch (static_cast<SQ_ALU_SCL_BANK_SWIZZLE>(inst.word1.BANK_SWIZZLE())) {\n      case SQ_ALU_SCL_BANK_SWIZZLE::SCL_210:\n         fmt::format_to(std::back_inserter(out), \" SCL_210\");\n         break;\n      case SQ_ALU_SCL_BANK_SWIZZLE::SCL_122:\n         fmt::format_to(std::back_inserter(out), \" SCL_122\");\n         break;\n      case SQ_ALU_SCL_BANK_SWIZZLE::SCL_212:\n         fmt::format_to(std::back_inserter(out), \" SCL_212\");\n         break;\n      case SQ_ALU_SCL_BANK_SWIZZLE::SCL_221:\n         fmt::format_to(std::back_inserter(out), \" SCL_221\");\n         break;\n      default:\n         decaf_abort(fmt::format(\"Unexpected BANK_SWIZZLE {}\", inst.word1.BANK_SWIZZLE()));\n      }\n   } else {\n      switch (inst.word1.BANK_SWIZZLE()) {\n      case SQ_ALU_VEC_BANK_SWIZZLE::VEC_012:\n         // This is default, no need to print\n         break;\n      case SQ_ALU_VEC_BANK_SWIZZLE::VEC_021:\n         fmt::format_to(std::back_inserter(out), \" VEC_021\");\n         break;\n      case SQ_ALU_VEC_BANK_SWIZZLE::VEC_120:\n         fmt::format_to(std::back_inserter(out), \" VEC_120\");\n         break;\n      case SQ_ALU_VEC_BANK_SWIZZLE::VEC_102:\n         fmt::format_to(std::back_inserter(out), \" VEC_102\");\n         break;\n      case SQ_ALU_VEC_BANK_SWIZZLE::VEC_201:\n         fmt::format_to(std::back_inserter(out), \" VEC_201\");\n         break;\n      case SQ_ALU_VEC_BANK_SWIZZLE::VEC_210:\n         fmt::format_to(std::back_inserter(out), \" VEC_210\");\n         break;\n      default:\n         decaf_abort(fmt::format(\"Unexpected BANK_SWIZZLE {}\", inst.word1.BANK_SWIZZLE()));\n      }\n   }\n\n   switch (inst.word0.PRED_SEL()) {\n   case SQ_PRED_SEL::OFF:\n      break;\n   case SQ_PRED_SEL::ZERO:\n      fmt::format_to(std::back_inserter(out), \" PRED_SEL_ZERO\");\n      break;\n   case SQ_PRED_SEL::ONE:\n      fmt::format_to(std::back_inserter(out), \" PRED_SEL_ONE\");\n      break;\n   default:\n      decaf_abort(fmt::format(\"Unexpected PRED_SEL {}\", inst.word0.PRED_SEL()));\n   }\n\n   if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n      if (inst.op2.UPDATE_EXECUTE_MASK()) {\n         fmt::format_to(std::back_inserter(out), \" UPDATE_EXEC_MASK\");\n      }\n\n      if (inst.op2.UPDATE_PRED()) {\n         fmt::format_to(std::back_inserter(out), \" UPDATE_PRED\");\n      }\n   }\n}\n\nstatic void\ndisassembleAluClause(State &state, const latte::ControlFlowInst &parent, uint32_t addr, uint32_t slots)\n{\n   static char unitName[5] = { 'x', 'y', 'z', 'w', 't' };\n   auto clause = reinterpret_cast<const AluInst *>(state.binary.data() + 8 * addr);\n\n   for (size_t slot = 0u; slot < slots; ) {\n      auto units = AluGroupUnits { };\n      auto group = AluGroup { clause + slot };\n\n      fmt::format_to(std::back_inserter(state.out), \"\\n{}{: <3}\", state.indent, state.groupPC);\n\n      for (auto j = 0u; j < group.instructions.size(); ++j) {\n         auto &inst = group.instructions[j];\n         auto unit = units.addInstructionUnit(inst);\n\n         if (j > 0) {\n            fmt::format_to(std::back_inserter(state.out), \"{}   \", state.indent);\n         }\n\n         fmt::format_to(std::back_inserter(state.out), \" {}: \", unitName[unit]);\n\n         disassembleAluInstruction(state.out, parent, inst, state.groupPC, unit, group.literals, 15);\n         fmt::format_to(std::back_inserter(state.out), \"\\n\");\n      }\n\n      slot = group.getNextSlot(slot);\n      state.groupPC++;\n   }\n}\n\nvoid\ndisassembleCfALUInstruction(fmt::memory_buffer &out,\n                            const ControlFlowInst &inst)\n{\n   auto name = getInstructionName(inst.alu.word1.CF_INST());\n   auto addr = inst.alu.word0.ADDR();\n   auto count = inst.alu.word1.COUNT() + 1;\n\n   fmt::format_to(std::back_inserter(out), \"{}: ADDR({}) CNT({})\", name, addr, count);\n\n   if (!inst.word1.BARRIER()) {\n      fmt::format_to(std::back_inserter(out), \" NO_BARRIER\");\n   }\n\n   if (inst.word1.WHOLE_QUAD_MODE()) {\n      fmt::format_to(std::back_inserter(out), \" WHOLE_QUAD\");\n   }\n\n   disassembleKcache(out, 0, inst.alu.word0.KCACHE_MODE0(), inst.alu.word0.KCACHE_BANK0(), inst.alu.word1.KCACHE_ADDR0());\n   disassembleKcache(out, 1, inst.alu.word1.KCACHE_MODE1(), inst.alu.word0.KCACHE_BANK1(), inst.alu.word1.KCACHE_ADDR1());\n}\n\nvoid\ndisassembleControlFlowALU(State &state, const ControlFlowInst &inst)\n{\n   auto addr = inst.alu.word0.ADDR();\n   auto count = inst.alu.word1.COUNT() + 1;\n\n   fmt::format_to(std::back_inserter(state.out), \"{}{:02} \", state.indent, state.cfPC);\n   disassembleCfALUInstruction(state.out, inst);\n\n   increaseIndent(state);\n   disassembleAluClause(state, inst, addr, count);\n   decreaseIndent(state);\n}\n\n} // namespace disassembler\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_disassembler_export.cpp",
    "content": "#include \"latte/latte_disassembler_state.h\"\n\n#include <iterator>\n\nnamespace latte\n{\n\nnamespace disassembler\n{\n\nchar\ndisassembleDestMask(SQ_SEL sel)\n{\n   switch (sel) {\n   case SQ_SEL::SEL_X:\n      return 'x';\n   case SQ_SEL::SEL_Y:\n      return 'y';\n   case SQ_SEL::SEL_Z:\n      return 'z';\n   case SQ_SEL::SEL_W:\n      return 'w';\n   case SQ_SEL::SEL_0:\n      return '0';\n   case SQ_SEL::SEL_1:\n      return '1';\n   case SQ_SEL::SEL_MASK:\n      return '_';\n   default:\n      return '?';\n   }\n}\n\nvoid\ndisassembleExpInstruction(fmt::memory_buffer &out, const ControlFlowInst &inst)\n{\n   auto id = inst.exp.word1.CF_INST();\n   auto name = getInstructionName(id);\n   fmt::format_to(std::back_inserter(out), \"{}:\", name);\n\n   auto type = inst.exp.word0.TYPE();\n   auto memExpType = static_cast<SQ_MEM_EXPORT_TYPE>(inst.exp.word0.TYPE());\n   auto arrayBase = inst.exp.word0.ARRAY_BASE();\n\n   if (id == SQ_CF_INST_EXP || id == SQ_CF_INST_EXP_DONE) {\n      switch (type) {\n      case SQ_EXPORT_TYPE::PIXEL:\n         fmt::format_to(std::back_inserter(out), \" PIX{}\", arrayBase);\n         break;\n      case SQ_EXPORT_TYPE::POS:\n         fmt::format_to(std::back_inserter(out), \" POS{}\", arrayBase - 60);\n         break;\n      case SQ_EXPORT_TYPE::PARAM:\n         fmt::format_to(std::back_inserter(out), \" PARAM{}\", arrayBase);\n         break;\n      default:\n         fmt::format_to(std::back_inserter(out), \" INVALID{}\", arrayBase);\n         break;\n      }\n   } else if (id >= SQ_CF_INST_MEM_STREAM0 && id <= SQ_CF_INST_MEM_STREAM3\n              && (memExpType == SQ_MEM_EXPORT_TYPE::READ || memExpType == SQ_MEM_EXPORT_TYPE::READ_IND)) {\n      fmt::format_to(std::back_inserter(out), \" INVALID_READ\");\n   } else {\n      if (memExpType == SQ_MEM_EXPORT_TYPE::WRITE || memExpType == SQ_MEM_EXPORT_TYPE::WRITE_IND) {\n         fmt::format_to(std::back_inserter(out), \" WRITE(\");\n      } else {\n         fmt::format_to(std::back_inserter(out), \" READ(\");\n      }\n\n      if (id == SQ_CF_INST_MEM_SCRATCH || id == SQ_CF_INST_MEM_REDUCTION) {\n         fmt::format_to(std::back_inserter(out), \"{}\", arrayBase * 16);\n      } else {\n         fmt::format_to(std::back_inserter(out), \"{}\", arrayBase * 4);\n      }\n\n      if (memExpType == SQ_MEM_EXPORT_TYPE::WRITE_IND || memExpType == SQ_MEM_EXPORT_TYPE::READ_IND) {\n         fmt::format_to(std::back_inserter(out), \" + R{}\", inst.exp.word0.INDEX_GPR());\n      }\n\n      fmt::format_to(std::back_inserter(out), \")\");\n\n   }\n\n   fmt::format_to(std::back_inserter(out), \", \");\n\n   if (inst.exp.word0.RW_REL() == SQ_REL::REL) {\n      fmt::format_to(std::back_inserter(out), \"R[AL + {}]\", inst.exp.word0.RW_GPR());\n   } else {\n      fmt::format_to(std::back_inserter(out), \"R{}\", inst.exp.word0.RW_GPR());\n   }\n\n   if (id == SQ_CF_INST_EXP || id == SQ_CF_INST_EXP_DONE) {\n      fmt::format_to(std::back_inserter(out), \".{}{}{}{}\",\n         disassembleDestMask(inst.exp.swiz.SEL_X()),\n         disassembleDestMask(inst.exp.swiz.SEL_Y()),\n         disassembleDestMask(inst.exp.swiz.SEL_Z()),\n         disassembleDestMask(inst.exp.swiz.SEL_W()));\n   } else {\n      fmt::format_to(std::back_inserter(out), \".{}{}{}{}\",\n         (inst.exp.buf.COMP_MASK() & (1 << 0) ? 'x' : '_'),\n         (inst.exp.buf.COMP_MASK() & (1 << 1) ? 'y' : '_'),\n         (inst.exp.buf.COMP_MASK() & (1 << 2) ? 'z' : '_'),\n         (inst.exp.buf.COMP_MASK() & (1 << 3) ? 'w' : '_'));\n\n      fmt::format_to(std::back_inserter(out), \" ARRAY_SIZE({})\", inst.exp.buf.ARRAY_SIZE());\n   }\n\n   if (inst.exp.word0.ELEM_SIZE()) {\n      fmt::format_to(std::back_inserter(out), \" ELEM_SIZE({})\", inst.exp.word0.ELEM_SIZE());\n   }\n\n   if (inst.exp.word1.BURST_COUNT()) {\n      fmt::format_to(std::back_inserter(out), \" BURSTCNT({})\", inst.exp.word1.BURST_COUNT());\n   }\n\n   if (!inst.exp.word1.BARRIER()) {\n      fmt::format_to(std::back_inserter(out), \" NO_BARRIER\");\n   }\n\n   if (inst.exp.word1.WHOLE_QUAD_MODE()) {\n      fmt::format_to(std::back_inserter(out), \" WHOLE_QUAD\");\n   }\n\n   if (inst.word1.VALID_PIXEL_MODE()) {\n      fmt::format_to(std::back_inserter(out), \" VALID_PIX\");\n   }\n}\n\nvoid\ndisassembleExport(State &state, const ControlFlowInst &inst)\n{\n   fmt::format_to(std::back_inserter(state.out), \"{}{:02} \", state.indent, state.cfPC);\n   disassembleExpInstruction(state.out, inst);\n   fmt::format_to(std::back_inserter(state.out), \"\\n\");\n}\n\n} // namespace disassembler\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_disassembler_state.h",
    "content": "#pragma once\n#include \"latte/latte_instructions.h\"\n\n#include <fmt/format.h>\n#include <gsl/gsl-lite.hpp>\n\nnamespace latte\n{\n\nnamespace disassembler\n{\n\nstruct State\n{\n   gsl::span<const uint8_t> binary;\n   fmt::memory_buffer out;\n   std::string indent;\n   size_t cfPC;\n   size_t groupPC;\n};\n\nvoid\nincreaseIndent(State& state);\n\nvoid\ndecreaseIndent(State& state);\n\nvoid\ndisassembleControlFlowALU(State& state, const ControlFlowInst& inst);\n\nvoid\ndisassembleVtxClause(State& state, const latte::ControlFlowInst& parent);\n\nvoid\ndisassembleTEXClause(State& state, const ControlFlowInst& inst);\n\nvoid\ndisassembleExport(State& state, const ControlFlowInst& inst);\n\nchar\ndisassembleDestMask(SQ_SEL sel);\n\nvoid\ndisassembleCondition(fmt::memory_buffer& out, const ControlFlowInst& inst);\n\nvoid\ndisassembleCfTEX(fmt::memory_buffer& out, const ControlFlowInst& inst);\n\nvoid\ndisassembleCfVTX(fmt::memory_buffer& out, const ControlFlowInst& inst);\n\nvoid\ndisassembleCF(fmt::memory_buffer& out, const ControlFlowInst& inst);\n\nvoid\ndisassembleAluInstruction(fmt::memory_buffer& out,\n                          const ControlFlowInst& parent,\n                          const AluInst& inst,\n                          size_t groupPC,\n                          SQ_CHAN unit,\n                          const gsl::span<const uint32_t>& literals,\n                          int namePad = 0);\n\nvoid\ndisassembleCfALUInstruction(fmt::memory_buffer& out,\n                            const ControlFlowInst& inst);\n\nvoid\ndisassembleExpInstruction(fmt::memory_buffer& out,\n                          const ControlFlowInst& inst);\n\nvoid\ndisassembleVtxInstruction(fmt::memory_buffer& out,\n                          const latte::ControlFlowInst& parent,\n                          const VertexFetchInst& tex,\n                          int namePad = 0);\n\nvoid\ndisassembleTexInstruction(fmt::memory_buffer& out,\n                          const latte::ControlFlowInst& parent,\n                          const TextureFetchInst& tex,\n                          int namePad = 0);\n\n} // namespace disassembler\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_disassembler_tex.cpp",
    "content": "#include \"latte/latte_disassembler_state.h\"\n\n#include <common/bitutils.h>\n#include <common/decaf_assert.h>\n#include <common/fixed.h>\n#include <fmt/core.h>\n#include <iterator>\n\nnamespace latte\n{\n\nnamespace disassembler\n{\n\nvoid\ndisassembleTexInstruction(fmt::memory_buffer &out,\n                          const latte::ControlFlowInst &parent,\n                          const TextureFetchInst &tex,\n                          int namePad)\n{\n   auto id = tex.word0.TEX_INST();\n   auto name = getInstructionName(id);\n\n   if (id == SQ_TEX_INST_VTX_FETCH || id == SQ_TEX_INST_VTX_SEMANTIC) {\n      // This only works because the TEX instruction IDs precisely match the\n      //  VTX instruction IDs.  Ensure that is the case for anything else\n      //  that is added to this redirection code.\n      auto vtx = *reinterpret_cast<const VertexFetchInst*>(&tex);\n      disassembleVtxInstruction(out, parent, vtx, namePad);\n      return;\n   }\n\n   fmt::format_to(std::back_inserter(out), \"{: <{}} \", name, namePad);\n\n   // dst\n   auto dstSelX = tex.word1.DST_SEL_X();\n   auto dstSelY = tex.word1.DST_SEL_Y();\n   auto dstSelZ = tex.word1.DST_SEL_Z();\n   auto dstSelW = tex.word1.DST_SEL_W();\n\n   if (dstSelX != latte::SQ_SEL::SEL_MASK || dstSelY != latte::SQ_SEL::SEL_MASK || dstSelZ != latte::SQ_SEL::SEL_MASK || dstSelW != latte::SQ_SEL::SEL_MASK) {\n      fmt::format_to(std::back_inserter(out), \"R{}\", tex.word1.DST_GPR());\n\n      if (tex.word1.DST_REL() == SQ_REL::REL) {\n         fmt::format_to(std::back_inserter(out), \"[AL]\");\n      }\n\n      fmt::format_to(std::back_inserter(out), \".{}{}{}{}\",\n         disassembleDestMask(dstSelX),\n         disassembleDestMask(dstSelY),\n         disassembleDestMask(dstSelZ),\n         disassembleDestMask(dstSelW));\n   } else {\n      fmt::format_to(std::back_inserter(out), \"____\");\n   }\n\n\n   // src\n   fmt::format_to(std::back_inserter(out), \", R{}\", tex.word0.SRC_GPR());\n\n   if (tex.word0.SRC_REL() == SQ_REL::REL) {\n      fmt::format_to(std::back_inserter(out), \"[AL]\");\n   }\n\n   fmt::format_to(std::back_inserter(out), \".{}{}{}{}\",\n      disassembleDestMask(tex.word2.SRC_SEL_X()),\n      disassembleDestMask(tex.word2.SRC_SEL_Y()),\n      disassembleDestMask(tex.word2.SRC_SEL_Z()),\n      disassembleDestMask(tex.word2.SRC_SEL_W()));\n\n   fmt::format_to(std::back_inserter(out), \", t{}, s{}\",\n                  tex.word0.RESOURCE_ID(),\n                  tex.word2.SAMPLER_ID());\n\n   if (tex.word1.LOD_BIAS()) {\n      fmt::format_to(std::back_inserter(out), \" LOD({})\", static_cast<float>(tex.word1.LOD_BIAS()));\n   }\n\n   if (tex.word0.FETCH_WHOLE_QUAD()) {\n      fmt::format_to(std::back_inserter(out), \" WHOLE_QUAD\");\n   }\n\n   if (tex.word0.BC_FRAC_MODE()) {\n      fmt::format_to(std::back_inserter(out), \" BC_FRAC_MODE\");\n   }\n\n   if (tex.word0.ALT_CONST()) {\n      fmt::format_to(std::back_inserter(out), \" ALT_CONST\");\n   }\n\n   auto normX = tex.word1.COORD_TYPE_X();\n   auto normY = tex.word1.COORD_TYPE_Y();\n   auto normZ = tex.word1.COORD_TYPE_Z();\n   auto normW = tex.word1.COORD_TYPE_W();\n\n   if (!normX || !normY || !normZ || !normW) {\n      fmt::format_to(std::back_inserter(out), \" DENORM(\");\n\n      if (!normX) {\n         fmt::format_to(std::back_inserter(out), \"X\");\n      }\n\n      if (!normY) {\n         fmt::format_to(std::back_inserter(out), \"Y\");\n      }\n\n      if (!normZ) {\n         fmt::format_to(std::back_inserter(out), \"Z\");\n      }\n\n      if (!normW) {\n         fmt::format_to(std::back_inserter(out), \"W\");\n      }\n\n      fmt::format_to(std::back_inserter(out), \")\");\n   }\n\n   if (tex.word2.OFFSET_X()) {\n      fmt::format_to(std::back_inserter(out), \" XOFFSET({})\", static_cast<float>(tex.word2.OFFSET_X()));\n   }\n\n   if (tex.word2.OFFSET_Y()) {\n      fmt::format_to(std::back_inserter(out), \" YOFFSET({})\", static_cast<float>(tex.word2.OFFSET_Y()));\n   }\n\n   if (tex.word2.OFFSET_Z()) {\n      fmt::format_to(std::back_inserter(out), \" ZOFFSET({})\", static_cast<float>(tex.word2.OFFSET_Z()));\n   }\n}\n\nvoid\ndisassembleTEXClause(State &state, const ControlFlowInst &inst)\n{\n   auto addr = inst.word0.ADDR();\n   auto count = (inst.word1.COUNT() | (inst.word1.COUNT_3() << 3)) + 1;\n   auto clause = reinterpret_cast<const TextureFetchInst *>(state.binary.data() + 8 * addr);\n\n   increaseIndent(state);\n\n   for (auto i = 0u; i < count; ++i) {\n      const auto &tex = clause[i];\n      auto id = tex.word0.TEX_INST();\n\n      fmt::format_to(std::back_inserter(state.out), \"\\n{}{: <3}    \", state.indent, state.groupPC);\n\n      if (id == SQ_TEX_INST_VTX_FETCH || id == SQ_TEX_INST_VTX_SEMANTIC) {\n         // Someone at AMD must have been having a laugh when they designed this...\n         auto vtx = *reinterpret_cast<const VertexFetchInst *>(&tex);\n         disassembleVtxInstruction(state.out, inst, vtx, 15);\n      } else {\n         disassembleTexInstruction(state.out, inst, tex, 15);\n      }\n\n      fmt::format_to(std::back_inserter(state.out), \"\\n\");\n      state.groupPC++;\n   }\n\n   decreaseIndent(state);\n}\n\nvoid\ndisassembleCfTEX(fmt::memory_buffer &out, const ControlFlowInst &inst)\n{\n   auto addr = inst.word0.ADDR();\n   auto count = (inst.word1.COUNT() | (inst.word1.COUNT_3() << 3)) + 1;\n\n   fmt::format_to(std::back_inserter(out), \": ADDR({}) CNT({})\", addr, count);\n\n   if (!inst.word1.BARRIER()) {\n      fmt::format_to(std::back_inserter(out), \" NO_BARRIER\");\n   }\n\n   disassembleCondition(out, inst);\n\n   if (inst.word1.WHOLE_QUAD_MODE()) {\n      fmt::format_to(std::back_inserter(out), \" WHOLE_QUAD\");\n   }\n\n   if (inst.word1.VALID_PIXEL_MODE()) {\n      fmt::format_to(std::back_inserter(out), \" VALID_PIX\");\n   }\n}\n\n} // namespace disassembler\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_disassembler_vtx.cpp",
    "content": "#include \"latte/latte_disassembler_state.h\"\n\n#include <common/decaf_assert.h>\n#include <fmt/core.h>\n#include <iterator>\n\nnamespace latte\n{\n\nnamespace disassembler\n{\n\nvoid\ndisassembleVtxInstruction(fmt::memory_buffer &out,\n                          const latte::ControlFlowInst &parent,\n                          const VertexFetchInst &vtx,\n                          int namePad)\n{\n   auto id = vtx.word0.VTX_INST();\n   auto name = getInstructionName(id);\n\n   fmt::format_to(std::back_inserter(out), \"{: <{}} \", name, namePad);\n\n   // dst\n   auto dstSelX = vtx.word1.DST_SEL_X();\n   auto dstSelY = vtx.word1.DST_SEL_Y();\n   auto dstSelZ = vtx.word1.DST_SEL_Z();\n   auto dstSelW = vtx.word1.DST_SEL_W();\n\n   if (dstSelX != latte::SQ_SEL::SEL_MASK || dstSelY != latte::SQ_SEL::SEL_MASK || dstSelZ != latte::SQ_SEL::SEL_MASK || dstSelW != latte::SQ_SEL::SEL_MASK) {\n      if (id == SQ_VTX_INST_SEMANTIC) {\n         fmt::format_to(std::back_inserter(out), \"SEM[{}]\", vtx.sem.SEMANTIC_ID());\n      } else {\n         fmt::format_to(std::back_inserter(out), \"R{}\", vtx.gpr.DST_GPR());\n\n         if (vtx.gpr.DST_REL() == SQ_REL::REL) {\n            fmt::format_to(std::back_inserter(out), \"[AL]\");\n         }\n      }\n\n      fmt::format_to(std::back_inserter(out), \".{}{}{}{}\",\n                     disassembleDestMask(dstSelX),\n                     disassembleDestMask(dstSelY),\n                     disassembleDestMask(dstSelZ),\n                     disassembleDestMask(dstSelW));\n   } else {\n      fmt::format_to(std::back_inserter(out), \"____\");\n   }\n\n\n   // src\n   fmt::format_to(std::back_inserter(out), \", R{}\", vtx.word0.SRC_GPR());\n\n   if (vtx.word0.SRC_REL() == SQ_REL::REL) {\n      fmt::format_to(std::back_inserter(out), \"[AL]\");\n   }\n\n   fmt::format_to(std::back_inserter(out), \".{}, b{}\",\n                  disassembleDestMask(vtx.word0.SRC_SEL_X()),\n                  vtx.word0.BUFFER_ID());\n\n\n   // fetch_type\n   fmt::format_to(std::back_inserter(out), \" FETCH_TYPE(\");\n   if (vtx.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::VERTEX_DATA) {\n      fmt::format_to(std::back_inserter(out), \"VERTEX_DATA\");\n   } else if (vtx.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::INSTANCE_DATA) {\n      fmt::format_to(std::back_inserter(out), \"INSTANCE_DATA\");\n   } else if(vtx.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::NO_INDEX_OFFSET) {\n      fmt::format_to(std::back_inserter(out), \"NO_INDEX_OFFSET\");\n   } else {\n      fmt::format_to(std::back_inserter(out), \"{}\", vtx.word0.FETCH_TYPE());\n   }\n   fmt::format_to(std::back_inserter(out), \")\");\n\n\n   // format\n   if (!vtx.word1.USE_CONST_FIELDS()) {\n      fmt::format_to(std::back_inserter(out), \" FORMAT(\");\n\n      fmt::format_to(std::back_inserter(out), \" {}\", vtx.word1.DATA_FORMAT());\n\n      if (vtx.word1.NUM_FORMAT_ALL() == 0) {\n         fmt::format_to(std::back_inserter(out), \" NORM\");\n      } else if (vtx.word1.NUM_FORMAT_ALL() == 1) {\n         fmt::format_to(std::back_inserter(out), \" INT\");\n      } else if (vtx.word1.NUM_FORMAT_ALL() == 2) {\n         fmt::format_to(std::back_inserter(out), \" SCALED\");\n      } else {\n         fmt::format_to(std::back_inserter(out), \"{}\", vtx.word1.NUM_FORMAT_ALL());\n      }\n\n      if (vtx.word1.FORMAT_COMP_ALL()) {\n         fmt::format_to(std::back_inserter(out), \" SIGNED\");\n      } else {\n         fmt::format_to(std::back_inserter(out), \" UNSIGNED\");\n      }\n\n      fmt::format_to(std::back_inserter(out), \" {}\", vtx.word1.SRF_MODE_ALL());\n\n      fmt::format_to(std::back_inserter(out), \")\");\n   }\n\n   if (vtx.word2.MEGA_FETCH()) {\n      fmt::format_to(std::back_inserter(out), \" MEGA({})\", vtx.word0.MEGA_FETCH_COUNT() + 1);\n   } else {\n      fmt::format_to(std::back_inserter(out), \" MINI({})\", vtx.word0.MEGA_FETCH_COUNT() + 1);\n   }\n\n   fmt::format_to(std::back_inserter(out), \" OFFSET({})\", vtx.word2.OFFSET());\n\n   if (vtx.word0.FETCH_WHOLE_QUAD()) {\n      fmt::format_to(std::back_inserter(out), \" WHOLE_QUAD\");\n   }\n\n   if (vtx.word2.ENDIAN_SWAP() == SQ_ENDIAN::SWAP_8IN16) {\n      fmt::format_to(std::back_inserter(out), \" ENDIAN_SWAP(8IN16)\");\n   } else if (vtx.word2.ENDIAN_SWAP() == SQ_ENDIAN::SWAP_8IN32) {\n      fmt::format_to(std::back_inserter(out), \" ENDIAN_SWAP(8IN32)\");\n   } else if (vtx.word2.ENDIAN_SWAP() != SQ_ENDIAN::NONE) {\n      fmt::format_to(std::back_inserter(out), \" ENDIAN_SWAP({})\", vtx.word2.ENDIAN_SWAP());\n   }\n\n   if (vtx.word2.CONST_BUF_NO_STRIDE()) {\n      fmt::format_to(std::back_inserter(out), \" CONST_BUF_NO_STRIDE\");\n   }\n\n   if (vtx.word2.ALT_CONST()) {\n      fmt::format_to(std::back_inserter(out), \" ALT_CONST\");\n   }\n}\n\nvoid\ndisassembleVtxClause(State &state, const latte::ControlFlowInst &inst)\n{\n   auto addr = inst.word0.ADDR();\n   auto count = (inst.word1.COUNT() | (inst.word1.COUNT_3() << 3)) + 1;\n   auto clause = reinterpret_cast<const VertexFetchInst *>(state.binary.data() + 8 * addr);\n\n   increaseIndent(state);\n\n   for (auto i = 0u; i < count; ++i) {\n      const auto &vtx = clause[i];\n\n      fmt::format_to(std::back_inserter(state.out), \"\\n{}{: <3}    \", state.indent, state.groupPC);\n      disassembleVtxInstruction(state.out, inst, vtx, 15);\n      fmt::format_to(std::back_inserter(state.out), \"\\n\");\n      state.groupPC++;\n   }\n\n   decreaseIndent(state);\n}\n\nvoid\ndisassembleCfVTX(fmt::memory_buffer &out, const ControlFlowInst &inst)\n{\n   auto addr = inst.word0.ADDR();\n   auto count = (inst.word1.COUNT() | (inst.word1.COUNT_3() << 3)) + 1;\n\n   fmt::format_to(std::back_inserter(out), \": ADDR({}) CNT({})\", addr, count);\n\n   if (!inst.word1.BARRIER()) {\n      fmt::format_to(std::back_inserter(out), \" NO_BARRIER\");\n   }\n\n   disassembleCondition(out, inst);\n}\n\n} // namespace disassembler\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_endian.h",
    "content": "#pragma once\n#include \"latte/latte_enum_cb.h\"\n#include <common/byte_swap.h>\n\nnamespace latte\n{\n\nstatic inline uint64_t\napplyEndianSwap(uint64_t value, latte::CB_ENDIAN swap)\n{\n   static const auto swap16 = [](uint64_t value, int pos)\n      {\n         auto word = static_cast<uint16_t>((value >> pos) & 0xFFFFu);\n         word = byte_swap(word);\n         return static_cast<uint64_t>(word) << pos;\n      };\n   static const auto swap32 = [](uint64_t value, int pos)\n      {\n         auto word = static_cast<uint32_t>((value >> pos) & 0xFFFFFFFFu);\n         word = byte_swap(word);\n         return static_cast<uint64_t>(word) << pos;\n      };\n\n   switch (swap) {\n   case latte::CB_ENDIAN::NONE:\n      break;\n   case latte::CB_ENDIAN::SWAP_8IN16:\n      value = swap16(value, 0) | swap16(value, 16) |\n              swap16(value, 32) | swap16(value, 48);\n      break;\n   case latte::CB_ENDIAN::SWAP_8IN32:\n      value = swap32(value, 0) | swap32(value, 32);\n      break;\n   case latte::CB_ENDIAN::SWAP_8IN64:\n      value = byte_swap(value);\n      break;\n   }\n\n   return value;\n}\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_formats.cpp",
    "content": "#include \"latte/latte_formats.h\"\n\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <fmt/core.h>\n\nnamespace latte\n{\n\nenum SurfaceFormatType : uint32_t\n{\n   Unorm = 0x0,\n   Uint = 0x1,\n   Snorm = 0x2,\n   Sint = 0x3,\n   Srgb = 0x4,\n   Float = 0x8,\n};\n\nstatic void\nvalidateSurfaceFormat(SurfaceFormat format)\n{\n   switch (format) {\n   case SurfaceFormat::R8Unorm:\n   case SurfaceFormat::R8Uint:\n   case SurfaceFormat::R8Snorm:\n   case SurfaceFormat::R8Sint:\n   case SurfaceFormat::R4G4Unorm:\n   case SurfaceFormat::R16Unorm:\n   case SurfaceFormat::R16Uint:\n   case SurfaceFormat::R16Snorm:\n   case SurfaceFormat::R16Sint:\n   case SurfaceFormat::R16Float:\n   case SurfaceFormat::R8G8Unorm:\n   case SurfaceFormat::R8G8Uint:\n   case SurfaceFormat::R8G8Snorm:\n   case SurfaceFormat::R8G8Sint:\n   case SurfaceFormat::R5G6B5Unorm:\n   case SurfaceFormat::R5G5B5A1Unorm:\n   case SurfaceFormat::R4G4B4A4Unorm:\n   case SurfaceFormat::A1B5G5R5Unorm:\n   case SurfaceFormat::R32Uint:\n   case SurfaceFormat::R32Sint:\n   case SurfaceFormat::R32Float:\n   case SurfaceFormat::R16G16Unorm:\n   case SurfaceFormat::R16G16Uint:\n   case SurfaceFormat::R16G16Snorm:\n   case SurfaceFormat::R16G16Sint:\n   case SurfaceFormat::R16G16Float:\n   case SurfaceFormat::D24UnormS8Uint:\n   case SurfaceFormat::X24G8Uint:\n   case SurfaceFormat::R11G11B10Float:\n   case SurfaceFormat::R10G10B10A2Unorm:\n   case SurfaceFormat::R10G10B10A2Uint:\n   case SurfaceFormat::R10G10B10A2Snorm:\n   case SurfaceFormat::R10G10B10A2Sint:\n   case SurfaceFormat::R8G8B8A8Unorm:\n   case SurfaceFormat::R8G8B8A8Uint:\n   case SurfaceFormat::R8G8B8A8Snorm:\n   case SurfaceFormat::R8G8B8A8Sint:\n   case SurfaceFormat::R8G8B8A8Srgb:\n   case SurfaceFormat::A2B10G10R10Unorm:\n   case SurfaceFormat::A2B10G10R10Uint:\n   case SurfaceFormat::D32FloatS8UintX24:\n   case SurfaceFormat::D32G8UintX24:\n   case SurfaceFormat::R32G32Uint:\n   case SurfaceFormat::R32G32Sint:\n   case SurfaceFormat::R32G32Float:\n   case SurfaceFormat::R16G16B16A16Unorm:\n   case SurfaceFormat::R16G16B16A16Uint:\n   case SurfaceFormat::R16G16B16A16Snorm:\n   case SurfaceFormat::R16G16B16A16Sint:\n   case SurfaceFormat::R16G16B16A16Float:\n   case SurfaceFormat::R32G32B32A32Uint:\n   case SurfaceFormat::R32G32B32A32Sint:\n   case SurfaceFormat::R32G32B32A32Float:\n   case SurfaceFormat::BC1Unorm:\n   case SurfaceFormat::BC1Srgb:\n   case SurfaceFormat::BC2Unorm:\n   case SurfaceFormat::BC2Srgb:\n   case SurfaceFormat::BC3Unorm:\n   case SurfaceFormat::BC3Srgb:\n   case SurfaceFormat::BC4Unorm:\n   case SurfaceFormat::BC4Snorm:\n   case SurfaceFormat::BC5Unorm:\n   case SurfaceFormat::BC5Snorm:\n   case SurfaceFormat::NV12:\n      return;\n   default:\n      decaf_abort(fmt::format(\"Unexpected generated surface format {}\", format));\n   }\n}\n\nstatic SurfaceFormatType\ngetSurfaceFormatType(latte::SQ_NUM_FORMAT numFormat,\n                     latte::SQ_FORMAT_COMP formatComp,\n                     bool forceDegamma)\n{\n   if (forceDegamma) {\n      decaf_check(numFormat == latte::SQ_NUM_FORMAT::NORM);\n      decaf_check(formatComp == latte::SQ_FORMAT_COMP::UNSIGNED);\n\n      return SurfaceFormatType::Srgb;\n   } else {\n      if (numFormat == latte::SQ_NUM_FORMAT::NORM) {\n         if (formatComp == latte::SQ_FORMAT_COMP::UNSIGNED) {\n            return SurfaceFormatType::Unorm;\n         } else if (formatComp == latte::SQ_FORMAT_COMP::SIGNED) {\n            return SurfaceFormatType::Snorm;\n         } else {\n            decaf_abort(fmt::format(\"Unexpected surface format comp {}\", formatComp));\n         }\n      } else if (numFormat == latte::SQ_NUM_FORMAT::INT) {\n         if (formatComp == latte::SQ_FORMAT_COMP::UNSIGNED) {\n            return SurfaceFormatType::Uint;\n         } else if (formatComp == latte::SQ_FORMAT_COMP::SIGNED) {\n            return SurfaceFormatType::Sint;\n         } else {\n            decaf_abort(fmt::format(\"Unexpected surface format comp {}\", formatComp));\n         }\n      } else if (numFormat == latte::SQ_NUM_FORMAT::SCALED) {\n         decaf_check(formatComp == latte::SQ_FORMAT_COMP::UNSIGNED);\n\n         return SurfaceFormatType::Float;\n      } else {\n         decaf_abort(fmt::format(\"Unexpected surface number format {}\", numFormat));\n      }\n   }\n}\n\nSurfaceFormat\ngetSurfaceFormat(latte::SQ_DATA_FORMAT dataFormat,\n                 latte::SQ_NUM_FORMAT numFormat,\n                 latte::SQ_FORMAT_COMP formatComp,\n                 bool forceDegamma)\n{\n   if (dataFormat == latte::SQ_DATA_FORMAT::FMT_INVALID) {\n      return latte::SurfaceFormat::Invalid;\n   }\n\n   auto formatType = getSurfaceFormatType(numFormat, formatComp, forceDegamma);\n\n   auto formatTypeBits = static_cast<uint32_t>(formatType) << 8;\n   auto dataFormatBits = static_cast<uint32_t>(dataFormat);\n\n   auto surfaceFormat = static_cast<SurfaceFormat>(formatTypeBits | dataFormatBits);\n   validateSurfaceFormat(surfaceFormat);\n\n   return surfaceFormat;\n}\n\nlatte::SQ_DATA_FORMAT\ngetSurfaceFormatDataFormat(latte::SurfaceFormat format)\n{\n   return static_cast<latte::SQ_DATA_FORMAT>(format & 0x00FF);\n}\n\nstatic SurfaceFormatType\ngetColorBufferSurfaceFormatType(latte::CB_NUMBER_TYPE numberType)\n{\n   switch (numberType) {\n   case latte::CB_NUMBER_TYPE::UNORM:\n      return SurfaceFormatType::Unorm;\n   case latte::CB_NUMBER_TYPE::SNORM:\n      return SurfaceFormatType::Snorm;\n   case latte::CB_NUMBER_TYPE::UINT:\n      return SurfaceFormatType::Uint;\n   case latte::CB_NUMBER_TYPE::SINT:\n      return SurfaceFormatType::Sint;\n   case latte::CB_NUMBER_TYPE::FLOAT:\n      return SurfaceFormatType::Float;\n   case latte::CB_NUMBER_TYPE::SRGB:\n      return SurfaceFormatType::Srgb;\n   default:\n      decaf_abort(fmt::format(\"Unexpected color buffer number type {}\", numberType));\n   }\n}\n\nSurfaceFormat\ngetColorBufferSurfaceFormat(latte::CB_FORMAT format, latte::CB_NUMBER_TYPE numberType)\n{\n   // Pick the correct surface format type for this number type\n   auto formatType = getColorBufferSurfaceFormatType(numberType);\n\n   // The format types belong in the upper portion of the bits\n   auto formatTypeBits = static_cast<uint32_t>(formatType) << 8;\n\n   // This is safe only becase CB_FORMAT perfectly overlays SQ_DATA_FORMAT\n   auto dataFormatBits = static_cast<uint32_t>(format);\n\n   // Generate our surface format\n   auto surfaceFormat = static_cast<SurfaceFormat>(formatTypeBits | dataFormatBits);\n   validateSurfaceFormat(surfaceFormat);\n\n   return surfaceFormat;\n}\n\nSurfaceFormat\ngetDepthBufferSurfaceFormat(latte::DB_FORMAT format)\n{\n   switch (format) {\n   case latte::DB_FORMAT::DEPTH_16:\n      return SurfaceFormat::R16Unorm;\n   case latte::DB_FORMAT::DEPTH_8_24:\n      return SurfaceFormat::D24UnormS8Uint;\n   //case latte::DB_FORMAT::DEPTH_8_24_FLOAT:\n      // I don't believe this format is supported by the WiiU\n   case latte::DB_FORMAT::DEPTH_32_FLOAT:\n      return SurfaceFormat::R32Float;\n   case latte::DB_FORMAT::DEPTH_X24_8_32_FLOAT:\n      return SurfaceFormat::D32G8UintX24;\n   }\n\n   decaf_abort(fmt::format(\"Depth buffer with unsupported format {}\", format));\n}\n\nuint32_t\ngetDataFormatBitsPerElement(latte::SQ_DATA_FORMAT format)\n{\n   switch (format) {\n   case latte::SQ_DATA_FORMAT::FMT_8:\n   case latte::SQ_DATA_FORMAT::FMT_3_3_2:\n      return 8;\n\n   case latte::SQ_DATA_FORMAT::FMT_8_8:\n   case latte::SQ_DATA_FORMAT::FMT_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_5_6_5:\n   case latte::SQ_DATA_FORMAT::FMT_5_5_5_1:\n   case latte::SQ_DATA_FORMAT::FMT_1_5_5_5:\n   case latte::SQ_DATA_FORMAT::FMT_4_4_4_4:\n      return 16;\n\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8:\n      return 24;\n\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8_8:\n   case latte::SQ_DATA_FORMAT::FMT_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_10_10_10_2:\n   case latte::SQ_DATA_FORMAT::FMT_2_10_10_10:\n   case latte::SQ_DATA_FORMAT::FMT_10_11_11:\n   case latte::SQ_DATA_FORMAT::FMT_10_11_11_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_11_11_10:\n   case latte::SQ_DATA_FORMAT::FMT_11_11_10_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_8_24:\n   case latte::SQ_DATA_FORMAT::FMT_8_24_FLOAT:\n      return 32;\n\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT:\n      return 48;\n\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_BC1:\n   case latte::SQ_DATA_FORMAT::FMT_BC4:\n      return 64;\n\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT:\n      return 96;\n\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_BC2:\n   case latte::SQ_DATA_FORMAT::FMT_BC3:\n   case latte::SQ_DATA_FORMAT::FMT_BC5:\n      return 128;\n\n   default:\n      decaf_abort(fmt::format(\"Unimplemented data format {}\", format));\n   }\n}\n\nbool\ngetDataFormatIsCompressed(latte::SQ_DATA_FORMAT format)\n{\n   switch (format) {\n   case latte::SQ_DATA_FORMAT::FMT_BC1:\n   case latte::SQ_DATA_FORMAT::FMT_BC2:\n   case latte::SQ_DATA_FORMAT::FMT_BC3:\n   case latte::SQ_DATA_FORMAT::FMT_BC4:\n   case latte::SQ_DATA_FORMAT::FMT_BC5:\n      return true;\n   default:\n      return false;\n   }\n}\n\nstd::string\ngetDataFormatName(latte::SQ_DATA_FORMAT format)\n{\n   switch (format) {\n   case latte::SQ_DATA_FORMAT::FMT_8:\n      return \"FMT_8\";\n   case latte::SQ_DATA_FORMAT::FMT_16:\n      return \"FMT_16\";\n   case latte::SQ_DATA_FORMAT::FMT_16_FLOAT:\n      return \"FMT_16_FLOAT\";\n   case latte::SQ_DATA_FORMAT::FMT_32:\n      return \"FMT_32\";\n   case latte::SQ_DATA_FORMAT::FMT_32_FLOAT:\n      return \"FMT_32_FLOAT\";\n   case latte::SQ_DATA_FORMAT::FMT_8_8:\n      return \"FMT_8_8\";\n   case latte::SQ_DATA_FORMAT::FMT_16_16:\n      return \"FMT_16_16\";\n   case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT:\n      return \"FMT_16_16_FLOAT\";\n   case latte::SQ_DATA_FORMAT::FMT_32_32:\n      return \"FMT_32_32\";\n   case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT:\n      return \"FMT_32_32_FLOAT\";\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8:\n      return \"FMT_8_8_8\";\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16:\n      return \"FMT_16_16_16\";\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT:\n      return \"FMT_16_16_16_FLOAT\";\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32:\n      return \"FMT_32_32_32\";\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT:\n      return \"FMT_32_32_32_FLOAT\";\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8_8:\n      return \"FMT_8_8_8_8\";\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16:\n      return \"FMT_16_16_16_16\";\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT:\n      return \"FMT_16_16_16_16_FLOAT\";\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32:\n      return \"FMT_32_32_32_32\";\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT:\n      return \"FMT_32_32_32_32_FLOAT\";\n   case latte::SQ_DATA_FORMAT::FMT_2_10_10_10:\n      return \"FMT_2_10_10_10\";\n   case latte::SQ_DATA_FORMAT::FMT_10_10_10_2:\n      return \"FMT_10_10_10_2\";\n   default:\n      decaf_abort(fmt::format(\"Unimplemented attribute format: {}\", format));\n   }\n}\n\nDataFormatMeta\ngetDataFormatMeta(latte::SQ_DATA_FORMAT format)\n{\n   static const auto DFT_UINT = DataFormatMetaType::UINT;\n   static const auto DFT_FLOAT = DataFormatMetaType::FLOAT;\n   static const auto BADELEM = DataFormatMetaElem { 0, 0, 0 };\n\n   // Note: In order to reduce the likelyhood of encountering an unsupported\n   // 8-bit type in the SPIRV, we intentionally collapse some types...\n\n   switch (format) {\n   case latte::SQ_DATA_FORMAT::FMT_8:\n      return{ 8, 1, DFT_UINT, {{ 0, 0, 8 }, BADELEM, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_4_4:\n      return{ 8, 1, DFT_UINT, {{0, 0, 4}, {0, 4, 4}, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_3_3_2:\n      return{ 8, 1, DFT_UINT, {{0, 0, 3}, {0, 3, 3}, {0, 6, 2}, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_16:\n      return{ 16, 1,DFT_UINT, {{ 0, 0, 16 }, BADELEM, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_16_FLOAT:\n      return{ 16, 1,DFT_FLOAT, {{ 0, 0, 16 }, BADELEM, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_8_8:\n      return{ 16, 1, DFT_UINT, {{ 0, 0, 8 },{ 0, 8, 8 }, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_5_6_5:\n      return{ 16, 1, DFT_UINT, {{0, 0, 5}, {0, 5, 6}, {0, 11, 5}, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_6_5_5:\n      return{ 16, 1, DFT_UINT, {{ 0, 0, 6 },{ 0, 6, 5 },{ 0, 11, 5 }, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_1_5_5_5:\n      return{ 16, 1, DFT_UINT, {{0, 11, 5}, {0, 6, 5}, {0, 1, 5}, {0, 0, 1}  } };\n   case latte::SQ_DATA_FORMAT::FMT_4_4_4_4:\n      return{ 16, 1, DFT_UINT, {{0, 0, 4}, {0, 4, 4}, {0, 8, 4}, {0, 12, 4} } };\n   case latte::SQ_DATA_FORMAT::FMT_5_5_5_1:\n      return{ 16, 1, DFT_UINT, {{0, 0, 5}, {0, 5, 5}, {0, 10, 5}, {0, 15, 1} } };\n   case latte::SQ_DATA_FORMAT::FMT_32:\n      return{ 32, 1,DFT_UINT, {{ 0, 0, 32 }, BADELEM, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_32_FLOAT:\n      return{ 32, 1,DFT_FLOAT, {{ 0, 0, 32 }, BADELEM, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_16_16:\n      return{ 16, 2,DFT_UINT, {{ 0, 0, 16 },{ 1, 0, 16 }, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT:\n      return{ 16, 2,DFT_FLOAT, {{ 0, 0, 16 },{ 1, 0, 16 }, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_8_24:\n      return{ 32, 1, DFT_UINT, {{0, 0, 8}, {0, 8, 24}, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_8_24_FLOAT:\n      return{ 32, 1, DFT_FLOAT, {{ 0, 0, 8 }, { 0, 8, 24 }, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_24_8:\n      return{ 32, 1, DFT_UINT, {{0, 0, 24}, {0, 24, 8}, BADELEM, BADELEM  } };\n   case latte::SQ_DATA_FORMAT::FMT_24_8_FLOAT:\n      return{ 32, 1, DFT_FLOAT, {{0, 0, 24}, {0, 24, 8}, BADELEM, BADELEM } };\n /*case latte::SQ_DATA_FORMAT::FMT_10_11_11:\n      return{ 32, 1, DFT_UINT, {{0, 0, 11}, {0, 11, 11}, {0, 22, 10}, BADELEM } };*/\n   case latte::SQ_DATA_FORMAT::FMT_10_11_11_FLOAT:\n      return{ 32, 1, DFT_FLOAT,{{0, 0, 11}, {0, 11, 11}, {0, 22, 10}, BADELEM } };\n   // This attribute format appears to oddly have the same layout as FMT_10_10_10_2?\n   case latte::SQ_DATA_FORMAT::FMT_2_10_10_10:\n      return{ 32, 1, DFT_UINT, {{0, 0, 10}, {0, 10, 10}, { 0, 20, 10 }, {0, 30, 2} } };\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8_8:\n      return{ 32, 1, DFT_UINT,{{ 0, 0, 8 },{ 0, 8, 8 },{ 0, 16, 8 },{0, 24, 8 } } };\n   case latte::SQ_DATA_FORMAT::FMT_10_10_10_2:\n      return{ 32, 1, DFT_UINT, {{0, 0, 10}, {0, 10, 10}, {0, 20, 10}, {0, 30, 2} } };\n   case latte::SQ_DATA_FORMAT::FMT_X24_8_32_FLOAT:\n      return{ 32, 2, DFT_FLOAT, {{0, 24, 8}, {1, 0, 32}, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_32_32:\n      return{ 32, 2,DFT_UINT,{{ 0, 0, 32 },{ 1, 0, 32 }, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT:\n      return{ 32, 2,DFT_FLOAT,{{ 0, 0, 32 },{ 1, 0, 32 }, BADELEM, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16:\n      return{ 16, 4,DFT_UINT,{{ 0, 0, 16 },{ 1, 0, 16 },{ 2, 0, 16 },{ 3, 0, 16 }  } };\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT:\n      return{ 16, 4,DFT_FLOAT,{{ 0, 0, 16 },{ 1, 0, 16 },{ 2, 0, 16 },{ 3, 0, 16 }  } };\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32:\n      return{ 32, 4,DFT_UINT,{{ 0, 0, 32 },{ 1, 0, 32 },{ 2, 0, 32 },{ 3, 0, 32 } } };\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT:\n      return{ 32, 4,DFT_FLOAT,{{ 0, 0, 32 },{ 1, 0, 32 },{ 2, 0, 32 },{ 3, 0, 32 }  } };\n      //case latte::SQ_DATA_FORMAT::FMT_1:\n      //case latte::SQ_DATA_FORMAT::FMT_GB_GR:\n      //case latte::SQ_DATA_FORMAT::FMT_BG_RG:\n      //case latte::SQ_DATA_FORMAT::FMT_32_AS_8:\n      //case latte::SQ_DATA_FORMAT::FMT_32_AS_8_8:\n      //case latte::SQ_DATA_FORMAT::FMT_5_9_9_9_SHAREDEXP:\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8:\n      return{ 8, 3,DFT_UINT,{{ 0, 0, 8 },{ 1, 0, 8 },{ 2, 0, 8 }, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16:\n      return{ 16, 3,DFT_UINT,{{ 0, 0, 16 },{ 1, 0, 16 },{ 2, 0, 16 }, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT:\n      return{ 16, 3,DFT_FLOAT,{{ 0, 0, 16 },{ 1, 0, 16 },{ 2, 0, 16 }, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32:\n      return{ 32, 3,DFT_UINT,{{ 0, 0, 32 },{ 1, 0, 32 },{ 2, 0, 32 }, BADELEM } };\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT:\n      return{ 32, 3,DFT_FLOAT,{{ 0, 0, 32 },{ 1, 0, 32 },{ 2, 0, 32 }, BADELEM } };\n      //case latte::SQ_DATA_FORMAT::FMT_BC1:\n      //case latte::SQ_DATA_FORMAT::FMT_BC2:\n      //case latte::SQ_DATA_FORMAT::FMT_BC3:\n      //case latte::SQ_DATA_FORMAT::FMT_BC4:\n      //case latte::SQ_DATA_FORMAT::FMT_BC5:\n      //case latte::SQ_DATA_FORMAT::FMT_APC0:\n      //case latte::SQ_DATA_FORMAT::FMT_APC1:\n      //case latte::SQ_DATA_FORMAT::FMT_APC2:\n      //case latte::SQ_DATA_FORMAT::FMT_APC3:\n      //case latte::SQ_DATA_FORMAT::FMT_APC4:\n      //case latte::SQ_DATA_FORMAT::FMT_APC5:\n      //case latte::SQ_DATA_FORMAT::FMT_APC6:\n      //case latte::SQ_DATA_FORMAT::FMT_APC7:\n      //case latte::SQ_DATA_FORMAT::FMT_CTX1:\n   default:\n      decaf_abort(fmt::format(\"Unimplemented attribute format: {}\", format));\n   }\n}\n\nuint32_t\ngetDataFormatComponents(latte::SQ_DATA_FORMAT format)\n{\n   switch (format) {\n   case latte::SQ_DATA_FORMAT::FMT_8:\n   case latte::SQ_DATA_FORMAT::FMT_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_FLOAT:\n      return 1;\n   case latte::SQ_DATA_FORMAT::FMT_8_8:\n   case latte::SQ_DATA_FORMAT::FMT_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT:\n      return 2;\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT:\n      return 3;\n   case latte::SQ_DATA_FORMAT::FMT_2_10_10_10:\n   case latte::SQ_DATA_FORMAT::FMT_10_10_10_2:\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8_8:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT:\n      return 4;\n   default:\n      decaf_abort(fmt::format(\"Unimplemented attribute format: {}\", format));\n   }\n}\n\nuint32_t\ngetDataFormatComponentBits(latte::SQ_DATA_FORMAT format)\n{\n   switch (format) {\n   case latte::SQ_DATA_FORMAT::FMT_8:\n   case latte::SQ_DATA_FORMAT::FMT_8_8:\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8:\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8_8:\n      return 8;\n   case latte::SQ_DATA_FORMAT::FMT_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT:\n      return 16;\n   case latte::SQ_DATA_FORMAT::FMT_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT:\n      return 32;\n   default:\n      decaf_abort(fmt::format(\"Unimplemented attribute format: {}\", format));\n   }\n}\n\nbool\ngetDataFormatIsFloat(latte::SQ_DATA_FORMAT format)\n{\n   switch (format) {\n\n   case latte::SQ_DATA_FORMAT::FMT_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT:\n      return true;\n   case latte::SQ_DATA_FORMAT::FMT_8:\n   case latte::SQ_DATA_FORMAT::FMT_16:\n   case latte::SQ_DATA_FORMAT::FMT_32:\n   case latte::SQ_DATA_FORMAT::FMT_8_8:\n   case latte::SQ_DATA_FORMAT::FMT_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32:\n   case latte::SQ_DATA_FORMAT::FMT_2_10_10_10:\n   case latte::SQ_DATA_FORMAT::FMT_10_10_10_2:\n   case latte::SQ_DATA_FORMAT::FMT_8_8_8_8:\n   case latte::SQ_DATA_FORMAT::FMT_16_16_16_16:\n   case latte::SQ_DATA_FORMAT::FMT_32_32_32_32:\n      return false;\n   default:\n      decaf_abort(fmt::format(\"Unimplemented attribute format: {}\", format));\n   }\n}\n\nuint32_t\ngetTexDimDimensions(latte::SQ_TEX_DIM dim)\n{\n   switch (dim) {\n   case latte::SQ_TEX_DIM::DIM_1D:\n      return 1;\n   case latte::SQ_TEX_DIM::DIM_2D:\n   case latte::SQ_TEX_DIM::DIM_2D_MSAA:\n   case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n      return 2;\n   case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n   case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n   case latte::SQ_TEX_DIM::DIM_CUBEMAP:\n   case latte::SQ_TEX_DIM::DIM_3D:\n      return 3;\n      break;\n   default:\n      decaf_abort(fmt::format(\"Unsupported texture dim: {}\", dim));\n   }\n}\n\nlatte::SQ_TILE_MODE\ngetArrayModeTileMode(latte::BUFFER_ARRAY_MODE mode)\n{\n   // The buffer array modes match up with our SQ_TILE_MODE's perfectly.\n   return static_cast<latte::SQ_TILE_MODE>(mode);\n}\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_instructions.cpp",
    "content": "#include \"latte/latte_instructions.h\"\n\nnamespace latte\n{\n\n#undef ALU_REDUC\n#undef ALU_VEC\n#undef ALU_TRANS\n#undef ALU_PRED_SET\n#undef ALU_INT\n#undef ALU_UINT\n#undef ALU_INT_IN\n#undef ALU_UINT_IN\n#undef ALU_INT_OUT\n#undef ALU_UINT_OUT\n\n#define ALU_REDUC    SQ_ALU_FLAG_REDUCTION\n#define ALU_VEC      SQ_ALU_FLAG_VECTOR\n#define ALU_TRANS    SQ_ALU_FLAG_TRANSCENDENTAL\n#define ALU_PRED_SET SQ_ALU_FLAG_PRED_SET\n\n#define ALU_INT_IN   SQ_ALU_FLAG_INT_IN\n#define ALU_INT_OUT  SQ_ALU_FLAG_INT_OUT\n\n#define ALU_UINT_IN  SQ_ALU_FLAG_UINT_IN\n#define ALU_UINT_OUT SQ_ALU_FLAG_UINT_OUT\n\n#define ALU_INT      SQ_ALU_FLAG_INT_IN  | SQ_ALU_FLAG_INT_OUT\n#define ALU_UINT     SQ_ALU_FLAG_UINT_IN | SQ_ALU_FLAG_UINT_OUT\n\nconst char *getInstructionName(SQ_CF_INST id)\n{\n   switch (id) {\n#define CF_INST(name, value) case SQ_CF_INST_##name: return #name;\n#include \"latte/latte_instructions_def.inl\"\n#undef CF_INST\n   default:\n      return \"UNKNOWN\";\n   }\n}\n\nconst char *getInstructionName(SQ_CF_EXP_INST id)\n{\n   switch (id) {\n#define EXP_INST(name, value) case SQ_CF_INST_##name: return #name;\n#include \"latte/latte_instructions_def.inl\"\n#undef EXP_INST\n   default:\n      return \"UNKNOWN\";\n   }\n}\n\nconst char *getInstructionName(SQ_CF_ALU_INST id)\n{\n   switch (id) {\n#define ALU_INST(name, value) case SQ_CF_INST_##name: return #name;\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_INST\n   default:\n      return \"UNKNOWN\";\n   }\n}\n\nconst char *getInstructionName(SQ_OP2_INST id)\n{\n   switch (id) {\n#define ALU_OP2(name, value, srcs, flags) case SQ_OP2_INST_##name: return #name;\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_OP2\n   default:\n      return \"UNKNOWN\";\n   }\n}\n\nconst char *getInstructionName(SQ_OP3_INST id)\n{\n   switch (id) {\n#define ALU_OP3(name, value, srcs, flags) case SQ_OP3_INST_##name: return #name;\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_OP3\n   default:\n      return \"UNKNOWN\";\n   }\n}\n\nconst char *getInstructionName(SQ_TEX_INST id)\n{\n   switch (id) {\n#define TEX_INST(name, value) case SQ_TEX_INST_##name: return #name;\n#include \"latte/latte_instructions_def.inl\"\n#undef TEX_INST\n   default:\n      return \"UNKNOWN\";\n   }\n}\n\nconst char *getInstructionName(SQ_VTX_INST id)\n{\n   switch (id) {\n#define VTX_INST(name, value) case SQ_VTX_INST_##name: return #name;\n#include \"latte/latte_instructions_def.inl\"\n#undef VTX_INST\n   default:\n      return \"UNKNOWN\";\n   }\n}\n\nuint32_t getInstructionNumSrcs(SQ_OP2_INST id)\n{\n   switch (id) {\n#define ALU_OP2(name, value, srcs, flags) case SQ_OP2_INST_##name: return srcs;\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_OP2\n   default:\n      return 0;\n   }\n}\n\nuint32_t getInstructionNumSrcs(SQ_OP3_INST id)\n{\n   switch (id) {\n#define ALU_OP3(name, value, srcs, flags) case SQ_OP3_INST_##name: return srcs;\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_OP3\n   default:\n      return 0;\n   }\n}\n\nSQ_ALU_FLAGS getInstructionFlags(SQ_OP2_INST id)\n{\n   switch (id) {\n#define ALU_OP2(name, value, srcs, flags) case SQ_OP2_INST_##name: return static_cast<SQ_ALU_FLAGS>(flags);\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_OP2\n   default:\n      return static_cast<SQ_ALU_FLAGS>(0);\n   }\n}\n\nSQ_ALU_FLAGS getInstructionFlags(SQ_OP3_INST id)\n{\n   switch (id) {\n#define ALU_OP3(name, value, srcs, flags) case SQ_OP3_INST_##name: return static_cast<SQ_ALU_FLAGS>(flags);\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_OP3\n   default:\n      return static_cast<SQ_ALU_FLAGS>(0);\n   }\n}\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/latte/latte_shaderparser.h",
    "content": "#pragma once\n#include \"latte/latte_constants.h\"\n#include \"latte/latte_instructions.h\"\n#include \"latte/latte_registers_spi.h\"\n#include \"latte/latte_registers_sq.h\"\n#include \"latte_decoders.h\"\n\n#include <common/decaf_assert.h>\n#include <cstdint>\n#include <gsl/gsl-lite.hpp>\n\nnamespace latte\n{\n\nclass ShaderParser\n{\npublic:\n   enum class Type : uint32_t\n   {\n      Unknown,\n      Fetch,\n      Vertex,\n      Geometry,\n      DataCache,\n      Pixel\n   };\n\n#define TEX_INST(x, ...) \\\n   virtual void translateTex_##x(const ControlFlowInst &cf, const TextureFetchInst &inst) { \\\n      decaf_abort(\"Unimplemented TEX instruction \"#x); \\\n   }\n#include \"latte/latte_instructions_def.inl\"\n#undef TEX_INST\n\n#define VTX_INST(x, ...) \\\n   virtual void translateVtx_##x(const ControlFlowInst &cf, const VertexFetchInst &ist) { \\\n      decaf_abort(\"Unimplemented VTX instruction \"#x); \\\n   }\n#include \"latte/latte_instructions_def.inl\"\n#undef VTX_INST\n\n#define ALU_OP2(x, ...) \\\n   virtual void translateAluOp2_##x(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { \\\n      decaf_abort(\"Unimplemented ALU OP2 instruction \"#x); \\\n   }\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_OP2\n\n#define ALU_OP3(x, ...) \\\n   virtual void translateAluOp3_##x(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) { \\\n      decaf_abort(\"Unimplemented ALU OP3 instruction \"#x); \\\n   }\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_OP3\n\n#define CF_INST(x, ...) \\\n   virtual void translateCf_##x(const ControlFlowInst &cf) { \\\n      decaf_abort(\"Unimplemented CF NORMAL instruction \"#x); \\\n   }\n#include \"latte/latte_instructions_def.inl\"\n#undef CF_INST\n\n#define EXP_INST(x, ...) \\\n   virtual void translateCf_##x(const ControlFlowInst &cf) { \\\n      decaf_abort(\"Unimplemented CF EXPORT instruction \"#x); \\\n   }\n#include \"latte/latte_instructions_def.inl\"\n#undef EXP_INST\n\n#define ALU_INST(x, ...) \\\n   virtual void translateCf_##x(const ControlFlowInst &cf) { \\\n      decaf_abort(\"Unimplemented CF ALU instruction \"#x); \\\n   }\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_INST\n\n   virtual void translateTexInst(const ControlFlowInst &cf, const TextureFetchInst &inst)\n   {\n      switch (inst.word0.TEX_INST()) {\n#define TEX_INST(x, ...) \\\n      case latte::SQ_TEX_INST_##x: \\\n         translateTex_##x(cf, inst); \\\n         break;\n#include \"latte/latte_instructions_def.inl\"\n#undef TEX_INST\n\n      default:\n         decaf_abort(\"Unexpected TEX instruction\");\n      }\n   }\n\n   virtual void translateVtxInst(const ControlFlowInst &cf, const VertexFetchInst &inst)\n   {\n      switch (inst.word0.VTX_INST()) {\n#define VTX_INST(x, ...) \\\n      case latte::SQ_VTX_INST_##x: \\\n         translateVtx_##x(cf, inst); \\\n         break;\n#include \"latte/latte_instructions_def.inl\"\n#undef VTX_INST\n\n      default:\n         decaf_abort(\"Unexpected VTX instruction\");\n      }\n   }\n\n   virtual void translateAluInst(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n   {\n      const AluInst *instX = &inst;\n\n      bool isReducInst = (unit == 0xffffffff);\n      if (isReducInst) {\n         instX = group.units[SQ_CHAN::X];\n      }\n\n      if (instX->word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n         switch (instX->op2.ALU_INST()) {\n#define ALU_OP2(x, ...) \\\n      case latte::SQ_OP2_INST_##x: \\\n         translateAluOp2_##x(cf, group, unit, inst); \\\n         break;\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_OP2\n\n         default:\n            decaf_abort(\"Unexpected ALU OP2 instruction\");\n         }\n      } else {\n         switch (instX->op3.ALU_INST()) {\n#define ALU_OP3(x, ...) \\\n      case latte::SQ_OP3_INST_##x: \\\n         translateAluOp3_##x(cf, group, unit, inst); \\\n         break;\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_OP3\n\n         default:\n            decaf_abort(\"Unexpected ALU OP3 instruction\");\n         }\n      }\n   }\n\n   virtual void translateTexClause(const ControlFlowInst &cf)\n   {\n      auto addr = cf.word0.ADDR();\n      auto count = (cf.word1.COUNT() | (cf.word1.COUNT_3() << 3)) + 1;\n\n      auto texInstData = reinterpret_cast<const TextureFetchInst *>(mBinary.data() + 8 * addr);\n      auto texInsts = gsl::make_span(texInstData, count);\n\n      for (auto i = 0; i < texInsts.size(); ++i) {\n         translateTexInst(cf, texInsts[i]);\n\n         mTexVtxPC++;\n      }\n   }\n\n   virtual void translateVtxClause(const ControlFlowInst &cf)\n   {\n      auto addr = cf.word0.ADDR();\n      auto count = (cf.word1.COUNT() | (cf.word1.COUNT_3() << 3)) + 1;\n\n      auto vtxInstData = reinterpret_cast<const VertexFetchInst *>(mBinary.data() + 8 * addr);\n      auto vtxInsts = gsl::make_span(vtxInstData, count);\n\n      for (auto i = 0; i < vtxInsts.size(); ++i) {\n         translateVtxInst(cf, vtxInsts[i]);\n\n         mTexVtxPC++;\n      }\n   }\n\n   virtual void translateAluGroup(const ControlFlowInst &cf, const AluInstructionGroup &group)\n   {\n      auto aluUnitIdx = 0;\n\n      if (isReductionInst(*group.units[0])) {\n         // For sanity, let's ensure every instruction in this reduction group\n         // has the same instruction id and the same CLAMP + OMOD values\n         for (auto i = 1u; i < 4; ++i) {\n            if (group.units[0]->word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n               if (group.units[i]->op2.ALU_INST() != group.units[0]->op2.ALU_INST()) {\n                  decaf_abort(\"Expected every instruction in reduction group to be the same.\");\n               }\n\n               if (group.units[i]->op2.OMOD() != group.units[0]->op2.OMOD()) {\n                  decaf_abort(\"Expected every instruction in reduction group to have the same output modifier.\");\n               }\n            } else {\n               if (group.units[i]->op3.ALU_INST() != group.units[0]->op3.ALU_INST()) {\n                  decaf_abort(\"Expected every instruction in reduction group to be the same.\");\n               }\n            }\n\n            if (group.units[i]->word1.CLAMP() != group.units[0]->word1.CLAMP()) {\n               decaf_abort(\"Expected every instruction in reduction group to have the same clamp value.\");\n            }\n         }\n\n         // Translate the reduction by doing executing the ALU instruction only\n         //  once for the whole reduction.  We pass an invalid instruction to try\n         //  and avoid having people accidentally use it...\n         static const uint32_t InvalidInstData[] = { 0xffffffff, 0xffffffff };\n         static const auto InvalidInstPtr = reinterpret_cast<const AluInst *>(InvalidInstData);\n         translateAluInst(cf, group, static_cast<SQ_CHAN>(0xffffffff), *InvalidInstPtr);\n\n         // Skip the 4 units we just processed as a reduction instruction\n         aluUnitIdx += 4;\n      }\n\n      for (; aluUnitIdx < 5; ++aluUnitIdx) {\n         auto &inst = *group.units[aluUnitIdx];\n\n         // Should never encounter reduction instructions during normal ALU\n         //  instruction processing after the above logic.\n         decaf_check(!isReductionInst(inst));\n\n         // Translate the ALU instruction\n         translateAluInst(cf, group, static_cast<SQ_CHAN>(aluUnitIdx), inst);\n\n         if (inst.word0.LAST()) {\n            break;\n         }\n      }\n   }\n\n   virtual void translateAluClause(const ControlFlowInst &cf)\n   {\n      auto addr = cf.alu.word0.ADDR();\n      auto count = cf.alu.word1.COUNT() + 1;\n\n      auto aluInstData = reinterpret_cast<const AluInst *>(mBinary.data() + 8 * addr);\n      auto aluInsts = gsl::make_span(aluInstData, count);\n\n      AluClauseParser clauseParser(aluInsts, mAluInstPreferVector);\n      while (!clauseParser.isEndOfClause()) {\n         auto group = clauseParser.readOneGroup();\n\n         translateAluGroup(cf, group);\n\n         mGroupPC++;\n      }\n   }\n\n   virtual void translateCfNormalInst(const ControlFlowInst &cf)\n   {\n      switch (cf.word1.CF_INST()) {\n#define CF_INST(x, ...) \\\n      case latte::SQ_CF_INST_##x: \\\n         translateCf_##x(cf); \\\n         break;\n#include \"latte/latte_instructions_def.inl\"\n#undef CF_INST\n\n      default:\n         decaf_abort(\"Unexpected CF NORMAL instruction id\");\n      }\n\n      // We need special handling for a couple of the instruction types\n      //  as they affect the control flow of the parser.\n      switch (cf.word1.CF_INST()) {\n      case SQ_CF_INST_RETURN:\n         // RETURN inside a non-function block doesn't make any sense\n         decaf_check(mIsFunction);\n\n         // Mark EOP as having been reached\n         mReachedEop = true;\n      }\n\n      // Handle potential END_OF_PROGRAM bit being set\n      if (cf.word1.END_OF_PROGRAM()) {\n         mReachedEop = true;\n      }\n   }\n\n   virtual void translateCfExportInst(const ControlFlowInst& cf)\n   {\n      switch (cf.exp.word1.CF_INST()) {\n#define EXP_INST(x, ...) \\\n      case latte::SQ_CF_INST_##x: \\\n         translateCf_##x(cf); \\\n         break;\n#include \"latte/latte_instructions_def.inl\"\n#undef EXP_INST\n\n      default:\n         decaf_abort(\"Unexpected CF EXPORT instruction id\");\n      }\n\n      // Handle potential END_OF_PROGRAM bit being set\n      if (cf.word1.END_OF_PROGRAM()) {\n         mReachedEop = true;\n      }\n   }\n\n   virtual void translateCfAluInst(const ControlFlowInst &cf)\n   {\n      switch (cf.alu.word1.CF_INST()) {\n#define ALU_INST(x, ...) \\\n      case latte::SQ_CF_INST_##x: \\\n         translateCf_##x(cf); \\\n         break;\n#include \"latte/latte_instructions_def.inl\"\n#undef ALU_INST\n\n      default:\n         decaf_abort(\"Unexpected CF ALU instruction id\");\n      }\n   }\n\n   virtual void translateCfInst(const ControlFlowInst& cf)\n   {\n      switch (cf.word1.CF_INST_TYPE()) {\n      case SQ_CF_INST_TYPE_NORMAL:\n         translateCfNormalInst(cf);\n         break;\n      case SQ_CF_INST_TYPE_EXPORT:\n         translateCfExportInst(cf);\n         break;\n      case SQ_CF_INST_TYPE_ALU:\n      case SQ_CF_INST_TYPE_ALU_EXTENDED:\n         translateCfAluInst(cf);\n         break;\n      default:\n         decaf_abort(\"Unexpected CF instruction type\");\n      }\n   }\n\n   virtual void translate()\n   {\n      decaf_check(mType != Type::Unknown);\n      decaf_check(!mBinary.empty());\n\n      if (mType == Type::Fetch) {\n         mIsFunction = true;\n      }\n\n      for (auto i = 0; i < mBinary.size() && !mReachedEop; i += sizeof(ControlFlowInst)) {\n         auto cf = *reinterpret_cast<const ControlFlowInst *>(mBinary.data() + i);\n         translateCfInst(cf);\n\n         mCfPC++;\n      }\n   }\n\n   virtual void reset()\n   {\n      mIsFunction = false;\n      mReachedEop = false;\n      mCfPC = 0;\n      mGroupPC = 0;\n      mTexVtxPC = 0;\n   }\n\nprotected:\n   // Input\n   Type mType = Type::Unknown;\n   gsl::span<const uint8_t> mBinary;\n   bool mAluInstPreferVector;\n\n   // Temporaries\n   bool mIsFunction = false;\n   bool mReachedEop = false;\n   uint32_t mCfPC = 0;\n   uint32_t mGroupPC = 0;\n   uint32_t mTexVtxPC = 0;\n\n};\n\n} // namespace latte\n"
  },
  {
    "path": "src/libgpu/src/null/null_driver.cpp",
    "content": "#include \"null_driver.h\"\n#include \"gpu_event.h\"\n#include \"gpu_ringbuffer.h\"\n\n#include <common/decaf_assert.h>\n\nnamespace null\n{\n\nvoid\nDriver::setWindowSystemInfo(const gpu::WindowSystemInfo &wsi)\n{\n}\n\nvoid\nDriver::windowHandleChanged(void *handle)\n{\n}\n\nvoid\nDriver::windowSizeChanged(int width, int height)\n{\n}\n\nvoid\nDriver::run()\n{\n   mRunning = true;\n\n   while (mRunning) {\n      if (gpu::ringbuffer::wait()) {\n         auto items = gpu::ringbuffer::read();\n         // TODO: We need to actually process pm4 to do EVENT_WRITE_EOP for retired timestamps\n      }\n   }\n}\n\nvoid\nDriver::runUntilFlip()\n{\n   decaf_abort(\"NullDriver::runUntilFlip unimplemented\");\n}\n\nvoid\nDriver::stop()\n{\n   mRunning = false;\n   gpu::ringbuffer::wake();\n}\n\ngpu::GraphicsDriverType\nDriver::type()\n{\n   return gpu::GraphicsDriverType::Null;\n}\n\ngpu::GraphicsDriverDebugInfo *\nDriver::getDebugInfo()\n{\n   return nullptr;\n}\n\nvoid\nDriver::notifyCpuFlush(phys_addr address,\n                       uint32_t size)\n{\n}\n\nvoid\nDriver::notifyGpuFlush(phys_addr address,\n                       uint32_t size)\n{\n}\n\n} // namespace null\n"
  },
  {
    "path": "src/libgpu/src/null/null_driver.h",
    "content": "#pragma once\n#include \"gpu_graphicsdriver.h\"\n\nnamespace null\n{\n\nclass Driver : public gpu::GraphicsDriver\n{\npublic:\n   virtual ~Driver() = default;\n\n   virtual void setWindowSystemInfo(const gpu::WindowSystemInfo &wsi) override;\n   virtual void windowHandleChanged(void *handle) override;\n   virtual void windowSizeChanged(int width, int height) override;\n\n   virtual void run() override;\n   virtual void runUntilFlip() override;\n   virtual void stop() override;\n\n   virtual gpu::GraphicsDriverType type() override;\n   virtual gpu::GraphicsDriverDebugInfo *getDebugInfo() override;\n\n   virtual void notifyCpuFlush(phys_addr address, uint32_t size) override;\n   virtual void notifyGpuFlush(phys_addr address, uint32_t size) override;\n\nprivate:\n   bool mRunning = false;\n};\n\n} // namespace null\n"
  },
  {
    "path": "src/libgpu/src/pm4_processor.cpp",
    "content": "#include \"latte/latte_pm4_reader.h\"\n#include \"gpu_memory.h\"\n#include \"pm4_processor.h\"\n\n#include <common/byte_swap_array.h>\n#include <common/log.h>\n#include <libcpu/mmu.h>\n\nvoid\nPm4Processor::indirectBufferCall(const IndirectBufferCall &data)\n{\n   auto buffer = gpu::internal::translateAddress<uint32_t>(data.addr);\n   runCommandBuffer({ buffer, data.size });\n}\n\nvoid\nPm4Processor::indirectBufferCallPriv(const IndirectBufferCallPriv &data)\n{\n   auto buffer = gpu::internal::translateAddress<uint32_t>(data.addr);\n   runCommandBuffer({ buffer, data.size });\n}\n\nvoid\nPm4Processor::runCommandBuffer(const gpu::ringbuffer::Buffer &buffer)\n{\n   decaf_check(mSwapScratchDepth + 1 < MaxPm4IndirectDepth);\n   auto& scratchBuffer = mSwapScratch[mSwapScratchDepth++];\n\n   auto numDwords = buffer.size();\n   auto swappedBytes = byte_swap_to_scratch<uint32_t>(\n      buffer.data(), static_cast<uint32_t>(buffer.size_bytes()), scratchBuffer);\n   auto swapped = reinterpret_cast<uint32_t*>(swappedBytes);\n\n   for (auto pos = 0u; pos < numDwords; ) {\n      auto header = *reinterpret_cast<Header *>(&swapped[pos]);\n      auto size = 0u;\n\n      if (swapped[pos] == 0) {\n         break;\n      }\n\n      switch (header.type()) {\n      case PacketType::Type3:\n      {\n         auto header3 = HeaderType3::get(header.value);\n         size = header3.size() + 1;\n\n         decaf_check(pos + size <= numDwords);\n         handlePacketType3(header3, gsl::make_span(&swapped[pos + 1], size));\n         break;\n      }\n      case PacketType::Type0:\n      {\n         auto header0 = HeaderType0::get(header.value);\n         size = header0.count() + 1;\n\n         decaf_check(pos + size <= numDwords);\n         handlePacketType0(header0, gsl::make_span(&swapped[pos + 1], size));\n         break;\n      }\n      case PacketType::Type2:\n      {\n         // Filler packet, ignore\n         break;\n      }\n      case PacketType::Type1:\n      default:\n         gLog->error(\"Invalid packet header type {}, header = 0x{:08X}\",\n                     header.type(), header.value);\n         pos = static_cast<uint32_t>(numDwords);\n         break;\n      }\n\n      pos += size + 1;\n   }\n\n   mSwapScratchDepth--;\n}\n\nvoid\nPm4Processor::handlePacketType0(HeaderType0 header, const gsl::span<uint32_t> &data)\n{\n   auto base = static_cast<latte::Register>(header.baseIndex() * 4);\n   setRegisters(base, data);\n}\n\nvoid\nPm4Processor::handlePacketType3(HeaderType3 header, const gsl::span<uint32_t> &data)\n{\n   PacketReader reader{ data };\n\n   switch (header.opcode()) {\n   case IT_OPCODE::DECAF_COPY_COLOR_TO_SCAN:\n      decafCopyColorToScan(read<DecafCopyColorToScan>(reader));\n      break;\n   case IT_OPCODE::DECAF_SWAP_BUFFERS:\n      decafSwapBuffers(read<DecafSwapBuffers>(reader));\n      break;\n   case IT_OPCODE::DECAF_CLEAR_COLOR:\n      decafClearColor(read<DecafClearColor>(reader));\n      break;\n   case IT_OPCODE::DECAF_CLEAR_DEPTH_STENCIL:\n      decafClearDepthStencil(read<DecafClearDepthStencil>(reader));\n      break;\n   case IT_OPCODE::DECAF_SET_BUFFER:\n      decafSetBuffer(read<DecafSetBuffer>(reader));\n      break;\n   case IT_OPCODE::DECAF_OSSCREEN_FLIP:\n      decafOSScreenFlip(read<DecafOSScreenFlip>(reader));\n      break;\n   case IT_OPCODE::DECAF_COPY_SURFACE:\n      decafCopySurface(read<DecafCopySurface>(reader));\n      break;\n   case IT_OPCODE::DECAF_EXPAND_COLORBUFFER:\n      decafExpandColorBuffer(read<DecafExpandColorBuffer>(reader));\n      break;\n   case IT_OPCODE::DRAW_INDEX_AUTO:\n      drawIndexAuto(read<DrawIndexAuto>(reader));\n      break;\n   case IT_OPCODE::DRAW_INDEX_2:\n      drawIndex2(read<DrawIndex2>(reader));\n      break;\n   case IT_OPCODE::DRAW_INDEX_IMMD:\n      drawIndexImmd(read<DrawIndexImmd>(reader));\n      break;\n   case IT_OPCODE::INDEX_TYPE:\n      indexType(read<IndexType>(reader));\n      break;\n   case IT_OPCODE::NUM_INSTANCES:\n      numInstances(read<NumInstances>(reader));\n      break;\n   case IT_OPCODE::SET_ALU_CONST:\n      setAluConsts(read<SetAluConsts>(reader));\n      break;\n   case IT_OPCODE::SET_CONFIG_REG:\n      setConfigRegs(read<SetConfigRegs>(reader));\n      break;\n   case IT_OPCODE::SET_CONTEXT_REG:\n      setContextRegs(read<SetContextRegs>(reader));\n      break;\n   case IT_OPCODE::SET_CTL_CONST:\n      setControlConstants(read<SetControlConstants>(reader));\n      break;\n   case IT_OPCODE::SET_LOOP_CONST:\n      setLoopConsts(read<SetLoopConsts>(reader));\n      break;\n   case IT_OPCODE::SET_SAMPLER:\n      setSamplers(read<SetSamplers>(reader));\n      break;\n   case IT_OPCODE::SET_RESOURCE:\n      setResources(read<SetResources>(reader));\n      break;\n   case IT_OPCODE::LOAD_CONFIG_REG:\n      loadConfigRegs(read<LoadConfigReg>(reader));\n      break;\n   case IT_OPCODE::LOAD_CONTEXT_REG:\n      loadContextRegs(read<LoadContextReg>(reader));\n      break;\n   case IT_OPCODE::LOAD_ALU_CONST:\n      loadAluConsts(read<LoadAluConst>(reader));\n      break;\n   case IT_OPCODE::LOAD_BOOL_CONST:\n      loadBoolConsts(read<LoadBoolConst>(reader));\n      break;\n   case IT_OPCODE::LOAD_LOOP_CONST:\n      loadLoopConsts(read<LoadLoopConst>(reader));\n      break;\n   case IT_OPCODE::LOAD_RESOURCE:\n      loadResources(read<latte::pm4::LoadResource>(reader));\n      break;\n   case IT_OPCODE::LOAD_SAMPLER:\n      loadSamplers(read<LoadSampler>(reader));\n      break;\n   case IT_OPCODE::LOAD_CTL_CONST:\n      loadControlConstants(read<LoadControlConst>(reader));\n      break;\n   case IT_OPCODE::INDIRECT_BUFFER:\n      indirectBufferCall(read<IndirectBufferCall>(reader));\n      break;\n   case IT_OPCODE::INDIRECT_BUFFER_PRIV:\n      indirectBufferCallPriv(read<IndirectBufferCallPriv>(reader));\n      break;\n   case IT_OPCODE::WAIT_REG_MEM:\n      waitMem(read<WaitMem>(reader));\n      break;\n   case IT_OPCODE::MEM_WRITE:\n      memWrite(read<MemWrite>(reader));\n      break;\n   case IT_OPCODE::EVENT_WRITE:\n      eventWrite(read<EventWrite>(reader));\n      break;\n   case IT_OPCODE::EVENT_WRITE_EOP:\n      eventWriteEOP(read<EventWriteEOP>(reader));\n      break;\n   case IT_OPCODE::PFP_SYNC_ME:\n      pfpSyncMe(read<PfpSyncMe>(reader));\n      break;\n   case IT_OPCODE::SET_PREDICATION:\n      setPredication(read<SetPredication>(reader));\n      break;\n   case IT_OPCODE::STRMOUT_BASE_UPDATE:\n      streamOutBaseUpdate(read<StreamOutBaseUpdate>(reader));\n      break;\n   case IT_OPCODE::STRMOUT_BUFFER_UPDATE:\n      streamOutBufferUpdate(read<StreamOutBufferUpdate>(reader));\n      break;\n   case IT_OPCODE::NOP:\n      nopPacket(read<Nop>(reader));\n      break;\n   case IT_OPCODE::SURFACE_SYNC:\n      surfaceSync(read<SurfaceSync>(reader));\n      break;\n   case IT_OPCODE::CONTEXT_CTL:\n      contextControl(read<ContextControl>(reader));\n      break;\n   case IT_OPCODE::COPY_DW:\n      copyDw(read<CopyDw>(reader));\n      break;\n   default:\n      gLog->debug(\"Unhandled pm4 packet type 3 opcode {}\", header.opcode());\n   }\n}\n\nvoid Pm4Processor::nopPacket(const Nop &data)\n{\n   auto str = std::string{};\n\n   if (data.strWords.size()) {\n      for (auto i = 0u; i < data.strWords.size(); ++i) {\n         auto word = data.strWords[i];\n\n         for (auto c = 0u; c < 4; ++c) {\n            auto chr = static_cast<char>((word >> (c * 8)) & 0xFF);\n\n            if (!chr) {\n               break;\n            }\n\n            str.push_back(chr);\n         }\n      }\n   }\n\n   if (false) {\n      gLog->debug(\"NOP unk: {} str: {}\", data.unk, str);\n   }\n}\n\nvoid\nPm4Processor::indexType(const IndexType &data)\n{\n   mRegisters[latte::Register::VGT_INDEX_TYPE / 4] = data.type.value;\n   mRegisters[latte::Register::VGT_DMA_INDEX_TYPE / 4] = data.type.value;\n}\n\nvoid\nPm4Processor::numInstances(const NumInstances &data)\n{\n   mRegisters[latte::Register::VGT_DMA_NUM_INSTANCES / 4] = data.count;\n}\n\nvoid Pm4Processor::contextControl(const ContextControl &data)\n{\n   mShadowState.LOAD_CONTROL = data.LOAD_CONTROL;\n   mShadowState.SHADOW_ENABLE = data.SHADOW_ENABLE;\n}\n\nvoid Pm4Processor::copyDw(const CopyDw &data)\n{\n   decaf_check(data.select.SRC() == latte::pm4::COPY_DW_SEL_MEMORY);\n   decaf_check(data.select.DST() == latte::pm4::COPY_DW_SEL_REGISTER);\n   decaf_check(data.dstLo == latte::Register::VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE);\n\n   mRegAddr_VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE = data.srcLo;\n}\n\nvoid Pm4Processor::shadowWrite(phys_ptr<uint32_t> memory,\n                                   const gsl::span<uint32_t> &registers)\n{\n   // Shadow memory is not byte-swapped\n   memcpy(memory.getRawPointer(), registers.data(), registers.size_bytes());\n}\n\nvoid\nPm4Processor::setRegisters(latte::Register base,\n                           const gsl::span<uint32_t> &values)\n{\n   if (latte::Register::SQ_VTX_SEMANTIC_CLEAR >= base &&\n         latte::Register::SQ_VTX_SEMANTIC_CLEAR < base + values.size_bytes()) {\n      auto clearRegBase = latte::Register::SQ_VTX_SEMANTIC_0 / 4;\n      auto valueIdx = (latte::Register::SQ_VTX_SEMANTIC_CLEAR - base) / 4;\n      auto clearFlags = values[valueIdx];\n      for (auto i = 0u; i < 32; ++i) {\n         if (clearFlags & (1 << i)) {\n            mRegisters[clearRegBase + i] = 0xffffffff;\n         }\n      }\n   }\n\n   memcpy(&mRegisters[base / 4], values.data(), values.size_bytes());\n}\n\nvoid Pm4Processor::setAluConsts(const SetAluConsts &data)\n{\n   decaf_check(data.id >= latte::Register::AluConstRegisterBase);\n   decaf_check(data.id < latte::Register::AluConstRegisterEnd);\n\n   if (mShadowState.SHADOW_ENABLE.ENABLE_ALU_CONST() && mShadowState.ALU_CONST_BASE) {\n      auto offset = (data.id - latte::Register::AluConstRegisterBase) / 4;\n      shadowWrite(mShadowState.ALU_CONST_BASE + offset, data.values);\n   }\n\n   setRegisters(data.id, data.values);\n}\n\nvoid Pm4Processor::setConfigRegs(const SetConfigRegs &data)\n{\n   decaf_check(data.id >= latte::Register::ConfigRegisterBase);\n   decaf_check(data.id < latte::Register::ConfigRegisterEnd);\n\n   if (mShadowState.SHADOW_ENABLE.ENABLE_CONFIG_REG() && mShadowState.CONFIG_REG_BASE) {\n      auto offset = (data.id - latte::Register::ConfigRegisterBase) / 4;\n      shadowWrite(mShadowState.CONFIG_REG_BASE + offset, data.values);\n   }\n\n   setRegisters(data.id, data.values);\n}\n\nvoid Pm4Processor::setContextRegs(const SetContextRegs &data)\n{\n   decaf_check(data.id >= latte::Register::ContextRegisterBase);\n   decaf_check(data.id < latte::Register::ContextRegisterEnd);\n\n   if (mShadowState.SHADOW_ENABLE.ENABLE_CONTEXT_REG() && mShadowState.CONTEXT_REG_BASE) {\n      auto offset = (data.id - latte::Register::ContextRegisterBase) / 4;\n      shadowWrite(mShadowState.CONTEXT_REG_BASE + offset, data.values);\n   }\n\n   setRegisters(data.id, data.values);\n}\n\nvoid Pm4Processor::setControlConstants(const SetControlConstants &data)\n{\n   decaf_check(data.id >= latte::Register::ControlRegisterBase);\n   decaf_check(data.id < latte::Register::ControlRegisterEnd);\n\n   if (mShadowState.SHADOW_ENABLE.ENABLE_CTL_CONST() && mShadowState.CTL_CONST_BASE) {\n      auto offset = (data.id - latte::Register::ControlRegisterBase) / 4;\n      shadowWrite(mShadowState.CTL_CONST_BASE + offset, data.values);\n   }\n\n   setRegisters(data.id, data.values);\n}\n\nvoid Pm4Processor::setLoopConsts(const SetLoopConsts &data)\n{\n   decaf_check(data.id >= latte::Register::LoopConstRegisterBase);\n   decaf_check(data.id < latte::Register::LoopConstRegisterEnd);\n\n   if (mShadowState.SHADOW_ENABLE.ENABLE_LOOP_CONST() && mShadowState.LOOP_CONST_BASE) {\n      auto offset = (data.id - latte::Register::LoopConstRegisterBase) / 4;\n      shadowWrite(mShadowState.LOOP_CONST_BASE + offset, data.values);\n   }\n\n   setRegisters(data.id, data.values);\n}\n\nvoid Pm4Processor::setSamplers(const SetSamplers &data)\n{\n   decaf_check(data.id >= latte::Register::SamplerRegisterBase);\n   decaf_check(data.id < latte::Register::SamplerRegisterEnd);\n\n   if (mShadowState.SHADOW_ENABLE.ENABLE_SAMPLER() && mShadowState.SAMPLER_CONST_BASE) {\n      auto offset = (data.id - latte::Register::SamplerRegisterBase) / 4;\n      shadowWrite(mShadowState.SAMPLER_CONST_BASE + offset, data.values);\n   }\n\n   setRegisters(data.id, data.values);\n}\n\nvoid Pm4Processor::setResources(const SetResources &data)\n{\n   if (mShadowState.SHADOW_ENABLE.ENABLE_RESOURCE() && mShadowState.RESOURCE_CONST_BASE) {\n      shadowWrite(mShadowState.RESOURCE_CONST_BASE + data.id, data.values);\n   }\n\n   auto id = static_cast<latte::Register>(latte::Register::ResourceRegisterBase + (4 * data.id));\n   setRegisters(id, data.values);\n}\n\nvoid Pm4Processor::loadRegisters(latte::Register base,\n   phys_addr address,\n   const gsl::span<std::pair<uint32_t, uint32_t>> &registers)\n{\n   auto src = phys_cast<uint32_t *>(address);\n   for (auto &range : registers) {\n      auto start = range.first;\n      auto count = range.second;\n\n      // We can directly call setRegisters since there is no byte-swapping for shadow.\n      setRegisters(static_cast<latte::Register>(base + start * 4),\n                   gsl::make_span(src.getRawPointer() + start, count));\n   }\n}\n\nvoid Pm4Processor::loadAluConsts(const LoadAluConst &data)\n{\n   if (mShadowState.LOAD_CONTROL.ENABLE_ALU_CONST()) {\n      mShadowState.ALU_CONST_BASE = phys_cast<uint32_t *>(data.addr);\n      loadRegisters(latte::Register::AluConstRegisterBase, data.addr, data.values);\n   }\n}\n\nvoid Pm4Processor::loadBoolConsts(const LoadBoolConst &data)\n{\n   if (mShadowState.LOAD_CONTROL.ENABLE_BOOL_CONST()) {\n      mShadowState.BOOL_CONST_BASE = phys_cast<uint32_t *>(data.addr);\n      loadRegisters(latte::Register::BoolConstRegisterBase, data.addr, data.values);\n   }\n}\n\nvoid Pm4Processor::loadConfigRegs(const LoadConfigReg &data)\n{\n   if (mShadowState.LOAD_CONTROL.ENABLE_CONFIG_REG()) {\n      mShadowState.CONFIG_REG_BASE = phys_cast<uint32_t *>(data.addr);\n      loadRegisters(latte::Register::ConfigRegisterBase, data.addr, data.values);\n   }\n}\n\nvoid Pm4Processor::loadContextRegs(const LoadContextReg &data)\n{\n   if (mShadowState.LOAD_CONTROL.ENABLE_CONTEXT_REG()) {\n      mShadowState.CONTEXT_REG_BASE = phys_cast<uint32_t *>(data.addr);\n      loadRegisters(latte::Register::ContextRegisterBase, data.addr, data.values);\n   }\n}\n\nvoid Pm4Processor::loadControlConstants(const LoadControlConst &data)\n{\n   if (mShadowState.LOAD_CONTROL.ENABLE_CTL_CONST()) {\n      mShadowState.CTL_CONST_BASE = phys_cast<uint32_t *>(data.addr);\n      loadRegisters(latte::Register::ControlRegisterBase, data.addr, data.values);\n   }\n}\n\nvoid Pm4Processor::loadLoopConsts(const LoadLoopConst &data)\n{\n   if (mShadowState.LOAD_CONTROL.ENABLE_LOOP_CONST()) {\n      mShadowState.LOOP_CONST_BASE = phys_cast<uint32_t *>(data.addr);\n      loadRegisters(latte::Register::LoopConstRegisterBase, data.addr, data.values);\n   }\n}\n\nvoid Pm4Processor::loadSamplers(const LoadSampler &data)\n{\n   if (mShadowState.LOAD_CONTROL.ENABLE_SAMPLER()) {\n      mShadowState.SAMPLER_CONST_BASE = phys_cast<uint32_t *>(data.addr);\n      loadRegisters(latte::Register::SamplerRegisterBase, data.addr, data.values);\n   }\n}\n\nvoid Pm4Processor::loadResources(const latte::pm4::LoadResource &data)\n{\n   if (mShadowState.LOAD_CONTROL.ENABLE_RESOURCE()) {\n      mShadowState.RESOURCE_CONST_BASE = phys_cast<uint32_t *>(data.addr);\n      loadRegisters(latte::Register::ResourceRegisterBase, data.addr, data.values);\n   }\n}\n"
  },
  {
    "path": "src/libgpu/src/pm4_processor.h",
    "content": "#pragma once\n#include \"latte/latte_pm4_commands.h\"\n#include \"gpu_ringbuffer.h\"\n\n#include <array>\n#include <common/byte_swap_array.h>\n#include <libcpu/pointer.h>\n#include <vector>\n\nusing namespace latte::pm4;\n\nconstexpr int MaxPm4IndirectDepth = 6;\n\nclass Pm4Processor\n{\nprotected:\n   Pm4Processor()\n   {\n   }\n\n   virtual void decafSetBuffer(const DecafSetBuffer &data) = 0;\n   virtual void decafCopyColorToScan(const DecafCopyColorToScan &data) = 0;\n   virtual void decafSwapBuffers(const DecafSwapBuffers &data) = 0;\n   virtual void decafClearColor(const DecafClearColor &data) = 0;\n   virtual void decafClearDepthStencil(const DecafClearDepthStencil &data) = 0;\n   virtual void decafOSScreenFlip(const DecafOSScreenFlip &data) = 0;\n   virtual void decafCopySurface(const DecafCopySurface &data) = 0;\n   virtual void decafExpandColorBuffer(const DecafExpandColorBuffer &data) = 0;\n   virtual void drawIndexAuto(const DrawIndexAuto &data) = 0;\n   virtual void drawIndex2(const DrawIndex2 &data) = 0;\n   virtual void drawIndexImmd(const DrawIndexImmd &data) = 0;\n   virtual void waitMem(const WaitMem &data) = 0;\n   virtual void memWrite(const MemWrite &data) = 0;\n   virtual void eventWrite(const EventWrite &data) = 0;\n   virtual void eventWriteEOP(const EventWriteEOP &data) = 0;\n   virtual void pfpSyncMe(const PfpSyncMe &data) = 0;\n   virtual void setPredication(const SetPredication &data) = 0;\n   virtual void streamOutBaseUpdate(const StreamOutBaseUpdate &data) = 0;\n   virtual void streamOutBufferUpdate(const StreamOutBufferUpdate &data) = 0;\n   virtual void surfaceSync(const SurfaceSync &data) = 0;\n\n   void handlePacketType0(HeaderType0 header, const gsl::span<uint32_t> &data);\n   void handlePacketType3(HeaderType3 header, const gsl::span<uint32_t> &data);\n   void nopPacket(const Nop &data);\n   void indirectBufferCall(const IndirectBufferCall &data);\n   void indirectBufferCallPriv(const IndirectBufferCallPriv &data);\n   void indexType(const IndexType &data);\n   void numInstances(const NumInstances &data);\n   void contextControl(const ContextControl &data);\n   void copyDw(const CopyDw &data);\n\n   void setAluConsts(const SetAluConsts &data);\n   void setConfigRegs(const SetConfigRegs &data);\n   void setContextRegs(const SetContextRegs &data);\n   void setControlConstants(const SetControlConstants &data);\n   void setLoopConsts(const SetLoopConsts &data);\n   void setSamplers(const SetSamplers &data);\n   void setResources(const SetResources &data);\n   void shadowWrite(phys_ptr<uint32_t> address, const gsl::span<uint32_t> &registers);\n   void setRegisters(latte::Register base, const gsl::span<uint32_t> &values);\n\n   void loadAluConsts(const LoadAluConst &data);\n   void loadBoolConsts(const LoadBoolConst &data);\n   void loadConfigRegs(const LoadConfigReg &data);\n   void loadContextRegs(const LoadContextReg &data);\n   void loadControlConstants(const LoadControlConst &data);\n   void loadLoopConsts(const LoadLoopConst &data);\n   void loadSamplers(const LoadSampler &data);\n   void loadResources(const latte::pm4::LoadResource &data); // Thanks Windows!\n   void loadRegisters(latte::Register base,\n                      phys_addr address,\n                      const gsl::span<std::pair<uint32_t, uint32_t>> &registers);\n\n   void runCommandBuffer(const gpu::ringbuffer::Buffer &buffer);\n\n   uint32_t *byteSwapRegValues(uint32_t *values, size_t numValues)\n   {\n      return reinterpret_cast<uint32_t*>(byte_swap_to_scratch<uint32_t>(\n         values, static_cast<uint32_t>(numValues) * sizeof(uint32_t), mRegisterScratch));\n   }\n\n   template<typename Type>\n   Type getRegister(uint32_t id)\n   {\n      decaf_check(id != latte::Register::VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE);\n\n      static_assert(sizeof(Type) == 4, \"Register storage must be a uint32_t\");\n      return *reinterpret_cast<Type *>(&mRegisters[id / 4]);\n   }\n\n   phys_addr getRegisterAddr(uint32_t id)\n   {\n      if (id == latte::Register::VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE) {\n         return mRegAddr_VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE;\n      }\n      decaf_abort(\"Unsupported register address fetch\");\n   }\n\n   latte::ShadowState mShadowState;\n   std::array<uint32_t, 0x10000> mRegisters = { 0 };\n   phys_addr mRegAddr_VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE = phys_addr { 0 };\n\n   std::vector<uint8_t> mRegisterScratch;\n   std::array<std::vector<uint8_t>, MaxPm4IndirectDepth> mSwapScratch;\n   uint32_t mSwapScratchDepth = 0;\n};\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_alu_op2.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"spirv_transpiler.h\"\n\nnamespace spirv\n{\n\nusing namespace latte;\n\n/*\n\nNotes:\n1. KILL instructions only are used to discard a pixel in the pixel\n   shader according to the ISA.  However, based on empirical evidence\n   in some Geometry shaders, its also used to cancel the shader when\n   the ringbuffers overflow.\n\n*/\n\nvoid Transpiler::translateAluOp2_ADD(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = mSpv->createBinOp(spv::OpFAdd, mSpv->floatType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_ADD_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT);\n\n   auto output = mSpv->createBinOp(spv::OpIAdd, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_AND_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT);\n\n   auto output = mSpv->createBinOp(spv::OpBitwiseAnd, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_ASHR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::UINT);\n\n   auto output = mSpv->createBinOp(spv::OpShiftRightArithmetic, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_COS(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n\n   // dst = cos(src0 / 0.1591549367)\n   auto halfPiFConst = mSpv->makeFloatConstant(0.1591549367f);\n   auto srcDived = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), src0, halfPiFConst);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Cos, { srcDived });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_EXP_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Exp, { src0 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid\nTranspiler::translateAluOp2_FLOOR(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::FLOAT);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Floor, { src0 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_FLT_TO_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::FLOAT);\n\n   auto output = mSpv->createUnaryOp(spv::OpConvertFToS, mSpv->intType(), src0);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_FLT_TO_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::FLOAT);\n\n   auto output = mSpv->createUnaryOp(spv::OpConvertFToU, mSpv->uintType(), src0);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_FRACT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Fract, { src0 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_INT_TO_FLT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n\n   auto output = mSpv->createUnaryOp(spv::OpConvertSToF, mSpv->floatType(), src0);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_KILLE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto pred = mSpv->createBinOp(spv::Op::OpFOrdEqual, mSpv->boolType(), src0, src1);\n   auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv };\n\n   if (mType == ShaderParser::Type::Pixel) {\n      mSpv->makeDiscard();\n   } else {\n      mSpv->makeReturn(false);\n   }\n\n   cond.makeEndIf();\n}\n\nvoid Transpiler::translateAluOp2_KILLE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto pred = mSpv->createBinOp(spv::Op::OpIEqual, mSpv->boolType(), src0, src1);\n   auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv };\n\n   if (mType == ShaderParser::Type::Pixel) {\n      mSpv->makeDiscard();\n   } else {\n      mSpv->makeReturn(false);\n   }\n\n   cond.makeEndIf();\n}\n\nvoid Transpiler::translateAluOp2_KILLNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto pred = mSpv->createBinOp(spv::Op::OpFOrdNotEqual, mSpv->boolType(), src0, src1);\n   auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv };\n\n   if (mType == ShaderParser::Type::Pixel) {\n      mSpv->makeDiscard();\n   } else {\n      mSpv->makeReturn(false);\n   }\n\n   cond.makeEndIf();\n}\n\nvoid Transpiler::translateAluOp2_KILLNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto pred = mSpv->createBinOp(spv::Op::OpINotEqual, mSpv->boolType(), src0, src1);\n   auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv };\n\n   if (mType == ShaderParser::Type::Pixel) {\n      mSpv->makeDiscard();\n   } else {\n      mSpv->makeReturn(false);\n   }\n\n   cond.makeEndIf();\n}\n\nvoid Transpiler::translateAluOp2_KILLGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto pred = mSpv->createBinOp(spv::Op::OpFOrdGreaterThan, mSpv->boolType(), src0, src1);\n   auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv };\n\n   if (mType == ShaderParser::Type::Pixel) {\n      mSpv->makeDiscard();\n   } else {\n      mSpv->makeReturn(false);\n   }\n\n   cond.makeEndIf();\n}\n\nvoid Transpiler::translateAluOp2_KILLGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto pred = mSpv->createBinOp(spv::Op::OpSGreaterThan, mSpv->boolType(), src0, src1);\n   auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv };\n\n   if (mType == ShaderParser::Type::Pixel) {\n      mSpv->makeDiscard();\n   } else {\n      mSpv->makeReturn(false);\n   }\n\n   cond.makeEndIf();\n}\n\nvoid Transpiler::translateAluOp2_KILLGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT);\n\n   auto pred = mSpv->createBinOp(spv::Op::OpUGreaterThan, mSpv->boolType(), src0, src1);\n   auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv };\n\n   if (mType == ShaderParser::Type::Pixel) {\n      mSpv->makeDiscard();\n   } else {\n      mSpv->makeReturn(false);\n   }\n\n   cond.makeEndIf();\n}\n\nvoid Transpiler::translateAluOp2_KILLGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto pred = mSpv->createBinOp(spv::Op::OpFOrdGreaterThanEqual, mSpv->boolType(), src0, src1);\n   auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv };\n\n   if (mType == ShaderParser::Type::Pixel) {\n      mSpv->makeDiscard();\n   } else {\n      mSpv->makeReturn(false);\n   }\n\n   cond.makeEndIf();\n}\n\nvoid Transpiler::translateAluOp2_KILLGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto pred = mSpv->createBinOp(spv::Op::OpSGreaterThanEqual, mSpv->boolType(), src0, src1);\n   auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv };\n\n   if (mType == ShaderParser::Type::Pixel) {\n      mSpv->makeDiscard();\n   } else {\n      mSpv->makeReturn(false);\n   }\n\n   cond.makeEndIf();\n}\n\nvoid Transpiler::translateAluOp2_KILLGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT);\n\n   auto pred = mSpv->createBinOp(spv::Op::OpUGreaterThanEqual, mSpv->boolType(), src0, src1);\n   auto cond = spv::Builder::If { pred, spv::SelectionControlMaskNone, *mSpv };\n\n   if (mType == ShaderParser::Type::Pixel) {\n      mSpv->makeDiscard();\n   } else {\n      mSpv->makeReturn(false);\n   }\n\n   cond.makeEndIf();\n}\n\nvoid Transpiler::translateAluOp2_LOG_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   // TODO: Implement log clamp-to-maxval\n   translateAluOp2_LOG_IEEE(cf, group, unit, inst);\n}\n\nvoid Transpiler::translateAluOp2_LOG_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::FLOAT);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Log, { src0 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_LSHL_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::UINT);\n\n   auto output = mSpv->createBinOp(spv::OpShiftLeftLogical, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_LSHR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::UINT);\n\n   auto output = mSpv->createBinOp(spv::OpShiftRightLogical, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_MAX(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FMax, { src0, src1 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_MAX_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   // TODO: MAX_DX10 has different behaviour to GLSL MAX\n   // I believe for dx10 max returns non-NaN value: max(n, NaN) = n max(NaN, n) = n\n   // Whereas glsl returns second parameter: max(n, NaN) = NaN, max(NaN, n) = n\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FMax, { src0, src1 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_MAX_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto output = mSpv->createBuiltinCall(mSpv->intType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450SMax, { src0, src1 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_MAX_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT);\n\n   auto output = mSpv->createBuiltinCall(mSpv->uintType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450UMax, { src0, src1 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_MIN(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FMin, { src0, src1 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_MIN_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   // TODO: MIN_DX10 has different behaviour to GLSL MIN\n   // I believe for dx10 min returns non-NaN value: min(n, NaN) = n min(NaN, n) = n\n   // Whereas glsl returns second parameter: min(n, NaN) = NaN, min(NaN, n) = n\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FMin, { src0, src1 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_MIN_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto output = mSpv->createBuiltinCall(mSpv->intType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450SMin, { src0, src1 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_MIN_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT);\n\n   auto output = mSpv->createBuiltinCall(mSpv->uintType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450UMin, { src0, src1 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_MOV(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   mSpv->writeAluOpDest(cf, group, unit, inst, src0);\n}\n\nvoid Transpiler::translateAluOp2_MOVA_FLOOR(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n\n   auto minClampConst = mSpv->makeIntConstant(-256);\n   auto maxClampConst = mSpv->makeIntConstant(255);\n   auto intVal = mSpv->createUnaryOp(spv::OpConvertFToS, mSpv->intType(), src0);\n   auto output = mSpv->createBuiltinCall(mSpv->intType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450SClamp, { intVal, minClampConst, maxClampConst });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output, true);\n}\n\nvoid Transpiler::translateAluOp2_MOVA_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   mSpv->writeAluOpDest(cf, group, unit, inst, src0, true);\n}\n\nvoid Transpiler::translateAluOp2_MUL(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   // Everyone gets to enjoy the IEEE standards since Vulkan is IEEE\n   // TODO: Check if there are any side-effects of making MUL be IEEE compliant.\n   translateAluOp2_MUL_IEEE(cf, group, unit, inst);\n}\n\nvoid Transpiler::translateAluOp2_MUL_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid\nTranspiler::translateAluOp2_MULLO_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT);\n\n   auto output = mSpv->createBinOp(spv::Op::OpIMul, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid\nTranspiler::translateAluOp2_MULLO_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::UINT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::UINT);\n\n   auto output = mSpv->createBinOp(spv::Op::OpIMul, mSpv->uintType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_NOP(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   // No need to perform any form of translation here...\n}\n\nvoid Transpiler::translateAluOp2_NOT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n   auto output = mSpv->createUnaryOp(spv::OpNot, mSpv->intType(), src0);\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_OR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT);\n\n   auto output = mSpv->createBinOp(spv::OpBitwiseOr, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_PRED_SETE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdEqual, mSpv->floatType(), src0, src1, true);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_PRED_SETE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto output = genPredSetOp(inst, spv::OpIEqual, mSpv->intType(), src0, src1, true);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_PRED_SETGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::FLOAT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::FLOAT);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdGreaterThanEqual, mSpv->floatType(), src0, src1, true);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_PRED_SETGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto output = genPredSetOp(inst, spv::OpSGreaterThanEqual, mSpv->intType(), src0, src1, true);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_PRED_SETGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT);\n\n   auto output = genPredSetOp(inst, spv::OpUGreaterThanEqual, mSpv->uintType(), src0, src1, true);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_PRED_SETGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::FLOAT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::FLOAT);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdGreaterThan, mSpv->floatType(), src0, src1, true);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_PRED_SETGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto output = genPredSetOp(inst, spv::OpSGreaterThan, mSpv->intType(), src0, src1, true);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_PRED_SETGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT);\n\n   auto output = genPredSetOp(inst, spv::OpUGreaterThan, mSpv->uintType(), src0, src1, true);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_PRED_SETNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdNotEqual, mSpv->floatType(), src0, src1, true);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_PRED_SETNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto output = genPredSetOp(inst, spv::OpINotEqual, mSpv->intType(), src0, src1, true);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2RecipCommon(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst, bool isRecipSqrt, bool isFF)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto zeroPositive = mSpv->makeFloatConstant(0.0f);\n   auto zeroNegative = mSpv->makeFloatConstant(-0.0f);\n\n   auto valueZero = spv::Id {};\n   auto valueRecip = spv::Id {};\n   auto blockZero = static_cast<spv::Block *>(nullptr);\n   auto blockRecip = static_cast<spv::Block *>(nullptr);\n\n   auto predIsZero = mSpv->createBinOp(spv::Op::OpFOrdEqual, mSpv->boolType(), src0, zeroPositive);\n   auto condIsZero = spv::Builder::If { predIsZero, spv::SelectionControlMaskNone, *mSpv };\n   {\n      auto src0Sign = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FSign, { src0 });\n      auto isNegative = mSpv->createBinOp(spv::Op::OpFOrdLessThan, mSpv->boolType(), src0Sign, zeroPositive);\n      if (isFF) {\n         valueZero = mSpv->createTriOp(spv::Op::OpSelect, mSpv->floatType(), isNegative, zeroNegative, zeroPositive);\n         blockZero = mSpv->getBuildPoint();\n      } else {\n         auto maxFloatNegative = mSpv->makeFloatConstant(std::numeric_limits<float>::lowest());\n         auto maxFloatPositive = mSpv->makeFloatConstant(std::numeric_limits<float>::max());\n         valueZero = mSpv->createTriOp(spv::Op::OpSelect, mSpv->floatType(), isNegative, maxFloatNegative, maxFloatPositive);\n         blockZero = mSpv->getBuildPoint();\n      }\n   }\n   condIsZero.makeBeginElse();\n   {\n      if (isRecipSqrt) {\n         valueRecip = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450InverseSqrt, { src0 });\n         blockRecip = mSpv->getBuildPoint();\n      } else {\n         auto one = mSpv->makeFloatConstant(1.0f);\n         valueRecip = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), one, src0);\n         blockRecip = mSpv->getBuildPoint();\n      }\n   }\n   condIsZero.makeEndIf();\n\n   auto output = mSpv->createOp(spv::OpPhi, mSpv->floatType(),\n      {\n         valueZero, blockZero->getId(),\n         valueRecip, blockRecip->getId(),\n      });\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_RECIP_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   translateAluOp2RecipCommon(cf, group, unit, inst, false, false);\n}\n\nvoid Transpiler::translateAluOp2_RECIP_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n\n   auto oneFConst = mSpv->makeFloatConstant(1.0f);\n   auto output = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), oneFConst, src0);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_RECIP_FF(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   translateAluOp2RecipCommon(cf, group, unit, inst, false, true);\n}\n\nvoid Transpiler::translateAluOp2_RECIP_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   latte::ShaderParser::translateAluOp2_RECIP_INT(cf, group, unit, inst);\n}\n\nvoid Transpiler::translateAluOp2_RECIP_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   latte::ShaderParser::translateAluOp2_RECIP_UINT(cf, group, unit, inst);\n}\n\nvoid Transpiler::translateAluOp2_RECIPSQRT_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   translateAluOp2RecipCommon(cf, group, unit, inst, true, false);\n}\n\nvoid Transpiler::translateAluOp2_RECIPSQRT_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450InverseSqrt, { src0 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_RECIPSQRT_FF(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   translateAluOp2RecipCommon(cf, group, unit, inst, true, true);\n}\n\nvoid Transpiler::translateAluOp2_RNDNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450RoundEven, { src0 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdEqual, mSpv->floatType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdEqual, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto output = genPredSetOp(inst, spv::OpIEqual, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdGreaterThanEqual, mSpv->floatType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETGE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdGreaterThanEqual, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto output = genPredSetOp(inst, spv::OpSGreaterThanEqual, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT);\n\n   auto output = genPredSetOp(inst, spv::OpUGreaterThanEqual, mSpv->uintType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdGreaterThan, mSpv->floatType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETGT_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdGreaterThan, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto output = genPredSetOp(inst, spv::OpSGreaterThan, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::UINT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::UINT);\n\n   auto output = genPredSetOp(inst, spv::OpUGreaterThan, mSpv->uintType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdNotEqual, mSpv->floatType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETNE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n\n   auto output = genPredSetOp(inst, spv::OpFOrdNotEqual, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SETNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n\n   auto output = genPredSetOp(inst, spv::OpINotEqual, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SIN(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n\n   // dst = sin(src0 / 0.1591549367)\n   auto halfPiFConst = mSpv->makeFloatConstant(0.1591549367f);\n   auto srcDived = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), src0, halfPiFConst);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Sin, { srcDived });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SQRT_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Sqrt, { src0 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_SUB_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT);\n\n   auto output = mSpv->createBinOp(spv::OpISub, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_TRUNC(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n\n   auto output = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450Trunc, { src0 });\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_UINT_TO_FLT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::UINT);\n\n   auto output = mSpv->createUnaryOp(spv::OpConvertUToF, mSpv->floatType(), src0);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp2_XOR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, latte::VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, latte::VarRefType::INT);\n\n   auto output = mSpv->createBinOp(spv::OpBitwiseXor, mSpv->intType(), src0, src1);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\n} // namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_alu_op3.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"spirv_transpiler.h\"\n\nnamespace spirv\n{\n\nusing namespace latte;\n\nvoid Transpiler::translateAluOp3_CNDE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n   auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2);\n\n   auto output = genAluCondOp(spv::Op::OpFOrdEqual, src0, src1, src2);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp3_CNDGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n   auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2);\n\n   auto output = genAluCondOp(spv::Op::OpFOrdGreaterThan, src0, src1, src2);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp3_CNDGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n   auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2);\n\n   auto output = genAluCondOp(spv::Op::OpFOrdGreaterThanEqual, src0, src1, src2);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp3_CNDE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n   auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2, VarRefType::INT);\n\n   auto output = genAluCondOp(spv::Op::OpIEqual, src0, src1, src2);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp3_CNDGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n   auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2, VarRefType::INT);\n\n   auto output = genAluCondOp(spv::Op::OpSGreaterThan, src0, src1, src2);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp3_CNDGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0, VarRefType::INT);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1, VarRefType::INT);\n   auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2, VarRefType::INT);\n\n   auto output = genAluCondOp(spv::Op::OpSGreaterThanEqual, src0, src1, src2);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp3_MULADD(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   translateAluOp3_MULADD_IEEE(cf, group, unit, inst);\n}\n\nvoid Transpiler::translateAluOp3_MULADD_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n   auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2);\n\n   auto muled = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), src0, src1);\n   auto output = mSpv->createBinOp(spv::Op::OpFAdd, mSpv->floatType(), muled, src2);\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp3_MULADD_M2(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n   auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2);\n\n   auto mul = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), src0, src1);\n   auto muladd = mSpv->createBinOp(spv::Op::OpFAdd, mSpv->floatType(), mul, src2);\n   auto output = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), muladd, mSpv->makeFloatConstant(2.0f));\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp3_MULADD_M4(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n   auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2);\n\n   auto mul = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), src0, src1);\n   auto muladd = mSpv->createBinOp(spv::Op::OpFAdd, mSpv->floatType(), mul, src2);\n   auto output = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), muladd, mSpv->makeFloatConstant(4.0f));\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\nvoid Transpiler::translateAluOp3_MULADD_D2(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluInstSrc(cf, group, inst, 0);\n   auto src1 = mSpv->readAluInstSrc(cf, group, inst, 1);\n   auto src2 = mSpv->readAluInstSrc(cf, group, inst, 2);\n\n   auto mul = mSpv->createBinOp(spv::Op::OpFMul, mSpv->floatType(), src0, src1);\n   auto muladd = mSpv->createBinOp(spv::Op::OpFAdd, mSpv->floatType(), mul, src2);\n   auto output = mSpv->createBinOp(spv::Op::OpFDiv, mSpv->floatType(), muladd, mSpv->makeFloatConstant(2.0f));\n\n   mSpv->writeAluOpDest(cf, group, unit, inst, output);\n}\n\n} // namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_alu_reduc.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"spirv_transpiler.h\"\n\nnamespace spirv\n{\n\nusing namespace latte;\n\nvoid Transpiler::translateAluOp2_CUBE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   // TODO: This instructure guarentees a specific ordering among its\n   // two source operands accross the entire reduction.  We aren't going\n   // to verify that guarentee here, but assume it holds.\n\n   auto srcX = mSpv->readAluInstSrc(cf, group, *group.units[SQ_CHAN::Z], 0);\n   auto srcY = mSpv->readAluInstSrc(cf, group, *group.units[SQ_CHAN::W], 0);\n   auto srcZ = mSpv->readAluInstSrc(cf, group, *group.units[SQ_CHAN::X], 0);\n\n   /*\n   Concise pseudocode (v3):\n   if (|z| >= |x| && |z| >= |y|)\n     t = -y\n     s = sign(z) * x\n     ma = 2z\n     f = (z < 0) ? 5 : 4\n   else if (|y| >= |x|)\n     t = sign(y) * z\n     s = x\n     ma = 2y\n     f = (y < 0) ? 3 : 2\n   else\n     t = -y\n     s = sign(x) * z\n     ma = 2x\n     f = (x < 0) ? 1 : 0\n\n    Note that CUBE reverses the order of the texture coordinates in the\n    output: out.yx = face.st\n   */\n\n\n   auto zeroFConst = mSpv->makeFloatConstant(0.0f);\n   auto oneFConst = mSpv->makeFloatConstant(1.0f);\n   auto twoFConst = mSpv->makeFloatConstant(2.0f);\n   auto threeFConst = mSpv->makeFloatConstant(3.0f);\n   auto fourFConst = mSpv->makeFloatConstant(4.0f);\n   auto fiveFConst = mSpv->makeFloatConstant(5.0f);\n\n   auto absX = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FAbs, { srcX });\n   auto absY = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FAbs, { srcY });\n   auto absZ = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FAbs, { srcZ });\n\n   // if (|z| >= |x| && |z| >= |y|) {\n   auto pred01_ZX = mSpv->createBinOp(spv::OpFOrdGreaterThanEqual, mSpv->boolType(), absZ, absX);\n   auto pred01_ZY = mSpv->createBinOp(spv::OpFOrdGreaterThanEqual, mSpv->boolType(), absZ, absY);\n   auto pred01 = mSpv->createBinOp(spv::OpLogicalAnd, mSpv->boolType(), pred01_ZX, pred01_ZY);\n\n   auto pred01Block = spv::Builder::If { pred01, spv::SelectionControlMaskNone, *mSpv };\n\n   spv::Id dstX01, dstY01, dstZ01, dstW01;\n   {\n      auto signZ = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FSign, { srcZ });\n      auto negY = mSpv->createUnaryOp(spv::OpFNegate, mSpv->floatType(), srcY);\n\n      dstX01 = negY;\n      dstY01 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), signZ, srcX);\n      dstZ01 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), srcZ, twoFConst);\n\n      auto predZls0 = mSpv->createBinOp(spv::OpFOrdLessThan, mSpv->boolType(), srcZ, zeroFConst);\n      dstW01 = mSpv->createOp(spv::OpSelect, mSpv->floatType(), { predZls0, fiveFConst, fourFConst });\n   }\n   auto dst01Block = mSpv->getBuildPoint();\n\n   pred01Block.makeBeginElse();\n\n   // } else if (|y| >= |x|) {\n   auto pred23 = mSpv->createBinOp(spv::OpFOrdGreaterThanEqual, mSpv->boolType(), absY, absX);\n\n   auto pred23Block = spv::Builder::If { pred23, spv::SelectionControlMaskNone, *mSpv };\n\n   spv::Id dstX23, dstY23, dstZ23, dstW23;\n   {\n      auto signY = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FSign, { srcY });\n\n      dstX23 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), signY, srcZ);\n      dstY23 = srcX;\n      dstZ23 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), srcY, twoFConst);\n\n      auto predYls0 = mSpv->createBinOp(spv::OpFOrdLessThan, mSpv->boolType(), srcY, zeroFConst);\n      dstW23 = mSpv->createOp(spv::OpSelect, mSpv->floatType(), { predYls0, threeFConst, twoFConst });\n   }\n   auto dst23Block = mSpv->getBuildPoint();\n\n   pred23Block.makeBeginElse();\n\n   // } else {\n   spv::Id dstX45, dstY45, dstZ45, dstW45;\n   {\n      auto signX = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FSign, { srcX });\n      auto negY = mSpv->createUnaryOp(spv::OpFNegate, mSpv->floatType(), srcY);\n\n      dstX45 = negY;\n      dstY45 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), signX, srcZ);\n      dstZ45 = mSpv->createBinOp(spv::OpFMul, mSpv->floatType(), srcX, twoFConst);\n\n      auto predXls0 = mSpv->createBinOp(spv::OpFOrdLessThan, mSpv->boolType(), srcX, zeroFConst);\n      dstW45 = mSpv->createOp(spv::OpSelect, mSpv->floatType(), { predXls0, oneFConst, zeroFConst });\n   }\n   auto dst45Block = mSpv->getBuildPoint();\n\n   pred23Block.makeEndIf();\n\n   auto dstX2345 = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstX23, dst23Block->getId(), dstX45, dst45Block->getId() });\n   auto dstY2345 = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstY23, dst23Block->getId(), dstY45, dst45Block->getId() });\n   auto dstZ2345 = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstZ23, dst23Block->getId(), dstZ45, dst45Block->getId() });\n   auto dstW2345 = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstW23, dst23Block->getId(), dstW45, dst45Block->getId() });\n\n   auto dst2345Block = mSpv->getBuildPoint();\n\n   pred01Block.makeEndIf();\n\n   auto dstX = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstX01, dst01Block->getId(), dstX2345, dst2345Block->getId() });\n   auto dstY = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstY01, dst01Block->getId(), dstY2345, dst2345Block->getId() });\n   auto dstZ = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstZ01, dst01Block->getId(), dstZ2345, dst2345Block->getId() });\n   auto dstW = mSpv->createOp(spv::OpPhi, mSpv->floatType(), { dstW01, dst01Block->getId(), dstW2345, dst2345Block->getId() });\n\n   // Okay, we now have a sorta bloody clue why we need to do this.  The CUBE instruction\n   // intentionally performs a some math to the values such that the values shift out of\n   // the 0.0-1.0 range and into the 1.0-2.0 range which has the benefit of a constant\n   // mantissa component to the floating point number.  Supposedly this improves the\n   // performance of the lookup for AMD hardware.  We would normally duplicate this\n   // behaviour, but need to undo it because the samplers are set to BORDER, which\n   // causes invalid values to be sampled.  We're not sure how its meant to work with a\n   // non-wrapping sampler, but thats a problem for another day.\n   {\n      auto dstAbsZ = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FAbs, { dstZ });\n\n      dstX = mSpv->createBinOp(spv::OpFSub, mSpv->floatType(), dstX, dstAbsZ);\n      dstY = mSpv->createBinOp(spv::OpFSub, mSpv->floatType(), dstY, dstAbsZ);\n   }\n\n   mSpv->writeAluOpDest(cf, group, SQ_CHAN::X, *group.units[SQ_CHAN::X], dstX);\n   mSpv->writeAluOpDest(cf, group, SQ_CHAN::Y, *group.units[SQ_CHAN::Y], dstY);\n   mSpv->writeAluOpDest(cf, group, SQ_CHAN::Z, *group.units[SQ_CHAN::Z], dstZ);\n   mSpv->writeAluOpDest(cf, group, SQ_CHAN::W, *group.units[SQ_CHAN::W], dstW);\n}\n\nvoid Transpiler::translateAluOp2_DOT4(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   translateAluOp2_DOT4_IEEE(cf, group, unit, inst);\n}\n\nvoid Transpiler::translateAluOp2_DOT4_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   auto src0 = mSpv->readAluReducSrc(cf, group, 0);\n   auto src1 = mSpv->readAluReducSrc(cf, group, 1);\n\n   auto output = mSpv->createBinOp(spv::Op::OpDot, mSpv->floatType(), src0, src1);\n\n   mSpv->writeAluReducDest(cf, group, output);\n}\n\n} // namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_cf.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"spirv_transpiler.h\"\n\nnamespace spirv\n{\n\nusing namespace latte;\n\nvoid Transpiler::translateCf_ALU(const ControlFlowInst &cf)\n{\n   auto afterBlock = mSpv->startCfCondBlock();\n\n   translateAluClause(cf);\n\n   mSpv->endCfCondBlock(afterBlock);\n}\n\nvoid Transpiler::translateCf_ALU_PUSH_BEFORE(const ControlFlowInst &cf)\n{\n   mSpv->pushStack();\n   translateCf_ALU(cf);\n}\n\nvoid Transpiler::translateCf_ALU_POP_AFTER(const ControlFlowInst &cf)\n{\n   translateCf_ALU(cf);\n   mSpv->popStack(1);\n}\n\nvoid Transpiler::translateCf_ALU_POP2_AFTER(const ControlFlowInst &cf)\n{\n   translateCf_ALU(cf);\n   mSpv->popStack(2);\n}\n\nvoid Transpiler::translateCf_ALU_EXT(const ControlFlowInst &cf)\n{\n   translateCf_ALU(cf);\n}\n\nvoid Transpiler::translateCf_ALU_CONTINUE(const ControlFlowInst &cf)\n{\n   translateCf_ALU(cf);\n\n   // If state is set to Inactive, set it to InactiveContinue\n   auto isInactive = mSpv->createBinOp(spv::OpIEqual, mSpv->boolType(),\n                                       mSpv->createLoad(mSpv->stateVar(), spv::NoPrecision),\n                                       mSpv->stateInactive());\n   auto ifBuilder = spv::Builder::If { isInactive, spv::SelectionControlMaskNone, *mSpv };\n   mSpv->createStore(mSpv->stateInactiveContinue(), mSpv->stateVar());\n   ifBuilder.makeEndIf();\n}\n\nvoid Transpiler::translateCf_ALU_BREAK(const ControlFlowInst &cf)\n{\n   translateCf_ALU(cf);\n\n   // If state is set to Inactive, set it to InactiveBreak\n   auto isInactive = mSpv->createBinOp(spv::OpIEqual, mSpv->boolType(),\n                                       mSpv->createLoad(mSpv->stateVar(), spv::NoPrecision),\n                                       mSpv->stateInactive());\n   auto ifBuilder = spv::Builder::If { isInactive, spv::SelectionControlMaskNone, *mSpv };\n   mSpv->createStore(mSpv->stateInactiveBreak(), mSpv->stateVar());\n   ifBuilder.makeEndIf();\n}\n\nvoid Transpiler::translateCf_ALU_ELSE_AFTER(const ControlFlowInst &cf)\n{\n   translateCf_ALU(cf);\n   translateCf_ELSE(cf);\n}\n\nvoid Transpiler::translateCf_CALL_FS(const ControlFlowInst &cf)\n{\n   mSpv->createFunctionCall(mSpv->getFunction(\"fs_main\"), {});\n}\n\nvoid Transpiler::translateCf_ELSE(const ControlFlowInst &cf)\n{\n   mSpv->elseStack();\n}\n\nvoid Transpiler::translateCf_JUMP(const ControlFlowInst &cf)\n{\n   // This is actually only an optimization so we can ignore it.\n}\n\nvoid Transpiler::translateCf_KILL(const ControlFlowInst &cf)\n{\n   latte::ShaderParser::translateCf_KILL(cf);\n}\n\nvoid Transpiler::translateCf_NOP(const ControlFlowInst &cf)\n{\n   // Who knows why they waste space with explicitly encoded NOP's...\n}\n\nvoid Transpiler::translateCf_PUSH(const ControlFlowInst &cf)\n{\n   mSpv->pushStack();\n}\n\nvoid Transpiler::translateCf_POP(const ControlFlowInst &cf)\n{\n   mSpv->popStack(cf.word1.POP_COUNT());\n}\n\nvoid Transpiler::translateCf_RETURN(const ControlFlowInst &cf)\n{\n   auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST());\n\n   mSpv->makeReturn(true);\n\n   mSpv->endCfCondBlock(afterBlock, true);\n}\n\nvoid Transpiler::translateCf_TEX(const ControlFlowInst &cf)\n{\n   auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST());\n\n   translateTexClause(cf);\n\n   mSpv->endCfCondBlock(afterBlock);\n}\n\nvoid Transpiler::translateCf_VTX(const ControlFlowInst &cf)\n{\n   auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST());\n\n   translateVtxClause(cf);\n\n   mSpv->endCfCondBlock(afterBlock);\n}\n\nvoid Transpiler::translateCf_VTX_TC(const ControlFlowInst &cf)\n{\n   auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST());\n\n   translateVtxClause(cf);\n\n   mSpv->endCfCondBlock(afterBlock);\n}\n\nvoid Transpiler::translateCf_LOOP_START(const ControlFlowInst &cf)\n{\n   latte::ShaderParser::translateCf_LOOP_START(cf);\n}\n\nvoid Transpiler::translateCf_LOOP_START_DX10(const ControlFlowInst &cf)\n{\n   decaf_check(cf.word1.COND() == latte::SQ_CF_COND::ACTIVE);\n\n   mSpv->pushStack();\n\n   auto loop = LoopState { };\n   loop.startPC = mCfPC;\n   loop.endPC = cf.word0.ADDR() - 1;\n   loop.head = &mSpv->makeNewBlock();\n   loop.body = &mSpv->makeNewBlock();\n   loop.merge = &mSpv->makeNewBlock();\n   loop.continue_target = &mSpv->makeNewBlock();\n   mLoopStack.emplace_back(loop);\n\n   mSpv->createBranch(loop.head);\n   mSpv->setBuildPoint(loop.head);\n   mSpv->createLoopMerge(loop.merge, loop.continue_target, spv::LoopControlMaskNone, {});\n   mSpv->createBranch(loop.body);\n   mSpv->setBuildPoint(loop.body);\n\n   auto stateVal = mSpv->createLoad(mSpv->stateVar(), spv::NoPrecision);\n\n   // If state is set to InactiveContinue, set it to Active\n   {\n      auto isInactiveContinue = mSpv->createBinOp(spv::OpIEqual, mSpv->boolType(),\n                                                  stateVal,\n                                                  mSpv->stateInactiveContinue());\n      auto ifBuilder = spv::Builder::If { isInactiveContinue, spv::SelectionControlMaskNone, *mSpv };\n      mSpv->createStore(mSpv->stateActive(), mSpv->stateVar());\n      ifBuilder.makeEndIf();\n   }\n\n   // If state is set to Inactive, set it to InactiveBreak\n   {\n      auto isInactive = mSpv->createBinOp(spv::OpIEqual, mSpv->boolType(), stateVal, mSpv->stateInactive());\n      auto ifBuilder = spv::Builder::If { isInactive, spv::SelectionControlMaskNone, *mSpv };\n      mSpv->createStore(mSpv->stateInactiveBreak(), mSpv->stateVar());\n      ifBuilder.makeEndIf();\n   }\n}\n\nvoid Transpiler::translateCf_LOOP_START_NO_AL(const ControlFlowInst &cf)\n{\n   latte::ShaderParser::translateCf_LOOP_START_NO_AL(cf);\n}\n\nvoid Transpiler::translateCf_LOOP_END(const ControlFlowInst &cf)\n{\n   auto loop = mLoopStack.back();\n\n   // Sanity check to ensure we are at the correct cfPC\n   decaf_check(mCfPC == loop.endPC);\n   decaf_check((cf.word0.ADDR() - 1) == loop.startPC);\n\n   mSpv->createBranch(loop.continue_target);\n   mSpv->setBuildPoint(loop.continue_target);\n\n   // Continue while state != InactiveBreak\n   auto predContinue = mSpv->createBinOp(spv::OpINotEqual, mSpv->boolType(),\n                                         mSpv->createLoad(mSpv->stateVar(), spv::NoPrecision),\n                                         mSpv->stateInactiveBreak());\n   mSpv->createConditionalBranch(predContinue, loop.head, loop.merge);\n\n   mSpv->setBuildPoint(loop.merge);\n   mLoopStack.pop_back();\n\n   // LOOP_END ignores POP_COUNT as per R600 ISA Documentation\n   mSpv->popStack(1);\n}\n\nvoid Transpiler::translateCf_LOOP_CONTINUE(const ControlFlowInst &cf)\n{\n   latte::ShaderParser::translateCf_LOOP_CONTINUE(cf);\n}\n\nvoid Transpiler::translateCf_LOOP_BREAK(const ControlFlowInst &cf)\n{\n   latte::ShaderParser::translateCf_LOOP_BREAK(cf);\n}\n\nvoid Transpiler::translateCf_EMIT_VERTEX(const ControlFlowInst &cf)\n{\n   auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST());\n\n   mSpv->createFunctionCall(mSpv->getFunction(\"dc_main\"), {});\n   mSpv->createNoResultOp(spv::OpEmitVertex);\n\n   mSpv->endCfCondBlock(afterBlock);\n}\n\nvoid Transpiler::translateCf_EMIT_CUT_VERTEX(const ControlFlowInst &cf)\n{\n   auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST());\n\n   mSpv->createFunctionCall(mSpv->getFunction(\"dc_main\"), {});\n   mSpv->createNoResultOp(spv::OpEmitVertex);\n   mSpv->createNoResultOp(spv::OpEndPrimitive);\n\n   mSpv->endCfCondBlock(afterBlock);\n}\n\nvoid Transpiler::translateCf_CUT_VERTEX(const ControlFlowInst &cf)\n{\n   auto afterBlock = mSpv->startCfCondBlock(cf.word1.COND(), cf.word1.CF_CONST());\n\n   mSpv->createNoResultOp(spv::OpEndPrimitive);\n\n   mSpv->endCfCondBlock(afterBlock);\n}\n\n} // namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_export.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"spirv_transpiler.h\"\n\nnamespace spirv\n{\n\nusing namespace latte;\n\nstatic inline void\ncalcSpecialVecPositions(latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl, uint32_t *miscVec, uint32_t *ccdist0Vec, uint32_t *ccdist1Vec)\n{\n   uint32_t currentPos = 1;\n\n   // TODO: When we encounter these, check the expected ordering.\n   decaf_check(!pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA());\n   decaf_check(!pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA());\n\n   if (pa_cl_vs_out_cntl.VS_OUT_MISC_VEC_ENA()) {\n      if (miscVec) {\n         *miscVec = currentPos;\n      }\n      currentPos++;\n   }\n\n   if (pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA()) {\n      if (ccdist0Vec) {\n         *ccdist0Vec = currentPos;\n      }\n      currentPos++;\n   }\n\n   if (pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA()) {\n      if (ccdist1Vec) {\n         *ccdist1Vec = currentPos;\n      }\n      currentPos++;\n   }\n}\n\nstatic inline uint32_t\ncalcMiscVecPos(latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl)\n{\n   uint32_t output;\n   calcSpecialVecPositions(pa_cl_vs_out_cntl, &output, nullptr, nullptr);\n   return output;\n}\n\nstatic inline uint32_t\ncalcCdist0VecPos(latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl)\n{\n   uint32_t output;\n   calcSpecialVecPositions(pa_cl_vs_out_cntl, nullptr, &output, nullptr);\n   return output;\n}\n\nstatic inline uint32_t\ncalcCdist1VecPos(latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl)\n{\n   uint32_t output;\n   calcSpecialVecPositions(pa_cl_vs_out_cntl, nullptr, nullptr, &output);\n   return output;\n}\n\nvoid Transpiler::translateGenericExport(const ControlFlowInst &cf)\n{\n   // cf.exp.word0.ARRAY_SIZE() is ignored for exports\n   // cf.exp.word0.ELEM_SIZE() is ignored for exports\n\n   GprRef srcGpr;\n   srcGpr = makeGprRef(cf.exp.word0.RW_GPR(), cf.exp.word0.RW_REL(), SQ_INDEX_MODE::LOOP);\n\n   ExportMaskRef exportRef;\n   exportRef.output = makeExportRef(cf.exp.word0.TYPE(), cf.exp.word0.ARRAY_BASE());\n   exportRef.mask[SQ_CHAN::X] = cf.exp.swiz.SEL_X();\n   exportRef.mask[SQ_CHAN::Y] = cf.exp.swiz.SEL_Y();\n   exportRef.mask[SQ_CHAN::Z] = cf.exp.swiz.SEL_Z();\n   exportRef.mask[SQ_CHAN::W] = cf.exp.swiz.SEL_W();\n\n   if (isSwizzleFullyMasked(exportRef.mask)) {\n      // We should just skip fully masked swizzles.\n      return;\n   }\n\n   auto exportCount = cf.exp.word1.BURST_COUNT() + 1;\n   for (auto i = 0u; i < exportCount; ++i) {\n      // Read the source GPR\n      auto sourcePtr = mSpv->getGprRef(srcGpr);\n      auto sourceVal = mSpv->createLoad(sourcePtr, spv::NoPrecision);\n\n      bool skipWrite = false;\n\n      if (mType == ShaderParser::Type::Pixel) {\n         // Update the export value type based on the color output format\n         if (exportRef.output.type == ExportRef::Type::Pixel ||\n             exportRef.output.type == ExportRef::Type::PixelWithFog) {\n            auto pixelFormat = mPixelOutType[exportRef.output.arrayBase];\n            if (pixelFormat == PixelOutputType::FLOAT) {\n               // We are already in the right format, nothing to do here...\n            } else if (pixelFormat == PixelOutputType::SINT) {\n               sourceVal = mSpv->createUnaryOp(spv::OpBitcast, mSpv->int4Type(), sourceVal);\n            } else if (pixelFormat == PixelOutputType::UINT) {\n               sourceVal = mSpv->createUnaryOp(spv::OpBitcast, mSpv->uint4Type(), sourceVal);\n            }\n         }\n\n         // Apply the appropriate masking.\n         if (exportRef.output.type == ExportRef::Type::Pixel ||\n             exportRef.output.type == ExportRef::Type::PixelWithFog) {\n\n            // Calculate that the number of exports we expect matchs our number\n            // of enabled rendertargets, or the search below will fail.\n            auto numExports = mSqPgmExportsPs.EXPORT_MODE() >> 1;\n            auto rtExports = 0u;\n            rtExports += mCbShaderControl.RT0_ENABLE() ? 1u : 0u;\n            rtExports += mCbShaderControl.RT1_ENABLE() ? 1u : 0u;\n            rtExports += mCbShaderControl.RT2_ENABLE() ? 1u : 0u;\n            rtExports += mCbShaderControl.RT3_ENABLE() ? 1u : 0u;\n            rtExports += mCbShaderControl.RT4_ENABLE() ? 1u : 0u;\n            rtExports += mCbShaderControl.RT5_ENABLE() ? 1u : 0u;\n            rtExports += mCbShaderControl.RT6_ENABLE() ? 1u : 0u;\n            rtExports += mCbShaderControl.RT7_ENABLE() ? 1u : 0u;\n            decaf_check(rtExports == numExports);\n\n            // Skip over render targets which are not being written.\n            do {\n               auto rtEnabled = !!((mCbShaderControl.value >> exportRef.output.arrayBase) & 0x1);\n               if (rtEnabled) {\n                  break;\n               }\n\n               exportRef.output.arrayBase++;\n            } while (exportRef.output.arrayBase < 8);\n            decaf_check(exportRef.output.arrayBase < 8);\n\n            auto compMask = (mCbShaderMask.value >> (exportRef.output.arrayBase * 4)) & 0xf;\n\n            spv::Id maskXVal, maskYVal, maskZVal, maskWVal;\n            auto sourceValType = mSpv->getTypeId(sourceVal);\n            if (sourceValType == mSpv->float4Type()) {\n               maskXVal = mSpv->makeFloatConstant(0.0f);\n               maskYVal = mSpv->makeFloatConstant(0.0f);\n               maskZVal = mSpv->makeFloatConstant(0.0f);\n               maskWVal = mSpv->makeFloatConstant(1.0f);\n            } else if (sourceValType == mSpv->int4Type()) {\n               maskXVal = mSpv->makeIntConstant(0);\n               maskYVal = mSpv->makeIntConstant(0);\n               maskZVal = mSpv->makeIntConstant(0);\n               maskWVal = mSpv->makeIntConstant(1);\n            } else if (sourceValType == mSpv->uint4Type()) {\n               maskXVal = mSpv->makeUintConstant(0);\n               maskYVal = mSpv->makeUintConstant(0);\n               maskZVal = mSpv->makeUintConstant(0);\n               maskWVal = mSpv->makeUintConstant(1);\n            } else {\n               decaf_abort(\"Unexpected texture output format in component masking.\");\n            }\n\n            if (!(compMask & 1)) {\n               sourceVal = mSpv->createOp(spv::OpCompositeInsert, sourceValType, { maskXVal, sourceVal, 0 });\n            }\n            if (!(compMask & 2)) {\n               sourceVal = mSpv->createOp(spv::OpCompositeInsert, sourceValType, { maskYVal, sourceVal, 1 });\n            }\n            if (!(compMask & 4)) {\n               sourceVal = mSpv->createOp(spv::OpCompositeInsert, sourceValType, { maskZVal, sourceVal, 2 });\n            }\n            if (!(compMask & 8)) {\n               sourceVal = mSpv->createOp(spv::OpCompositeInsert, sourceValType, { maskWVal, sourceVal, 3 });\n            }\n         }\n\n         if (exportRef.output.type == ExportRef::Type::ComputedZ) {\n            if (!mDbShaderControl.Z_EXPORT_ENABLE()) {\n               // The shader exported a Z, but its not enabled.  Lets skip it.\n               skipWrite = true;\n            }\n         }\n      }\n\n      if (mType == ShaderParser::Type::Vertex || mType == ShaderParser::Type::DataCache) {\n         // Check if this is a position output special case.\n         if (exportRef.output.type == ExportRef::Type::Position) {\n            if (exportRef.output.arrayBase == 0) {\n               // POS_0 is just a standard position output.\n            } else {\n               auto pa_cl_vs_out_cntl = mPaClVsOutCntl;\n\n               // Lets check for things we do not support\n               decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_0());\n               decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_1());\n               decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_2());\n               decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_3());\n               decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_4());\n               decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_5());\n               decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_6());\n               decaf_check(!pa_cl_vs_out_cntl.CLIP_DIST_ENA_7());\n               decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_0());\n               decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_1());\n               decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_2());\n               decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_3());\n               decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_4());\n               decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_5());\n               decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_6());\n               decaf_check(!pa_cl_vs_out_cntl.CULL_DIST_ENA_7());\n               decaf_check(!pa_cl_vs_out_cntl.USE_VTX_EDGE_FLAG());\n               decaf_check(!pa_cl_vs_out_cntl.USE_VTX_VIEWPORT_INDX());\n               decaf_check(!pa_cl_vs_out_cntl.USE_VTX_KILL_FLAG());\n               decaf_check(!pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA());\n               decaf_check(!pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA());\n               decaf_check(!pa_cl_vs_out_cntl.USE_VTX_GS_CUT_FLAG());\n\n               // The MISC side-bus seems related to the VS_OUT_MISC_VEC_ENA\n               // pa_cl_vs_out_cntl.VS_OUT_MISC_SIDE_BUS_ENA()\n\n               // We have to manually swizzle the value here for use below\n               sourceVal = mSpv->applySelMask(spv::NoResult, sourceVal, exportRef.mask);\n\n               if (exportRef.output.arrayBase == calcMiscVecPos(pa_cl_vs_out_cntl)) {\n                  if (pa_cl_vs_out_cntl.USE_VTX_RENDER_TARGET_INDX()) {\n                     auto sourceZ = mSpv->createOp(spv::OpCompositeExtract, mSpv->floatType(), { sourceVal, 2 });\n                     auto sourceZInt = mSpv->createUnaryOp(spv::OpBitcast, mSpv->intType(), sourceZ);\n                     mSpv->createStore(sourceZInt, mSpv->layerIdVar());\n                  }\n               } else if (exportRef.output.arrayBase == calcCdist0VecPos(pa_cl_vs_out_cntl)) {\n                  decaf_abort(\"Unsupported CCDIST0 usage\");\n               } else if (exportRef.output.arrayBase == calcCdist1VecPos(pa_cl_vs_out_cntl)) {\n                  decaf_abort(\"Unsupported CCDIST1 usage\");\n               } else {\n                  decaf_abort(\"Unexpected position export index\");\n               }\n\n               // We have to skip the write, since its already done.\n               skipWrite = true;\n            }\n         }\n      }\n\n      // Write the exported data\n      if (!skipWrite) {\n         mSpv->writeExportRef(exportRef, sourceVal);\n      }\n\n      // Increase the indexing for each export\n      srcGpr.next();\n      exportRef.output.next();\n   }\n}\n\nvoid Transpiler::translateCf_EXP(const ControlFlowInst &cf)\n{\n   translateGenericExport(cf);\n}\n\nvoid Transpiler::translateCf_EXP_DONE(const ControlFlowInst &cf)\n{\n   translateGenericExport(cf);\n}\n\nvoid Transpiler::translateGenericStream(const ControlFlowInst &cf, int streamIdx)\n{\n   decaf_check(streamIdx < 4);\n\n   // Find the right stride for this particular streamout.\n   auto streamOutStride = mStreamOutStride[streamIdx];\n\n   GprRef srcGpr;\n   srcGpr = makeGprRef(cf.exp.word0.RW_GPR(), cf.exp.word0.RW_REL(), SQ_INDEX_MODE::LOOP);\n\n   ExportMaskRef exportRef;\n   exportRef.output = makeStreamExportRef(cf.exp.word0.TYPE(),\n                                          cf.exp.word0.INDEX_GPR(),\n                                          streamIdx,\n                                          streamOutStride,\n                                          cf.exp.word0.ARRAY_BASE(),\n                                          cf.exp.buf.ARRAY_SIZE() + 1,\n                                          cf.exp.word0.ELEM_SIZE() + 1);\n   exportRef.mask[SQ_CHAN::X] = (cf.exp.buf.COMP_MASK() & (1 << 0)) ? latte::SQ_SEL::SEL_X : latte::SQ_SEL::SEL_MASK;\n   exportRef.mask[SQ_CHAN::Y] = (cf.exp.buf.COMP_MASK() & (1 << 1)) ? latte::SQ_SEL::SEL_Y : latte::SQ_SEL::SEL_MASK;\n   exportRef.mask[SQ_CHAN::Z] = (cf.exp.buf.COMP_MASK() & (1 << 2)) ? latte::SQ_SEL::SEL_Z : latte::SQ_SEL::SEL_MASK;\n   exportRef.mask[SQ_CHAN::W] = (cf.exp.buf.COMP_MASK() & (1 << 3)) ? latte::SQ_SEL::SEL_W : latte::SQ_SEL::SEL_MASK;\n\n   if (isSwizzleFullyMasked(exportRef.mask)) {\n      // We should just skip fully masked swizzles.\n      return;\n   }\n\n   auto exportCount = cf.exp.word1.BURST_COUNT() + 1;\n   for (auto i = 0u; i < exportCount; ++i) {\n      // Read the source GPR\n      auto sourcePtr = mSpv->getGprRef(srcGpr);\n      auto sourceVal = mSpv->createLoad(sourcePtr, spv::NoPrecision);\n\n      // Write the exported data\n      mSpv->writeExportRef(exportRef, sourceVal);\n\n      // Increase the indexing for each export\n      srcGpr.next();\n      exportRef.output.next();\n   }\n}\n\nvoid Transpiler::translateCf_MEM_STREAM0(const ControlFlowInst &cf)\n{\n   translateGenericStream(cf, 0);\n}\n\nvoid Transpiler::translateCf_MEM_STREAM1(const ControlFlowInst &cf)\n{\n   translateGenericStream(cf, 1);\n}\n\nvoid Transpiler::translateCf_MEM_STREAM2(const ControlFlowInst &cf)\n{\n   translateGenericStream(cf, 2);\n}\n\nvoid Transpiler::translateCf_MEM_STREAM3(const ControlFlowInst &cf)\n{\n   translateGenericStream(cf, 3);\n}\n\nvoid Transpiler::translateCf_MEM_RING(const ControlFlowInst &cf)\n{\n   GprRef srcGpr;\n   srcGpr = makeGprRef(cf.exp.word0.RW_GPR(), cf.exp.word0.RW_REL(), SQ_INDEX_MODE::LOOP);\n\n   ExportMaskRef exportRef;\n   if (mType == ShaderParser::Type::Vertex) {\n      exportRef.output = makeVsGsRingExportRef(cf.exp.word0.TYPE(),\n                                               cf.exp.word0.INDEX_GPR(),\n                                               cf.exp.word0.ARRAY_BASE(),\n                                               cf.exp.buf.ARRAY_SIZE() + 1,\n                                               cf.exp.word0.ELEM_SIZE() + 1);\n   } else if (mType == ShaderParser::Type::Geometry) {\n      exportRef.output = makeGsDcRingExportRef(cf.exp.word0.TYPE(),\n                                               cf.exp.word0.INDEX_GPR(),\n                                               cf.exp.word0.ARRAY_BASE(),\n                                               cf.exp.buf.ARRAY_SIZE() + 1,\n                                               cf.exp.word0.ELEM_SIZE() + 1);\n   } else {\n      decaf_abort(\"Unexpected shader type for MEM_RING\")\n   }\n   exportRef.mask[SQ_CHAN::X] = (cf.exp.buf.COMP_MASK() & (1 << 0)) ? latte::SQ_SEL::SEL_X : latte::SQ_SEL::SEL_MASK;\n   exportRef.mask[SQ_CHAN::Y] = (cf.exp.buf.COMP_MASK() & (1 << 1)) ? latte::SQ_SEL::SEL_Y : latte::SQ_SEL::SEL_MASK;\n   exportRef.mask[SQ_CHAN::Z] = (cf.exp.buf.COMP_MASK() & (1 << 2)) ? latte::SQ_SEL::SEL_Z : latte::SQ_SEL::SEL_MASK;\n   exportRef.mask[SQ_CHAN::W] = (cf.exp.buf.COMP_MASK() & (1 << 3)) ? latte::SQ_SEL::SEL_W : latte::SQ_SEL::SEL_MASK;\n\n   if (isSwizzleFullyMasked(exportRef.mask)) {\n      // We should just skip fully masked swizzles.\n      return;\n   }\n\n   auto exportCount = cf.exp.word1.BURST_COUNT() + 1;\n   for (auto i = 0u; i < exportCount; ++i) {\n      // Read the source GPR\n      auto sourcePtr = mSpv->getGprRef(srcGpr);\n      auto sourceVal = mSpv->createLoad(sourcePtr, spv::NoPrecision);\n\n      // Write the exported data\n      mSpv->writeExportRef(exportRef, sourceVal);\n\n      // Increase the indexing for each export\n      srcGpr.next();\n      exportRef.output.next();\n   }\n}\n\n} // namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_helpers.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"spirv_transpiler.h\"\n\nnamespace spirv\n{\n\nusing namespace latte;\n\n// TODO: We really need to make up our minds on whether these kinds of things exist\n// as part of the Transpiler or the ShaderSpvBuilder...  I think that if the item\n// requires access to any of the latte:: structures, it should probably exist here\n// and only generic things belong in ShaderSpvBuilder.\n\nspv::Id\nTranspiler::genAluCondOp(spv::Op predOp, spv::Id lhsVal, spv::Id trueVal, spv::Id falseVal)\n{\n   auto trueType = mSpv->getTypeId(trueVal);\n   auto falseType = mSpv->getTypeId(trueVal);\n   decaf_check(trueType == falseType);\n\n   spv::Id rhsVal;\n   auto lhsValType = mSpv->getTypeId(lhsVal);\n   if (mSpv->isFloatType(lhsValType)) {\n      rhsVal = mSpv->makeFloatConstant(0.0f);\n   } else if (mSpv->isUintType(lhsValType)) {\n      rhsVal = mSpv->makeUintConstant(0);\n   } else if (mSpv->isIntType(lhsValType)) {\n      rhsVal = mSpv->makeUintConstant(0);\n   } else {\n      decaf_abort(\"Unexpected type passed for ALU condition\");\n   }\n\n   auto pred = mSpv->createBinOp(predOp, mSpv->boolType(), lhsVal, rhsVal);\n   return mSpv->createTriOp(spv::Op::OpSelect, trueType, pred, trueVal, falseVal);\n}\n\nspv::Id\nTranspiler::genPredSetOp(const AluInst &inst, spv::Op predOp, spv::Id typeId, spv::Id lhsVal, spv::Id rhsVal, bool updatesPredicate)\n{\n   // Ensure this is an OP2, which is what we expect\n   decaf_check(inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2);\n\n   auto pred = mSpv->createBinOp(predOp, mSpv->boolType(), lhsVal, rhsVal);\n\n   if (updatesPredicate) {\n      if (inst.op2.UPDATE_PRED()) {\n         mSpv->createStore(pred, mSpv->predicateVar());\n      }\n\n      if (inst.op2.UPDATE_EXECUTE_MASK()) {\n         auto newStateVal = mSpv->createTriOp(spv::Op::OpSelect, mSpv->intType(), pred,\n                                              mSpv->stateActive(), mSpv->stateInactive());\n         mSpv->createStore(newStateVal, mSpv->stateVar());\n      }\n   }\n\n   spv::Id trueVal, falseVal;\n   if (mSpv->isFloatType(typeId)) {\n      trueVal = mSpv->makeFloatConstant(1.0f);\n      falseVal = mSpv->makeFloatConstant(0.0f);\n   } else if (mSpv->isUintType(typeId)) {\n      trueVal = mSpv->makeUintConstant(0xffffffff);\n      falseVal = mSpv->makeUintConstant(0);\n   } else if (mSpv->isIntType(typeId)) {\n      trueVal = mSpv->makeIntConstant(-1);\n      falseVal = mSpv->makeIntConstant(0);\n   } else {\n      decaf_abort(\"Unexpected return type for predicate op\");\n   }\n\n   return mSpv->createTriOp(spv::Op::OpSelect, typeId, pred, trueVal, falseVal);\n}\n\n} // namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_pushconstants.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n#include <cstdint>\n#include <common/align.h>\n\nnamespace spirv\n{\n\nstruct Vec4\n{\n   float x;\n   float y;\n   float z;\n   float w;\n};\n\nstruct alignas(16) VertexPushConstants\n{\n   Vec4 posMulAdd;\n   Vec4 zSpaceMul;\n   float pointSize;\n};\nstatic constexpr int VertexPushConstantsSize = sizeof(VertexPushConstants);\nstatic constexpr int VertexPushConstantsOffset = 0;\n\nstruct alignas(16) FragmentPushConstants\n{\n   uint32_t alphaFunc;\n   float alphaRef;\n   uint32_t needsPremultiply;\n};\nstatic constexpr int FragmentPushConstantsSize = sizeof(FragmentPushConstants);\nstatic constexpr int FragmentPushConstantsOffset = VertexPushConstantsSize;\n\n// Vulkan only requires 4 byte alignment but it seems MoltenVK wants us to\n// align to 16 bytes.\nstatic_assert((VertexPushConstantsSize % 16) == 0);\nstatic_assert((VertexPushConstantsOffset % 16) == 0);\n\nstatic_assert((FragmentPushConstantsSize % 16) == 0);\nstatic_assert((FragmentPushConstantsOffset % 16) == 0);\n\n} // namespace spirv\n\n#endif\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_shaderspvbuilder.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n#include \"latte/latte_shaderparser.h\"\n#include \"spirv_spvbuilder.h\"\n#include \"spirv_pushconstants.h\"\n#include \"gpu_config.h\"\n\nnamespace spirv\n{\n\nusing namespace latte;\n\nenum class ConstantsMode : uint32_t\n{\n   Unknown,\n   Registers,\n   Buffers\n};\n\nclass ShaderSpvBuilder : public SpvBuilder\n{\npublic:\n   ShaderSpvBuilder(spv::ExecutionModel execModel)\n   {\n      setEmitOpLines();\n      setSource(spv::SourceLanguage::SourceLanguageUnknown, 0);\n      setMemoryModel(spv::AddressingModel::AddressingModelLogical, spv::MemoryModel::MemoryModelGLSL450);\n      addCapability(spv::Capability::CapabilityShader);\n\n      auto mainFn = makeEntryPoint(\"main\");\n      mFunctions[\"main\"] = mainFn;\n\n      auto entry = addEntryPoint(execModel, mainFn, \"main\");\n\n      mEntryPoint = entry;\n   }\n\n   void setBindingBase(int bindingBase)\n   {\n      mBindingBase = bindingBase;\n   }\n\n   void elseStack()\n   {\n      // stackIndexVal = *stackIndexVar\n      auto stackIdxVal = createLoad(stackIndexVar(), spv::NoPrecision);\n      addName(stackIdxVal, \"stackIdx\");\n\n      // prevStackIdxVal = stackIndexVal - 1\n      auto oneConst = makeIntConstant(1);\n      auto prevStackIdxVal = createBinOp(spv::OpISub, intType(), stackIdxVal, oneConst);\n\n      // prevStateVal = stack[stackIndexVal]\n      auto prevStackPtr = createAccessChain(spv::StorageClass::StorageClassPrivate, stackVar(), { prevStackIdxVal });\n      addName(prevStackPtr, \"prevStackPtr\");\n      auto prevStateVal = createLoad(prevStackPtr, spv::NoPrecision);\n      addName(prevStateVal, \"prevStackVal\");\n\n      // if (prevStackVal == Active) {\n      auto isActive = createBinOp(spv::OpIEqual, boolType(),\n                                  prevStateVal,\n                                  stateActive());\n      auto ifBuilder = spv::Builder::If { isActive, spv::SelectionControlMaskNone, *this };\n\n      // state = *stateVar\n      auto stateVal = createLoad(stateVar(), spv::NoPrecision);\n      addName(stateVal, \"state\");\n\n      // newState = (state == Active) ? InactiveBreak : Active\n      auto pred = createBinOp(spv::OpIEqual, boolType(), stateVal, stateActive());\n      auto newState = createTriOp(spv::OpSelect, intType(), pred, stateInactive(), stateActive());\n\n      // *stateVar = newState\n      createStore(newState, stateVar());\n\n      // }\n      ifBuilder.makeEndIf();\n   }\n\n   void pushStack()\n   {\n      // stackIndexVal = *stackIndexVar\n      auto stackIdxVal = createLoad(stackIndexVar(), spv::NoPrecision);\n      addName(stackIdxVal, \"stackIdx\");\n\n      // state = *stateVar\n      auto stateVal = createLoad(stateVar(), spv::NoPrecision);\n      addName(stateVal, \"state\");\n\n      // stack[stackIndexVal] = stateVal\n      auto stackPtr = createAccessChain(spv::StorageClass::StorageClassPrivate, stackVar(), { stackIdxVal });\n      addName(stackPtr, \"stackPtr\");\n      createStore(stateVal, stackPtr);\n\n      // stackIndexVal += 1\n      auto constPushCount = makeIntConstant(1);\n      auto newStackIdxVal = createBinOp(spv::Op::OpIAdd, intType(), stackIdxVal, constPushCount);\n      this->addName(newStackIdxVal, \"newStackIdx\");\n\n      // *stackIndexVar = stackIndexVal\n      createStore(newStackIdxVal, stackIndexVar());\n   }\n\n   void popStack(int popCount)\n   {\n      // stateIdxVal = *stackIndexVar\n      auto stackIdxVal = createLoad(stackIndexVar(), spv::NoPrecision);\n      addName(stackIdxVal, \"stackIdx\");\n\n      if (popCount > 0) {\n         // stateIdxVal -= {popCount}\n         auto constPopCount = makeIntConstant(popCount);\n         auto newStackIdxVal = createBinOp(spv::Op::OpISub, intType(), stackIdxVal, constPopCount);\n         addName(newStackIdxVal, \"newStackIdx\");\n\n         // *stackIndexVar = stateIdxVal\n         createStore(newStackIdxVal, stackIndexVar());\n         stackIdxVal = newStackIdxVal;\n      }\n\n      // newStateVal = stack[stackIndexVal]\n      auto stackPtr = createAccessChain(spv::StorageClass::StorageClassPrivate, stackVar(), { stackIdxVal });\n      addName(stackPtr, \"stackPtr\");\n      auto newStateVal = createLoad(stackPtr, spv::NoPrecision);\n      addName(newStateVal, \"newState\");\n\n      // *stateVar = newStateVal\n      createStore(newStateVal, stateVar());\n   }\n\n   spv::Block *startCfCondBlock(latte::SQ_CF_COND cond = latte::SQ_CF_COND::ACTIVE,\n                                uint32_t condConst = 0)\n   {\n      // TODO: Support other cond types\n      // Requires implementing the CF_CONST registers\n      decaf_check(cond == latte::SQ_CF_COND::ACTIVE);\n\n      auto insideBlock = &makeNewBlock();\n      auto afterBlock = &makeNewBlock();\n\n      auto state = createLoad(stateVar(), spv::NoPrecision);\n      auto pred = createBinOp(spv::OpIEqual, boolType(), state, stateActive());\n\n      createSelectionMerge(afterBlock, spv::SelectionControlMaskNone);\n      createConditionalBranch(pred, insideBlock, afterBlock);\n\n      setBuildPoint(insideBlock);\n      return afterBlock;\n   }\n\n   void endCfCondBlock(spv::Block *afterBlock, bool blockReturned = false)\n   {\n      if (!blockReturned) {\n         createBranch(afterBlock);\n      }\n      setBuildPoint(afterBlock);\n   }\n\n   spv::Id readChanSel(spv::Id input, latte::SQ_CHAN chan)\n   {\n      decaf_check(chan != latte::SQ_CHAN::T);\n      return createCompositeExtract(input, floatType(), { static_cast<unsigned int>(chan) });\n   }\n\n   spv::Id getGprRefGprIndex(const latte::GprRef &gpr)\n   {\n      spv::Id regIdx;\n\n      // regIdx = {gprNumber}\n      auto constGprNum = makeUintConstant(gpr.number);\n      regIdx = constGprNum;\n\n      switch (gpr.indexMode) {\n      case latte::GprIndexMode::None:\n      {\n         // for R[{gprNumber}]\n         break;\n      }\n      case latte::GprIndexMode::AR_X:\n      {\n         // for R[{gprNumber} + AR.x]\n\n         // ARx = $AR.x\n         auto ARxVal = getArId(SQ_CHAN::X);\n\n         // regIdx = regIdx + ARx\n         auto gprNumARx = createBinOp(spv::Op::OpIAdd, uintType(), regIdx, ARxVal);\n         addName(gprNumARx, \"gprNum\");\n         regIdx = gprNumARx;\n\n         break;\n      }\n      case latte::GprIndexMode::AL:\n      {\n         // for R[{gprNumber} + AL]\n\n         // AL = *ALVar\n         auto ALVal = createLoad(ALVar(), spv::NoPrecision);\n         addName(ALVal, \"AL\");\n\n         // regIdx = regIdx + AL\n         regIdx = createBinOp(spv::Op::OpIAdd, uintType(), regIdx, ALVal);\n         break;\n      }\n      default:\n         decaf_abort(\"Unexpected GPR index mode\");\n      }\n\n      return regIdx;\n   }\n\n   spv::Id getGprRef(const latte::GprRef &gpr)\n   {\n      auto regIdx = getGprRefGprIndex(gpr);\n\n      // $ = &R[regIdx]\n      return createAccessChain(spv::StorageClass::StorageClassPrivate, gprVar(), { regIdx });\n   }\n\n   spv::Id readGprChanRef(const latte::GprChanRef &ref)\n   {\n      auto regIdxVal = getGprRefGprIndex(ref.gpr);\n\n      decaf_check(ref.chan != latte::SQ_CHAN::T);\n      auto chanConst = makeUintConstant(static_cast<unsigned int>(ref.chan));\n\n      // $ = R[regIdx].{refChan}\n      auto gprChanPtr = createAccessChain(spv::StorageClass::StorageClassPrivate, gprVar(), { regIdxVal, chanConst });\n      return createLoad(gprChanPtr, spv::NoPrecision);\n   }\n\n   spv::Id readGprSelRef(const latte::GprSelRef &ref)\n   {\n      switch (ref.sel) {\n      case latte::SQ_SEL::SEL_X:\n         return readGprChanRef(GprChanRef { ref.gpr, latte::SQ_CHAN::X });\n      case latte::SQ_SEL::SEL_Y:\n         return readGprChanRef(GprChanRef { ref.gpr, latte::SQ_CHAN::Y });\n      case latte::SQ_SEL::SEL_Z:\n         return readGprChanRef(GprChanRef { ref.gpr, latte::SQ_CHAN::Z });\n      case latte::SQ_SEL::SEL_W:\n         return readGprChanRef(GprChanRef { ref.gpr, latte::SQ_CHAN::W });\n      case latte::SQ_SEL::SEL_0:\n         return makeFloatConstant(0.0f);\n      case latte::SQ_SEL::SEL_1:\n         return makeFloatConstant(1.0f);\n      default:\n         decaf_abort(\"Unexpected SQ_SEL in gpr sel ref\");\n      }\n   }\n\n   spv::Id readGprMaskRef(const latte::GprMaskRef &ref)\n   {\n      // Read the source GPR\n      auto srcGprPtr = getGprRef(ref.gpr);\n      auto srcGprVal = createLoad(srcGprPtr, spv::NoPrecision);\n\n      // Apply the source GPR swizzling\n      decaf_check(isSwizzleFullyUnmasked(ref.mask));\n      return applySelMask(spv::NoResult, srcGprVal, ref.mask);\n   }\n\n   spv::Id getCbufferRefIndex(const latte::CbufferRef &ref)\n   {\n      auto indexVal = makeIntConstant(ref.index);\n\n      switch (ref.indexMode) {\n      case latte::CbufferIndexMode::None:\n         // This is the default mode...\n         break;\n      case latte::CbufferIndexMode::AL:\n         indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, createLoad(ALVar(), spv::NoPrecision));\n         break;\n      default:\n         decaf_abort(\"Unexpected cfile index mode\");\n      }\n\n      return indexVal;\n   }\n\n   spv::Id getCbufferRef(const latte::CbufferRef &ref)\n   {\n      auto thisCbufVar = cbufferVar(ref.bufferId);\n      auto cbufIdxVal = getCbufferRefIndex(ref);\n\n      // $ = &CBUFFER{bufferindex}[index]\n      auto zeroConst = makeUintConstant(0);\n      return createAccessChain(spv::StorageClass::StorageClassUniform, thisCbufVar, { zeroConst, cbufIdxVal });\n   }\n\n   spv::Id readCbufferChanRef(const latte::CbufferChanRef &ref)\n   {\n      auto thisCbufVar = cbufferVar(ref.cbuffer.bufferId);\n      auto cbufIdxVal = getCbufferRefIndex(ref.cbuffer);\n\n      decaf_check(ref.chan != latte::SQ_CHAN::T);\n      auto chanConst = makeUintConstant(static_cast<unsigned int>(ref.chan));\n\n      // $ = CBUFFER{bufferindex}[index].{refChan}\n      auto zeroConst = makeUintConstant(0);\n      auto cfileChanPtr = createAccessChain(spv::StorageClass::StorageClassUniform, thisCbufVar, { zeroConst, cbufIdxVal, chanConst });\n      return createLoad(cfileChanPtr, spv::NoPrecision);\n   }\n\n   spv::Id getCfileRefIndex(const latte::CfileRef &ref)\n   {\n      auto indexVal = makeIntConstant(ref.index);\n\n      switch (ref.indexMode) {\n      case latte::CfileIndexMode::None:\n         // This is the default mode...\n         break;\n      case latte::CfileIndexMode::AR_X:\n         indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, getArId(SQ_CHAN::X));\n         break;\n      case latte::CfileIndexMode::AR_Y:\n         indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, getArId(SQ_CHAN::Y));\n         break;\n      case latte::CfileIndexMode::AR_Z:\n         indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, getArId(SQ_CHAN::Z));\n         break;\n      case latte::CfileIndexMode::AR_W:\n         indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, getArId(SQ_CHAN::W));\n         break;\n      case latte::CfileIndexMode::AL:\n         indexVal = createBinOp(spv::OpIAdd, intType(), indexVal, createLoad(ALVar(), spv::NoPrecision));\n         break;\n      default:\n         decaf_abort(\"Unexpected cfile index mode\");\n      }\n\n      return indexVal;\n   }\n\n   spv::Id getCfileRef(const latte::CfileRef &ref)\n   {\n      auto cfileIdxVal = getCfileRefIndex(ref);\n\n      // $ = &CFILE[cfileIdx]\n      auto zeroConst = makeUintConstant(0);\n      return createAccessChain(spv::StorageClass::StorageClassUniform, cfileVar(), { zeroConst, cfileIdxVal });\n   }\n\n   spv::Id readCfileChanRef(const latte::CfileChanRef &ref)\n   {\n      auto cfileIdxVal = getCfileRefIndex(ref.cfile);\n\n      decaf_check(ref.chan != latte::SQ_CHAN::T);\n      auto chanConst = makeUintConstant(static_cast<unsigned int>(ref.chan));\n\n      // $ = CFILE[index].{refChan}\n      auto zeroConst = makeUintConstant(0);\n      auto cfileChanPtr = createAccessChain(spv::StorageClass::StorageClassUniform, cfileVar(), { zeroConst, cfileIdxVal, chanConst });\n      return createLoad(cfileChanPtr, spv::NoPrecision);\n   }\n\n   spv::Id readSrcVarRef(const SrcVarRef& srcRef)\n   {\n      spv::Id srcId;\n\n      if (srcRef.type == latte::SrcVarRef::Type::GPR) {\n         srcId = readGprChanRef(srcRef.gprChan);\n      } else if (srcRef.type == latte::SrcVarRef::Type::CBUFFER) {\n         srcId = readCbufferChanRef(srcRef.cbufferChan);\n      } else if (srcRef.type == latte::SrcVarRef::Type::CFILE) {\n         srcId = readCfileChanRef(srcRef.cfileChan);\n      } else if (srcRef.type == latte::SrcVarRef::Type::PREVRES) {\n         decaf_check(srcRef.prevres.unit >= latte::SQ_CHAN::X);\n         decaf_check(srcRef.prevres.unit <= latte::SQ_CHAN::T);\n         srcId = getPvId(srcRef.prevres.unit);\n      } else if (srcRef.type == latte::SrcVarRef::Type::VALUE) {\n         if (srcRef.valueType == latte::VarRefType::FLOAT) {\n            // We write floats as UINT's which are bitcast to preserve precision\n            // in the case that the value will be used as a uint.\n            auto srcFloatData = makeUintConstant(srcRef.value.uintValue);\n            srcId = createUnaryOp(spv::OpBitcast, floatType(), srcFloatData);\n         } else if (srcRef.valueType == latte::VarRefType::INT) {\n            srcId = makeIntConstant(srcRef.value.intValue);\n         } else if (srcRef.valueType == latte::VarRefType::UINT) {\n            srcId = makeUintConstant(srcRef.value.uintValue);\n         } else {\n            decaf_abort(\"Unexpected source value type\");\n         }\n      } else {\n         decaf_abort(\"Unexpected source var type\");\n      }\n\n      if (srcRef.valueType == latte::VarRefType::FLOAT) {\n         // We are naturally a float for SPIRV conversion\n      } else if (srcRef.valueType == latte::VarRefType::INT) {\n         srcId = createUnaryOp(spv::Op::OpBitcast, intType(), srcId);\n      } else if (srcRef.valueType == latte::VarRefType::UINT) {\n         srcId = createUnaryOp(spv::Op::OpBitcast, uintType(), srcId);\n      } else {\n         decaf_abort(\"Unexpected source value type\");\n      }\n\n      if (srcRef.isAbsolute) {\n         if (getTypeId(srcId) == intType()) {\n            srcId = createBuiltinCall(intType(), glslStd450(), GLSLstd450::GLSLstd450SAbs, { srcId });\n         } else if (getTypeId(srcId) == floatType()) {\n            srcId = createBuiltinCall(floatType(), glslStd450(), GLSLstd450::GLSLstd450FAbs, { srcId });\n         } else {\n            decaf_abort(\"unsupported source type for absolution\");\n         }\n      }\n\n      if (srcRef.isNegated) {\n         if (getTypeId(srcId) == intType()) {\n            srcId = createUnaryOp(spv::Op::OpSNegate, intType(), srcId);\n         } else if (getTypeId(srcId) == floatType()) {\n            srcId = createUnaryOp(spv::Op::OpFNegate, floatType(), srcId);\n         } else {\n            decaf_abort(\"unsupported source type for negation\");\n         }\n      }\n\n      return srcId;\n   }\n\n   spv::Id readAluInstSrc(const latte::ControlFlowInst &cf, const latte::AluInstructionGroup &group, const latte::AluInst &inst,\n                      uint32_t srcIndex, latte::VarRefType valueType = latte::VarRefType::FLOAT)\n   {\n      auto srcRef = makeSrcVar(cf, group, inst, srcIndex, valueType);\n      return readSrcVarRef(srcRef);\n   }\n\n   spv::Id readAluReducSrc(const latte::ControlFlowInst &cf, const latte::AluInstructionGroup &group,\n                           uint32_t srcIndex, latte::VarRefType valueType = latte::VarRefType::FLOAT)\n   {\n      spv::Id resultType;\n      if (valueType == VarRefType::FLOAT) {\n         resultType = float4Type();\n      } else if (valueType == VarRefType::UINT) {\n         resultType = uint4Type();\n      } else if (valueType == VarRefType::INT) {\n         resultType = int4Type();\n      } else {\n         decaf_abort(\"Unexpected value type\");\n      }\n\n      auto srcX = makeSrcVar(cf, group, *group.units[SQ_CHAN::X], srcIndex, valueType);\n      auto srcY = makeSrcVar(cf, group, *group.units[SQ_CHAN::Y], srcIndex, valueType);\n      auto srcZ = makeSrcVar(cf, group, *group.units[SQ_CHAN::Z], srcIndex, valueType);\n      auto srcW = makeSrcVar(cf, group, *group.units[SQ_CHAN::W], srcIndex, valueType);\n\n      // In order to improve our shader debugging experience, if we are simply\n      // fetching the entirety of a register, lets fetch it all at once, instead\n      // of doing it component by component...  There is actually an opportunity\n      // to allow swizzling here too.  Thats probably unneccessary though.\n      if (srcX.type == SrcVarRef::Type::GPR &&\n          srcY.type == SrcVarRef::Type::GPR &&\n          srcZ.type == SrcVarRef::Type::GPR &&\n          srcW.type == SrcVarRef::Type::GPR &&\n          srcX.gprChan.chan == latte::SQ_CHAN::X &&\n          srcY.gprChan.chan == latte::SQ_CHAN::Y &&\n          srcZ.gprChan.chan == latte::SQ_CHAN::Z &&\n          srcW.gprChan.chan == latte::SQ_CHAN::W &&\n          (srcX.gprChan.gpr.number == srcY.gprChan.gpr.number) &&\n          (srcY.gprChan.gpr.number == srcZ.gprChan.gpr.number) &&\n          (srcZ.gprChan.gpr.number == srcW.gprChan.gpr.number) &&\n          (srcX.gprChan.gpr.indexMode == srcY.gprChan.gpr.indexMode) &&\n          (srcY.gprChan.gpr.indexMode == srcZ.gprChan.gpr.indexMode) &&\n          (srcZ.gprChan.gpr.indexMode == srcW.gprChan.gpr.indexMode))\n      {\n         auto srcRegPtr = getGprRef(srcX.gprChan.gpr);\n         return createLoad(srcRegPtr, spv::NoPrecision);\n      }\n\n      if (srcX.type == SrcVarRef::Type::CFILE &&\n          srcY.type == SrcVarRef::Type::CFILE &&\n          srcZ.type == SrcVarRef::Type::CFILE &&\n          srcW.type == SrcVarRef::Type::CFILE &&\n          srcX.cfileChan.chan == latte::SQ_CHAN::X &&\n          srcY.cfileChan.chan == latte::SQ_CHAN::Y &&\n          srcZ.cfileChan.chan == latte::SQ_CHAN::Z &&\n          srcW.cfileChan.chan == latte::SQ_CHAN::W &&\n          srcX.cfileChan.cfile.index == srcY.cfileChan.cfile.index &&\n          srcY.cfileChan.cfile.index == srcZ.cfileChan.cfile.index &&\n          srcZ.cfileChan.cfile.index == srcW.cfileChan.cfile.index &&\n          srcX.cfileChan.cfile.indexMode == srcY.cfileChan.cfile.indexMode &&\n          srcY.cfileChan.cfile.indexMode == srcZ.cfileChan.cfile.indexMode &&\n          srcZ.cfileChan.cfile.indexMode == srcW.cfileChan.cfile.indexMode)\n      {\n         auto srcCfilePtr = getCfileRef(srcX.cfileChan.cfile);\n         return createLoad(srcCfilePtr, spv::NoPrecision);\n      }\n\n      if (srcX.type == SrcVarRef::Type::CBUFFER &&\n          srcY.type == SrcVarRef::Type::CBUFFER &&\n          srcZ.type == SrcVarRef::Type::CBUFFER &&\n          srcW.type == SrcVarRef::Type::CBUFFER &&\n          srcX.cbufferChan.chan == latte::SQ_CHAN::X &&\n          srcY.cbufferChan.chan == latte::SQ_CHAN::Y &&\n          srcZ.cbufferChan.chan == latte::SQ_CHAN::Z &&\n          srcW.cbufferChan.chan == latte::SQ_CHAN::W &&\n          srcX.cbufferChan.cbuffer.bufferId == srcY.cbufferChan.cbuffer.bufferId &&\n          srcY.cbufferChan.cbuffer.bufferId == srcZ.cbufferChan.cbuffer.bufferId &&\n          srcZ.cbufferChan.cbuffer.bufferId == srcW.cbufferChan.cbuffer.bufferId &&\n          srcX.cbufferChan.cbuffer.index == srcY.cbufferChan.cbuffer.index &&\n          srcY.cbufferChan.cbuffer.index == srcZ.cbufferChan.cbuffer.index &&\n          srcZ.cbufferChan.cbuffer.index == srcW.cbufferChan.cbuffer.index &&\n          srcX.cbufferChan.cbuffer.indexMode == srcY.cbufferChan.cbuffer.indexMode &&\n          srcY.cbufferChan.cbuffer.indexMode == srcZ.cbufferChan.cbuffer.indexMode &&\n          srcZ.cbufferChan.cbuffer.indexMode == srcW.cbufferChan.cbuffer.indexMode) {\n         auto srcCbufferPtr = getCbufferRef(srcX.cbufferChan.cbuffer);\n         return createLoad(srcCbufferPtr, spv::NoPrecision);\n      }\n\n      return createCompositeConstruct(resultType, {\n            readSrcVarRef(srcX),\n            readSrcVarRef(srcY),\n            readSrcVarRef(srcZ),\n            readSrcVarRef(srcW) });\n   }\n\n   void writeGprChanRef(const GprChanRef& ref, spv::Id srcId)\n   {\n      auto regIdx = getGprRefGprIndex(ref.gpr);\n\n      decaf_check(ref.chan != latte::SQ_CHAN::T);\n      auto chanConst = makeUintConstant(static_cast<unsigned int>(ref.chan));\n\n      // $ = R[regIdx].{refChan}\n      auto gprChanPtr = createAccessChain(spv::StorageClass::StorageClassPrivate, gprVar(), { regIdx, chanConst });\n      createStore(srcId, gprChanPtr);\n   }\n\n   void writeGprMaskRef(const latte::GprMaskRef &ref, spv::Id srcId)\n   {\n      // Perform any neccessary type conversions\n      auto srcTypeId = getTypeId(srcId);\n      if (srcTypeId == float4Type()) {\n         // Nothing to do, we are already a float!\n      } else if (srcTypeId == int4Type()) {\n         srcId = createUnaryOp(spv::OpBitcast, float4Type(), srcId);\n      } else if (srcTypeId == uint4Type()) {\n         srcId = createUnaryOp(spv::OpBitcast, float4Type(), srcId);\n      } else {\n         decaf_abort(\"Unexpected type at gpr masked instruction write\");\n      }\n\n      // Grab a reference to our GPR\n      auto gprRef = getGprRef(ref.gpr);\n\n      // We must put the srcId here, since some swizzles will just be rearranging\n      auto writeVal = srcId;\n\n      // If the swizzle masks anything, we need to load the original value\n      // of the export so that we can preserve that data not being written.\n      if (!isSwizzleFullyUnmasked(ref.mask)) {\n         writeVal = createLoad(gprRef, spv::NoPrecision);\n      }\n\n      writeVal = applySelMask(writeVal, srcId, ref.mask);\n\n      createStore(writeVal, gprRef);\n   }\n\n   spv::Id shrinkVector(spv::Id value, uint32_t maxComponents)\n   {\n      // We only support 4-component vector types here...\n      decaf_check(getNumComponents(value) == 4);\n\n      // Figure out what type we need to return\n      auto valueType = getTypeId(value);\n      auto valueBaseType = this->getContainedTypeId(valueType);\n\n      if (maxComponents == 1) {\n         return createOp(spv::OpCompositeExtract, valueBaseType, { value, 0 });\n      } else if (maxComponents == 2) {\n         return createOp(spv::OpVectorShuffle, vecType(valueBaseType, 2), { value, value, 0, 1 });\n      } else if (maxComponents == 3) {\n         return createOp(spv::OpVectorShuffle, vecType(valueBaseType, 3), { value, value, 0, 1, 2 });\n      } else if (maxComponents == 4) {\n         return value;\n      } else {\n         decaf_abort(\"Unexpected component count during vector shrink\");\n      }\n   }\n\n   spv::Id applySelMask(spv::Id dest, spv::Id src, std::array<SQ_SEL, 4> mask, uint32_t maxComponents = 4)\n   {\n      // We only support doing masking against 4-component vectors.\n      decaf_check(getNumComponents(src) == 4);\n\n      auto sourceType = getTypeId(src);\n      auto sourceBaseType = this->getContainedTypeId(sourceType);\n\n      // For simplicity in the checking below, we set the unused mask values\n      // to the defaults that we might expect otherwise.\n      for (auto i = maxComponents; i < 4; ++i) {\n         mask[i] = static_cast<latte::SQ_SEL>(latte::SQ_SEL::SEL_X + i);\n      }\n\n      // If the swizzle is just XYZW, we don't actually need to do anything...\n      bool isNoop = true;\n      isNoop &= (mask[0] == latte::SQ_SEL::SEL_X);\n      isNoop &= (mask[1] == latte::SQ_SEL::SEL_Y);\n      isNoop &= (mask[2] == latte::SQ_SEL::SEL_Z);\n      isNoop &= (mask[3] == latte::SQ_SEL::SEL_W);\n      if (isNoop) {\n         return shrinkVector(src, maxComponents);\n      }\n\n      // If the swizzle is ____, we should just skip processing entirely\n      bool isMasked = true;\n      isMasked &= (mask[0] == latte::SQ_SEL::SEL_MASK);\n      isMasked &= (mask[1] == latte::SQ_SEL::SEL_MASK);\n      isMasked &= (mask[2] == latte::SQ_SEL::SEL_MASK);\n      isMasked &= (mask[3] == latte::SQ_SEL::SEL_MASK);\n      if (isMasked) {\n         decaf_check(dest != spv::NoResult);\n         return shrinkVector(dest, maxComponents);\n      }\n\n      // Lets see if this swizzle is using constants at all, since we can optimize\n      // away the need for an intermediary vector\n      bool usesConstants = false;\n      for (auto selIdx = 0; selIdx < 4; ++selIdx) {\n         switch (mask[selIdx]) {\n         case latte::SQ_SEL::SEL_X:\n            break;\n         case latte::SQ_SEL::SEL_Y:\n            break;\n         case latte::SQ_SEL::SEL_Z:\n            break;\n         case latte::SQ_SEL::SEL_W:\n            break;\n         case latte::SQ_SEL::SEL_0:\n            usesConstants = true;\n            break;\n         case latte::SQ_SEL::SEL_1:\n            usesConstants = true;\n            break;\n         case latte::SQ_SEL::SEL_MASK:\n            break;\n         default:\n            decaf_abort(\"Unexpected selector during masking operation.\");\n         }\n      }\n\n      if (!usesConstants) {\n         // If the swizzle isn't using constants, we can just directly shuffle\n         // between the two input vectors in a single go.\n\n         std::array<unsigned int, 4> shuffleIdx = { 0, 1, 2, 3 };\n         for (auto selIdx = 0u; selIdx < maxComponents; ++selIdx) {\n            switch (mask[selIdx]) {\n            case latte::SQ_SEL::SEL_X:\n               shuffleIdx[selIdx] = 4;\n               break;\n            case latte::SQ_SEL::SEL_Y:\n               shuffleIdx[selIdx] = 5;\n               break;\n            case latte::SQ_SEL::SEL_Z:\n               shuffleIdx[selIdx] = 6;\n               break;\n            case latte::SQ_SEL::SEL_W:\n               shuffleIdx[selIdx] = 7;\n               break;\n            case latte::SQ_SEL::SEL_MASK:\n               break;\n            default:\n               decaf_abort(\"Unexpected selector during masking operation.\");\n            }\n         }\n\n         if (dest == spv::NoResult) {\n            decaf_check(isSwizzleFullyUnmasked(mask));\n            dest = src;\n         }\n\n         if (maxComponents == 1) {\n            return createOp(spv::OpCompositeExtract, sourceBaseType, { src, shuffleIdx[0] });\n         } else if (maxComponents == 2) {\n            return createOp(spv::Op::OpVectorShuffle, vecType(sourceBaseType, 2), { dest, src, shuffleIdx[0], shuffleIdx[1] });\n         } else if (maxComponents == 2) {\n            return createOp(spv::Op::OpVectorShuffle, vecType(sourceBaseType, 3), { dest, src, shuffleIdx[0], shuffleIdx[1], shuffleIdx[2] });\n         } else if (maxComponents == 4) {\n            return createOp(spv::Op::OpVectorShuffle, vecType(sourceBaseType, 4), { dest, src, shuffleIdx[0], shuffleIdx[1], shuffleIdx[2], shuffleIdx[3] });\n         } else {\n            decaf_abort(\"Unexpected component count during swizzle\");\n         }\n      }\n\n      // If the swizzle is using constants, we need to pull out the individual pieces\n      // and then build a new vector with all the values\n\n      std::array<spv::Id, 4> resultElems = { spv::NoResult };\n\n      // Because its possible to swizzle the same source channel into multiple places\n      // of the destination, we keep a cache of the ones we've already extracted.\n      std::array<spv::Id, 4> sourceElems = { spv::NoResult };\n      auto fetchSrcElem = [&](unsigned int elemIdx)\n      {\n         auto& elem = sourceElems[elemIdx];\n         if (!elem) {\n            elem = createOp(spv::OpCompositeExtract, floatType(), { src, elemIdx });\n         }\n         return elem;\n      };\n\n      for (auto selIdx = 0u; selIdx < maxComponents; ++selIdx) {\n         switch (mask[selIdx]) {\n         case latte::SQ_SEL::SEL_X:\n            resultElems[selIdx] = fetchSrcElem(0);\n            break;\n         case latte::SQ_SEL::SEL_Y:\n            resultElems[selIdx] = fetchSrcElem(1);\n            break;\n         case latte::SQ_SEL::SEL_Z:\n            resultElems[selIdx] = fetchSrcElem(2);\n            break;\n         case latte::SQ_SEL::SEL_W:\n            resultElems[selIdx] = fetchSrcElem(3);\n            break;\n         case latte::SQ_SEL::SEL_0:\n            resultElems[selIdx] = makeFloatConstant(0.0f);\n            break;\n         case latte::SQ_SEL::SEL_1:\n            resultElems[selIdx] = makeFloatConstant(1.0f);\n            break;\n         case latte::SQ_SEL::SEL_MASK:\n            resultElems[selIdx] = createOp(spv::OpCompositeExtract, floatType(), { dest, selIdx });\n            break;\n         default:\n            decaf_abort(\"Unexpected selector during masking operation.\");\n         }\n      }\n\n      if (maxComponents == 1) {\n         return resultElems[0];\n      } else if (maxComponents == 2) {\n         return createOp(spv::OpCompositeConstruct, vecType(sourceBaseType, 2), { resultElems[0], resultElems[1] });\n      } else if (maxComponents == 2) {\n         return createOp(spv::OpCompositeConstruct, vecType(sourceBaseType, 3), { resultElems[0], resultElems[1], resultElems[2] });\n      } else if (maxComponents == 4) {\n         return createOp(spv::OpCompositeConstruct, vecType(sourceBaseType, 4), { resultElems[0], resultElems[1], resultElems[2], resultElems[3] });\n      } else {\n         decaf_abort(\"Unexpected component count during swizzle result construct\");\n      }\n   }\n\n   void writeAluOpDest(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst, spv::Id srcId, bool forAr = false)\n   {\n      if (inst.word1.ENCODING() == SQ_ALU_ENCODING::OP2) {\n         switch (inst.op2.OMOD()) {\n         case SQ_ALU_OMOD::D2:\n            decaf_check(getTypeId(srcId) == floatType());\n            srcId = createBinOp(spv::Op::OpFMul, floatType(), srcId, makeFloatConstant(0.5f));\n            break;\n         case SQ_ALU_OMOD::M2:\n            decaf_check(getTypeId(srcId) == floatType());\n            srcId = createBinOp(spv::Op::OpFMul, floatType(), srcId, makeFloatConstant(2));\n            break;\n         case SQ_ALU_OMOD::M4:\n            decaf_check(getTypeId(srcId) == floatType());\n            srcId = createBinOp(spv::Op::OpFMul, floatType(), srcId, makeFloatConstant(4));\n            break;\n         case SQ_ALU_OMOD::OFF:\n            // Nothing to do\n            break;\n         default:\n            decaf_abort(\"Unexpected dest var OMOD\");\n         }\n      }\n\n      if (inst.word1.CLAMP()) {\n         decaf_check(getTypeId(srcId) == floatType());\n         srcId = createBuiltinCall(floatType(), glslStd450(), GLSLstd450::GLSLstd450FClamp, { srcId, makeFloatConstant(0), makeFloatConstant(1) });\n      }\n\n      if (forAr) {\n         // Instruction is responsible for dispatching the result as a\n         //  UINT automatically, so we can safely directly store it\n         //  as AR is typed as UINT in the shader.\n      } else {\n         // Instruction returns the value in whatever type is intended\n         //  by the instruction.  We use meta-data to translate that\n         //  type to the float type used for storage in shader.\n         auto flags = getInstructionFlags(inst);\n         if (flags & SQ_ALU_FLAG_INT_OUT) {\n            decaf_check(getTypeId(srcId) == intType());\n         } else if (flags & SQ_ALU_FLAG_UINT_OUT) {\n            decaf_check(getTypeId(srcId) == uintType());\n         }\n\n         auto srcTypeId = getTypeId(srcId);\n         if (srcTypeId == floatType()) {\n            // Nothing to do, we are already a float!\n         } else if (srcTypeId == intType()) {\n            srcId = createUnaryOp(spv::OpBitcast, floatType(), srcId);\n         } else if (srcTypeId == uintType()) {\n            srcId = createUnaryOp(spv::OpBitcast, floatType(), srcId);\n         } else {\n            decaf_abort(\"Unexpected type at ALU instruction write\");\n         }\n      }\n\n      // According to the documentation, AR/PV writes are only valid for their\n      // respective instructions, and other accesses are illegal...\n      if (forAr) {\n         decaf_check(unit != latte::SQ_CHAN::T);\n\n         mARId[unit] = srcId;\n      } else {\n         mNextPrevResId[unit] = srcId;\n      }\n\n      if (inst.word1.ENCODING() != SQ_ALU_ENCODING::OP2 || inst.op2.WRITE_MASK()) {\n         // According to the docs, GPR writes are undefined for AR instructions\n         // though its worth noting that it does not actually say its illegal.\n         decaf_check(!forAr);\n\n         GprChanRef destGpr;\n         destGpr.gpr = makeGprRef(inst.word1.DST_GPR(), inst.word1.DST_REL(), inst.word0.INDEX_MODE());\n         destGpr.chan = inst.word1.DST_CHAN();\n         mAluGroupWrites.push_back({ destGpr, srcId });\n      }\n   }\n\n   void flushAluGroupWrites()\n   {\n      for (auto& write : mAluGroupWrites) {\n         writeGprChanRef(write.first, write.second);\n      }\n      mAluGroupWrites.clear();\n   }\n\n   void writeAluReducDest(const ControlFlowInst &cf, const AluInstructionGroup &group, spv::Id srcId, bool forAr = false)\n   {\n      // Reduction instructions occupy XYZW, but T is free for other operations.\n      // Note that we intentionally select a default output unit of X here, this\n      // ensures that if there is no specifically chosen unit, that this will mean\n      // that X implicitly had its write-mask disabled, and the writeAluOpDest\n      // function will elide the register write.  Note that no matter which unit\n      // is writing, the result always goes to PV.x\n      auto outputUnit = SQ_CHAN::X;\n      for (auto i = 0u; i < 4u; ++i) {\n         if (group.units[i]->op2.WRITE_MASK()) {\n            outputUnit = static_cast<SQ_CHAN>(i);\n            break;\n         }\n      }\n\n      writeAluOpDest(cf, group, SQ_CHAN::X, *group.units[outputUnit], srcId, forAr);\n   }\n\n   void updatePredicateAndExecuteMask(const ControlFlowInst &cf, const AluInst &inst, spv::Id pred)\n   {\n      if (inst.op2.UPDATE_PRED()) {\n         createStore(pred, predicateVar());\n      }\n\n      if (inst.op2.UPDATE_EXECUTE_MASK()) {\n         createStore(createTriOp(spv::Op::OpSelect, intType(), pred,\n                                 stateActive(), stateInactive()),\n                     stateVar());\n      }\n   }\n\n   spv::Id getExportRefVar(const ExportRef& ref, spv::Id dataType)\n   {\n      if (ref.type == ExportRef::Type::Position) {\n         decaf_check(dataType == floatType());\n         decaf_check(ref.dataStride == 0);\n         decaf_check(ref.arraySize == 1);\n         decaf_check(ref.elemCount == 1);\n         decaf_check(ref.indexGpr == -1);\n         return posExportVar(ref.arrayBase);\n      } else if (ref.type == ExportRef::Type::Param) {\n         decaf_check(dataType == floatType());\n         decaf_check(ref.dataStride == 0);\n         decaf_check(ref.arraySize == 1);\n         decaf_check(ref.elemCount == 1);\n         decaf_check(ref.indexGpr == -1);\n         return paramExportVar(ref.arrayBase);\n      } else if (ref.type == ExportRef::Type::Pixel) {\n         decaf_check(ref.dataStride == 0);\n         decaf_check(ref.arraySize == 1);\n         decaf_check(ref.elemCount == 1);\n         decaf_check(ref.indexGpr == -1);\n         return pixelExportVar(ref.arrayBase, dataType);\n      } else if (ref.type == ExportRef::Type::ComputedZ) {\n         decaf_check(dataType == floatType());\n         decaf_check(ref.dataStride == 0);\n         decaf_check(ref.arrayBase == 0);\n         decaf_check(ref.arraySize == 1);\n         decaf_check(ref.elemCount == 1);\n         decaf_check(ref.indexGpr == -1);\n         return zExportVar();\n      } else if (ref.type >= ExportRef::Type::Stream0Write &&\n                 ref.type <= ExportRef::Type::Stream3Write) {\n         decaf_check(dataType == floatType());\n         auto streamIdx =\n            static_cast<uint32_t>(ref.type) -\n            static_cast<uint32_t>(ExportRef::Type::Stream0Write);\n         return memExportWriteVar(streamIdx, ref.dataStride, ref.indexGpr, ref.arrayBase, ref.arraySize, ref.elemCount);\n      } else if (ref.type == ExportRef::Type::VsGsRingWrite) {\n         decaf_check(dataType == floatType());\n         decaf_check(ref.dataStride == 0);\n         return vsGsRingExportWriteVar(ref.indexGpr, ref.arrayBase, ref.arraySize, ref.elemCount);\n      } else if (ref.type == ExportRef::Type::GsDcRingWrite) {\n         decaf_check(dataType == floatType());\n         decaf_check(ref.dataStride == 0);\n         return gsDcRingExportWriteVar(ref.indexGpr, ref.arrayBase, ref.arraySize, ref.elemCount);\n      } else {\n         decaf_abort(\"Encountered unexpected export type\");\n      }\n   }\n\n   // Takes a value as input and expands it out to being a 4-component\n   // vector of the same underlying type.\n   spv::Id expandVector(spv::Id value)\n   {\n      auto numComps = getNumComponents(value);\n\n      auto baseType = getTypeId(value);\n      if (numComps > 1) {\n         baseType = getContainedTypeId(baseType);\n      }\n\n      if (baseType == floatType()) {\n         auto zeroFConst = makeFloatConstant(0.0f);\n         if (numComps == 1) {\n            value = createOp(spv::OpCompositeConstruct, float4Type(), { value, zeroFConst, zeroFConst, zeroFConst });\n         } else if (numComps == 2) {\n            value = createOp(spv::OpCompositeConstruct, float4Type(), { value, zeroFConst, zeroFConst });\n         } else if (numComps == 3) {\n            value = createOp(spv::OpCompositeConstruct, float4Type(), { value, zeroFConst });\n         } else if (numComps == 4) {\n            // Already the right size\n         } else {\n            decaf_abort(\"Unexpected number of export components.\");\n         }\n      } else if (baseType == intType()) {\n         auto zeroConst = makeIntConstant(0);\n         if (numComps == 1) {\n            value = createOp(spv::OpCompositeConstruct, int4Type(), { value, zeroConst, zeroConst, zeroConst });\n         } else if (numComps == 2) {\n            value = createOp(spv::OpCompositeConstruct, int4Type(), { value, zeroConst, zeroConst });\n         } else if (numComps == 3) {\n            value = createOp(spv::OpCompositeConstruct, int4Type(), { value, zeroConst });\n         } else if (numComps == 4) {\n            // Already the right size\n         } else {\n            decaf_abort(\"Unexpected number of export components.\");\n         }\n      } else if (baseType == uintType()) {\n         auto zeroUConst = makeUintConstant(0);\n         if (numComps == 1) {\n            value = createOp(spv::OpCompositeConstruct, uint4Type(), { value, zeroUConst, zeroUConst, zeroUConst });\n         } else if (numComps == 2) {\n            value = createOp(spv::OpCompositeConstruct, uint4Type(), { value, zeroUConst, zeroUConst });\n         } else if (numComps == 3) {\n            value = createOp(spv::OpCompositeConstruct, uint4Type(), { value, zeroUConst });\n         } else if (numComps == 4) {\n            // Already the right size\n         } else {\n            decaf_abort(\"Unexpected number of export components.\");\n         }\n      } else {\n         decaf_abort(\"Unexpected export source data type\");\n      }\n\n      return value;\n   }\n\n   void writeExportRef(const ExportMaskRef& ref, spv::Id srcId)\n   {\n      // Fetch the underlying type of the export.  Note that we require that\n      // this method be invoked with 4-component vectors only!\n      auto srcBaseType = getContainedTypeId(getTypeId(srcId));\n\n      // Find the export reference for this, we pass in the source type to hint\n      // at the export creation what the output type should be.  This function\n      // must return something that is of the same type!\n      auto exportPtr = getExportRefVar(ref.output, srcBaseType);\n\n      // Lets figure out the correct size of the data so that we can\n      // shrink the source down to the right size at the same time.\n      auto exportType = getDerefTypeId(exportPtr);\n      auto numExportComps = getNumTypeComponents(exportType);\n\n      // Apply any export masking operation thats needed, this will also\n      // shrink the object down to the expected size at the same time.\n      // Note: We shouldn't be reading from outputs, but sometimes they\n      // mask data into a write, which makes very little sense...\n      auto origData = createLoad(exportPtr, spv::NoPrecision);\n      auto exportVal = applySelMask(origData, srcId, ref.mask, numExportComps);\n\n      auto sourceValType = getTypeId(exportVal);\n\n      // Lets perform any specialized behaviour depending on the export type\n      if (ref.output.type == ExportRef::Type::Position) {\n         decaf_check(sourceValType == float4Type());\n         // Need to reposition the depth values from (-1.0 to 1.0) to (0.0 to 1.0)\n\n         auto zeroConst = makeUintConstant(0);\n         auto oneConst = makeUintConstant(1);\n         auto zeroFConst = makeFloatConstant(0.0f);\n         auto oneFConst = makeFloatConstant(1.0f);\n\n         auto posMulAddPtr = createAccessChain(spv::StorageClass::StorageClassPushConstant, vsPushConstVar(), { zeroConst });\n         auto posMulAddVal = createLoad(posMulAddPtr, spv::NoPrecision);\n\n         auto zSpaceMulPtr = createAccessChain(spv::StorageClass::StorageClassPushConstant, vsPushConstVar(), { oneConst });\n         auto zSpaceMulVal = createLoad(zSpaceMulPtr, spv::NoPrecision);\n\n         // pos.xy = (pos.xy * posMulAdd.xy) + posMulAdd.zw;\n\n         auto posMulPart = createOp(spv::OpVectorShuffle, float2Type(), { posMulAddVal, posMulAddVal, 0, 1 });\n         auto posMulVal = createOp(spv::OpCompositeConstruct, float4Type(), { posMulPart, oneFConst, oneFConst });\n\n         auto posAddPart = createOp(spv::OpVectorShuffle, float2Type(), { posMulAddVal, posMulAddVal, 2, 3 });\n         auto posAddVal = createOp(spv::OpCompositeConstruct, float4Type(), { posAddPart, zeroFConst, zeroFConst });\n\n         exportVal = createBinOp(spv::OpFMul, float4Type(), exportVal, posMulVal);\n         exportVal = createBinOp(spv::OpFAdd, float4Type(), exportVal, posAddVal);\n\n         // pos.y = -pos.y\n\n         auto exportY = createOp(spv::OpCompositeExtract, floatType(), { exportVal, 1 });\n         exportY = createUnaryOp(spv::OpFNegate, floatType(), exportY);\n         exportVal = createOp(spv::OpCompositeInsert, float4Type(), { exportY, exportVal, 1 });\n\n         // pos.z = (pos.z + (pos.w * zSpaceMul.x)) * zSpaceMul.y;\n\n         auto zsYWMul = createOp(spv::OpCompositeExtract, floatType(), { zSpaceMulVal, 0 });\n         auto zsYYMul = createOp(spv::OpCompositeExtract, floatType(), { zSpaceMulVal, 1 });\n\n         auto exportZ = createOp(spv::OpCompositeExtract, floatType(), { exportVal, 2 });\n         auto exportW = createOp(spv::OpCompositeExtract, floatType(), { exportVal, 3 });\n\n         auto yWAdd = createBinOp(spv::OpFMul, floatType(), exportW, zsYWMul);\n         auto zAdj = createBinOp(spv::OpFAdd, floatType(), exportZ, yWAdd);\n         auto zFinal = createBinOp(spv::OpFMul, floatType(), zAdj, zsYYMul);\n\n         exportVal = createOp(spv::OpCompositeInsert, float4Type(), { zFinal, exportVal, 2 });\n      }\n\n      if (ref.output.type == ExportRef::Type::Pixel) {\n         decaf_check(sourceValType == float4Type() || sourceValType == int4Type() || sourceValType == uint4Type());\n\n         auto zeroConst = makeUintConstant(0);\n         auto oneConst = makeUintConstant(1);\n\n         // We use the first exported pixel to perform alpha reference testing.  This\n         // may not actually be the correct behaviour.\n         // TODO: Check which exported pixel does alpha testing.\n         if (ref.output.arrayBase == 0 && sourceValType == float4Type()) {\n            auto exportAlpha = createOp(spv::OpCompositeExtract, floatType(), { exportVal, 3 });\n\n            auto alphaFuncPtr = createAccessChain(spv::StorageClassPushConstant, psPushConstVar(), { zeroConst });\n            auto alphaDataVal = createLoad(alphaFuncPtr, spv::NoPrecision);\n            auto alphaFuncVal = createBinOp(spv::OpBitwiseAnd, uintType(), alphaDataVal, makeUintConstant(0xFF));\n\n            auto alphaRefPtr = createAccessChain(spv::StorageClassPushConstant, psPushConstVar(), { oneConst });\n            auto alphaRefVal = createLoad(alphaRefPtr, spv::NoPrecision);\n\n            auto makeCompareBlock = [&](spv::Op op)\n            {\n               auto pred = createBinOp(op, boolType(), exportAlpha, alphaRefVal);\n               auto notPred = createUnaryOp(spv::Op::OpLogicalNot, boolType(), pred);\n               auto block = spv::Builder::If { notPred, spv::SelectionControlMaskNone, *this };\n               makeDiscard();\n               block.makeEndIf();\n            };\n            auto makeEqCompareBlock = [&](bool wantEquality)\n            {\n               auto epsilonConst = makeFloatConstant(0.0001f);\n               auto alphaDiff = createBinOp(spv::OpFSub, floatType(), exportAlpha, alphaRefVal);\n               auto alphaDiffAbs = createBuiltinCall(floatType(), glslStd450(), GLSLstd450::GLSLstd450FAbs, { alphaDiff });\n               spv::Id pred;\n               if (wantEquality) {\n                  pred = createBinOp(spv::OpFOrdGreaterThan, boolType(), alphaDiffAbs, epsilonConst);\n               } else {\n                  pred = createBinOp(spv::OpFOrdLessThanEqual, boolType(), alphaDiffAbs, epsilonConst);\n               }\n               auto block = spv::Builder::If { pred, spv::SelectionControlMaskNone, *this };\n               makeDiscard();\n               block.makeEndIf();\n            };\n\n            auto switchSegments = std::vector<spv::Block *> { };\n            makeSwitch(alphaFuncVal, spv::SelectionControlMaskNone, 7,\n                       {\n                           latte::REF_FUNC::NEVER,\n                           latte::REF_FUNC::LESS,\n                           latte::REF_FUNC::EQUAL,\n                           latte::REF_FUNC::LESS_EQUAL,\n                           latte::REF_FUNC::GREATER,\n                           latte::REF_FUNC::NOT_EQUAL,\n                           latte::REF_FUNC::GREATER_EQUAL,\n                       },\n                       { 0, 1, 2, 3, 4, 5, 6 }, -1, switchSegments);\n\n            nextSwitchSegment(switchSegments, latte::REF_FUNC::NEVER);\n            makeDiscard();\n            addSwitchBreak();\n\n            nextSwitchSegment(switchSegments, latte::REF_FUNC::LESS);\n            makeCompareBlock(spv::OpFOrdLessThan);\n            addSwitchBreak();\n\n            nextSwitchSegment(switchSegments, latte::REF_FUNC::EQUAL);\n            makeEqCompareBlock(true);\n            addSwitchBreak();\n\n            nextSwitchSegment(switchSegments, latte::REF_FUNC::LESS_EQUAL);\n            makeCompareBlock(spv::OpFOrdLessThanEqual);\n            addSwitchBreak();\n\n            nextSwitchSegment(switchSegments, latte::REF_FUNC::GREATER);\n            makeCompareBlock(spv::OpFOrdGreaterThan);\n            addSwitchBreak();\n\n            nextSwitchSegment(switchSegments, latte::REF_FUNC::NOT_EQUAL);\n            makeEqCompareBlock(false);\n            addSwitchBreak();\n\n            nextSwitchSegment(switchSegments, latte::REF_FUNC::GREATER_EQUAL);\n            makeCompareBlock(spv::OpFOrdGreaterThanEqual);\n            addSwitchBreak();\n\n            endSwitch(switchSegments);\n         }\n\n         auto pixelTmpVar = createVariable(spv::NoPrecision, spv::StorageClassPrivate, sourceValType, \"_pixelTmp\");\n         createStore(exportVal, pixelTmpVar);\n\n         auto alphaDataPtr = createAccessChain(spv::StorageClassPushConstant, psPushConstVar(), { zeroConst });\n         auto alphaDataVal = createLoad(alphaDataPtr, spv::NoPrecision);\n         auto logicOpVal = createBinOp(spv::OpShiftRightLogical, uintType(), alphaDataVal, makeUintConstant(8));\n\n         auto lopSet = createBinOp(spv::Op::OpIEqual, boolType(), logicOpVal, makeUintConstant(1));\n         auto eq1block = spv::Builder::If { lopSet, spv::SelectionControlMaskNone, *this };\n         {\n            if (sourceValType == float4Type()) {\n               auto newValElem = makeFloatConstant(1.0f);\n               auto newVal = makeCompositeConstant(float4Type(), { newValElem, newValElem, newValElem, newValElem });\n               createStore(newVal, pixelTmpVar);\n            } else if (sourceValType == int4Type()) {\n               auto newValElem = makeIntConstant(0xFFFFFFFF);\n               auto newVal = makeCompositeConstant(int4Type(), { newValElem, newValElem, newValElem, newValElem });\n               createStore(newVal, pixelTmpVar);\n            } else if (sourceValType == uint4Type()) {\n               auto newValElem = makeUintConstant(0xFFFFFFFF);\n               auto newVal = makeCompositeConstant(uint4Type(), { newValElem, newValElem, newValElem, newValElem });\n               createStore(newVal, pixelTmpVar);\n            } else {\n               decaf_abort(\"Unexpected source pixel variable type\");\n            }\n         }\n         eq1block.makeBeginElse();\n         {\n            auto lopClear = createBinOp(spv::Op::OpIEqual, boolType(), logicOpVal, makeUintConstant(2));\n            auto eq2block = spv::Builder::If { lopClear, spv::SelectionControlMaskNone, *this };\n            {\n               if (sourceValType == float4Type()) {\n                  auto newValElem = makeFloatConstant(0.0f);\n                  auto newVal = makeCompositeConstant(float4Type(), { newValElem, newValElem, newValElem, newValElem });\n                  createStore(newVal, pixelTmpVar);\n               } else if (sourceValType == int4Type()) {\n                  auto newValElem = makeIntConstant(0);\n                  auto newVal = makeCompositeConstant(int4Type(), { newValElem, newValElem, newValElem, newValElem });\n                  createStore(newVal, pixelTmpVar);\n               } else if (sourceValType == uint4Type()) {\n                  auto newValElem = makeUintConstant(0);\n                  auto newVal = makeCompositeConstant(uint4Type(), { newValElem, newValElem, newValElem, newValElem });\n                  createStore(newVal, pixelTmpVar);\n               } else {\n                  decaf_abort(\"Unexpected source pixel variable type\");\n               }\n            }\n            eq2block.makeEndIf();\n         }\n         eq1block.makeEndIf();\n\n         // We need to premultiply the alpha in cases where premultiplied alpha is enabled\n         // globally but this specific target is not performing the premultiplication.\n         if (sourceValType == float4Type()) {\n            auto twoConst = makeUintConstant(2);\n            auto oneFConst = makeFloatConstant(1.0f);\n\n            auto needsPremulPtr = createAccessChain(spv::StorageClassPushConstant, psPushConstVar(), { twoConst });\n            auto needsPremulVal = createLoad(needsPremulPtr, spv::NoPrecision);\n\n            auto targetBitConst = makeUintConstant(1 << ref.output.arrayBase);\n            auto targetBitVal = createBinOp(spv::OpBitwiseAnd, uintType(), needsPremulVal, targetBitConst);\n            auto pred = createBinOp(spv::OpINotEqual, boolType(), targetBitVal, zeroConst);\n            auto eq0block = spv::Builder::If { pred, spv::SelectionControlMaskNone, *this };\n            {\n               exportVal = createLoad(pixelTmpVar, spv::NoPrecision);\n\n               auto exportAlpha = createOp(spv::OpCompositeExtract, floatType(), { exportVal, 3 });\n               auto premulMul = createOp(spv::OpCompositeConstruct, float4Type(), { exportAlpha, exportAlpha, exportAlpha, oneFConst });\n               auto premulExportVal = createBinOp(spv::OpFMul, float4Type(), exportVal, premulMul);\n\n               createStore(premulExportVal, pixelTmpVar);\n            }\n            eq0block.makeEndIf();\n         }\n\n         exportVal = createLoad(pixelTmpVar, spv::NoPrecision);\n      }\n\n      createStore(exportVal, exportPtr);\n   }\n\n   spv::Id vertexIdVar()\n   {\n      if (!mVertexId) {\n         mVertexId = createVariable(spv::NoPrecision, spv::StorageClassInput, intType(), \"VertexID\");\n         addDecoration(mVertexId, spv::DecorationBuiltIn, spv::BuiltInVertexIndex);\n         mEntryPoint->addIdOperand(mVertexId);\n      }\n      return mVertexId;\n   }\n\n   spv::Id instanceIdVar()\n   {\n      if (!mInstanceId) {\n         mInstanceId = createVariable(spv::NoPrecision, spv::StorageClassInput, intType(), \"InstanceID\");\n         addDecoration(mInstanceId, spv::DecorationBuiltIn, spv::BuiltInInstanceIndex);\n         mEntryPoint->addIdOperand(mInstanceId);\n      }\n      return mInstanceId;\n   }\n\n   spv::Id fragCoordVar()\n   {\n      if (!mFragCoord) {\n         mFragCoord = createVariable(spv::NoPrecision, spv::StorageClassInput, float4Type(), \"FragCoord\");\n         addDecoration(mFragCoord, spv::DecorationBuiltIn, spv::BuiltInFragCoord);\n         mEntryPoint->addIdOperand(mFragCoord);\n      }\n      return mFragCoord;\n   }\n\n   spv::Id frontFacingVar()\n   {\n      if (!mFrontFacing) {\n         mFrontFacing = createVariable(spv::NoPrecision, spv::StorageClassInput, boolType(), \"FrontFacing\");\n         addDecoration(mFrontFacing, spv::DecorationBuiltIn, spv::BuiltInFrontFacing);\n         mEntryPoint->addIdOperand(mFrontFacing);\n      }\n      return mFrontFacing;\n   }\n\n   spv::Id layerIdVar()\n   {\n      if (!mLayerId) {\n         mLayerId = createVariable(spv::NoPrecision, spv::StorageClassOutput, intType(), \"LayerID\");\n         addDecoration(mLayerId, spv::DecorationBuiltIn, spv::BuiltInLayer);\n         mEntryPoint->addIdOperand(mLayerId);\n      }\n      return mLayerId;\n   }\n\n   spv::Id pointSizeVar()\n   {\n      if (!mPointSize) {\n         mPointSize = createVariable(spv::NoPrecision, spv::StorageClassOutput, floatType(), \"PointSize\");\n         addDecoration(mPointSize, spv::DecorationBuiltIn, spv::BuiltInPointSize);\n         mEntryPoint->addIdOperand(mPointSize);\n      }\n      return mPointSize;\n   }\n\n   spv::Id inputAttribVar(int semLocation, spv::Id attribType)\n   {\n      auto attribIdx = mAttribInputs.size();\n\n      auto attribVar = createVariable(spv::NoPrecision, spv::StorageClassInput, attribType,\n                                      fmt::format(\"INPUT_{}\", attribIdx).c_str());\n      addDecoration(attribVar, spv::DecorationLocation, static_cast<int>(semLocation));\n\n      mEntryPoint->addIdOperand(attribVar);\n      mAttribInputs.push_back(attribVar);\n\n      return attribVar;\n   }\n\n   spv::Id inputParamVar(int semLocation)\n   {\n      auto paramIdx = mParamInputs.size();\n\n      auto inputVar = createVariable(spv::NoPrecision, spv::StorageClassInput, float4Type(),\n                                     fmt::format(\"PARAM_{}\", paramIdx).c_str());\n      addDecoration(inputVar, spv::DecorationLocation, static_cast<int>(semLocation));\n\n      mEntryPoint->addIdOperand(inputVar);\n      mParamInputs.push_back(inputVar);\n\n      return inputVar;\n   }\n\n   spv::Id inputRingVar(int index)\n   {\n      while (mRingInputs.size() <= index) {\n         mRingInputs.push_back(spv::NoResult);\n      }\n\n      auto inputVar = mRingInputs[index];\n      if (!inputVar) {\n         inputVar = createVariable(spv::NoPrecision, spv::StorageClassInput, arrayType(float4Type(), 16, 3),\n                                   fmt::format(\"RINGIN_{}\", index).c_str());\n         addDecoration(inputVar, spv::DecorationLocation, static_cast<int>(index));\n\n         mEntryPoint->addIdOperand(inputVar);\n         mRingInputs[index] = inputVar;\n      }\n\n      return inputVar;\n   }\n\n   spv::Id vsPushConstVar()\n   {\n      if (!mVsPushConsts) {\n         auto vsPushStruct = makeStructType({\n               float4Type(), float4Type(), floatType()\n            }, \"VS_PUSH_CONSTANTS\");\n\n         addMemberDecoration(vsPushStruct, 0, spv::DecorationOffset,\n            spirv::VertexPushConstantsOffset + static_cast<int>(offsetof(spirv::VertexPushConstants, posMulAdd)));\n         addMemberName(vsPushStruct, 0, \"posMulAdd\");\n\n         addMemberDecoration(vsPushStruct, 1, spv::DecorationOffset,\n            spirv::VertexPushConstantsOffset + static_cast<int>(offsetof(spirv::VertexPushConstants, zSpaceMul)));\n         addMemberName(vsPushStruct, 1, \"zSpaceMul\");\n\n         addMemberDecoration(vsPushStruct, 2, spv::DecorationOffset,\n            spirv::VertexPushConstantsOffset + static_cast<int>(offsetof(spirv::VertexPushConstants, pointSize)));\n         addMemberName(vsPushStruct, 2, \"pointSize\");\n\n         addDecoration(vsPushStruct, spv::DecorationBlock);\n         mVsPushConsts = createVariable(spv::NoPrecision, spv::StorageClassPushConstant, vsPushStruct, \"VS_PUSH\");\n      }\n      return mVsPushConsts;\n   }\n\n   spv::Id gsPushConstVar()\n   {\n      decaf_abort(\"There are not geometry shader push constants\");\n   }\n\n   spv::Id psPushConstVar()\n   {\n      if (!mPsPushConsts) {\n         auto psPushStruct = makeStructType({\n               uintType(), floatType(), uintType()\n            }, \"PS_PUSH_DATA\");\n\n         addMemberDecoration(psPushStruct, 0, spv::DecorationOffset,\n            spirv::FragmentPushConstantsOffset + static_cast<int>(offsetof(spirv::FragmentPushConstants, alphaFunc)));\n         addMemberName(psPushStruct, 0, \"alphaFunc\");\n\n         addMemberDecoration(psPushStruct, 1, spv::DecorationOffset,\n            spirv::FragmentPushConstantsOffset + static_cast<int>(offsetof(spirv::FragmentPushConstants, alphaRef)));\n         addMemberName(psPushStruct, 1, \"alphaRef\");\n\n         addMemberDecoration(psPushStruct, 2, spv::DecorationOffset,\n            spirv::FragmentPushConstantsOffset + static_cast<int>(offsetof(spirv::FragmentPushConstants, needsPremultiply)));\n         addMemberName(psPushStruct, 2, \"needsPremultiply\");\n\n         addDecoration(psPushStruct, spv::DecorationBlock);\n         mPsPushConsts = createVariable(spv::NoPrecision, spv::StorageClassPushConstant, psPushStruct, \"PS_PUSH\");\n      }\n      return mPsPushConsts;\n   }\n\n   spv::Id cfileVar()\n   {\n      if (!mRegistersBuffer) {\n         auto regsType = arrayType(float4Type(), 16, 256);\n         auto structType = this->makeStructType({ regsType }, \"CFILE_DATA\");\n         addMemberDecoration(structType, 0, spv::DecorationOffset, 0);\n         addDecoration(structType, spv::DecorationBufferBlock);\n         addMemberName(structType, 0, \"values\");\n\n         auto bindingIdx = mBindingBase + latte::MaxTextures;\n\n         mRegistersBuffer = createVariable(spv::NoPrecision, spv::StorageClassUniform, structType, \"CFILE\");\n         addDecoration(mRegistersBuffer, spv::DecorationDescriptorSet, 0);\n         addDecoration(mRegistersBuffer, spv::DecorationBinding, bindingIdx);\n         addDecoration(mRegistersBuffer, spv::DecorationNonWritable);\n      }\n\n      return mRegistersBuffer;\n   }\n\n   spv::Id cbufferVar(uint32_t cbufferIdx)\n   {\n      decaf_check(cbufferIdx < latte::MaxUniformBlocks);\n\n      auto cbuffer = mUniformBuffers[cbufferIdx];\n      if (!cbuffer) {\n         auto valuesType = arrayType(float4Type(), 16, 0);\n\n         auto structType = this->makeStructType({ valuesType }, fmt::format(\"CBUFFER_DATA_{}\", cbufferIdx).c_str());\n         addMemberDecoration(structType, 0, spv::DecorationOffset, 0);\n         addDecoration(structType, spv::DecorationBufferBlock);\n         addMemberName(structType, 0, \"values\");\n\n         auto bindingIdx = mBindingBase + latte::MaxTextures + cbufferIdx;\n\n         cbuffer = createVariable(spv::NoPrecision, spv::StorageClassUniform, structType, fmt::format(\"CBUFFER_{}\", cbufferIdx).c_str());\n         addDecoration(cbuffer, spv::DecorationDescriptorSet, 0);\n         addDecoration(cbuffer, spv::DecorationBinding, bindingIdx);\n         addDecoration(cbuffer, spv::DecorationNonWritable);\n\n         mUniformBuffers[cbufferIdx] = cbuffer;\n      }\n\n      return cbuffer;\n   }\n\n   spv::Id samplerVar(uint32_t samplerIdx)\n   {\n      decaf_check(samplerIdx < latte::MaxSamplers);\n\n      auto samplerId = mSamplers[samplerIdx];\n      if (!samplerId) {\n         samplerId = createVariable(spv::NoPrecision, spv::StorageClassUniformConstant, samplerType());\n         addName(samplerId, fmt::format(\"SAMPLER_{}\", samplerIdx).c_str());\n\n         auto bindingIdx = mBindingBase + samplerIdx;\n\n         addDecoration(samplerId, spv::DecorationDescriptorSet, 0);\n         addDecoration(samplerId, spv::DecorationBinding, bindingIdx);\n\n         mSamplers[samplerIdx] = samplerId;\n      }\n\n      return samplerId;\n   }\n\n   spv::Id textureVarType(uint32_t textureIdx, latte::SQ_TEX_DIM texDim, TextureInputType texFormat)\n   {\n      decaf_check(textureIdx < latte::MaxTextures);\n\n      spv::Id resultType;\n      if (texFormat == TextureInputType::FLOAT) {\n         resultType = floatType();\n      } else if (texFormat == TextureInputType::INT) {\n         resultType = uintType();\n      } else {\n         decaf_abort(\"Unexpected texture input format\");\n      }\n\n      auto textureType = mTextureTypes[textureIdx];\n      if (!textureType) {\n         // TODO: This shouldn't exist here...\n         switch (texDim) {\n         case latte::SQ_TEX_DIM::DIM_1D:\n            textureType = makeImageType(resultType, spv::Dim1D, false, false, false, 1, spv::ImageFormatUnknown);\n            break;\n         case latte::SQ_TEX_DIM::DIM_2D:\n         case latte::SQ_TEX_DIM::DIM_2D_MSAA:\n            textureType = makeImageType(resultType, spv::Dim2D, false, false, false, 1, spv::ImageFormatUnknown);\n            break;\n         case latte::SQ_TEX_DIM::DIM_3D:\n            textureType = makeImageType(resultType, spv::Dim3D, false, false, false, 1, spv::ImageFormatUnknown);\n            break;\n         case latte::SQ_TEX_DIM::DIM_CUBEMAP:\n            textureType = makeImageType(resultType, spv::Dim2D, false, true, false, 1, spv::ImageFormatUnknown);\n            break;\n         case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n            textureType = makeImageType(resultType, spv::Dim1D, false, true, false, 1, spv::ImageFormatUnknown);\n            break;\n         case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n         case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n            textureType = makeImageType(resultType, spv::Dim2D, false, true, false, 1, spv::ImageFormatUnknown);\n            break;\n         default:\n            decaf_abort(\"Unexpected texture dim type\");\n         }\n\n         mTextureTypes[textureIdx] = textureType;\n      }\n\n      return textureType;\n   }\n\n   spv::Id textureVar(uint32_t textureIdx, latte::SQ_TEX_DIM texDim, TextureInputType texFormat)\n   {\n      decaf_check(textureIdx < latte::MaxTextures);\n\n      auto textureId = mTextures[textureIdx];\n      if (!textureId) {\n         textureId = createVariable(spv::NoPrecision, spv::StorageClassUniformConstant, textureVarType(textureIdx, texDim, texFormat));\n         addName(textureId, fmt::format(\"TEXTURE_{}\", textureIdx).c_str());\n\n         auto bindingIdx = mBindingBase + textureIdx;\n\n         addDecoration(textureId, spv::DecorationDescriptorSet, 0);\n         addDecoration(textureId, spv::DecorationBinding, bindingIdx);\n\n         mTextures[textureIdx] = textureId;\n      }\n\n      return textureId;\n   }\n\n   spv::Id pixelExportVar(uint32_t pixelIdx, spv::Id outputType)\n   {\n      decaf_check(pixelIdx < latte::MaxRenderTargets);\n\n      auto exportType = vecType(outputType, 4);\n\n      auto& exportId = mPixelExports[pixelIdx];\n      if (!exportId) {\n         exportId = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassOutput, exportType, fmt::format(\"PIXEL_{}\", pixelIdx).c_str());\n         addDecoration(exportId, spv::Decoration::DecorationLocation, pixelIdx);\n         mPixelExports[pixelIdx] = exportId;\n\n         mEntryPoint->addIdOperand(exportId);\n      }\n\n      // Lets confirm that the type is correct!  If we fetched this from the\n      // map, its possible that the type has changed, which we do not handle.\n      decaf_check(getDerefTypeId(exportId) == exportType);\n\n      return exportId;\n   }\n\n   spv::Id posExportVar(uint32_t posIdx)\n   {\n      decaf_check(posIdx < 4);\n\n      auto& exportId = mPosExports[posIdx];\n      if (!exportId) {\n         exportId = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassOutput, float4Type(), fmt::format(\"POS_{}\", posIdx).c_str());\n\n         if (posIdx == 0) {\n            // Mark this as being the position output\n            addDecoration(exportId, spv::DecorationBuiltIn, spv::BuiltInPosition);\n         } else {\n            // Mark this to a location, not sure if this is really needed\n            addDecoration(exportId, spv::Decoration::DecorationLocation, 60 + posIdx);\n         }\n\n         mEntryPoint->addIdOperand(exportId);\n      }\n\n      return exportId;\n   }\n\n   spv::Id paramExportVar(uint32_t paramIdx)\n   {\n      decaf_check(paramIdx < 32);\n\n      auto& exportId = mParamExports[paramIdx];\n      if (!exportId) {\n         exportId = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassOutput, float4Type(), fmt::format(\"PARAM_{}\", paramIdx).c_str());\n         addDecoration(exportId, spv::Decoration::DecorationLocation, paramIdx);\n\n         mEntryPoint->addIdOperand(exportId);\n      }\n\n      return exportId;\n   }\n\n   spv::Id zExportVar()\n   {\n      if (!mZExport) {\n         addExecutionMode(mFunctions[\"main\"], spv::ExecutionModeDepthReplacing);\n\n         mZExport = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassOutput, floatType(), \"Z_EXPORT\");\n         addDecoration(mZExport, spv::DecorationBuiltIn, spv::BuiltInFragDepth);\n         mEntryPoint->addIdOperand(mZExport);\n      }\n\n      return mZExport;\n   }\n\n   spv::Id memExportWriteVar(uint32_t streamIdx, uint32_t dataStride, uint32_t indexGpr, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount)\n   {\n      decaf_check(streamIdx < 4);\n      decaf_check(dataStride > 0);\n\n      // We do not currently support dynamic indexing into streamout\n      decaf_check(indexGpr == -1);\n\n      // TODO: Support write prevention based on the array size.\n\n      // We have to add the transform feedback capability to use Xfb\n      addCapability(spv::CapabilityTransformFeedback);\n      addExecutionMode(mFunctions[\"main\"], spv::ExecutionModeXfb);\n\n      // Calculate the offset of vec4's since that is all we currently support\n      auto& streamExports = mMemWriteExports[streamIdx];\n\n      // We only support writing 1-4 dwords per memory write.  This might break\n      // down when someone does a burst write :S\n      decaf_check(elemCount == 1);\n      decaf_check(arraySize <= 4);\n      auto writeElemCount = arraySize * elemCount;\n\n      // We only support writing in vec4 increments\n      auto bufferOffset = arrayBase * 4;\n\n      // Determine the type of this stream out object\n      spv::Id resultType = spv::NoResult;\n      if (writeElemCount == 1) {\n         resultType = floatType();\n      } else if (writeElemCount == 2) {\n         resultType = float2Type();\n      } else if (writeElemCount == 3) {\n         resultType = float3Type();\n      } else if (writeElemCount == 4) {\n         resultType = float4Type();\n      } else {\n         decaf_abort(\"Unexpected stream out item size\");\n      }\n\n      auto& exportId = streamExports[bufferOffset];\n      if (!exportId) {\n         // We currently place the stream-out data at the end of the params\n         // assuming that nobody will ever use all stream-out with parameter\n         // output as well.  It may break in wierd cases.\n\n         auto streamOutIndex = mNumStreamOut++;\n\n         exportId = createVariable(spv::NoPrecision, spv::StorageClassOutput, resultType, fmt::format(\"STREAMOUT_{}_{}\", streamIdx, bufferOffset).c_str());\n         addDecoration(exportId, spv::DecorationLocation, 31 - streamOutIndex);\n         addDecoration(exportId, spv::DecorationXfbBuffer, streamIdx);\n         addDecoration(exportId, spv::DecorationXfbStride, dataStride);\n         addDecoration(exportId, spv::DecorationOffset, bufferOffset);\n\n         mEntryPoint->addIdOperand(exportId);\n      }\n\n      decaf_check(getDerefTypeId(exportId) == resultType);\n\n      return exportId;\n   }\n\n   spv::Id vsGsRingExportWriteVar(uint32_t indexGpr, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount)\n   {\n      // TODO: Support write prevention based on the array size.\n\n      // We do not currently support dynamic indexing into streamout\n      decaf_check(indexGpr == -1);\n\n      auto bufferOffset = arrayBase * elemCount;\n      decaf_check(bufferOffset % 16 == 0);\n      auto bufferVec4Offset = bufferOffset / 16;\n\n      // Determine the size for this ringbuffer write\n      spv::Id resultType = spv::NoResult;\n      if (elemCount == 1) {\n         resultType = floatType();\n      } else if (elemCount == 2) {\n         resultType = float2Type();\n      } else if (elemCount == 3) {\n         resultType = float3Type();\n      } else if (elemCount == 4) {\n         resultType = float4Type();\n      } else {\n         decaf_abort(\"Unexpected element count for ringbuffer write\");\n      }\n\n      auto& exportId = mRingWriteExports[bufferVec4Offset];\n      if (!exportId) {\n         exportId = createVariable(spv::NoPrecision, spv::StorageClassOutput, resultType, fmt::format(\"VSRING_{}\", bufferVec4Offset).c_str());\n         addDecoration(exportId, spv::DecorationLocation, bufferVec4Offset);\n\n         mEntryPoint->addIdOperand(exportId);\n      }\n\n      decaf_check(getDerefTypeId(exportId) == resultType);\n\n      return exportId;\n   }\n\n   spv::Id gsDcRingExportWriteVar(uint32_t indexGpr, uint32_t arrayBase, uint32_t arraySize, uint32_t elemCount)\n   {\n      // TODO: Support write prevention based on the array size.\n\n      // Calculate the overall offset into the buffer\n      auto bufferOffset = arrayBase * elemCount;\n\n      // Grab the vec4 offset, writes must only occur on the boundaries\n      // of a single vec4 write...\n      decaf_check(bufferOffset % 16 == 0);\n      auto ringVec4Offset = bufferOffset / 16;\n\n      // Create a pointer to our memory object\n      auto ringOffset = makeIntConstant(ringVec4Offset);\n\n      if (indexGpr != -1) {\n         // Fetch the index GPR value\n         GprSelRef gprRef;\n         gprRef.gpr.number = indexGpr;\n         gprRef.gpr.indexMode = GprIndexMode::None;\n         gprRef.sel = latte::SQ_SEL::SEL_X;\n         auto indexGprVal = readGprSelRef(gprRef);\n\n         // Bitcast it to an integer\n         indexGprVal = createUnaryOp(spv::OpBitcast, intType(), indexGprVal);\n\n         // Add the indexed value to the ring offset\n         ringOffset = createBinOp(spv::OpIAdd, intType(), indexGprVal, ringOffset);\n      }\n\n      return createAccessChain(spv::StorageClassPrivate, ringVar(), { ringOffset });\n   }\n\n   spv::Id stateVar()\n   {\n      if (!mState) {\n         mState = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, intType(), \"stateVar\");\n      }\n      return mState;\n   }\n\n   spv::Id stateActive()\n   {\n      return makeIntConstant(0);\n   }\n\n   spv::Id stateInactive()\n   {\n      return makeIntConstant(1);\n   }\n\n   spv::Id stateInactiveBreak()\n   {\n      return makeIntConstant(2);\n   }\n\n   spv::Id stateInactiveContinue()\n   {\n      return makeIntConstant(3);\n   }\n\n   spv::Id predicateVar()\n   {\n      if (!mPredicate) {\n         mPredicate = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, boolType(), \"predVar\");\n      }\n      return mPredicate;\n   }\n\n   spv::Id stackVar()\n   {\n      if (!mStack) {\n         auto stackType = arrayType(intType(), 4, 16);\n         mStack = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, stackType, \"stackVar\");\n      }\n      return mStack;\n   }\n\n   spv::Id stackIndexVar()\n   {\n      if (!mStackIndex) {\n         mStackIndex = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, intType(), \"stackIdxVar\");\n      }\n      return mStackIndex;\n   }\n\n   spv::Id gprVar()\n   {\n      if (!mGpr) {\n         auto gprType = arrayType(float4Type(), 16, 128);\n         mGpr = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, gprType, \"RVar\");\n      }\n      return mGpr;\n   }\n\n   spv::Id ringVar()\n   {\n      if (!mRing) {\n         auto ringType = arrayType(float4Type(), 16, 128);\n         mRing = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, ringType, \"LocalRing\");\n      }\n      return mRing;\n   }\n\n   spv::Id ringOffsetVar()\n   {\n      if (!mRingOffset) {\n         mRingOffset = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, uintType(), \"RingIndex\");\n      }\n      return mRingOffset;\n   }\n\n   spv::Id ALVar()\n   {\n      if (!mAL) {\n         mAL = createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, intType(), \"ALVar\");\n      }\n      return mAL;\n   }\n\n   void resetAr()\n   {\n      // It is not legal to modify the AR register in the same instruction group\n      // that reads it, so we don't need to worry about swapping things around\n      // like we do for PV/PS.\n\n      for (auto i = 0; i < 4; ++i) {\n         mARId[i] = spv::NoResult;\n      }\n   }\n\n   void swapPrevRes()\n   {\n      // Make the next prevRes active\n      mPrevResId = mNextPrevResId;\n\n      // Clear the next prevRes's\n      for (auto i = 0; i < 5; ++i) {\n         mNextPrevResId[i] = spv::NoResult;\n      }\n   }\n\n   spv::Id getPvId(latte::SQ_CHAN unit)\n   {\n      auto pvVal = mPrevResId[unit];\n      decaf_check(getTypeId(pvVal) == floatType());\n      return pvVal;\n   }\n\n   spv::Id getArId(latte::SQ_CHAN unit)\n   {\n      decaf_check(unit != latte::SQ_CHAN::T);\n\n      auto arVal = mARId[unit];\n      decaf_check(getTypeId(arVal) == intType());\n      return arVal;\n   }\n\n   bool hasFunction(const std::string& name) const\n   {\n      return mFunctions.find(name) != mFunctions.end();\n   }\n\n   spv::Function *getFunction(const std::string& name)\n   {\n      auto &func = mFunctions[name];\n      if (!func) {\n         auto savePos = getBuildPoint();\n         auto entryBlock = static_cast<spv::Block *>(nullptr);\n         func = makeFunctionEntry(spv::NoPrecision, makeVoidType(),\n                                  name.c_str(), {}, {}, &entryBlock);\n         setBuildPoint(savePos);\n      }\n      return func;\n   }\n\n   bool isSamplerUsed(uint32_t samplerIdx) const\n   {\n      decaf_check(samplerIdx <= latte::MaxSamplers);\n      return mSamplers[samplerIdx] != spv::NoResult;\n   }\n\n   bool isTextureUsed(uint32_t textureIdx) const\n   {\n      decaf_check(textureIdx <= latte::MaxTextures);\n      return mTextures[textureIdx] != spv::NoResult;\n   }\n\n   bool isConstantFileUsed() const\n   {\n      return mRegistersBuffer != spv::NoResult;\n   }\n\n   bool isUniformBufferUsed(uint32_t bufferIdx) const\n   {\n      decaf_check(bufferIdx <= latte::MaxUniformBlocks);\n      return mUniformBuffers[bufferIdx] != spv::NoResult;\n   }\n\n   bool isStreamOutUsed(uint32_t bufferIdx) const\n   {\n      decaf_check(bufferIdx <= latte::MaxStreamOutBuffers);\n      return !mMemWriteExports[bufferIdx].empty();\n   }\n\n   bool isPixelOutUsed(uint32_t exportIdx) const\n   {\n      return mPixelExports.find(exportIdx) != mPixelExports.end();\n   }\n\n   int getNumPosExports()\n   {\n      return (int)mPosExports.size();\n   }\n\n   int getNumParamExports()\n   {\n      return (int)mParamExports.size();\n   }\n\n   int getNumPixelExports()\n   {\n      return (int)mPixelExports.size();\n   }\n\n   void makeDiscard()\n   {\n      makeStatementTerminator(spv::OpKill, \"post-discard\");\n   }\n\nprotected:\n   std::vector<std::pair<GprChanRef, spv::Id>> mAluGroupWrites;\n\n   uint32_t mBindingBase;\n   spv::Instruction *mEntryPoint = nullptr;\n   std::unordered_map<std::string, spv::Function*> mFunctions;\n\n   spv::Id mVertexId = spv::NoResult;\n   spv::Id mInstanceId = spv::NoResult;\n   spv::Id mFragCoord = spv::NoResult;\n   spv::Id mFrontFacing = spv::NoResult;\n   spv::Id mLayerId = spv::NoResult;\n   spv::Id mPointSize = spv::NoResult;\n\n   spv::Id mRegistersBuffer = spv::NoResult;\n   std::array<spv::Id, latte::MaxUniformBlocks> mUniformBuffers = { spv::NoResult };\n\n   spv::Id mVsPushConsts = spv::NoResult;\n   spv::Id mGsPushConsts = spv::NoResult;\n   spv::Id mPsPushConsts = spv::NoResult;\n\n   std::vector<spv::Id> mAttribInputs;\n   std::vector<spv::Id> mParamInputs;\n   std::vector<spv::Id> mRingInputs;\n\n   std::array<spv::Id, latte::MaxSamplers> mSamplers = { spv::NoResult };\n   std::array<spv::Id, latte::MaxTextures> mTextureTypes = { spv::NoResult };\n   std::array<spv::Id, latte::MaxTextures> mTextures = { spv::NoResult };\n\n   std::map<uint32_t, spv::Id> mPixelExports;\n   std::map<uint32_t, spv::Id> mPosExports;\n   std::map<uint32_t, spv::Id> mParamExports;\n   spv::Id mZExport = spv::NoResult;\n   std::array<std::map<uint32_t, spv::Id>, latte::MaxStreamOutBuffers> mMemWriteExports;\n   uint32_t mNumStreamOut = 0;\n   std::map<uint32_t, spv::Id> mRingWriteExports;\n\n   spv::Id mState = spv::NoResult;\n   spv::Id mPredicate = spv::NoResult;\n   spv::Id mStackIndex = spv::NoResult;\n   spv::Id mStack = spv::NoResult;\n   spv::Id mGpr = spv::NoResult;\n   spv::Id mAL = spv::NoResult;\n   spv::Id mRing = spv::NoResult;\n   spv::Id mRingOffset = spv::NoResult;\n\n   std::array<spv::Id, 4> mARId = { spv::NoResult };\n   std::array<spv::Id, 5> mPrevResId = { spv::NoResult };\n   std::array<spv::Id, 5> mNextPrevResId = { spv::NoResult };\n\n};\n\n} //namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_spvbuilder.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n#include <common/bit_cast.h>\n#include <common/strutils.h>\n#include <SPIRV/SpvBuilder.h>\n#include <SPIRV/GLSL.std.450.h>\n\nnamespace spirv\n{\n\nclass SpvBuilder : public spv::Builder\n{\npublic:\n   SpvBuilder()\n      : spv::Builder(0x10000, 0xFFFFFFFF, nullptr)\n   {\n   }\n\n   spv::Id createAccessChain(spv::StorageClass storageClass,\n                             spv::Id base, const std::vector<spv::Id> &offsets)\n   {\n      SpvBuilder::accessChain.base = base;\n      SpvBuilder::accessChain.indexChain = offsets;\n      return spv::Builder::createAccessChain(storageClass, base, offsets);\n   }\n\n   // ------------------------------------------------------------\n   // Constants\n   // ------------------------------------------------------------\n\n   spv::Id makeFloatConstant(float f, bool specConstant = false)\n   {\n      if (specConstant) {\n         return spv::Builder::makeFloatConstant(f, specConstant);\n      }\n\n      auto bitsValue = bit_cast<uint32_t>(f);\n      auto constVal = mFloatConstants[bitsValue];\n      if (constVal) {\n         return constVal;\n      }\n\n      constVal = spv::Builder::makeFloatConstant(f, specConstant);\n      auto name = fmt::format(\"CONST_{}f\", f);\n      replace_all(name, '-', 'n');\n      replace_all(name, '.', 'p');\n      addName(constVal, name.c_str());\n\n      mFloatConstants[bitsValue] = constVal;\n      return constVal;\n   }\n\n   spv::Id makeUintConstant(unsigned int u, bool specConstant = false)\n   {\n      if (specConstant) {\n         return spv::Builder::makeUintConstant(u, specConstant);\n      }\n\n      auto constVal = mUintConstants[u];\n      if (constVal) {\n         return constVal;\n      }\n\n      constVal = spv::Builder::makeUintConstant(u, specConstant);\n      switch (u) {\n      case 0: addName(constVal, \"CONST_0_or_X\"); break;\n      case 1: addName(constVal, \"CONST_1_or_Y\"); break;\n      case 2: addName(constVal, \"CONST_2_or_Z\"); break;\n      case 3: addName(constVal, \"CONST_3_or_W\"); break;\n      case 4: addName(constVal, \"CONST_4_or_T\"); break;\n      default:\n         if (u < 100) {\n            addName(constVal, fmt::format(\"CONST_{}\", u).c_str());\n         } else {\n            addName(constVal, fmt::format(\"CONST_0x{:x}\", u).c_str());\n         }\n         break;\n      }\n\n      mUintConstants[u] = constVal;\n      return constVal;\n   }\n\n   spv::Id vectorizeConstant(spv::Id srcId, int elemCount)\n   {\n      std::vector<spv::Id> sources(elemCount);\n      for (auto i = 0u; i < sources.size(); ++i) {\n         sources[i] = srcId;\n      }\n      return makeCompositeConstant(makeVectorType(getTypeId(srcId), elemCount), sources);\n   }\n\n\n   // ------------------------------------------------------------\n   // Types\n   // ------------------------------------------------------------\n\n   spv::Id makeIntType(int width)\n   {\n      if (width == 8) {\n         addCapability(spv::Capability::CapabilityInt8);\n      } else if (width == 16) {\n         addCapability(spv::Capability::CapabilityInt16);\n      }\n      return spv::Builder::makeIntType(width);\n   }\n\n   spv::Id makeUintType(int width)\n   {\n      if (width == 8) {\n         addCapability(spv::Capability::CapabilityInt8);\n      } else if (width == 16) {\n         addCapability(spv::Capability::CapabilityInt16);\n      }\n      return spv::Builder::makeUintType(width);\n   }\n\n   spv::Id samplerType()\n   {\n      if (!mSamplerType) {\n         mSamplerType = makeSamplerType();\n         addName(mSamplerType, \"sampler\");\n      }\n      return mSamplerType;\n   }\n\n   spv::Id boolType()\n   {\n      if (!mBoolType) {\n         mBoolType = makeBoolType();\n         addName(mBoolType, \"bool\");\n      }\n      return mBoolType;\n   }\n\n   spv::Id floatType()\n   {\n      if (!mFloatType) {\n         mFloatType = makeFloatType(32);\n         addName(mFloatType, \"float\");\n      }\n      return mFloatType;\n   }\n   spv::Id float2Type() { return vecType(floatType(), 2); }\n   spv::Id float3Type() { return vecType(floatType(), 3); }\n   spv::Id float4Type() { return vecType(floatType(), 4); }\n\n   spv::Id ubyteType()\n   {\n      if (!mUbyteType) {\n         mUbyteType = makeUintType(8);\n         addName(mUbyteType, \"ubyte\");\n      }\n      return mUbyteType;\n   }\n   spv::Id ubyte2Type() { return vecType(ubyteType(), 2); }\n   spv::Id ubyte3Type() { return vecType(ubyteType(), 3); }\n   spv::Id ubyte4Type() { return vecType(ubyteType(), 4); }\n\n   spv::Id ushortType()\n   {\n      if (!mUshortType) {\n         mUshortType = makeUintType(16);\n         addName(mUshortType, \"ushort\");\n      }\n      return mUshortType;\n   }\n   spv::Id ushort2Type() { return vecType(ushortType(), 2); }\n   spv::Id ushort3Type() { return vecType(ushortType(), 3); }\n   spv::Id ushort4Type() { return vecType(ushortType(), 4); }\n\n   spv::Id intType()\n   {\n      if (!mIntType) {\n         mIntType = makeIntType(32);\n         addName(mIntType, \"int\");\n      }\n      return mIntType;\n   }\n   spv::Id int2Type() { return vecType(intType(), 2); }\n   spv::Id int3Type() { return vecType(intType(), 3); }\n   spv::Id int4Type() { return vecType(intType(), 4); }\n\n   spv::Id uintType()\n   {\n      if (!mUintType) {\n         mUintType = makeUintType(32);\n         addName(mUintType, \"uint\");\n      }\n      return mUintType;\n   }\n   spv::Id uint2Type() { return vecType(uintType(), 2); }\n   spv::Id uint3Type() { return vecType(uintType(), 3); }\n   spv::Id uint4Type() { return vecType(uintType(), 4); }\n\n   spv::Id vecType(spv::Id elemType, int elemCount)\n   {\n      if (elemCount == 1) {\n         return elemType;\n      }\n\n      auto vecPair = std::make_pair(elemType, elemCount);\n      auto vecType = mVecType[vecPair];\n      if (!vecType) {\n         vecType = makeVectorType(elemType, elemCount);\n\n         auto baseTypeName = getTypeName(elemType);\n         if (baseTypeName.empty()) {\n            decaf_abort(\"Unexpected element type for vector type\");\n         }\n         addName(vecType, fmt::format(\"{}{}\", baseTypeName, elemCount).c_str());\n\n         mVecType[vecPair] = vecType;\n      }\n      return vecType;\n   }\n\n   spv::Id arrayType(spv::Id elemType, int stride, int elemCount = 0)\n   {\n      auto arrPair = std::make_pair(elemType, elemCount);\n      auto arrType = mArrType[arrPair];\n      if (!arrType) {\n         if (elemCount == 0) {\n            arrType = makeRuntimeArray(elemType);\n         } else {\n            auto sizeId = makeUintConstant(elemCount);\n            arrType = makeArrayType(elemType, sizeId, stride);\n         }\n         addDecoration(arrType, spv::Decoration::DecorationArrayStride, stride);\n\n         auto baseTypeName = getTypeName(elemType);\n         if (!baseTypeName.size()) {\n            decaf_abort(\"Unexpected element type for vector type\");\n         }\n         if (elemCount == 0) {\n            addName(arrType, fmt::format(\"{}[]\", baseTypeName).c_str());\n         } else {\n            addName(arrType, fmt::format(\"{}[{}]\", baseTypeName, elemCount).c_str());\n         }\n\n         mArrType[arrPair] = arrType;\n      }\n      return arrType;\n   }\n\n\n   // ------------------------------------------------------------\n   // Extension Library Access\n   // ------------------------------------------------------------\n\n   spv::Id glslStd450()\n   {\n      if (!mGlslStd450) {\n         mGlslStd450 = import(\"GLSL.std.450\");\n         addName(mGlslStd450, \"glslStd450\");\n      }\n      return mGlslStd450;\n   }\n\n\n   // ------------------------------------------------------------\n   // Type Names\n   // ------------------------------------------------------------\n\n   std::string getTypeName(spv::Id typeId)\n   {\n      for (auto &nameInst : names) {\n         if (nameInst->getIdOperand(0) == typeId) {\n            auto numCharOps = nameInst->getNumOperands() - 1;\n            char *readName = new char[numCharOps * 4];\n            for (auto i = 0; i < numCharOps; ++i) {\n               auto pieceValue = nameInst->getImmediateOperand(1 + i);\n               readName[i * 4 + 0] = (pieceValue >> 0) & 0xFF;\n               readName[i * 4 + 1] = (pieceValue >> 8) & 0xFF;\n               readName[i * 4 + 2] = (pieceValue >> 16) & 0xFF;\n               readName[i * 4 + 3] = (pieceValue >> 24) & 0xFF;\n            }\n            std::string readNameStr(readName);\n            delete[] readName;\n            return readNameStr;\n         }\n      }\n      return std::string();\n   }\n\n\n   // ------------------------------------------------------------\n   // Byte Swapping\n   // ------------------------------------------------------------\n\n   spv::Id bswap8in16(spv::Id srcId)\n   {\n      auto sourceTypeId = getTypeId(srcId);\n\n      auto inputTypeId = sourceTypeId;\n      auto numComps = getNumTypeComponents(inputTypeId);\n      if (isVectorType(inputTypeId)) {\n         inputTypeId = getContainedTypeId(inputTypeId);\n      }\n\n      if (inputTypeId == uintType()) {\n         auto xoxoConst = makeUintConstant(0xFF00FF00);\n         auto oxoxConst = makeUintConstant(0x00FF00FF);\n         auto shiftConst = makeUintConstant(8);\n\n         if (numComps > 1) {\n            xoxoConst = vectorizeConstant(xoxoConst, numComps);\n            oxoxConst = vectorizeConstant(oxoxConst, numComps);\n            shiftConst = vectorizeConstant(shiftConst, numComps);\n         }\n\n         auto xoxoBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, xoxoConst);\n         auto oxoxBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, oxoxConst);\n\n         auto oxoxBitsMoved = createBinOp(spv::Op::OpShiftRightLogical, sourceTypeId, xoxoBits, shiftConst);\n         auto xoxoBitsMoved = createBinOp(spv::Op::OpShiftLeftLogical, sourceTypeId, oxoxBits, shiftConst);\n\n         return createBinOp(spv::Op::OpBitwiseOr, sourceTypeId, oxoxBitsMoved, xoxoBitsMoved);\n      } else if (inputTypeId == ushortType()) {\n         auto xoConst = makeUint16Constant(0xFF00);\n         auto oxConst = makeUint16Constant(0x00FF);\n         auto shiftConst = makeUintConstant(8);\n\n         if (numComps > 1) {\n            xoConst = vectorizeConstant(xoConst, numComps);\n            oxConst = vectorizeConstant(oxConst, numComps);\n            shiftConst = vectorizeConstant(shiftConst, numComps);\n         }\n\n         auto xoBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, xoConst);\n         auto oxBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, oxConst);\n\n         auto oxBitsMoved = createBinOp(spv::Op::OpShiftRightLogical, sourceTypeId, xoBits, shiftConst);\n         auto xoBitsMoved = createBinOp(spv::Op::OpShiftLeftLogical, sourceTypeId, oxBits, shiftConst);\n\n         return createBinOp(spv::Op::OpBitwiseOr, sourceTypeId, oxBitsMoved, xoBitsMoved);\n      } else {\n         decaf_abort(\"Attempted to do 8in16 byte swap on invalid type\");\n      }\n   }\n\n   spv::Id bswap8in32(spv::Id srcId)\n   {\n      auto sourceTypeId = getTypeId(srcId);\n\n      auto xoooConst = makeUintConstant(0xFF000000);\n      auto oxooConst = makeUintConstant(0x00FF0000);\n      auto ooxoConst = makeUintConstant(0x0000FF00);\n      auto oooxConst = makeUintConstant(0x000000FF);\n      auto littleShiftConst = makeUintConstant(8);\n      auto bigShiftConst = makeUintConstant(24);\n\n      auto inputTypeId = sourceTypeId;\n      if (isVectorType(inputTypeId)) {\n         auto numComps = getNumTypeComponents(inputTypeId);\n         inputTypeId = getContainedTypeId(inputTypeId);\n\n         xoooConst = vectorizeConstant(xoooConst, numComps);\n         oxooConst = vectorizeConstant(oxooConst, numComps);\n         ooxoConst = vectorizeConstant(ooxoConst, numComps);\n         oooxConst = vectorizeConstant(oooxConst, numComps);\n         littleShiftConst = vectorizeConstant(littleShiftConst, numComps);\n         bigShiftConst = vectorizeConstant(bigShiftConst, numComps);\n      }\n      decaf_check(inputTypeId == uintType());\n\n      auto xoooBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, xoooConst);\n      auto oxooBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, oxooConst);\n      auto ooxoBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, ooxoConst);\n      auto oooxBits = createBinOp(spv::Op::OpBitwiseAnd, sourceTypeId, srcId, oooxConst);\n\n      auto xoooBitsMoved = createBinOp(spv::Op::OpShiftLeftLogical, sourceTypeId, oooxBits, bigShiftConst);\n      auto oxooBitsMoved = createBinOp(spv::Op::OpShiftLeftLogical, sourceTypeId, ooxoBits, littleShiftConst);\n      auto ooxoBitsMoved = createBinOp(spv::Op::OpShiftRightLogical, sourceTypeId, oxooBits, littleShiftConst);\n      auto oooxBitsMoved = createBinOp(spv::Op::OpShiftRightLogical, sourceTypeId, xoooBits, bigShiftConst);\n\n      auto xxooBitsMerged = createBinOp(spv::Op::OpBitwiseOr, sourceTypeId, xoooBitsMoved, oxooBitsMoved);\n      auto xxxoBitsMerged = createBinOp(spv::Op::OpBitwiseOr, sourceTypeId, xxooBitsMerged, ooxoBitsMoved);\n      return createBinOp(spv::Op::OpBitwiseOr, sourceTypeId, xxxoBitsMerged, oooxBitsMoved);\n   }\n\n   /*\n   // From OpenGL ES 3.0 spec 2.1.3\n   function from11uf(v) {\n     const e = v >> 6;\n     const m = v & 0x3F;\n     if (e === 0) {\n       if (m === 0) {\n         return 0;\n       } else {\n         return Math.pow(2, -14) * (m / 64);\n       }\n     } else {\n       if (e < 31) {\n         return Math.pow(2, e - 15) * (1 + m / 64);\n       } else {\n         if (m === 0) {\n           return 0;  // Inf\n         } else {\n           return 0;  // Nan\n         }\n       }\n     }\n   }\n   */\n   spv::Id unpackFloat11(spv::Id srcId)\n   {\n      decaf_check(getTypeId(srcId) == uintType());\n\n      auto resPtr = createVariable(spv::NoPrecision, spv::StorageClassPrivate, floatType(), \"unpackF11Res\");\n\n      auto eVal = createBinOp(spv::OpShiftRightLogical, uintType(), srcId, makeIntConstant(6));\n      auto mVal = createBinOp(spv::OpBitwiseAnd, uintType(), srcId, makeUintConstant(0x3F));\n\n      auto eIsZero = createBinOp(spv::Op::OpIEqual, boolType(), eVal, makeUintConstant(0));\n      auto eIsZeroBlock = spv::Builder::If { eIsZero, spv::SelectionControlMaskNone, *this };\n      {\n         auto mIsZero = createBinOp(spv::Op::OpIEqual, boolType(), mVal, makeUintConstant(0));\n         auto mIsZeroBlk = spv::Builder::If { mIsZero, spv::SelectionControlMaskNone, *this };\n         {\n            createStore(makeFloatConstant(0.0f), resPtr);\n         }\n         mIsZeroBlk.makeBeginElse();\n         {\n            auto mFlt = createUnaryOp(spv::OpConvertUToF, floatType(), mVal);\n            auto mDiv64 = createBinOp(spv::OpFDiv, floatType(), mFlt, makeFloatConstant(64.0f));\n            auto resVal = createBinOp(spv::OpFMul, floatType(), mDiv64, makeFloatConstant(powf(2.0f, -14)));\n            createStore(resVal, resPtr);\n         }\n         mIsZeroBlk.makeEndIf();\n      }\n      eIsZeroBlock.makeBeginElse();\n      {\n         auto eLessThan31 = createBinOp(spv::OpULessThan, boolType(), eVal, makeUintConstant(31));\n         auto eLessThan31Blk = spv::Builder::If { eLessThan31, spv::SelectionControlMaskNone, *this };\n         {\n            auto eMinus15 = createBinOp(spv::OpISub, uintType(), eVal, makeUintConstant(15));\n            auto eMinus15Flt = createUnaryOp(spv::OpConvertUToF, floatType(), eMinus15);\n            auto ePow = createBuiltinCall(floatType(), glslStd450(), GLSLstd450::GLSLstd450Pow, { makeFloatConstant(2.0f), eMinus15Flt });\n\n            auto mFlt = createUnaryOp(spv::OpConvertUToF, floatType(), mVal);\n            auto mDiv64 = createBinOp(spv::OpFDiv, floatType(), mFlt, makeFloatConstant(64.0f));\n            auto mDiv64Plus1 = createBinOp(spv::OpFAdd, floatType(), mDiv64, makeFloatConstant(1.0f));\n            auto resVal = createBinOp(spv::OpFMul, floatType(), ePow, mDiv64Plus1);\n            createStore(resVal, resPtr);\n         }\n         eLessThan31Blk.makeBeginElse();\n         {\n            auto mIsZero = createBinOp(spv::Op::OpIEqual, boolType(), mVal, makeUintConstant(0));\n            auto mIsZeroBlk = spv::Builder::If { mIsZero, spv::SelectionControlMaskNone, *this };\n            {\n               createStore(makeFloatConstant(0.0f), resPtr); // INF\n            }\n            mIsZeroBlk.makeBeginElse();\n            {\n               createStore(makeFloatConstant(0.0f), resPtr); // NAN\n            }\n            mIsZeroBlk.makeEndIf();\n         }\n         eLessThan31Blk.makeEndIf();\n      }\n      eIsZeroBlock.makeEndIf();\n\n      return createLoad(resPtr, spv::NoPrecision);\n   }\n   /*\n   // From OpenGL ES 3.0 spec 2.1.4\n   function from10uf(v) {\n     const e = v >> 5;\n     const m = v & 0x1F;\n     if (e === 0) {\n       if (m === 0) {\n         return 0;\n       } else {\n         return Math.pow(2, -14) * (m / 32);\n       }\n     } else {\n       if (e < 31) {\n         return Math.pow(2, e - 15) * (1 + m / 32);\n       } else {\n         if (m === 0) {\n           return 0;  // Inf\n         } else {\n           return 0;  // Nan\n         }\n       }\n     }\n   }\n   */\n   spv::Id unpackFloat10(spv::Id srcId)\n   {\n      decaf_check(getTypeId(srcId) == uintType());\n\n      auto resPtr = createVariable(spv::NoPrecision, spv::StorageClassPrivate, floatType(), \"unpackF11Res\");\n\n      auto eVal = createBinOp(spv::OpShiftRightLogical, uintType(), srcId, makeIntConstant(5));\n      auto mVal = createBinOp(spv::OpBitwiseAnd, uintType(), srcId, makeUintConstant(0x1F));\n\n      auto eIsZero = createBinOp(spv::Op::OpIEqual, boolType(), eVal, makeUintConstant(0));\n      auto eIsZeroBlock = spv::Builder::If { eIsZero, spv::SelectionControlMaskNone, *this };\n      {\n         auto mIsZero = createBinOp(spv::Op::OpIEqual, boolType(), mVal, makeUintConstant(0));\n         auto mIsZeroBlk = spv::Builder::If { mIsZero, spv::SelectionControlMaskNone, *this };\n         {\n            createStore(makeFloatConstant(0.0f), resPtr);\n         }\n         mIsZeroBlk.makeBeginElse();\n         {\n            auto mFlt = createUnaryOp(spv::OpConvertUToF, floatType(), mVal);\n            auto mDiv64 = createBinOp(spv::OpFDiv, floatType(), mFlt, makeFloatConstant(32.0f));\n            auto resVal = createBinOp(spv::OpFMul, floatType(), mDiv64, makeFloatConstant(pow(2.0f, -14)));\n            createStore(resVal, resPtr);\n         }\n         mIsZeroBlk.makeEndIf();\n      }\n      eIsZeroBlock.makeBeginElse();\n      {\n         auto eLessThan31 = createBinOp(spv::OpULessThan, boolType(), eVal, makeUintConstant(31));\n         auto eLessThan31Blk = spv::Builder::If { eLessThan31, spv::SelectionControlMaskNone, *this };\n         {\n            auto eMinus15 = createBinOp(spv::OpISub, uintType(), eVal, makeUintConstant(15));\n            auto eMinus15Flt = createUnaryOp(spv::OpConvertUToF, floatType(), eMinus15);\n            auto ePow = createBuiltinCall(floatType(), glslStd450(), GLSLstd450::GLSLstd450Pow, { makeFloatConstant(2.0f), eMinus15Flt });\n\n            auto mFlt = createUnaryOp(spv::OpConvertUToF, floatType(), mVal);\n            auto mDiv64 = createBinOp(spv::OpFDiv, floatType(), mFlt, makeFloatConstant(32.0f));\n            auto mDiv64Plus1 = createBinOp(spv::OpFAdd, floatType(), mDiv64, makeFloatConstant(1.0f));\n            auto resVal = createBinOp(spv::OpFMul, floatType(), ePow, mDiv64Plus1);\n            createStore(resVal, resPtr);\n         }\n         eLessThan31Blk.makeBeginElse();\n         {\n            auto mIsZero = createBinOp(spv::Op::OpIEqual, boolType(), mVal, makeUintConstant(0));\n            auto mIsZeroBlk = spv::Builder::If { mIsZero, spv::SelectionControlMaskNone, *this };\n            {\n               createStore(makeFloatConstant(0.0f), resPtr); // INF\n            }\n            mIsZeroBlk.makeBeginElse();\n            {\n               createStore(makeFloatConstant(0.0f), resPtr); // NAN\n            }\n            mIsZeroBlk.makeEndIf();\n         }\n         eLessThan31Blk.makeEndIf();\n      }\n      eIsZeroBlock.makeEndIf();\n\n      return createLoad(resPtr, spv::NoPrecision);\n   }\nprotected:\n   std::unordered_map<unsigned int, spv::Id> mUintConstants;\n   std::unordered_map<uint32_t, spv::Id> mFloatConstants;\n\n   spv::Id mSamplerType = spv::NoResult;\n   spv::Id mBoolType = spv::NoResult;\n   spv::Id mFloatType = spv::NoResult;\n   spv::Id mUbyteType = spv::NoResult;\n   spv::Id mUshortType = spv::NoResult;\n   spv::Id mIntType = spv::NoResult;\n   spv::Id mUintType = spv::NoResult;\n   std::map<std::pair<spv::Id, int>, spv::Id> mVecType;\n   std::map<std::pair<spv::Id, int>, spv::Id> mArrType;\n\n   spv::Id mGlslStd450 = spv::NoResult;\n\n};\n\n} // namespace gpu\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_tex.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"spirv_transpiler.h\"\n\nnamespace spirv\n{\n\nvoid Transpiler::translateGenericSample(const ControlFlowInst &cf, const TextureFetchInst &inst, uint32_t sampleMode)\n{\n   // inst.word0.FETCH_WHOLE_QUAD(); - Optimization which can be ignored\n   // inst.word1.LOD_BIAS(); - Only used by certain SAMPLE instructions\n\n   decaf_check(!inst.word0.BC_FRAC_MODE());\n\n   if (mType != ShaderParser::Type::Pixel) {\n      // If we are not in the pixel shader, we need to automatically use\n      // LOD zero, even if implicit LOD is specified by the instruction.\n      if (!(sampleMode & SampleMode::Gather)) {\n         sampleMode |= SampleMode::LodZero;\n      }\n   }\n\n   auto textureId = inst.word0.RESOURCE_ID();\n   auto samplerId = inst.word2.SAMPLER_ID();\n   auto texDim = mTexDims[textureId];\n   auto texFormat = mTexFormats[textureId];\n\n   auto expectedCoordType = SQ_TEX_COORD_TYPE::NORMALIZED;\n   if (sampleMode & SampleMode::Load) {\n      expectedCoordType = SQ_TEX_COORD_TYPE::UNNORMALIZED;\n   }\n\n   switch (texDim) {\n   case latte::SQ_TEX_DIM::DIM_1D:\n      decaf_check(inst.word1.COORD_TYPE_X() == expectedCoordType);\n      break;\n   case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n      decaf_check(inst.word1.COORD_TYPE_X() == expectedCoordType);\n      break;\n   case latte::SQ_TEX_DIM::DIM_2D:\n   case latte::SQ_TEX_DIM::DIM_2D_MSAA:\n      break;\n   case latte::SQ_TEX_DIM::DIM_3D:\n   case latte::SQ_TEX_DIM::DIM_CUBEMAP:\n      decaf_check(inst.word1.COORD_TYPE_X() == expectedCoordType);\n      decaf_check(inst.word1.COORD_TYPE_Y() == expectedCoordType);\n      decaf_check(inst.word1.COORD_TYPE_Z() == expectedCoordType);\n      break;\n   case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n   case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n      decaf_check(inst.word1.COORD_TYPE_X() == expectedCoordType);\n      decaf_check(inst.word1.COORD_TYPE_Y() == expectedCoordType);\n      break;\n   default:\n      decaf_abort(\"Unexpected texture sample dim\");\n   }\n\n   GprMaskRef srcGpr;\n   srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP);\n   srcGpr.mask[SQ_CHAN::X] = inst.word2.SRC_SEL_X();\n   srcGpr.mask[SQ_CHAN::Y] = inst.word2.SRC_SEL_Y();\n   srcGpr.mask[SQ_CHAN::Z] = inst.word2.SRC_SEL_Z();\n   srcGpr.mask[SQ_CHAN::W] = inst.word2.SRC_SEL_W();\n\n   GprMaskRef destGpr;\n   destGpr.gpr = makeGprRef(inst.word1.DST_GPR(), inst.word1.DST_REL(), SQ_INDEX_MODE::LOOP);\n   destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X();\n   destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y();\n   destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z();\n   destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W();\n\n   auto lodBias = static_cast<float>(inst.word1.LOD_BIAS());\n   auto offsetX = static_cast<int>(inst.word2.OFFSET_X());\n   auto offsetY = static_cast<int>(inst.word2.OFFSET_Y());\n   auto offsetZ = static_cast<int>(inst.word2.OFFSET_Z());\n\n   auto srcGprVal = mSpv->readGprMaskRef(srcGpr);\n\n   uint32_t imageOperands = 0;\n   std::vector<unsigned int> operandParams;\n\n   if (sampleMode & SampleMode::LodBias) {\n      imageOperands |= spv::ImageOperandsBiasMask;\n\n      auto lodSource = mSpv->makeFloatConstant(lodBias);\n      operandParams.push_back(lodSource);\n   }\n\n   if (sampleMode & SampleMode::Lod) {\n      imageOperands |= spv::ImageOperandsLodMask;\n\n      auto lodSource = mSpv->createOp(spv::OpCompositeExtract, mSpv->floatType(), { srcGprVal, 3 });\n      operandParams.push_back(lodSource);\n   } else if (sampleMode & SampleMode::LodZero) {\n      imageOperands |= spv::ImageOperandsLodMask;\n\n      auto lodSource = mSpv->makeFloatConstant(0.0f);\n      operandParams.push_back(lodSource);\n   }\n\n   if (sampleMode & SampleMode::Gradient) {\n      decaf_abort(\"We do not currently support gradient instructions\");\n   }\n\n   if (offsetX > 0 || offsetY > 0 || offsetZ > 0) {\n      imageOperands |= spv::ImageOperandsConstOffsetMask;\n\n      spv::Id offsetVal = spv::NoResult;\n      switch (texDim) {\n      case latte::SQ_TEX_DIM::DIM_1D:\n         offsetVal = mSpv->makeIntConstant(offsetX);\n         break;\n      case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n      case latte::SQ_TEX_DIM::DIM_2D:\n      case latte::SQ_TEX_DIM::DIM_2D_MSAA:\n      case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n      case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n      case latte::SQ_TEX_DIM::DIM_CUBEMAP:\n         offsetVal = mSpv->makeCompositeConstant(mSpv->int2Type(), {\n                                                 mSpv->makeIntConstant(offsetX),\n                                                 mSpv->makeIntConstant(offsetY) });\n         break;\n      case latte::SQ_TEX_DIM::DIM_3D:\n         offsetVal = mSpv->makeCompositeConstant(mSpv->int3Type(), {\n                                                 mSpv->makeIntConstant(offsetX),\n                                                 mSpv->makeIntConstant(offsetY),\n                                                 mSpv->makeIntConstant(offsetZ) });\n         break;\n      default:\n         decaf_abort(\"Unexpected texture sample dim\");\n      }\n\n      operandParams.push_back(offsetVal);\n   }\n\n   auto texVarType = mSpv->textureVarType(textureId, texDim, texFormat);\n   auto texVar = mSpv->textureVar(textureId, texDim, texFormat);\n   auto texVal = mSpv->createLoad(texVar, spv::NoPrecision);\n\n   // Lets build our actual operation\n   spv::Op sampleOp = spv::OpNop;\n   std::vector<unsigned int> sampleParams;\n\n   if (!(sampleMode & SampleMode::Load)) {\n      auto sampVar = mSpv->samplerVar(samplerId);\n      auto sampVal = mSpv->createLoad(sampVar, spv::NoPrecision);\n      auto sampledType = mSpv->makeSampledImageType(texVarType);\n      auto sampledVal = mSpv->createOp(spv::OpSampledImage, sampledType, { texVal, sampVal });\n      sampleParams.push_back(sampledVal);\n\n      spv::Id srcCoords = srcGprVal;\n      switch (texDim) {\n      case latte::SQ_TEX_DIM::DIM_1D:\n         srcCoords = mSpv->createOp(spv::OpCompositeExtract, mSpv->floatType(), { srcCoords, 0 });\n         break;\n      case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n      case latte::SQ_TEX_DIM::DIM_2D:\n      case latte::SQ_TEX_DIM::DIM_2D_MSAA:\n         srcCoords = mSpv->createOp(spv::OpVectorShuffle, mSpv->float2Type(), { srcCoords, srcCoords, 0, 1 });\n         break;\n      case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n      case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n      case latte::SQ_TEX_DIM::DIM_3D:\n      case latte::SQ_TEX_DIM::DIM_CUBEMAP:\n         srcCoords = mSpv->createOp(spv::OpVectorShuffle, mSpv->float3Type(), { srcCoords, srcCoords, 0, 1, 2 });\n         break;\n      default:\n         decaf_abort(\"Unexpected texture sample dim\");\n      }\n      sampleParams.push_back(srcCoords);\n\n      // Pick the operation, and potentially add the comparison.\n      if (sampleMode & SampleMode::Gather) {\n         // It is not legal to combine this with any other kind of operation\n         decaf_check(sampleMode == SampleMode::Gather);\n\n         sampleOp = spv::OpImageGather;\n\n         auto compToFetch = mSpv->makeUintConstant(0);\n         sampleParams.push_back(compToFetch);\n      } else if (sampleMode & SampleMode::Compare) {\n         auto drefVal = mSpv->createOp(spv::OpCompositeExtract, mSpv->floatType(), { srcGprVal, 3 });\n\n         if (imageOperands & spv::ImageOperandsLodMask) {\n            sampleOp = spv::OpImageSampleDrefExplicitLod;\n         } else {\n            sampleOp = spv::OpImageSampleDrefImplicitLod;\n         }\n\n         sampleParams.push_back(drefVal);\n      } else {\n         if (imageOperands & spv::ImageOperandsLodMask) {\n            sampleOp = spv::OpImageSampleExplicitLod;\n         } else {\n            sampleOp = spv::OpImageSampleImplicitLod;\n         }\n      }\n   } else {\n      // It is not legal to combine this with any other kind of operation\n      decaf_check(sampleMode == SampleMode::Load);\n\n      spv::Id srcCoords = srcGprVal;\n      srcCoords = mSpv->createUnaryOp(spv::OpBitcast, mSpv->uint4Type(), srcCoords);\n\n      switch (texDim) {\n      case latte::SQ_TEX_DIM::DIM_1D:\n         srcCoords = mSpv->createOp(spv::OpCompositeExtract, mSpv->uintType(), { srcCoords, 0 });\n         break;\n      case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n      case latte::SQ_TEX_DIM::DIM_2D:\n      case latte::SQ_TEX_DIM::DIM_2D_MSAA:\n         srcCoords = mSpv->createOp(spv::OpVectorShuffle, mSpv->uint2Type(), { srcCoords, srcCoords, 0, 1 });\n         break;\n      case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n      case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n      case latte::SQ_TEX_DIM::DIM_3D:\n      case latte::SQ_TEX_DIM::DIM_CUBEMAP:\n         srcCoords = mSpv->createOp(spv::OpVectorShuffle, mSpv->uint3Type(), { srcCoords, srcCoords, 0, 1, 2 });\n         break;\n      default:\n         decaf_abort(\"Unexpected texture sample dim\");\n      }\n\n      sampleParams.push_back(texVal);\n      sampleParams.push_back(srcCoords);\n\n      sampleOp = spv::OpImageFetch;\n   }\n\n   decaf_check(sampleOp != spv::OpNop);\n\n   // Merge all the image operands in now.\n   if (imageOperands != 0) {\n      sampleParams.push_back(imageOperands);\n\n      decaf_check(!operandParams.empty());\n      for (auto& param : operandParams) {\n         sampleParams.push_back(param);\n      }\n   } else {\n      decaf_check(operandParams.empty());\n   }\n\n   auto zeroFVal = mSpv->makeFloatConstant(0.0f);\n   auto oneFVal = mSpv->makeFloatConstant(1.0f);\n\n   spv::Id output = spv::NoResult;\n   if (sampleMode & SampleMode::Compare) {\n      // We do not support doing depth comparison on a UINT depth buffer\n      decaf_check(texFormat == TextureInputType::FLOAT);\n\n      auto compareVal = mSpv->createOp(sampleOp, mSpv->floatType(), sampleParams);\n\n      // TODO: This is really cheating, we should just check the write mask\n      // and do a single component write instead...\n      output = mSpv->createCompositeConstruct(mSpv->float4Type(), { compareVal, zeroFVal, zeroFVal, oneFVal });\n   } else {\n      if (texFormat == TextureInputType::FLOAT) {\n         output = mSpv->createOp(sampleOp, mSpv->float4Type(), sampleParams);\n      } else if (texFormat == TextureInputType::INT) {\n         output = mSpv->createOp(sampleOp, mSpv->uint4Type(), sampleParams);\n      } else {\n         decaf_abort(\"Unexpected texture format type\");\n      }\n   }\n\n   mSpv->writeGprMaskRef(destGpr, output);\n}\n\nvoid Transpiler::translateTex_LD(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Load);\n}\n\nvoid Transpiler::translateTex_FETCH4(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Gather);\n}\n\nvoid Transpiler::translateTex_SAMPLE(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::None);\n}\n\nvoid Transpiler::translateTex_SAMPLE_L(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Lod);\n}\n\nvoid Transpiler::translateTex_SAMPLE_LB(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::LodBias);\n}\n\nvoid Transpiler::translateTex_SAMPLE_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::LodZero);\n}\n\nvoid Transpiler::translateTex_SAMPLE_G(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Gradient);\n}\n\nvoid Transpiler::translateTex_SAMPLE_G_L(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Gradient | SampleMode::Lod);\n}\n\nvoid Transpiler::translateTex_SAMPLE_G_LB(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Gradient | SampleMode::LodBias);\n}\n\nvoid Transpiler::translateTex_SAMPLE_G_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Gradient | SampleMode::LodZero);\n}\n\nvoid Transpiler::translateTex_SAMPLE_C(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Compare);\n}\n\nvoid Transpiler::translateTex_SAMPLE_C_L(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::Lod);\n}\n\nvoid Transpiler::translateTex_SAMPLE_C_LB(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::LodBias);\n}\n\nvoid Transpiler::translateTex_SAMPLE_C_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::LodZero);\n}\n\nvoid Transpiler::translateTex_SAMPLE_C_G(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::Gradient);\n}\n\nvoid Transpiler::translateTex_SAMPLE_C_G_L(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::Gradient | SampleMode::Lod);\n}\n\nvoid Transpiler::translateTex_SAMPLE_C_G_LB(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::Gradient | SampleMode::LodBias);\n}\n\nvoid Transpiler::translateTex_SAMPLE_C_G_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   translateGenericSample(cf, inst, SampleMode::Compare | SampleMode::Gradient | SampleMode::LodZero);\n}\n\nvoid Transpiler::translateTex_GET_TEXTURE_INFO(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   auto textureId = inst.word0.RESOURCE_ID();\n   // inst.word2.SAMPLER_ID();\n   auto texDim = mTexDims[textureId];\n   auto texFormat = mTexFormats[textureId];\n\n   GprMaskRef srcGpr;\n   srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP);\n   srcGpr.mask[SQ_CHAN::X] = inst.word2.SRC_SEL_X();\n   srcGpr.mask[SQ_CHAN::Y] = inst.word2.SRC_SEL_Y();\n   srcGpr.mask[SQ_CHAN::Z] = inst.word2.SRC_SEL_Z();\n   srcGpr.mask[SQ_CHAN::W] = inst.word2.SRC_SEL_W();\n\n   GprMaskRef destGpr;\n   destGpr.gpr = makeGprRef(inst.word1.DST_GPR(), inst.word1.DST_REL(), SQ_INDEX_MODE::LOOP);\n   destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X();\n   destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y();\n   destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z();\n   destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W();\n\n   // We have to register the image-query capability in order to query texture data.\n   mSpv->addCapability(spv::CapabilityImageQuery);\n\n   auto texVar = mSpv->textureVar(textureId, texDim, texFormat);\n   auto image = mSpv->createLoad(texVar, spv::NoPrecision);\n\n   auto srcGprVal = mSpv->readGprMaskRef(srcGpr);\n   auto srcLodValFloat = mSpv->createOp(spv::OpCompositeExtract, mSpv->floatType(), { srcGprVal, 0 });\n   auto srcLodVal = mSpv->createUnaryOp(spv::OpBitcast, mSpv->intType(), srcLodValFloat);\n\n   auto oneIConst = mSpv->makeIntConstant(1);\n   auto sizeIConst = mSpv->makeIntConstant(6);\n\n   spv::Id sizeInfo;\n   switch (texDim) {\n   case latte::SQ_TEX_DIM::DIM_1D: {\n      auto sizeInfo1d = mSpv->createBinOp(spv::OpImageQuerySizeLod, mSpv->intType(), image, srcLodVal);\n      sizeInfo = mSpv->createOp(spv::OpCompositeConstruct, mSpv->int3Type(), { sizeInfo1d, oneIConst, oneIConst });\n      break;\n   }\n   case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n   case latte::SQ_TEX_DIM::DIM_2D:\n   case latte::SQ_TEX_DIM::DIM_2D_MSAA: {\n      auto sizeInfo2d = mSpv->createBinOp(spv::OpImageQuerySizeLod, mSpv->int2Type(), image, srcLodVal);\n      sizeInfo = mSpv->createOp(spv::OpCompositeConstruct, mSpv->int3Type(), { sizeInfo2d, oneIConst });\n      break;\n   }\n   case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n   case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n   case latte::SQ_TEX_DIM::DIM_3D: {\n      sizeInfo = mSpv->createBinOp(spv::OpImageQuerySizeLod, mSpv->int3Type(), image, srcLodVal);\n      break;\n   }\n   case latte::SQ_TEX_DIM::DIM_CUBEMAP: {\n      auto sizeInfoCube = mSpv->createBinOp(spv::OpImageQuerySizeLod, mSpv->int3Type(), image, srcLodVal);\n      auto cubemapArrSideSize = mSpv->createOp(spv::OpCompositeExtract, mSpv->intType(), { sizeInfoCube, 3 });\n      auto cubemapArrSize = mSpv->createBinOp(spv::OpSDiv, mSpv->intType(), cubemapArrSideSize, sizeIConst);\n      sizeInfo = mSpv->createOp(spv::OpCompositeInsert, mSpv->int3Type(), { cubemapArrSize, sizeInfoCube, 3 });\n      break;\n   }\n   default:\n      decaf_abort(\"Unexpected texture sample dim\");\n   }\n\n   auto levelInfo = mSpv->createUnaryOp(spv::OpImageQueryLevels, mSpv->intType(), image);\n   auto output = mSpv->createOp(spv::OpCompositeConstruct, mSpv->int4Type(), { sizeInfo, levelInfo });\n\n   mSpv->writeGprMaskRef(destGpr, output);\n}\n\nvoid Transpiler::translateTex_SET_CUBEMAP_INDEX(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   // TODO: It is possible that we are supposed to somehow force\n   //  a specific face to be used in spite of the coordinates.\n   //decaf_abort(\"Unsupported SET_CUBEMAP_INDEX\")\n}\n\nvoid Transpiler::translateTex_GET_GRADIENTS_H(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   auto textureId = inst.word0.RESOURCE_ID();\n   auto samplerId = inst.word2.SAMPLER_ID();\n   decaf_check(textureId == 0);\n   decaf_check(samplerId == 0);\n\n   // TODO: Make a latte decoder for reading these registers...\n   GprMaskRef srcGpr;\n   srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP);\n   srcGpr.mask[SQ_CHAN::X] = inst.word2.SRC_SEL_X();\n   srcGpr.mask[SQ_CHAN::Y] = inst.word2.SRC_SEL_Y();\n   srcGpr.mask[SQ_CHAN::Z] = inst.word2.SRC_SEL_Z();\n   srcGpr.mask[SQ_CHAN::W] = inst.word2.SRC_SEL_W();\n\n   GprMaskRef destGpr;\n   destGpr.gpr = makeGprRef(inst.word1.DST_GPR(), inst.word1.DST_REL(), SQ_INDEX_MODE::LOOP);\n   destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X();\n   destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y();\n   destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z();\n   destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W();\n\n   auto srcGprVal = mSpv->readGprMaskRef(srcGpr);\n\n   mSpv->addCapability(spv::CapabilityDerivativeControl);\n   auto output = mSpv->createUnaryOp(spv::OpDPdx, mSpv->float4Type(), srcGprVal);\n\n   mSpv->writeGprMaskRef(destGpr, output);\n}\n\nvoid Transpiler::translateTex_GET_GRADIENTS_V(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   auto textureId = inst.word0.RESOURCE_ID();\n   auto samplerId = inst.word2.SAMPLER_ID();\n   decaf_check(textureId == 0);\n   decaf_check(samplerId == 0);\n\n   // TODO: Make a latte decoder for reading these registers...\n   GprMaskRef srcGpr;\n   srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP);\n   srcGpr.mask[SQ_CHAN::X] = inst.word2.SRC_SEL_X();\n   srcGpr.mask[SQ_CHAN::Y] = inst.word2.SRC_SEL_Y();\n   srcGpr.mask[SQ_CHAN::Z] = inst.word2.SRC_SEL_Z();\n   srcGpr.mask[SQ_CHAN::W] = inst.word2.SRC_SEL_W();\n\n   GprMaskRef destGpr;\n   destGpr.gpr = makeGprRef(inst.word1.DST_GPR(), inst.word1.DST_REL(), SQ_INDEX_MODE::LOOP);\n   destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X();\n   destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y();\n   destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z();\n   destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W();\n\n   auto srcGprVal = mSpv->readGprMaskRef(srcGpr);\n\n   mSpv->addCapability(spv::CapabilityDerivativeControl);\n   auto output = mSpv->createUnaryOp(spv::OpDPdy, mSpv->float4Type(), srcGprVal);\n\n   mSpv->writeGprMaskRef(destGpr, output);\n}\n\nvoid Transpiler::translateTex_VTX_FETCH(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   // The TextureFetchInst perfectly matches the VertexFetchInst\n   translateVtx_FETCH(cf, *reinterpret_cast<const VertexFetchInst*>(&inst));\n}\n\nvoid Transpiler::translateTex_VTX_SEMANTIC(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   // The TextureFetchInst perfectly matches the VertexFetchInst\n   translateVtx_SEMANTIC(cf, *reinterpret_cast<const VertexFetchInst*>(&inst));\n}\n\n} // namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_translate.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n\n#include \"latte/latte_constants.h\"\n#include \"latte/latte_registers_cb.h\"\n#include \"latte/latte_registers_db.h\"\n#include \"latte/latte_registers_sq.h\"\n#include \"latte/latte_registers_spi.h\"\n#include \"latte/latte_registers_pa.h\"\n#include \"latte/latte_registers_vgt.h\"\n\n#include <common/datahash.h>\n#include <gsl/gsl-lite.hpp>\n\nnamespace spirv\n{\n\nenum class ShaderType : uint32_t\n{\n   Unknown,\n   Vertex,\n   Geometry,\n   Pixel\n};\n\nenum class TextureInputType : uint32_t\n{\n   NONE,\n   FLOAT,\n   INT\n};\n\nenum class PixelOutputType : uint32_t\n{\n   FLOAT,\n   SINT,\n   UINT\n};\n\nstruct AttribBuffer\n{\n   enum class IndexMode : uint32_t\n   {\n      PerVertex,\n      PerInstance,\n   };\n\n   enum class DivisorMode : uint32_t\n   {\n      CONST_1,\n      REGISTER_0,\n      REGISTER_1\n   };\n\n   bool isUsed = false;\n   IndexMode indexMode = IndexMode::PerVertex;\n   DivisorMode divisorMode = DivisorMode::CONST_1;\n};\n\nstruct AttribElem\n{\n   uint32_t bufferIndex = 0;\n   uint32_t offset = 0;\n   uint32_t elemWidth = 0;\n   uint32_t elemCount = 0;\n};\n\n#pragma pack(push, 1)\n\nstruct ShaderDesc\n{\n   ShaderType type = ShaderType::Unknown;\n   gsl::span<const uint8_t> binary;\n   bool aluInstPreferVector = true;\n\n   std::array<latte::SQ_TEX_DIM, latte::MaxTextures> texDims;\n   std::array<TextureInputType, latte::MaxTextures> texFormat;\n\n   DataHash hash() const\n   {\n      return DataHash {}.write(*this);\n   }\n};\n\nstruct VertexShaderDesc : public ShaderDesc\n{\n   gsl::span<const uint8_t> fsBinary;\n\n   std::array<uint32_t, latte::MaxStreamOutBuffers> streamOutStride;\n\n   struct\n   {\n      latte::SQ_PGM_RESOURCES_VS sq_pgm_resources_vs;\n      std::array<latte::SQ_VTX_SEMANTIC_N, 32> sq_vtx_semantics;\n      latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl;\n   } regs;\n\n   DataHash hash() const\n   {\n      return DataHash {}.write(*this);\n   }\n};\n\nstruct GeometryShaderDesc : public ShaderDesc\n{\n   gsl::span<const uint8_t> dcBinary;\n\n   std::array<uint32_t, latte::MaxStreamOutBuffers> streamOutStride;\n\n   struct\n   {\n      latte::SQ_GS_VERT_ITEMSIZE sq_gs_vert_itemsize;\n      latte::VGT_GS_OUT_PRIMITIVE_TYPE vgt_gs_out_prim_type;\n      latte::VGT_GS_MODE vgt_gs_mode;\n      uint32_t sq_gsvs_ring_itemsize;\n      latte::PA_CL_VS_OUT_CNTL pa_cl_vs_out_cntl;\n   } regs;\n\n   DataHash hash() const\n   {\n      return DataHash {}.write(*this);\n   }\n};\n\nstruct PixelShaderDesc : public ShaderDesc\n{\n   std::array<PixelOutputType, latte::MaxRenderTargets> pixelOutType;\n\n   struct\n   {\n      latte::SQ_PGM_RESOURCES_PS sq_pgm_resources_ps;\n      latte::SQ_PGM_EXPORTS_PS sq_pgm_exports_ps;\n      latte::SPI_VS_OUT_CONFIG spi_vs_out_config;\n      std::array<latte::SPI_VS_OUT_ID_N, 10> spi_vs_out_ids;\n      latte::SPI_PS_IN_CONTROL_0 spi_ps_in_control_0;\n      latte::SPI_PS_IN_CONTROL_1 spi_ps_in_control_1;\n      std::array<latte::SPI_PS_INPUT_CNTL_N, 32> spi_ps_input_cntls;\n      latte::CB_SHADER_CONTROL cb_shader_control;\n      latte::CB_SHADER_MASK cb_shader_mask;\n      latte::DB_SHADER_CONTROL db_shader_control;\n   } regs;\n\n   DataHash hash() const\n   {\n      return DataHash {}.write(*this);\n   }\n};\n\n#pragma pack(pop)\n\nstruct RectStubShaderDesc\n{\n   uint32_t numVsExports;\n\n   DataHash hash() const\n   {\n      return DataHash {}.write(*this);\n   }\n};\n\nstruct ShaderMeta\n{\n   std::array<bool, latte::MaxSamplers> samplerUsed;\n   std::array<bool, latte::MaxTextures> textureUsed;\n   std::array<bool, latte::MaxUniformBlocks> cbufferUsed;\n   uint32_t cfileUsed;\n};\n\nstruct VertexShaderMeta : public ShaderMeta\n{\n   uint32_t numExports;\n   std::array<bool, latte::MaxStreamOutBuffers> streamOutUsed;\n   std::array<AttribBuffer, latte::MaxAttribBuffers> attribBuffers;\n   std::vector<AttribElem> attribElems;\n};\n\nstruct GeometryShaderMeta : public ShaderMeta\n{\n   std::array<bool, latte::MaxStreamOutBuffers> streamOutUsed;\n};\n\nstruct PixelShaderMeta : public ShaderMeta\n{\n   std::array<bool, latte::MaxRenderTargets> pixelOutUsed;\n};\n\nstruct Shader\n{\n   std::vector<unsigned int> binary;\n};\n\nstruct VertexShader : public Shader\n{\n   VertexShaderMeta meta;\n};\n\nstruct GeometryShader : public Shader\n{\n   GeometryShaderMeta meta;\n};\n\nstruct PixelShader : public Shader\n{\n   PixelShaderMeta meta;\n};\n\nstruct RectStubShader\n{\n   std::vector<unsigned int> binary;\n};\n\nbool\ntranslate(const ShaderDesc& shaderDesc, Shader *shader);\n\nRectStubShaderDesc\ngenerateRectSubShaderDesc(VertexShader *vertexShader);\n\nbool\ngenerateRectStub(const RectStubShaderDesc& shaderDesc, RectStubShader *shader);\n\nstd::string\nshaderToString(const Shader *shader);\n\n} // namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_transpiler.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"spirv_transpiler.h\"\n#include \"latte/latte_disassembler.h\"\n\n#include <SPIRV/disassemble.h>\n#include <regex>\n\nnamespace spirv\n{\n\nusing namespace latte;\n\nvoid Transpiler::writeCfAluUnitLine(ShaderParser::Type shaderType, int cfId, int groupId, int unitId)\n{\n   int shaderId;\n   switch (shaderType) {\n   case ShaderParser::Type::Fetch:\n      shaderId = 1;\n      break;\n   case ShaderParser::Type::Vertex:\n      shaderId = 2;\n      break;\n   case ShaderParser::Type::DataCache:\n      shaderId = 3;\n      break;\n   case ShaderParser::Type::Geometry:\n      shaderId = 4;\n      break;\n   case ShaderParser::Type::Pixel:\n      shaderId = 5;\n      break;\n   default:\n      decaf_abort(\"unexpected shader type.\")\n   }\n\n   if (cfId < 0) {\n      cfId = 999;\n   }\n   if (groupId < 0) {\n      groupId = 999;\n   }\n   if (unitId < 0) {\n      unitId = 9;\n   }\n\n   mSpv->setLine(shaderId * 10000000 + cfId * 10000 + groupId * 10 + unitId);\n}\n\nvoid Transpiler::translateTexInst(const ControlFlowInst &cf, const TextureFetchInst &inst)\n{\n   writeCfAluUnitLine(mType, mCfPC, mTexVtxPC, -1);\n\n   ShaderParser::translateTexInst(cf, inst);\n}\n\nvoid Transpiler::translateVtxInst(const ControlFlowInst &cf, const VertexFetchInst &inst)\n{\n   writeCfAluUnitLine(mType, mCfPC, mTexVtxPC, -1);\n\n   ShaderParser::translateVtxInst(cf, inst);\n}\n\nvoid Transpiler::translateAluInst(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst)\n{\n   writeCfAluUnitLine(mType, mCfPC, mGroupPC, static_cast<uint32_t>(unit));\n\n   ShaderParser::translateAluInst(cf, group, unit, inst);\n}\n\nvoid Transpiler::translateAluGroup(const ControlFlowInst &cf, const AluInstructionGroup &group)\n{\n   writeCfAluUnitLine(mType, mCfPC, mGroupPC, -1);\n\n   ShaderParser::translateAluGroup(cf, group);\n\n   mSpv->flushAluGroupWrites();\n   mSpv->swapPrevRes();\n}\n\nvoid Transpiler::translateCfNormalInst(const ControlFlowInst& cf)\n{\n   // cf.word1.BARRIER(); - No need to handle coherency in SPIRV\n   // cf.word1.WHOLE_QUAD_MODE(); - No need to handle optimization\n   // cf.word1.VALID_PIXEL_MODE(); - No need to handle optimization\n\n   writeCfAluUnitLine(mType, mCfPC, -1, -1);\n\n   ShaderParser::translateCfNormalInst(cf);\n}\nvoid Transpiler::translateCfExportInst(const ControlFlowInst& cf)\n{\n   // cf.word1.BARRIER(); - No need to handle coherency in SPIRV\n   // cf.word1.WHOLE_QUAD_MODE(); - No need to handle optimization\n   // cf.word1.VALID_PIXEL_MODE(); - No need to handle optimization\n\n   writeCfAluUnitLine(mType, mCfPC, -1, -1);\n\n   ShaderParser::translateCfExportInst(cf);\n}\nvoid Transpiler::translateCfAluInst(const ControlFlowInst& cf)\n{\n   // cf.word1.BARRIER(); - No need to handle coherency in SPIRV\n   // cf.word1.WHOLE_QUAD_MODE(); - No need to handle optimization\n\n   writeCfAluUnitLine(mType, mCfPC, -1, -1);\n\n   ShaderParser::translateCfAluInst(cf);\n\n   mSpv->resetAr();\n}\n\nvoid Transpiler::translate()\n{\n   ShaderParser::reset();\n\n   writeCfAluUnitLine(mType, -1, -1, -1);\n\n   ShaderParser::translate();\n}\n\nint findVsOutputLocation(const std::array<uint8_t, 40>& semantics, uint32_t semanticId)\n{\n   for (auto i = 0u; i < semantics.size(); ++i) {\n      if (semantics[i] == semanticId) {\n         return static_cast<int>(i);\n      }\n   }\n   return -1;\n}\n\nvoid Transpiler::writeGenericProlog(ShaderSpvBuilder &spvGen)\n{\n   spvGen.createStore(spvGen.makeIntConstant(0), spvGen.stackIndexVar());\n   spvGen.createStore(spvGen.stateActive(), spvGen.stateVar());\n}\n\nvoid Transpiler::writeVertexProlog(ShaderSpvBuilder &spvGen, const VertexShaderDesc& desc)\n{\n   // This is implied as being enabled if we see a write to POS_1\n   //desc.regs.pa_cl_vs_out_cntl.VS_OUT_MISC_VEC_ENA();\n\n   // I don't quite understand the semantics of this.\n   //desc.regs.pa_cl_vs_out_cntl.VS_OUT_MISC_SIDE_BUS_ENA();\n\n   // We do not currently support these cases:\n   decaf_check(!desc.regs.pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA());\n   decaf_check(!desc.regs.pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA());\n\n   writeGenericProlog(spvGen);\n\n   auto oneConst = spvGen.makeIntConstant(1);\n   auto twoConst = spvGen.makeIntConstant(2);\n\n   if (desc.regs.pa_cl_vs_out_cntl.USE_VTX_POINT_SIZE()) {\n      auto pointSizePtr = spvGen.createAccessChain(spv::StorageClass::StorageClassPushConstant, spvGen.vsPushConstVar(), { twoConst });\n      auto pointSizeVal = spvGen.createLoad(pointSizePtr, spv::NoPrecision);\n      spvGen.createStore(pointSizeVal, spvGen.pointSizeVar());\n   }\n\n   // Note that because we use VertexIndex, we have to subtrack the base value\n   // away from gl_VertexIndex to receive the same value we received in GL.\n   auto zSpaceMulPtr = spvGen.createAccessChain(spv::StorageClass::StorageClassPushConstant, spvGen.vsPushConstVar(), { oneConst });\n   auto zSpaceMulVal = spvGen.createLoad(zSpaceMulPtr, spv::NoPrecision);\n   auto vertexBaseFlt = spvGen.createOp(spv::OpCompositeExtract, spvGen.floatType(), { zSpaceMulVal, 2 });\n   auto vertexBaseVal = spvGen.createUnaryOp(spv::OpBitcast, spvGen.intType(), vertexBaseFlt);\n   auto instanceBaseFlt = spvGen.createOp(spv::OpCompositeExtract, spvGen.floatType(), { zSpaceMulVal, 3 });\n   auto instanceBaseVal = spvGen.createUnaryOp(spv::OpBitcast, spvGen.intType(), instanceBaseFlt);\n\n   auto vertexIdVal = spvGen.createLoad(spvGen.vertexIdVar(), spv::NoPrecision);\n   vertexIdVal = spvGen.createBinOp(spv::OpISub, spvGen.intType(), vertexIdVal, vertexBaseVal);\n   vertexIdVal = spvGen.createUnaryOp(spv::OpBitcast, spvGen.floatType(), vertexIdVal);\n\n   auto instanceIdVal = spvGen.createLoad(spvGen.instanceIdVar(), spv::NoPrecision);\n   instanceIdVal = spvGen.createBinOp(spv::OpISub, spvGen.intType(), instanceIdVal, instanceBaseVal);\n   instanceIdVal = spvGen.createUnaryOp(spv::OpBitcast, spvGen.floatType(), instanceIdVal);\n\n   auto zeroFConst = spvGen.makeFloatConstant(0.0f);\n   auto initialR0 = spvGen.createOp(spv::OpCompositeConstruct, spvGen.float4Type(),\n                                   { vertexIdVal, instanceIdVal, zeroFConst, zeroFConst });\n\n   GprMaskRef gpr0;\n   gpr0.gpr.number = 0;\n   gpr0.gpr.indexMode = GprIndexMode::None;\n   gpr0.mask[0] = latte::SQ_SEL::SEL_X;\n   gpr0.mask[1] = latte::SQ_SEL::SEL_Y;\n   gpr0.mask[2] = latte::SQ_SEL::SEL_Z;\n   gpr0.mask[3] = latte::SQ_SEL::SEL_W;\n   spvGen.writeGprMaskRef(gpr0, initialR0);\n}\n\nvoid Transpiler::writeGeometryProlog(ShaderSpvBuilder &spvGen, const GeometryShaderDesc& desc)\n{\n   // This is implied as being enabled if we see a write to POS_1\n   //desc.regs.pa_cl_vs_out_cntl.VS_OUT_MISC_VEC_ENA();\n\n   // I don't quite understand the semantics of this.\n   //desc.regs.pa_cl_vs_out_cntl.VS_OUT_MISC_SIDE_BUS_ENA();\n\n   // We do not currently support these cases:\n   decaf_check(!desc.regs.pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA());\n   decaf_check(!desc.regs.pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA());\n\n   spvGen.addCapability(spv::CapabilityGeometry);\n\n   auto mainFn = spvGen.getFunction(\"main\");\n   spvGen.addExecutionMode(mainFn, spv::ExecutionModeInvocations, 1);\n   spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputVertices, 64);\n\n   spvGen.addExecutionMode(mainFn, spv::ExecutionModeTriangles);\n\n   switch (desc.regs.vgt_gs_out_prim_type) {\n   case latte::VGT_GS_OUT_PRIMITIVE_TYPE::POINTLIST:\n      spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputPoints);\n      break;\n   case latte::VGT_GS_OUT_PRIMITIVE_TYPE::LINESTRIP:\n      spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputLineStrip);\n      break;\n   case latte::VGT_GS_OUT_PRIMITIVE_TYPE::TRISTRIP:\n      spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputTriangleStrip);\n      break;\n   default:\n      decaf_abort(\"Unexpected geometry shader primitive type\");\n   }\n\n   writeGenericProlog(spvGen);\n\n   // Initialize the ring index to 0\n   spvGen.createStore(spvGen.makeUintConstant(0), spvGen.ringOffsetVar());\n\n   auto zeroFConst = spvGen.makeFloatConstant(0.0f);\n   auto zeroConstAsF = spvGen.createUnaryOp(spv::OpBitcast, spvGen.floatType(), spvGen.makeUintConstant(0));\n   auto oneConstAsF = spvGen.createUnaryOp(spv::OpBitcast, spvGen.floatType(), spvGen.makeUintConstant(1));\n   auto twoConstAsF = spvGen.createUnaryOp(spv::OpBitcast, spvGen.floatType(), spvGen.makeUintConstant(2));\n   auto initialR0 = spvGen.createOp(spv::OpCompositeConstruct, spvGen.float4Type(),\n                                    { zeroConstAsF, oneConstAsF, zeroFConst, twoConstAsF });\n\n   GprMaskRef gpr0;\n   gpr0.gpr.number = 0;\n   gpr0.gpr.indexMode = GprIndexMode::None;\n   gpr0.mask[0] = latte::SQ_SEL::SEL_X;\n   gpr0.mask[1] = latte::SQ_SEL::SEL_Y;\n   gpr0.mask[2] = latte::SQ_SEL::SEL_Z;\n   gpr0.mask[3] = latte::SQ_SEL::SEL_W;\n   spvGen.writeGprMaskRef(gpr0, initialR0);\n}\n\nvoid Transpiler::writePixelProlog(ShaderSpvBuilder &spvGen, const PixelShaderDesc& desc)\n{\n   writeGenericProlog(spvGen);\n\n   auto mainFn = spvGen.getFunction(\"main\");\n   spvGen.addExecutionMode(mainFn, spv::ExecutionModeOriginUpperLeft);\n\n   // We don't currently support baryocentric sampling\n   decaf_check(!desc.regs.spi_ps_in_control_0.BARYC_AT_SAMPLE_ENA());\n\n   // These simply control feature enablement for the interpolation\n   //  of the PS inputs down below:\n   // psDesc.regs.spi_ps_in_control_0.BARYC_SAMPLE_CNTL()\n   // psDesc.regs.spi_ps_in_control_0.LINEAR_GRADIENT_ENA()\n   // psDesc.regs.spi_ps_in_control_0.PERSP_GRADIENT_ENA()\n\n   // We do not currently support fixed point positions\n   decaf_check(!desc.regs.spi_ps_in_control_1.FIXED_PT_POSITION_ENA());\n   //psDesc.regs.spi_ps_in_control_1.FIXED_PT_POSITION_ADDR();\n\n   // I don't even know how to handle this being off!?\n   //psDesc.regs.spi_ps_in_control_1.FOG_ADDR();\n\n   // We do not currently support pixel indexing\n   decaf_check(!desc.regs.spi_ps_in_control_1.GEN_INDEX_PIX());\n   //psDesc.regs.spi_ps_in_control_1.GEN_INDEX_PIX_ADDR();\n\n   // I don't believe this is used on our GPU...\n   //psDesc.regs.spi_ps_in_control_1.POSITION_ULC();\n\n   std::map<uint32_t, latte::SPI_PS_INPUT_CNTL_N*> inputCntlMap;\n\n   auto numInputs = desc.regs.spi_ps_in_control_0.NUM_INTERP();\n   for (auto inputIdx = 0u; inputIdx < numInputs; ++inputIdx) {\n      auto &spi_ps_input_cntl = desc.regs.spi_ps_input_cntls[inputIdx];\n      auto gprRef = spvGen.getGprRef({ inputIdx, latte::GprIndexMode::None });\n      auto inputSemantic = spi_ps_input_cntl.SEMANTIC();\n\n      // Locate the vertex shader output that matches\n      int semLocation = -1;\n      for (auto semIdx = 0; semIdx < 10; ++semIdx) {\n         if (inputSemantic == desc.regs.spi_vs_out_ids[semIdx].SEMANTIC_0()) {\n            semLocation = semIdx * 4 + 0;\n            break;\n         } else if (inputSemantic == desc.regs.spi_vs_out_ids[semIdx].SEMANTIC_1()) {\n            semLocation = semIdx * 4 + 1;\n            break;\n         } else if (inputSemantic == desc.regs.spi_vs_out_ids[semIdx].SEMANTIC_2()) {\n            semLocation = semIdx * 4 + 2;\n            break;\n         } else if (inputSemantic == desc.regs.spi_vs_out_ids[semIdx].SEMANTIC_3()) {\n            semLocation = semIdx * 4 + 3;\n            break;\n         }\n      }\n\n      if (desc.regs.spi_ps_in_control_0.POSITION_ENA() &&\n          desc.regs.spi_ps_in_control_0.POSITION_ADDR() == inputIdx) {\n         // TODO: Handle desc.regs.spi_ps_in_control_0.POSITION_CENTROID();\n         // TODO: Handle desc.regs.spi_ps_in_control_0.POSITION_SAMPLE();\n         spvGen.createStore(spvGen.createLoad(spvGen.fragCoordVar(), spv::NoPrecision), gprRef);\n         continue;\n      }\n\n      if (semLocation < 0) {\n         // There was no matching semantic output from the VS...\n         auto zeroConst = spvGen.makeFloatConstant(0.0f);\n         auto oneConst = spvGen.makeFloatConstant(1.0f);\n\n         spv::Id defaultVal;\n         switch (spi_ps_input_cntl.DEFAULT_VAL()) {\n         case 0:\n            defaultVal = spvGen.makeCompositeConstant(spvGen.float4Type(),\n                                                      { zeroConst, zeroConst, zeroConst, zeroConst });\n            break;\n         case 1:\n            defaultVal = spvGen.makeCompositeConstant(spvGen.float4Type(),\n                                                      { zeroConst, zeroConst, zeroConst, oneConst });\n            break;\n         case 2:\n            defaultVal = spvGen.makeCompositeConstant(spvGen.float4Type(),\n                                                      { oneConst, oneConst, oneConst, zeroConst });\n            break;\n         case 3:\n            defaultVal = spvGen.makeCompositeConstant(spvGen.float4Type(),\n                                                      { oneConst, oneConst, oneConst, oneConst });\n            break;\n         default:\n            decaf_abort(\"Unexpected PS input semantic default\");\n         }\n\n         spvGen.createStore(defaultVal, gprRef);\n\n         continue;\n      }\n\n      auto inputVar = spvGen.inputParamVar(static_cast<uint32_t>(semLocation));\n\n      decaf_check(!spi_ps_input_cntl.CYL_WRAP());\n      decaf_check(!spi_ps_input_cntl.PT_SPRITE_TEX());\n\n      if (spi_ps_input_cntl.FLAT_SHADE()) {\n         decaf_check(!spi_ps_input_cntl.SEL_LINEAR());\n         decaf_check(!spi_ps_input_cntl.SEL_SAMPLE());\n\n         // Using centroid qualifier with flat shading doesn't make sense\n         decaf_check(!spi_ps_input_cntl.SEL_CENTROID());\n\n         spvGen.addDecoration(inputVar, spv::DecorationFlat);\n      } else if (spi_ps_input_cntl.SEL_LINEAR()) {\n         decaf_check(!spi_ps_input_cntl.FLAT_SHADE());\n         decaf_check(!spi_ps_input_cntl.SEL_SAMPLE());\n\n         spvGen.addDecoration(inputVar, spv::DecorationNoPerspective);\n\n         if (spi_ps_input_cntl.SEL_CENTROID()) {\n            spvGen.addDecoration(inputVar, spv::DecorationCentroid);\n         }\n      } else if (spi_ps_input_cntl.SEL_SAMPLE()) {\n         decaf_check(!spi_ps_input_cntl.FLAT_SHADE());\n         decaf_check(!spi_ps_input_cntl.SEL_LINEAR());\n\n         // Using centroid qualifier with sample shading doesn't make sense\n         decaf_check(!spi_ps_input_cntl.SEL_CENTROID());\n\n         spvGen.addDecoration(inputVar, spv::DecorationSample);\n      } else {\n         // The default will be fine...\n         if (spi_ps_input_cntl.SEL_CENTROID()) {\n            spvGen.addDecoration(inputVar, spv::DecorationCentroid);\n         }\n      }\n\n      auto inputVal = spvGen.createLoad(inputVar, spv::NoPrecision);\n      spvGen.createStore(inputVal, gprRef);\n   }\n\n   if (desc.regs.spi_ps_in_control_1.FRONT_FACE_ENA()) {\n      auto ffGprIdx = desc.regs.spi_ps_in_control_1.FRONT_FACE_ADDR();\n      auto ffChanIdx = desc.regs.spi_ps_in_control_1.FRONT_FACE_CHAN();\n\n      latte::GprChanRef ffRef;\n      ffRef.gpr = latte::makeGprRef(ffGprIdx);\n      ffRef.chan = static_cast<SQ_CHAN>(ffChanIdx);\n\n      auto frontFacingVar = spvGen.frontFacingVar();\n      auto frontFacingVal = spvGen.createLoad(frontFacingVar, spv::NoPrecision);\n\n      spv::Id output = spv::NoResult;\n\n      auto ffBitMode = desc.regs.spi_ps_in_control_1.FRONT_FACE_ALL_BITS();\n      if (ffBitMode == 0) {\n         // Sign bit represents front facing (-1.0f Back, +1.0f Front)\n         auto backConst = spvGen.makeFloatConstant(-1.0f);\n         auto frontConst = spvGen.makeFloatConstant(+1.0f);\n         output = spvGen.createTriOp(spv::OpSelect, spvGen.floatType(), frontFacingVal, frontConst, backConst);\n      } else if (ffBitMode == 1) {\n         // Full value represents front facing (0 Back, 1 Front)\n         auto backConst = spvGen.makeUintConstant(0);\n         auto frontConst = spvGen.makeUintConstant(1);\n         output = spvGen.createTriOp(spv::OpSelect, spvGen.uintType(), frontFacingVal, frontConst, backConst);\n      } else {\n         decaf_abort(\"Unexpected front face bit mode.\");\n      }\n\n      spvGen.writeGprChanRef(ffRef, output);\n   }\n}\n\nbool Transpiler::translate(const ShaderDesc& shaderDesc, Shader *shader)\n{\n   auto state = Transpiler {};\n\n   if (shaderDesc.type == ShaderType::Vertex) {\n      state.mType = Transpiler::Type::Vertex;\n   } else if (shaderDesc.type == ShaderType::Geometry) {\n      state.mType = Transpiler::Type::Geometry;\n   } else if (shaderDesc.type == ShaderType::Pixel) {\n      state.mType = Transpiler::Type::Pixel;\n   } else {\n      decaf_abort(\"Unexpected shader type\");\n   }\n\n   spv::ExecutionModel spvExecModel;\n   if (shaderDesc.type == ShaderType::Vertex) {\n      spvExecModel = spv::ExecutionModel::ExecutionModelVertex;\n   } else if (shaderDesc.type == ShaderType::Geometry) {\n      spvExecModel = spv::ExecutionModel::ExecutionModelGeometry;\n   } else if (shaderDesc.type == ShaderType::Pixel) {\n      spvExecModel = spv::ExecutionModel::ExecutionModelFragment;\n   } else {\n      decaf_abort(\"Unexpected shader type\");\n   }\n\n   auto spvGen = ShaderSpvBuilder(spvExecModel);\n   spvGen.setSource(spv::SourceLanguageOpenCL_CPP, 0);\n   spvGen.setSourceFile(\"none\");\n\n   state.mSpv = &spvGen;\n   state.mBinary = shaderDesc.binary;\n   state.mAluInstPreferVector = shaderDesc.aluInstPreferVector;\n\n   state.mTexDims = shaderDesc.texDims;\n   state.mTexFormats = shaderDesc.texFormat;\n\n   if (shaderDesc.type == ShaderType::Vertex) {\n      auto &vsDesc = *reinterpret_cast<const VertexShaderDesc*>(&shaderDesc);\n\n      spvGen.setSourceText(latte::disassemble(shaderDesc.binary) + \"\\n\\n\" +\n                           latte::disassemble(vsDesc.fsBinary, true));\n\n      spvGen.setBindingBase(32 * 0);\n\n      state.mSqVtxSemantics = vsDesc.regs.sq_vtx_semantics;\n      state.mPaClVsOutCntl = vsDesc.regs.pa_cl_vs_out_cntl;\n      state.mStreamOutStride = vsDesc.streamOutStride;\n\n      Transpiler::writeVertexProlog(spvGen, vsDesc);\n   } else if (shaderDesc.type == ShaderType::Geometry) {\n      auto &gsDesc = *reinterpret_cast<const GeometryShaderDesc*>(&shaderDesc);\n\n      spvGen.setSourceText(latte::disassemble(shaderDesc.binary) + \"\\n\\n\" +\n                           latte::disassemble(gsDesc.dcBinary));\n\n      spvGen.setBindingBase(32 * 1);\n\n      state.mPaClVsOutCntl = gsDesc.regs.pa_cl_vs_out_cntl;\n      state.mStreamOutStride = gsDesc.streamOutStride;\n\n      Transpiler::writeGeometryProlog(spvGen, gsDesc);\n   } else if (shaderDesc.type == ShaderType::Pixel) {\n      auto &psDesc = *reinterpret_cast<const PixelShaderDesc*>(&shaderDesc);\n\n      spvGen.setSourceText(latte::disassemble(shaderDesc.binary));\n\n      spvGen.setBindingBase(32 * 2);\n\n      state.mPixelOutType = psDesc.pixelOutType;\n      state.mSqPgmExportsPs = psDesc.regs.sq_pgm_exports_ps;\n      state.mCbShaderControl = psDesc.regs.cb_shader_control;\n      state.mCbShaderMask = psDesc.regs.cb_shader_mask;\n      state.mDbShaderControl = psDesc.regs.db_shader_control;\n\n      Transpiler::writePixelProlog(spvGen, psDesc);\n   }\n\n   state.translate();\n   spvGen.makeReturn(true);\n\n   if (shaderDesc.type != ShaderType::Vertex) {\n      if (spvGen.hasFunction(\"fs_main\")) {\n         decaf_abort(\"Non-vertex-shader called into a FS function, wat?\");\n      }\n   }\n\n   if (shaderDesc.type != ShaderType::Geometry) {\n      if (spvGen.hasFunction(\"dc_main\")) {\n         decaf_abort(\"Non-vertex-shader called into a DC function, wat?\");\n      }\n   }\n\n   ShaderMeta genericMeta;\n\n   for (auto i = 0u; i < latte::MaxSamplers; ++i) {\n      genericMeta.samplerUsed[i] = spvGen.isSamplerUsed(i);\n   }\n   for (auto i = 0u; i < latte::MaxTextures; ++i) {\n      genericMeta.textureUsed[i] = spvGen.isTextureUsed(i);\n   }\n\n   genericMeta.cfileUsed = spvGen.isConstantFileUsed();\n\n   for (auto i = 0u; i < latte::MaxUniformBlocks; ++i) {\n      auto cbufferUsed = spvGen.isUniformBufferUsed(i);\n      genericMeta.cbufferUsed[i] = cbufferUsed;\n      if (genericMeta.cfileUsed && cbufferUsed) {\n         decaf_abort(\"Shader used both constant buffers and the constants file\");\n      }\n   }\n\n   if (shaderDesc.type == ShaderType::Vertex) {\n      auto& vsDesc = *reinterpret_cast<const VertexShaderDesc*>(&shaderDesc);\n      auto vsShader = reinterpret_cast<VertexShader*>(shader);\n\n      if (spvGen.hasFunction(\"fs_main\")) {\n         auto fsFunc = spvGen.getFunction(\"fs_main\");\n         spvGen.setBuildPoint(fsFunc->getEntryBlock());\n\n         state.mType = ShaderParser::Type::Fetch;\n         state.mBinary = vsDesc.fsBinary;\n         state.translate();\n\n         // Write the return statement at the end of the function\n         spvGen.makeReturn(true);\n      }\n\n      static_cast<ShaderMeta&>(vsShader->meta) = genericMeta;\n      vsShader->meta.numExports = spvGen.getNumParamExports();\n      vsShader->meta.attribBuffers = state.mAttribBuffers;\n      vsShader->meta.attribElems = state.mAttribElems;\n\n      for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) {\n         vsShader->meta.streamOutUsed[i] = spvGen.isStreamOutUsed(i);\n      }\n   } else if (shaderDesc.type == ShaderType::Geometry) {\n      auto& gsDesc = *reinterpret_cast<const GeometryShaderDesc*>(&shaderDesc);\n      auto gsShader = reinterpret_cast<GeometryShader*>(shader);\n\n      if (spvGen.hasFunction(\"dc_main\")) {\n         auto dcFunc = spvGen.getFunction(\"dc_main\");\n         spvGen.setBuildPoint(dcFunc->getEntryBlock());\n\n         // Need to save our GPRs first\n\n         auto gprType = spvGen.arrayType(spvGen.float4Type(), 16, 128);\n         auto gprSaveVar = spvGen.createVariable(spv::NoPrecision, spv::StorageClass::StorageClassPrivate, gprType, \"RVarSave\");\n         spvGen.createNoResultOp(spv::OpCopyMemory, { gprSaveVar, spvGen.gprVar() });\n\n         // Translate the shader\n         state.mType = ShaderParser::Type::DataCache;\n         state.mBinary = gsDesc.dcBinary;\n         state.translate();\n\n         // We need to increment the ring offset at the end of each.  Note that we only\n         // support striding in vec4 intervals.\n         auto ringItemStride = gsDesc.regs.sq_gs_vert_itemsize.ITEMSIZE();\n         decaf_check(ringItemStride % 4 == 0);\n         auto ringStride = ringItemStride / 4;\n         auto ringStrideConst = spvGen.makeIntConstant(ringStride);\n         auto ringOffsetVal = spvGen.createLoad(spvGen.ringOffsetVar(), spv::NoPrecision);\n         auto newRingOffset = spvGen.createBinOp(spv::OpIAdd, spvGen.uintType(), ringOffsetVal, ringStrideConst);\n         spvGen.createStore(newRingOffset, spvGen.ringOffsetVar());\n\n         // Restore our GPRs\n         spvGen.createNoResultOp(spv::OpCopyMemory, { spvGen.gprVar(), gprSaveVar });\n\n         // Write the return statement at the end of the function\n         spvGen.makeReturn(true);\n      }\n\n      static_cast<ShaderMeta&>(gsShader->meta) = genericMeta;\n\n      for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) {\n         gsShader->meta.streamOutUsed[i] = spvGen.isStreamOutUsed(i);\n      }\n   } else if (shaderDesc.type == ShaderType::Pixel) {\n      auto psShader = reinterpret_cast<PixelShader*>(shader);\n\n      static_cast<ShaderMeta&>(psShader->meta) = genericMeta;\n\n      for (auto i = 0; i < latte::MaxRenderTargets; ++i) {\n         psShader->meta.pixelOutUsed[i] = spvGen.isPixelOutUsed(i);\n      }\n   }\n\n   shader->binary.clear();\n   spvGen.dump(shader->binary);\n\n   return true;\n}\n\nbool translate(const ShaderDesc& shaderDesc, Shader *shader)\n{\n   return Transpiler::translate(shaderDesc, shader);\n}\n\nRectStubShaderDesc\ngenerateRectSubShaderDesc(VertexShader *vertexShader)\n{\n   RectStubShaderDesc desc;\n   desc.numVsExports = vertexShader->meta.numExports;\n   return desc;\n}\n\nbool generateRectStub(const RectStubShaderDesc& shaderDesc, RectStubShader *shader)\n{\n   SpvBuilder spvGen;\n\n   spvGen.setMemoryModel(spv::AddressingModel::AddressingModelLogical, spv::MemoryModel::MemoryModelGLSL450);\n   spvGen.addCapability(spv::CapabilityShader);\n   spvGen.addCapability(spv::CapabilityGeometry);\n\n   auto mainFn = spvGen.makeEntryPoint(\"main\");\n   auto entry = spvGen.addEntryPoint(spv::ExecutionModelGeometry, mainFn, \"main\");\n\n   spvGen.addExecutionMode(mainFn, spv::ExecutionModeTriangles);\n   spvGen.addExecutionMode(mainFn, spv::ExecutionModeInvocations, 1);\n   spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputTriangleStrip);\n   spvGen.addExecutionMode(mainFn, spv::ExecutionModeOutputVertices, 6);\n\n   auto zeroConst = spvGen.makeIntConstant(0);\n   auto oneConst = spvGen.makeIntConstant(1);\n   auto twoConst = spvGen.makeIntConstant(2);\n   auto twoFConst = spvGen.vectorizeConstant(spvGen.makeFloatConstant(2.0f), 4);\n\n   auto glInType = spvGen.makeStructType({ spvGen.float4Type() }, \"gl_in\");\n   spvGen.addDecoration(glInType, spv::DecorationBlock);\n   spvGen.addMemberDecoration(glInType, 0, spv::DecorationBuiltIn, spv::BuiltInPosition);\n   spvGen.addMemberName(glInType, 0, \"gl_Position\");\n   auto glInArrType = spvGen.arrayType(glInType, 16, 3);\n   auto glInArrVar = spvGen.createVariable(spv::NoPrecision, spv::StorageClassInput, glInArrType, \"gl_in\");\n   entry->addIdOperand(glInArrVar);\n\n   auto perVertexType = spvGen.makeStructType({ spvGen.float4Type() }, \"gl_PerVertex\");\n   spvGen.addDecoration(perVertexType, spv::DecorationBlock);\n   spvGen.addMemberDecoration(perVertexType, 0, spv::DecorationBuiltIn, spv::BuiltInPosition);\n   spvGen.addMemberName(perVertexType, 0, \"gl_Position\");\n   auto perVertexVar = spvGen.createVariable(spv::NoPrecision, spv::StorageClassOutput, perVertexType, \"gl_PerVertex\");\n   entry->addIdOperand(perVertexVar);\n\n   std::array<spv::Id, 3> posInVals;\n   std::vector<std::array<spv::Id, 3>> paramInVals;\n   spv::Id posOutPtr;\n   std::vector<spv::Id> paramOutPtrs;\n\n   auto posInPtr0 = spvGen.createAccessChain(spv::StorageClassInput, glInArrVar, { zeroConst, zeroConst });\n   auto posInPtr1 = spvGen.createAccessChain(spv::StorageClassInput, glInArrVar, { oneConst, zeroConst });\n   auto posInPtr2 = spvGen.createAccessChain(spv::StorageClassInput, glInArrVar, { twoConst, zeroConst });\n   posInVals[0] = spvGen.createLoad(posInPtr0, spv::NoPrecision);\n   posInVals[1] = spvGen.createLoad(posInPtr1, spv::NoPrecision);\n   posInVals[2] = spvGen.createLoad(posInPtr2, spv::NoPrecision);\n\n   posOutPtr = spvGen.createAccessChain(spv::StorageClassOutput, perVertexVar, { zeroConst });\n\n   for (auto i = 0u; i < shaderDesc.numVsExports; ++i) {\n      auto paramType = spvGen.float4Type();\n      auto paramInVarType = spvGen.arrayType(paramType, 16, 3);\n      auto paramInVar = spvGen.createVariable(spv::NoPrecision, spv::StorageClassInput, paramInVarType, fmt::format(\"PARAM_{}_IN\", i).c_str());\n      spvGen.addDecoration(paramInVar, spv::DecorationLocation, i);\n      entry->addIdOperand(paramInVar);\n\n      std::array<spv::Id, 3> paramInVal;\n      auto paramInPtr0 = spvGen.createAccessChain(spv::StorageClassInput, paramInVar, { zeroConst });\n      auto paramInPtr1 = spvGen.createAccessChain(spv::StorageClassInput, paramInVar, { oneConst });\n      auto paramInPtr2 = spvGen.createAccessChain(spv::StorageClassInput, paramInVar, { twoConst });\n      paramInVal[0] = spvGen.createLoad(paramInPtr0, spv::NoPrecision);\n      paramInVal[1] = spvGen.createLoad(paramInPtr1, spv::NoPrecision);\n      paramInVal[2] = spvGen.createLoad(paramInPtr2, spv::NoPrecision);\n      paramInVals.emplace_back(paramInVal);\n\n      auto paramOutVar = spvGen.createVariable(spv::NoPrecision, spv::StorageClassOutput, paramType, fmt::format(\"PARAM_{}_OUT\", i).c_str());\n      spvGen.addDecoration(paramOutVar, spv::DecorationLocation, i);\n      entry->addIdOperand(paramOutVar);\n      paramOutPtrs.push_back(paramOutVar);\n   }\n\n   // Reflects v across tangent created by t1/t2\n   auto reflectVec4 = [&](spv::Id v, spv::Id t1, spv::Id t2)\n   {\n      auto midPointAdd = spvGen.createBinOp(spv::OpFAdd, spvGen.float4Type(), t1, t2);\n      auto midPoint = spvGen.createBinOp(spv::OpFDiv, spvGen.float4Type(), midPointAdd, twoFConst);\n      auto cornerToMidPoint = spvGen.createBinOp(spv::OpFSub, spvGen.float4Type(), midPoint, v);\n      return spvGen.createBinOp(spv::OpFAdd, spvGen.float4Type(), midPoint, cornerToMidPoint);\n   };\n\n   // Emits a vertex that was sent as input\n   auto emitVertex = [&](int vertexId) {\n      spvGen.createStore(posInVals[vertexId], posOutPtr);\n\n      for (auto paramIdx = 0u; paramIdx < shaderDesc.numVsExports; ++paramIdx) {\n         spvGen.createStore(paramInVals[paramIdx][vertexId], paramOutPtrs[paramIdx]);\n      }\n\n      spvGen.createNoResultOp(spv::OpEmitVertex);\n   };\n\n   // Emits a vertex generated by flipping v accross the tangent created by t1/t2\n   auto emitGenVertex = [&](int vId, int t1Id, int t2Id)\n   {\n      auto posVal = reflectVec4(posInVals[vId],\n                                posInVals[t1Id],\n                                posInVals[t2Id]);\n      spvGen.createStore(posVal, posOutPtr);\n\n      for (auto paramIdx = 0u; paramIdx < shaderDesc.numVsExports; ++paramIdx) {\n         auto paramVal = reflectVec4(paramInVals[paramIdx][vId],\n                                     paramInVals[paramIdx][t1Id],\n                                     paramInVals[paramIdx][t2Id]);\n         spvGen.createStore(paramVal, paramOutPtrs[paramIdx]);\n      }\n\n      spvGen.createNoResultOp(spv::OpEmitVertex);\n   };\n\n   /*\n   len0 = | in[0] - in[1] |\n   len1 = | in[0] - in[2] |\n   len2 = | in[1] - in[2] |\n\n   if (len0 > len1 && len0 > len2) {\n      // 0 -> 1 is the hypotenuse\n   } else if (len1 > len2) {\n      // 0 -> 2 is the hypotenuse\n   } else {\n      // 1 -> 2 is the hypotenuse\n   }\n   */\n\n   // Generate the simplest initial triangle\n   emitVertex(0);\n   emitVertex(1);\n   emitVertex(2);\n\n   spvGen.createNoResultOp(spv::OpEndPrimitive);\n\n   // Identify the hypotenuse and generate the opposing triangle\n   auto posDiff0to1 = spvGen.createBinOp(spv::OpFSub, spvGen.float4Type(), posInVals[0], posInVals[1]);\n   auto posDiff0to2 = spvGen.createBinOp(spv::OpFSub, spvGen.float4Type(), posInVals[0], posInVals[2]);\n   auto posDiff1to2 = spvGen.createBinOp(spv::OpFSub, spvGen.float4Type(), posInVals[1], posInVals[2]);\n\n   auto posLen0to1 = spvGen.createBuiltinCall(spvGen.floatType(), spvGen.glslStd450(), GLSLstd450::GLSLstd450Length, { posDiff0to1 });\n   auto posLen0to2 = spvGen.createBuiltinCall(spvGen.floatType(), spvGen.glslStd450(), GLSLstd450::GLSLstd450Length, { posDiff0to2 });\n   auto posLen1to2 = spvGen.createBuiltinCall(spvGen.floatType(), spvGen.glslStd450(), GLSLstd450::GLSLstd450Length, { posDiff1to2 });\n\n   auto len0gt1 = spvGen.createBinOp(spv::OpFOrdGreaterThan, spvGen.boolType(), posLen0to1, posLen0to2);\n   auto len0gt2 = spvGen.createBinOp(spv::OpFOrdGreaterThan, spvGen.boolType(), posLen0to1, posLen1to2);\n   auto len0to1Longest = spvGen.createBinOp(spv::OpLogicalAnd, spvGen.boolType(), len0gt1, len0gt2);\n\n   auto len0to1Block = spv::Builder::If { len0to1Longest, spv::SelectionControlMaskNone, spvGen };\n   {\n      emitVertex(0);\n      emitGenVertex(2, 0, 1);\n      emitVertex(1);\n   }\n   len0to1Block.makeBeginElse();\n   {\n      auto len0to2Longest = spvGen.createBinOp(spv::OpFOrdGreaterThan, spvGen.boolType(), posLen0to2, posLen1to2);\n      auto len0to2Block = spv::Builder::If { len0to2Longest, spv::SelectionControlMaskNone, spvGen };\n      {\n         emitVertex(0);\n         emitGenVertex(1, 0, 2);\n         emitVertex(2);\n      }\n      len0to2Block.makeBeginElse();\n      {\n         emitVertex(1);\n         emitGenVertex(0, 1, 2);\n         emitVertex(2);\n      }\n      len0to2Block.makeEndIf();\n   }\n   len0to1Block.makeEndIf();\n\n   spvGen.createNoResultOp(spv::OpEndPrimitive);\n\n   spvGen.makeReturn(true);\n\n   shader->binary.clear();\n   spvGen.dump(shader->binary);\n\n   return true;\n}\n\nstd::string\nshaderToString(const Shader *shader)\n{\n   std::ostringstream outputAssemblyStream;\n   spv::Disassemble(outputAssemblyStream, shader->binary);\n\n   auto outputAssembly = outputAssemblyStream.str();\n\n   // To improve readability, translate the OpLine instructions to something readable...\n   // This code assumes that all OpLine lines are in the format of `c0aaa0uu`\n   // where c is the CF unit, a is the ALU and u is the unit.  Note that c can be longer\n   // than a single character.\n   outputAssembly = std::regex_replace(outputAssembly,\n                                       std::regex(\"(Line [^ ]+ (\\\\d{1})(\\\\d{3})(\\\\d{3})((\\\\d{1})) 0)\"),\n                                       \"// %IMATYPE$2%; CF: %IMACF$3%; GROUP: %IMAGROUP$4%; UNIT: %IMAUNIT$5%;\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMATYPE1%\"), \"FS\"); // Convert shader type to text\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMATYPE2%\"), \"VS\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMATYPE3%\"), \"DC\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMATYPE4%\"), \"GS\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMATYPE5%\"), \"PS\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"CF: %IMACF999%;\"), \"\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMACF(0*)(\\\\d+)%\"), \"$2\"); // Drop trailing 0's\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"GROUP: %IMAGROUP999%;\"), \"\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMAGROUP(0*)(\\\\d+)%\"), \"$2\"); // Drop trailing 0's\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMAUNIT0%\"), \"X\"); // Convert unit numbers to text\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMAUNIT1%\"), \"Y\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMAUNIT2%\"), \"Z\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMAUNIT3%\"), \"W\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"%IMAUNIT4%\"), \"T\");\n   outputAssembly = std::regex_replace(outputAssembly, std::regex(\"UNIT: %IMAUNIT9%;\"), \"\");\n\n   return outputAssembly;\n}\n\n} // namespace hlsl2\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_transpiler.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n#include \"latte/latte_shaderparser.h\"\n#include \"spirv_translate.h\"\n#include \"spirv_shaderspvbuilder.h\"\n\nnamespace spirv\n{\n\nusing namespace latte;\n\nclass Transpiler : public latte::ShaderParser\n{\n   enum SampleMode : uint32_t\n   {\n      None = 0,\n      Lod = 1 << 0,\n      LodBias = 1 << 1,\n      LodZero = 1 << 2,\n      Gradient = 1 << 3,\n      Compare = 1 << 4,\n      Gather = 1 << 5,\n      Load = 1 << 6\n   };\n\npublic:\n   void translateTex_LD(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_FETCH4(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_L(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_G(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_G_L(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_G_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_G_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_C(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_C_L(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_C_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_C_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_C_G(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_C_G_L(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_C_G_LB(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SAMPLE_C_G_LZ(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_GET_TEXTURE_INFO(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_SET_CUBEMAP_INDEX(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_GET_GRADIENTS_H(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_GET_GRADIENTS_V(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_VTX_FETCH(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateTex_VTX_SEMANTIC(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n\n   void translateVtx_FETCH(const ControlFlowInst &cf, const VertexFetchInst &inst) override;\n   void translateVtx_SEMANTIC(const ControlFlowInst &cf, const VertexFetchInst &inst) override;\n\n   void translateAluOp2_ADD(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_ADD_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_AND_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_ASHR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_COS(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_EXP_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_FLOOR(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_FLT_TO_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_FLT_TO_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_FRACT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_INT_TO_FLT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_KILLE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_KILLE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_KILLNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_KILLNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_KILLGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_KILLGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_KILLGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_KILLGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_KILLGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_KILLGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_LOG_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_LOG_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_LSHL_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_LSHR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MAX(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MAX_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MAX_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MAX_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MIN(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MIN_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MIN_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MIN_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MOV(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MOVA_FLOOR(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MOVA_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MUL(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MUL_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MULLO_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_MULLO_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_NOP(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_NOT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_OR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_PRED_SETE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_PRED_SETE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_PRED_SETGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_PRED_SETGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_PRED_SETGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_PRED_SETGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_PRED_SETGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_PRED_SETGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_PRED_SETNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_PRED_SETNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_RECIP_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_RECIP_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_RECIP_FF(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_RECIP_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_RECIP_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_RECIPSQRT_CLAMPED(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_RECIPSQRT_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_RECIPSQRT_FF(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_RNDNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETGE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETGE_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETGT_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETGT_UINT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETNE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETNE_DX10(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SETNE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SIN(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SQRT_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_SUB_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_UINT_TO_FLT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_TRUNC(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_XOR_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n\n   void translateAluOp2_CUBE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_DOT4(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp2_DOT4_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n\n   void translateAluOp3_CNDE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp3_CNDGT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp3_CNDGE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp3_CNDE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp3_CNDGT_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp3_CNDGE_INT(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp3_MULADD(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp3_MULADD_IEEE(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp3_MULADD_M2(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp3_MULADD_M4(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluOp3_MULADD_D2(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n\n   void translateTexInst(const ControlFlowInst &cf, const TextureFetchInst &inst) override;\n   void translateVtxInst(const ControlFlowInst &cf, const VertexFetchInst &inst) override;\n   void translateAluInst(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst) override;\n   void translateAluGroup(const ControlFlowInst &cf, const AluInstructionGroup &group) override;\n\n   void translateCf_ALU(const ControlFlowInst &cf) override;\n   void translateCf_ALU_PUSH_BEFORE(const ControlFlowInst &cf) override;\n   void translateCf_ALU_POP_AFTER(const ControlFlowInst &cf) override;\n   void translateCf_ALU_POP2_AFTER(const ControlFlowInst &cf) override;\n   void translateCf_ALU_EXT(const ControlFlowInst &cf) override;\n   void translateCf_ALU_CONTINUE(const ControlFlowInst &cf) override;\n   void translateCf_ALU_BREAK(const ControlFlowInst &cf) override;\n   void translateCf_ALU_ELSE_AFTER(const ControlFlowInst &cf) override;\n   void translateCf_CALL_FS(const ControlFlowInst &cf) override;\n   void translateCf_ELSE(const ControlFlowInst &cf) override;\n   void translateCf_JUMP(const ControlFlowInst &cf) override;\n   void translateCf_KILL(const ControlFlowInst &cf) override;\n   void translateCf_NOP(const ControlFlowInst &cf) override;\n   void translateCf_PUSH(const ControlFlowInst &cf) override;\n   void translateCf_POP(const ControlFlowInst &cf) override;\n   void translateCf_RETURN(const ControlFlowInst &cf) override;\n   void translateCf_LOOP_START(const ControlFlowInst &cf) override;\n   void translateCf_LOOP_START_DX10(const ControlFlowInst &cf) override;\n   void translateCf_LOOP_START_NO_AL(const ControlFlowInst &cf) override;\n   void translateCf_LOOP_END(const ControlFlowInst &cf) override;\n   void translateCf_LOOP_CONTINUE(const ControlFlowInst &cf) override;\n   void translateCf_LOOP_BREAK(const ControlFlowInst &cf) override;\n   void translateCf_TEX(const ControlFlowInst &cf) override;\n   void translateCf_VTX(const ControlFlowInst &cf) override;\n   void translateCf_VTX_TC(const ControlFlowInst &cf) override;\n   void translateCf_EMIT_VERTEX(const ControlFlowInst &cf) override;\n   void translateCf_EMIT_CUT_VERTEX(const ControlFlowInst &cf) override;\n   void translateCf_CUT_VERTEX(const ControlFlowInst &cf) override;\n\n   void translateCf_EXP(const ControlFlowInst &cf) override;\n   void translateCf_EXP_DONE(const ControlFlowInst &cf) override;\n   void translateCf_MEM_STREAM0(const ControlFlowInst &cf) override;\n   void translateCf_MEM_STREAM1(const ControlFlowInst &cf) override;\n   void translateCf_MEM_STREAM2(const ControlFlowInst &cf) override;\n   void translateCf_MEM_STREAM3(const ControlFlowInst &cf) override;\n   void translateCf_MEM_RING(const ControlFlowInst &cf) override;\n\n   void translateCfNormalInst(const ControlFlowInst& cf) override;\n   void translateCfExportInst(const ControlFlowInst& cf) override;\n   void translateCfAluInst(const ControlFlowInst& cf) override;\n\n   // Our own helpers\n   spv::Id genAluCondOp(spv::Op predOp, spv::Id lhsVal, spv::Id trueVal, spv::Id falseVal);\n   spv::Id genPredSetOp(const AluInst &inst, spv::Op predOp, spv::Id typeId, spv::Id lhsVal, spv::Id rhsVal, bool updatesPredicate = false);\n   void translateGenericStream(const ControlFlowInst &cf, int streamIdx);\n   void translateGenericExport(const ControlFlowInst &cf);\n   void translateGenericSample(const ControlFlowInst &cf, const TextureFetchInst &inst, uint32_t sampleMode);\n\n   void translateAluOp2RecipCommon(const ControlFlowInst &cf, const AluInstructionGroup &group, SQ_CHAN unit, const AluInst &inst, bool isRecipSqrt, bool isFF);\n\n   static void writeGenericProlog(ShaderSpvBuilder &spv);\n   static void writeVertexProlog(ShaderSpvBuilder &spv, const VertexShaderDesc& desc);\n   static void writeGeometryProlog(ShaderSpvBuilder &spv, const GeometryShaderDesc& desc);\n   static void writePixelProlog(ShaderSpvBuilder &spv, const PixelShaderDesc& desc);\n\n   void translate() override;\n   static bool translate(const ShaderDesc& shaderDesc, Shader *shader);\n\nprotected:\n   void writeCfAluUnitLine(ShaderParser::Type shaderType, int cfId, int groupId, int unitId);\n\n   ShaderSpvBuilder *mSpv;\n\n   // Inputs\n   std::array<latte::SQ_TEX_DIM, latte::MaxTextures> mTexDims;\n   std::array<TextureInputType, latte::MaxTextures> mTexFormats;\n   std::array<latte::SQ_VTX_SEMANTIC_N, 32> mSqVtxSemantics;\n   std::array<uint32_t, latte::MaxStreamOutBuffers> mStreamOutStride;\n   latte::PA_CL_VS_OUT_CNTL mPaClVsOutCntl;\n   std::array<PixelOutputType, latte::MaxRenderTargets> mPixelOutType;\n   latte::SQ_PGM_EXPORTS_PS mSqPgmExportsPs;\n   latte::CB_SHADER_CONTROL mCbShaderControl;\n   latte::CB_SHADER_MASK mCbShaderMask;\n   latte::DB_SHADER_CONTROL mDbShaderControl;\n\n   // Outputs\n   std::array<AttribBuffer, latte::MaxAttribBuffers> mAttribBuffers;\n   std::vector<AttribElem> mAttribElems;\n   std::array<bool, latte::MaxRenderTargets> mPixelOutUsed;\n\n   struct LoopState\n   {\n      uint32_t startPC;\n      uint32_t endPC;\n      spv::Block *head;\n      spv::Block *body;\n      spv::Block *continue_target;\n      spv::Block *merge;\n   };\n   std::vector<LoopState> mLoopStack;\n\n};\n\n} // namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/spirv/spirv_vtx.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"spirv_transpiler.h\"\n#include \"latte/latte_formats.h\"\n\nnamespace spirv\n{\n\nvoid Transpiler::translateVtx_FETCH(const ControlFlowInst &cf, const VertexFetchInst &inst)\n{\n   // MEGA fetches are an optimization on the GPU cache.  There is\n   // no need to do any particular work during translation.\n   //inst.word2.MEGA_FETCH()\n   //inst.word0.MEGA_FETCH_COUNT()\n\n   // Let's only support a very expected set of values\n   decaf_check(inst.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::NO_INDEX_OFFSET);\n   decaf_check(inst.word1.USE_CONST_FIELDS() == 1);\n\n   // Grab the source register information\n   GprSelRef srcGpr;\n   srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP);\n   srcGpr.sel = inst.word0.SRC_SEL_X();\n\n   // Grab the destination register information\n   GprMaskRef destGpr;\n   destGpr.gpr = makeGprRef(inst.gpr.DST_GPR(), inst.gpr.DST_REL(), SQ_INDEX_MODE::LOOP);\n   destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X();\n   destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y();\n   destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z();\n   destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W();\n\n   // Intercept a GSIN resource fetch, and process it specially.\n   if (mType == ShaderParser::Type::Geometry) {\n      auto id = inst.word0.BUFFER_ID() + SQ_RES_OFFSET::GS_TEX_RESOURCE_0;\n      if (id == SQ_RES_OFFSET::GS_GSIN_RESOURCE) {\n\n         // We only support vec4 ring buffer items\n         decaf_check(inst.word2.OFFSET() % 16 == 0);\n         auto ringOffset = inst.word2.OFFSET() / 16;\n\n         auto inputVar = mSpv->inputRingVar(ringOffset);\n\n         // We check to make sure everything makes sense...\n         decaf_check(srcGpr.gpr.indexMode == GprIndexMode::None);\n\n         spv::Id vertIdxVal;\n         if (srcGpr.gpr.number == 0 && srcGpr.sel == latte::SQ_SEL::SEL_X) {\n            vertIdxVal = mSpv->makeIntConstant(0);\n         } else if (srcGpr.gpr.number == 0 && srcGpr.sel == latte::SQ_SEL::SEL_Y) {\n            vertIdxVal = mSpv->makeIntConstant(1);\n         } else if (srcGpr.gpr.number == 0 && srcGpr.sel == latte::SQ_SEL::SEL_W) {\n            vertIdxVal = mSpv->makeIntConstant(2);\n         } else if (srcGpr.gpr.number == 1 && srcGpr.sel == latte::SQ_SEL::SEL_X) {\n            vertIdxVal = mSpv->makeIntConstant(3);\n         } else if (srcGpr.gpr.number == 1 && srcGpr.sel == latte::SQ_SEL::SEL_Y) {\n            vertIdxVal = mSpv->makeIntConstant(4);\n         } else if (srcGpr.gpr.number == 1 && srcGpr.sel == latte::SQ_SEL::SEL_W) {\n            vertIdxVal = mSpv->makeIntConstant(5);\n         } else {\n            decaf_abort(\"Unexpected vertex index selection\")\n         }\n\n         auto outputPtr = mSpv->createAccessChain(spv::StorageClassInput, inputVar, { vertIdxVal });\n         auto outputVal = mSpv->createLoad(outputPtr, spv::NoPrecision);\n\n         auto gprRef = mSpv->getGprRef(destGpr.gpr);\n\n         auto destVal = spv::NoResult;\n         if (!latte::isSwizzleFullyUnmasked(destGpr.mask)) {\n            destVal = mSpv->createLoad(gprRef, spv::NoPrecision);\n         }\n\n         auto maskedVal = mSpv->applySelMask(destVal, outputVal, destGpr.mask);\n\n         mSpv->createStore(maskedVal, gprRef);\n\n         return;\n      }\n   } else if (mType == ShaderParser::Type::DataCache) {\n      auto id = inst.word0.BUFFER_ID() + SQ_RES_OFFSET::GS_TEX_RESOURCE_0;\n      if (id == SQ_RES_OFFSET::GS_GSIN_RESOURCE) {\n         auto ringOffsetVal = mSpv->createLoad(mSpv->ringOffsetVar(), spv::NoPrecision);\n\n         // We check to make sure everything makes sense...\n         decaf_check(srcGpr.gpr.number == 0);\n         decaf_check(srcGpr.gpr.indexMode == GprIndexMode::None);\n         decaf_check(srcGpr.sel == latte::SQ_SEL::SEL_X);\n\n         // Note that this is blocked out above, so this code may not be correct...\n         decaf_check(inst.word2.OFFSET() % 16 == 0);\n         auto itemOffsetVal = mSpv->makeUintConstant(inst.word2.OFFSET() / 16);\n\n         auto realOffset = mSpv->createBinOp(spv::OpIAdd, mSpv->uintType(), ringOffsetVal, itemOffsetVal);\n\n         auto outputPtr = mSpv->createAccessChain(spv::StorageClassPrivate, mSpv->ringVar(), { realOffset });\n         auto outputVal = mSpv->createLoad(outputPtr, spv::NoPrecision);\n\n         auto gprRef = mSpv->getGprRef(destGpr.gpr);\n\n         // TODO: This logic is duplicated a bunch of places, we should converge\n         // this into a centralized function in mSpv...\n         auto destVal = spv::NoResult;\n         if (!latte::isSwizzleFullyUnmasked(destGpr.mask)) {\n            destVal = mSpv->createLoad(gprRef, spv::NoPrecision);\n         }\n\n         auto maskedVal = mSpv->applySelMask(destVal, outputVal, destGpr.mask);\n\n         mSpv->createStore(maskedVal, gprRef);\n\n         return;\n      }\n   }\n\n   decaf_check(inst.word2.OFFSET() == 0);\n\n   uint32_t cbufferIdx;\n   if (mType == ShaderParser::Type::Vertex) {\n      auto id = inst.word0.BUFFER_ID() + SQ_RES_OFFSET::VS_TEX_RESOURCE_0;\n\n      if (id >= SQ_RES_OFFSET::VS_BUF_RESOURCE_0 && id <= SQ_RES_OFFSET::VS_BUF_RESOURCE_0 + 16) {\n         cbufferIdx = id - SQ_RES_OFFSET::VS_BUF_RESOURCE_0;\n\n      } else {\n         decaf_abort(\"Unsupported vertex shader VTX_FETCH vertex resource\");\n      }\n   } else if (mType == ShaderParser::Type::Geometry) {\n      auto id = inst.word0.BUFFER_ID() + SQ_RES_OFFSET::GS_TEX_RESOURCE_0;\n\n      if (id >= SQ_RES_OFFSET::GS_BUF_RESOURCE_0 && id <= SQ_RES_OFFSET::GS_BUF_RESOURCE_0 + 16) {\n         cbufferIdx = id - SQ_RES_OFFSET::GS_BUF_RESOURCE_0;\n      } else {\n         decaf_abort(\"Unsupported vertex shader VTX_FETCH geometry resource\");\n      }\n   } else if (mType == ShaderParser::Type::Pixel) {\n      auto id = inst.word0.BUFFER_ID() + SQ_RES_OFFSET::PS_TEX_RESOURCE_0;\n\n      if (id >= SQ_RES_OFFSET::PS_BUF_RESOURCE_0 && id <= SQ_RES_OFFSET::PS_BUF_RESOURCE_0 + 16) {\n         cbufferIdx = id - SQ_RES_OFFSET::PS_BUF_RESOURCE_0;\n      } else {\n         decaf_abort(\"Unsupported vertex shader VTX_FETCH pixel resource\");\n      }\n   } else {\n      decaf_abort(\"Unsupported shader type for VTX_FETCH\");\n   }\n\n   auto indexValFloat = mSpv->readGprSelRef(srcGpr);\n   auto indexVal = mSpv->createUnaryOp(spv::OpBitcast, mSpv->uintType(), indexValFloat);\n\n   auto cbufferVar = mSpv->cbufferVar(cbufferIdx);\n\n   // TODO: Should probably move this into SPV\n   auto zeroConst = mSpv->makeUintConstant(0);\n   auto cbufferPtr = mSpv->createAccessChain(spv::StorageClassUniform, cbufferVar, { zeroConst, indexVal });\n   auto outputVal = mSpv->createLoad(cbufferPtr, spv::NoPrecision);\n\n   auto gprRef = mSpv->getGprRef(destGpr.gpr);\n\n   // TODO: This logic is duplicated a bunch of places, we should converge\n   // this into a centralized function in mSpv...\n   auto destVal = spv::NoResult;\n   if (!latte::isSwizzleFullyUnmasked(destGpr.mask)) {\n      destVal = mSpv->createLoad(gprRef, spv::NoPrecision);\n   }\n\n   auto maskedVal = mSpv->applySelMask(destVal, outputVal, destGpr.mask);\n\n   mSpv->createStore(maskedVal, gprRef);\n}\n\nint findVtxSemanticGpr(const std::array<latte::SQ_VTX_SEMANTIC_N, 32>& vtxSemantics, uint8_t semanticId)\n{\n   for (auto i = 0; i < 32; ++i) {\n      auto foundSemanticId = vtxSemantics[i].SEMANTIC_ID();\n      if (semanticId == foundSemanticId) {\n         return i;\n      }\n   }\n   return -1;\n}\n\nvoid Transpiler::translateVtx_SEMANTIC(const ControlFlowInst &cf, const VertexFetchInst &inst)\n{\n   // Because this instruction has an SRC register to determine where to source\n   //  the indexing data from, but we need a constant, we assume that it will always\n   //  use R0, and that it will contain what it originally starts with during shader\n   //  startup.  In order to ensure this is the case, we only allow SEMANTIC inst's\n   //  to execute within a Fetch Shader for now, and we also ensure that the fetch\n   //  shader is always invoked as the first instruction of a vertex shader.  We\n   //  hope that this never needs to be fixed, otherwise things are going to get\n   //  EXTREMELY complicated (need to do register expression propagation).\n   decaf_check(mType == ShaderParser::Type::Fetch);\n\n   // We do not support fetch constant fields as I don't even\n   //  know what they are at the moment!\n   decaf_check(!inst.word1.USE_CONST_FIELDS());\n\n   // More stuff I have no clue about...\n   decaf_check(!inst.word1.SRF_MODE_ALL());\n\n   // We know what this one does, but I don't know how to implement it,\n   //  and I do not think it will be needed right now...\n   decaf_check(!inst.word2.CONST_BUF_NO_STRIDE());\n\n   // We use the DATA_FORMAT to determine the fetch sizing.  This allows us\n   //  to more easily handle the splitting of XYZW groups below.\n   //inst.word2.MEGA_FETCH();\n   //inst.word0.MEGA_FETCH_COUNT();\n\n   GprSelRef srcGpr;\n   srcGpr.gpr = makeGprRef(inst.word0.SRC_GPR(), inst.word0.SRC_REL(), SQ_INDEX_MODE::LOOP);\n   srcGpr.sel = inst.word0.SRC_SEL_X();\n\n   // We do not support indexing for fetch from buffers...\n   decaf_check(srcGpr.gpr.indexMode == GprIndexMode::None);\n\n   // Try and locate a matching semantic from the semantic table\n   auto semanticGprIdx = findVtxSemanticGpr(mSqVtxSemantics, inst.sem.SEMANTIC_ID());\n   if (semanticGprIdx < 0) {\n      // We didn't find anythign in the semantic table, we can simply\n      // ignore this instruction in that case.\n      return;\n   }\n\n   GprMaskRef destGpr;\n   destGpr.gpr.indexMode = GprIndexMode::None;\n   destGpr.gpr.number = static_cast<uint32_t>(semanticGprIdx + 1);\n   destGpr.mask[SQ_CHAN::X] = inst.word1.DST_SEL_X();\n   destGpr.mask[SQ_CHAN::Y] = inst.word1.DST_SEL_Y();\n   destGpr.mask[SQ_CHAN::Z] = inst.word1.DST_SEL_Z();\n   destGpr.mask[SQ_CHAN::W] = inst.word1.DST_SEL_W();\n\n   if (destGpr.gpr.number == 0xffffffff) {\n      // This is not semantically mapped, so we can actually skip it entirely!\n      return;\n   }\n\n   auto dataFormat = inst.word1.DATA_FORMAT();\n   auto fmtMeta = latte::getDataFormatMeta(dataFormat);\n\n   // We currently only support vertex fetches inside a fetch shader...\n   decaf_check(mType == ShaderParser::Type::Fetch);\n\n   // Figure out which attribute buffer this is referencing\n   auto bufferId = inst.word0.BUFFER_ID();\n   auto attribBase = latte::SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 - latte::SQ_RES_OFFSET::VS_TEX_RESOURCE_0;\n   decaf_check(attribBase <= bufferId && bufferId <= attribBase + 16);\n\n   auto attribBufferId = bufferId - attribBase;\n   auto bufferOffset = inst.word2.OFFSET();\n\n   AttribBuffer::IndexMode indexMode;\n   AttribBuffer::DivisorMode divisorMode;\n\n   if (inst.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::VERTEX_DATA) {\n      indexMode = AttribBuffer::IndexMode::PerVertex;\n      divisorMode = AttribBuffer::DivisorMode::CONST_1;\n   } else if (inst.word0.FETCH_TYPE() == SQ_VTX_FETCH_TYPE::INSTANCE_DATA) {\n      indexMode = AttribBuffer::IndexMode::PerInstance;\n\n      if (srcGpr.sel == SQ_SEL::SEL_Y) {\n         divisorMode = AttribBuffer::DivisorMode::REGISTER_0;\n      } else if (srcGpr.sel == SQ_SEL::SEL_Z) {\n         divisorMode = AttribBuffer::DivisorMode::REGISTER_1;\n      } else if (srcGpr.sel == SQ_SEL::SEL_W) {\n         divisorMode = AttribBuffer::DivisorMode::CONST_1;\n      } else {\n         decaf_abort(\"Unexpected vertex fetch divisor selector\");\n      }\n   } else {\n      decaf_abort(\"Unexpected vertex fetch type\");\n   }\n\n   // Set up our buffer information\n   auto& inputBuffer = mAttribBuffers[attribBufferId];\n   if (inputBuffer.isUsed) {\n      // If its already in use, confirm matching indexing\n      decaf_check(inputBuffer.indexMode == indexMode);\n      decaf_check(inputBuffer.divisorMode == divisorMode);\n   } else {\n      // If its not in use, lets set this up ourselves.\n      inputBuffer = AttribBuffer { true, indexMode, divisorMode };\n   }\n\n   // Set up our attribute information\n   AttribElem inputAttrib;\n   inputAttrib.bufferIndex = attribBufferId;\n   inputAttrib.offset = bufferOffset;\n   inputAttrib.elemWidth = fmtMeta.inputWidth;\n   inputAttrib.elemCount = fmtMeta.inputCount;\n   mAttribElems.push_back(inputAttrib);\n   auto inputId = mAttribElems.size() - 1;\n\n   auto swapMode = inst.word2.ENDIAN_SWAP();\n   auto formatComp = inst.word1.FORMAT_COMP_ALL();\n   auto numFormat = inst.word1.NUM_FORMAT_ALL();\n\n   spv::Id sourceElemType = mSpv->uintType();\n\n   // Get a vector of the source element type\n   auto sourceType = mSpv->vecType(sourceElemType, fmtMeta.inputCount);\n\n   // Create the input variable\n   auto sourceVar = mSpv->inputAttribVar(static_cast<uint32_t>(inputId), sourceType);\n\n   // Load the input data\n   auto source = mSpv->createLoad(sourceVar, spv::NoPrecision);\n\n   // Perform byte swapping on the input data, where appropriate\n   if (swapMode == SQ_ENDIAN::SWAP_8IN16) {\n      decaf_check(fmtMeta.inputWidth == 16 || fmtMeta.inputWidth == 32);\n      source = mSpv->bswap8in16(source);\n   } else if (swapMode == SQ_ENDIAN::SWAP_8IN32) {\n      decaf_check(fmtMeta.inputWidth == 32);\n      source = mSpv->bswap8in32(source);\n   } else if (swapMode != SQ_ENDIAN::NONE) {\n      decaf_abort(\"Encountered unexpected endian swap mode\");\n   }\n\n   // Store each element for operating on independantly...\n   std::array<spv::Id, 4> inputElems = { spv::NoResult, spv::NoResult, spv::NoResult, spv::NoResult };\n   if (fmtMeta.inputCount > 1) {\n      for (auto i = 0u; i < fmtMeta.inputCount; ++i) {\n         inputElems[i] = mSpv->createOp(spv::OpCompositeExtract, sourceElemType, { source, i });\n      }\n   } else {\n      for (auto i = 0u; i < fmtMeta.inputCount; ++i) {\n         inputElems[i] = source;\n      }\n   }\n\n   // Upcast each element to a uint for bitshift extraction\n   if (sourceElemType != mSpv->uintType()) {\n      for (auto i = 0u; i < fmtMeta.inputCount; ++i) {\n         inputElems[i] = mSpv->createUnaryOp(spv::OpUConvert, mSpv->uintType(), inputElems[i]);\n      }\n   }\n\n   // Calculate the number of resulting elements based on the lengths\n   int outputElemCount = 0;\n   for (auto i = 0u; i < 4u; ++i) {\n      if (fmtMeta.elems[i].length > 0) {\n         outputElemCount = i + 1;\n      }\n   }\n\n   // Extract the appropriate bits if needed...\n   std::array<spv::Id, 4> elems = { spv::NoResult, spv::NoResult, spv::NoResult, spv::NoResult };\n   for (auto i = 0; i < outputElemCount; ++i) {\n      auto &elem = fmtMeta.elems[i];\n\n      // If the element width matches perfectly, we can just use it directly\n      if (elem.length == fmtMeta.inputWidth) {\n         elems[i] = inputElems[elem.index];\n         continue;\n      }\n\n      auto startConst = mSpv->makeIntConstant(elem.start);\n      auto lengthConst = mSpv->makeIntConstant(elem.length);\n      elems[i] = mSpv->createTriOp(spv::OpBitFieldUExtract, mSpv->uintType(), inputElems[elem.index], startConst, lengthConst);\n   }\n\n   for (auto i = 0; i < outputElemCount; ++i) {\n      auto &elem = fmtMeta.elems[i];\n      auto fieldMax = static_cast<uint64_t>(1u) << elem.length;\n\n      if (fmtMeta.type == DataFormatMetaType::FLOAT) {\n         if (elem.length == 16) {\n            // Bitcast the data to a float from half-float data\n            // elem = unpackHalf2x16(elem).x\n            auto unpackedFloat2 = mSpv->createBuiltinCall(mSpv->float2Type(), mSpv->glslStd450(), GLSLstd450::GLSLstd450UnpackHalf2x16, { elems[i] });\n            elems[i] = mSpv->createOp(spv::Op::OpCompositeExtract, mSpv->floatType(), { unpackedFloat2, 0 });\n         } else if (elem.length == 32) {\n            // Bitcast the data to a float\n            // elem = *(float*)&elem\n            elems[i] = mSpv->createUnaryOp(spv::Op::OpBitcast, mSpv->floatType(), elems[i]);\n         } else if (elem.length == 10) {\n            elems[i] = mSpv->unpackFloat10(elems[i]);\n         } else if (elem.length == 11) {\n            elems[i] = mSpv->unpackFloat11(elems[i]);\n         } else {\n            decaf_abort(\"Unexpected float data width\");\n         }\n      } else {\n         if (formatComp == latte::SQ_FORMAT_COMP::SIGNED && elem.length > 2) {\n            // Perform sign-extension and conversion to a signed integer\n            if (elem.length == 32) {\n               // If its 32 bits, we don't need to perform sign extension, just bitcast it\n               // elem = *(int*)&elem\n               elems[i] = mSpv->createUnaryOp(spv::Op::OpBitcast, mSpv->intType(), elems[i]);\n            } else {\n               // If its less than 32 bits, we use bitfield extraction to sign extend\n               auto offsetConst = mSpv->makeIntConstant(0);\n               auto lengthConst = mSpv->makeIntConstant(elem.length);\n               auto signedElem = mSpv->createUnaryOp(spv::OpBitcast, mSpv->intType(), elems[i]);\n               elems[i] = mSpv->createTriOp(spv::Op::OpBitFieldSExtract, mSpv->intType(), signedElem, offsetConst, lengthConst);\n            }\n         } else {\n            // We are already in UINT format as we needed\n         }\n      }\n\n      if (numFormat == latte::SQ_NUM_FORMAT::NORM) {\n         decaf_check(fmtMeta.type == DataFormatMetaType::UINT);\n\n         auto fieldMask = fieldMax - 1;\n\n         if (formatComp == latte::SQ_FORMAT_COMP::SIGNED && elem.length > 2) {\n            // Type must already be a signed type from above\n            decaf_check(mSpv->getTypeId(elems[i]) == mSpv->intType());\n\n            // elem = clamp((float)(elem) / float(FIELD_MASK/2), -1.0f, 1.0f)\n            auto normMaxConst = mSpv->makeFloatConstant(float(fieldMask / 2));\n            auto normNegConst = mSpv->makeFloatConstant(-1.0f);\n            auto normPosConst = mSpv->makeFloatConstant(+1.0f);\n            auto floatElem = mSpv->createUnaryOp(spv::OpConvertSToF, mSpv->floatType(), elems[i]);\n            auto normElem = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), floatElem, normMaxConst);\n            elems[i] = mSpv->createBuiltinCall(mSpv->floatType(), mSpv->glslStd450(), GLSLstd450::GLSLstd450FClamp,\n                                               { normElem, normNegConst, normPosConst });\n         } else {\n            // Type must already be a unsigned type from above\n            decaf_check(mSpv->getTypeId(elems[i]) == mSpv->uintType());\n\n            // elem = float(elem) / float(FIELD_MASK)\n            auto normMaxConst = mSpv->makeFloatConstant(float(fieldMask));\n            auto floatElem = mSpv->createUnaryOp(spv::OpConvertSToF, mSpv->floatType(), elems[i]);\n            elems[i] = mSpv->createBinOp(spv::OpFDiv, mSpv->floatType(), floatElem, normMaxConst);\n         }\n      } else if (numFormat == latte::SQ_NUM_FORMAT::INT) {\n         auto origTypeId = mSpv->getTypeId(elems[i]);\n\n         if (formatComp == latte::SQ_FORMAT_COMP::SIGNED) {\n            // elem = int(elem)\n            if (origTypeId == mSpv->floatType()) {\n               elems[i] = mSpv->createUnaryOp(spv::Op::OpConvertFToS, mSpv->intType(), elems[i]);\n            } else if (origTypeId == mSpv->intType()) {\n               // We are already an int type, no need to convert\n            } else if (origTypeId == mSpv->uintType()) {\n               elems[i] = mSpv->createUnaryOp(spv::OpBitcast, mSpv->intType(), elems[i]);\n            } else {\n               decaf_abort(\"Unexpected format conversion type.\");\n            }\n         } else {\n            // elem = uint(elem)\n            if (origTypeId == mSpv->floatType()) {\n               elems[i] = mSpv->createUnaryOp(spv::Op::OpConvertFToU, mSpv->uintType(), elems[i]);\n            } else if (origTypeId == mSpv->intType()) {\n               elems[i] = mSpv->createUnaryOp(spv::OpBitcast, mSpv->uintType(), elems[i]);\n            } else if (origTypeId == mSpv->uintType()) {\n               // We are already a uint type, no need to convert\n            } else {\n               decaf_abort(\"Unexpected format conversion type.\");\n            }\n         }\n      } else if (numFormat == latte::SQ_NUM_FORMAT::SCALED) {\n         // formatComp ignored as SIGNED/UNSIGNED makes no sense at this stage\n\n         auto origTypeId = mSpv->getTypeId(elems[i]);\n\n         // elem = float(elem)\n         if (origTypeId == mSpv->floatType()) {\n            // We are already a float type, no need to convert\n         } else if (origTypeId == mSpv->intType()) {\n            elems[i] = mSpv->createUnaryOp(spv::Op::OpConvertSToF, mSpv->floatType(), elems[i]);\n         } else if (origTypeId == mSpv->uintType()) {\n            elems[i] = mSpv->createUnaryOp(spv::Op::OpConvertUToF, mSpv->floatType(), elems[i]);\n         } else {\n            decaf_abort(\"Unexpected format conversion type.\");\n         }\n      } else {\n         decaf_abort(\"Unexpected vertex fetch numFormat\");\n      }\n   }\n\n   // Figure out what format of elements we have as output\n   auto elemsType = mSpv->getTypeId(elems[0]);\n   for (auto i = 1; i < outputElemCount; ++i) {\n      decaf_check(mSpv->getTypeId(elems[i]) == elemsType);\n   }\n\n   // Fill remaining values with defaults\n   for (auto i = outputElemCount; i < 4; ++i) {\n      if (elemsType == mSpv->floatType()) {\n         if (i != 3) {\n            elems[i] = mSpv->makeFloatConstant(0.0f);\n         } else {\n            elems[i] = mSpv->makeFloatConstant(1.0f);\n         }\n      } else if (elemsType == mSpv->intType()) {\n         if (i != 3) {\n            elems[i] = mSpv->makeIntConstant(0);\n         } else {\n            elems[i] = mSpv->makeIntConstant(1);\n         }\n      } else if (elemsType == mSpv->uintType()) {\n         if (i != 3) {\n            elems[i] = mSpv->makeUintConstant(0);\n         } else {\n            elems[i] = mSpv->makeUintConstant(1);\n         }\n      } else {\n         decaf_abort(\"Unexpected element format output in fetch\")\n      }\n   }\n\n   auto outputType = mSpv->vecType(elemsType, 4);\n   auto outputVal = mSpv->createOp(spv::Op::OpCompositeConstruct, outputType,\n                                   { elems[0], elems[1], elems[2], elems[3] });\n\n   mSpv->writeGprMaskRef(destGpr, outputVal);\n}\n\n} // namespace spirv\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vk_mem_alloc.cpp",
    "content": "#ifdef DECAF_VULKAN\n\n#define VMA_IMPLEMENTATION\n#include \"vk_mem_alloc_decaf.h\"\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vk_mem_alloc.h",
    "content": "//\n// Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n// THE SOFTWARE.\n//\n\n#ifndef AMD_VULKAN_MEMORY_ALLOCATOR_H\n#define AMD_VULKAN_MEMORY_ALLOCATOR_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n   /** \\mainpage Vulkan Memory Allocator\n\n   <b>Version 2.3.0</b> (2019-12-04)\n\n   Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved. \\n\n   License: MIT\n\n   Documentation of all members: vk_mem_alloc.h\n\n   \\section main_table_of_contents Table of contents\n\n   - <b>User guide</b>\n     - \\subpage quick_start\n       - [Project setup](@ref quick_start_project_setup)\n       - [Initialization](@ref quick_start_initialization)\n       - [Resource allocation](@ref quick_start_resource_allocation)\n     - \\subpage choosing_memory_type\n       - [Usage](@ref choosing_memory_type_usage)\n       - [Required and preferred flags](@ref choosing_memory_type_required_preferred_flags)\n       - [Explicit memory types](@ref choosing_memory_type_explicit_memory_types)\n       - [Custom memory pools](@ref choosing_memory_type_custom_memory_pools)\n       - [Dedicated allocations](@ref choosing_memory_type_dedicated_allocations)\n     - \\subpage memory_mapping\n       - [Mapping functions](@ref memory_mapping_mapping_functions)\n       - [Persistently mapped memory](@ref memory_mapping_persistently_mapped_memory)\n       - [Cache flush and invalidate](@ref memory_mapping_cache_control)\n       - [Finding out if memory is mappable](@ref memory_mapping_finding_if_memory_mappable)\n     - \\subpage staying_within_budget\n       - [Querying for budget](@ref staying_within_budget_querying_for_budget)\n       - [Controlling memory usage](@ref staying_within_budget_controlling_memory_usage)\n     - \\subpage custom_memory_pools\n       - [Choosing memory type index](@ref custom_memory_pools_MemTypeIndex)\n       - [Linear allocation algorithm](@ref linear_algorithm)\n         - [Free-at-once](@ref linear_algorithm_free_at_once)\n         - [Stack](@ref linear_algorithm_stack)\n         - [Double stack](@ref linear_algorithm_double_stack)\n         - [Ring buffer](@ref linear_algorithm_ring_buffer)\n       - [Buddy allocation algorithm](@ref buddy_algorithm)\n     - \\subpage defragmentation\n       - [Defragmenting CPU memory](@ref defragmentation_cpu)\n       - [Defragmenting GPU memory](@ref defragmentation_gpu)\n       - [Additional notes](@ref defragmentation_additional_notes)\n       - [Writing custom allocation algorithm](@ref defragmentation_custom_algorithm)\n     - \\subpage lost_allocations\n     - \\subpage statistics\n       - [Numeric statistics](@ref statistics_numeric_statistics)\n       - [JSON dump](@ref statistics_json_dump)\n     - \\subpage allocation_annotation\n       - [Allocation user data](@ref allocation_user_data)\n       - [Allocation names](@ref allocation_names)\n     - \\subpage debugging_memory_usage\n       - [Memory initialization](@ref debugging_memory_usage_initialization)\n       - [Margins](@ref debugging_memory_usage_margins)\n       - [Corruption detection](@ref debugging_memory_usage_corruption_detection)\n     - \\subpage record_and_replay\n   - \\subpage usage_patterns\n     - [Common mistakes](@ref usage_patterns_common_mistakes)\n     - [Simple patterns](@ref usage_patterns_simple)\n     - [Advanced patterns](@ref usage_patterns_advanced)\n   - \\subpage configuration\n     - [Pointers to Vulkan functions](@ref config_Vulkan_functions)\n     - [Custom host memory allocator](@ref custom_memory_allocator)\n     - [Device memory allocation callbacks](@ref allocation_callbacks)\n     - [Device heap memory limit](@ref heap_memory_limit)\n     - \\subpage vk_khr_dedicated_allocation\n   - \\subpage general_considerations\n     - [Thread safety](@ref general_considerations_thread_safety)\n     - [Validation layer warnings](@ref general_considerations_validation_layer_warnings)\n     - [Allocation algorithm](@ref general_considerations_allocation_algorithm)\n     - [Features not supported](@ref general_considerations_features_not_supported)\n\n   \\section main_see_also See also\n\n   - [Product page on GPUOpen](https://gpuopen.com/gaming-product/vulkan-memory-allocator/)\n   - [Source repository on GitHub](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)\n\n\n\n\n   \\page quick_start Quick start\n\n   \\section quick_start_project_setup Project setup\n\n   Vulkan Memory Allocator comes in form of a \"stb-style\" single header file.\n   You don't need to build it as a separate library project.\n   You can add this file directly to your project and submit it to code repository next to your other source files.\n\n   \"Single header\" doesn't mean that everything is contained in C/C++ declarations,\n   like it tends to be in case of inline functions or C++ templates.\n   It means that implementation is bundled with interface in a single file and needs to be extracted using preprocessor macro.\n   If you don't do it properly, you will get linker errors.\n\n   To do it properly:\n\n   -# Include \"vk_mem_alloc.h\" file in each CPP file where you want to use the library.\n      This includes declarations of all members of the library.\n   -# In exacly one CPP file define following macro before this include.\n      It enables also internal definitions.\n\n   \\code\n   #define VMA_IMPLEMENTATION\n   #include \"vk_mem_alloc.h\"\n   \\endcode\n\n   It may be a good idea to create dedicated CPP file just for this purpose.\n\n   Note on language: This library is written in C++, but has C-compatible interface.\n   Thus you can include and use vk_mem_alloc.h in C or C++ code, but full\n   implementation with `VMA_IMPLEMENTATION` macro must be compiled as C++, NOT as C.\n\n   Please note that this library includes header `<vulkan/vulkan.h>`, which in turn\n   includes `<windows.h>` on Windows. If you need some specific macros defined\n   before including these headers (like `WIN32_LEAN_AND_MEAN` or\n   `WINVER` for Windows, `VK_USE_PLATFORM_WIN32_KHR` for Vulkan), you must define\n   them before every `#include` of this library.\n\n\n   \\section quick_start_initialization Initialization\n\n   At program startup:\n\n   -# Initialize Vulkan to have `VkPhysicalDevice` and `VkDevice` object.\n   -# Fill VmaAllocatorCreateInfo structure and create #VmaAllocator object by\n      calling vmaCreateAllocator().\n\n   \\code\n   VmaAllocatorCreateInfo allocatorInfo = {};\n   allocatorInfo.physicalDevice = physicalDevice;\n   allocatorInfo.device = device;\n\n   VmaAllocator allocator;\n   vmaCreateAllocator(&allocatorInfo, &allocator);\n   \\endcode\n\n   \\section quick_start_resource_allocation Resource allocation\n\n   When you want to create a buffer or image:\n\n   -# Fill `VkBufferCreateInfo` / `VkImageCreateInfo` structure.\n   -# Fill VmaAllocationCreateInfo structure.\n   -# Call vmaCreateBuffer() / vmaCreateImage() to get `VkBuffer`/`VkImage` with memory\n      already allocated and bound to it.\n\n   \\code\n   VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\n   bufferInfo.size = 65536;\n   bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\n   VmaAllocationCreateInfo allocInfo = {};\n   allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n\n   VkBuffer buffer;\n   VmaAllocation allocation;\n   vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);\n   \\endcode\n\n   Don't forget to destroy your objects when no longer needed:\n\n   \\code\n   vmaDestroyBuffer(allocator, buffer, allocation);\n   vmaDestroyAllocator(allocator);\n   \\endcode\n\n\n   \\page choosing_memory_type Choosing memory type\n\n   Physical devices in Vulkan support various combinations of memory heaps and\n   types. Help with choosing correct and optimal memory type for your specific\n   resource is one of the key features of this library. You can use it by filling\n   appropriate members of VmaAllocationCreateInfo structure, as described below.\n   You can also combine multiple methods.\n\n   -# If you just want to find memory type index that meets your requirements, you\n      can use function: vmaFindMemoryTypeIndex(), vmaFindMemoryTypeIndexForBufferInfo(),\n      vmaFindMemoryTypeIndexForImageInfo().\n   -# If you want to allocate a region of device memory without association with any\n      specific image or buffer, you can use function vmaAllocateMemory(). Usage of\n      this function is not recommended and usually not needed.\n      vmaAllocateMemoryPages() function is also provided for creating multiple allocations at once,\n      which may be useful for sparse binding.\n   -# If you already have a buffer or an image created, you want to allocate memory\n      for it and then you will bind it yourself, you can use function\n      vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage().\n      For binding you should use functions: vmaBindBufferMemory(), vmaBindImageMemory()\n      or their extended versions: vmaBindBufferMemory2(), vmaBindImageMemory2().\n   -# If you want to create a buffer or an image, allocate memory for it and bind\n      them together, all in one call, you can use function vmaCreateBuffer(),\n      vmaCreateImage(). This is the easiest and recommended way to use this library.\n\n   When using 3. or 4., the library internally queries Vulkan for memory types\n   supported for that buffer or image (function `vkGetBufferMemoryRequirements()`)\n   and uses only one of these types.\n\n   If no memory type can be found that meets all the requirements, these functions\n   return `VK_ERROR_FEATURE_NOT_PRESENT`.\n\n   You can leave VmaAllocationCreateInfo structure completely filled with zeros.\n   It means no requirements are specified for memory type.\n   It is valid, although not very useful.\n\n   \\section choosing_memory_type_usage Usage\n\n   The easiest way to specify memory requirements is to fill member\n   VmaAllocationCreateInfo::usage using one of the values of enum #VmaMemoryUsage.\n   It defines high level, common usage types.\n   For more details, see description of this enum.\n\n   For example, if you want to create a uniform buffer that will be filled using\n   transfer only once or infrequently and used for rendering every frame, you can\n   do it using following code:\n\n   \\code\n   VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\n   bufferInfo.size = 65536;\n   bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\n   VmaAllocationCreateInfo allocInfo = {};\n   allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n\n   VkBuffer buffer;\n   VmaAllocation allocation;\n   vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);\n   \\endcode\n\n   \\section choosing_memory_type_required_preferred_flags Required and preferred flags\n\n   You can specify more detailed requirements by filling members\n   VmaAllocationCreateInfo::requiredFlags and VmaAllocationCreateInfo::preferredFlags\n   with a combination of bits from enum `VkMemoryPropertyFlags`. For example,\n   if you want to create a buffer that will be persistently mapped on host (so it\n   must be `HOST_VISIBLE`) and preferably will also be `HOST_COHERENT` and `HOST_CACHED`,\n   use following code:\n\n   \\code\n   VmaAllocationCreateInfo allocInfo = {};\n   allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;\n   allocInfo.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;\n   allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;\n\n   VkBuffer buffer;\n   VmaAllocation allocation;\n   vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);\n   \\endcode\n\n   A memory type is chosen that has all the required flags and as many preferred\n   flags set as possible.\n\n   If you use VmaAllocationCreateInfo::usage, it is just internally converted to\n   a set of required and preferred flags.\n\n   \\section choosing_memory_type_explicit_memory_types Explicit memory types\n\n   If you inspected memory types available on the physical device and you have\n   a preference for memory types that you want to use, you can fill member\n   VmaAllocationCreateInfo::memoryTypeBits. It is a bit mask, where each bit set\n   means that a memory type with that index is allowed to be used for the\n   allocation. Special value 0, just like `UINT32_MAX`, means there are no\n   restrictions to memory type index.\n\n   Please note that this member is NOT just a memory type index.\n   Still you can use it to choose just one, specific memory type.\n   For example, if you already determined that your buffer should be created in\n   memory type 2, use following code:\n\n   \\code\n   uint32_t memoryTypeIndex = 2;\n\n   VmaAllocationCreateInfo allocInfo = {};\n   allocInfo.memoryTypeBits = 1u << memoryTypeIndex;\n\n   VkBuffer buffer;\n   VmaAllocation allocation;\n   vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);\n   \\endcode\n\n   \\section choosing_memory_type_custom_memory_pools Custom memory pools\n\n   If you allocate from custom memory pool, all the ways of specifying memory\n   requirements described above are not applicable and the aforementioned members\n   of VmaAllocationCreateInfo structure are ignored. Memory type is selected\n   explicitly when creating the pool and then used to make all the allocations from\n   that pool. For further details, see \\ref custom_memory_pools.\n\n   \\section choosing_memory_type_dedicated_allocations Dedicated allocations\n\n   Memory for allocations is reserved out of larger block of `VkDeviceMemory`\n   allocated from Vulkan internally. That's the main feature of this whole library.\n   You can still request a separate memory block to be created for an allocation,\n   just like you would do in a trivial solution without using any allocator.\n   In that case, a buffer or image is always bound to that memory at offset 0.\n   This is called a \"dedicated allocation\".\n   You can explicitly request it by using flag #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.\n   The library can also internally decide to use dedicated allocation in some cases, e.g.:\n\n   - When the size of the allocation is large.\n   - When [VK_KHR_dedicated_allocation](@ref vk_khr_dedicated_allocation) extension is enabled\n     and it reports that dedicated allocation is required or recommended for the resource.\n   - When allocation of next big memory block fails due to not enough device memory,\n     but allocation with the exact requested size succeeds.\n\n\n   \\page memory_mapping Memory mapping\n\n   To \"map memory\" in Vulkan means to obtain a CPU pointer to `VkDeviceMemory`,\n   to be able to read from it or write to it in CPU code.\n   Mapping is possible only of memory allocated from a memory type that has\n   `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` flag.\n   Functions `vkMapMemory()`, `vkUnmapMemory()` are designed for this purpose.\n   You can use them directly with memory allocated by this library,\n   but it is not recommended because of following issue:\n   Mapping the same `VkDeviceMemory` block multiple times is illegal - only one mapping at a time is allowed.\n   This includes mapping disjoint regions. Mapping is not reference-counted internally by Vulkan.\n   Because of this, Vulkan Memory Allocator provides following facilities:\n\n   \\section memory_mapping_mapping_functions Mapping functions\n\n   The library provides following functions for mapping of a specific #VmaAllocation: vmaMapMemory(), vmaUnmapMemory().\n   They are safer and more convenient to use than standard Vulkan functions.\n   You can map an allocation multiple times simultaneously - mapping is reference-counted internally.\n   You can also map different allocations simultaneously regardless of whether they use the same `VkDeviceMemory` block.\n   The way it's implemented is that the library always maps entire memory block, not just region of the allocation.\n   For further details, see description of vmaMapMemory() function.\n   Example:\n\n   \\code\n   // Having these objects initialized:\n\n   struct ConstantBuffer\n   {\n       ...\n   };\n   ConstantBuffer constantBufferData;\n\n   VmaAllocator allocator;\n   VkBuffer constantBuffer;\n   VmaAllocation constantBufferAllocation;\n\n   // You can map and fill your buffer using following code:\n\n   void* mappedData;\n   vmaMapMemory(allocator, constantBufferAllocation, &mappedData);\n   memcpy(mappedData, &constantBufferData, sizeof(constantBufferData));\n   vmaUnmapMemory(allocator, constantBufferAllocation);\n   \\endcode\n\n   When mapping, you may see a warning from Vulkan validation layer similar to this one:\n\n   <i>Mapping an image with layout VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL can result in undefined behavior if this memory is used by the device. Only GENERAL or PREINITIALIZED should be used.</i>\n\n   It happens because the library maps entire `VkDeviceMemory` block, where different\n   types of images and buffers may end up together, especially on GPUs with unified memory like Intel.\n   You can safely ignore it if you are sure you access only memory of the intended\n   object that you wanted to map.\n\n\n   \\section memory_mapping_persistently_mapped_memory Persistently mapped memory\n\n   Kepping your memory persistently mapped is generally OK in Vulkan.\n   You don't need to unmap it before using its data on the GPU.\n   The library provides a special feature designed for that:\n   Allocations made with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag set in\n   VmaAllocationCreateInfo::flags stay mapped all the time,\n   so you can just access CPU pointer to it any time\n   without a need to call any \"map\" or \"unmap\" function.\n   Example:\n\n   \\code\n   VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\n   bufCreateInfo.size = sizeof(ConstantBuffer);\n   bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;\n\n   VmaAllocationCreateInfo allocCreateInfo = {};\n   allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;\n   allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;\n\n   VkBuffer buf;\n   VmaAllocation alloc;\n   VmaAllocationInfo allocInfo;\n   vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);\n\n   // Buffer is already mapped. You can access its memory.\n   memcpy(allocInfo.pMappedData, &constantBufferData, sizeof(constantBufferData));\n   \\endcode\n\n   There are some exceptions though, when you should consider mapping memory only for a short period of time:\n\n   - When operating system is Windows 7 or 8.x (Windows 10 is not affected because it uses WDDM2),\n     device is discrete AMD GPU,\n     and memory type is the special 256 MiB pool of `DEVICE_LOCAL + HOST_VISIBLE` memory\n     (selected when you use #VMA_MEMORY_USAGE_CPU_TO_GPU),\n     then whenever a memory block allocated from this memory type stays mapped\n     for the time of any call to `vkQueueSubmit()` or `vkQueuePresentKHR()`, this\n     block is migrated by WDDM to system RAM, which degrades performance. It doesn't\n     matter if that particular memory block is actually used by the command buffer\n     being submitted.\n   - On Mac/MoltenVK there is a known bug - [Issue #175](https://github.com/KhronosGroup/MoltenVK/issues/175)\n     which requires unmapping before GPU can see updated texture.\n   - Keeping many large memory blocks mapped may impact performance or stability of some debugging tools.\n\n   \\section memory_mapping_cache_control Cache flush and invalidate\n\n   Memory in Vulkan doesn't need to be unmapped before using it on GPU,\n   but unless a memory types has `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` flag set,\n   you need to manually **invalidate** cache before reading of mapped pointer\n   and **flush** cache after writing to mapped pointer.\n   Map/unmap operations don't do that automatically.\n   Vulkan provides following functions for this purpose `vkFlushMappedMemoryRanges()`,\n   `vkInvalidateMappedMemoryRanges()`, but this library provides more convenient\n   functions that refer to given allocation object: vmaFlushAllocation(),\n   vmaInvalidateAllocation().\n\n   Regions of memory specified for flush/invalidate must be aligned to\n   `VkPhysicalDeviceLimits::nonCoherentAtomSize`. This is automatically ensured by the library.\n   In any memory type that is `HOST_VISIBLE` but not `HOST_COHERENT`, all allocations\n   within blocks are aligned to this value, so their offsets are always multiply of\n   `nonCoherentAtomSize` and two different allocations never share same \"line\" of this size.\n\n   Please note that memory allocated with #VMA_MEMORY_USAGE_CPU_ONLY is guaranteed to be `HOST_COHERENT`.\n\n   Also, Windows drivers from all 3 **PC** GPU vendors (AMD, Intel, NVIDIA)\n   currently provide `HOST_COHERENT` flag on all memory types that are\n   `HOST_VISIBLE`, so on this platform you may not need to bother.\n\n   \\section memory_mapping_finding_if_memory_mappable Finding out if memory is mappable\n\n   It may happen that your allocation ends up in memory that is `HOST_VISIBLE` (available for mapping)\n   despite it wasn't explicitly requested.\n   For example, application may work on integrated graphics with unified memory (like Intel) or\n   allocation from video memory might have failed, so the library chose system memory as fallback.\n\n   You can detect this case and map such allocation to access its memory on CPU directly,\n   instead of launching a transfer operation.\n   In order to do that: inspect `allocInfo.memoryType`, call vmaGetMemoryTypeProperties(),\n   and look for `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` flag in properties of that memory type.\n\n   \\code\n   VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\n   bufCreateInfo.size = sizeof(ConstantBuffer);\n   bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\n   VmaAllocationCreateInfo allocCreateInfo = {};\n   allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n   allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;\n\n   VkBuffer buf;\n   VmaAllocation alloc;\n   VmaAllocationInfo allocInfo;\n   vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);\n\n   VkMemoryPropertyFlags memFlags;\n   vmaGetMemoryTypeProperties(allocator, allocInfo.memoryType, &memFlags);\n   if((memFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0)\n   {\n       // Allocation ended up in mappable memory. You can map it and access it directly.\n       void* mappedData;\n       vmaMapMemory(allocator, alloc, &mappedData);\n       memcpy(mappedData, &constantBufferData, sizeof(constantBufferData));\n       vmaUnmapMemory(allocator, alloc);\n   }\n   else\n   {\n       // Allocation ended up in non-mappable memory.\n       // You need to create CPU-side buffer in VMA_MEMORY_USAGE_CPU_ONLY and make a transfer.\n   }\n   \\endcode\n\n   You can even use #VMA_ALLOCATION_CREATE_MAPPED_BIT flag while creating allocations\n   that are not necessarily `HOST_VISIBLE` (e.g. using #VMA_MEMORY_USAGE_GPU_ONLY).\n   If the allocation ends up in memory type that is `HOST_VISIBLE`, it will be persistently mapped and you can use it directly.\n   If not, the flag is just ignored.\n   Example:\n\n   \\code\n   VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\n   bufCreateInfo.size = sizeof(ConstantBuffer);\n   bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\n   VmaAllocationCreateInfo allocCreateInfo = {};\n   allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n   allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;\n\n   VkBuffer buf;\n   VmaAllocation alloc;\n   VmaAllocationInfo allocInfo;\n   vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);\n\n   if(allocInfo.pUserData != nullptr)\n   {\n       // Allocation ended up in mappable memory.\n       // It's persistently mapped. You can access it directly.\n       memcpy(allocInfo.pMappedData, &constantBufferData, sizeof(constantBufferData));\n   }\n   else\n   {\n       // Allocation ended up in non-mappable memory.\n       // You need to create CPU-side buffer in VMA_MEMORY_USAGE_CPU_ONLY and make a transfer.\n   }\n   \\endcode\n\n\n   \\page staying_within_budget Staying within budget\n\n   When developing a graphics-intensive game or program, it is important to avoid allocating\n   more GPU memory than it's physically available. When the memory is over-committed,\n   various bad things can happen, depending on the specific GPU, graphics driver, and\n   operating system:\n\n   - It may just work without any problems.\n   - The application may slow down because some memory blocks are moved to system RAM\n     and the GPU has to access them through PCI Express bus.\n   - A new allocation may take very long time to complete, even few seconds, and possibly\n     freeze entire system.\n   - The new allocation may fail with `VK_ERROR_OUT_OF_DEVICE_MEMORY`.\n   - It may even result in GPU crash (TDR), observed as `VK_ERROR_DEVICE_LOST`\n     returned somewhere later.\n\n   \\section staying_within_budget_querying_for_budget Querying for budget\n\n   To query for current memory usage and available budget, use function vmaGetBudget().\n   Returned structure #VmaBudget contains quantities expressed in bytes, per Vulkan memory heap.\n\n   Please note that this function returns different information and works faster than\n   vmaCalculateStats(). vmaGetBudget() can be called every frame or even before every\n   allocation, while vmaCalculateStats() is intended to be used rarely,\n   only to obtain statistical information, e.g. for debugging purposes.\n\n   It is recommended to use <b>VK_EXT_memory_budget</b> device extension to obtain information\n   about the budget from Vulkan device. VMA is able to use this extension automatically.\n   When not enabled, the allocator behaves same way, but then it estimates current usage\n   and available budget based on its internal information and Vulkan memory heap sizes,\n   which may be less precise. In order to use this extension:\n\n   1. Make sure extensions VK_EXT_memory_budget and VK_KHR_get_physical_device_properties2\n      required by it are available and enable them. Please note that the first is a device\n      extension and the second is instance extension!\n   2. Use flag #VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT when creating #VmaAllocator object.\n   3. Make sure to call vmaSetCurrentFrameIndex() every frame. Budget is queried from\n      Vulkan inside of it to avoid overhead of querying it with every allocation.\n\n   \\section staying_within_budget_controlling_memory_usage Controlling memory usage\n\n   There are many ways in which you can try to stay within the budget.\n\n   First, when making new allocation requires allocating a new memory block, the library\n   tries not to exceed the budget automatically. If a block with default recommended size\n   (e.g. 256 MB) would go over budget, a smaller block is allocated, possibly even\n   dedicated memory for just this resource.\n\n   If the size of the requested resource plus current memory usage is more than the\n   budget, by default the library still tries to create it, leaving it to the Vulkan\n   implementation whether the allocation succeeds or fails. You can change this behavior\n   by using #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag. With it, the allocation is\n   not made if it would exceed the budget or if the budget is already exceeded.\n   Some other allocations become lost instead to make room for it, if the mechanism of\n   [lost allocations](@ref lost_allocations) is used.\n   If that is not possible, the allocation fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY`.\n   Example usage pattern may be to pass the #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag\n   when creating resources that are not essential for the application (e.g. the texture\n   of a specific object) and not to pass it when creating critically important resources\n   (e.g. render targets).\n\n   Finally, you can also use #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT flag to make sure\n   a new allocation is created only when it fits inside one of the existing memory blocks.\n   If it would require to allocate a new block, if fails instead with `VK_ERROR_OUT_OF_DEVICE_MEMORY`.\n   This also ensures that the function call is very fast because it never goes to Vulkan\n   to obtain a new block.\n\n   Please note that creating \\ref custom_memory_pools with VmaPoolCreateInfo::minBlockCount\n   set to more than 0 will try to allocate memory blocks without checking whether they\n   fit within budget.\n\n\n   \\page custom_memory_pools Custom memory pools\n\n   A memory pool contains a number of `VkDeviceMemory` blocks.\n   The library automatically creates and manages default pool for each memory type available on the device.\n   Default memory pool automatically grows in size.\n   Size of allocated blocks is also variable and managed automatically.\n\n   You can create custom pool and allocate memory out of it.\n   It can be useful if you want to:\n\n   - Keep certain kind of allocations separate from others.\n   - Enforce particular, fixed size of Vulkan memory blocks.\n   - Limit maximum amount of Vulkan memory allocated for that pool.\n   - Reserve minimum or fixed amount of Vulkan memory always preallocated for that pool.\n\n   To use custom memory pools:\n\n   -# Fill VmaPoolCreateInfo structure.\n   -# Call vmaCreatePool() to obtain #VmaPool handle.\n   -# When making an allocation, set VmaAllocationCreateInfo::pool to this handle.\n      You don't need to specify any other parameters of this structure, like `usage`.\n\n   Example:\n\n   \\code\n   // Create a pool that can have at most 2 blocks, 128 MiB each.\n   VmaPoolCreateInfo poolCreateInfo = {};\n   poolCreateInfo.memoryTypeIndex = ...\n   poolCreateInfo.blockSize = 128ull * 1024 * 1024;\n   poolCreateInfo.maxBlockCount = 2;\n\n   VmaPool pool;\n   vmaCreatePool(allocator, &poolCreateInfo, &pool);\n\n   // Allocate a buffer out of it.\n   VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\n   bufCreateInfo.size = 1024;\n   bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\n   VmaAllocationCreateInfo allocCreateInfo = {};\n   allocCreateInfo.pool = pool;\n\n   VkBuffer buf;\n   VmaAllocation alloc;\n   VmaAllocationInfo allocInfo;\n   vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);\n   \\endcode\n\n   You have to free all allocations made from this pool before destroying it.\n\n   \\code\n   vmaDestroyBuffer(allocator, buf, alloc);\n   vmaDestroyPool(allocator, pool);\n   \\endcode\n\n   \\section custom_memory_pools_MemTypeIndex Choosing memory type index\n\n   When creating a pool, you must explicitly specify memory type index.\n   To find the one suitable for your buffers or images, you can use helper functions\n   vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo().\n   You need to provide structures with example parameters of buffers or images\n   that you are going to create in that pool.\n\n   \\code\n   VkBufferCreateInfo exampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\n   exampleBufCreateInfo.size = 1024; // Whatever.\n   exampleBufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; // Change if needed.\n\n   VmaAllocationCreateInfo allocCreateInfo = {};\n   allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; // Change if needed.\n\n   uint32_t memTypeIndex;\n   vmaFindMemoryTypeIndexForBufferInfo(allocator, &exampleBufCreateInfo, &allocCreateInfo, &memTypeIndex);\n\n   VmaPoolCreateInfo poolCreateInfo = {};\n   poolCreateInfo.memoryTypeIndex = memTypeIndex;\n   // ...\n   \\endcode\n\n   When creating buffers/images allocated in that pool, provide following parameters:\n\n   - `VkBufferCreateInfo`: Prefer to pass same parameters as above.\n     Otherwise you risk creating resources in a memory type that is not suitable for them, which may result in undefined behavior.\n     Using different `VK_BUFFER_USAGE_` flags may work, but you shouldn't create images in a pool intended for buffers\n     or the other way around.\n   - VmaAllocationCreateInfo: You don't need to pass same parameters. Fill only `pool` member.\n     Other members are ignored anyway.\n\n   \\section linear_algorithm Linear allocation algorithm\n\n   Each Vulkan memory block managed by this library has accompanying metadata that\n   keeps track of used and unused regions. By default, the metadata structure and\n   algorithm tries to find best place for new allocations among free regions to\n   optimize memory usage. This way you can allocate and free objects in any order.\n\n   ![Default allocation algorithm](../gfx/Linear_allocator_1_algo_default.png)\n\n   Sometimes there is a need to use simpler, linear allocation algorithm. You can\n   create custom pool that uses such algorithm by adding flag\n   #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT to VmaPoolCreateInfo::flags while creating\n   #VmaPool object. Then an alternative metadata management is used. It always\n   creates new allocations after last one and doesn't reuse free regions after\n   allocations freed in the middle. It results in better allocation performance and\n   less memory consumed by metadata.\n\n   ![Linear allocation algorithm](../gfx/Linear_allocator_2_algo_linear.png)\n\n   With this one flag, you can create a custom pool that can be used in many ways:\n   free-at-once, stack, double stack, and ring buffer. See below for details.\n\n   \\subsection linear_algorithm_free_at_once Free-at-once\n\n   In a pool that uses linear algorithm, you still need to free all the allocations\n   individually, e.g. by using vmaFreeMemory() or vmaDestroyBuffer(). You can free\n   them in any order. New allocations are always made after last one - free space\n   in the middle is not reused. However, when you release all the allocation and\n   the pool becomes empty, allocation starts from the beginning again. This way you\n   can use linear algorithm to speed up creation of allocations that you are going\n   to release all at once.\n\n   ![Free-at-once](../gfx/Linear_allocator_3_free_at_once.png)\n\n   This mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount\n   value that allows multiple memory blocks.\n\n   \\subsection linear_algorithm_stack Stack\n\n   When you free an allocation that was created last, its space can be reused.\n   Thanks to this, if you always release allocations in the order opposite to their\n   creation (LIFO - Last In First Out), you can achieve behavior of a stack.\n\n   ![Stack](../gfx/Linear_allocator_4_stack.png)\n\n   This mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount\n   value that allows multiple memory blocks.\n\n   \\subsection linear_algorithm_double_stack Double stack\n\n   The space reserved by a custom pool with linear algorithm may be used by two\n   stacks:\n\n   - First, default one, growing up from offset 0.\n   - Second, \"upper\" one, growing down from the end towards lower offsets.\n\n   To make allocation from upper stack, add flag #VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT\n   to VmaAllocationCreateInfo::flags.\n\n   ![Double stack](../gfx/Linear_allocator_7_double_stack.png)\n\n   Double stack is available only in pools with one memory block -\n   VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined.\n\n   When the two stacks' ends meet so there is not enough space between them for a\n   new allocation, such allocation fails with usual\n   `VK_ERROR_OUT_OF_DEVICE_MEMORY` error.\n\n   \\subsection linear_algorithm_ring_buffer Ring buffer\n\n   When you free some allocations from the beginning and there is not enough free space\n   for a new one at the end of a pool, allocator's \"cursor\" wraps around to the\n   beginning and starts allocation there. Thanks to this, if you always release\n   allocations in the same order as you created them (FIFO - First In First Out),\n   you can achieve behavior of a ring buffer / queue.\n\n   ![Ring buffer](../gfx/Linear_allocator_5_ring_buffer.png)\n\n   Pools with linear algorithm support [lost allocations](@ref lost_allocations) when used as ring buffer.\n   If there is not enough free space for a new allocation, but existing allocations\n   from the front of the queue can become lost, they become lost and the allocation\n   succeeds.\n\n   ![Ring buffer with lost allocations](../gfx/Linear_allocator_6_ring_buffer_lost.png)\n\n   Ring buffer is available only in pools with one memory block -\n   VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined.\n\n   \\section buddy_algorithm Buddy allocation algorithm\n\n   There is another allocation algorithm that can be used with custom pools, called\n   \"buddy\". Its internal data structure is based on a tree of blocks, each having\n   size that is a power of two and a half of its parent's size. When you want to\n   allocate memory of certain size, a free node in the tree is located. If it's too\n   large, it is recursively split into two halves (called \"buddies\"). However, if\n   requested allocation size is not a power of two, the size of a tree node is\n   aligned up to the nearest power of two and the remaining space is wasted. When\n   two buddy nodes become free, they are merged back into one larger node.\n\n   ![Buddy allocator](../gfx/Buddy_allocator.png)\n\n   The advantage of buddy allocation algorithm over default algorithm is faster\n   allocation and deallocation, as well as smaller external fragmentation. The\n   disadvantage is more wasted space (internal fragmentation).\n\n   For more information, please read [\"Buddy memory allocation\" on Wikipedia](https://en.wikipedia.org/wiki/Buddy_memory_allocation)\n   or other sources that describe this concept in general.\n\n   To use buddy allocation algorithm with a custom pool, add flag\n   #VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT to VmaPoolCreateInfo::flags while creating\n   #VmaPool object.\n\n   Several limitations apply to pools that use buddy algorithm:\n\n   - It is recommended to use VmaPoolCreateInfo::blockSize that is a power of two.\n     Otherwise, only largest power of two smaller than the size is used for\n     allocations. The remaining space always stays unused.\n   - [Margins](@ref debugging_memory_usage_margins) and\n     [corruption detection](@ref debugging_memory_usage_corruption_detection)\n     don't work in such pools.\n   - [Lost allocations](@ref lost_allocations) don't work in such pools. You can\n     use them, but they never become lost. Support may be added in the future.\n   - [Defragmentation](@ref defragmentation) doesn't work with allocations made from\n     such pool.\n\n   \\page defragmentation Defragmentation\n\n   Interleaved allocations and deallocations of many objects of varying size can\n   cause fragmentation over time, which can lead to a situation where the library is unable\n   to find a continuous range of free memory for a new allocation despite there is\n   enough free space, just scattered across many small free ranges between existing\n   allocations.\n\n   To mitigate this problem, you can use defragmentation feature:\n   structure #VmaDefragmentationInfo2, function vmaDefragmentationBegin(), vmaDefragmentationEnd().\n   Given set of allocations,\n   this function can move them to compact used memory, ensure more continuous free\n   space and possibly also free some `VkDeviceMemory` blocks.\n\n   What the defragmentation does is:\n\n   - Updates #VmaAllocation objects to point to new `VkDeviceMemory` and offset.\n     After allocation has been moved, its VmaAllocationInfo::deviceMemory and/or\n     VmaAllocationInfo::offset changes. You must query them again using\n     vmaGetAllocationInfo() if you need them.\n   - Moves actual data in memory.\n\n   What it doesn't do, so you need to do it yourself:\n\n   - Recreate buffers and images that were bound to allocations that were defragmented and\n     bind them with their new places in memory.\n     You must use `vkDestroyBuffer()`, `vkDestroyImage()`,\n     `vkCreateBuffer()`, `vkCreateImage()`, vmaBindBufferMemory(), vmaBindImageMemory()\n     for that purpose and NOT vmaDestroyBuffer(),\n     vmaDestroyImage(), vmaCreateBuffer(), vmaCreateImage(), because you don't need to\n     destroy or create allocation objects!\n   - Recreate views and update descriptors that point to these buffers and images.\n\n   \\section defragmentation_cpu Defragmenting CPU memory\n\n   Following example demonstrates how you can run defragmentation on CPU.\n   Only allocations created in memory types that are `HOST_VISIBLE` can be defragmented.\n   Others are ignored.\n\n   The way it works is:\n\n   - It temporarily maps entire memory blocks when necessary.\n   - It moves data using `memmove()` function.\n\n   \\code\n   // Given following variables already initialized:\n   VkDevice device;\n   VmaAllocator allocator;\n   std::vector<VkBuffer> buffers;\n   std::vector<VmaAllocation> allocations;\n\n\n   const uint32_t allocCount = (uint32_t)allocations.size();\n   std::vector<VkBool32> allocationsChanged(allocCount);\n\n   VmaDefragmentationInfo2 defragInfo = {};\n   defragInfo.allocationCount = allocCount;\n   defragInfo.pAllocations = allocations.data();\n   defragInfo.pAllocationsChanged = allocationsChanged.data();\n   defragInfo.maxCpuBytesToMove = VK_WHOLE_SIZE; // No limit.\n   defragInfo.maxCpuAllocationsToMove = UINT32_MAX; // No limit.\n\n   VmaDefragmentationContext defragCtx;\n   vmaDefragmentationBegin(allocator, &defragInfo, nullptr, &defragCtx);\n   vmaDefragmentationEnd(allocator, defragCtx);\n\n   for(uint32_t i = 0; i < allocCount; ++i)\n   {\n       if(allocationsChanged[i])\n       {\n           // Destroy buffer that is immutably bound to memory region which is no longer valid.\n           vkDestroyBuffer(device, buffers[i], nullptr);\n\n           // Create new buffer with same parameters.\n           VkBufferCreateInfo bufferInfo = ...;\n           vkCreateBuffer(device, &bufferInfo, nullptr, &buffers[i]);\n\n           // You can make dummy call to vkGetBufferMemoryRequirements here to silence validation layer warning.\n\n           // Bind new buffer to new memory region. Data contained in it is already moved.\n           VmaAllocationInfo allocInfo;\n           vmaGetAllocationInfo(allocator, allocations[i], &allocInfo);\n           vmaBindBufferMemory(allocator, allocations[i], buffers[i]);\n       }\n   }\n   \\endcode\n\n   Setting VmaDefragmentationInfo2::pAllocationsChanged is optional.\n   This output array tells whether particular allocation in VmaDefragmentationInfo2::pAllocations at the same index\n   has been modified during defragmentation.\n   You can pass null, but you then need to query every allocation passed to defragmentation\n   for new parameters using vmaGetAllocationInfo() if you might need to recreate and rebind a buffer or image associated with it.\n\n   If you use [Custom memory pools](@ref choosing_memory_type_custom_memory_pools),\n   you can fill VmaDefragmentationInfo2::poolCount and VmaDefragmentationInfo2::pPools\n   instead of VmaDefragmentationInfo2::allocationCount and VmaDefragmentationInfo2::pAllocations\n   to defragment all allocations in given pools.\n   You cannot use VmaDefragmentationInfo2::pAllocationsChanged in that case.\n   You can also combine both methods.\n\n   \\section defragmentation_gpu Defragmenting GPU memory\n\n   It is also possible to defragment allocations created in memory types that are not `HOST_VISIBLE`.\n   To do that, you need to pass a command buffer that meets requirements as described in\n   VmaDefragmentationInfo2::commandBuffer. The way it works is:\n\n   - It creates temporary buffers and binds them to entire memory blocks when necessary.\n   - It issues `vkCmdCopyBuffer()` to passed command buffer.\n\n   Example:\n\n   \\code\n   // Given following variables already initialized:\n   VkDevice device;\n   VmaAllocator allocator;\n   VkCommandBuffer commandBuffer;\n   std::vector<VkBuffer> buffers;\n   std::vector<VmaAllocation> allocations;\n\n\n   const uint32_t allocCount = (uint32_t)allocations.size();\n   std::vector<VkBool32> allocationsChanged(allocCount);\n\n   VkCommandBufferBeginInfo cmdBufBeginInfo = ...;\n   vkBeginCommandBuffer(commandBuffer, &cmdBufBeginInfo);\n\n   VmaDefragmentationInfo2 defragInfo = {};\n   defragInfo.allocationCount = allocCount;\n   defragInfo.pAllocations = allocations.data();\n   defragInfo.pAllocationsChanged = allocationsChanged.data();\n   defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE; // Notice it's \"GPU\" this time.\n   defragInfo.maxGpuAllocationsToMove = UINT32_MAX; // Notice it's \"GPU\" this time.\n   defragInfo.commandBuffer = commandBuffer;\n\n   VmaDefragmentationContext defragCtx;\n   vmaDefragmentationBegin(allocator, &defragInfo, nullptr, &defragCtx);\n\n   vkEndCommandBuffer(commandBuffer);\n\n   // Submit commandBuffer.\n   // Wait for a fence that ensures commandBuffer execution finished.\n\n   vmaDefragmentationEnd(allocator, defragCtx);\n\n   for(uint32_t i = 0; i < allocCount; ++i)\n   {\n       if(allocationsChanged[i])\n       {\n           // Destroy buffer that is immutably bound to memory region which is no longer valid.\n           vkDestroyBuffer(device, buffers[i], nullptr);\n\n           // Create new buffer with same parameters.\n           VkBufferCreateInfo bufferInfo = ...;\n           vkCreateBuffer(device, &bufferInfo, nullptr, &buffers[i]);\n\n           // You can make dummy call to vkGetBufferMemoryRequirements here to silence validation layer warning.\n\n           // Bind new buffer to new memory region. Data contained in it is already moved.\n           VmaAllocationInfo allocInfo;\n           vmaGetAllocationInfo(allocator, allocations[i], &allocInfo);\n           vmaBindBufferMemory(allocator, allocations[i], buffers[i]);\n       }\n   }\n   \\endcode\n\n   You can combine these two methods by specifying non-zero `maxGpu*` as well as `maxCpu*` parameters.\n   The library automatically chooses best method to defragment each memory pool.\n\n   You may try not to block your entire program to wait until defragmentation finishes,\n   but do it in the background, as long as you carefully fullfill requirements described\n   in function vmaDefragmentationBegin().\n\n   \\section defragmentation_additional_notes Additional notes\n\n   It is only legal to defragment allocations bound to:\n\n   - buffers\n   - images created with `VK_IMAGE_CREATE_ALIAS_BIT`, `VK_IMAGE_TILING_LINEAR`, and\n     being currently in `VK_IMAGE_LAYOUT_GENERAL` or `VK_IMAGE_LAYOUT_PREINITIALIZED`.\n\n   Defragmentation of images created with `VK_IMAGE_TILING_OPTIMAL` or in any other\n   layout may give undefined results.\n\n   If you defragment allocations bound to images, new images to be bound to new\n   memory region after defragmentation should be created with `VK_IMAGE_LAYOUT_PREINITIALIZED`\n   and then transitioned to their original layout from before defragmentation if\n   needed using an image memory barrier.\n\n   While using defragmentation, you may experience validation layer warnings, which you just need to ignore.\n   See [Validation layer warnings](@ref general_considerations_validation_layer_warnings).\n\n   Please don't expect memory to be fully compacted after defragmentation.\n   Algorithms inside are based on some heuristics that try to maximize number of Vulkan\n   memory blocks to make totally empty to release them, as well as to maximimze continuous\n   empty space inside remaining blocks, while minimizing the number and size of allocations that\n   need to be moved. Some fragmentation may still remain - this is normal.\n\n   \\section defragmentation_custom_algorithm Writing custom defragmentation algorithm\n\n   If you want to implement your own, custom defragmentation algorithm,\n   there is infrastructure prepared for that,\n   but it is not exposed through the library API - you need to hack its source code.\n   Here are steps needed to do this:\n\n   -# Main thing you need to do is to define your own class derived from base abstract\n      class `VmaDefragmentationAlgorithm` and implement your version of its pure virtual methods.\n      See definition and comments of this class for details.\n   -# Your code needs to interact with device memory block metadata.\n      If you need more access to its data than it's provided by its public interface,\n      declare your new class as a friend class e.g. in class `VmaBlockMetadata_Generic`.\n   -# If you want to create a flag that would enable your algorithm or pass some additional\n      flags to configure it, add them to `VmaDefragmentationFlagBits` and use them in\n      VmaDefragmentationInfo2::flags.\n   -# Modify function `VmaBlockVectorDefragmentationContext::Begin` to create object\n      of your new class whenever needed.\n\n\n   \\page lost_allocations Lost allocations\n\n   If your game oversubscribes video memory, if may work OK in previous-generation\n   graphics APIs (DirectX 9, 10, 11, OpenGL) because resources are automatically\n   paged to system RAM. In Vulkan you can't do it because when you run out of\n   memory, an allocation just fails. If you have more data (e.g. textures) that can\n   fit into VRAM and you don't need it all at once, you may want to upload them to\n   GPU on demand and \"push out\" ones that are not used for a long time to make room\n   for the new ones, effectively using VRAM (or a cartain memory pool) as a form of\n   cache. Vulkan Memory Allocator can help you with that by supporting a concept of\n   \"lost allocations\".\n\n   To create an allocation that can become lost, include #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT\n   flag in VmaAllocationCreateInfo::flags. Before using a buffer or image bound to\n   such allocation in every new frame, you need to query it if it's not lost.\n   To check it, call vmaTouchAllocation().\n   If the allocation is lost, you should not use it or buffer/image bound to it.\n   You mustn't forget to destroy this allocation and this buffer/image.\n   vmaGetAllocationInfo() can also be used for checking status of the allocation.\n   Allocation is lost when returned VmaAllocationInfo::deviceMemory == `VK_NULL_HANDLE`.\n\n   To create an allocation that can make some other allocations lost to make room\n   for it, use #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flag. You will\n   usually use both flags #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT and\n   #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT at the same time.\n\n   Warning! Current implementation uses quite naive, brute force algorithm,\n   which can make allocation calls that use #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT\n   flag quite slow. A new, more optimal algorithm and data structure to speed this\n   up is planned for the future.\n\n   <b>Q: When interleaving creation of new allocations with usage of existing ones,\n   how do you make sure that an allocation won't become lost while it's used in the\n   current frame?</b>\n\n   It is ensured because vmaTouchAllocation() / vmaGetAllocationInfo() not only returns allocation\n   status/parameters and checks whether it's not lost, but when it's not, it also\n   atomically marks it as used in the current frame, which makes it impossible to\n   become lost in that frame. It uses lockless algorithm, so it works fast and\n   doesn't involve locking any internal mutex.\n\n   <b>Q: What if my allocation may still be in use by the GPU when it's rendering a\n   previous frame while I already submit new frame on the CPU?</b>\n\n   You can make sure that allocations \"touched\" by vmaTouchAllocation() / vmaGetAllocationInfo() will not\n   become lost for a number of additional frames back from the current one by\n   specifying this number as VmaAllocatorCreateInfo::frameInUseCount (for default\n   memory pool) and VmaPoolCreateInfo::frameInUseCount (for custom pool).\n\n   <b>Q: How do you inform the library when new frame starts?</b>\n\n   You need to call function vmaSetCurrentFrameIndex().\n\n   Example code:\n\n   \\code\n   struct MyBuffer\n   {\n       VkBuffer m_Buf = nullptr;\n       VmaAllocation m_Alloc = nullptr;\n\n       // Called when the buffer is really needed in the current frame.\n       void EnsureBuffer();\n   };\n\n   void MyBuffer::EnsureBuffer()\n   {\n       // Buffer has been created.\n       if(m_Buf != VK_NULL_HANDLE)\n       {\n           // Check if its allocation is not lost + mark it as used in current frame.\n           if(vmaTouchAllocation(allocator, m_Alloc))\n           {\n               // It's all OK - safe to use m_Buf.\n               return;\n           }\n       }\n\n       // Buffer not yet exists or lost - destroy and recreate it.\n\n       vmaDestroyBuffer(allocator, m_Buf, m_Alloc);\n\n       VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\n       bufCreateInfo.size = 1024;\n       bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n\n       VmaAllocationCreateInfo allocCreateInfo = {};\n       allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n       allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT |\n           VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;\n\n       vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &m_Buf, &m_Alloc, nullptr);\n   }\n   \\endcode\n\n   When using lost allocations, you may see some Vulkan validation layer warnings\n   about overlapping regions of memory bound to different kinds of buffers and\n   images. This is still valid as long as you implement proper handling of lost\n   allocations (like in the example above) and don't use them.\n\n   You can create an allocation that is already in lost state from the beginning using function\n   vmaCreateLostAllocation(). It may be useful if you need a \"dummy\" allocation that is not null.\n\n   You can call function vmaMakePoolAllocationsLost() to set all eligible allocations\n   in a specified custom pool to lost state.\n   Allocations that have been \"touched\" in current frame or VmaPoolCreateInfo::frameInUseCount frames back\n   cannot become lost.\n\n   <b>Q: Can I touch allocation that cannot become lost?</b>\n\n   Yes, although it has no visible effect.\n   Calls to vmaGetAllocationInfo() and vmaTouchAllocation() update last use frame index\n   also for allocations that cannot become lost, but the only way to observe it is to dump\n   internal allocator state using vmaBuildStatsString().\n   You can use this feature for debugging purposes to explicitly mark allocations that you use\n   in current frame and then analyze JSON dump to see for how long each allocation stays unused.\n\n\n   \\page statistics Statistics\n\n   This library contains functions that return information about its internal state,\n   especially the amount of memory allocated from Vulkan.\n   Please keep in mind that these functions need to traverse all internal data structures\n   to gather these information, so they may be quite time-consuming.\n   Don't call them too often.\n\n   \\section statistics_numeric_statistics Numeric statistics\n\n   You can query for overall statistics of the allocator using function vmaCalculateStats().\n   Information are returned using structure #VmaStats.\n   It contains #VmaStatInfo - number of allocated blocks, number of allocations\n   (occupied ranges in these blocks), number of unused (free) ranges in these blocks,\n   number of bytes used and unused (but still allocated from Vulkan) and other information.\n   They are summed across memory heaps, memory types and total for whole allocator.\n\n   You can query for statistics of a custom pool using function vmaGetPoolStats().\n   Information are returned using structure #VmaPoolStats.\n\n   You can query for information about specific allocation using function vmaGetAllocationInfo().\n   It fill structure #VmaAllocationInfo.\n\n   \\section statistics_json_dump JSON dump\n\n   You can dump internal state of the allocator to a string in JSON format using function vmaBuildStatsString().\n   The result is guaranteed to be correct JSON.\n   It uses ANSI encoding.\n   Any strings provided by user (see [Allocation names](@ref allocation_names))\n   are copied as-is and properly escaped for JSON, so if they use UTF-8, ISO-8859-2 or any other encoding,\n   this JSON string can be treated as using this encoding.\n   It must be freed using function vmaFreeStatsString().\n\n   The format of this JSON string is not part of official documentation of the library,\n   but it will not change in backward-incompatible way without increasing library major version number\n   and appropriate mention in changelog.\n\n   The JSON string contains all the data that can be obtained using vmaCalculateStats().\n   It can also contain detailed map of allocated memory blocks and their regions -\n   free and occupied by allocations.\n   This allows e.g. to visualize the memory or assess fragmentation.\n\n\n   \\page allocation_annotation Allocation names and user data\n\n   \\section allocation_user_data Allocation user data\n\n   You can annotate allocations with your own information, e.g. for debugging purposes.\n   To do that, fill VmaAllocationCreateInfo::pUserData field when creating\n   an allocation. It's an opaque `void*` pointer. You can use it e.g. as a pointer,\n   some handle, index, key, ordinal number or any other value that would associate\n   the allocation with your custom metadata.\n\n   \\code\n   VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };\n   // Fill bufferInfo...\n\n   MyBufferMetadata* pMetadata = CreateBufferMetadata();\n\n   VmaAllocationCreateInfo allocCreateInfo = {};\n   allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n   allocCreateInfo.pUserData = pMetadata;\n\n   VkBuffer buffer;\n   VmaAllocation allocation;\n   vmaCreateBuffer(allocator, &bufferInfo, &allocCreateInfo, &buffer, &allocation, nullptr);\n   \\endcode\n\n   The pointer may be later retrieved as VmaAllocationInfo::pUserData:\n\n   \\code\n   VmaAllocationInfo allocInfo;\n   vmaGetAllocationInfo(allocator, allocation, &allocInfo);\n   MyBufferMetadata* pMetadata = (MyBufferMetadata*)allocInfo.pUserData;\n   \\endcode\n\n   It can also be changed using function vmaSetAllocationUserData().\n\n   Values of (non-zero) allocations' `pUserData` are printed in JSON report created by\n   vmaBuildStatsString(), in hexadecimal form.\n\n   \\section allocation_names Allocation names\n\n   There is alternative mode available where `pUserData` pointer is used to point to\n   a null-terminated string, giving a name to the allocation. To use this mode,\n   set #VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT flag in VmaAllocationCreateInfo::flags.\n   Then `pUserData` passed as VmaAllocationCreateInfo::pUserData or argument to\n   vmaSetAllocationUserData() must be either null or pointer to a null-terminated string.\n   The library creates internal copy of the string, so the pointer you pass doesn't need\n   to be valid for whole lifetime of the allocation. You can free it after the call.\n\n   \\code\n   VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };\n   // Fill imageInfo...\n\n   std::string imageName = \"Texture: \";\n   imageName += fileName;\n\n   VmaAllocationCreateInfo allocCreateInfo = {};\n   allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n   allocCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;\n   allocCreateInfo.pUserData = imageName.c_str();\n\n   VkImage image;\n   VmaAllocation allocation;\n   vmaCreateImage(allocator, &imageInfo, &allocCreateInfo, &image, &allocation, nullptr);\n   \\endcode\n\n   The value of `pUserData` pointer of the allocation will be different than the one\n   you passed when setting allocation's name - pointing to a buffer managed\n   internally that holds copy of the string.\n\n   \\code\n   VmaAllocationInfo allocInfo;\n   vmaGetAllocationInfo(allocator, allocation, &allocInfo);\n   const char* imageName = (const char*)allocInfo.pUserData;\n   printf(\"Image name: %s\\n\", imageName);\n   \\endcode\n\n   That string is also printed in JSON report created by vmaBuildStatsString().\n\n\n   \\page debugging_memory_usage Debugging incorrect memory usage\n\n   If you suspect a bug with memory usage, like usage of uninitialized memory or\n   memory being overwritten out of bounds of an allocation,\n   you can use debug features of this library to verify this.\n\n   \\section debugging_memory_usage_initialization Memory initialization\n\n   If you experience a bug with incorrect and nondeterministic data in your program and you suspect uninitialized memory to be used,\n   you can enable automatic memory initialization to verify this.\n   To do it, define macro `VMA_DEBUG_INITIALIZE_ALLOCATIONS` to 1.\n\n   \\code\n   #define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1\n   #include \"vk_mem_alloc.h\"\n   \\endcode\n\n   It makes memory of all new allocations initialized to bit pattern `0xDCDCDCDC`.\n   Before an allocation is destroyed, its memory is filled with bit pattern `0xEFEFEFEF`.\n   Memory is automatically mapped and unmapped if necessary.\n\n   If you find these values while debugging your program, good chances are that you incorrectly\n   read Vulkan memory that is allocated but not initialized, or already freed, respectively.\n\n   Memory initialization works only with memory types that are `HOST_VISIBLE`.\n   It works also with dedicated allocations.\n   It doesn't work with allocations created with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag,\n   as they cannot be mapped.\n\n   \\section debugging_memory_usage_margins Margins\n\n   By default, allocations are laid out in memory blocks next to each other if possible\n   (considering required alignment, `bufferImageGranularity`, and `nonCoherentAtomSize`).\n\n   ![Allocations without margin](../gfx/Margins_1.png)\n\n   Define macro `VMA_DEBUG_MARGIN` to some non-zero value (e.g. 16) to enforce specified\n   number of bytes as a margin before and after every allocation.\n\n   \\code\n   #define VMA_DEBUG_MARGIN 16\n   #include \"vk_mem_alloc.h\"\n   \\endcode\n\n   ![Allocations with margin](../gfx/Margins_2.png)\n\n   If your bug goes away after enabling margins, it means it may be caused by memory\n   being overwritten outside of allocation boundaries. It is not 100% certain though.\n   Change in application behavior may also be caused by different order and distribution\n   of allocations across memory blocks after margins are applied.\n\n   The margin is applied also before first and after last allocation in a block.\n   It may occur only once between two adjacent allocations.\n\n   Margins work with all types of memory.\n\n   Margin is applied only to allocations made out of memory blocks and not to dedicated\n   allocations, which have their own memory block of specific size.\n   It is thus not applied to allocations made using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT flag\n   or those automatically decided to put into dedicated allocations, e.g. due to its\n   large size or recommended by VK_KHR_dedicated_allocation extension.\n   Margins are also not active in custom pools created with #VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT flag.\n\n   Margins appear in [JSON dump](@ref statistics_json_dump) as part of free space.\n\n   Note that enabling margins increases memory usage and fragmentation.\n\n   \\section debugging_memory_usage_corruption_detection Corruption detection\n\n   You can additionally define macro `VMA_DEBUG_DETECT_CORRUPTION` to 1 to enable validation\n   of contents of the margins.\n\n   \\code\n   #define VMA_DEBUG_MARGIN 16\n   #define VMA_DEBUG_DETECT_CORRUPTION 1\n   #include \"vk_mem_alloc.h\"\n   \\endcode\n\n   When this feature is enabled, number of bytes specified as `VMA_DEBUG_MARGIN`\n   (it must be multiply of 4) before and after every allocation is filled with a magic number.\n   This idea is also know as \"canary\".\n   Memory is automatically mapped and unmapped if necessary.\n\n   This number is validated automatically when the allocation is destroyed.\n   If it's not equal to the expected value, `VMA_ASSERT()` is executed.\n   It clearly means that either CPU or GPU overwritten the memory outside of boundaries of the allocation,\n   which indicates a serious bug.\n\n   You can also explicitly request checking margins of all allocations in all memory blocks\n   that belong to specified memory types by using function vmaCheckCorruption(),\n   or in memory blocks that belong to specified custom pool, by using function\n   vmaCheckPoolCorruption().\n\n   Margin validation (corruption detection) works only for memory types that are\n   `HOST_VISIBLE` and `HOST_COHERENT`.\n\n\n   \\page record_and_replay Record and replay\n\n   \\section record_and_replay_introduction Introduction\n\n   While using the library, sequence of calls to its functions together with their\n   parameters can be recorded to a file and later replayed using standalone player\n   application. It can be useful to:\n\n   - Test correctness - check if same sequence of calls will not cause crash or\n     failures on a target platform.\n   - Gather statistics - see number of allocations, peak memory usage, number of\n     calls etc.\n   - Benchmark performance - see how much time it takes to replay the whole\n     sequence.\n\n   \\section record_and_replay_usage Usage\n\n   Recording functionality is disabled by default.\n   To enable it, define following macro before every include of this library:\n\n   \\code\n   #define VMA_RECORDING_ENABLED 1\n   \\endcode\n\n   <b>To record sequence of calls to a file:</b> Fill in\n   VmaAllocatorCreateInfo::pRecordSettings member while creating #VmaAllocator\n   object. File is opened and written during whole lifetime of the allocator.\n\n   <b>To replay file:</b> Use VmaReplay - standalone command-line program.\n   Precompiled binary can be found in \"bin\" directory.\n   Its source can be found in \"src/VmaReplay\" directory.\n   Its project is generated by Premake.\n   Command line syntax is printed when the program is launched without parameters.\n   Basic usage:\n\n       VmaReplay.exe MyRecording.csv\n\n   <b>Documentation of file format</b> can be found in file: \"docs/Recording file format.md\".\n   It's a human-readable, text file in CSV format (Comma Separated Values).\n\n   \\section record_and_replay_additional_considerations Additional considerations\n\n   - Replaying file that was recorded on a different GPU (with different parameters\n     like `bufferImageGranularity`, `nonCoherentAtomSize`, and especially different\n     set of memory heaps and types) may give different performance and memory usage\n     results, as well as issue some warnings and errors.\n   - Current implementation of recording in VMA, as well as VmaReplay application, is\n     coded and tested only on Windows. Inclusion of recording code is driven by\n     `VMA_RECORDING_ENABLED` macro. Support for other platforms should be easy to\n     add. Contributions are welcomed.\n\n\n   \\page usage_patterns Recommended usage patterns\n\n   See also slides from talk:\n   [Sawicki, Adam. Advanced Graphics Techniques Tutorial: Memory management in Vulkan and DX12. Game Developers Conference, 2018](https://www.gdcvault.com/play/1025458/Advanced-Graphics-Techniques-Tutorial-New)\n\n\n   \\section usage_patterns_common_mistakes Common mistakes\n\n   <b>Use of CPU_TO_GPU instead of CPU_ONLY memory</b>\n\n   #VMA_MEMORY_USAGE_CPU_TO_GPU is recommended only for resources that will be\n   mapped and written by the CPU, as well as read directly by the GPU - like some\n   buffers or textures updated every frame (dynamic). If you create a staging copy\n   of a resource to be written by CPU and then used as a source of transfer to\n   another resource placed in the GPU memory, that staging resource should be\n   created with #VMA_MEMORY_USAGE_CPU_ONLY. Please read the descriptions of these\n   enums carefully for details.\n\n   <b>Unnecessary use of custom pools</b>\n\n   \\ref custom_memory_pools may be useful for special purposes - when you want to\n   keep certain type of resources separate e.g. to reserve minimum amount of memory\n   for them, limit maximum amount of memory they can occupy, or make some of them\n   push out the other through the mechanism of \\ref lost_allocations. For most\n   resources this is not needed and so it is not recommended to create #VmaPool\n   objects and allocations out of them. Allocating from the default pool is sufficient.\n\n   \\section usage_patterns_simple Simple patterns\n\n   \\subsection usage_patterns_simple_render_targets Render targets\n\n   <b>When:</b>\n   Any resources that you frequently write and read on GPU,\n   e.g. images used as color attachments (aka \"render targets\"), depth-stencil attachments,\n   images/buffers used as storage image/buffer (aka \"Unordered Access View (UAV)\").\n\n   <b>What to do:</b>\n   Create them in video memory that is fastest to access from GPU using\n   #VMA_MEMORY_USAGE_GPU_ONLY.\n\n   Consider using [VK_KHR_dedicated_allocation](@ref vk_khr_dedicated_allocation) extension\n   and/or manually creating them as dedicated allocations using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT,\n   especially if they are large or if you plan to destroy and recreate them e.g. when\n   display resolution changes.\n   Prefer to create such resources first and all other GPU resources (like textures and vertex buffers) later.\n\n   \\subsection usage_patterns_simple_immutable_resources Immutable resources\n\n   <b>When:</b>\n   Any resources that you fill on CPU only once (aka \"immutable\") or infrequently\n   and then read frequently on GPU,\n   e.g. textures, vertex and index buffers, constant buffers that don't change often.\n\n   <b>What to do:</b>\n   Create them in video memory that is fastest to access from GPU using\n   #VMA_MEMORY_USAGE_GPU_ONLY.\n\n   To initialize content of such resource, create a CPU-side (aka \"staging\") copy of it\n   in system memory - #VMA_MEMORY_USAGE_CPU_ONLY, map it, fill it,\n   and submit a transfer from it to the GPU resource.\n   You can keep the staging copy if you need it for another upload transfer in the future.\n   If you don't, you can destroy it or reuse this buffer for uploading different resource\n   after the transfer finishes.\n\n   Prefer to create just buffers in system memory rather than images, even for uploading textures.\n   Use `vkCmdCopyBufferToImage()`.\n   Dont use images with `VK_IMAGE_TILING_LINEAR`.\n\n   \\subsection usage_patterns_dynamic_resources Dynamic resources\n\n   <b>When:</b>\n   Any resources that change frequently (aka \"dynamic\"), e.g. every frame or every draw call,\n   written on CPU, read on GPU.\n\n   <b>What to do:</b>\n   Create them using #VMA_MEMORY_USAGE_CPU_TO_GPU.\n   You can map it and write to it directly on CPU, as well as read from it on GPU.\n\n   This is a more complex situation. Different solutions are possible,\n   and the best one depends on specific GPU type, but you can use this simple approach for the start.\n   Prefer to write to such resource sequentially (e.g. using `memcpy`).\n   Don't perform random access or any reads from it on CPU, as it may be very slow.\n\n   \\subsection usage_patterns_readback Readback\n\n   <b>When:</b>\n   Resources that contain data written by GPU that you want to read back on CPU,\n   e.g. results of some computations.\n\n   <b>What to do:</b>\n   Create them using #VMA_MEMORY_USAGE_GPU_TO_CPU.\n   You can write to them directly on GPU, as well as map and read them on CPU.\n\n   \\section usage_patterns_advanced Advanced patterns\n\n   \\subsection usage_patterns_integrated_graphics Detecting integrated graphics\n\n   You can support integrated graphics (like Intel HD Graphics, AMD APU) better\n   by detecting it in Vulkan.\n   To do it, call `vkGetPhysicalDeviceProperties()`, inspect\n   `VkPhysicalDeviceProperties::deviceType` and look for `VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU`.\n   When you find it, you can assume that memory is unified and all memory types are comparably fast\n   to access from GPU, regardless of `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`.\n\n   You can then sum up sizes of all available memory heaps and treat them as useful for\n   your GPU resources, instead of only `DEVICE_LOCAL` ones.\n   You can also prefer to create your resources in memory types that are `HOST_VISIBLE` to map them\n   directly instead of submitting explicit transfer (see below).\n\n   \\subsection usage_patterns_direct_vs_transfer Direct access versus transfer\n\n   For resources that you frequently write on CPU and read on GPU, many solutions are possible:\n\n   -# Create one copy in video memory using #VMA_MEMORY_USAGE_GPU_ONLY,\n      second copy in system memory using #VMA_MEMORY_USAGE_CPU_ONLY and submit explicit tranfer each time.\n   -# Create just single copy using #VMA_MEMORY_USAGE_CPU_TO_GPU, map it and fill it on CPU,\n      read it directly on GPU.\n   -# Create just single copy using #VMA_MEMORY_USAGE_CPU_ONLY, map it and fill it on CPU,\n      read it directly on GPU.\n\n   Which solution is the most efficient depends on your resource and especially on the GPU.\n   It is best to measure it and then make the decision.\n   Some general recommendations:\n\n   - On integrated graphics use (2) or (3) to avoid unnecesary time and memory overhead\n     related to using a second copy and making transfer.\n   - For small resources (e.g. constant buffers) use (2).\n     Discrete AMD cards have special 256 MiB pool of video memory that is directly mappable.\n     Even if the resource ends up in system memory, its data may be cached on GPU after first\n     fetch over PCIe bus.\n   - For larger resources (e.g. textures), decide between (1) and (2).\n     You may want to differentiate NVIDIA and AMD, e.g. by looking for memory type that is\n     both `DEVICE_LOCAL` and `HOST_VISIBLE`. When you find it, use (2), otherwise use (1).\n\n   Similarly, for resources that you frequently write on GPU and read on CPU, multiple\n   solutions are possible:\n\n   -# Create one copy in video memory using #VMA_MEMORY_USAGE_GPU_ONLY,\n      second copy in system memory using #VMA_MEMORY_USAGE_GPU_TO_CPU and submit explicit tranfer each time.\n   -# Create just single copy using #VMA_MEMORY_USAGE_GPU_TO_CPU, write to it directly on GPU,\n      map it and read it on CPU.\n\n   You should take some measurements to decide which option is faster in case of your specific\n   resource.\n\n   If you don't want to specialize your code for specific types of GPUs, you can still make\n   an simple optimization for cases when your resource ends up in mappable memory to use it\n   directly in this case instead of creating CPU-side staging copy.\n   For details see [Finding out if memory is mappable](@ref memory_mapping_finding_if_memory_mappable).\n\n\n   \\page configuration Configuration\n\n   Please check \"CONFIGURATION SECTION\" in the code to find macros that you can define\n   before each include of this file or change directly in this file to provide\n   your own implementation of basic facilities like assert, `min()` and `max()` functions,\n   mutex, atomic etc.\n   The library uses its own implementation of containers by default, but you can switch to using\n   STL containers instead.\n\n   For example, define `VMA_ASSERT(expr)` before including the library to provide\n   custom implementation of the assertion, compatible with your project.\n   By default it is defined to standard C `assert(expr)` in `_DEBUG` configuration\n   and empty otherwise.\n\n   \\section config_Vulkan_functions Pointers to Vulkan functions\n\n   The library uses Vulkan functions straight from the `vulkan.h` header by default.\n   If you want to provide your own pointers to these functions, e.g. fetched using\n   `vkGetInstanceProcAddr()` and `vkGetDeviceProcAddr()`:\n\n   -# Define `VMA_STATIC_VULKAN_FUNCTIONS 0`.\n   -# Provide valid pointers through VmaAllocatorCreateInfo::pVulkanFunctions.\n\n   \\section custom_memory_allocator Custom host memory allocator\n\n   If you use custom allocator for CPU memory rather than default operator `new`\n   and `delete` from C++, you can make this library using your allocator as well\n   by filling optional member VmaAllocatorCreateInfo::pAllocationCallbacks. These\n   functions will be passed to Vulkan, as well as used by the library itself to\n   make any CPU-side allocations.\n\n   \\section allocation_callbacks Device memory allocation callbacks\n\n   The library makes calls to `vkAllocateMemory()` and `vkFreeMemory()` internally.\n   You can setup callbacks to be informed about these calls, e.g. for the purpose\n   of gathering some statistics. To do it, fill optional member\n   VmaAllocatorCreateInfo::pDeviceMemoryCallbacks.\n\n   \\section heap_memory_limit Device heap memory limit\n\n   When device memory of certain heap runs out of free space, new allocations may\n   fail (returning error code) or they may succeed, silently pushing some existing\n   memory blocks from GPU VRAM to system RAM (which degrades performance). This\n   behavior is implementation-dependant - it depends on GPU vendor and graphics\n   driver.\n\n   On AMD cards it can be controlled while creating Vulkan device object by using\n   VK_AMD_memory_overallocation_behavior extension, if available.\n\n   Alternatively, if you want to test how your program behaves with limited amount of Vulkan device\n   memory available without switching your graphics card to one that really has\n   smaller VRAM, you can use a feature of this library intended for this purpose.\n   To do it, fill optional member VmaAllocatorCreateInfo::pHeapSizeLimit.\n\n\n\n   \\page vk_khr_dedicated_allocation VK_KHR_dedicated_allocation\n\n   VK_KHR_dedicated_allocation is a Vulkan extension which can be used to improve\n   performance on some GPUs. It augments Vulkan API with possibility to query\n   driver whether it prefers particular buffer or image to have its own, dedicated\n   allocation (separate `VkDeviceMemory` block) for better efficiency - to be able\n   to do some internal optimizations.\n\n   The extension is supported by this library. It will be used automatically when\n   enabled. To enable it:\n\n   1 . When creating Vulkan device, check if following 2 device extensions are\n   supported (call `vkEnumerateDeviceExtensionProperties()`).\n   If yes, enable them (fill `VkDeviceCreateInfo::ppEnabledExtensionNames`).\n\n   - VK_KHR_get_memory_requirements2\n   - VK_KHR_dedicated_allocation\n\n   If you enabled these extensions:\n\n   2 . Use #VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag when creating\n   your #VmaAllocator`to inform the library that you enabled required extensions\n   and you want the library to use them.\n\n   \\code\n   allocatorInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT;\n\n   vmaCreateAllocator(&allocatorInfo, &allocator);\n   \\endcode\n\n   That's all. The extension will be automatically used whenever you create a\n   buffer using vmaCreateBuffer() or image using vmaCreateImage().\n\n   When using the extension together with Vulkan Validation Layer, you will receive\n   warnings like this:\n\n       vkBindBufferMemory(): Binding memory to buffer 0x33 but vkGetBufferMemoryRequirements() has not been called on that buffer.\n\n   It is OK, you should just ignore it. It happens because you use function\n   `vkGetBufferMemoryRequirements2KHR()` instead of standard\n   `vkGetBufferMemoryRequirements()`, while the validation layer seems to be\n   unaware of it.\n\n   To learn more about this extension, see:\n\n   - [VK_KHR_dedicated_allocation in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html#VK_KHR_dedicated_allocation)\n   - [VK_KHR_dedicated_allocation unofficial manual](http://asawicki.info/articles/VK_KHR_dedicated_allocation.php5)\n\n\n\n   \\page general_considerations General considerations\n\n   \\section general_considerations_thread_safety Thread safety\n\n   - The library has no global state, so separate #VmaAllocator objects can be used\n     independently.\n     There should be no need to create multiple such objects though - one per `VkDevice` is enough.\n   - By default, all calls to functions that take #VmaAllocator as first parameter\n     are safe to call from multiple threads simultaneously because they are\n     synchronized internally when needed.\n   - When the allocator is created with #VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT\n     flag, calls to functions that take such #VmaAllocator object must be\n     synchronized externally.\n   - Access to a #VmaAllocation object must be externally synchronized. For example,\n     you must not call vmaGetAllocationInfo() and vmaMapMemory() from different\n     threads at the same time if you pass the same #VmaAllocation object to these\n     functions.\n\n   \\section general_considerations_validation_layer_warnings Validation layer warnings\n\n   When using this library, you can meet following types of warnings issued by\n   Vulkan validation layer. They don't necessarily indicate a bug, so you may need\n   to just ignore them.\n\n   - *vkBindBufferMemory(): Binding memory to buffer 0xeb8e4 but vkGetBufferMemoryRequirements() has not been called on that buffer.*\n     - It happens when VK_KHR_dedicated_allocation extension is enabled.\n       `vkGetBufferMemoryRequirements2KHR` function is used instead, while validation layer seems to be unaware of it.\n   - *Mapping an image with layout VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL can result in undefined behavior if this memory is used by the device. Only GENERAL or PREINITIALIZED should be used.*\n     - It happens when you map a buffer or image, because the library maps entire\n       `VkDeviceMemory` block, where different types of images and buffers may end\n       up together, especially on GPUs with unified memory like Intel.\n   - *Non-linear image 0xebc91 is aliased with linear buffer 0xeb8e4 which may indicate a bug.*\n     - It happens when you use lost allocations, and a new image or buffer is\n       created in place of an existing object that bacame lost.\n     - It may happen also when you use [defragmentation](@ref defragmentation).\n\n   \\section general_considerations_allocation_algorithm Allocation algorithm\n\n   The library uses following algorithm for allocation, in order:\n\n   -# Try to find free range of memory in existing blocks.\n   -# If failed, try to create a new block of `VkDeviceMemory`, with preferred block size.\n   -# If failed, try to create such block with size/2, size/4, size/8.\n   -# If failed and #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flag was\n      specified, try to find space in existing blocks, possilby making some other\n      allocations lost.\n   -# If failed, try to allocate separate `VkDeviceMemory` for this allocation,\n      just like when you use #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.\n   -# If failed, choose other memory type that meets the requirements specified in\n      VmaAllocationCreateInfo and go to point 1.\n   -# If failed, return `VK_ERROR_OUT_OF_DEVICE_MEMORY`.\n\n   \\section general_considerations_features_not_supported Features not supported\n\n   Features deliberately excluded from the scope of this library:\n\n   - Data transfer. Uploading (straming) and downloading data of buffers and images\n     between CPU and GPU memory and related synchronization is responsibility of the user.\n     Defining some \"texture\" object that would automatically stream its data from a\n     staging copy in CPU memory to GPU memory would rather be a feature of another,\n     higher-level library implemented on top of VMA.\n   - Allocations for imported/exported external memory. They tend to require\n     explicit memory type index and dedicated allocation anyway, so they don't\n     interact with main features of this library. Such special purpose allocations\n     should be made manually, using `vkCreateBuffer()` and `vkAllocateMemory()`.\n   - Recreation of buffers and images. Although the library has functions for\n     buffer and image creation (vmaCreateBuffer(), vmaCreateImage()), you need to\n     recreate these objects yourself after defragmentation. That's because the big\n     structures `VkBufferCreateInfo`, `VkImageCreateInfo` are not stored in\n     #VmaAllocation object.\n   - Handling CPU memory allocation failures. When dynamically creating small C++\n     objects in CPU memory (not Vulkan memory), allocation failures are not checked\n     and handled gracefully, because that would complicate code significantly and\n     is usually not needed in desktop PC applications anyway.\n   - Code free of any compiler warnings. Maintaining the library to compile and\n     work correctly on so many different platforms is hard enough. Being free of\n     any warnings, on any version of any compiler, is simply not feasible.\n   - This is a C++ library with C interface.\n     Bindings or ports to any other programming languages are welcomed as external projects and\n     are not going to be included into this repository.\n\n   */\n\n   /*\n   Define this macro to 0/1 to disable/enable support for recording functionality,\n   available through VmaAllocatorCreateInfo::pRecordSettings.\n   */\n#ifndef VMA_RECORDING_ENABLED\n#define VMA_RECORDING_ENABLED 0\n#endif\n\n#ifndef NOMINMAX\n#define NOMINMAX // For windows.h\n#endif\n\n#ifndef VULKAN_H_\n#include <vulkan/vulkan.h>\n#endif\n\n#if VMA_RECORDING_ENABLED\n#include <windows.h>\n#endif\n\n   // Define this macro to declare maximum supported Vulkan version in format AAABBBCCC,\n   // where AAA = major, BBB = minor, CCC = patch.\n   // If you want to use version > 1.0, it still needs to be enabled via VmaAllocatorCreateInfo::vulkanApiVersion.\n#if !defined(VMA_VULKAN_VERSION)\n#if defined(VK_VERSION_1_1)\n#define VMA_VULKAN_VERSION 1001000\n#else\n#define VMA_VULKAN_VERSION 1000000\n#endif\n#endif\n\n#if !defined(VMA_DEDICATED_ALLOCATION)\n#if VK_KHR_get_memory_requirements2 && VK_KHR_dedicated_allocation\n#define VMA_DEDICATED_ALLOCATION 1\n#else\n#define VMA_DEDICATED_ALLOCATION 0\n#endif\n#endif\n\n#if !defined(VMA_BIND_MEMORY2)\n#if VK_KHR_bind_memory2\n#define VMA_BIND_MEMORY2 1\n#else\n#define VMA_BIND_MEMORY2 0\n#endif\n#endif\n\n#if !defined(VMA_MEMORY_BUDGET)\n#if VK_EXT_memory_budget && (VK_KHR_get_physical_device_properties2 || VMA_VULKAN_VERSION >= 1001000)\n#define VMA_MEMORY_BUDGET 1\n#else\n#define VMA_MEMORY_BUDGET 0\n#endif\n#endif\n\n// Define these macros to decorate all public functions with additional code,\n// before and after returned type, appropriately. This may be useful for\n// exporing the functions when compiling VMA as a separate library. Example:\n// #define VMA_CALL_PRE  __declspec(dllexport)\n// #define VMA_CALL_POST __cdecl\n#ifndef VMA_CALL_PRE\n#define VMA_CALL_PRE\n#endif\n#ifndef VMA_CALL_POST\n#define VMA_CALL_POST\n#endif\n\n/** \\struct VmaAllocator\n\\brief Represents main object of this library initialized.\n\nFill structure #VmaAllocatorCreateInfo and call function vmaCreateAllocator() to create it.\nCall function vmaDestroyAllocator() to destroy it.\n\nIt is recommended to create just one object of this type per `VkDevice` object,\nright after Vulkan is initialized and keep it alive until before Vulkan device is destroyed.\n*/\n   VK_DEFINE_HANDLE(VmaAllocator)\n\n      /// Callback function called after successful vkAllocateMemory.\n      typedef void (VKAPI_PTR* PFN_vmaAllocateDeviceMemoryFunction)(\n         VmaAllocator      allocator,\n         uint32_t          memoryType,\n         VkDeviceMemory    memory,\n         VkDeviceSize      size);\n   /// Callback function called before vkFreeMemory.\n   typedef void (VKAPI_PTR* PFN_vmaFreeDeviceMemoryFunction)(\n      VmaAllocator      allocator,\n      uint32_t          memoryType,\n      VkDeviceMemory    memory,\n      VkDeviceSize      size);\n\n   /** \\brief Set of callbacks that the library will call for `vkAllocateMemory` and `vkFreeMemory`.\n\n   Provided for informative purpose, e.g. to gather statistics about number of\n   allocations or total amount of memory allocated in Vulkan.\n\n   Used in VmaAllocatorCreateInfo::pDeviceMemoryCallbacks.\n   */\n   typedef struct VmaDeviceMemoryCallbacks\n   {\n      /// Optional, can be null.\n      PFN_vmaAllocateDeviceMemoryFunction pfnAllocate;\n      /// Optional, can be null.\n      PFN_vmaFreeDeviceMemoryFunction pfnFree;\n   } VmaDeviceMemoryCallbacks;\n\n   /// Flags for created #VmaAllocator.\n   typedef enum VmaAllocatorCreateFlagBits\n   {\n      /** \\brief Allocator and all objects created from it will not be synchronized internally, so you must guarantee they are used from only one thread at a time or synchronized externally by you.\n\n      Using this flag may increase performance because internal mutexes are not used.\n      */\n      VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT = 0x00000001,\n      /** \\brief Enables usage of VK_KHR_dedicated_allocation extension.\n\n      The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_0`.\n      When it's `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1.\n\n      Using this extenion will automatically allocate dedicated blocks of memory for\n      some buffers and images instead of suballocating place for them out of bigger\n      memory blocks (as if you explicitly used #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT\n      flag) when it is recommended by the driver. It may improve performance on some\n      GPUs.\n\n      You may set this flag only if you found out that following device extensions are\n      supported, you enabled them while creating Vulkan device passed as\n      VmaAllocatorCreateInfo::device, and you want them to be used internally by this\n      library:\n\n      - VK_KHR_get_memory_requirements2 (device extension)\n      - VK_KHR_dedicated_allocation (device extension)\n\n      When this flag is set, you can experience following warnings reported by Vulkan\n      validation layer. You can ignore them.\n\n      > vkBindBufferMemory(): Binding memory to buffer 0x2d but vkGetBufferMemoryRequirements() has not been called on that buffer.\n      */\n      VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT = 0x00000002,\n      /**\n      Enables usage of VK_KHR_bind_memory2 extension.\n\n      The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_0`.\n      When it's `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1.\n\n      You may set this flag only if you found out that this device extension is supported,\n      you enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device,\n      and you want it to be used internally by this library.\n\n      The extension provides functions `vkBindBufferMemory2KHR` and `vkBindImageMemory2KHR`,\n      which allow to pass a chain of `pNext` structures while binding.\n      This flag is required if you use `pNext` parameter in vmaBindBufferMemory2() or vmaBindImageMemory2().\n      */\n      VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT = 0x00000004,\n      /**\n      Enables usage of VK_EXT_memory_budget extension.\n\n      You may set this flag only if you found out that this device extension is supported,\n      you enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device,\n      and you want it to be used internally by this library, along with another instance extension\n      VK_KHR_get_physical_device_properties2, which is required by it (or Vulkan 1.1, where this extension is promoted).\n\n      The extension provides query for current memory usage and budget, which will probably\n      be more accurate than an estimation used by the library otherwise.\n      */\n      VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT = 0x00000008,\n\n      VMA_ALLOCATOR_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n   } VmaAllocatorCreateFlagBits;\n   typedef VkFlags VmaAllocatorCreateFlags;\n\n   /** \\brief Pointers to some Vulkan functions - a subset used by the library.\n\n   Used in VmaAllocatorCreateInfo::pVulkanFunctions.\n   */\n   typedef struct VmaVulkanFunctions\n   {\n      PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;\n      PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;\n      PFN_vkAllocateMemory vkAllocateMemory;\n      PFN_vkFreeMemory vkFreeMemory;\n      PFN_vkMapMemory vkMapMemory;\n      PFN_vkUnmapMemory vkUnmapMemory;\n      PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;\n      PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;\n      PFN_vkBindBufferMemory vkBindBufferMemory;\n      PFN_vkBindImageMemory vkBindImageMemory;\n      PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;\n      PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;\n      PFN_vkCreateBuffer vkCreateBuffer;\n      PFN_vkDestroyBuffer vkDestroyBuffer;\n      PFN_vkCreateImage vkCreateImage;\n      PFN_vkDestroyImage vkDestroyImage;\n      PFN_vkCmdCopyBuffer vkCmdCopyBuffer;\n#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n      PFN_vkGetBufferMemoryRequirements2KHR vkGetBufferMemoryRequirements2KHR;\n      PFN_vkGetImageMemoryRequirements2KHR vkGetImageMemoryRequirements2KHR;\n#endif\n#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000\n      PFN_vkBindBufferMemory2KHR vkBindBufferMemory2KHR;\n      PFN_vkBindImageMemory2KHR vkBindImageMemory2KHR;\n#endif\n#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000\n      PFN_vkGetPhysicalDeviceMemoryProperties2KHR vkGetPhysicalDeviceMemoryProperties2KHR;\n#endif\n   } VmaVulkanFunctions;\n\n   /// Flags to be used in VmaRecordSettings::flags.\n   typedef enum VmaRecordFlagBits\n   {\n      /** \\brief Enables flush after recording every function call.\n\n      Enable it if you expect your application to crash, which may leave recording file truncated.\n      It may degrade performance though.\n      */\n      VMA_RECORD_FLUSH_AFTER_CALL_BIT = 0x00000001,\n\n      VMA_RECORD_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n   } VmaRecordFlagBits;\n   typedef VkFlags VmaRecordFlags;\n\n   /// Parameters for recording calls to VMA functions. To be used in VmaAllocatorCreateInfo::pRecordSettings.\n   typedef struct VmaRecordSettings\n   {\n      /// Flags for recording. Use #VmaRecordFlagBits enum.\n      VmaRecordFlags flags;\n      /** \\brief Path to the file that should be written by the recording.\n\n      Suggested extension: \"csv\".\n      If the file already exists, it will be overwritten.\n      It will be opened for the whole time #VmaAllocator object is alive.\n      If opening this file fails, creation of the whole allocator object fails.\n      */\n      const char* pFilePath;\n   } VmaRecordSettings;\n\n   /// Description of a Allocator to be created.\n   typedef struct VmaAllocatorCreateInfo\n   {\n      /// Flags for created allocator. Use #VmaAllocatorCreateFlagBits enum.\n      VmaAllocatorCreateFlags flags;\n      /// Vulkan physical device.\n      /** It must be valid throughout whole lifetime of created allocator. */\n      VkPhysicalDevice physicalDevice;\n      /// Vulkan device.\n      /** It must be valid throughout whole lifetime of created allocator. */\n      VkDevice device;\n      /// Preferred size of a single `VkDeviceMemory` block to be allocated from large heaps > 1 GiB. Optional.\n      /** Set to 0 to use default, which is currently 256 MiB. */\n      VkDeviceSize preferredLargeHeapBlockSize;\n      /// Custom CPU memory allocation callbacks. Optional.\n      /** Optional, can be null. When specified, will also be used for all CPU-side memory allocations. */\n      const VkAllocationCallbacks* pAllocationCallbacks;\n      /// Informative callbacks for `vkAllocateMemory`, `vkFreeMemory`. Optional.\n      /** Optional, can be null. */\n      const VmaDeviceMemoryCallbacks* pDeviceMemoryCallbacks;\n      /** \\brief Maximum number of additional frames that are in use at the same time as current frame.\n\n      This value is used only when you make allocations with\n      VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag. Such allocation cannot become\n      lost if allocation.lastUseFrameIndex >= allocator.currentFrameIndex - frameInUseCount.\n\n      For example, if you double-buffer your command buffers, so resources used for\n      rendering in previous frame may still be in use by the GPU at the moment you\n      allocate resources needed for the current frame, set this value to 1.\n\n      If you want to allow any allocations other than used in the current frame to\n      become lost, set this value to 0.\n      */\n      uint32_t frameInUseCount;\n      /** \\brief Either null or a pointer to an array of limits on maximum number of bytes that can be allocated out of particular Vulkan memory heap.\n\n      If not NULL, it must be a pointer to an array of\n      `VkPhysicalDeviceMemoryProperties::memoryHeapCount` elements, defining limit on\n      maximum number of bytes that can be allocated out of particular Vulkan memory\n      heap.\n\n      Any of the elements may be equal to `VK_WHOLE_SIZE`, which means no limit on that\n      heap. This is also the default in case of `pHeapSizeLimit` = NULL.\n\n      If there is a limit defined for a heap:\n\n      - If user tries to allocate more memory from that heap using this allocator,\n        the allocation fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY`.\n      - If the limit is smaller than heap size reported in `VkMemoryHeap::size`, the\n        value of this limit will be reported instead when using vmaGetMemoryProperties().\n\n      Warning! Using this feature may not be equivalent to installing a GPU with\n      smaller amount of memory, because graphics driver doesn't necessary fail new\n      allocations with `VK_ERROR_OUT_OF_DEVICE_MEMORY` result when memory capacity is\n      exceeded. It may return success and just silently migrate some device memory\n      blocks to system RAM. This driver behavior can also be controlled using\n      VK_AMD_memory_overallocation_behavior extension.\n      */\n      const VkDeviceSize* pHeapSizeLimit;\n      /** \\brief Pointers to Vulkan functions. Can be null if you leave define `VMA_STATIC_VULKAN_FUNCTIONS 1`.\n\n      If you leave define `VMA_STATIC_VULKAN_FUNCTIONS 1` in configuration section,\n      you can pass null as this member, because the library will fetch pointers to\n      Vulkan functions internally in a static way, like:\n\n          vulkanFunctions.vkAllocateMemory = &vkAllocateMemory;\n\n      Fill this member if you want to provide your own pointers to Vulkan functions,\n      e.g. fetched using `vkGetInstanceProcAddr()` and `vkGetDeviceProcAddr()`.\n      */\n      const VmaVulkanFunctions* pVulkanFunctions;\n      /** \\brief Parameters for recording of VMA calls. Can be null.\n\n      If not null, it enables recording of calls to VMA functions to a file.\n      If support for recording is not enabled using `VMA_RECORDING_ENABLED` macro,\n      creation of the allocator object fails with `VK_ERROR_FEATURE_NOT_PRESENT`.\n      */\n      const VmaRecordSettings* pRecordSettings;\n      /** \\brief Optional handle to Vulkan instance object.\n\n      Optional, can be null. Must be set if #VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT flas is used\n      or if `vulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)`.\n      */\n      VkInstance instance;\n      /** \\brief Optional. The highest version of Vulkan that the application is designed to use.\n\n      It must be a value in the format as created by macro `VK_MAKE_VERSION` or a constant like: `VK_API_VERSION_1_1`, `VK_API_VERSION_1_0`.\n      The patch version number specified is ignored. Only the major and minor versions are considered.\n      It must be less or euqal (preferably equal) to value as passed to `vkCreateInstance` as `VkApplicationInfo::apiVersion`.\n      Only versions 1.0 and 1.1 are supported by the current implementation.\n      Leaving it initialized to zero is equivalent to `VK_API_VERSION_1_0`.\n      */\n      uint32_t vulkanApiVersion;\n   } VmaAllocatorCreateInfo;\n\n   /// Creates Allocator object.\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator(\n      const VmaAllocatorCreateInfo* pCreateInfo,\n      VmaAllocator* pAllocator);\n\n   /// Destroys allocator object.\n   VMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator(\n      VmaAllocator allocator);\n\n   /**\n   PhysicalDeviceProperties are fetched from physicalDevice by the allocator.\n   You can access it here, without fetching it again on your own.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties(\n      VmaAllocator allocator,\n      const VkPhysicalDeviceProperties** ppPhysicalDeviceProperties);\n\n   /**\n   PhysicalDeviceMemoryProperties are fetched from physicalDevice by the allocator.\n   You can access it here, without fetching it again on your own.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties(\n      VmaAllocator allocator,\n      const VkPhysicalDeviceMemoryProperties** ppPhysicalDeviceMemoryProperties);\n\n   /**\n   \\brief Given Memory Type Index, returns Property Flags of this memory type.\n\n   This is just a convenience function. Same information can be obtained using\n   vmaGetMemoryProperties().\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties(\n      VmaAllocator allocator,\n      uint32_t memoryTypeIndex,\n      VkMemoryPropertyFlags* pFlags);\n\n   /** \\brief Sets index of the current frame.\n\n   This function must be used if you make allocations with\n   #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT and\n   #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flags to inform the allocator\n   when a new frame begins. Allocations queried using vmaGetAllocationInfo() cannot\n   become lost in the current frame.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex(\n      VmaAllocator allocator,\n      uint32_t frameIndex);\n\n   /** \\brief Calculated statistics of memory usage in entire allocator.\n   */\n   typedef struct VmaStatInfo\n   {\n      /// Number of `VkDeviceMemory` Vulkan memory blocks allocated.\n      uint32_t blockCount;\n      /// Number of #VmaAllocation allocation objects allocated.\n      uint32_t allocationCount;\n      /// Number of free ranges of memory between allocations.\n      uint32_t unusedRangeCount;\n      /// Total number of bytes occupied by all allocations.\n      VkDeviceSize usedBytes;\n      /// Total number of bytes occupied by unused ranges.\n      VkDeviceSize unusedBytes;\n      VkDeviceSize allocationSizeMin, allocationSizeAvg, allocationSizeMax;\n      VkDeviceSize unusedRangeSizeMin, unusedRangeSizeAvg, unusedRangeSizeMax;\n   } VmaStatInfo;\n\n   /// General statistics from current state of Allocator.\n   typedef struct VmaStats\n   {\n      VmaStatInfo memoryType[VK_MAX_MEMORY_TYPES];\n      VmaStatInfo memoryHeap[VK_MAX_MEMORY_HEAPS];\n      VmaStatInfo total;\n   } VmaStats;\n\n   /** \\brief Retrieves statistics from current state of the Allocator.\n\n   This function is called \"calculate\" not \"get\" because it has to traverse all\n   internal data structures, so it may be quite slow. For faster but more brief statistics\n   suitable to be called every frame or every allocation, use vmaGetBudget().\n\n   Note that when using allocator from multiple threads, returned information may immediately\n   become outdated.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaCalculateStats(\n      VmaAllocator allocator,\n      VmaStats* pStats);\n\n   /** \\brief Statistics of current memory usage and available budget, in bytes, for specific memory heap.\n   */\n   typedef struct VmaBudget\n   {\n      /** \\brief Sum size of all `VkDeviceMemory` blocks allocated from particular heap, in bytes.\n      */\n      VkDeviceSize blockBytes;\n\n      /** \\brief Sum size of all allocations created in particular heap, in bytes.\n\n      Usually less or equal than `blockBytes`.\n      Difference `blockBytes - allocationBytes` is the amount of memory allocated but unused -\n      available for new allocations or wasted due to fragmentation.\n\n      It might be greater than `blockBytes` if there are some allocations in lost state, as they account\n      to this value as well.\n      */\n      VkDeviceSize allocationBytes;\n\n      /** \\brief Estimated current memory usage of the program, in bytes.\n\n      Fetched from system using `VK_EXT_memory_budget` extension if enabled.\n\n      It might be different than `blockBytes` (usually higher) due to additional implicit objects\n      also occupying the memory, like swapchain, pipelines, descriptor heaps, command buffers, or\n      `VkDeviceMemory` blocks allocated outside of this library, if any.\n      */\n      VkDeviceSize usage;\n\n      /** \\brief Estimated amount of memory available to the program, in bytes.\n\n      Fetched from system using `VK_EXT_memory_budget` extension if enabled.\n\n      It might be different (most probably smaller) than `VkMemoryHeap::size[heapIndex]` due to factors\n      external to the program, like other programs also consuming system resources.\n      Difference `budget - usage` is the amount of additional memory that can probably\n      be allocated without problems. Exceeding the budget may result in various problems.\n      */\n      VkDeviceSize budget;\n   } VmaBudget;\n\n   /** \\brief Retrieves information about current memory budget for all memory heaps.\n\n   \\param[out] pBudget Must point to array with number of elements at least equal to number of memory heaps in physical device used.\n\n   This function is called \"get\" not \"calculate\" because it is very fast, suitable to be called\n   every frame or every allocation. For more detailed statistics use vmaCalculateStats().\n\n   Note that when using allocator from multiple threads, returned information may immediately\n   become outdated.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaGetBudget(\n      VmaAllocator allocator,\n      VmaBudget* pBudget);\n\n#ifndef VMA_STATS_STRING_ENABLED\n#define VMA_STATS_STRING_ENABLED 1\n#endif\n\n#if VMA_STATS_STRING_ENABLED\n\n   /// Builds and returns statistics as string in JSON format.\n   /** @param[out] ppStatsString Must be freed using vmaFreeStatsString() function.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString(\n      VmaAllocator allocator,\n      char** ppStatsString,\n      VkBool32 detailedMap);\n\n   VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString(\n      VmaAllocator allocator,\n      char* pStatsString);\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\n   /** \\struct VmaPool\n   \\brief Represents custom memory pool\n\n   Fill structure VmaPoolCreateInfo and call function vmaCreatePool() to create it.\n   Call function vmaDestroyPool() to destroy it.\n\n   For more information see [Custom memory pools](@ref choosing_memory_type_custom_memory_pools).\n   */\n   VK_DEFINE_HANDLE(VmaPool)\n\n      typedef enum VmaMemoryUsage\n   {\n      /** No intended memory usage specified.\n      Use other members of VmaAllocationCreateInfo to specify your requirements.\n      */\n      VMA_MEMORY_USAGE_UNKNOWN = 0,\n      /** Memory will be used on device only, so fast access from the device is preferred.\n      It usually means device-local GPU (video) memory.\n      No need to be mappable on host.\n      It is roughly equivalent of `D3D12_HEAP_TYPE_DEFAULT`.\n\n      Usage:\n\n      - Resources written and read by device, e.g. images used as attachments.\n      - Resources transferred from host once (immutable) or infrequently and read by\n        device multiple times, e.g. textures to be sampled, vertex buffers, uniform\n        (constant) buffers, and majority of other types of resources used on GPU.\n\n      Allocation may still end up in `HOST_VISIBLE` memory on some implementations.\n      In such case, you are free to map it.\n      You can use #VMA_ALLOCATION_CREATE_MAPPED_BIT with this usage type.\n      */\n      VMA_MEMORY_USAGE_GPU_ONLY = 1,\n      /** Memory will be mappable on host.\n      It usually means CPU (system) memory.\n      Guarantees to be `HOST_VISIBLE` and `HOST_COHERENT`.\n      CPU access is typically uncached. Writes may be write-combined.\n      Resources created in this pool may still be accessible to the device, but access to them can be slow.\n      It is roughly equivalent of `D3D12_HEAP_TYPE_UPLOAD`.\n\n      Usage: Staging copy of resources used as transfer source.\n      */\n      VMA_MEMORY_USAGE_CPU_ONLY = 2,\n      /**\n      Memory that is both mappable on host (guarantees to be `HOST_VISIBLE`) and preferably fast to access by GPU.\n      CPU access is typically uncached. Writes may be write-combined.\n\n      Usage: Resources written frequently by host (dynamic), read by device. E.g. textures, vertex buffers, uniform buffers updated every frame or every draw call.\n      */\n      VMA_MEMORY_USAGE_CPU_TO_GPU = 3,\n      /** Memory mappable on host (guarantees to be `HOST_VISIBLE`) and cached.\n      It is roughly equivalent of `D3D12_HEAP_TYPE_READBACK`.\n\n      Usage:\n\n      - Resources written by device, read by host - results of some computations, e.g. screen capture, average scene luminance for HDR tone mapping.\n      - Any resources read or accessed randomly on host, e.g. CPU-side copy of vertex buffer used as source of transfer, but also used for collision detection.\n      */\n      VMA_MEMORY_USAGE_GPU_TO_CPU = 4,\n      /** CPU memory - memory that is preferably not `DEVICE_LOCAL`, but also not guaranteed to be `HOST_VISIBLE`.\n\n      Usage: Staging copy of resources moved from GPU memory to CPU memory as part\n      of custom paging/residency mechanism, to be moved back to GPU memory when needed.\n      */\n      VMA_MEMORY_USAGE_CPU_COPY = 5,\n      /** Lazily allocated GPU memory having `VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT`.\n      Exists mostly on mobile platforms. Using it on desktop PC or other GPUs with no such memory type present will fail the allocation.\n\n      Usage: Memory for transient attachment images (color attachments, depth attachments etc.), created with `VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT`.\n\n      Allocations with this usage are always created as dedicated - it implies #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.\n      */\n      VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED = 6,\n\n      VMA_MEMORY_USAGE_MAX_ENUM = 0x7FFFFFFF\n   } VmaMemoryUsage;\n\n   /// Flags to be passed as VmaAllocationCreateInfo::flags.\n   typedef enum VmaAllocationCreateFlagBits\n   {\n      /** \\brief Set this flag if the allocation should have its own memory block.\n\n      Use it for special, big resources, like fullscreen images used as attachments.\n\n      You should not use this flag if VmaAllocationCreateInfo::pool is not null.\n      */\n      VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT = 0x00000001,\n\n      /** \\brief Set this flag to only try to allocate from existing `VkDeviceMemory` blocks and never create new such block.\n\n      If new allocation cannot be placed in any of the existing blocks, allocation\n      fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY` error.\n\n      You should not use #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT and\n      #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT at the same time. It makes no sense.\n\n      If VmaAllocationCreateInfo::pool is not null, this flag is implied and ignored. */\n      VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT = 0x00000002,\n      /** \\brief Set this flag to use a memory that will be persistently mapped and retrieve pointer to it.\n\n      Pointer to mapped memory will be returned through VmaAllocationInfo::pMappedData.\n\n      Is it valid to use this flag for allocation made from memory type that is not\n      `HOST_VISIBLE`. This flag is then ignored and memory is not mapped. This is\n      useful if you need an allocation that is efficient to use on GPU\n      (`DEVICE_LOCAL`) and still want to map it directly if possible on platforms that\n      support it (e.g. Intel GPU).\n\n      You should not use this flag together with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT.\n      */\n      VMA_ALLOCATION_CREATE_MAPPED_BIT = 0x00000004,\n      /** Allocation created with this flag can become lost as a result of another\n      allocation with #VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flag, so you\n      must check it before use.\n\n      To check if allocation is not lost, call vmaGetAllocationInfo() and check if\n      VmaAllocationInfo::deviceMemory is not `VK_NULL_HANDLE`.\n\n      For details about supporting lost allocations, see Lost Allocations\n      chapter of User Guide on Main Page.\n\n      You should not use this flag together with #VMA_ALLOCATION_CREATE_MAPPED_BIT.\n      */\n      VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT = 0x00000008,\n      /** While creating allocation using this flag, other allocations that were\n      created with flag #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT can become lost.\n\n      For details about supporting lost allocations, see Lost Allocations\n      chapter of User Guide on Main Page.\n      */\n      VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT = 0x00000010,\n      /** Set this flag to treat VmaAllocationCreateInfo::pUserData as pointer to a\n      null-terminated string. Instead of copying pointer value, a local copy of the\n      string is made and stored in allocation's `pUserData`. The string is automatically\n      freed together with the allocation. It is also used in vmaBuildStatsString().\n      */\n      VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT = 0x00000020,\n      /** Allocation will be created from upper stack in a double stack pool.\n\n      This flag is only allowed for custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT flag.\n      */\n      VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT = 0x00000040,\n      /** Create both buffer/image and allocation, but don't bind them together.\n      It is useful when you want to bind yourself to do some more advanced binding, e.g. using some extensions.\n      The flag is meaningful only with functions that bind by default: vmaCreateBuffer(), vmaCreateImage().\n      Otherwise it is ignored.\n      */\n      VMA_ALLOCATION_CREATE_DONT_BIND_BIT = 0x00000080,\n      /** Create allocation only if additional device memory required for it, if any, won't exceed\n      memory budget. Otherwise return `VK_ERROR_OUT_OF_DEVICE_MEMORY`.\n      */\n      VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT = 0x00000100,\n\n      /** Allocation strategy that chooses smallest possible free range for the\n      allocation.\n      */\n      VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT = 0x00010000,\n      /** Allocation strategy that chooses biggest possible free range for the\n      allocation.\n      */\n      VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT = 0x00020000,\n      /** Allocation strategy that chooses first suitable free range for the\n      allocation.\n\n      \"First\" doesn't necessarily means the one with smallest offset in memory,\n      but rather the one that is easiest and fastest to find.\n      */\n      VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT = 0x00040000,\n\n      /** Allocation strategy that tries to minimize memory usage.\n      */\n      VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT,\n      /** Allocation strategy that tries to minimize allocation time.\n      */\n      VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT = VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT,\n      /** Allocation strategy that tries to minimize memory fragmentation.\n      */\n      VMA_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT,\n\n      /** A bit mask to extract only `STRATEGY` bits from entire set of flags.\n      */\n      VMA_ALLOCATION_CREATE_STRATEGY_MASK =\n      VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT |\n      VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT |\n      VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT,\n\n      VMA_ALLOCATION_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n   } VmaAllocationCreateFlagBits;\n   typedef VkFlags VmaAllocationCreateFlags;\n\n   typedef struct VmaAllocationCreateInfo\n   {\n      /// Use #VmaAllocationCreateFlagBits enum.\n      VmaAllocationCreateFlags flags;\n      /** \\brief Intended usage of memory.\n\n      You can leave #VMA_MEMORY_USAGE_UNKNOWN if you specify memory requirements in other way. \\n\n      If `pool` is not null, this member is ignored.\n      */\n      VmaMemoryUsage usage;\n      /** \\brief Flags that must be set in a Memory Type chosen for an allocation.\n\n      Leave 0 if you specify memory requirements in other way. \\n\n      If `pool` is not null, this member is ignored.*/\n      VkMemoryPropertyFlags requiredFlags;\n      /** \\brief Flags that preferably should be set in a memory type chosen for an allocation.\n\n      Set to 0 if no additional flags are prefered. \\n\n      If `pool` is not null, this member is ignored. */\n      VkMemoryPropertyFlags preferredFlags;\n      /** \\brief Bitmask containing one bit set for every memory type acceptable for this allocation.\n\n      Value 0 is equivalent to `UINT32_MAX` - it means any memory type is accepted if\n      it meets other requirements specified by this structure, with no further\n      restrictions on memory type index. \\n\n      If `pool` is not null, this member is ignored.\n      */\n      uint32_t memoryTypeBits;\n      /** \\brief Pool that this allocation should be created in.\n\n      Leave `VK_NULL_HANDLE` to allocate from default pool. If not null, members:\n      `usage`, `requiredFlags`, `preferredFlags`, `memoryTypeBits` are ignored.\n      */\n      VmaPool pool;\n      /** \\brief Custom general-purpose pointer that will be stored in #VmaAllocation, can be read as VmaAllocationInfo::pUserData and changed using vmaSetAllocationUserData().\n\n      If #VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT is used, it must be either\n      null or pointer to a null-terminated string. The string will be then copied to\n      internal buffer, so it doesn't need to be valid after allocation call.\n      */\n      void* pUserData;\n   } VmaAllocationCreateInfo;\n\n   /**\n   \\brief Helps to find memoryTypeIndex, given memoryTypeBits and VmaAllocationCreateInfo.\n\n   This algorithm tries to find a memory type that:\n\n   - Is allowed by memoryTypeBits.\n   - Contains all the flags from pAllocationCreateInfo->requiredFlags.\n   - Matches intended usage.\n   - Has as many flags from pAllocationCreateInfo->preferredFlags as possible.\n\n   \\return Returns VK_ERROR_FEATURE_NOT_PRESENT if not found. Receiving such result\n   from this function or any other allocating function probably means that your\n   device doesn't support any memory type with requested features for the specific\n   type of resource you want to use it for. Please check parameters of your\n   resource, like image layout (OPTIMAL versus LINEAR) or mip level count.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex(\n      VmaAllocator allocator,\n      uint32_t memoryTypeBits,\n      const VmaAllocationCreateInfo* pAllocationCreateInfo,\n      uint32_t* pMemoryTypeIndex);\n\n   /**\n   \\brief Helps to find memoryTypeIndex, given VkBufferCreateInfo and VmaAllocationCreateInfo.\n\n   It can be useful e.g. to determine value to be used as VmaPoolCreateInfo::memoryTypeIndex.\n   It internally creates a temporary, dummy buffer that never has memory bound.\n   It is just a convenience function, equivalent to calling:\n\n   - `vkCreateBuffer`\n   - `vkGetBufferMemoryRequirements`\n   - `vmaFindMemoryTypeIndex`\n   - `vkDestroyBuffer`\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo(\n      VmaAllocator allocator,\n      const VkBufferCreateInfo* pBufferCreateInfo,\n      const VmaAllocationCreateInfo* pAllocationCreateInfo,\n      uint32_t* pMemoryTypeIndex);\n\n   /**\n   \\brief Helps to find memoryTypeIndex, given VkImageCreateInfo and VmaAllocationCreateInfo.\n\n   It can be useful e.g. to determine value to be used as VmaPoolCreateInfo::memoryTypeIndex.\n   It internally creates a temporary, dummy image that never has memory bound.\n   It is just a convenience function, equivalent to calling:\n\n   - `vkCreateImage`\n   - `vkGetImageMemoryRequirements`\n   - `vmaFindMemoryTypeIndex`\n   - `vkDestroyImage`\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo(\n      VmaAllocator allocator,\n      const VkImageCreateInfo* pImageCreateInfo,\n      const VmaAllocationCreateInfo* pAllocationCreateInfo,\n      uint32_t* pMemoryTypeIndex);\n\n   /// Flags to be passed as VmaPoolCreateInfo::flags.\n   typedef enum VmaPoolCreateFlagBits\n   {\n      /** \\brief Use this flag if you always allocate only buffers and linear images or only optimal images out of this pool and so Buffer-Image Granularity can be ignored.\n\n      This is an optional optimization flag.\n\n      If you always allocate using vmaCreateBuffer(), vmaCreateImage(),\n      vmaAllocateMemoryForBuffer(), then you don't need to use it because allocator\n      knows exact type of your allocations so it can handle Buffer-Image Granularity\n      in the optimal way.\n\n      If you also allocate using vmaAllocateMemoryForImage() or vmaAllocateMemory(),\n      exact type of such allocations is not known, so allocator must be conservative\n      in handling Buffer-Image Granularity, which can lead to suboptimal allocation\n      (wasted memory). In that case, if you can make sure you always allocate only\n      buffers and linear images or only optimal images out of this pool, use this flag\n      to make allocator disregard Buffer-Image Granularity and so make allocations\n      faster and more optimal.\n      */\n      VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT = 0x00000002,\n\n      /** \\brief Enables alternative, linear allocation algorithm in this pool.\n\n      Specify this flag to enable linear allocation algorithm, which always creates\n      new allocations after last one and doesn't reuse space from allocations freed in\n      between. It trades memory consumption for simplified algorithm and data\n      structure, which has better performance and uses less memory for metadata.\n\n      By using this flag, you can achieve behavior of free-at-once, stack,\n      ring buffer, and double stack. For details, see documentation chapter\n      \\ref linear_algorithm.\n\n      When using this flag, you must specify VmaPoolCreateInfo::maxBlockCount == 1 (or 0 for default).\n\n      For more details, see [Linear allocation algorithm](@ref linear_algorithm).\n      */\n      VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT = 0x00000004,\n\n      /** \\brief Enables alternative, buddy allocation algorithm in this pool.\n\n      It operates on a tree of blocks, each having size that is a power of two and\n      a half of its parent's size. Comparing to default algorithm, this one provides\n      faster allocation and deallocation and decreased external fragmentation,\n      at the expense of more memory wasted (internal fragmentation).\n\n      For more details, see [Buddy allocation algorithm](@ref buddy_algorithm).\n      */\n      VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT = 0x00000008,\n\n      /** Bit mask to extract only `ALGORITHM` bits from entire set of flags.\n      */\n      VMA_POOL_CREATE_ALGORITHM_MASK =\n      VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT |\n      VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT,\n\n      VMA_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n   } VmaPoolCreateFlagBits;\n   typedef VkFlags VmaPoolCreateFlags;\n\n   /** \\brief Describes parameter of created #VmaPool.\n   */\n   typedef struct VmaPoolCreateInfo\n   {\n      /** \\brief Vulkan memory type index to allocate this pool from.\n      */\n      uint32_t memoryTypeIndex;\n      /** \\brief Use combination of #VmaPoolCreateFlagBits.\n      */\n      VmaPoolCreateFlags flags;\n      /** \\brief Size of a single `VkDeviceMemory` block to be allocated as part of this pool, in bytes. Optional.\n\n      Specify nonzero to set explicit, constant size of memory blocks used by this\n      pool.\n\n      Leave 0 to use default and let the library manage block sizes automatically.\n      Sizes of particular blocks may vary.\n      */\n      VkDeviceSize blockSize;\n      /** \\brief Minimum number of blocks to be always allocated in this pool, even if they stay empty.\n\n      Set to 0 to have no preallocated blocks and allow the pool be completely empty.\n      */\n      size_t minBlockCount;\n      /** \\brief Maximum number of blocks that can be allocated in this pool. Optional.\n\n      Set to 0 to use default, which is `SIZE_MAX`, which means no limit.\n\n      Set to same value as VmaPoolCreateInfo::minBlockCount to have fixed amount of memory allocated\n      throughout whole lifetime of this pool.\n      */\n      size_t maxBlockCount;\n      /** \\brief Maximum number of additional frames that are in use at the same time as current frame.\n\n      This value is used only when you make allocations with\n      #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag. Such allocation cannot become\n      lost if allocation.lastUseFrameIndex >= allocator.currentFrameIndex - frameInUseCount.\n\n      For example, if you double-buffer your command buffers, so resources used for\n      rendering in previous frame may still be in use by the GPU at the moment you\n      allocate resources needed for the current frame, set this value to 1.\n\n      If you want to allow any allocations other than used in the current frame to\n      become lost, set this value to 0.\n      */\n      uint32_t frameInUseCount;\n   } VmaPoolCreateInfo;\n\n   /** \\brief Describes parameter of existing #VmaPool.\n   */\n   typedef struct VmaPoolStats\n   {\n      /** \\brief Total amount of `VkDeviceMemory` allocated from Vulkan for this pool, in bytes.\n      */\n      VkDeviceSize size;\n      /** \\brief Total number of bytes in the pool not used by any #VmaAllocation.\n      */\n      VkDeviceSize unusedSize;\n      /** \\brief Number of #VmaAllocation objects created from this pool that were not destroyed or lost.\n      */\n      size_t allocationCount;\n      /** \\brief Number of continuous memory ranges in the pool not used by any #VmaAllocation.\n      */\n      size_t unusedRangeCount;\n      /** \\brief Size of the largest continuous free memory region available for new allocation.\n\n      Making a new allocation of that size is not guaranteed to succeed because of\n      possible additional margin required to respect alignment and buffer/image\n      granularity.\n      */\n      VkDeviceSize unusedRangeSizeMax;\n      /** \\brief Number of `VkDeviceMemory` blocks allocated for this pool.\n      */\n      size_t blockCount;\n   } VmaPoolStats;\n\n   /** \\brief Allocates Vulkan device memory and creates #VmaPool object.\n\n   @param allocator Allocator object.\n   @param pCreateInfo Parameters of pool to create.\n   @param[out] pPool Handle to created pool.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool(\n      VmaAllocator allocator,\n      const VmaPoolCreateInfo* pCreateInfo,\n      VmaPool* pPool);\n\n   /** \\brief Destroys #VmaPool object and frees Vulkan device memory.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool(\n      VmaAllocator allocator,\n      VmaPool pool);\n\n   /** \\brief Retrieves statistics of existing #VmaPool object.\n\n   @param allocator Allocator object.\n   @param pool Pool object.\n   @param[out] pPoolStats Statistics of specified pool.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStats(\n      VmaAllocator allocator,\n      VmaPool pool,\n      VmaPoolStats* pPoolStats);\n\n   /** \\brief Marks all allocations in given pool as lost if they are not used in current frame or VmaPoolCreateInfo::frameInUseCount back from now.\n\n   @param allocator Allocator object.\n   @param pool Pool.\n   @param[out] pLostAllocationCount Number of allocations marked as lost. Optional - pass null if you don't need this information.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaMakePoolAllocationsLost(\n      VmaAllocator allocator,\n      VmaPool pool,\n      size_t* pLostAllocationCount);\n\n   /** \\brief Checks magic number in margins around all allocations in given memory pool in search for corruptions.\n\n   Corruption detection is enabled only when `VMA_DEBUG_DETECT_CORRUPTION` macro is defined to nonzero,\n   `VMA_DEBUG_MARGIN` is defined to nonzero and the pool is created in memory type that is\n   `HOST_VISIBLE` and `HOST_COHERENT`. For more information, see [Corruption detection](@ref debugging_memory_usage_corruption_detection).\n\n   Possible return values:\n\n   - `VK_ERROR_FEATURE_NOT_PRESENT` - corruption detection is not enabled for specified pool.\n   - `VK_SUCCESS` - corruption detection has been performed and succeeded.\n   - `VK_ERROR_VALIDATION_FAILED_EXT` - corruption detection has been performed and found memory corruptions around one of the allocations.\n     `VMA_ASSERT` is also fired in that case.\n   - Other value: Error returned by Vulkan, e.g. memory mapping failure.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool);\n\n   /** \\brief Retrieves name of a custom pool.\n\n   After the call `ppName` is either null or points to an internally-owned null-terminated string\n   containing name of the pool that was previously set. The pointer becomes invalid when the pool is\n   destroyed or its name is changed using vmaSetPoolName().\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName(\n      VmaAllocator allocator,\n      VmaPool pool,\n      const char** ppName);\n\n   /** \\brief Sets name of a custom pool.\n\n   `pName` can be either null or pointer to a null-terminated string with new name for the pool.\n   Function makes internal copy of the string, so it can be changed or freed immediately after this call.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName(\n      VmaAllocator allocator,\n      VmaPool pool,\n      const char* pName);\n\n   /** \\struct VmaAllocation\n   \\brief Represents single memory allocation.\n\n   It may be either dedicated block of `VkDeviceMemory` or a specific region of a bigger block of this type\n   plus unique offset.\n\n   There are multiple ways to create such object.\n   You need to fill structure VmaAllocationCreateInfo.\n   For more information see [Choosing memory type](@ref choosing_memory_type).\n\n   Although the library provides convenience functions that create Vulkan buffer or image,\n   allocate memory for it and bind them together,\n   binding of the allocation to a buffer or an image is out of scope of the allocation itself.\n   Allocation object can exist without buffer/image bound,\n   binding can be done manually by the user, and destruction of it can be done\n   independently of destruction of the allocation.\n\n   The object also remembers its size and some other information.\n   To retrieve this information, use function vmaGetAllocationInfo() and inspect\n   returned structure VmaAllocationInfo.\n\n   Some kinds allocations can be in lost state.\n   For more information, see [Lost allocations](@ref lost_allocations).\n   */\n   VK_DEFINE_HANDLE(VmaAllocation)\n\n      /** \\brief Parameters of #VmaAllocation objects, that can be retrieved using function vmaGetAllocationInfo().\n      */\n      typedef struct VmaAllocationInfo\n   {\n      /** \\brief Memory type index that this allocation was allocated from.\n\n      It never changes.\n      */\n      uint32_t memoryType;\n      /** \\brief Handle to Vulkan memory object.\n\n      Same memory object can be shared by multiple allocations.\n\n      It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost.\n\n      If the allocation is lost, it is equal to `VK_NULL_HANDLE`.\n      */\n      VkDeviceMemory deviceMemory;\n      /** \\brief Offset into deviceMemory object to the beginning of this allocation, in bytes. (deviceMemory, offset) pair is unique to this allocation.\n\n      It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost.\n      */\n      VkDeviceSize offset;\n      /** \\brief Size of this allocation, in bytes.\n\n      It never changes, unless allocation is lost.\n      */\n      VkDeviceSize size;\n      /** \\brief Pointer to the beginning of this allocation as mapped data.\n\n      If the allocation hasn't been mapped using vmaMapMemory() and hasn't been\n      created with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag, this value null.\n\n      It can change after call to vmaMapMemory(), vmaUnmapMemory().\n      It can also change after call to vmaDefragment() if this allocation is passed to the function.\n      */\n      void* pMappedData;\n      /** \\brief Custom general-purpose pointer that was passed as VmaAllocationCreateInfo::pUserData or set using vmaSetAllocationUserData().\n\n      It can change after call to vmaSetAllocationUserData() for this allocation.\n      */\n      void* pUserData;\n   } VmaAllocationInfo;\n\n   /** \\brief General purpose memory allocation.\n\n   @param[out] pAllocation Handle to allocated memory.\n   @param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo().\n\n   You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages().\n\n   It is recommended to use vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(),\n   vmaCreateBuffer(), vmaCreateImage() instead whenever possible.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory(\n      VmaAllocator allocator,\n      const VkMemoryRequirements* pVkMemoryRequirements,\n      const VmaAllocationCreateInfo* pCreateInfo,\n      VmaAllocation* pAllocation,\n      VmaAllocationInfo* pAllocationInfo);\n\n   /** \\brief General purpose memory allocation for multiple allocation objects at once.\n\n   @param allocator Allocator object.\n   @param pVkMemoryRequirements Memory requirements for each allocation.\n   @param pCreateInfo Creation parameters for each alloction.\n   @param allocationCount Number of allocations to make.\n   @param[out] pAllocations Pointer to array that will be filled with handles to created allocations.\n   @param[out] pAllocationInfo Optional. Pointer to array that will be filled with parameters of created allocations.\n\n   You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages().\n\n   Word \"pages\" is just a suggestion to use this function to allocate pieces of memory needed for sparse binding.\n   It is just a general purpose allocation function able to make multiple allocations at once.\n   It may be internally optimized to be more efficient than calling vmaAllocateMemory() `allocationCount` times.\n\n   All allocations are made using same parameters. All of them are created out of the same memory pool and type.\n   If any allocation fails, all allocations already made within this function call are also freed, so that when\n   returned result is not `VK_SUCCESS`, `pAllocation` array is always entirely filled with `VK_NULL_HANDLE`.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages(\n      VmaAllocator allocator,\n      const VkMemoryRequirements* pVkMemoryRequirements,\n      const VmaAllocationCreateInfo* pCreateInfo,\n      size_t allocationCount,\n      VmaAllocation* pAllocations,\n      VmaAllocationInfo* pAllocationInfo);\n\n   /**\n   @param[out] pAllocation Handle to allocated memory.\n   @param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo().\n\n   You should free the memory using vmaFreeMemory().\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer(\n      VmaAllocator allocator,\n      VkBuffer buffer,\n      const VmaAllocationCreateInfo* pCreateInfo,\n      VmaAllocation* pAllocation,\n      VmaAllocationInfo* pAllocationInfo);\n\n   /// Function similar to vmaAllocateMemoryForBuffer().\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage(\n      VmaAllocator allocator,\n      VkImage image,\n      const VmaAllocationCreateInfo* pCreateInfo,\n      VmaAllocation* pAllocation,\n      VmaAllocationInfo* pAllocationInfo);\n\n   /** \\brief Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(), or vmaAllocateMemoryForImage().\n\n   Passing `VK_NULL_HANDLE` as `allocation` is valid. Such function call is just skipped.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory(\n      VmaAllocator allocator,\n      VmaAllocation allocation);\n\n   /** \\brief Frees memory and destroys multiple allocations.\n\n   Word \"pages\" is just a suggestion to use this function to free pieces of memory used for sparse binding.\n   It is just a general purpose function to free memory and destroy allocations made using e.g. vmaAllocateMemory(),\n   vmaAllocateMemoryPages() and other functions.\n   It may be internally optimized to be more efficient than calling vmaFreeMemory() `allocationCount` times.\n\n   Allocations in `pAllocations` array can come from any memory pools and types.\n   Passing `VK_NULL_HANDLE` as elements of `pAllocations` array is valid. Such entries are just skipped.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages(\n      VmaAllocator allocator,\n      size_t allocationCount,\n      VmaAllocation* pAllocations);\n\n   /** \\brief Deprecated.\n\n   In version 2.2.0 it used to try to change allocation's size without moving or reallocating it.\n   In current version it returns `VK_SUCCESS` only if `newSize` equals current allocation's size.\n   Otherwise returns `VK_ERROR_OUT_OF_POOL_MEMORY`, indicating that allocation's size could not be changed.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaResizeAllocation(\n      VmaAllocator allocator,\n      VmaAllocation allocation,\n      VkDeviceSize newSize);\n\n   /** \\brief Returns current information about specified allocation and atomically marks it as used in current frame.\n\n   Current paramters of given allocation are returned in `pAllocationInfo`.\n\n   This function also atomically \"touches\" allocation - marks it as used in current frame,\n   just like vmaTouchAllocation().\n   If the allocation is in lost state, `pAllocationInfo->deviceMemory == VK_NULL_HANDLE`.\n\n   Although this function uses atomics and doesn't lock any mutex, so it should be quite efficient,\n   you can avoid calling it too often.\n\n   - You can retrieve same VmaAllocationInfo structure while creating your resource, from function\n     vmaCreateBuffer(), vmaCreateImage(). You can remember it if you are sure parameters don't change\n     (e.g. due to defragmentation or allocation becoming lost).\n   - If you just want to check if allocation is not lost, vmaTouchAllocation() will work faster.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo(\n      VmaAllocator allocator,\n      VmaAllocation allocation,\n      VmaAllocationInfo* pAllocationInfo);\n\n   /** \\brief Returns `VK_TRUE` if allocation is not lost and atomically marks it as used in current frame.\n\n   If the allocation has been created with #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag,\n   this function returns `VK_TRUE` if it's not in lost state, so it can still be used.\n   It then also atomically \"touches\" the allocation - marks it as used in current frame,\n   so that you can be sure it won't become lost in current frame or next `frameInUseCount` frames.\n\n   If the allocation is in lost state, the function returns `VK_FALSE`.\n   Memory of such allocation, as well as buffer or image bound to it, should not be used.\n   Lost allocation and the buffer/image still need to be destroyed.\n\n   If the allocation has been created without #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag,\n   this function always returns `VK_TRUE`.\n   */\n   VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaTouchAllocation(\n      VmaAllocator allocator,\n      VmaAllocation allocation);\n\n   /** \\brief Sets pUserData in given allocation to new value.\n\n   If the allocation was created with VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT,\n   pUserData must be either null, or pointer to a null-terminated string. The function\n   makes local copy of the string and sets it as allocation's `pUserData`. String\n   passed as pUserData doesn't need to be valid for whole lifetime of the allocation -\n   you can free it after this call. String previously pointed by allocation's\n   pUserData is freed from memory.\n\n   If the flag was not used, the value of pointer `pUserData` is just copied to\n   allocation's `pUserData`. It is opaque, so you can use it however you want - e.g.\n   as a pointer, ordinal number or some handle to you own data.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData(\n      VmaAllocator allocator,\n      VmaAllocation allocation,\n      void* pUserData);\n\n   /** \\brief Creates new allocation that is in lost state from the beginning.\n\n   It can be useful if you need a dummy, non-null allocation.\n\n   You still need to destroy created object using vmaFreeMemory().\n\n   Returned allocation is not tied to any specific memory pool or memory type and\n   not bound to any image or buffer. It has size = 0. It cannot be turned into\n   a real, non-empty allocation.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaCreateLostAllocation(\n      VmaAllocator allocator,\n      VmaAllocation* pAllocation);\n\n   /** \\brief Maps memory represented by given allocation and returns pointer to it.\n\n   Maps memory represented by given allocation to make it accessible to CPU code.\n   When succeeded, `*ppData` contains pointer to first byte of this memory.\n   If the allocation is part of bigger `VkDeviceMemory` block, the pointer is\n   correctly offseted to the beginning of region assigned to this particular\n   allocation.\n\n   Mapping is internally reference-counted and synchronized, so despite raw Vulkan\n   function `vkMapMemory()` cannot be used to map same block of `VkDeviceMemory`\n   multiple times simultaneously, it is safe to call this function on allocations\n   assigned to the same memory block. Actual Vulkan memory will be mapped on first\n   mapping and unmapped on last unmapping.\n\n   If the function succeeded, you must call vmaUnmapMemory() to unmap the\n   allocation when mapping is no longer needed or before freeing the allocation, at\n   the latest.\n\n   It also safe to call this function multiple times on the same allocation. You\n   must call vmaUnmapMemory() same number of times as you called vmaMapMemory().\n\n   It is also safe to call this function on allocation created with\n   #VMA_ALLOCATION_CREATE_MAPPED_BIT flag. Its memory stays mapped all the time.\n   You must still call vmaUnmapMemory() same number of times as you called\n   vmaMapMemory(). You must not call vmaUnmapMemory() additional time to free the\n   \"0-th\" mapping made automatically due to #VMA_ALLOCATION_CREATE_MAPPED_BIT flag.\n\n   This function fails when used on allocation made in memory type that is not\n   `HOST_VISIBLE`.\n\n   This function always fails when called for allocation that was created with\n   #VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag. Such allocations cannot be\n   mapped.\n\n   This function doesn't automatically flush or invalidate caches.\n   If the allocation is made from a memory types that is not `HOST_COHERENT`,\n   you also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory(\n      VmaAllocator allocator,\n      VmaAllocation allocation,\n      void** ppData);\n\n   /** \\brief Unmaps memory represented by given allocation, mapped previously using vmaMapMemory().\n\n   For details, see description of vmaMapMemory().\n\n   This function doesn't automatically flush or invalidate caches.\n   If the allocation is made from a memory types that is not `HOST_COHERENT`,\n   you also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory(\n      VmaAllocator allocator,\n      VmaAllocation allocation);\n\n   /** \\brief Flushes memory of given allocation.\n\n   Calls `vkFlushMappedMemoryRanges()` for memory associated with given range of given allocation.\n   It needs to be called after writing to a mapped memory for memory types that are not `HOST_COHERENT`.\n   Unmap operation doesn't do that automatically.\n\n   - `offset` must be relative to the beginning of allocation.\n   - `size` can be `VK_WHOLE_SIZE`. It means all memory from `offset` the the end of given allocation.\n   - `offset` and `size` don't have to be aligned.\n     They are internally rounded down/up to multiply of `nonCoherentAtomSize`.\n   - If `size` is 0, this call is ignored.\n   - If memory type that the `allocation` belongs to is not `HOST_VISIBLE` or it is `HOST_COHERENT`,\n     this call is ignored.\n\n   Warning! `offset` and `size` are relative to the contents of given `allocation`.\n   If you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively.\n   Do not pass allocation's offset as `offset`!!!\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size);\n\n   /** \\brief Invalidates memory of given allocation.\n\n   Calls `vkInvalidateMappedMemoryRanges()` for memory associated with given range of given allocation.\n   It needs to be called before reading from a mapped memory for memory types that are not `HOST_COHERENT`.\n   Map operation doesn't do that automatically.\n\n   - `offset` must be relative to the beginning of allocation.\n   - `size` can be `VK_WHOLE_SIZE`. It means all memory from `offset` the the end of given allocation.\n   - `offset` and `size` don't have to be aligned.\n     They are internally rounded down/up to multiply of `nonCoherentAtomSize`.\n   - If `size` is 0, this call is ignored.\n   - If memory type that the `allocation` belongs to is not `HOST_VISIBLE` or it is `HOST_COHERENT`,\n     this call is ignored.\n\n   Warning! `offset` and `size` are relative to the contents of given `allocation`.\n   If you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively.\n   Do not pass allocation's offset as `offset`!!!\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size);\n\n   /** \\brief Checks magic number in margins around all allocations in given memory types (in both default and custom pools) in search for corruptions.\n\n   @param memoryTypeBits Bit mask, where each bit set means that a memory type with that index should be checked.\n\n   Corruption detection is enabled only when `VMA_DEBUG_DETECT_CORRUPTION` macro is defined to nonzero,\n   `VMA_DEBUG_MARGIN` is defined to nonzero and only for memory types that are\n   `HOST_VISIBLE` and `HOST_COHERENT`. For more information, see [Corruption detection](@ref debugging_memory_usage_corruption_detection).\n\n   Possible return values:\n\n   - `VK_ERROR_FEATURE_NOT_PRESENT` - corruption detection is not enabled for any of specified memory types.\n   - `VK_SUCCESS` - corruption detection has been performed and succeeded.\n   - `VK_ERROR_VALIDATION_FAILED_EXT` - corruption detection has been performed and found memory corruptions around one of the allocations.\n     `VMA_ASSERT` is also fired in that case.\n   - Other value: Error returned by Vulkan, e.g. memory mapping failure.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits);\n\n   /** \\struct VmaDefragmentationContext\n   \\brief Represents Opaque object that represents started defragmentation process.\n\n   Fill structure #VmaDefragmentationInfo2 and call function vmaDefragmentationBegin() to create it.\n   Call function vmaDefragmentationEnd() to destroy it.\n   */\n   VK_DEFINE_HANDLE(VmaDefragmentationContext)\n\n      /// Flags to be used in vmaDefragmentationBegin(). None at the moment. Reserved for future use.\n      typedef enum VmaDefragmentationFlagBits\n   {\n      VMA_DEFRAGMENTATION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF\n   } VmaDefragmentationFlagBits;\n   typedef VkFlags VmaDefragmentationFlags;\n\n   /** \\brief Parameters for defragmentation.\n\n   To be used with function vmaDefragmentationBegin().\n   */\n   typedef struct VmaDefragmentationInfo2\n   {\n      /** \\brief Reserved for future use. Should be 0.\n      */\n      VmaDefragmentationFlags flags;\n      /** \\brief Number of allocations in `pAllocations` array.\n      */\n      uint32_t allocationCount;\n      /** \\brief Pointer to array of allocations that can be defragmented.\n\n      The array should have `allocationCount` elements.\n      The array should not contain nulls.\n      Elements in the array should be unique - same allocation cannot occur twice.\n      It is safe to pass allocations that are in the lost state - they are ignored.\n      All allocations not present in this array are considered non-moveable during this defragmentation.\n      */\n      VmaAllocation* pAllocations;\n      /** \\brief Optional, output. Pointer to array that will be filled with information whether the allocation at certain index has been changed during defragmentation.\n\n      The array should have `allocationCount` elements.\n      You can pass null if you are not interested in this information.\n      */\n      VkBool32* pAllocationsChanged;\n      /** \\brief Numer of pools in `pPools` array.\n      */\n      uint32_t poolCount;\n      /** \\brief Either null or pointer to array of pools to be defragmented.\n\n      All the allocations in the specified pools can be moved during defragmentation\n      and there is no way to check if they were really moved as in `pAllocationsChanged`,\n      so you must query all the allocations in all these pools for new `VkDeviceMemory`\n      and offset using vmaGetAllocationInfo() if you might need to recreate buffers\n      and images bound to them.\n\n      The array should have `poolCount` elements.\n      The array should not contain nulls.\n      Elements in the array should be unique - same pool cannot occur twice.\n\n      Using this array is equivalent to specifying all allocations from the pools in `pAllocations`.\n      It might be more efficient.\n      */\n      VmaPool* pPools;\n      /** \\brief Maximum total numbers of bytes that can be copied while moving allocations to different places using transfers on CPU side, like `memcpy()`, `memmove()`.\n\n      `VK_WHOLE_SIZE` means no limit.\n      */\n      VkDeviceSize maxCpuBytesToMove;\n      /** \\brief Maximum number of allocations that can be moved to a different place using transfers on CPU side, like `memcpy()`, `memmove()`.\n\n      `UINT32_MAX` means no limit.\n      */\n      uint32_t maxCpuAllocationsToMove;\n      /** \\brief Maximum total numbers of bytes that can be copied while moving allocations to different places using transfers on GPU side, posted to `commandBuffer`.\n\n      `VK_WHOLE_SIZE` means no limit.\n      */\n      VkDeviceSize maxGpuBytesToMove;\n      /** \\brief Maximum number of allocations that can be moved to a different place using transfers on GPU side, posted to `commandBuffer`.\n\n      `UINT32_MAX` means no limit.\n      */\n      uint32_t maxGpuAllocationsToMove;\n      /** \\brief Optional. Command buffer where GPU copy commands will be posted.\n\n      If not null, it must be a valid command buffer handle that supports Transfer queue type.\n      It must be in the recording state and outside of a render pass instance.\n      You need to submit it and make sure it finished execution before calling vmaDefragmentationEnd().\n\n      Passing null means that only CPU defragmentation will be performed.\n      */\n      VkCommandBuffer commandBuffer;\n   } VmaDefragmentationInfo2;\n\n   /** \\brief Deprecated. Optional configuration parameters to be passed to function vmaDefragment().\n\n   \\deprecated This is a part of the old interface. It is recommended to use structure #VmaDefragmentationInfo2 and function vmaDefragmentationBegin() instead.\n   */\n   typedef struct VmaDefragmentationInfo\n   {\n      /** \\brief Maximum total numbers of bytes that can be copied while moving allocations to different places.\n\n      Default is `VK_WHOLE_SIZE`, which means no limit.\n      */\n      VkDeviceSize maxBytesToMove;\n      /** \\brief Maximum number of allocations that can be moved to different place.\n\n      Default is `UINT32_MAX`, which means no limit.\n      */\n      uint32_t maxAllocationsToMove;\n   } VmaDefragmentationInfo;\n\n   /** \\brief Statistics returned by function vmaDefragment(). */\n   typedef struct VmaDefragmentationStats\n   {\n      /// Total number of bytes that have been copied while moving allocations to different places.\n      VkDeviceSize bytesMoved;\n      /// Total number of bytes that have been released to the system by freeing empty `VkDeviceMemory` objects.\n      VkDeviceSize bytesFreed;\n      /// Number of allocations that have been moved to different places.\n      uint32_t allocationsMoved;\n      /// Number of empty `VkDeviceMemory` objects that have been released to the system.\n      uint32_t deviceMemoryBlocksFreed;\n   } VmaDefragmentationStats;\n\n   /** \\brief Begins defragmentation process.\n\n   @param allocator Allocator object.\n   @param pInfo Structure filled with parameters of defragmentation.\n   @param[out] pStats Optional. Statistics of defragmentation. You can pass null if you are not interested in this information.\n   @param[out] pContext Context object that must be passed to vmaDefragmentationEnd() to finish defragmentation.\n   @return `VK_SUCCESS` and `*pContext == null` if defragmentation finished within this function call. `VK_NOT_READY` and `*pContext != null` if defragmentation has been started and you need to call vmaDefragmentationEnd() to finish it. Negative value in case of error.\n\n   Use this function instead of old, deprecated vmaDefragment().\n\n   Warning! Between the call to vmaDefragmentationBegin() and vmaDefragmentationEnd():\n\n   - You should not use any of allocations passed as `pInfo->pAllocations` or\n     any allocations that belong to pools passed as `pInfo->pPools`,\n     including calling vmaGetAllocationInfo(), vmaTouchAllocation(), or access\n     their data.\n   - Some mutexes protecting internal data structures may be locked, so trying to\n     make or free any allocations, bind buffers or images, map memory, or launch\n     another simultaneous defragmentation in between may cause stall (when done on\n     another thread) or deadlock (when done on the same thread), unless you are\n     100% sure that defragmented allocations are in different pools.\n   - Information returned via `pStats` and `pInfo->pAllocationsChanged` are undefined.\n     They become valid after call to vmaDefragmentationEnd().\n   - If `pInfo->commandBuffer` is not null, you must submit that command buffer\n     and make sure it finished execution before calling vmaDefragmentationEnd().\n\n   For more information and important limitations regarding defragmentation, see documentation chapter:\n   [Defragmentation](@ref defragmentation).\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationBegin(\n      VmaAllocator allocator,\n      const VmaDefragmentationInfo2* pInfo,\n      VmaDefragmentationStats* pStats,\n      VmaDefragmentationContext* pContext);\n\n   /** \\brief Ends defragmentation process.\n\n   Use this function to finish defragmentation started by vmaDefragmentationBegin().\n   It is safe to pass `context == null`. The function then does nothing.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationEnd(\n      VmaAllocator allocator,\n      VmaDefragmentationContext context);\n\n   /** \\brief Deprecated. Compacts memory by moving allocations.\n\n   @param pAllocations Array of allocations that can be moved during this compation.\n   @param allocationCount Number of elements in pAllocations and pAllocationsChanged arrays.\n   @param[out] pAllocationsChanged Array of boolean values that will indicate whether matching allocation in pAllocations array has been moved. This parameter is optional. Pass null if you don't need this information.\n   @param pDefragmentationInfo Configuration parameters. Optional - pass null to use default values.\n   @param[out] pDefragmentationStats Statistics returned by the function. Optional - pass null if you don't need this information.\n   @return `VK_SUCCESS` if completed, negative error code in case of error.\n\n   \\deprecated This is a part of the old interface. It is recommended to use structure #VmaDefragmentationInfo2 and function vmaDefragmentationBegin() instead.\n\n   This function works by moving allocations to different places (different\n   `VkDeviceMemory` objects and/or different offsets) in order to optimize memory\n   usage. Only allocations that are in `pAllocations` array can be moved. All other\n   allocations are considered nonmovable in this call. Basic rules:\n\n   - Only allocations made in memory types that have\n     `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` and `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT`\n     flags can be compacted. You may pass other allocations but it makes no sense -\n     these will never be moved.\n   - Custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT or\n     #VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT flag are not defragmented. Allocations\n     passed to this function that come from such pools are ignored.\n   - Allocations created with #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT or\n     created as dedicated allocations for any other reason are also ignored.\n   - Both allocations made with or without #VMA_ALLOCATION_CREATE_MAPPED_BIT\n     flag can be compacted. If not persistently mapped, memory will be mapped\n     temporarily inside this function if needed.\n   - You must not pass same #VmaAllocation object multiple times in `pAllocations` array.\n\n   The function also frees empty `VkDeviceMemory` blocks.\n\n   Warning: This function may be time-consuming, so you shouldn't call it too often\n   (like after every resource creation/destruction).\n   You can call it on special occasions (like when reloading a game level or\n   when you just destroyed a lot of objects). Calling it every frame may be OK, but\n   you should measure that on your platform.\n\n   For more information, see [Defragmentation](@ref defragmentation) chapter.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragment(\n      VmaAllocator allocator,\n      VmaAllocation* pAllocations,\n      size_t allocationCount,\n      VkBool32* pAllocationsChanged,\n      const VmaDefragmentationInfo* pDefragmentationInfo,\n      VmaDefragmentationStats* pDefragmentationStats);\n\n   /** \\brief Binds buffer to allocation.\n\n   Binds specified buffer to region of memory represented by specified allocation.\n   Gets `VkDeviceMemory` handle and offset from the allocation.\n   If you want to create a buffer, allocate memory for it and bind them together separately,\n   you should use this function for binding instead of standard `vkBindBufferMemory()`,\n   because it ensures proper synchronization so that when a `VkDeviceMemory` object is used by multiple\n   allocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from multiple threads simultaneously\n   (which is illegal in Vulkan).\n\n   It is recommended to use function vmaCreateBuffer() instead of this one.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory(\n      VmaAllocator allocator,\n      VmaAllocation allocation,\n      VkBuffer buffer);\n\n   /** \\brief Binds buffer to allocation with additional parameters.\n\n   @param allocationLocalOffset Additional offset to be added while binding, relative to the beginnig of the `allocation`. Normally it should be 0.\n   @param pNext A chain of structures to be attached to `VkBindBufferMemoryInfoKHR` structure used internally. Normally it should be null.\n\n   This function is similar to vmaBindBufferMemory(), but it provides additional parameters.\n\n   If `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag\n   or with VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_1`. Otherwise the call fails.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2(\n      VmaAllocator allocator,\n      VmaAllocation allocation,\n      VkDeviceSize allocationLocalOffset,\n      VkBuffer buffer,\n      const void* pNext);\n\n   /** \\brief Binds image to allocation.\n\n   Binds specified image to region of memory represented by specified allocation.\n   Gets `VkDeviceMemory` handle and offset from the allocation.\n   If you want to create an image, allocate memory for it and bind them together separately,\n   you should use this function for binding instead of standard `vkBindImageMemory()`,\n   because it ensures proper synchronization so that when a `VkDeviceMemory` object is used by multiple\n   allocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from multiple threads simultaneously\n   (which is illegal in Vulkan).\n\n   It is recommended to use function vmaCreateImage() instead of this one.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory(\n      VmaAllocator allocator,\n      VmaAllocation allocation,\n      VkImage image);\n\n   /** \\brief Binds image to allocation with additional parameters.\n\n   @param allocationLocalOffset Additional offset to be added while binding, relative to the beginnig of the `allocation`. Normally it should be 0.\n   @param pNext A chain of structures to be attached to `VkBindImageMemoryInfoKHR` structure used internally. Normally it should be null.\n\n   This function is similar to vmaBindImageMemory(), but it provides additional parameters.\n\n   If `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag\n   or with VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_1`. Otherwise the call fails.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2(\n      VmaAllocator allocator,\n      VmaAllocation allocation,\n      VkDeviceSize allocationLocalOffset,\n      VkImage image,\n      const void* pNext);\n\n   /**\n   @param[out] pBuffer Buffer that was created.\n   @param[out] pAllocation Allocation that was created.\n   @param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo().\n\n   This function automatically:\n\n   -# Creates buffer.\n   -# Allocates appropriate memory for it.\n   -# Binds the buffer with the memory.\n\n   If any of these operations fail, buffer and allocation are not created,\n   returned value is negative error code, *pBuffer and *pAllocation are null.\n\n   If the function succeeded, you must destroy both buffer and allocation when you\n   no longer need them using either convenience function vmaDestroyBuffer() or\n   separately, using `vkDestroyBuffer()` and vmaFreeMemory().\n\n   If VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag was used,\n   VK_KHR_dedicated_allocation extension is used internally to query driver whether\n   it requires or prefers the new buffer to have dedicated allocation. If yes,\n   and if dedicated allocation is possible (VmaAllocationCreateInfo::pool is null\n   and VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT is not used), it creates dedicated\n   allocation for this buffer, just like when using\n   VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.\n   */\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(\n      VmaAllocator allocator,\n      const VkBufferCreateInfo* pBufferCreateInfo,\n      const VmaAllocationCreateInfo* pAllocationCreateInfo,\n      VkBuffer* pBuffer,\n      VmaAllocation* pAllocation,\n      VmaAllocationInfo* pAllocationInfo);\n\n   /** \\brief Destroys Vulkan buffer and frees allocated memory.\n\n   This is just a convenience function equivalent to:\n\n   \\code\n   vkDestroyBuffer(device, buffer, allocationCallbacks);\n   vmaFreeMemory(allocator, allocation);\n   \\endcode\n\n   It it safe to pass null as buffer and/or allocation.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer(\n      VmaAllocator allocator,\n      VkBuffer buffer,\n      VmaAllocation allocation);\n\n   /// Function similar to vmaCreateBuffer().\n   VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage(\n      VmaAllocator allocator,\n      const VkImageCreateInfo* pImageCreateInfo,\n      const VmaAllocationCreateInfo* pAllocationCreateInfo,\n      VkImage* pImage,\n      VmaAllocation* pAllocation,\n      VmaAllocationInfo* pAllocationInfo);\n\n   /** \\brief Destroys Vulkan image and frees allocated memory.\n\n   This is just a convenience function equivalent to:\n\n   \\code\n   vkDestroyImage(device, image, allocationCallbacks);\n   vmaFreeMemory(allocator, allocation);\n   \\endcode\n\n   It it safe to pass null as image and/or allocation.\n   */\n   VMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage(\n      VmaAllocator allocator,\n      VkImage image,\n      VmaAllocation allocation);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // AMD_VULKAN_MEMORY_ALLOCATOR_H\n\n// For Visual Studio IntelliSense.\n#if defined(__cplusplus) && defined(__INTELLISENSE__)\n#define VMA_IMPLEMENTATION\n#endif\n\n#ifdef VMA_IMPLEMENTATION\n#undef VMA_IMPLEMENTATION\n\n#include <cstdint>\n#include <cstdlib>\n#include <cstring>\n\n/*******************************************************************************\nCONFIGURATION SECTION\n\nDefine some of these macros before each #include of this header or change them\nhere if you need other then default behavior depending on your environment.\n*/\n\n/*\nDefine this macro to 1 to make the library fetch pointers to Vulkan functions\ninternally, like:\n\n    vulkanFunctions.vkAllocateMemory = &vkAllocateMemory;\n\nDefine to 0 if you are going to provide you own pointers to Vulkan functions via\nVmaAllocatorCreateInfo::pVulkanFunctions.\n*/\n#if !defined(VMA_STATIC_VULKAN_FUNCTIONS) && !defined(VK_NO_PROTOTYPES)\n#define VMA_STATIC_VULKAN_FUNCTIONS 1\n#endif\n\n// Define this macro to 1 to make the library use STL containers instead of its own implementation.\n//#define VMA_USE_STL_CONTAINERS 1\n\n/* Set this macro to 1 to make the library including and using STL containers:\nstd::pair, std::vector, std::list, std::unordered_map.\n\nSet it to 0 or undefined to make the library using its own implementation of\nthe containers.\n*/\n#if VMA_USE_STL_CONTAINERS\n#define VMA_USE_STL_VECTOR 1\n#define VMA_USE_STL_UNORDERED_MAP 1\n#define VMA_USE_STL_LIST 1\n#endif\n\n#ifndef VMA_USE_STL_SHARED_MUTEX\n// Compiler conforms to C++17.\n#if __cplusplus >= 201703L\n#define VMA_USE_STL_SHARED_MUTEX 1\n// Visual studio defines __cplusplus properly only when passed additional parameter: /Zc:__cplusplus\n// Otherwise it's always 199711L, despite shared_mutex works since Visual Studio 2015 Update 2.\n// See: https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/\n#elif defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023918 && __cplusplus == 199711L && _MSVC_LANG >= 201703L\n#define VMA_USE_STL_SHARED_MUTEX 1\n#else\n#define VMA_USE_STL_SHARED_MUTEX 0\n#endif\n#endif\n\n/*\nTHESE INCLUDES ARE NOT ENABLED BY DEFAULT.\nLibrary has its own container implementation.\n*/\n#if VMA_USE_STL_VECTOR\n#include <vector>\n#endif\n\n#if VMA_USE_STL_UNORDERED_MAP\n#include <unordered_map>\n#endif\n\n#if VMA_USE_STL_LIST\n#include <list>\n#endif\n\n/*\nFollowing headers are used in this CONFIGURATION section only, so feel free to\nremove them if not needed.\n*/\n#include <cassert> // for assert\n#include <algorithm> // for min, max\n#include <mutex>\n\n#ifndef VMA_NULL\n// Value used as null pointer. Define it to e.g.: nullptr, NULL, 0, (void*)0.\n#define VMA_NULL   nullptr\n#endif\n\n#if defined(__ANDROID_API__) && (__ANDROID_API__ < 16)\n#include <cstdlib>\nvoid* aligned_alloc(size_t alignment, size_t size)\n{\n   // alignment must be >= sizeof(void*)\n   if (alignment < sizeof(void*)) {\n      alignment = sizeof(void*);\n   }\n\n   return memalign(alignment, size);\n}\n#elif defined(__APPLE__) || defined(__ANDROID__) || (defined(__linux__) && defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC))\n#include <cstdlib>\nvoid* aligned_alloc(size_t alignment, size_t size)\n{\n   // alignment must be >= sizeof(void*)\n   if (alignment < sizeof(void*)) {\n      alignment = sizeof(void*);\n   }\n\n   void* pointer;\n   if (posix_memalign(&pointer, alignment, size) == 0)\n      return pointer;\n   return VMA_NULL;\n}\n#endif\n\n// If your compiler is not compatible with C++11 and definition of\n// aligned_alloc() function is missing, uncommeting following line may help:\n\n//#include <malloc.h>\n\n// Normal assert to check for programmer's errors, especially in Debug configuration.\n#ifndef VMA_ASSERT\n#ifdef _DEBUG\n#define VMA_ASSERT(expr)         assert(expr)\n#else\n#define VMA_ASSERT(expr)\n#endif\n#endif\n\n// Assert that will be called very often, like inside data structures e.g. operator[].\n// Making it non-empty can make program slow.\n#ifndef VMA_HEAVY_ASSERT\n#ifdef _DEBUG\n#define VMA_HEAVY_ASSERT(expr)   //VMA_ASSERT(expr)\n#else\n#define VMA_HEAVY_ASSERT(expr)\n#endif\n#endif\n\n#ifndef VMA_ALIGN_OF\n#define VMA_ALIGN_OF(type)       (__alignof(type))\n#endif\n\n#ifndef VMA_SYSTEM_ALIGNED_MALLOC\n#if defined(_WIN32)\n#define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment)   (_aligned_malloc((size), (alignment)))\n#else\n#define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment)   (aligned_alloc((alignment), (size) ))\n#endif\n#endif\n\n#ifndef VMA_SYSTEM_FREE\n#if defined(_WIN32)\n#define VMA_SYSTEM_FREE(ptr)   _aligned_free(ptr)\n#else\n#define VMA_SYSTEM_FREE(ptr)   free(ptr)\n#endif\n#endif\n\n#ifndef VMA_MIN\n#define VMA_MIN(v1, v2)    (std::min((v1), (v2)))\n#endif\n\n#ifndef VMA_MAX\n#define VMA_MAX(v1, v2)    (std::max((v1), (v2)))\n#endif\n\n#ifndef VMA_SWAP\n#define VMA_SWAP(v1, v2)   std::swap((v1), (v2))\n#endif\n\n#ifndef VMA_SORT\n#define VMA_SORT(beg, end, cmp)  std::sort(beg, end, cmp)\n#endif\n\n#ifndef VMA_DEBUG_LOG\n#define VMA_DEBUG_LOG(format, ...)\n/*\n#define VMA_DEBUG_LOG(format, ...) do { \\\n    printf(format, __VA_ARGS__); \\\n    printf(\"\\n\"); \\\n} while(false)\n*/\n#endif\n\n// Define this macro to 1 to enable functions: vmaBuildStatsString, vmaFreeStatsString.\n#if VMA_STATS_STRING_ENABLED\nstatic inline void VmaUint32ToStr(char* outStr, size_t strLen, uint32_t num)\n{\n   snprintf(outStr, strLen, \"%u\", static_cast<unsigned int>(num));\n}\nstatic inline void VmaUint64ToStr(char* outStr, size_t strLen, uint64_t num)\n{\n   snprintf(outStr, strLen, \"%llu\", static_cast<unsigned long long>(num));\n}\nstatic inline void VmaPtrToStr(char* outStr, size_t strLen, const void* ptr)\n{\n   snprintf(outStr, strLen, \"%p\", ptr);\n}\n#endif\n\n#ifndef VMA_MUTEX\nclass VmaMutex\n{\npublic:\n   void Lock() { m_Mutex.lock(); }\n   void Unlock() { m_Mutex.unlock(); }\nprivate:\n   std::mutex m_Mutex;\n};\n#define VMA_MUTEX VmaMutex\n#endif\n\n// Read-write mutex, where \"read\" is shared access, \"write\" is exclusive access.\n#ifndef VMA_RW_MUTEX\n#if VMA_USE_STL_SHARED_MUTEX\n    // Use std::shared_mutex from C++17.\n#include <shared_mutex>\nclass VmaRWMutex\n{\npublic:\n   void LockRead() { m_Mutex.lock_shared(); }\n   void UnlockRead() { m_Mutex.unlock_shared(); }\n   void LockWrite() { m_Mutex.lock(); }\n   void UnlockWrite() { m_Mutex.unlock(); }\nprivate:\n   std::shared_mutex m_Mutex;\n};\n#define VMA_RW_MUTEX VmaRWMutex\n#elif defined(_WIN32) && defined(WINVER) && WINVER >= 0x0600\n    // Use SRWLOCK from WinAPI.\n    // Minimum supported client = Windows Vista, server = Windows Server 2008.\nclass VmaRWMutex\n{\npublic:\n   VmaRWMutex() { InitializeSRWLock(&m_Lock); }\n   void LockRead() { AcquireSRWLockShared(&m_Lock); }\n   void UnlockRead() { ReleaseSRWLockShared(&m_Lock); }\n   void LockWrite() { AcquireSRWLockExclusive(&m_Lock); }\n   void UnlockWrite() { ReleaseSRWLockExclusive(&m_Lock); }\nprivate:\n   SRWLOCK m_Lock;\n};\n#define VMA_RW_MUTEX VmaRWMutex\n#else\n    // Less efficient fallback: Use normal mutex.\nclass VmaRWMutex\n{\npublic:\n   void LockRead() { m_Mutex.Lock(); }\n   void UnlockRead() { m_Mutex.Unlock(); }\n   void LockWrite() { m_Mutex.Lock(); }\n   void UnlockWrite() { m_Mutex.Unlock(); }\nprivate:\n   VMA_MUTEX m_Mutex;\n};\n#define VMA_RW_MUTEX VmaRWMutex\n#endif // #if VMA_USE_STL_SHARED_MUTEX\n#endif // #ifndef VMA_RW_MUTEX\n\n/*\nIf providing your own implementation, you need to implement a subset of std::atomic.\n*/\n#ifndef VMA_ATOMIC_UINT32\n#include <atomic>\n#define VMA_ATOMIC_UINT32 std::atomic<uint32_t>\n#endif\n\n#ifndef VMA_ATOMIC_UINT64\n#include <atomic>\n#define VMA_ATOMIC_UINT64 std::atomic<uint64_t>\n#endif\n\n#ifndef VMA_DEBUG_ALWAYS_DEDICATED_MEMORY\n/**\nEvery allocation will have its own memory block.\nDefine to 1 for debugging purposes only.\n*/\n#define VMA_DEBUG_ALWAYS_DEDICATED_MEMORY (0)\n#endif\n\n#ifndef VMA_DEBUG_ALIGNMENT\n/**\nMinimum alignment of all allocations, in bytes.\nSet to more than 1 for debugging purposes only. Must be power of two.\n*/\n#define VMA_DEBUG_ALIGNMENT (1)\n#endif\n\n#ifndef VMA_DEBUG_MARGIN\n/**\nMinimum margin before and after every allocation, in bytes.\nSet nonzero for debugging purposes only.\n*/\n#define VMA_DEBUG_MARGIN (0)\n#endif\n\n#ifndef VMA_DEBUG_INITIALIZE_ALLOCATIONS\n/**\nDefine this macro to 1 to automatically fill new allocations and destroyed\nallocations with some bit pattern.\n*/\n#define VMA_DEBUG_INITIALIZE_ALLOCATIONS (0)\n#endif\n\n#ifndef VMA_DEBUG_DETECT_CORRUPTION\n/**\nDefine this macro to 1 together with non-zero value of VMA_DEBUG_MARGIN to\nenable writing magic value to the margin before and after every allocation and\nvalidating it, so that memory corruptions (out-of-bounds writes) are detected.\n*/\n#define VMA_DEBUG_DETECT_CORRUPTION (0)\n#endif\n\n#ifndef VMA_DEBUG_GLOBAL_MUTEX\n/**\nSet this to 1 for debugging purposes only, to enable single mutex protecting all\nentry calls to the library. Can be useful for debugging multithreading issues.\n*/\n#define VMA_DEBUG_GLOBAL_MUTEX (0)\n#endif\n\n#ifndef VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY\n/**\nMinimum value for VkPhysicalDeviceLimits::bufferImageGranularity.\nSet to more than 1 for debugging purposes only. Must be power of two.\n*/\n#define VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY (1)\n#endif\n\n#ifndef VMA_SMALL_HEAP_MAX_SIZE\n/// Maximum size of a memory heap in Vulkan to consider it \"small\".\n#define VMA_SMALL_HEAP_MAX_SIZE (1024ull * 1024 * 1024)\n#endif\n\n#ifndef VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE\n   /// Default size of a block allocated as single VkDeviceMemory from a \"large\" heap.\n#define VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE (256ull * 1024 * 1024)\n#endif\n\n#ifndef VMA_CLASS_NO_COPY\n#define VMA_CLASS_NO_COPY(className) \\\n        private: \\\n            className(const className&) = delete; \\\n            className& operator=(const className&) = delete;\n#endif\n\nstatic const uint32_t VMA_FRAME_INDEX_LOST = UINT32_MAX;\n\n// Decimal 2139416166, float NaN, little-endian binary 66 E6 84 7F.\nstatic const uint32_t VMA_CORRUPTION_DETECTION_MAGIC_VALUE = 0x7F84E666;\n\nstatic const uint8_t VMA_ALLOCATION_FILL_PATTERN_CREATED = 0xDC;\nstatic const uint8_t VMA_ALLOCATION_FILL_PATTERN_DESTROYED = 0xEF;\n\n/*******************************************************************************\nEND OF CONFIGURATION\n*/\n\nstatic const uint32_t VMA_ALLOCATION_INTERNAL_STRATEGY_MIN_OFFSET = 0x10000000u;\n\nstatic VkAllocationCallbacks VmaEmptyAllocationCallbacks = {\n    VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL };\n\n// Returns number of bits set to 1 in (v).\nstatic inline uint32_t VmaCountBitsSet(uint32_t v)\n{\n   uint32_t c = v - ((v >> 1) & 0x55555555);\n   c = ((c >> 2) & 0x33333333) + (c & 0x33333333);\n   c = ((c >> 4) + c) & 0x0F0F0F0F;\n   c = ((c >> 8) + c) & 0x00FF00FF;\n   c = ((c >> 16) + c) & 0x0000FFFF;\n   return c;\n}\n\n// Aligns given value up to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 16.\n// Use types like uint32_t, uint64_t as T.\ntemplate <typename T>\nstatic inline T VmaAlignUp(T val, T align)\n{\n   return (val + align - 1) / align * align;\n}\n// Aligns given value down to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 8.\n// Use types like uint32_t, uint64_t as T.\ntemplate <typename T>\nstatic inline T VmaAlignDown(T val, T align)\n{\n   return val / align * align;\n}\n\n// Division with mathematical rounding to nearest number.\ntemplate <typename T>\nstatic inline T VmaRoundDiv(T x, T y)\n{\n   return (x + (y / (T)2)) / y;\n}\n\n/*\nReturns true if given number is a power of two.\nT must be unsigned integer number or signed integer but always nonnegative.\nFor 0 returns true.\n*/\ntemplate <typename T>\ninline bool VmaIsPow2(T x)\n{\n   return (x & (x - 1)) == 0;\n}\n\n// Returns smallest power of 2 greater or equal to v.\nstatic inline uint32_t VmaNextPow2(uint32_t v)\n{\n   v--;\n   v |= v >> 1;\n   v |= v >> 2;\n   v |= v >> 4;\n   v |= v >> 8;\n   v |= v >> 16;\n   v++;\n   return v;\n}\nstatic inline uint64_t VmaNextPow2(uint64_t v)\n{\n   v--;\n   v |= v >> 1;\n   v |= v >> 2;\n   v |= v >> 4;\n   v |= v >> 8;\n   v |= v >> 16;\n   v |= v >> 32;\n   v++;\n   return v;\n}\n\n// Returns largest power of 2 less or equal to v.\nstatic inline uint32_t VmaPrevPow2(uint32_t v)\n{\n   v |= v >> 1;\n   v |= v >> 2;\n   v |= v >> 4;\n   v |= v >> 8;\n   v |= v >> 16;\n   v = v ^ (v >> 1);\n   return v;\n}\nstatic inline uint64_t VmaPrevPow2(uint64_t v)\n{\n   v |= v >> 1;\n   v |= v >> 2;\n   v |= v >> 4;\n   v |= v >> 8;\n   v |= v >> 16;\n   v |= v >> 32;\n   v = v ^ (v >> 1);\n   return v;\n}\n\nstatic inline bool VmaStrIsEmpty(const char* pStr)\n{\n   return pStr == VMA_NULL || *pStr == '\\0';\n}\n\n#if VMA_STATS_STRING_ENABLED\n\nstatic const char* VmaAlgorithmToStr(uint32_t algorithm)\n{\n   switch (algorithm) {\n   case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT:\n      return \"Linear\";\n   case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT:\n      return \"Buddy\";\n   case 0:\n      return \"Default\";\n   default:\n      VMA_ASSERT(0);\n      return \"\";\n   }\n}\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\n#ifndef VMA_SORT\n\ntemplate<typename Iterator, typename Compare>\nIterator VmaQuickSortPartition(Iterator beg, Iterator end, Compare cmp)\n{\n   Iterator centerValue = end; --centerValue;\n   Iterator insertIndex = beg;\n   for (Iterator memTypeIndex = beg; memTypeIndex < centerValue; ++memTypeIndex) {\n      if (cmp(*memTypeIndex, *centerValue)) {\n         if (insertIndex != memTypeIndex) {\n            VMA_SWAP(*memTypeIndex, *insertIndex);\n         }\n         ++insertIndex;\n      }\n   }\n   if (insertIndex != centerValue) {\n      VMA_SWAP(*insertIndex, *centerValue);\n   }\n   return insertIndex;\n}\n\ntemplate<typename Iterator, typename Compare>\nvoid VmaQuickSort(Iterator beg, Iterator end, Compare cmp)\n{\n   if (beg < end) {\n      Iterator it = VmaQuickSortPartition<Iterator, Compare>(beg, end, cmp);\n      VmaQuickSort<Iterator, Compare>(beg, it, cmp);\n      VmaQuickSort<Iterator, Compare>(it + 1, end, cmp);\n   }\n}\n\n#define VMA_SORT(beg, end, cmp) VmaQuickSort(beg, end, cmp)\n\n#endif // #ifndef VMA_SORT\n\n/*\nReturns true if two memory blocks occupy overlapping pages.\nResourceA must be in less memory offset than ResourceB.\n\nAlgorithm is based on \"Vulkan 1.0.39 - A Specification (with all registered Vulkan extensions)\"\nchapter 11.6 \"Resource Memory Association\", paragraph \"Buffer-Image Granularity\".\n*/\nstatic inline bool VmaBlocksOnSamePage(\n   VkDeviceSize resourceAOffset,\n   VkDeviceSize resourceASize,\n   VkDeviceSize resourceBOffset,\n   VkDeviceSize pageSize)\n{\n   VMA_ASSERT(resourceAOffset + resourceASize <= resourceBOffset && resourceASize > 0 && pageSize > 0);\n   VkDeviceSize resourceAEnd = resourceAOffset + resourceASize - 1;\n   VkDeviceSize resourceAEndPage = resourceAEnd & ~(pageSize - 1);\n   VkDeviceSize resourceBStart = resourceBOffset;\n   VkDeviceSize resourceBStartPage = resourceBStart & ~(pageSize - 1);\n   return resourceAEndPage == resourceBStartPage;\n}\n\nenum VmaSuballocationType\n{\n   VMA_SUBALLOCATION_TYPE_FREE = 0,\n   VMA_SUBALLOCATION_TYPE_UNKNOWN = 1,\n   VMA_SUBALLOCATION_TYPE_BUFFER = 2,\n   VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN = 3,\n   VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR = 4,\n   VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL = 5,\n   VMA_SUBALLOCATION_TYPE_MAX_ENUM = 0x7FFFFFFF\n};\n\n/*\nReturns true if given suballocation types could conflict and must respect\nVkPhysicalDeviceLimits::bufferImageGranularity. They conflict if one is buffer\nor linear image and another one is optimal image. If type is unknown, behave\nconservatively.\n*/\nstatic inline bool VmaIsBufferImageGranularityConflict(\n   VmaSuballocationType suballocType1,\n   VmaSuballocationType suballocType2)\n{\n   if (suballocType1 > suballocType2) {\n      VMA_SWAP(suballocType1, suballocType2);\n   }\n\n   switch (suballocType1) {\n   case VMA_SUBALLOCATION_TYPE_FREE:\n      return false;\n   case VMA_SUBALLOCATION_TYPE_UNKNOWN:\n      return true;\n   case VMA_SUBALLOCATION_TYPE_BUFFER:\n      return\n         suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN ||\n         suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL;\n   case VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN:\n      return\n         suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN ||\n         suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR ||\n         suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL;\n   case VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR:\n      return\n         suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL;\n   case VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL:\n      return false;\n   default:\n      VMA_ASSERT(0);\n      return true;\n   }\n}\n\nstatic void VmaWriteMagicValue(void* pData, VkDeviceSize offset)\n{\n#if VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_DETECT_CORRUPTION\n   uint32_t* pDst = (uint32_t*)((char*)pData + offset);\n   const size_t numberCount = VMA_DEBUG_MARGIN / sizeof(uint32_t);\n   for (size_t i = 0; i < numberCount; ++i, ++pDst) {\n      *pDst = VMA_CORRUPTION_DETECTION_MAGIC_VALUE;\n   }\n#else\n   // no-op\n#endif\n}\n\nstatic bool VmaValidateMagicValue(const void* pData, VkDeviceSize offset)\n{\n#if VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_DETECT_CORRUPTION\n   const uint32_t* pSrc = (const uint32_t*)((const char*)pData + offset);\n   const size_t numberCount = VMA_DEBUG_MARGIN / sizeof(uint32_t);\n   for (size_t i = 0; i < numberCount; ++i, ++pSrc) {\n      if (*pSrc != VMA_CORRUPTION_DETECTION_MAGIC_VALUE) {\n         return false;\n      }\n   }\n#endif\n   return true;\n}\n\n/*\nFills structure with parameters of an example buffer to be used for transfers\nduring GPU memory defragmentation.\n*/\nstatic void VmaFillGpuDefragmentationBufferCreateInfo(VkBufferCreateInfo& outBufCreateInfo)\n{\n   memset(&outBufCreateInfo, 0, sizeof(outBufCreateInfo));\n   outBufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;\n   outBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;\n   outBufCreateInfo.size = (VkDeviceSize)VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE; // Example size.\n}\n\n// Helper RAII class to lock a mutex in constructor and unlock it in destructor (at the end of scope).\nstruct VmaMutexLock\n{\n   VMA_CLASS_NO_COPY(VmaMutexLock)\npublic:\n   VmaMutexLock(VMA_MUTEX& mutex, bool useMutex = true) :\n      m_pMutex(useMutex ? &mutex : VMA_NULL)\n   {\n      if (m_pMutex) { m_pMutex->Lock(); }\n   }\n   ~VmaMutexLock()\n   {\n      if (m_pMutex) { m_pMutex->Unlock(); }\n   }\nprivate:\n   VMA_MUTEX* m_pMutex;\n};\n\n// Helper RAII class to lock a RW mutex in constructor and unlock it in destructor (at the end of scope), for reading.\nstruct VmaMutexLockRead\n{\n   VMA_CLASS_NO_COPY(VmaMutexLockRead)\npublic:\n   VmaMutexLockRead(VMA_RW_MUTEX& mutex, bool useMutex) :\n      m_pMutex(useMutex ? &mutex : VMA_NULL)\n   {\n      if (m_pMutex) { m_pMutex->LockRead(); }\n   }\n   ~VmaMutexLockRead() { if (m_pMutex) { m_pMutex->UnlockRead(); } }\nprivate:\n   VMA_RW_MUTEX* m_pMutex;\n};\n\n// Helper RAII class to lock a RW mutex in constructor and unlock it in destructor (at the end of scope), for writing.\nstruct VmaMutexLockWrite\n{\n   VMA_CLASS_NO_COPY(VmaMutexLockWrite)\npublic:\n   VmaMutexLockWrite(VMA_RW_MUTEX& mutex, bool useMutex) :\n      m_pMutex(useMutex ? &mutex : VMA_NULL)\n   {\n      if (m_pMutex) { m_pMutex->LockWrite(); }\n   }\n   ~VmaMutexLockWrite() { if (m_pMutex) { m_pMutex->UnlockWrite(); } }\nprivate:\n   VMA_RW_MUTEX* m_pMutex;\n};\n\n#if VMA_DEBUG_GLOBAL_MUTEX\nstatic VMA_MUTEX gDebugGlobalMutex;\n#define VMA_DEBUG_GLOBAL_MUTEX_LOCK VmaMutexLock debugGlobalMutexLock(gDebugGlobalMutex, true);\n#else\n#define VMA_DEBUG_GLOBAL_MUTEX_LOCK\n#endif\n\n// Minimum size of a free suballocation to register it in the free suballocation collection.\nstatic const VkDeviceSize VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER = 16;\n\n/*\nPerforms binary search and returns iterator to first element that is greater or\nequal to (key), according to comparison (cmp).\n\nCmp should return true if first argument is less than second argument.\n\nReturned value is the found element, if present in the collection or place where\nnew element with value (key) should be inserted.\n*/\ntemplate <typename CmpLess, typename IterT, typename KeyT>\nstatic IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp)\n{\n   size_t down = 0, up = (end - beg);\n   while (down < up) {\n      const size_t mid = (down + up) / 2;\n      if (cmp(*(beg + mid), key)) {\n         down = mid + 1;\n      } else {\n         up = mid;\n      }\n   }\n   return beg + down;\n}\n\ntemplate<typename CmpLess, typename IterT, typename KeyT>\nIterT VmaBinaryFindSorted(const IterT& beg, const IterT& end, const KeyT& value, const CmpLess& cmp)\n{\n   IterT it = VmaBinaryFindFirstNotLess<CmpLess, IterT, KeyT>(\n      beg, end, value, cmp);\n   if (it == end ||\n      (!cmp(*it, value) && !cmp(value, *it))) {\n      return it;\n   }\n   return end;\n}\n\n/*\nReturns true if all pointers in the array are not-null and unique.\nWarning! O(n^2) complexity. Use only inside VMA_HEAVY_ASSERT.\nT must be pointer type, e.g. VmaAllocation, VmaPool.\n*/\ntemplate<typename T>\nstatic bool VmaValidatePointerArray(uint32_t count, const T* arr)\n{\n   for (uint32_t i = 0; i < count; ++i) {\n      const T iPtr = arr[i];\n      if (iPtr == VMA_NULL) {\n         return false;\n      }\n      for (uint32_t j = i + 1; j < count; ++j) {\n         if (iPtr == arr[j]) {\n            return false;\n         }\n      }\n   }\n   return true;\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Memory allocation\n\nstatic void* VmaMalloc(const VkAllocationCallbacks* pAllocationCallbacks, size_t size, size_t alignment)\n{\n   if ((pAllocationCallbacks != VMA_NULL) &&\n      (pAllocationCallbacks->pfnAllocation != VMA_NULL)) {\n      return (*pAllocationCallbacks->pfnAllocation)(\n         pAllocationCallbacks->pUserData,\n         size,\n         alignment,\n         VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);\n   } else {\n      return VMA_SYSTEM_ALIGNED_MALLOC(size, alignment);\n   }\n}\n\nstatic void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr)\n{\n   if ((pAllocationCallbacks != VMA_NULL) &&\n      (pAllocationCallbacks->pfnFree != VMA_NULL)) {\n      (*pAllocationCallbacks->pfnFree)(pAllocationCallbacks->pUserData, ptr);\n   } else {\n      VMA_SYSTEM_FREE(ptr);\n   }\n}\n\ntemplate<typename T>\nstatic T* VmaAllocate(const VkAllocationCallbacks* pAllocationCallbacks)\n{\n   return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T), VMA_ALIGN_OF(T));\n}\n\ntemplate<typename T>\nstatic T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, size_t count)\n{\n   return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T) * count, VMA_ALIGN_OF(T));\n}\n\n#define vma_new(allocator, type)   new(VmaAllocate<type>(allocator))(type)\n\n#define vma_new_array(allocator, type, count)   new(VmaAllocateArray<type>((allocator), (count)))(type)\n\ntemplate<typename T>\nstatic void vma_delete(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr)\n{\n   ptr->~T();\n   VmaFree(pAllocationCallbacks, ptr);\n}\n\ntemplate<typename T>\nstatic void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr, size_t count)\n{\n   if (ptr != VMA_NULL) {\n      for (size_t i = count; i--; ) {\n         ptr[i].~T();\n      }\n      VmaFree(pAllocationCallbacks, ptr);\n   }\n}\n\nstatic char* VmaCreateStringCopy(const VkAllocationCallbacks* allocs, const char* srcStr)\n{\n   if (srcStr != VMA_NULL) {\n      const size_t len = strlen(srcStr);\n      char* const result = vma_new_array(allocs, char, len + 1);\n      memcpy(result, srcStr, len + 1);\n      return result;\n   } else {\n      return VMA_NULL;\n   }\n}\n\nstatic void VmaFreeString(const VkAllocationCallbacks* allocs, char* str)\n{\n   if (str != VMA_NULL) {\n      const size_t len = strlen(str);\n      vma_delete_array(allocs, str, len + 1);\n   }\n}\n\n// STL-compatible allocator.\ntemplate<typename T>\nclass VmaStlAllocator\n{\npublic:\n   const VkAllocationCallbacks* const m_pCallbacks;\n   typedef T value_type;\n\n   VmaStlAllocator(const VkAllocationCallbacks* pCallbacks) : m_pCallbacks(pCallbacks) { }\n   template<typename U> VmaStlAllocator(const VmaStlAllocator<U>& src) : m_pCallbacks(src.m_pCallbacks) { }\n\n   T* allocate(size_t n) { return VmaAllocateArray<T>(m_pCallbacks, n); }\n   void deallocate(T* p, size_t n) { VmaFree(m_pCallbacks, p); }\n\n   template<typename U>\n   bool operator==(const VmaStlAllocator<U>& rhs) const\n   {\n      return m_pCallbacks == rhs.m_pCallbacks;\n   }\n   template<typename U>\n   bool operator!=(const VmaStlAllocator<U>& rhs) const\n   {\n      return m_pCallbacks != rhs.m_pCallbacks;\n   }\n\n   VmaStlAllocator& operator=(const VmaStlAllocator& x) = delete;\n};\n\n#if VMA_USE_STL_VECTOR\n\n#define VmaVector std::vector\n\ntemplate<typename T, typename allocatorT>\nstatic void VmaVectorInsert(std::vector<T, allocatorT>& vec, size_t index, const T& item)\n{\n   vec.insert(vec.begin() + index, item);\n}\n\ntemplate<typename T, typename allocatorT>\nstatic void VmaVectorRemove(std::vector<T, allocatorT>& vec, size_t index)\n{\n   vec.erase(vec.begin() + index);\n}\n\n#else // #if VMA_USE_STL_VECTOR\n\n/* Class with interface compatible with subset of std::vector.\nT must be POD because constructors and destructors are not called and memcpy is\nused for these objects. */\ntemplate<typename T, typename AllocatorT>\nclass VmaVector\n{\npublic:\n   typedef T value_type;\n\n   VmaVector(const AllocatorT& allocator) :\n      m_Allocator(allocator),\n      m_pArray(VMA_NULL),\n      m_Count(0),\n      m_Capacity(0)\n   {\n   }\n\n   VmaVector(size_t count, const AllocatorT& allocator) :\n      m_Allocator(allocator),\n      m_pArray(count ? (T*)VmaAllocateArray<T>(allocator.m_pCallbacks, count) : VMA_NULL),\n      m_Count(count),\n      m_Capacity(count)\n   {\n   }\n\n   // This version of the constructor is here for compatibility with pre-C++14 std::vector.\n   // value is unused.\n   VmaVector(size_t count, const T& value, const AllocatorT& allocator)\n      : VmaVector(count, allocator)\n   {\n   }\n\n   VmaVector(const VmaVector<T, AllocatorT>& src) :\n      m_Allocator(src.m_Allocator),\n      m_pArray(src.m_Count ? (T*)VmaAllocateArray<T>(src.m_Allocator.m_pCallbacks, src.m_Count) : VMA_NULL),\n      m_Count(src.m_Count),\n      m_Capacity(src.m_Count)\n   {\n      if (m_Count != 0) {\n         memcpy(m_pArray, src.m_pArray, m_Count * sizeof(T));\n      }\n   }\n\n   ~VmaVector()\n   {\n      VmaFree(m_Allocator.m_pCallbacks, m_pArray);\n   }\n\n   VmaVector& operator=(const VmaVector<T, AllocatorT>& rhs)\n   {\n      if (&rhs != this) {\n         resize(rhs.m_Count);\n         if (m_Count != 0) {\n            memcpy(m_pArray, rhs.m_pArray, m_Count * sizeof(T));\n         }\n      }\n      return *this;\n   }\n\n   bool empty() const { return m_Count == 0; }\n   size_t size() const { return m_Count; }\n   T* data() { return m_pArray; }\n   const T* data() const { return m_pArray; }\n\n   T& operator[](size_t index)\n   {\n      VMA_HEAVY_ASSERT(index < m_Count);\n      return m_pArray[index];\n   }\n   const T& operator[](size_t index) const\n   {\n      VMA_HEAVY_ASSERT(index < m_Count);\n      return m_pArray[index];\n   }\n\n   T& front()\n   {\n      VMA_HEAVY_ASSERT(m_Count > 0);\n      return m_pArray[0];\n   }\n   const T& front() const\n   {\n      VMA_HEAVY_ASSERT(m_Count > 0);\n      return m_pArray[0];\n   }\n   T& back()\n   {\n      VMA_HEAVY_ASSERT(m_Count > 0);\n      return m_pArray[m_Count - 1];\n   }\n   const T& back() const\n   {\n      VMA_HEAVY_ASSERT(m_Count > 0);\n      return m_pArray[m_Count - 1];\n   }\n\n   void reserve(size_t newCapacity, bool freeMemory = false)\n   {\n      newCapacity = VMA_MAX(newCapacity, m_Count);\n\n      if ((newCapacity < m_Capacity) && !freeMemory) {\n         newCapacity = m_Capacity;\n      }\n\n      if (newCapacity != m_Capacity) {\n         T* const newArray = newCapacity ? VmaAllocateArray<T>(m_Allocator, newCapacity) : VMA_NULL;\n         if (m_Count != 0) {\n            memcpy(newArray, m_pArray, m_Count * sizeof(T));\n         }\n         VmaFree(m_Allocator.m_pCallbacks, m_pArray);\n         m_Capacity = newCapacity;\n         m_pArray = newArray;\n      }\n   }\n\n   void resize(size_t newCount, bool freeMemory = false)\n   {\n      size_t newCapacity = m_Capacity;\n      if (newCount > m_Capacity) {\n         newCapacity = VMA_MAX(newCount, VMA_MAX(m_Capacity * 3 / 2, (size_t)8));\n      } else if (freeMemory) {\n         newCapacity = newCount;\n      }\n\n      if (newCapacity != m_Capacity) {\n         T* const newArray = newCapacity ? VmaAllocateArray<T>(m_Allocator.m_pCallbacks, newCapacity) : VMA_NULL;\n         const size_t elementsToCopy = VMA_MIN(m_Count, newCount);\n         if (elementsToCopy != 0) {\n            memcpy(newArray, m_pArray, elementsToCopy * sizeof(T));\n         }\n         VmaFree(m_Allocator.m_pCallbacks, m_pArray);\n         m_Capacity = newCapacity;\n         m_pArray = newArray;\n      }\n\n      m_Count = newCount;\n   }\n\n   void clear(bool freeMemory = false)\n   {\n      resize(0, freeMemory);\n   }\n\n   void insert(size_t index, const T& src)\n   {\n      VMA_HEAVY_ASSERT(index <= m_Count);\n      const size_t oldCount = size();\n      resize(oldCount + 1);\n      if (index < oldCount) {\n         memmove(m_pArray + (index + 1), m_pArray + index, (oldCount - index) * sizeof(T));\n      }\n      m_pArray[index] = src;\n   }\n\n   void remove(size_t index)\n   {\n      VMA_HEAVY_ASSERT(index < m_Count);\n      const size_t oldCount = size();\n      if (index < oldCount - 1) {\n         memmove(m_pArray + index, m_pArray + (index + 1), (oldCount - index - 1) * sizeof(T));\n      }\n      resize(oldCount - 1);\n   }\n\n   void push_back(const T& src)\n   {\n      const size_t newIndex = size();\n      resize(newIndex + 1);\n      m_pArray[newIndex] = src;\n   }\n\n   void pop_back()\n   {\n      VMA_HEAVY_ASSERT(m_Count > 0);\n      resize(size() - 1);\n   }\n\n   void push_front(const T& src)\n   {\n      insert(0, src);\n   }\n\n   void pop_front()\n   {\n      VMA_HEAVY_ASSERT(m_Count > 0);\n      remove(0);\n   }\n\n   typedef T* iterator;\n\n   iterator begin() { return m_pArray; }\n   iterator end() { return m_pArray + m_Count; }\n\nprivate:\n   AllocatorT m_Allocator;\n   T* m_pArray;\n   size_t m_Count;\n   size_t m_Capacity;\n};\n\ntemplate<typename T, typename allocatorT>\nstatic void VmaVectorInsert(VmaVector<T, allocatorT>& vec, size_t index, const T& item)\n{\n   vec.insert(index, item);\n}\n\ntemplate<typename T, typename allocatorT>\nstatic void VmaVectorRemove(VmaVector<T, allocatorT>& vec, size_t index)\n{\n   vec.remove(index);\n}\n\n#endif // #if VMA_USE_STL_VECTOR\n\ntemplate<typename CmpLess, typename VectorT>\nsize_t VmaVectorInsertSorted(VectorT& vector, const typename VectorT::value_type& value)\n{\n   const size_t indexToInsert = VmaBinaryFindFirstNotLess(\n      vector.data(),\n      vector.data() + vector.size(),\n      value,\n      CmpLess()) - vector.data();\n   VmaVectorInsert(vector, indexToInsert, value);\n   return indexToInsert;\n}\n\ntemplate<typename CmpLess, typename VectorT>\nbool VmaVectorRemoveSorted(VectorT& vector, const typename VectorT::value_type& value)\n{\n   CmpLess comparator;\n   typename VectorT::iterator it = VmaBinaryFindFirstNotLess(\n      vector.begin(),\n      vector.end(),\n      value,\n      comparator);\n   if ((it != vector.end()) && !comparator(*it, value) && !comparator(value, *it)) {\n      size_t indexToRemove = it - vector.begin();\n      VmaVectorRemove(vector, indexToRemove);\n      return true;\n   }\n   return false;\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// class VmaPoolAllocator\n\n/*\nAllocator for objects of type T using a list of arrays (pools) to speed up\nallocation. Number of elements that can be allocated is not bounded because\nallocator can create multiple blocks.\n*/\ntemplate<typename T>\nclass VmaPoolAllocator\n{\n   VMA_CLASS_NO_COPY(VmaPoolAllocator)\npublic:\n   VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCallbacks, uint32_t firstBlockCapacity);\n   ~VmaPoolAllocator();\n   T* Alloc();\n   void Free(T* ptr);\n\nprivate:\n   union Item\n   {\n      uint32_t NextFreeIndex;\n      alignas(T) char Value[sizeof(T)];\n   };\n\n   struct ItemBlock\n   {\n      Item* pItems;\n      uint32_t Capacity;\n      uint32_t FirstFreeIndex;\n   };\n\n   const VkAllocationCallbacks* m_pAllocationCallbacks;\n   const uint32_t m_FirstBlockCapacity;\n   VmaVector< ItemBlock, VmaStlAllocator<ItemBlock> > m_ItemBlocks;\n\n   ItemBlock& CreateNewBlock();\n};\n\ntemplate<typename T>\nVmaPoolAllocator<T>::VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCallbacks, uint32_t firstBlockCapacity) :\n   m_pAllocationCallbacks(pAllocationCallbacks),\n   m_FirstBlockCapacity(firstBlockCapacity),\n   m_ItemBlocks(VmaStlAllocator<ItemBlock>(pAllocationCallbacks))\n{\n   VMA_ASSERT(m_FirstBlockCapacity > 1);\n}\n\ntemplate<typename T>\nVmaPoolAllocator<T>::~VmaPoolAllocator()\n{\n   for (size_t i = m_ItemBlocks.size(); i--; )\n      vma_delete_array(m_pAllocationCallbacks, m_ItemBlocks[i].pItems, m_ItemBlocks[i].Capacity);\n   m_ItemBlocks.clear();\n}\n\ntemplate<typename T>\nT* VmaPoolAllocator<T>::Alloc()\n{\n   for (size_t i = m_ItemBlocks.size(); i--; ) {\n      ItemBlock& block = m_ItemBlocks[i];\n      // This block has some free items: Use first one.\n      if (block.FirstFreeIndex != UINT32_MAX) {\n         Item* const pItem = &block.pItems[block.FirstFreeIndex];\n         block.FirstFreeIndex = pItem->NextFreeIndex;\n         T* result = (T*)&pItem->Value;\n         new(result)T(); // Explicit constructor call.\n         return result;\n      }\n   }\n\n   // No block has free item: Create new one and use it.\n   ItemBlock& newBlock = CreateNewBlock();\n   Item* const pItem = &newBlock.pItems[0];\n   newBlock.FirstFreeIndex = pItem->NextFreeIndex;\n   T* result = (T*)&pItem->Value;\n   new(result)T(); // Explicit constructor call.\n   return result;\n}\n\ntemplate<typename T>\nvoid VmaPoolAllocator<T>::Free(T* ptr)\n{\n   // Search all memory blocks to find ptr.\n   for (size_t i = m_ItemBlocks.size(); i--; ) {\n      ItemBlock& block = m_ItemBlocks[i];\n\n      // Casting to union.\n      Item* pItemPtr;\n      memcpy(&pItemPtr, &ptr, sizeof(pItemPtr));\n\n      // Check if pItemPtr is in address range of this block.\n      if ((pItemPtr >= block.pItems) && (pItemPtr < block.pItems + block.Capacity)) {\n         ptr->~T(); // Explicit destructor call.\n         const uint32_t index = static_cast<uint32_t>(pItemPtr - block.pItems);\n         pItemPtr->NextFreeIndex = block.FirstFreeIndex;\n         block.FirstFreeIndex = index;\n         return;\n      }\n   }\n   VMA_ASSERT(0 && \"Pointer doesn't belong to this memory pool.\");\n}\n\ntemplate<typename T>\ntypename VmaPoolAllocator<T>::ItemBlock& VmaPoolAllocator<T>::CreateNewBlock()\n{\n   const uint32_t newBlockCapacity = m_ItemBlocks.empty() ?\n      m_FirstBlockCapacity : m_ItemBlocks.back().Capacity * 3 / 2;\n\n   const ItemBlock newBlock = {\n       vma_new_array(m_pAllocationCallbacks, Item, newBlockCapacity),\n       newBlockCapacity,\n       0 };\n\n   m_ItemBlocks.push_back(newBlock);\n\n   // Setup singly-linked list of all free items in this block.\n   for (uint32_t i = 0; i < newBlockCapacity - 1; ++i)\n      newBlock.pItems[i].NextFreeIndex = i + 1;\n   newBlock.pItems[newBlockCapacity - 1].NextFreeIndex = UINT32_MAX;\n   return m_ItemBlocks.back();\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// class VmaRawList, VmaList\n\n#if VMA_USE_STL_LIST\n\n#define VmaList std::list\n\n#else // #if VMA_USE_STL_LIST\n\ntemplate<typename T>\nstruct VmaListItem\n{\n   VmaListItem* pPrev;\n   VmaListItem* pNext;\n   T Value;\n};\n\n// Doubly linked list.\ntemplate<typename T>\nclass VmaRawList\n{\n   VMA_CLASS_NO_COPY(VmaRawList)\npublic:\n   typedef VmaListItem<T> ItemType;\n\n   VmaRawList(const VkAllocationCallbacks* pAllocationCallbacks);\n   ~VmaRawList();\n   void Clear();\n\n   size_t GetCount() const { return m_Count; }\n   bool IsEmpty() const { return m_Count == 0; }\n\n   ItemType* Front() { return m_pFront; }\n   const ItemType* Front() const { return m_pFront; }\n   ItemType* Back() { return m_pBack; }\n   const ItemType* Back() const { return m_pBack; }\n\n   ItemType* PushBack();\n   ItemType* PushFront();\n   ItemType* PushBack(const T& value);\n   ItemType* PushFront(const T& value);\n   void PopBack();\n   void PopFront();\n\n   // Item can be null - it means PushBack.\n   ItemType* InsertBefore(ItemType* pItem);\n   // Item can be null - it means PushFront.\n   ItemType* InsertAfter(ItemType* pItem);\n\n   ItemType* InsertBefore(ItemType* pItem, const T& value);\n   ItemType* InsertAfter(ItemType* pItem, const T& value);\n\n   void Remove(ItemType* pItem);\n\nprivate:\n   const VkAllocationCallbacks* const m_pAllocationCallbacks;\n   VmaPoolAllocator<ItemType> m_ItemAllocator;\n   ItemType* m_pFront;\n   ItemType* m_pBack;\n   size_t m_Count;\n};\n\ntemplate<typename T>\nVmaRawList<T>::VmaRawList(const VkAllocationCallbacks* pAllocationCallbacks) :\n   m_pAllocationCallbacks(pAllocationCallbacks),\n   m_ItemAllocator(pAllocationCallbacks, 128),\n   m_pFront(VMA_NULL),\n   m_pBack(VMA_NULL),\n   m_Count(0)\n{\n}\n\ntemplate<typename T>\nVmaRawList<T>::~VmaRawList()\n{\n   // Intentionally not calling Clear, because that would be unnecessary\n   // computations to return all items to m_ItemAllocator as free.\n}\n\ntemplate<typename T>\nvoid VmaRawList<T>::Clear()\n{\n   if (IsEmpty() == false) {\n      ItemType* pItem = m_pBack;\n      while (pItem != VMA_NULL) {\n         ItemType* const pPrevItem = pItem->pPrev;\n         m_ItemAllocator.Free(pItem);\n         pItem = pPrevItem;\n      }\n      m_pFront = VMA_NULL;\n      m_pBack = VMA_NULL;\n      m_Count = 0;\n   }\n}\n\ntemplate<typename T>\nVmaListItem<T>* VmaRawList<T>::PushBack()\n{\n   ItemType* const pNewItem = m_ItemAllocator.Alloc();\n   pNewItem->pNext = VMA_NULL;\n   if (IsEmpty()) {\n      pNewItem->pPrev = VMA_NULL;\n      m_pFront = pNewItem;\n      m_pBack = pNewItem;\n      m_Count = 1;\n   } else {\n      pNewItem->pPrev = m_pBack;\n      m_pBack->pNext = pNewItem;\n      m_pBack = pNewItem;\n      ++m_Count;\n   }\n   return pNewItem;\n}\n\ntemplate<typename T>\nVmaListItem<T>* VmaRawList<T>::PushFront()\n{\n   ItemType* const pNewItem = m_ItemAllocator.Alloc();\n   pNewItem->pPrev = VMA_NULL;\n   if (IsEmpty()) {\n      pNewItem->pNext = VMA_NULL;\n      m_pFront = pNewItem;\n      m_pBack = pNewItem;\n      m_Count = 1;\n   } else {\n      pNewItem->pNext = m_pFront;\n      m_pFront->pPrev = pNewItem;\n      m_pFront = pNewItem;\n      ++m_Count;\n   }\n   return pNewItem;\n}\n\ntemplate<typename T>\nVmaListItem<T>* VmaRawList<T>::PushBack(const T& value)\n{\n   ItemType* const pNewItem = PushBack();\n   pNewItem->Value = value;\n   return pNewItem;\n}\n\ntemplate<typename T>\nVmaListItem<T>* VmaRawList<T>::PushFront(const T& value)\n{\n   ItemType* const pNewItem = PushFront();\n   pNewItem->Value = value;\n   return pNewItem;\n}\n\ntemplate<typename T>\nvoid VmaRawList<T>::PopBack()\n{\n   VMA_HEAVY_ASSERT(m_Count > 0);\n   ItemType* const pBackItem = m_pBack;\n   ItemType* const pPrevItem = pBackItem->pPrev;\n   if (pPrevItem != VMA_NULL) {\n      pPrevItem->pNext = VMA_NULL;\n   }\n   m_pBack = pPrevItem;\n   m_ItemAllocator.Free(pBackItem);\n   --m_Count;\n}\n\ntemplate<typename T>\nvoid VmaRawList<T>::PopFront()\n{\n   VMA_HEAVY_ASSERT(m_Count > 0);\n   ItemType* const pFrontItem = m_pFront;\n   ItemType* const pNextItem = pFrontItem->pNext;\n   if (pNextItem != VMA_NULL) {\n      pNextItem->pPrev = VMA_NULL;\n   }\n   m_pFront = pNextItem;\n   m_ItemAllocator.Free(pFrontItem);\n   --m_Count;\n}\n\ntemplate<typename T>\nvoid VmaRawList<T>::Remove(ItemType* pItem)\n{\n   VMA_HEAVY_ASSERT(pItem != VMA_NULL);\n   VMA_HEAVY_ASSERT(m_Count > 0);\n\n   if (pItem->pPrev != VMA_NULL) {\n      pItem->pPrev->pNext = pItem->pNext;\n   } else {\n      VMA_HEAVY_ASSERT(m_pFront == pItem);\n      m_pFront = pItem->pNext;\n   }\n\n   if (pItem->pNext != VMA_NULL) {\n      pItem->pNext->pPrev = pItem->pPrev;\n   } else {\n      VMA_HEAVY_ASSERT(m_pBack == pItem);\n      m_pBack = pItem->pPrev;\n   }\n\n   m_ItemAllocator.Free(pItem);\n   --m_Count;\n}\n\ntemplate<typename T>\nVmaListItem<T>* VmaRawList<T>::InsertBefore(ItemType* pItem)\n{\n   if (pItem != VMA_NULL) {\n      ItemType* const prevItem = pItem->pPrev;\n      ItemType* const newItem = m_ItemAllocator.Alloc();\n      newItem->pPrev = prevItem;\n      newItem->pNext = pItem;\n      pItem->pPrev = newItem;\n      if (prevItem != VMA_NULL) {\n         prevItem->pNext = newItem;\n      } else {\n         VMA_HEAVY_ASSERT(m_pFront == pItem);\n         m_pFront = newItem;\n      }\n      ++m_Count;\n      return newItem;\n   } else\n      return PushBack();\n}\n\ntemplate<typename T>\nVmaListItem<T>* VmaRawList<T>::InsertAfter(ItemType* pItem)\n{\n   if (pItem != VMA_NULL) {\n      ItemType* const nextItem = pItem->pNext;\n      ItemType* const newItem = m_ItemAllocator.Alloc();\n      newItem->pNext = nextItem;\n      newItem->pPrev = pItem;\n      pItem->pNext = newItem;\n      if (nextItem != VMA_NULL) {\n         nextItem->pPrev = newItem;\n      } else {\n         VMA_HEAVY_ASSERT(m_pBack == pItem);\n         m_pBack = newItem;\n      }\n      ++m_Count;\n      return newItem;\n   } else\n      return PushFront();\n}\n\ntemplate<typename T>\nVmaListItem<T>* VmaRawList<T>::InsertBefore(ItemType* pItem, const T& value)\n{\n   ItemType* const newItem = InsertBefore(pItem);\n   newItem->Value = value;\n   return newItem;\n}\n\ntemplate<typename T>\nVmaListItem<T>* VmaRawList<T>::InsertAfter(ItemType* pItem, const T& value)\n{\n   ItemType* const newItem = InsertAfter(pItem);\n   newItem->Value = value;\n   return newItem;\n}\n\ntemplate<typename T, typename AllocatorT>\nclass VmaList\n{\n   VMA_CLASS_NO_COPY(VmaList)\npublic:\n   class iterator\n   {\n   public:\n      iterator() :\n         m_pList(VMA_NULL),\n         m_pItem(VMA_NULL)\n      {\n      }\n\n      T& operator*() const\n      {\n         VMA_HEAVY_ASSERT(m_pItem != VMA_NULL);\n         return m_pItem->Value;\n      }\n      T* operator->() const\n      {\n         VMA_HEAVY_ASSERT(m_pItem != VMA_NULL);\n         return &m_pItem->Value;\n      }\n\n      iterator& operator++()\n      {\n         VMA_HEAVY_ASSERT(m_pItem != VMA_NULL);\n         m_pItem = m_pItem->pNext;\n         return *this;\n      }\n      iterator& operator--()\n      {\n         if (m_pItem != VMA_NULL) {\n            m_pItem = m_pItem->pPrev;\n         } else {\n            VMA_HEAVY_ASSERT(!m_pList->IsEmpty());\n            m_pItem = m_pList->Back();\n         }\n         return *this;\n      }\n\n      iterator operator++(int)\n      {\n         iterator result = *this;\n         ++* this;\n         return result;\n      }\n      iterator operator--(int)\n      {\n         iterator result = *this;\n         --* this;\n         return result;\n      }\n\n      bool operator==(const iterator& rhs) const\n      {\n         VMA_HEAVY_ASSERT(m_pList == rhs.m_pList);\n         return m_pItem == rhs.m_pItem;\n      }\n      bool operator!=(const iterator& rhs) const\n      {\n         VMA_HEAVY_ASSERT(m_pList == rhs.m_pList);\n         return m_pItem != rhs.m_pItem;\n      }\n\n   private:\n      VmaRawList<T>* m_pList;\n      VmaListItem<T>* m_pItem;\n\n      iterator(VmaRawList<T>* pList, VmaListItem<T>* pItem) :\n         m_pList(pList),\n         m_pItem(pItem)\n      {\n      }\n\n      friend class VmaList<T, AllocatorT>;\n   };\n\n   class const_iterator\n   {\n   public:\n      const_iterator() :\n         m_pList(VMA_NULL),\n         m_pItem(VMA_NULL)\n      {\n      }\n\n      const_iterator(const iterator& src) :\n         m_pList(src.m_pList),\n         m_pItem(src.m_pItem)\n      {\n      }\n\n      const T& operator*() const\n      {\n         VMA_HEAVY_ASSERT(m_pItem != VMA_NULL);\n         return m_pItem->Value;\n      }\n      const T* operator->() const\n      {\n         VMA_HEAVY_ASSERT(m_pItem != VMA_NULL);\n         return &m_pItem->Value;\n      }\n\n      const_iterator& operator++()\n      {\n         VMA_HEAVY_ASSERT(m_pItem != VMA_NULL);\n         m_pItem = m_pItem->pNext;\n         return *this;\n      }\n      const_iterator& operator--()\n      {\n         if (m_pItem != VMA_NULL) {\n            m_pItem = m_pItem->pPrev;\n         } else {\n            VMA_HEAVY_ASSERT(!m_pList->IsEmpty());\n            m_pItem = m_pList->Back();\n         }\n         return *this;\n      }\n\n      const_iterator operator++(int)\n      {\n         const_iterator result = *this;\n         ++* this;\n         return result;\n      }\n      const_iterator operator--(int)\n      {\n         const_iterator result = *this;\n         --* this;\n         return result;\n      }\n\n      bool operator==(const const_iterator& rhs) const\n      {\n         VMA_HEAVY_ASSERT(m_pList == rhs.m_pList);\n         return m_pItem == rhs.m_pItem;\n      }\n      bool operator!=(const const_iterator& rhs) const\n      {\n         VMA_HEAVY_ASSERT(m_pList == rhs.m_pList);\n         return m_pItem != rhs.m_pItem;\n      }\n\n   private:\n      const_iterator(const VmaRawList<T>* pList, const VmaListItem<T>* pItem) :\n         m_pList(pList),\n         m_pItem(pItem)\n      {\n      }\n\n      const VmaRawList<T>* m_pList;\n      const VmaListItem<T>* m_pItem;\n\n      friend class VmaList<T, AllocatorT>;\n   };\n\n   VmaList(const AllocatorT& allocator) : m_RawList(allocator.m_pCallbacks) { }\n\n   bool empty() const { return m_RawList.IsEmpty(); }\n   size_t size() const { return m_RawList.GetCount(); }\n\n   iterator begin() { return iterator(&m_RawList, m_RawList.Front()); }\n   iterator end() { return iterator(&m_RawList, VMA_NULL); }\n\n   const_iterator cbegin() const { return const_iterator(&m_RawList, m_RawList.Front()); }\n   const_iterator cend() const { return const_iterator(&m_RawList, VMA_NULL); }\n\n   void clear() { m_RawList.Clear(); }\n   void push_back(const T& value) { m_RawList.PushBack(value); }\n   void erase(iterator it) { m_RawList.Remove(it.m_pItem); }\n   iterator insert(iterator it, const T& value) { return iterator(&m_RawList, m_RawList.InsertBefore(it.m_pItem, value)); }\n\nprivate:\n   VmaRawList<T> m_RawList;\n};\n\n#endif // #if VMA_USE_STL_LIST\n\n////////////////////////////////////////////////////////////////////////////////\n// class VmaMap\n\n// Unused in this version.\n#if 0\n\n#if VMA_USE_STL_UNORDERED_MAP\n\n#define VmaPair std::pair\n\n#define VMA_MAP_TYPE(KeyT, ValueT) \\\n    std::unordered_map< KeyT, ValueT, std::hash<KeyT>, std::equal_to<KeyT>, VmaStlAllocator< std::pair<KeyT, ValueT> > >\n\n#else // #if VMA_USE_STL_UNORDERED_MAP\n\ntemplate<typename T1, typename T2>\nstruct VmaPair\n{\n   T1 first;\n   T2 second;\n\n   VmaPair() : first(), second() { }\n   VmaPair(const T1& firstSrc, const T2& secondSrc) : first(firstSrc), second(secondSrc) { }\n};\n\n/* Class compatible with subset of interface of std::unordered_map.\nKeyT, ValueT must be POD because they will be stored in VmaVector.\n*/\ntemplate<typename KeyT, typename ValueT>\nclass VmaMap\n{\npublic:\n   typedef VmaPair<KeyT, ValueT> PairType;\n   typedef PairType* iterator;\n\n   VmaMap(const VmaStlAllocator<PairType>& allocator) : m_Vector(allocator) { }\n\n   iterator begin() { return m_Vector.begin(); }\n   iterator end() { return m_Vector.end(); }\n\n   void insert(const PairType& pair);\n   iterator find(const KeyT& key);\n   void erase(iterator it);\n\nprivate:\n   VmaVector< PairType, VmaStlAllocator<PairType> > m_Vector;\n};\n\n#define VMA_MAP_TYPE(KeyT, ValueT) VmaMap<KeyT, ValueT>\n\ntemplate<typename FirstT, typename SecondT>\nstruct VmaPairFirstLess\n{\n   bool operator()(const VmaPair<FirstT, SecondT>& lhs, const VmaPair<FirstT, SecondT>& rhs) const\n   {\n      return lhs.first < rhs.first;\n   }\n   bool operator()(const VmaPair<FirstT, SecondT>& lhs, const FirstT& rhsFirst) const\n   {\n      return lhs.first < rhsFirst;\n   }\n};\n\ntemplate<typename KeyT, typename ValueT>\nvoid VmaMap<KeyT, ValueT>::insert(const PairType& pair)\n{\n   const size_t indexToInsert = VmaBinaryFindFirstNotLess(\n      m_Vector.data(),\n      m_Vector.data() + m_Vector.size(),\n      pair,\n      VmaPairFirstLess<KeyT, ValueT>()) - m_Vector.data();\n   VmaVectorInsert(m_Vector, indexToInsert, pair);\n}\n\ntemplate<typename KeyT, typename ValueT>\nVmaPair<KeyT, ValueT>* VmaMap<KeyT, ValueT>::find(const KeyT& key)\n{\n   PairType* it = VmaBinaryFindFirstNotLess(\n      m_Vector.data(),\n      m_Vector.data() + m_Vector.size(),\n      key,\n      VmaPairFirstLess<KeyT, ValueT>());\n   if ((it != m_Vector.end()) && (it->first == key)) {\n      return it;\n   } else {\n      return m_Vector.end();\n   }\n}\n\ntemplate<typename KeyT, typename ValueT>\nvoid VmaMap<KeyT, ValueT>::erase(iterator it)\n{\n   VmaVectorRemove(m_Vector, it - m_Vector.begin());\n}\n\n#endif // #if VMA_USE_STL_UNORDERED_MAP\n\n#endif // #if 0\n\n////////////////////////////////////////////////////////////////////////////////\n\nclass VmaDeviceMemoryBlock;\n\nenum VMA_CACHE_OPERATION { VMA_CACHE_FLUSH, VMA_CACHE_INVALIDATE };\n\nstruct VmaAllocation_T\n{\nprivate:\n   static const uint8_t MAP_COUNT_FLAG_PERSISTENT_MAP = 0x80;\n\n   enum FLAGS\n   {\n      FLAG_USER_DATA_STRING = 0x01,\n   };\n\npublic:\n   enum ALLOCATION_TYPE\n   {\n      ALLOCATION_TYPE_NONE,\n      ALLOCATION_TYPE_BLOCK,\n      ALLOCATION_TYPE_DEDICATED,\n   };\n\n   /*\n   This struct is allocated using VmaPoolAllocator.\n   */\n\n   void Ctor(uint32_t currentFrameIndex, bool userDataString)\n   {\n      m_Alignment = 1;\n      m_Size = 0;\n      m_MemoryTypeIndex = 0;\n      m_pUserData = VMA_NULL;\n      m_LastUseFrameIndex = currentFrameIndex;\n      m_Type = (uint8_t)ALLOCATION_TYPE_NONE;\n      m_SuballocationType = (uint8_t)VMA_SUBALLOCATION_TYPE_UNKNOWN;\n      m_MapCount = 0;\n      m_Flags = userDataString ? (uint8_t)FLAG_USER_DATA_STRING : 0;\n\n#if VMA_STATS_STRING_ENABLED\n      m_CreationFrameIndex = currentFrameIndex;\n      m_BufferImageUsage = 0;\n#endif\n   }\n\n   void Dtor()\n   {\n      VMA_ASSERT((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) == 0 && \"Allocation was not unmapped before destruction.\");\n\n      // Check if owned string was freed.\n      VMA_ASSERT(m_pUserData == VMA_NULL);\n   }\n\n   void InitBlockAllocation(\n      VmaDeviceMemoryBlock* block,\n      VkDeviceSize offset,\n      VkDeviceSize alignment,\n      VkDeviceSize size,\n      uint32_t memoryTypeIndex,\n      VmaSuballocationType suballocationType,\n      bool mapped,\n      bool canBecomeLost)\n   {\n      VMA_ASSERT(m_Type == ALLOCATION_TYPE_NONE);\n      VMA_ASSERT(block != VMA_NULL);\n      m_Type = (uint8_t)ALLOCATION_TYPE_BLOCK;\n      m_Alignment = alignment;\n      m_Size = size;\n      m_MemoryTypeIndex = memoryTypeIndex;\n      m_MapCount = mapped ? MAP_COUNT_FLAG_PERSISTENT_MAP : 0;\n      m_SuballocationType = (uint8_t)suballocationType;\n      m_BlockAllocation.m_Block = block;\n      m_BlockAllocation.m_Offset = offset;\n      m_BlockAllocation.m_CanBecomeLost = canBecomeLost;\n   }\n\n   void InitLost()\n   {\n      VMA_ASSERT(m_Type == ALLOCATION_TYPE_NONE);\n      VMA_ASSERT(m_LastUseFrameIndex.load() == VMA_FRAME_INDEX_LOST);\n      m_Type = (uint8_t)ALLOCATION_TYPE_BLOCK;\n      m_MemoryTypeIndex = 0;\n      m_BlockAllocation.m_Block = VMA_NULL;\n      m_BlockAllocation.m_Offset = 0;\n      m_BlockAllocation.m_CanBecomeLost = true;\n   }\n\n   void ChangeBlockAllocation(\n      VmaAllocator hAllocator,\n      VmaDeviceMemoryBlock* block,\n      VkDeviceSize offset);\n\n   void ChangeOffset(VkDeviceSize newOffset);\n\n   // pMappedData not null means allocation is created with MAPPED flag.\n   void InitDedicatedAllocation(\n      uint32_t memoryTypeIndex,\n      VkDeviceMemory hMemory,\n      VmaSuballocationType suballocationType,\n      void* pMappedData,\n      VkDeviceSize size)\n   {\n      VMA_ASSERT(m_Type == ALLOCATION_TYPE_NONE);\n      VMA_ASSERT(hMemory != VK_NULL_HANDLE);\n      m_Type = (uint8_t)ALLOCATION_TYPE_DEDICATED;\n      m_Alignment = 0;\n      m_Size = size;\n      m_MemoryTypeIndex = memoryTypeIndex;\n      m_SuballocationType = (uint8_t)suballocationType;\n      m_MapCount = (pMappedData != VMA_NULL) ? MAP_COUNT_FLAG_PERSISTENT_MAP : 0;\n      m_DedicatedAllocation.m_hMemory = hMemory;\n      m_DedicatedAllocation.m_pMappedData = pMappedData;\n   }\n\n   ALLOCATION_TYPE GetType() const { return (ALLOCATION_TYPE)m_Type; }\n   VkDeviceSize GetAlignment() const { return m_Alignment; }\n   VkDeviceSize GetSize() const { return m_Size; }\n   bool IsUserDataString() const { return (m_Flags & FLAG_USER_DATA_STRING) != 0; }\n   void* GetUserData() const { return m_pUserData; }\n   void SetUserData(VmaAllocator hAllocator, void* pUserData);\n   VmaSuballocationType GetSuballocationType() const { return (VmaSuballocationType)m_SuballocationType; }\n\n   VmaDeviceMemoryBlock* GetBlock() const\n   {\n      VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK);\n      return m_BlockAllocation.m_Block;\n   }\n   VkDeviceSize GetOffset() const;\n   VkDeviceMemory GetMemory() const;\n   uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; }\n   bool IsPersistentMap() const { return (m_MapCount & MAP_COUNT_FLAG_PERSISTENT_MAP) != 0; }\n   void* GetMappedData() const;\n   bool CanBecomeLost() const;\n\n   uint32_t GetLastUseFrameIndex() const\n   {\n      return m_LastUseFrameIndex.load();\n   }\n   bool CompareExchangeLastUseFrameIndex(uint32_t& expected, uint32_t desired)\n   {\n      return m_LastUseFrameIndex.compare_exchange_weak(expected, desired);\n   }\n   /*\n   - If hAllocation.LastUseFrameIndex + frameInUseCount < allocator.CurrentFrameIndex,\n     makes it lost by setting LastUseFrameIndex = VMA_FRAME_INDEX_LOST and returns true.\n   - Else, returns false.\n\n   If hAllocation is already lost, assert - you should not call it then.\n   If hAllocation was not created with CAN_BECOME_LOST_BIT, assert.\n   */\n   bool MakeLost(uint32_t currentFrameIndex, uint32_t frameInUseCount);\n\n   void DedicatedAllocCalcStatsInfo(VmaStatInfo& outInfo)\n   {\n      VMA_ASSERT(m_Type == ALLOCATION_TYPE_DEDICATED);\n      outInfo.blockCount = 1;\n      outInfo.allocationCount = 1;\n      outInfo.unusedRangeCount = 0;\n      outInfo.usedBytes = m_Size;\n      outInfo.unusedBytes = 0;\n      outInfo.allocationSizeMin = outInfo.allocationSizeMax = m_Size;\n      outInfo.unusedRangeSizeMin = UINT64_MAX;\n      outInfo.unusedRangeSizeMax = 0;\n   }\n\n   void BlockAllocMap();\n   void BlockAllocUnmap();\n   VkResult DedicatedAllocMap(VmaAllocator hAllocator, void** ppData);\n   void DedicatedAllocUnmap(VmaAllocator hAllocator);\n\n#if VMA_STATS_STRING_ENABLED\n   uint32_t GetCreationFrameIndex() const { return m_CreationFrameIndex; }\n   uint32_t GetBufferImageUsage() const { return m_BufferImageUsage; }\n\n   void InitBufferImageUsage(uint32_t bufferImageUsage)\n   {\n      VMA_ASSERT(m_BufferImageUsage == 0);\n      m_BufferImageUsage = bufferImageUsage;\n   }\n\n   void PrintParameters(class VmaJsonWriter& json) const;\n#endif\n\nprivate:\n   VkDeviceSize m_Alignment;\n   VkDeviceSize m_Size;\n   void* m_pUserData;\n   VMA_ATOMIC_UINT32 m_LastUseFrameIndex;\n   uint32_t m_MemoryTypeIndex;\n   uint8_t m_Type; // ALLOCATION_TYPE\n   uint8_t m_SuballocationType; // VmaSuballocationType\n   // Bit 0x80 is set when allocation was created with VMA_ALLOCATION_CREATE_MAPPED_BIT.\n   // Bits with mask 0x7F are reference counter for vmaMapMemory()/vmaUnmapMemory().\n   uint8_t m_MapCount;\n   uint8_t m_Flags; // enum FLAGS\n\n   // Allocation out of VmaDeviceMemoryBlock.\n   struct BlockAllocation\n   {\n      VmaDeviceMemoryBlock* m_Block;\n      VkDeviceSize m_Offset;\n      bool m_CanBecomeLost;\n   };\n\n   // Allocation for an object that has its own private VkDeviceMemory.\n   struct DedicatedAllocation\n   {\n      VkDeviceMemory m_hMemory;\n      void* m_pMappedData; // Not null means memory is mapped.\n   };\n\n   union\n   {\n      // Allocation out of VmaDeviceMemoryBlock.\n      BlockAllocation m_BlockAllocation;\n      // Allocation for an object that has its own private VkDeviceMemory.\n      DedicatedAllocation m_DedicatedAllocation;\n   };\n\n#if VMA_STATS_STRING_ENABLED\n   uint32_t m_CreationFrameIndex;\n   uint32_t m_BufferImageUsage; // 0 if unknown.\n#endif\n\n   void FreeUserDataString(VmaAllocator hAllocator);\n};\n\n/*\nRepresents a region of VmaDeviceMemoryBlock that is either assigned and returned as\nallocated memory block or free.\n*/\nstruct VmaSuballocation\n{\n   VkDeviceSize offset;\n   VkDeviceSize size;\n   VmaAllocation hAllocation;\n   VmaSuballocationType type;\n};\n\n// Comparator for offsets.\nstruct VmaSuballocationOffsetLess\n{\n   bool operator()(const VmaSuballocation& lhs, const VmaSuballocation& rhs) const\n   {\n      return lhs.offset < rhs.offset;\n   }\n};\nstruct VmaSuballocationOffsetGreater\n{\n   bool operator()(const VmaSuballocation& lhs, const VmaSuballocation& rhs) const\n   {\n      return lhs.offset > rhs.offset;\n   }\n};\n\ntypedef VmaList< VmaSuballocation, VmaStlAllocator<VmaSuballocation> > VmaSuballocationList;\n\n// Cost of one additional allocation lost, as equivalent in bytes.\nstatic const VkDeviceSize VMA_LOST_ALLOCATION_COST = 1048576;\n\nenum class VmaAllocationRequestType\n{\n   Normal,\n   // Used by \"Linear\" algorithm.\n   UpperAddress,\n   EndOf1st,\n   EndOf2nd,\n};\n\n/*\nParameters of planned allocation inside a VmaDeviceMemoryBlock.\n\nIf canMakeOtherLost was false:\n- item points to a FREE suballocation.\n- itemsToMakeLostCount is 0.\n\nIf canMakeOtherLost was true:\n- item points to first of sequence of suballocations, which are either FREE,\n  or point to VmaAllocations that can become lost.\n- itemsToMakeLostCount is the number of VmaAllocations that need to be made lost for\n  the requested allocation to succeed.\n*/\nstruct VmaAllocationRequest\n{\n   VkDeviceSize offset;\n   VkDeviceSize sumFreeSize; // Sum size of free items that overlap with proposed allocation.\n   VkDeviceSize sumItemSize; // Sum size of items to make lost that overlap with proposed allocation.\n   VmaSuballocationList::iterator item;\n   size_t itemsToMakeLostCount;\n   void* customData;\n   VmaAllocationRequestType type;\n\n   VkDeviceSize CalcCost() const\n   {\n      return sumItemSize + itemsToMakeLostCount * VMA_LOST_ALLOCATION_COST;\n   }\n};\n\n/*\nData structure used for bookkeeping of allocations and unused ranges of memory\nin a single VkDeviceMemory block.\n*/\nclass VmaBlockMetadata\n{\npublic:\n   VmaBlockMetadata(VmaAllocator hAllocator);\n   virtual ~VmaBlockMetadata() { }\n   virtual void Init(VkDeviceSize size) { m_Size = size; }\n\n   // Validates all data structures inside this object. If not valid, returns false.\n   virtual bool Validate() const = 0;\n   VkDeviceSize GetSize() const { return m_Size; }\n   virtual size_t GetAllocationCount() const = 0;\n   virtual VkDeviceSize GetSumFreeSize() const = 0;\n   virtual VkDeviceSize GetUnusedRangeSizeMax() const = 0;\n   // Returns true if this block is empty - contains only single free suballocation.\n   virtual bool IsEmpty() const = 0;\n\n   virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const = 0;\n   // Shouldn't modify blockCount.\n   virtual void AddPoolStats(VmaPoolStats& inoutStats) const = 0;\n\n#if VMA_STATS_STRING_ENABLED\n   virtual void PrintDetailedMap(class VmaJsonWriter& json) const = 0;\n#endif\n\n   // Tries to find a place for suballocation with given parameters inside this block.\n   // If succeeded, fills pAllocationRequest and returns true.\n   // If failed, returns false.\n   virtual bool CreateAllocationRequest(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VkDeviceSize bufferImageGranularity,\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      bool upperAddress,\n      VmaSuballocationType allocType,\n      bool canMakeOtherLost,\n      // Always one of VMA_ALLOCATION_CREATE_STRATEGY_* or VMA_ALLOCATION_INTERNAL_STRATEGY_* flags.\n      uint32_t strategy,\n      VmaAllocationRequest* pAllocationRequest) = 0;\n\n   virtual bool MakeRequestedAllocationsLost(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VmaAllocationRequest* pAllocationRequest) = 0;\n\n   virtual uint32_t MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount) = 0;\n\n   virtual VkResult CheckCorruption(const void* pBlockData) = 0;\n\n   // Makes actual allocation based on request. Request must already be checked and valid.\n   virtual void Alloc(\n      const VmaAllocationRequest& request,\n      VmaSuballocationType type,\n      VkDeviceSize allocSize,\n      VmaAllocation hAllocation) = 0;\n\n   // Frees suballocation assigned to given memory region.\n   virtual void Free(const VmaAllocation allocation) = 0;\n   virtual void FreeAtOffset(VkDeviceSize offset) = 0;\n\nprotected:\n   const VkAllocationCallbacks* GetAllocationCallbacks() const { return m_pAllocationCallbacks; }\n\n#if VMA_STATS_STRING_ENABLED\n   void PrintDetailedMap_Begin(class VmaJsonWriter& json,\n                               VkDeviceSize unusedBytes,\n                               size_t allocationCount,\n                               size_t unusedRangeCount) const;\n   void PrintDetailedMap_Allocation(class VmaJsonWriter& json,\n                                    VkDeviceSize offset,\n                                    VmaAllocation hAllocation) const;\n   void PrintDetailedMap_UnusedRange(class VmaJsonWriter& json,\n                                     VkDeviceSize offset,\n                                     VkDeviceSize size) const;\n   void PrintDetailedMap_End(class VmaJsonWriter& json) const;\n#endif\n\nprivate:\n   VkDeviceSize m_Size;\n   const VkAllocationCallbacks* m_pAllocationCallbacks;\n};\n\n#define VMA_VALIDATE(cond) do { if(!(cond)) { \\\n        VMA_ASSERT(0 && \"Validation failed: \" #cond); \\\n        return false; \\\n    } } while(false)\n\nclass VmaBlockMetadata_Generic : public VmaBlockMetadata\n{\n   VMA_CLASS_NO_COPY(VmaBlockMetadata_Generic)\npublic:\n   VmaBlockMetadata_Generic(VmaAllocator hAllocator);\n   virtual ~VmaBlockMetadata_Generic();\n   virtual void Init(VkDeviceSize size);\n\n   virtual bool Validate() const;\n   virtual size_t GetAllocationCount() const { return m_Suballocations.size() - m_FreeCount; }\n   virtual VkDeviceSize GetSumFreeSize() const { return m_SumFreeSize; }\n   virtual VkDeviceSize GetUnusedRangeSizeMax() const;\n   virtual bool IsEmpty() const;\n\n   virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const;\n   virtual void AddPoolStats(VmaPoolStats& inoutStats) const;\n\n#if VMA_STATS_STRING_ENABLED\n   virtual void PrintDetailedMap(class VmaJsonWriter& json) const;\n#endif\n\n   virtual bool CreateAllocationRequest(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VkDeviceSize bufferImageGranularity,\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      bool upperAddress,\n      VmaSuballocationType allocType,\n      bool canMakeOtherLost,\n      uint32_t strategy,\n      VmaAllocationRequest* pAllocationRequest);\n\n   virtual bool MakeRequestedAllocationsLost(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VmaAllocationRequest* pAllocationRequest);\n\n   virtual uint32_t MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount);\n\n   virtual VkResult CheckCorruption(const void* pBlockData);\n\n   virtual void Alloc(\n      const VmaAllocationRequest& request,\n      VmaSuballocationType type,\n      VkDeviceSize allocSize,\n      VmaAllocation hAllocation);\n\n   virtual void Free(const VmaAllocation allocation);\n   virtual void FreeAtOffset(VkDeviceSize offset);\n\n   ////////////////////////////////////////////////////////////////////////////////\n   // For defragmentation\n\n   bool IsBufferImageGranularityConflictPossible(\n      VkDeviceSize bufferImageGranularity,\n      VmaSuballocationType& inOutPrevSuballocType) const;\n\nprivate:\n   friend class VmaDefragmentationAlgorithm_Generic;\n   friend class VmaDefragmentationAlgorithm_Fast;\n\n   uint32_t m_FreeCount;\n   VkDeviceSize m_SumFreeSize;\n   VmaSuballocationList m_Suballocations;\n   // Suballocations that are free and have size greater than certain threshold.\n   // Sorted by size, ascending.\n   VmaVector< VmaSuballocationList::iterator, VmaStlAllocator< VmaSuballocationList::iterator > > m_FreeSuballocationsBySize;\n\n   bool ValidateFreeSuballocationList() const;\n\n   // Checks if requested suballocation with given parameters can be placed in given pFreeSuballocItem.\n   // If yes, fills pOffset and returns true. If no, returns false.\n   bool CheckAllocation(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VkDeviceSize bufferImageGranularity,\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      VmaSuballocationType allocType,\n      VmaSuballocationList::const_iterator suballocItem,\n      bool canMakeOtherLost,\n      VkDeviceSize* pOffset,\n      size_t* itemsToMakeLostCount,\n      VkDeviceSize* pSumFreeSize,\n      VkDeviceSize* pSumItemSize) const;\n   // Given free suballocation, it merges it with following one, which must also be free.\n   void MergeFreeWithNext(VmaSuballocationList::iterator item);\n   // Releases given suballocation, making it free.\n   // Merges it with adjacent free suballocations if applicable.\n   // Returns iterator to new free suballocation at this place.\n   VmaSuballocationList::iterator FreeSuballocation(VmaSuballocationList::iterator suballocItem);\n   // Given free suballocation, it inserts it into sorted list of\n   // m_FreeSuballocationsBySize if it's suitable.\n   void RegisterFreeSuballocation(VmaSuballocationList::iterator item);\n   // Given free suballocation, it removes it from sorted list of\n   // m_FreeSuballocationsBySize if it's suitable.\n   void UnregisterFreeSuballocation(VmaSuballocationList::iterator item);\n};\n\n/*\nAllocations and their references in internal data structure look like this:\n\nif(m_2ndVectorMode == SECOND_VECTOR_EMPTY):\n\n        0 +-------+\n          |       |\n          |       |\n          |       |\n          +-------+\n          | Alloc |  1st[m_1stNullItemsBeginCount]\n          +-------+\n          | Alloc |  1st[m_1stNullItemsBeginCount + 1]\n          +-------+\n          |  ...  |\n          +-------+\n          | Alloc |  1st[1st.size() - 1]\n          +-------+\n          |       |\n          |       |\n          |       |\nGetSize() +-------+\n\nif(m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER):\n\n        0 +-------+\n          | Alloc |  2nd[0]\n          +-------+\n          | Alloc |  2nd[1]\n          +-------+\n          |  ...  |\n          +-------+\n          | Alloc |  2nd[2nd.size() - 1]\n          +-------+\n          |       |\n          |       |\n          |       |\n          +-------+\n          | Alloc |  1st[m_1stNullItemsBeginCount]\n          +-------+\n          | Alloc |  1st[m_1stNullItemsBeginCount + 1]\n          +-------+\n          |  ...  |\n          +-------+\n          | Alloc |  1st[1st.size() - 1]\n          +-------+\n          |       |\nGetSize() +-------+\n\nif(m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK):\n\n        0 +-------+\n          |       |\n          |       |\n          |       |\n          +-------+\n          | Alloc |  1st[m_1stNullItemsBeginCount]\n          +-------+\n          | Alloc |  1st[m_1stNullItemsBeginCount + 1]\n          +-------+\n          |  ...  |\n          +-------+\n          | Alloc |  1st[1st.size() - 1]\n          +-------+\n          |       |\n          |       |\n          |       |\n          +-------+\n          | Alloc |  2nd[2nd.size() - 1]\n          +-------+\n          |  ...  |\n          +-------+\n          | Alloc |  2nd[1]\n          +-------+\n          | Alloc |  2nd[0]\nGetSize() +-------+\n\n*/\nclass VmaBlockMetadata_Linear : public VmaBlockMetadata\n{\n   VMA_CLASS_NO_COPY(VmaBlockMetadata_Linear)\npublic:\n   VmaBlockMetadata_Linear(VmaAllocator hAllocator);\n   virtual ~VmaBlockMetadata_Linear();\n   virtual void Init(VkDeviceSize size);\n\n   virtual bool Validate() const;\n   virtual size_t GetAllocationCount() const;\n   virtual VkDeviceSize GetSumFreeSize() const { return m_SumFreeSize; }\n   virtual VkDeviceSize GetUnusedRangeSizeMax() const;\n   virtual bool IsEmpty() const { return GetAllocationCount() == 0; }\n\n   virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const;\n   virtual void AddPoolStats(VmaPoolStats& inoutStats) const;\n\n#if VMA_STATS_STRING_ENABLED\n   virtual void PrintDetailedMap(class VmaJsonWriter& json) const;\n#endif\n\n   virtual bool CreateAllocationRequest(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VkDeviceSize bufferImageGranularity,\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      bool upperAddress,\n      VmaSuballocationType allocType,\n      bool canMakeOtherLost,\n      uint32_t strategy,\n      VmaAllocationRequest* pAllocationRequest);\n\n   virtual bool MakeRequestedAllocationsLost(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VmaAllocationRequest* pAllocationRequest);\n\n   virtual uint32_t MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount);\n\n   virtual VkResult CheckCorruption(const void* pBlockData);\n\n   virtual void Alloc(\n      const VmaAllocationRequest& request,\n      VmaSuballocationType type,\n      VkDeviceSize allocSize,\n      VmaAllocation hAllocation);\n\n   virtual void Free(const VmaAllocation allocation);\n   virtual void FreeAtOffset(VkDeviceSize offset);\n\nprivate:\n   /*\n   There are two suballocation vectors, used in ping-pong way.\n   The one with index m_1stVectorIndex is called 1st.\n   The one with index (m_1stVectorIndex ^ 1) is called 2nd.\n   2nd can be non-empty only when 1st is not empty.\n   When 2nd is not empty, m_2ndVectorMode indicates its mode of operation.\n   */\n   typedef VmaVector< VmaSuballocation, VmaStlAllocator<VmaSuballocation> > SuballocationVectorType;\n\n   enum SECOND_VECTOR_MODE\n   {\n      SECOND_VECTOR_EMPTY,\n      /*\n      Suballocations in 2nd vector are created later than the ones in 1st, but they\n      all have smaller offset.\n      */\n      SECOND_VECTOR_RING_BUFFER,\n      /*\n      Suballocations in 2nd vector are upper side of double stack.\n      They all have offsets higher than those in 1st vector.\n      Top of this stack means smaller offsets, but higher indices in this vector.\n      */\n      SECOND_VECTOR_DOUBLE_STACK,\n   };\n\n   VkDeviceSize m_SumFreeSize;\n   SuballocationVectorType m_Suballocations0, m_Suballocations1;\n   uint32_t m_1stVectorIndex;\n   SECOND_VECTOR_MODE m_2ndVectorMode;\n\n   SuballocationVectorType& AccessSuballocations1st() { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; }\n   SuballocationVectorType& AccessSuballocations2nd() { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; }\n   const SuballocationVectorType& AccessSuballocations1st() const { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; }\n   const SuballocationVectorType& AccessSuballocations2nd() const { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; }\n\n   // Number of items in 1st vector with hAllocation = null at the beginning.\n   size_t m_1stNullItemsBeginCount;\n   // Number of other items in 1st vector with hAllocation = null somewhere in the middle.\n   size_t m_1stNullItemsMiddleCount;\n   // Number of items in 2nd vector with hAllocation = null.\n   size_t m_2ndNullItemsCount;\n\n   bool ShouldCompact1st() const;\n   void CleanupAfterFree();\n\n   bool CreateAllocationRequest_LowerAddress(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VkDeviceSize bufferImageGranularity,\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      VmaSuballocationType allocType,\n      bool canMakeOtherLost,\n      uint32_t strategy,\n      VmaAllocationRequest* pAllocationRequest);\n   bool CreateAllocationRequest_UpperAddress(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VkDeviceSize bufferImageGranularity,\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      VmaSuballocationType allocType,\n      bool canMakeOtherLost,\n      uint32_t strategy,\n      VmaAllocationRequest* pAllocationRequest);\n};\n\n/*\n- GetSize() is the original size of allocated memory block.\n- m_UsableSize is this size aligned down to a power of two.\n  All allocations and calculations happen relative to m_UsableSize.\n- GetUnusableSize() is the difference between them.\n  It is repoted as separate, unused range, not available for allocations.\n\nNode at level 0 has size = m_UsableSize.\nEach next level contains nodes with size 2 times smaller than current level.\nm_LevelCount is the maximum number of levels to use in the current object.\n*/\nclass VmaBlockMetadata_Buddy : public VmaBlockMetadata\n{\n   VMA_CLASS_NO_COPY(VmaBlockMetadata_Buddy)\npublic:\n   VmaBlockMetadata_Buddy(VmaAllocator hAllocator);\n   virtual ~VmaBlockMetadata_Buddy();\n   virtual void Init(VkDeviceSize size);\n\n   virtual bool Validate() const;\n   virtual size_t GetAllocationCount() const { return m_AllocationCount; }\n   virtual VkDeviceSize GetSumFreeSize() const { return m_SumFreeSize + GetUnusableSize(); }\n   virtual VkDeviceSize GetUnusedRangeSizeMax() const;\n   virtual bool IsEmpty() const { return m_Root->type == Node::TYPE_FREE; }\n\n   virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const;\n   virtual void AddPoolStats(VmaPoolStats& inoutStats) const;\n\n#if VMA_STATS_STRING_ENABLED\n   virtual void PrintDetailedMap(class VmaJsonWriter& json) const;\n#endif\n\n   virtual bool CreateAllocationRequest(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VkDeviceSize bufferImageGranularity,\n      VkDeviceSize allocSize,\n      VkDeviceSize allocAlignment,\n      bool upperAddress,\n      VmaSuballocationType allocType,\n      bool canMakeOtherLost,\n      uint32_t strategy,\n      VmaAllocationRequest* pAllocationRequest);\n\n   virtual bool MakeRequestedAllocationsLost(\n      uint32_t currentFrameIndex,\n      uint32_t frameInUseCount,\n      VmaAllocationRequest* pAllocationRequest);\n\n   virtual uint32_t MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount);\n\n   virtual VkResult CheckCorruption(const void* pBlockData) { return VK_ERROR_FEATURE_NOT_PRESENT; }\n\n   virtual void Alloc(\n      const VmaAllocationRequest& request,\n      VmaSuballocationType type,\n      VkDeviceSize allocSize,\n      VmaAllocation hAllocation);\n\n   virtual void Free(const VmaAllocation allocation) { FreeAtOffset(allocation, allocation->GetOffset()); }\n   virtual void FreeAtOffset(VkDeviceSize offset) { FreeAtOffset(VMA_NULL, offset); }\n\nprivate:\n   static const VkDeviceSize MIN_NODE_SIZE = 32;\n   static const size_t MAX_LEVELS = 30;\n\n   struct ValidationContext\n   {\n      size_t calculatedAllocationCount;\n      size_t calculatedFreeCount;\n      VkDeviceSize calculatedSumFreeSize;\n\n      ValidationContext() :\n         calculatedAllocationCount(0),\n         calculatedFreeCount(0),\n         calculatedSumFreeSize(0)\n      {\n      }\n   };\n\n   struct Node\n   {\n      VkDeviceSize offset;\n      enum TYPE\n      {\n         TYPE_FREE,\n         TYPE_ALLOCATION,\n         TYPE_SPLIT,\n         TYPE_COUNT\n      } type;\n      Node* parent;\n      Node* buddy;\n\n      union\n      {\n         struct\n         {\n            Node* prev;\n            Node* next;\n         } free;\n         struct\n         {\n            VmaAllocation alloc;\n         } allocation;\n         struct\n         {\n            Node* leftChild;\n         } split;\n      };\n   };\n\n   // Size of the memory block aligned down to a power of two.\n   VkDeviceSize m_UsableSize;\n   uint32_t m_LevelCount;\n\n   Node* m_Root;\n   struct\n   {\n      Node* front;\n      Node* back;\n   } m_FreeList[MAX_LEVELS];\n   // Number of nodes in the tree with type == TYPE_ALLOCATION.\n   size_t m_AllocationCount;\n   // Number of nodes in the tree with type == TYPE_FREE.\n   size_t m_FreeCount;\n   // This includes space wasted due to internal fragmentation. Doesn't include unusable size.\n   VkDeviceSize m_SumFreeSize;\n\n   VkDeviceSize GetUnusableSize() const { return GetSize() - m_UsableSize; }\n   void DeleteNode(Node* node);\n   bool ValidateNode(ValidationContext& ctx, const Node* parent, const Node* curr, uint32_t level, VkDeviceSize levelNodeSize) const;\n   uint32_t AllocSizeToLevel(VkDeviceSize allocSize) const;\n   inline VkDeviceSize LevelToNodeSize(uint32_t level) const { return m_UsableSize >> level; }\n   // Alloc passed just for validation. Can be null.\n   void FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset);\n   void CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const;\n   // Adds node to the front of FreeList at given level.\n   // node->type must be FREE.\n   // node->free.prev, next can be undefined.\n   void AddToFreeListFront(uint32_t level, Node* node);\n   // Removes node from FreeList at given level.\n   // node->type must be FREE.\n   // node->free.prev, next stay untouched.\n   void RemoveFromFreeList(uint32_t level, Node* node);\n\n#if VMA_STATS_STRING_ENABLED\n   void PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const;\n#endif\n};\n\n/*\nRepresents a single block of device memory (`VkDeviceMemory`) with all the\ndata about its regions (aka suballocations, #VmaAllocation), assigned and free.\n\nThread-safety: This class must be externally synchronized.\n*/\nclass VmaDeviceMemoryBlock\n{\n   VMA_CLASS_NO_COPY(VmaDeviceMemoryBlock)\npublic:\n   VmaBlockMetadata* m_pMetadata;\n\n   VmaDeviceMemoryBlock(VmaAllocator hAllocator);\n\n   ~VmaDeviceMemoryBlock()\n   {\n      VMA_ASSERT(m_MapCount == 0 && \"VkDeviceMemory block is being destroyed while it is still mapped.\");\n      VMA_ASSERT(m_hMemory == VK_NULL_HANDLE);\n   }\n\n   // Always call after construction.\n   void Init(\n      VmaAllocator hAllocator,\n      VmaPool hParentPool,\n      uint32_t newMemoryTypeIndex,\n      VkDeviceMemory newMemory,\n      VkDeviceSize newSize,\n      uint32_t id,\n      uint32_t algorithm);\n   // Always call before destruction.\n   void Destroy(VmaAllocator allocator);\n\n   VmaPool GetParentPool() const { return m_hParentPool; }\n   VkDeviceMemory GetDeviceMemory() const { return m_hMemory; }\n   uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; }\n   uint32_t GetId() const { return m_Id; }\n   void* GetMappedData() const { return m_pMappedData; }\n\n   // Validates all data structures inside this object. If not valid, returns false.\n   bool Validate() const;\n\n   VkResult CheckCorruption(VmaAllocator hAllocator);\n\n   // ppData can be null.\n   VkResult Map(VmaAllocator hAllocator, uint32_t count, void** ppData);\n   void Unmap(VmaAllocator hAllocator, uint32_t count);\n\n   VkResult WriteMagicValueAroundAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize);\n   VkResult ValidateMagicValueAroundAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize);\n\n   VkResult BindBufferMemory(\n      const VmaAllocator hAllocator,\n      const VmaAllocation hAllocation,\n      VkDeviceSize allocationLocalOffset,\n      VkBuffer hBuffer,\n      const void* pNext);\n   VkResult BindImageMemory(\n      const VmaAllocator hAllocator,\n      const VmaAllocation hAllocation,\n      VkDeviceSize allocationLocalOffset,\n      VkImage hImage,\n      const void* pNext);\n\nprivate:\n   VmaPool m_hParentPool; // VK_NULL_HANDLE if not belongs to custom pool.\n   uint32_t m_MemoryTypeIndex;\n   uint32_t m_Id;\n   VkDeviceMemory m_hMemory;\n\n   /*\n   Protects access to m_hMemory so it's not used by multiple threads simultaneously, e.g. vkMapMemory, vkBindBufferMemory.\n   Also protects m_MapCount, m_pMappedData.\n   Allocations, deallocations, any change in m_pMetadata is protected by parent's VmaBlockVector::m_Mutex.\n   */\n   VMA_MUTEX m_Mutex;\n   uint32_t m_MapCount;\n   void* m_pMappedData;\n};\n\nstruct VmaPointerLess\n{\n   bool operator()(const void* lhs, const void* rhs) const\n   {\n      return lhs < rhs;\n   }\n};\n\nstruct VmaDefragmentationMove\n{\n   size_t srcBlockIndex;\n   size_t dstBlockIndex;\n   VkDeviceSize srcOffset;\n   VkDeviceSize dstOffset;\n   VkDeviceSize size;\n};\n\nclass VmaDefragmentationAlgorithm;\n\n/*\nSequence of VmaDeviceMemoryBlock. Represents memory blocks allocated for a specific\nVulkan memory type.\n\nSynchronized internally with a mutex.\n*/\nstruct VmaBlockVector\n{\n   VMA_CLASS_NO_COPY(VmaBlockVector)\npublic:\n   VmaBlockVector(\n      VmaAllocator hAllocator,\n      VmaPool hParentPool,\n      uint32_t memoryTypeIndex,\n      VkDeviceSize preferredBlockSize,\n      size_t minBlockCount,\n      size_t maxBlockCount,\n      VkDeviceSize bufferImageGranularity,\n      uint32_t frameInUseCount,\n      bool explicitBlockSize,\n      uint32_t algorithm);\n   ~VmaBlockVector();\n\n   VkResult CreateMinBlocks();\n\n   VmaAllocator GetAllocator() const { return m_hAllocator; }\n   VmaPool GetParentPool() const { return m_hParentPool; }\n   bool IsCustomPool() const { return m_hParentPool != VMA_NULL; }\n   uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; }\n   VkDeviceSize GetPreferredBlockSize() const { return m_PreferredBlockSize; }\n   VkDeviceSize GetBufferImageGranularity() const { return m_BufferImageGranularity; }\n   uint32_t GetFrameInUseCount() const { return m_FrameInUseCount; }\n   uint32_t GetAlgorithm() const { return m_Algorithm; }\n\n   void GetPoolStats(VmaPoolStats* pStats);\n\n   bool IsEmpty();\n   bool IsCorruptionDetectionEnabled() const;\n\n   VkResult Allocate(\n      uint32_t currentFrameIndex,\n      VkDeviceSize size,\n      VkDeviceSize alignment,\n      const VmaAllocationCreateInfo& createInfo,\n      VmaSuballocationType suballocType,\n      size_t allocationCount,\n      VmaAllocation* pAllocations);\n\n   void Free(const VmaAllocation hAllocation);\n\n   // Adds statistics of this BlockVector to pStats.\n   void AddStats(VmaStats* pStats);\n\n#if VMA_STATS_STRING_ENABLED\n   void PrintDetailedMap(class VmaJsonWriter& json);\n#endif\n\n   void MakePoolAllocationsLost(\n      uint32_t currentFrameIndex,\n      size_t* pLostAllocationCount);\n   VkResult CheckCorruption();\n\n   // Saves results in pCtx->res.\n   void Defragment(\n      class VmaBlockVectorDefragmentationContext* pCtx,\n      VmaDefragmentationStats* pStats,\n      VkDeviceSize& maxCpuBytesToMove, uint32_t& maxCpuAllocationsToMove,\n      VkDeviceSize& maxGpuBytesToMove, uint32_t& maxGpuAllocationsToMove,\n      VkCommandBuffer commandBuffer);\n   void DefragmentationEnd(\n      class VmaBlockVectorDefragmentationContext* pCtx,\n      VmaDefragmentationStats* pStats);\n\n   ////////////////////////////////////////////////////////////////////////////////\n   // To be used only while the m_Mutex is locked. Used during defragmentation.\n\n   size_t GetBlockCount() const { return m_Blocks.size(); }\n   VmaDeviceMemoryBlock* GetBlock(size_t index) const { return m_Blocks[index]; }\n   size_t CalcAllocationCount() const;\n   bool IsBufferImageGranularityConflictPossible() const;\n\nprivate:\n   friend class VmaDefragmentationAlgorithm_Generic;\n\n   const VmaAllocator m_hAllocator;\n   const VmaPool m_hParentPool;\n   const uint32_t m_MemoryTypeIndex;\n   const VkDeviceSize m_PreferredBlockSize;\n   const size_t m_MinBlockCount;\n   const size_t m_MaxBlockCount;\n   const VkDeviceSize m_BufferImageGranularity;\n   const uint32_t m_FrameInUseCount;\n   const bool m_ExplicitBlockSize;\n   const uint32_t m_Algorithm;\n   VMA_RW_MUTEX m_Mutex;\n\n   /* There can be at most one allocation that is completely empty (except when minBlockCount > 0) -\n   a hysteresis to avoid pessimistic case of alternating creation and destruction of a VkDeviceMemory. */\n   bool m_HasEmptyBlock;\n   // Incrementally sorted by sumFreeSize, ascending.\n   VmaVector< VmaDeviceMemoryBlock*, VmaStlAllocator<VmaDeviceMemoryBlock*> > m_Blocks;\n   uint32_t m_NextBlockId;\n\n   VkDeviceSize CalcMaxBlockSize() const;\n\n   // Finds and removes given block from vector.\n   void Remove(VmaDeviceMemoryBlock* pBlock);\n\n   // Performs single step in sorting m_Blocks. They may not be fully sorted\n   // after this call.\n   void IncrementallySortBlocks();\n\n   VkResult AllocatePage(\n      uint32_t currentFrameIndex,\n      VkDeviceSize size,\n      VkDeviceSize alignment,\n      const VmaAllocationCreateInfo& createInfo,\n      VmaSuballocationType suballocType,\n      VmaAllocation* pAllocation);\n\n   // To be used only without CAN_MAKE_OTHER_LOST flag.\n   VkResult AllocateFromBlock(\n      VmaDeviceMemoryBlock* pBlock,\n      uint32_t currentFrameIndex,\n      VkDeviceSize size,\n      VkDeviceSize alignment,\n      VmaAllocationCreateFlags allocFlags,\n      void* pUserData,\n      VmaSuballocationType suballocType,\n      uint32_t strategy,\n      VmaAllocation* pAllocation);\n\n   VkResult CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex);\n\n   // Saves result to pCtx->res.\n   void ApplyDefragmentationMovesCpu(\n      class VmaBlockVectorDefragmentationContext* pDefragCtx,\n      const VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves);\n   // Saves result to pCtx->res.\n   void ApplyDefragmentationMovesGpu(\n      class VmaBlockVectorDefragmentationContext* pDefragCtx,\n      const VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves,\n      VkCommandBuffer commandBuffer);\n\n   /*\n   Used during defragmentation. pDefragmentationStats is optional. It's in/out\n   - updated with new data.\n   */\n   void FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats);\n\n   void UpdateHasEmptyBlock();\n};\n\nstruct VmaPool_T\n{\n   VMA_CLASS_NO_COPY(VmaPool_T)\npublic:\n   VmaBlockVector m_BlockVector;\n\n   VmaPool_T(\n      VmaAllocator hAllocator,\n      const VmaPoolCreateInfo& createInfo,\n      VkDeviceSize preferredBlockSize);\n   ~VmaPool_T();\n\n   uint32_t GetId() const { return m_Id; }\n   void SetId(uint32_t id) { VMA_ASSERT(m_Id == 0); m_Id = id; }\n\n   const char* GetName() const { return m_Name; }\n   void SetName(const char* pName);\n\n#if VMA_STATS_STRING_ENABLED\n   //void PrintDetailedMap(class VmaStringBuilder& sb);\n#endif\n\nprivate:\n   uint32_t m_Id;\n   char* m_Name;\n};\n\n/*\nPerforms defragmentation:\n\n- Updates `pBlockVector->m_pMetadata`.\n- Updates allocations by calling ChangeBlockAllocation() or ChangeOffset().\n- Does not move actual data, only returns requested moves as `moves`.\n*/\nclass VmaDefragmentationAlgorithm\n{\n   VMA_CLASS_NO_COPY(VmaDefragmentationAlgorithm)\npublic:\n   VmaDefragmentationAlgorithm(\n      VmaAllocator hAllocator,\n      VmaBlockVector* pBlockVector,\n      uint32_t currentFrameIndex) :\n      m_hAllocator(hAllocator),\n      m_pBlockVector(pBlockVector),\n      m_CurrentFrameIndex(currentFrameIndex)\n   {\n   }\n   virtual ~VmaDefragmentationAlgorithm()\n   {\n   }\n\n   virtual void AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged) = 0;\n   virtual void AddAll() = 0;\n\n   virtual VkResult Defragment(\n      VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves,\n      VkDeviceSize maxBytesToMove,\n      uint32_t maxAllocationsToMove) = 0;\n\n   virtual VkDeviceSize GetBytesMoved() const = 0;\n   virtual uint32_t GetAllocationsMoved() const = 0;\n\nprotected:\n   VmaAllocator const m_hAllocator;\n   VmaBlockVector* const m_pBlockVector;\n   const uint32_t m_CurrentFrameIndex;\n\n   struct AllocationInfo\n   {\n      VmaAllocation m_hAllocation;\n      VkBool32* m_pChanged;\n\n      AllocationInfo() :\n         m_hAllocation(VK_NULL_HANDLE),\n         m_pChanged(VMA_NULL)\n      {\n      }\n      AllocationInfo(VmaAllocation hAlloc, VkBool32* pChanged) :\n         m_hAllocation(hAlloc),\n         m_pChanged(pChanged)\n      {\n      }\n   };\n};\n\nclass VmaDefragmentationAlgorithm_Generic : public VmaDefragmentationAlgorithm\n{\n   VMA_CLASS_NO_COPY(VmaDefragmentationAlgorithm_Generic)\npublic:\n   VmaDefragmentationAlgorithm_Generic(\n      VmaAllocator hAllocator,\n      VmaBlockVector* pBlockVector,\n      uint32_t currentFrameIndex,\n      bool overlappingMoveSupported);\n   virtual ~VmaDefragmentationAlgorithm_Generic();\n\n   virtual void AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged);\n   virtual void AddAll() { m_AllAllocations = true; }\n\n   virtual VkResult Defragment(\n      VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves,\n      VkDeviceSize maxBytesToMove,\n      uint32_t maxAllocationsToMove);\n\n   virtual VkDeviceSize GetBytesMoved() const { return m_BytesMoved; }\n   virtual uint32_t GetAllocationsMoved() const { return m_AllocationsMoved; }\n\nprivate:\n   uint32_t m_AllocationCount;\n   bool m_AllAllocations;\n\n   VkDeviceSize m_BytesMoved;\n   uint32_t m_AllocationsMoved;\n\n   struct AllocationInfoSizeGreater\n   {\n      bool operator()(const AllocationInfo& lhs, const AllocationInfo& rhs) const\n      {\n         return lhs.m_hAllocation->GetSize() > rhs.m_hAllocation->GetSize();\n      }\n   };\n\n   struct AllocationInfoOffsetGreater\n   {\n      bool operator()(const AllocationInfo& lhs, const AllocationInfo& rhs) const\n      {\n         return lhs.m_hAllocation->GetOffset() > rhs.m_hAllocation->GetOffset();\n      }\n   };\n\n   struct BlockInfo\n   {\n      size_t m_OriginalBlockIndex;\n      VmaDeviceMemoryBlock* m_pBlock;\n      bool m_HasNonMovableAllocations;\n      VmaVector< AllocationInfo, VmaStlAllocator<AllocationInfo> > m_Allocations;\n\n      BlockInfo(const VkAllocationCallbacks* pAllocationCallbacks) :\n         m_OriginalBlockIndex(SIZE_MAX),\n         m_pBlock(VMA_NULL),\n         m_HasNonMovableAllocations(true),\n         m_Allocations(pAllocationCallbacks)\n      {\n      }\n\n      void CalcHasNonMovableAllocations()\n      {\n         const size_t blockAllocCount = m_pBlock->m_pMetadata->GetAllocationCount();\n         const size_t defragmentAllocCount = m_Allocations.size();\n         m_HasNonMovableAllocations = blockAllocCount != defragmentAllocCount;\n      }\n\n      void SortAllocationsBySizeDescending()\n      {\n         VMA_SORT(m_Allocations.begin(), m_Allocations.end(), AllocationInfoSizeGreater());\n      }\n\n      void SortAllocationsByOffsetDescending()\n      {\n         VMA_SORT(m_Allocations.begin(), m_Allocations.end(), AllocationInfoOffsetGreater());\n      }\n   };\n\n   struct BlockPointerLess\n   {\n      bool operator()(const BlockInfo* pLhsBlockInfo, const VmaDeviceMemoryBlock* pRhsBlock) const\n      {\n         return pLhsBlockInfo->m_pBlock < pRhsBlock;\n      }\n      bool operator()(const BlockInfo* pLhsBlockInfo, const BlockInfo* pRhsBlockInfo) const\n      {\n         return pLhsBlockInfo->m_pBlock < pRhsBlockInfo->m_pBlock;\n      }\n   };\n\n   // 1. Blocks with some non-movable allocations go first.\n   // 2. Blocks with smaller sumFreeSize go first.\n   struct BlockInfoCompareMoveDestination\n   {\n      bool operator()(const BlockInfo* pLhsBlockInfo, const BlockInfo* pRhsBlockInfo) const\n      {\n         if (pLhsBlockInfo->m_HasNonMovableAllocations && !pRhsBlockInfo->m_HasNonMovableAllocations) {\n            return true;\n         }\n         if (!pLhsBlockInfo->m_HasNonMovableAllocations && pRhsBlockInfo->m_HasNonMovableAllocations) {\n            return false;\n         }\n         if (pLhsBlockInfo->m_pBlock->m_pMetadata->GetSumFreeSize() < pRhsBlockInfo->m_pBlock->m_pMetadata->GetSumFreeSize()) {\n            return true;\n         }\n         return false;\n      }\n   };\n\n   typedef VmaVector< BlockInfo*, VmaStlAllocator<BlockInfo*> > BlockInfoVector;\n   BlockInfoVector m_Blocks;\n\n   VkResult DefragmentRound(\n      VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves,\n      VkDeviceSize maxBytesToMove,\n      uint32_t maxAllocationsToMove);\n\n   size_t CalcBlocksWithNonMovableCount() const;\n\n   static bool MoveMakesSense(\n      size_t dstBlockIndex, VkDeviceSize dstOffset,\n      size_t srcBlockIndex, VkDeviceSize srcOffset);\n};\n\nclass VmaDefragmentationAlgorithm_Fast : public VmaDefragmentationAlgorithm\n{\n   VMA_CLASS_NO_COPY(VmaDefragmentationAlgorithm_Fast)\npublic:\n   VmaDefragmentationAlgorithm_Fast(\n      VmaAllocator hAllocator,\n      VmaBlockVector* pBlockVector,\n      uint32_t currentFrameIndex,\n      bool overlappingMoveSupported);\n   virtual ~VmaDefragmentationAlgorithm_Fast();\n\n   virtual void AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged) { ++m_AllocationCount; }\n   virtual void AddAll() { m_AllAllocations = true; }\n\n   virtual VkResult Defragment(\n      VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves,\n      VkDeviceSize maxBytesToMove,\n      uint32_t maxAllocationsToMove);\n\n   virtual VkDeviceSize GetBytesMoved() const { return m_BytesMoved; }\n   virtual uint32_t GetAllocationsMoved() const { return m_AllocationsMoved; }\n\nprivate:\n   struct BlockInfo\n   {\n      size_t origBlockIndex;\n   };\n\n   class FreeSpaceDatabase\n   {\n   public:\n      FreeSpaceDatabase()\n      {\n         FreeSpace s = {};\n         s.blockInfoIndex = SIZE_MAX;\n         for (size_t i = 0; i < MAX_COUNT; ++i) {\n            m_FreeSpaces[i] = s;\n         }\n      }\n\n      void Register(size_t blockInfoIndex, VkDeviceSize offset, VkDeviceSize size)\n      {\n         if (size < VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) {\n            return;\n         }\n\n         // Find first invalid or the smallest structure.\n         size_t bestIndex = SIZE_MAX;\n         for (size_t i = 0; i < MAX_COUNT; ++i) {\n            // Empty structure.\n            if (m_FreeSpaces[i].blockInfoIndex == SIZE_MAX) {\n               bestIndex = i;\n               break;\n            }\n            if (m_FreeSpaces[i].size < size &&\n               (bestIndex == SIZE_MAX || m_FreeSpaces[bestIndex].size > m_FreeSpaces[i].size)) {\n               bestIndex = i;\n            }\n         }\n\n         if (bestIndex != SIZE_MAX) {\n            m_FreeSpaces[bestIndex].blockInfoIndex = blockInfoIndex;\n            m_FreeSpaces[bestIndex].offset = offset;\n            m_FreeSpaces[bestIndex].size = size;\n         }\n      }\n\n      bool Fetch(VkDeviceSize alignment, VkDeviceSize size,\n                 size_t& outBlockInfoIndex, VkDeviceSize& outDstOffset)\n      {\n         size_t bestIndex = SIZE_MAX;\n         VkDeviceSize bestFreeSpaceAfter = 0;\n         for (size_t i = 0; i < MAX_COUNT; ++i) {\n            // Structure is valid.\n            if (m_FreeSpaces[i].blockInfoIndex != SIZE_MAX) {\n               const VkDeviceSize dstOffset = VmaAlignUp(m_FreeSpaces[i].offset, alignment);\n               // Allocation fits into this structure.\n               if (dstOffset + size <= m_FreeSpaces[i].offset + m_FreeSpaces[i].size) {\n                  const VkDeviceSize freeSpaceAfter = (m_FreeSpaces[i].offset + m_FreeSpaces[i].size) -\n                     (dstOffset + size);\n                  if (bestIndex == SIZE_MAX || freeSpaceAfter > bestFreeSpaceAfter) {\n                     bestIndex = i;\n                     bestFreeSpaceAfter = freeSpaceAfter;\n                  }\n               }\n            }\n         }\n\n         if (bestIndex != SIZE_MAX) {\n            outBlockInfoIndex = m_FreeSpaces[bestIndex].blockInfoIndex;\n            outDstOffset = VmaAlignUp(m_FreeSpaces[bestIndex].offset, alignment);\n\n            if (bestFreeSpaceAfter >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) {\n               // Leave this structure for remaining empty space.\n               const VkDeviceSize alignmentPlusSize = (outDstOffset - m_FreeSpaces[bestIndex].offset) + size;\n               m_FreeSpaces[bestIndex].offset += alignmentPlusSize;\n               m_FreeSpaces[bestIndex].size -= alignmentPlusSize;\n            } else {\n               // This structure becomes invalid.\n               m_FreeSpaces[bestIndex].blockInfoIndex = SIZE_MAX;\n            }\n\n            return true;\n         }\n\n         return false;\n      }\n\n   private:\n      static const size_t MAX_COUNT = 4;\n\n      struct FreeSpace\n      {\n         size_t blockInfoIndex; // SIZE_MAX means this structure is invalid.\n         VkDeviceSize offset;\n         VkDeviceSize size;\n      } m_FreeSpaces[MAX_COUNT];\n   };\n\n   const bool m_OverlappingMoveSupported;\n\n   uint32_t m_AllocationCount;\n   bool m_AllAllocations;\n\n   VkDeviceSize m_BytesMoved;\n   uint32_t m_AllocationsMoved;\n\n   VmaVector< BlockInfo, VmaStlAllocator<BlockInfo> > m_BlockInfos;\n\n   void PreprocessMetadata();\n   void PostprocessMetadata();\n   void InsertSuballoc(VmaBlockMetadata_Generic* pMetadata, const VmaSuballocation& suballoc);\n};\n\nstruct VmaBlockDefragmentationContext\n{\n   enum BLOCK_FLAG\n   {\n      BLOCK_FLAG_USED = 0x00000001,\n   };\n   uint32_t flags;\n   VkBuffer hBuffer;\n};\n\nclass VmaBlockVectorDefragmentationContext\n{\n   VMA_CLASS_NO_COPY(VmaBlockVectorDefragmentationContext)\npublic:\n   VkResult res;\n   bool mutexLocked;\n   VmaVector< VmaBlockDefragmentationContext, VmaStlAllocator<VmaBlockDefragmentationContext> > blockContexts;\n\n   VmaBlockVectorDefragmentationContext(\n      VmaAllocator hAllocator,\n      VmaPool hCustomPool, // Optional.\n      VmaBlockVector* pBlockVector,\n      uint32_t currFrameIndex);\n   ~VmaBlockVectorDefragmentationContext();\n\n   VmaPool GetCustomPool() const { return m_hCustomPool; }\n   VmaBlockVector* GetBlockVector() const { return m_pBlockVector; }\n   VmaDefragmentationAlgorithm* GetAlgorithm() const { return m_pAlgorithm; }\n\n   void AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged);\n   void AddAll() { m_AllAllocations = true; }\n\n   void Begin(bool overlappingMoveSupported);\n\nprivate:\n   const VmaAllocator m_hAllocator;\n   // Null if not from custom pool.\n   const VmaPool m_hCustomPool;\n   // Redundant, for convenience not to fetch from m_hCustomPool->m_BlockVector or m_hAllocator->m_pBlockVectors.\n   VmaBlockVector* const m_pBlockVector;\n   const uint32_t m_CurrFrameIndex;\n   // Owner of this object.\n   VmaDefragmentationAlgorithm* m_pAlgorithm;\n\n   struct AllocInfo\n   {\n      VmaAllocation hAlloc;\n      VkBool32* pChanged;\n   };\n   // Used between constructor and Begin.\n   VmaVector< AllocInfo, VmaStlAllocator<AllocInfo> > m_Allocations;\n   bool m_AllAllocations;\n};\n\nstruct VmaDefragmentationContext_T\n{\nprivate:\n   VMA_CLASS_NO_COPY(VmaDefragmentationContext_T)\npublic:\n   VmaDefragmentationContext_T(\n      VmaAllocator hAllocator,\n      uint32_t currFrameIndex,\n      uint32_t flags,\n      VmaDefragmentationStats* pStats);\n   ~VmaDefragmentationContext_T();\n\n   void AddPools(uint32_t poolCount, VmaPool* pPools);\n   void AddAllocations(\n      uint32_t allocationCount,\n      VmaAllocation* pAllocations,\n      VkBool32* pAllocationsChanged);\n\n   /*\n   Returns:\n   - `VK_SUCCESS` if succeeded and object can be destroyed immediately.\n   - `VK_NOT_READY` if succeeded but the object must remain alive until vmaDefragmentationEnd().\n   - Negative value if error occured and object can be destroyed immediately.\n   */\n   VkResult Defragment(\n      VkDeviceSize maxCpuBytesToMove, uint32_t maxCpuAllocationsToMove,\n      VkDeviceSize maxGpuBytesToMove, uint32_t maxGpuAllocationsToMove,\n      VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats);\n\nprivate:\n   const VmaAllocator m_hAllocator;\n   const uint32_t m_CurrFrameIndex;\n   const uint32_t m_Flags;\n   VmaDefragmentationStats* const m_pStats;\n   // Owner of these objects.\n   VmaBlockVectorDefragmentationContext* m_DefaultPoolContexts[VK_MAX_MEMORY_TYPES];\n   // Owner of these objects.\n   VmaVector< VmaBlockVectorDefragmentationContext*, VmaStlAllocator<VmaBlockVectorDefragmentationContext*> > m_CustomPoolContexts;\n};\n\n#if VMA_RECORDING_ENABLED\n\nclass VmaRecorder\n{\npublic:\n   VmaRecorder();\n   VkResult Init(const VmaRecordSettings& settings, bool useMutex);\n   void WriteConfiguration(\n      const VkPhysicalDeviceProperties& devProps,\n      const VkPhysicalDeviceMemoryProperties& memProps,\n      uint32_t vulkanApiVersion,\n      bool dedicatedAllocationExtensionEnabled,\n      bool bindMemory2ExtensionEnabled,\n      bool memoryBudgetExtensionEnabled);\n   ~VmaRecorder();\n\n   void RecordCreateAllocator(uint32_t frameIndex);\n   void RecordDestroyAllocator(uint32_t frameIndex);\n   void RecordCreatePool(uint32_t frameIndex,\n                         const VmaPoolCreateInfo& createInfo,\n                         VmaPool pool);\n   void RecordDestroyPool(uint32_t frameIndex, VmaPool pool);\n   void RecordAllocateMemory(uint32_t frameIndex,\n                             const VkMemoryRequirements& vkMemReq,\n                             const VmaAllocationCreateInfo& createInfo,\n                             VmaAllocation allocation);\n   void RecordAllocateMemoryPages(uint32_t frameIndex,\n                                  const VkMemoryRequirements& vkMemReq,\n                                  const VmaAllocationCreateInfo& createInfo,\n                                  uint64_t allocationCount,\n                                  const VmaAllocation* pAllocations);\n   void RecordAllocateMemoryForBuffer(uint32_t frameIndex,\n                                      const VkMemoryRequirements& vkMemReq,\n                                      bool requiresDedicatedAllocation,\n                                      bool prefersDedicatedAllocation,\n                                      const VmaAllocationCreateInfo& createInfo,\n                                      VmaAllocation allocation);\n   void RecordAllocateMemoryForImage(uint32_t frameIndex,\n                                     const VkMemoryRequirements& vkMemReq,\n                                     bool requiresDedicatedAllocation,\n                                     bool prefersDedicatedAllocation,\n                                     const VmaAllocationCreateInfo& createInfo,\n                                     VmaAllocation allocation);\n   void RecordFreeMemory(uint32_t frameIndex,\n                         VmaAllocation allocation);\n   void RecordFreeMemoryPages(uint32_t frameIndex,\n                              uint64_t allocationCount,\n                              const VmaAllocation* pAllocations);\n   void RecordSetAllocationUserData(uint32_t frameIndex,\n                                    VmaAllocation allocation,\n                                    const void* pUserData);\n   void RecordCreateLostAllocation(uint32_t frameIndex,\n                                   VmaAllocation allocation);\n   void RecordMapMemory(uint32_t frameIndex,\n                        VmaAllocation allocation);\n   void RecordUnmapMemory(uint32_t frameIndex,\n                          VmaAllocation allocation);\n   void RecordFlushAllocation(uint32_t frameIndex,\n                              VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size);\n   void RecordInvalidateAllocation(uint32_t frameIndex,\n                                   VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size);\n   void RecordCreateBuffer(uint32_t frameIndex,\n                           const VkBufferCreateInfo& bufCreateInfo,\n                           const VmaAllocationCreateInfo& allocCreateInfo,\n                           VmaAllocation allocation);\n   void RecordCreateImage(uint32_t frameIndex,\n                          const VkImageCreateInfo& imageCreateInfo,\n                          const VmaAllocationCreateInfo& allocCreateInfo,\n                          VmaAllocation allocation);\n   void RecordDestroyBuffer(uint32_t frameIndex,\n                            VmaAllocation allocation);\n   void RecordDestroyImage(uint32_t frameIndex,\n                           VmaAllocation allocation);\n   void RecordTouchAllocation(uint32_t frameIndex,\n                              VmaAllocation allocation);\n   void RecordGetAllocationInfo(uint32_t frameIndex,\n                                VmaAllocation allocation);\n   void RecordMakePoolAllocationsLost(uint32_t frameIndex,\n                                      VmaPool pool);\n   void RecordDefragmentationBegin(uint32_t frameIndex,\n                                   const VmaDefragmentationInfo2& info,\n                                   VmaDefragmentationContext ctx);\n   void RecordDefragmentationEnd(uint32_t frameIndex,\n                                 VmaDefragmentationContext ctx);\n   void RecordSetPoolName(uint32_t frameIndex,\n                          VmaPool pool,\n                          const char* name);\n\nprivate:\n   struct CallParams\n   {\n      uint32_t threadId;\n      double time;\n   };\n\n   class UserDataString\n   {\n   public:\n      UserDataString(VmaAllocationCreateFlags allocFlags, const void* pUserData);\n      const char* GetString() const { return m_Str; }\n\n   private:\n      char m_PtrStr[17];\n      const char* m_Str;\n   };\n\n   bool m_UseMutex;\n   VmaRecordFlags m_Flags;\n   FILE* m_File;\n   VMA_MUTEX m_FileMutex;\n   int64_t m_Freq;\n   int64_t m_StartCounter;\n\n   void GetBasicParams(CallParams& outParams);\n\n   // T must be a pointer type, e.g. VmaAllocation, VmaPool.\n   template<typename T>\n   void PrintPointerList(uint64_t count, const T* pItems)\n   {\n      if (count) {\n         fprintf(m_File, \"%p\", pItems[0]);\n         for (uint64_t i = 1; i < count; ++i) {\n            fprintf(m_File, \" %p\", pItems[i]);\n         }\n      }\n   }\n\n   void PrintPointerList(uint64_t count, const VmaAllocation* pItems);\n   void Flush();\n};\n\n#endif // #if VMA_RECORDING_ENABLED\n\n/*\nThread-safe wrapper over VmaPoolAllocator free list, for allocation of VmaAllocation_T objects.\n*/\nclass VmaAllocationObjectAllocator\n{\n   VMA_CLASS_NO_COPY(VmaAllocationObjectAllocator)\npublic:\n   VmaAllocationObjectAllocator(const VkAllocationCallbacks* pAllocationCallbacks);\n\n   VmaAllocation Allocate();\n   void Free(VmaAllocation hAlloc);\n\nprivate:\n   VMA_MUTEX m_Mutex;\n   VmaPoolAllocator<VmaAllocation_T> m_Allocator;\n};\n\nstruct VmaCurrentBudgetData\n{\n   VMA_ATOMIC_UINT64 m_BlockBytes[VK_MAX_MEMORY_HEAPS];\n   VMA_ATOMIC_UINT64 m_AllocationBytes[VK_MAX_MEMORY_HEAPS];\n\n#if VMA_MEMORY_BUDGET\n   VMA_ATOMIC_UINT32 m_OperationsSinceBudgetFetch;\n   VMA_RW_MUTEX m_BudgetMutex;\n   uint64_t m_VulkanUsage[VK_MAX_MEMORY_HEAPS];\n   uint64_t m_VulkanBudget[VK_MAX_MEMORY_HEAPS];\n   uint64_t m_BlockBytesAtBudgetFetch[VK_MAX_MEMORY_HEAPS];\n#endif // #if VMA_MEMORY_BUDGET\n\n   VmaCurrentBudgetData()\n   {\n      for (uint32_t heapIndex = 0; heapIndex < VK_MAX_MEMORY_HEAPS; ++heapIndex) {\n         m_BlockBytes[heapIndex] = 0;\n         m_AllocationBytes[heapIndex] = 0;\n#if VMA_MEMORY_BUDGET\n         m_VulkanUsage[heapIndex] = 0;\n         m_VulkanBudget[heapIndex] = 0;\n         m_BlockBytesAtBudgetFetch[heapIndex] = 0;\n#endif\n      }\n\n#if VMA_MEMORY_BUDGET\n      m_OperationsSinceBudgetFetch = 0;\n#endif\n   }\n\n   void AddAllocation(uint32_t heapIndex, VkDeviceSize allocationSize)\n   {\n      m_AllocationBytes[heapIndex] += allocationSize;\n#if VMA_MEMORY_BUDGET\n      ++m_OperationsSinceBudgetFetch;\n#endif\n   }\n\n   void RemoveAllocation(uint32_t heapIndex, VkDeviceSize allocationSize)\n   {\n      VMA_ASSERT(m_AllocationBytes[heapIndex] >= allocationSize); // DELME\n      m_AllocationBytes[heapIndex] -= allocationSize;\n#if VMA_MEMORY_BUDGET\n      ++m_OperationsSinceBudgetFetch;\n#endif\n   }\n};\n\n// Main allocator object.\nstruct VmaAllocator_T\n{\n   VMA_CLASS_NO_COPY(VmaAllocator_T)\npublic:\n   bool m_UseMutex;\n   uint32_t m_VulkanApiVersion;\n   bool m_UseKhrDedicatedAllocation; // Can be set only if m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0).\n   bool m_UseKhrBindMemory2; // Can be set only if m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0).\n   bool m_UseExtMemoryBudget;\n   VkDevice m_hDevice;\n   VkInstance m_hInstance;\n   bool m_AllocationCallbacksSpecified;\n   VkAllocationCallbacks m_AllocationCallbacks;\n   VmaDeviceMemoryCallbacks m_DeviceMemoryCallbacks;\n   VmaAllocationObjectAllocator m_AllocationObjectAllocator;\n\n   // Each bit (1 << i) is set if HeapSizeLimit is enabled for that heap, so cannot allocate more than the heap size.\n   uint32_t m_HeapSizeLimitMask;\n\n   VkPhysicalDeviceProperties m_PhysicalDeviceProperties;\n   VkPhysicalDeviceMemoryProperties m_MemProps;\n\n   // Default pools.\n   VmaBlockVector* m_pBlockVectors[VK_MAX_MEMORY_TYPES];\n\n   // Each vector is sorted by memory (handle value).\n   typedef VmaVector< VmaAllocation, VmaStlAllocator<VmaAllocation> > AllocationVectorType;\n   AllocationVectorType* m_pDedicatedAllocations[VK_MAX_MEMORY_TYPES];\n   VMA_RW_MUTEX m_DedicatedAllocationsMutex[VK_MAX_MEMORY_TYPES];\n\n   VmaCurrentBudgetData m_Budget;\n\n   VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo);\n   VkResult Init(const VmaAllocatorCreateInfo* pCreateInfo);\n   ~VmaAllocator_T();\n\n   const VkAllocationCallbacks* GetAllocationCallbacks() const\n   {\n      return m_AllocationCallbacksSpecified ? &m_AllocationCallbacks : 0;\n   }\n   const VmaVulkanFunctions& GetVulkanFunctions() const\n   {\n      return m_VulkanFunctions;\n   }\n\n   VkDeviceSize GetBufferImageGranularity() const\n   {\n      return VMA_MAX(\n         static_cast<VkDeviceSize>(VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY),\n         m_PhysicalDeviceProperties.limits.bufferImageGranularity);\n   }\n\n   uint32_t GetMemoryHeapCount() const { return m_MemProps.memoryHeapCount; }\n   uint32_t GetMemoryTypeCount() const { return m_MemProps.memoryTypeCount; }\n\n   uint32_t MemoryTypeIndexToHeapIndex(uint32_t memTypeIndex) const\n   {\n      VMA_ASSERT(memTypeIndex < m_MemProps.memoryTypeCount);\n      return m_MemProps.memoryTypes[memTypeIndex].heapIndex;\n   }\n   // True when specific memory type is HOST_VISIBLE but not HOST_COHERENT.\n   bool IsMemoryTypeNonCoherent(uint32_t memTypeIndex) const\n   {\n      return (m_MemProps.memoryTypes[memTypeIndex].propertyFlags & (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) ==\n         VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;\n   }\n   // Minimum alignment for all allocations in specific memory type.\n   VkDeviceSize GetMemoryTypeMinAlignment(uint32_t memTypeIndex) const\n   {\n      return IsMemoryTypeNonCoherent(memTypeIndex) ?\n         VMA_MAX((VkDeviceSize)VMA_DEBUG_ALIGNMENT, m_PhysicalDeviceProperties.limits.nonCoherentAtomSize) :\n         (VkDeviceSize)VMA_DEBUG_ALIGNMENT;\n   }\n\n   bool IsIntegratedGpu() const\n   {\n      return m_PhysicalDeviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU;\n   }\n\n#if VMA_RECORDING_ENABLED\n   VmaRecorder* GetRecorder() const { return m_pRecorder; }\n#endif\n\n   void GetBufferMemoryRequirements(\n      VkBuffer hBuffer,\n      VkMemoryRequirements& memReq,\n      bool& requiresDedicatedAllocation,\n      bool& prefersDedicatedAllocation) const;\n   void GetImageMemoryRequirements(\n      VkImage hImage,\n      VkMemoryRequirements& memReq,\n      bool& requiresDedicatedAllocation,\n      bool& prefersDedicatedAllocation) const;\n\n   // Main allocation function.\n   VkResult AllocateMemory(\n      const VkMemoryRequirements& vkMemReq,\n      bool requiresDedicatedAllocation,\n      bool prefersDedicatedAllocation,\n      VkBuffer dedicatedBuffer,\n      VkImage dedicatedImage,\n      const VmaAllocationCreateInfo& createInfo,\n      VmaSuballocationType suballocType,\n      size_t allocationCount,\n      VmaAllocation* pAllocations);\n\n   // Main deallocation function.\n   void FreeMemory(\n      size_t allocationCount,\n      const VmaAllocation* pAllocations);\n\n   VkResult ResizeAllocation(\n      const VmaAllocation alloc,\n      VkDeviceSize newSize);\n\n   void CalculateStats(VmaStats* pStats);\n\n   void GetBudget(\n      VmaBudget* outBudget, uint32_t firstHeap, uint32_t heapCount);\n\n#if VMA_STATS_STRING_ENABLED\n   void PrintDetailedMap(class VmaJsonWriter& json);\n#endif\n\n   VkResult DefragmentationBegin(\n      const VmaDefragmentationInfo2& info,\n      VmaDefragmentationStats* pStats,\n      VmaDefragmentationContext* pContext);\n   VkResult DefragmentationEnd(\n      VmaDefragmentationContext context);\n\n   void GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo);\n   bool TouchAllocation(VmaAllocation hAllocation);\n\n   VkResult CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPool* pPool);\n   void DestroyPool(VmaPool pool);\n   void GetPoolStats(VmaPool pool, VmaPoolStats* pPoolStats);\n\n   void SetCurrentFrameIndex(uint32_t frameIndex);\n   uint32_t GetCurrentFrameIndex() const { return m_CurrentFrameIndex.load(); }\n\n   void MakePoolAllocationsLost(\n      VmaPool hPool,\n      size_t* pLostAllocationCount);\n   VkResult CheckPoolCorruption(VmaPool hPool);\n   VkResult CheckCorruption(uint32_t memoryTypeBits);\n\n   void CreateLostAllocation(VmaAllocation* pAllocation);\n\n   // Call to Vulkan function vkAllocateMemory with accompanying bookkeeping.\n   VkResult AllocateVulkanMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory);\n   // Call to Vulkan function vkFreeMemory with accompanying bookkeeping.\n   void FreeVulkanMemory(uint32_t memoryType, VkDeviceSize size, VkDeviceMemory hMemory);\n   // Call to Vulkan function vkBindBufferMemory or vkBindBufferMemory2KHR.\n   VkResult BindVulkanBuffer(\n      VkDeviceMemory memory,\n      VkDeviceSize memoryOffset,\n      VkBuffer buffer,\n      const void* pNext);\n   // Call to Vulkan function vkBindImageMemory or vkBindImageMemory2KHR.\n   VkResult BindVulkanImage(\n      VkDeviceMemory memory,\n      VkDeviceSize memoryOffset,\n      VkImage image,\n      const void* pNext);\n\n   VkResult Map(VmaAllocation hAllocation, void** ppData);\n   void Unmap(VmaAllocation hAllocation);\n\n   VkResult BindBufferMemory(\n      VmaAllocation hAllocation,\n      VkDeviceSize allocationLocalOffset,\n      VkBuffer hBuffer,\n      const void* pNext);\n   VkResult BindImageMemory(\n      VmaAllocation hAllocation,\n      VkDeviceSize allocationLocalOffset,\n      VkImage hImage,\n      const void* pNext);\n\n   void FlushOrInvalidateAllocation(\n      VmaAllocation hAllocation,\n      VkDeviceSize offset, VkDeviceSize size,\n      VMA_CACHE_OPERATION op);\n\n   void FillAllocation(const VmaAllocation hAllocation, uint8_t pattern);\n\n   /*\n   Returns bit mask of memory types that can support defragmentation on GPU as\n   they support creation of required buffer for copy operations.\n   */\n   uint32_t GetGpuDefragmentationMemoryTypeBits();\n\nprivate:\n   VkDeviceSize m_PreferredLargeHeapBlockSize;\n\n   VkPhysicalDevice m_PhysicalDevice;\n   VMA_ATOMIC_UINT32 m_CurrentFrameIndex;\n   VMA_ATOMIC_UINT32 m_GpuDefragmentationMemoryTypeBits; // UINT32_MAX means uninitialized.\n\n   VMA_RW_MUTEX m_PoolsMutex;\n   // Protected by m_PoolsMutex. Sorted by pointer value.\n   VmaVector<VmaPool, VmaStlAllocator<VmaPool> > m_Pools;\n   uint32_t m_NextPoolId;\n\n   VmaVulkanFunctions m_VulkanFunctions;\n\n#if VMA_RECORDING_ENABLED\n   VmaRecorder* m_pRecorder;\n#endif\n\n   void ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions);\n\n   VkDeviceSize CalcPreferredBlockSize(uint32_t memTypeIndex);\n\n   VkResult AllocateMemoryOfType(\n      VkDeviceSize size,\n      VkDeviceSize alignment,\n      bool dedicatedAllocation,\n      VkBuffer dedicatedBuffer,\n      VkImage dedicatedImage,\n      const VmaAllocationCreateInfo& createInfo,\n      uint32_t memTypeIndex,\n      VmaSuballocationType suballocType,\n      size_t allocationCount,\n      VmaAllocation* pAllocations);\n\n   // Helper function only to be used inside AllocateDedicatedMemory.\n   VkResult AllocateDedicatedMemoryPage(\n      VkDeviceSize size,\n      VmaSuballocationType suballocType,\n      uint32_t memTypeIndex,\n      const VkMemoryAllocateInfo& allocInfo,\n      bool map,\n      bool isUserDataString,\n      void* pUserData,\n      VmaAllocation* pAllocation);\n\n   // Allocates and registers new VkDeviceMemory specifically for dedicated allocations.\n   VkResult AllocateDedicatedMemory(\n      VkDeviceSize size,\n      VmaSuballocationType suballocType,\n      uint32_t memTypeIndex,\n      bool withinBudget,\n      bool map,\n      bool isUserDataString,\n      void* pUserData,\n      VkBuffer dedicatedBuffer,\n      VkImage dedicatedImage,\n      size_t allocationCount,\n      VmaAllocation* pAllocations);\n\n   void FreeDedicatedMemory(const VmaAllocation allocation);\n\n   /*\n   Calculates and returns bit mask of memory types that can support defragmentation\n   on GPU as they support creation of required buffer for copy operations.\n   */\n   uint32_t CalculateGpuDefragmentationMemoryTypeBits() const;\n\n#if VMA_MEMORY_BUDGET\n   void UpdateVulkanBudget();\n#endif // #if VMA_MEMORY_BUDGET\n};\n\n////////////////////////////////////////////////////////////////////////////////\n// Memory allocation #2 after VmaAllocator_T definition\n\nstatic void* VmaMalloc(VmaAllocator hAllocator, size_t size, size_t alignment)\n{\n   return VmaMalloc(&hAllocator->m_AllocationCallbacks, size, alignment);\n}\n\nstatic void VmaFree(VmaAllocator hAllocator, void* ptr)\n{\n   VmaFree(&hAllocator->m_AllocationCallbacks, ptr);\n}\n\ntemplate<typename T>\nstatic T* VmaAllocate(VmaAllocator hAllocator)\n{\n   return (T*)VmaMalloc(hAllocator, sizeof(T), VMA_ALIGN_OF(T));\n}\n\ntemplate<typename T>\nstatic T* VmaAllocateArray(VmaAllocator hAllocator, size_t count)\n{\n   return (T*)VmaMalloc(hAllocator, sizeof(T) * count, VMA_ALIGN_OF(T));\n}\n\ntemplate<typename T>\nstatic void vma_delete(VmaAllocator hAllocator, T* ptr)\n{\n   if (ptr != VMA_NULL) {\n      ptr->~T();\n      VmaFree(hAllocator, ptr);\n   }\n}\n\ntemplate<typename T>\nstatic void vma_delete_array(VmaAllocator hAllocator, T* ptr, size_t count)\n{\n   if (ptr != VMA_NULL) {\n      for (size_t i = count; i--; )\n         ptr[i].~T();\n      VmaFree(hAllocator, ptr);\n   }\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// VmaStringBuilder\n\n#if VMA_STATS_STRING_ENABLED\n\nclass VmaStringBuilder\n{\npublic:\n   VmaStringBuilder(VmaAllocator alloc) : m_Data(VmaStlAllocator<char>(alloc->GetAllocationCallbacks())) { }\n   size_t GetLength() const { return m_Data.size(); }\n   const char* GetData() const { return m_Data.data(); }\n\n   void Add(char ch) { m_Data.push_back(ch); }\n   void Add(const char* pStr);\n   void AddNewLine() { Add('\\n'); }\n   void AddNumber(uint32_t num);\n   void AddNumber(uint64_t num);\n   void AddPointer(const void* ptr);\n\nprivate:\n   VmaVector< char, VmaStlAllocator<char> > m_Data;\n};\n\nvoid VmaStringBuilder::Add(const char* pStr)\n{\n   const size_t strLen = strlen(pStr);\n   if (strLen > 0) {\n      const size_t oldCount = m_Data.size();\n      m_Data.resize(oldCount + strLen);\n      memcpy(m_Data.data() + oldCount, pStr, strLen);\n   }\n}\n\nvoid VmaStringBuilder::AddNumber(uint32_t num)\n{\n   char buf[11];\n   buf[10] = '\\0';\n   char* p = &buf[10];\n   do {\n      *--p = '0' + (num % 10);\n      num /= 10;\n   } while (num);\n   Add(p);\n}\n\nvoid VmaStringBuilder::AddNumber(uint64_t num)\n{\n   char buf[21];\n   buf[20] = '\\0';\n   char* p = &buf[20];\n   do {\n      *--p = '0' + (num % 10);\n      num /= 10;\n   } while (num);\n   Add(p);\n}\n\nvoid VmaStringBuilder::AddPointer(const void* ptr)\n{\n   char buf[21];\n   VmaPtrToStr(buf, sizeof(buf), ptr);\n   Add(buf);\n}\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\n////////////////////////////////////////////////////////////////////////////////\n// VmaJsonWriter\n\n#if VMA_STATS_STRING_ENABLED\n\nclass VmaJsonWriter\n{\n   VMA_CLASS_NO_COPY(VmaJsonWriter)\npublic:\n   VmaJsonWriter(const VkAllocationCallbacks* pAllocationCallbacks, VmaStringBuilder& sb);\n   ~VmaJsonWriter();\n\n   void BeginObject(bool singleLine = false);\n   void EndObject();\n\n   void BeginArray(bool singleLine = false);\n   void EndArray();\n\n   void WriteString(const char* pStr);\n   void BeginString(const char* pStr = VMA_NULL);\n   void ContinueString(const char* pStr);\n   void ContinueString(uint32_t n);\n   void ContinueString(uint64_t n);\n   void ContinueString_Pointer(const void* ptr);\n   void EndString(const char* pStr = VMA_NULL);\n\n   void WriteNumber(uint32_t n);\n   void WriteNumber(uint64_t n);\n   void WriteBool(bool b);\n   void WriteNull();\n\nprivate:\n   static const char* const INDENT;\n\n   enum COLLECTION_TYPE\n   {\n      COLLECTION_TYPE_OBJECT,\n      COLLECTION_TYPE_ARRAY,\n   };\n   struct StackItem\n   {\n      COLLECTION_TYPE type;\n      uint32_t valueCount;\n      bool singleLineMode;\n   };\n\n   VmaStringBuilder& m_SB;\n   VmaVector< StackItem, VmaStlAllocator<StackItem> > m_Stack;\n   bool m_InsideString;\n\n   void BeginValue(bool isString);\n   void WriteIndent(bool oneLess = false);\n};\n\nconst char* const VmaJsonWriter::INDENT = \"  \";\n\nVmaJsonWriter::VmaJsonWriter(const VkAllocationCallbacks* pAllocationCallbacks, VmaStringBuilder& sb) :\n   m_SB(sb),\n   m_Stack(VmaStlAllocator<StackItem>(pAllocationCallbacks)),\n   m_InsideString(false)\n{\n}\n\nVmaJsonWriter::~VmaJsonWriter()\n{\n   VMA_ASSERT(!m_InsideString);\n   VMA_ASSERT(m_Stack.empty());\n}\n\nvoid VmaJsonWriter::BeginObject(bool singleLine)\n{\n   VMA_ASSERT(!m_InsideString);\n\n   BeginValue(false);\n   m_SB.Add('{');\n\n   StackItem item;\n   item.type = COLLECTION_TYPE_OBJECT;\n   item.valueCount = 0;\n   item.singleLineMode = singleLine;\n   m_Stack.push_back(item);\n}\n\nvoid VmaJsonWriter::EndObject()\n{\n   VMA_ASSERT(!m_InsideString);\n\n   WriteIndent(true);\n   m_SB.Add('}');\n\n   VMA_ASSERT(!m_Stack.empty() && m_Stack.back().type == COLLECTION_TYPE_OBJECT);\n   m_Stack.pop_back();\n}\n\nvoid VmaJsonWriter::BeginArray(bool singleLine)\n{\n   VMA_ASSERT(!m_InsideString);\n\n   BeginValue(false);\n   m_SB.Add('[');\n\n   StackItem item;\n   item.type = COLLECTION_TYPE_ARRAY;\n   item.valueCount = 0;\n   item.singleLineMode = singleLine;\n   m_Stack.push_back(item);\n}\n\nvoid VmaJsonWriter::EndArray()\n{\n   VMA_ASSERT(!m_InsideString);\n\n   WriteIndent(true);\n   m_SB.Add(']');\n\n   VMA_ASSERT(!m_Stack.empty() && m_Stack.back().type == COLLECTION_TYPE_ARRAY);\n   m_Stack.pop_back();\n}\n\nvoid VmaJsonWriter::WriteString(const char* pStr)\n{\n   BeginString(pStr);\n   EndString();\n}\n\nvoid VmaJsonWriter::BeginString(const char* pStr)\n{\n   VMA_ASSERT(!m_InsideString);\n\n   BeginValue(true);\n   m_SB.Add('\"');\n   m_InsideString = true;\n   if (pStr != VMA_NULL && pStr[0] != '\\0') {\n      ContinueString(pStr);\n   }\n}\n\nvoid VmaJsonWriter::ContinueString(const char* pStr)\n{\n   VMA_ASSERT(m_InsideString);\n\n   const size_t strLen = strlen(pStr);\n   for (size_t i = 0; i < strLen; ++i) {\n      char ch = pStr[i];\n      if (ch == '\\\\') {\n         m_SB.Add(\"\\\\\\\\\");\n      } else if (ch == '\"') {\n         m_SB.Add(\"\\\\\\\"\");\n      } else if (ch >= 32) {\n         m_SB.Add(ch);\n      } else switch (ch) {\n      case '\\b':\n         m_SB.Add(\"\\\\b\");\n         break;\n      case '\\f':\n         m_SB.Add(\"\\\\f\");\n         break;\n      case '\\n':\n         m_SB.Add(\"\\\\n\");\n         break;\n      case '\\r':\n         m_SB.Add(\"\\\\r\");\n         break;\n      case '\\t':\n         m_SB.Add(\"\\\\t\");\n         break;\n      default:\n         VMA_ASSERT(0 && \"Character not currently supported.\");\n         break;\n      }\n   }\n}\n\nvoid VmaJsonWriter::ContinueString(uint32_t n)\n{\n   VMA_ASSERT(m_InsideString);\n   m_SB.AddNumber(n);\n}\n\nvoid VmaJsonWriter::ContinueString(uint64_t n)\n{\n   VMA_ASSERT(m_InsideString);\n   m_SB.AddNumber(n);\n}\n\nvoid VmaJsonWriter::ContinueString_Pointer(const void* ptr)\n{\n   VMA_ASSERT(m_InsideString);\n   m_SB.AddPointer(ptr);\n}\n\nvoid VmaJsonWriter::EndString(const char* pStr)\n{\n   VMA_ASSERT(m_InsideString);\n   if (pStr != VMA_NULL && pStr[0] != '\\0') {\n      ContinueString(pStr);\n   }\n   m_SB.Add('\"');\n   m_InsideString = false;\n}\n\nvoid VmaJsonWriter::WriteNumber(uint32_t n)\n{\n   VMA_ASSERT(!m_InsideString);\n   BeginValue(false);\n   m_SB.AddNumber(n);\n}\n\nvoid VmaJsonWriter::WriteNumber(uint64_t n)\n{\n   VMA_ASSERT(!m_InsideString);\n   BeginValue(false);\n   m_SB.AddNumber(n);\n}\n\nvoid VmaJsonWriter::WriteBool(bool b)\n{\n   VMA_ASSERT(!m_InsideString);\n   BeginValue(false);\n   m_SB.Add(b ? \"true\" : \"false\");\n}\n\nvoid VmaJsonWriter::WriteNull()\n{\n   VMA_ASSERT(!m_InsideString);\n   BeginValue(false);\n   m_SB.Add(\"null\");\n}\n\nvoid VmaJsonWriter::BeginValue(bool isString)\n{\n   if (!m_Stack.empty()) {\n      StackItem& currItem = m_Stack.back();\n      if (currItem.type == COLLECTION_TYPE_OBJECT &&\n          currItem.valueCount % 2 == 0) {\n         VMA_ASSERT(isString);\n      }\n\n      if (currItem.type == COLLECTION_TYPE_OBJECT &&\n          currItem.valueCount % 2 != 0) {\n         m_SB.Add(\": \");\n      } else if (currItem.valueCount > 0) {\n         m_SB.Add(\", \");\n         WriteIndent();\n      } else {\n         WriteIndent();\n      }\n      ++currItem.valueCount;\n   }\n}\n\nvoid VmaJsonWriter::WriteIndent(bool oneLess)\n{\n   if (!m_Stack.empty() && !m_Stack.back().singleLineMode) {\n      m_SB.AddNewLine();\n\n      size_t count = m_Stack.size();\n      if (count > 0 && oneLess) {\n         --count;\n      }\n      for (size_t i = 0; i < count; ++i) {\n         m_SB.Add(INDENT);\n      }\n   }\n}\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\n////////////////////////////////////////////////////////////////////////////////\n\nvoid VmaAllocation_T::SetUserData(VmaAllocator hAllocator, void* pUserData)\n{\n   if (IsUserDataString()) {\n      VMA_ASSERT(pUserData == VMA_NULL || pUserData != m_pUserData);\n\n      FreeUserDataString(hAllocator);\n\n      if (pUserData != VMA_NULL) {\n         m_pUserData = VmaCreateStringCopy(hAllocator->GetAllocationCallbacks(), (const char*)pUserData);\n      }\n   } else {\n      m_pUserData = pUserData;\n   }\n}\n\nvoid VmaAllocation_T::ChangeBlockAllocation(\n   VmaAllocator hAllocator,\n   VmaDeviceMemoryBlock* block,\n   VkDeviceSize offset)\n{\n   VMA_ASSERT(block != VMA_NULL);\n   VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK);\n\n   // Move mapping reference counter from old block to new block.\n   if (block != m_BlockAllocation.m_Block) {\n      uint32_t mapRefCount = m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP;\n      if (IsPersistentMap())\n         ++mapRefCount;\n      m_BlockAllocation.m_Block->Unmap(hAllocator, mapRefCount);\n      block->Map(hAllocator, mapRefCount, VMA_NULL);\n   }\n\n   m_BlockAllocation.m_Block = block;\n   m_BlockAllocation.m_Offset = offset;\n}\n\nvoid VmaAllocation_T::ChangeOffset(VkDeviceSize newOffset)\n{\n   VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK);\n   m_BlockAllocation.m_Offset = newOffset;\n}\n\nVkDeviceSize VmaAllocation_T::GetOffset() const\n{\n   switch (m_Type) {\n   case ALLOCATION_TYPE_BLOCK:\n      return m_BlockAllocation.m_Offset;\n   case ALLOCATION_TYPE_DEDICATED:\n      return 0;\n   default:\n      VMA_ASSERT(0);\n      return 0;\n   }\n}\n\nVkDeviceMemory VmaAllocation_T::GetMemory() const\n{\n   switch (m_Type) {\n   case ALLOCATION_TYPE_BLOCK:\n      return m_BlockAllocation.m_Block->GetDeviceMemory();\n   case ALLOCATION_TYPE_DEDICATED:\n      return m_DedicatedAllocation.m_hMemory;\n   default:\n      VMA_ASSERT(0);\n      return VK_NULL_HANDLE;\n   }\n}\n\nvoid* VmaAllocation_T::GetMappedData() const\n{\n   switch (m_Type) {\n   case ALLOCATION_TYPE_BLOCK:\n      if (m_MapCount != 0) {\n         void* pBlockData = m_BlockAllocation.m_Block->GetMappedData();\n         VMA_ASSERT(pBlockData != VMA_NULL);\n         return (char*)pBlockData + m_BlockAllocation.m_Offset;\n      } else {\n         return VMA_NULL;\n      }\n      break;\n   case ALLOCATION_TYPE_DEDICATED:\n      VMA_ASSERT((m_DedicatedAllocation.m_pMappedData != VMA_NULL) == (m_MapCount != 0));\n      return m_DedicatedAllocation.m_pMappedData;\n   default:\n      VMA_ASSERT(0);\n      return VMA_NULL;\n   }\n}\n\nbool VmaAllocation_T::CanBecomeLost() const\n{\n   switch (m_Type) {\n   case ALLOCATION_TYPE_BLOCK:\n      return m_BlockAllocation.m_CanBecomeLost;\n   case ALLOCATION_TYPE_DEDICATED:\n      return false;\n   default:\n      VMA_ASSERT(0);\n      return false;\n   }\n}\n\nbool VmaAllocation_T::MakeLost(uint32_t currentFrameIndex, uint32_t frameInUseCount)\n{\n   VMA_ASSERT(CanBecomeLost());\n\n   /*\n   Warning: This is a carefully designed algorithm.\n   Do not modify unless you really know what you're doing :)\n   */\n   uint32_t localLastUseFrameIndex = GetLastUseFrameIndex();\n   for (;;) {\n      if (localLastUseFrameIndex == VMA_FRAME_INDEX_LOST) {\n         VMA_ASSERT(0);\n         return false;\n      } else if (localLastUseFrameIndex + frameInUseCount >= currentFrameIndex) {\n         return false;\n      } else // Last use time earlier than current time.\n      {\n         if (CompareExchangeLastUseFrameIndex(localLastUseFrameIndex, VMA_FRAME_INDEX_LOST)) {\n            // Setting hAllocation.LastUseFrameIndex atomic to VMA_FRAME_INDEX_LOST is enough to mark it as LOST.\n            // Calling code just needs to unregister this allocation in owning VmaDeviceMemoryBlock.\n            return true;\n         }\n      }\n   }\n}\n\n#if VMA_STATS_STRING_ENABLED\n\n// Correspond to values of enum VmaSuballocationType.\nstatic const char* VMA_SUBALLOCATION_TYPE_NAMES[] = {\n    \"FREE\",\n    \"UNKNOWN\",\n    \"BUFFER\",\n    \"IMAGE_UNKNOWN\",\n    \"IMAGE_LINEAR\",\n    \"IMAGE_OPTIMAL\",\n};\n\nvoid VmaAllocation_T::PrintParameters(class VmaJsonWriter& json) const\n{\n   json.WriteString(\"Type\");\n   json.WriteString(VMA_SUBALLOCATION_TYPE_NAMES[m_SuballocationType]);\n\n   json.WriteString(\"Size\");\n   json.WriteNumber(m_Size);\n\n   if (m_pUserData != VMA_NULL) {\n      json.WriteString(\"UserData\");\n      if (IsUserDataString()) {\n         json.WriteString((const char*)m_pUserData);\n      } else {\n         json.BeginString();\n         json.ContinueString_Pointer(m_pUserData);\n         json.EndString();\n      }\n   }\n\n   json.WriteString(\"CreationFrameIndex\");\n   json.WriteNumber(m_CreationFrameIndex);\n\n   json.WriteString(\"LastUseFrameIndex\");\n   json.WriteNumber(GetLastUseFrameIndex());\n\n   if (m_BufferImageUsage != 0) {\n      json.WriteString(\"Usage\");\n      json.WriteNumber(m_BufferImageUsage);\n   }\n}\n\n#endif\n\nvoid VmaAllocation_T::FreeUserDataString(VmaAllocator hAllocator)\n{\n   VMA_ASSERT(IsUserDataString());\n   VmaFreeString(hAllocator->GetAllocationCallbacks(), (char*)m_pUserData);\n   m_pUserData = VMA_NULL;\n}\n\nvoid VmaAllocation_T::BlockAllocMap()\n{\n   VMA_ASSERT(GetType() == ALLOCATION_TYPE_BLOCK);\n\n   if ((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) < 0x7F) {\n      ++m_MapCount;\n   } else {\n      VMA_ASSERT(0 && \"Allocation mapped too many times simultaneously.\");\n   }\n}\n\nvoid VmaAllocation_T::BlockAllocUnmap()\n{\n   VMA_ASSERT(GetType() == ALLOCATION_TYPE_BLOCK);\n\n   if ((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) != 0) {\n      --m_MapCount;\n   } else {\n      VMA_ASSERT(0 && \"Unmapping allocation not previously mapped.\");\n   }\n}\n\nVkResult VmaAllocation_T::DedicatedAllocMap(VmaAllocator hAllocator, void** ppData)\n{\n   VMA_ASSERT(GetType() == ALLOCATION_TYPE_DEDICATED);\n\n   if (m_MapCount != 0) {\n      if ((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) < 0x7F) {\n         VMA_ASSERT(m_DedicatedAllocation.m_pMappedData != VMA_NULL);\n         *ppData = m_DedicatedAllocation.m_pMappedData;\n         ++m_MapCount;\n         return VK_SUCCESS;\n      } else {\n         VMA_ASSERT(0 && \"Dedicated allocation mapped too many times simultaneously.\");\n         return VK_ERROR_MEMORY_MAP_FAILED;\n      }\n   } else {\n      VkResult result = (*hAllocator->GetVulkanFunctions().vkMapMemory)(\n         hAllocator->m_hDevice,\n         m_DedicatedAllocation.m_hMemory,\n         0, // offset\n         VK_WHOLE_SIZE,\n         0, // flags\n         ppData);\n      if (result == VK_SUCCESS) {\n         m_DedicatedAllocation.m_pMappedData = *ppData;\n         m_MapCount = 1;\n      }\n      return result;\n   }\n}\n\nvoid VmaAllocation_T::DedicatedAllocUnmap(VmaAllocator hAllocator)\n{\n   VMA_ASSERT(GetType() == ALLOCATION_TYPE_DEDICATED);\n\n   if ((m_MapCount & ~MAP_COUNT_FLAG_PERSISTENT_MAP) != 0) {\n      --m_MapCount;\n      if (m_MapCount == 0) {\n         m_DedicatedAllocation.m_pMappedData = VMA_NULL;\n         (*hAllocator->GetVulkanFunctions().vkUnmapMemory)(\n            hAllocator->m_hDevice,\n            m_DedicatedAllocation.m_hMemory);\n      }\n   } else {\n      VMA_ASSERT(0 && \"Unmapping dedicated allocation not previously mapped.\");\n   }\n}\n\n#if VMA_STATS_STRING_ENABLED\n\nstatic void VmaPrintStatInfo(VmaJsonWriter& json, const VmaStatInfo& stat)\n{\n   json.BeginObject();\n\n   json.WriteString(\"Blocks\");\n   json.WriteNumber(stat.blockCount);\n\n   json.WriteString(\"Allocations\");\n   json.WriteNumber(stat.allocationCount);\n\n   json.WriteString(\"UnusedRanges\");\n   json.WriteNumber(stat.unusedRangeCount);\n\n   json.WriteString(\"UsedBytes\");\n   json.WriteNumber(stat.usedBytes);\n\n   json.WriteString(\"UnusedBytes\");\n   json.WriteNumber(stat.unusedBytes);\n\n   if (stat.allocationCount > 1) {\n      json.WriteString(\"AllocationSize\");\n      json.BeginObject(true);\n      json.WriteString(\"Min\");\n      json.WriteNumber(stat.allocationSizeMin);\n      json.WriteString(\"Avg\");\n      json.WriteNumber(stat.allocationSizeAvg);\n      json.WriteString(\"Max\");\n      json.WriteNumber(stat.allocationSizeMax);\n      json.EndObject();\n   }\n\n   if (stat.unusedRangeCount > 1) {\n      json.WriteString(\"UnusedRangeSize\");\n      json.BeginObject(true);\n      json.WriteString(\"Min\");\n      json.WriteNumber(stat.unusedRangeSizeMin);\n      json.WriteString(\"Avg\");\n      json.WriteNumber(stat.unusedRangeSizeAvg);\n      json.WriteString(\"Max\");\n      json.WriteNumber(stat.unusedRangeSizeMax);\n      json.EndObject();\n   }\n\n   json.EndObject();\n}\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\nstruct VmaSuballocationItemSizeLess\n{\n   bool operator()(\n      const VmaSuballocationList::iterator lhs,\n      const VmaSuballocationList::iterator rhs) const\n   {\n      return lhs->size < rhs->size;\n   }\n   bool operator()(\n      const VmaSuballocationList::iterator lhs,\n      VkDeviceSize rhsSize) const\n   {\n      return lhs->size < rhsSize;\n   }\n};\n\n\n////////////////////////////////////////////////////////////////////////////////\n// class VmaBlockMetadata\n\nVmaBlockMetadata::VmaBlockMetadata(VmaAllocator hAllocator) :\n   m_Size(0),\n   m_pAllocationCallbacks(hAllocator->GetAllocationCallbacks())\n{\n}\n\n#if VMA_STATS_STRING_ENABLED\n\nvoid VmaBlockMetadata::PrintDetailedMap_Begin(class VmaJsonWriter& json,\n                                              VkDeviceSize unusedBytes,\n                                              size_t allocationCount,\n                                              size_t unusedRangeCount) const\n{\n   json.BeginObject();\n\n   json.WriteString(\"TotalBytes\");\n   json.WriteNumber(GetSize());\n\n   json.WriteString(\"UnusedBytes\");\n   json.WriteNumber(unusedBytes);\n\n   json.WriteString(\"Allocations\");\n   json.WriteNumber((uint64_t)allocationCount);\n\n   json.WriteString(\"UnusedRanges\");\n   json.WriteNumber((uint64_t)unusedRangeCount);\n\n   json.WriteString(\"Suballocations\");\n   json.BeginArray();\n}\n\nvoid VmaBlockMetadata::PrintDetailedMap_Allocation(class VmaJsonWriter& json,\n                                                   VkDeviceSize offset,\n                                                   VmaAllocation hAllocation) const\n{\n   json.BeginObject(true);\n\n   json.WriteString(\"Offset\");\n   json.WriteNumber(offset);\n\n   hAllocation->PrintParameters(json);\n\n   json.EndObject();\n}\n\nvoid VmaBlockMetadata::PrintDetailedMap_UnusedRange(class VmaJsonWriter& json,\n                                                    VkDeviceSize offset,\n                                                    VkDeviceSize size) const\n{\n   json.BeginObject(true);\n\n   json.WriteString(\"Offset\");\n   json.WriteNumber(offset);\n\n   json.WriteString(\"Type\");\n   json.WriteString(VMA_SUBALLOCATION_TYPE_NAMES[VMA_SUBALLOCATION_TYPE_FREE]);\n\n   json.WriteString(\"Size\");\n   json.WriteNumber(size);\n\n   json.EndObject();\n}\n\nvoid VmaBlockMetadata::PrintDetailedMap_End(class VmaJsonWriter& json) const\n{\n   json.EndArray();\n   json.EndObject();\n}\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\n////////////////////////////////////////////////////////////////////////////////\n// class VmaBlockMetadata_Generic\n\nVmaBlockMetadata_Generic::VmaBlockMetadata_Generic(VmaAllocator hAllocator) :\n   VmaBlockMetadata(hAllocator),\n   m_FreeCount(0),\n   m_SumFreeSize(0),\n   m_Suballocations(VmaStlAllocator<VmaSuballocation>(hAllocator->GetAllocationCallbacks())),\n   m_FreeSuballocationsBySize(VmaStlAllocator<VmaSuballocationList::iterator>(hAllocator->GetAllocationCallbacks()))\n{\n}\n\nVmaBlockMetadata_Generic::~VmaBlockMetadata_Generic()\n{\n}\n\nvoid VmaBlockMetadata_Generic::Init(VkDeviceSize size)\n{\n   VmaBlockMetadata::Init(size);\n\n   m_FreeCount = 1;\n   m_SumFreeSize = size;\n\n   VmaSuballocation suballoc = {};\n   suballoc.offset = 0;\n   suballoc.size = size;\n   suballoc.type = VMA_SUBALLOCATION_TYPE_FREE;\n   suballoc.hAllocation = VK_NULL_HANDLE;\n\n   VMA_ASSERT(size > VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER);\n   m_Suballocations.push_back(suballoc);\n   VmaSuballocationList::iterator suballocItem = m_Suballocations.end();\n   --suballocItem;\n   m_FreeSuballocationsBySize.push_back(suballocItem);\n}\n\nbool VmaBlockMetadata_Generic::Validate() const\n{\n   VMA_VALIDATE(!m_Suballocations.empty());\n\n   // Expected offset of new suballocation as calculated from previous ones.\n   VkDeviceSize calculatedOffset = 0;\n   // Expected number of free suballocations as calculated from traversing their list.\n   uint32_t calculatedFreeCount = 0;\n   // Expected sum size of free suballocations as calculated from traversing their list.\n   VkDeviceSize calculatedSumFreeSize = 0;\n   // Expected number of free suballocations that should be registered in\n   // m_FreeSuballocationsBySize calculated from traversing their list.\n   size_t freeSuballocationsToRegister = 0;\n   // True if previous visited suballocation was free.\n   bool prevFree = false;\n\n   for (VmaSuballocationList::const_iterator suballocItem = m_Suballocations.cbegin();\n        suballocItem != m_Suballocations.cend();\n        ++suballocItem) {\n      const VmaSuballocation& subAlloc = *suballocItem;\n\n      // Actual offset of this suballocation doesn't match expected one.\n      VMA_VALIDATE(subAlloc.offset == calculatedOffset);\n\n      const bool currFree = (subAlloc.type == VMA_SUBALLOCATION_TYPE_FREE);\n      // Two adjacent free suballocations are invalid. They should be merged.\n      VMA_VALIDATE(!prevFree || !currFree);\n\n      VMA_VALIDATE(currFree == (subAlloc.hAllocation == VK_NULL_HANDLE));\n\n      if (currFree) {\n         calculatedSumFreeSize += subAlloc.size;\n         ++calculatedFreeCount;\n         if (subAlloc.size >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) {\n            ++freeSuballocationsToRegister;\n         }\n\n         // Margin required between allocations - every free space must be at least that large.\n         VMA_VALIDATE(subAlloc.size >= VMA_DEBUG_MARGIN);\n      } else {\n         VMA_VALIDATE(subAlloc.hAllocation->GetOffset() == subAlloc.offset);\n         VMA_VALIDATE(subAlloc.hAllocation->GetSize() == subAlloc.size);\n\n         // Margin required between allocations - previous allocation must be free.\n         VMA_VALIDATE(VMA_DEBUG_MARGIN == 0 || prevFree);\n      }\n\n      calculatedOffset += subAlloc.size;\n      prevFree = currFree;\n   }\n\n   // Number of free suballocations registered in m_FreeSuballocationsBySize doesn't\n   // match expected one.\n   VMA_VALIDATE(m_FreeSuballocationsBySize.size() == freeSuballocationsToRegister);\n\n   VkDeviceSize lastSize = 0;\n   for (size_t i = 0; i < m_FreeSuballocationsBySize.size(); ++i) {\n      VmaSuballocationList::iterator suballocItem = m_FreeSuballocationsBySize[i];\n\n      // Only free suballocations can be registered in m_FreeSuballocationsBySize.\n      VMA_VALIDATE(suballocItem->type == VMA_SUBALLOCATION_TYPE_FREE);\n      // They must be sorted by size ascending.\n      VMA_VALIDATE(suballocItem->size >= lastSize);\n\n      lastSize = suballocItem->size;\n   }\n\n   // Check if totals match calculacted values.\n   VMA_VALIDATE(ValidateFreeSuballocationList());\n   VMA_VALIDATE(calculatedOffset == GetSize());\n   VMA_VALIDATE(calculatedSumFreeSize == m_SumFreeSize);\n   VMA_VALIDATE(calculatedFreeCount == m_FreeCount);\n\n   return true;\n}\n\nVkDeviceSize VmaBlockMetadata_Generic::GetUnusedRangeSizeMax() const\n{\n   if (!m_FreeSuballocationsBySize.empty()) {\n      return m_FreeSuballocationsBySize.back()->size;\n   } else {\n      return 0;\n   }\n}\n\nbool VmaBlockMetadata_Generic::IsEmpty() const\n{\n   return (m_Suballocations.size() == 1) && (m_FreeCount == 1);\n}\n\nvoid VmaBlockMetadata_Generic::CalcAllocationStatInfo(VmaStatInfo& outInfo) const\n{\n   outInfo.blockCount = 1;\n\n   const uint32_t rangeCount = (uint32_t)m_Suballocations.size();\n   outInfo.allocationCount = rangeCount - m_FreeCount;\n   outInfo.unusedRangeCount = m_FreeCount;\n\n   outInfo.unusedBytes = m_SumFreeSize;\n   outInfo.usedBytes = GetSize() - outInfo.unusedBytes;\n\n   outInfo.allocationSizeMin = UINT64_MAX;\n   outInfo.allocationSizeMax = 0;\n   outInfo.unusedRangeSizeMin = UINT64_MAX;\n   outInfo.unusedRangeSizeMax = 0;\n\n   for (VmaSuballocationList::const_iterator suballocItem = m_Suballocations.cbegin();\n        suballocItem != m_Suballocations.cend();\n        ++suballocItem) {\n      const VmaSuballocation& suballoc = *suballocItem;\n      if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE) {\n         outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size);\n         outInfo.allocationSizeMax = VMA_MAX(outInfo.allocationSizeMax, suballoc.size);\n      } else {\n         outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, suballoc.size);\n         outInfo.unusedRangeSizeMax = VMA_MAX(outInfo.unusedRangeSizeMax, suballoc.size);\n      }\n   }\n}\n\nvoid VmaBlockMetadata_Generic::AddPoolStats(VmaPoolStats& inoutStats) const\n{\n   const uint32_t rangeCount = (uint32_t)m_Suballocations.size();\n\n   inoutStats.size += GetSize();\n   inoutStats.unusedSize += m_SumFreeSize;\n   inoutStats.allocationCount += rangeCount - m_FreeCount;\n   inoutStats.unusedRangeCount += m_FreeCount;\n   inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, GetUnusedRangeSizeMax());\n}\n\n#if VMA_STATS_STRING_ENABLED\n\nvoid VmaBlockMetadata_Generic::PrintDetailedMap(class VmaJsonWriter& json) const\n{\n   PrintDetailedMap_Begin(json,\n                          m_SumFreeSize, // unusedBytes\n                          m_Suballocations.size() - (size_t)m_FreeCount, // allocationCount\n                          m_FreeCount); // unusedRangeCount\n\n   size_t i = 0;\n   for (VmaSuballocationList::const_iterator suballocItem = m_Suballocations.cbegin();\n        suballocItem != m_Suballocations.cend();\n        ++suballocItem, ++i) {\n      if (suballocItem->type == VMA_SUBALLOCATION_TYPE_FREE) {\n         PrintDetailedMap_UnusedRange(json, suballocItem->offset, suballocItem->size);\n      } else {\n         PrintDetailedMap_Allocation(json, suballocItem->offset, suballocItem->hAllocation);\n      }\n   }\n\n   PrintDetailedMap_End(json);\n}\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\nbool VmaBlockMetadata_Generic::CreateAllocationRequest(\n   uint32_t currentFrameIndex,\n   uint32_t frameInUseCount,\n   VkDeviceSize bufferImageGranularity,\n   VkDeviceSize allocSize,\n   VkDeviceSize allocAlignment,\n   bool upperAddress,\n   VmaSuballocationType allocType,\n   bool canMakeOtherLost,\n   uint32_t strategy,\n   VmaAllocationRequest* pAllocationRequest)\n{\n   VMA_ASSERT(allocSize > 0);\n   VMA_ASSERT(!upperAddress);\n   VMA_ASSERT(allocType != VMA_SUBALLOCATION_TYPE_FREE);\n   VMA_ASSERT(pAllocationRequest != VMA_NULL);\n   VMA_HEAVY_ASSERT(Validate());\n\n   pAllocationRequest->type = VmaAllocationRequestType::Normal;\n\n   // There is not enough total free space in this block to fullfill the request: Early return.\n   if (canMakeOtherLost == false &&\n       m_SumFreeSize < allocSize + 2 * VMA_DEBUG_MARGIN) {\n      return false;\n   }\n\n   // New algorithm, efficiently searching freeSuballocationsBySize.\n   const size_t freeSuballocCount = m_FreeSuballocationsBySize.size();\n   if (freeSuballocCount > 0) {\n      if (strategy == VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT) {\n         // Find first free suballocation with size not less than allocSize + 2 * VMA_DEBUG_MARGIN.\n         VmaSuballocationList::iterator* const it = VmaBinaryFindFirstNotLess(\n            m_FreeSuballocationsBySize.data(),\n            m_FreeSuballocationsBySize.data() + freeSuballocCount,\n            allocSize + 2 * VMA_DEBUG_MARGIN,\n            VmaSuballocationItemSizeLess());\n         size_t index = it - m_FreeSuballocationsBySize.data();\n         for (; index < freeSuballocCount; ++index) {\n            if (CheckAllocation(\n               currentFrameIndex,\n               frameInUseCount,\n               bufferImageGranularity,\n               allocSize,\n               allocAlignment,\n               allocType,\n               m_FreeSuballocationsBySize[index],\n               false, // canMakeOtherLost\n               &pAllocationRequest->offset,\n               &pAllocationRequest->itemsToMakeLostCount,\n               &pAllocationRequest->sumFreeSize,\n               &pAllocationRequest->sumItemSize)) {\n               pAllocationRequest->item = m_FreeSuballocationsBySize[index];\n               return true;\n            }\n         }\n      } else if (strategy == VMA_ALLOCATION_INTERNAL_STRATEGY_MIN_OFFSET) {\n         for (VmaSuballocationList::iterator it = m_Suballocations.begin();\n              it != m_Suballocations.end();\n              ++it) {\n            if (it->type == VMA_SUBALLOCATION_TYPE_FREE && CheckAllocation(\n               currentFrameIndex,\n               frameInUseCount,\n               bufferImageGranularity,\n               allocSize,\n               allocAlignment,\n               allocType,\n               it,\n               false, // canMakeOtherLost\n               &pAllocationRequest->offset,\n               &pAllocationRequest->itemsToMakeLostCount,\n               &pAllocationRequest->sumFreeSize,\n               &pAllocationRequest->sumItemSize)) {\n               pAllocationRequest->item = it;\n               return true;\n            }\n         }\n      } else // WORST_FIT, FIRST_FIT\n      {\n         // Search staring from biggest suballocations.\n         for (size_t index = freeSuballocCount; index--; ) {\n            if (CheckAllocation(\n               currentFrameIndex,\n               frameInUseCount,\n               bufferImageGranularity,\n               allocSize,\n               allocAlignment,\n               allocType,\n               m_FreeSuballocationsBySize[index],\n               false, // canMakeOtherLost\n               &pAllocationRequest->offset,\n               &pAllocationRequest->itemsToMakeLostCount,\n               &pAllocationRequest->sumFreeSize,\n               &pAllocationRequest->sumItemSize)) {\n               pAllocationRequest->item = m_FreeSuballocationsBySize[index];\n               return true;\n            }\n         }\n      }\n   }\n\n   if (canMakeOtherLost) {\n      // Brute-force algorithm. TODO: Come up with something better.\n\n      bool found = false;\n      VmaAllocationRequest tmpAllocRequest = {};\n      tmpAllocRequest.type = VmaAllocationRequestType::Normal;\n      for (VmaSuballocationList::iterator suballocIt = m_Suballocations.begin();\n           suballocIt != m_Suballocations.end();\n           ++suballocIt) {\n         if (suballocIt->type == VMA_SUBALLOCATION_TYPE_FREE ||\n             suballocIt->hAllocation->CanBecomeLost()) {\n            if (CheckAllocation(\n               currentFrameIndex,\n               frameInUseCount,\n               bufferImageGranularity,\n               allocSize,\n               allocAlignment,\n               allocType,\n               suballocIt,\n               canMakeOtherLost,\n               &tmpAllocRequest.offset,\n               &tmpAllocRequest.itemsToMakeLostCount,\n               &tmpAllocRequest.sumFreeSize,\n               &tmpAllocRequest.sumItemSize)) {\n               if (strategy == VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT) {\n                  *pAllocationRequest = tmpAllocRequest;\n                  pAllocationRequest->item = suballocIt;\n                  break;\n               }\n               if (!found || tmpAllocRequest.CalcCost() < pAllocationRequest->CalcCost()) {\n                  *pAllocationRequest = tmpAllocRequest;\n                  pAllocationRequest->item = suballocIt;\n                  found = true;\n               }\n            }\n         }\n      }\n\n      return found;\n   }\n\n   return false;\n}\n\nbool VmaBlockMetadata_Generic::MakeRequestedAllocationsLost(\n   uint32_t currentFrameIndex,\n   uint32_t frameInUseCount,\n   VmaAllocationRequest* pAllocationRequest)\n{\n   VMA_ASSERT(pAllocationRequest && pAllocationRequest->type == VmaAllocationRequestType::Normal);\n\n   while (pAllocationRequest->itemsToMakeLostCount > 0) {\n      if (pAllocationRequest->item->type == VMA_SUBALLOCATION_TYPE_FREE) {\n         ++pAllocationRequest->item;\n      }\n      VMA_ASSERT(pAllocationRequest->item != m_Suballocations.end());\n      VMA_ASSERT(pAllocationRequest->item->hAllocation != VK_NULL_HANDLE);\n      VMA_ASSERT(pAllocationRequest->item->hAllocation->CanBecomeLost());\n      if (pAllocationRequest->item->hAllocation->MakeLost(currentFrameIndex, frameInUseCount)) {\n         pAllocationRequest->item = FreeSuballocation(pAllocationRequest->item);\n         --pAllocationRequest->itemsToMakeLostCount;\n      } else {\n         return false;\n      }\n   }\n\n   VMA_HEAVY_ASSERT(Validate());\n   VMA_ASSERT(pAllocationRequest->item != m_Suballocations.end());\n   VMA_ASSERT(pAllocationRequest->item->type == VMA_SUBALLOCATION_TYPE_FREE);\n\n   return true;\n}\n\nuint32_t VmaBlockMetadata_Generic::MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount)\n{\n   uint32_t lostAllocationCount = 0;\n   for (VmaSuballocationList::iterator it = m_Suballocations.begin();\n        it != m_Suballocations.end();\n        ++it) {\n      if (it->type != VMA_SUBALLOCATION_TYPE_FREE &&\n          it->hAllocation->CanBecomeLost() &&\n          it->hAllocation->MakeLost(currentFrameIndex, frameInUseCount)) {\n         it = FreeSuballocation(it);\n         ++lostAllocationCount;\n      }\n   }\n   return lostAllocationCount;\n}\n\nVkResult VmaBlockMetadata_Generic::CheckCorruption(const void* pBlockData)\n{\n   for (VmaSuballocationList::iterator it = m_Suballocations.begin();\n        it != m_Suballocations.end();\n        ++it) {\n      if (it->type != VMA_SUBALLOCATION_TYPE_FREE) {\n         if (!VmaValidateMagicValue(pBlockData, it->offset - VMA_DEBUG_MARGIN)) {\n            VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED BEFORE VALIDATED ALLOCATION!\");\n            return VK_ERROR_VALIDATION_FAILED_EXT;\n         }\n         if (!VmaValidateMagicValue(pBlockData, it->offset + it->size)) {\n            VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!\");\n            return VK_ERROR_VALIDATION_FAILED_EXT;\n         }\n      }\n   }\n\n   return VK_SUCCESS;\n}\n\nvoid VmaBlockMetadata_Generic::Alloc(\n   const VmaAllocationRequest& request,\n   VmaSuballocationType type,\n   VkDeviceSize allocSize,\n   VmaAllocation hAllocation)\n{\n   VMA_ASSERT(request.type == VmaAllocationRequestType::Normal);\n   VMA_ASSERT(request.item != m_Suballocations.end());\n   VmaSuballocation& suballoc = *request.item;\n   // Given suballocation is a free block.\n   VMA_ASSERT(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);\n   // Given offset is inside this suballocation.\n   VMA_ASSERT(request.offset >= suballoc.offset);\n   const VkDeviceSize paddingBegin = request.offset - suballoc.offset;\n   VMA_ASSERT(suballoc.size >= paddingBegin + allocSize);\n   const VkDeviceSize paddingEnd = suballoc.size - paddingBegin - allocSize;\n\n   // Unregister this free suballocation from m_FreeSuballocationsBySize and update\n   // it to become used.\n   UnregisterFreeSuballocation(request.item);\n\n   suballoc.offset = request.offset;\n   suballoc.size = allocSize;\n   suballoc.type = type;\n   suballoc.hAllocation = hAllocation;\n\n   // If there are any free bytes remaining at the end, insert new free suballocation after current one.\n   if (paddingEnd) {\n      VmaSuballocation paddingSuballoc = {};\n      paddingSuballoc.offset = request.offset + allocSize;\n      paddingSuballoc.size = paddingEnd;\n      paddingSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE;\n      VmaSuballocationList::iterator next = request.item;\n      ++next;\n      const VmaSuballocationList::iterator paddingEndItem =\n         m_Suballocations.insert(next, paddingSuballoc);\n      RegisterFreeSuballocation(paddingEndItem);\n   }\n\n   // If there are any free bytes remaining at the beginning, insert new free suballocation before current one.\n   if (paddingBegin) {\n      VmaSuballocation paddingSuballoc = {};\n      paddingSuballoc.offset = request.offset - paddingBegin;\n      paddingSuballoc.size = paddingBegin;\n      paddingSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE;\n      const VmaSuballocationList::iterator paddingBeginItem =\n         m_Suballocations.insert(request.item, paddingSuballoc);\n      RegisterFreeSuballocation(paddingBeginItem);\n   }\n\n   // Update totals.\n   m_FreeCount = m_FreeCount - 1;\n   if (paddingBegin > 0) {\n      ++m_FreeCount;\n   }\n   if (paddingEnd > 0) {\n      ++m_FreeCount;\n   }\n   m_SumFreeSize -= allocSize;\n}\n\nvoid VmaBlockMetadata_Generic::Free(const VmaAllocation allocation)\n{\n   for (VmaSuballocationList::iterator suballocItem = m_Suballocations.begin();\n        suballocItem != m_Suballocations.end();\n        ++suballocItem) {\n      VmaSuballocation& suballoc = *suballocItem;\n      if (suballoc.hAllocation == allocation) {\n         FreeSuballocation(suballocItem);\n         VMA_HEAVY_ASSERT(Validate());\n         return;\n      }\n   }\n   VMA_ASSERT(0 && \"Not found!\");\n}\n\nvoid VmaBlockMetadata_Generic::FreeAtOffset(VkDeviceSize offset)\n{\n   for (VmaSuballocationList::iterator suballocItem = m_Suballocations.begin();\n        suballocItem != m_Suballocations.end();\n        ++suballocItem) {\n      VmaSuballocation& suballoc = *suballocItem;\n      if (suballoc.offset == offset) {\n         FreeSuballocation(suballocItem);\n         return;\n      }\n   }\n   VMA_ASSERT(0 && \"Not found!\");\n}\n\nbool VmaBlockMetadata_Generic::ValidateFreeSuballocationList() const\n{\n   VkDeviceSize lastSize = 0;\n   for (size_t i = 0, count = m_FreeSuballocationsBySize.size(); i < count; ++i) {\n      const VmaSuballocationList::iterator it = m_FreeSuballocationsBySize[i];\n\n      VMA_VALIDATE(it->type == VMA_SUBALLOCATION_TYPE_FREE);\n      VMA_VALIDATE(it->size >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER);\n      VMA_VALIDATE(it->size >= lastSize);\n      lastSize = it->size;\n   }\n   return true;\n}\n\nbool VmaBlockMetadata_Generic::CheckAllocation(\n   uint32_t currentFrameIndex,\n   uint32_t frameInUseCount,\n   VkDeviceSize bufferImageGranularity,\n   VkDeviceSize allocSize,\n   VkDeviceSize allocAlignment,\n   VmaSuballocationType allocType,\n   VmaSuballocationList::const_iterator suballocItem,\n   bool canMakeOtherLost,\n   VkDeviceSize* pOffset,\n   size_t* itemsToMakeLostCount,\n   VkDeviceSize* pSumFreeSize,\n   VkDeviceSize* pSumItemSize) const\n{\n   VMA_ASSERT(allocSize > 0);\n   VMA_ASSERT(allocType != VMA_SUBALLOCATION_TYPE_FREE);\n   VMA_ASSERT(suballocItem != m_Suballocations.cend());\n   VMA_ASSERT(pOffset != VMA_NULL);\n\n   *itemsToMakeLostCount = 0;\n   *pSumFreeSize = 0;\n   *pSumItemSize = 0;\n\n   if (canMakeOtherLost) {\n      if (suballocItem->type == VMA_SUBALLOCATION_TYPE_FREE) {\n         *pSumFreeSize = suballocItem->size;\n      } else {\n         if (suballocItem->hAllocation->CanBecomeLost() &&\n             suballocItem->hAllocation->GetLastUseFrameIndex() + frameInUseCount < currentFrameIndex) {\n            ++* itemsToMakeLostCount;\n            *pSumItemSize = suballocItem->size;\n         } else {\n            return false;\n         }\n      }\n\n      // Remaining size is too small for this request: Early return.\n      if (GetSize() - suballocItem->offset < allocSize) {\n         return false;\n      }\n\n      // Start from offset equal to beginning of this suballocation.\n      *pOffset = suballocItem->offset;\n\n      // Apply VMA_DEBUG_MARGIN at the beginning.\n      if (VMA_DEBUG_MARGIN > 0) {\n         *pOffset += VMA_DEBUG_MARGIN;\n      }\n\n      // Apply alignment.\n      *pOffset = VmaAlignUp(*pOffset, allocAlignment);\n\n      // Check previous suballocations for BufferImageGranularity conflicts.\n      // Make bigger alignment if necessary.\n      if (bufferImageGranularity > 1) {\n         bool bufferImageGranularityConflict = false;\n         VmaSuballocationList::const_iterator prevSuballocItem = suballocItem;\n         while (prevSuballocItem != m_Suballocations.cbegin()) {\n            --prevSuballocItem;\n            const VmaSuballocation& prevSuballoc = *prevSuballocItem;\n            if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, *pOffset, bufferImageGranularity)) {\n               if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType)) {\n                  bufferImageGranularityConflict = true;\n                  break;\n               }\n            } else\n               // Already on previous page.\n               break;\n         }\n         if (bufferImageGranularityConflict) {\n            *pOffset = VmaAlignUp(*pOffset, bufferImageGranularity);\n         }\n      }\n\n      // Now that we have final *pOffset, check if we are past suballocItem.\n      // If yes, return false - this function should be called for another suballocItem as starting point.\n      if (*pOffset >= suballocItem->offset + suballocItem->size) {\n         return false;\n      }\n\n      // Calculate padding at the beginning based on current offset.\n      const VkDeviceSize paddingBegin = *pOffset - suballocItem->offset;\n\n      // Calculate required margin at the end.\n      const VkDeviceSize requiredEndMargin = VMA_DEBUG_MARGIN;\n\n      const VkDeviceSize totalSize = paddingBegin + allocSize + requiredEndMargin;\n      // Another early return check.\n      if (suballocItem->offset + totalSize > GetSize()) {\n         return false;\n      }\n\n      // Advance lastSuballocItem until desired size is reached.\n      // Update itemsToMakeLostCount.\n      VmaSuballocationList::const_iterator lastSuballocItem = suballocItem;\n      if (totalSize > suballocItem->size) {\n         VkDeviceSize remainingSize = totalSize - suballocItem->size;\n         while (remainingSize > 0) {\n            ++lastSuballocItem;\n            if (lastSuballocItem == m_Suballocations.cend()) {\n               return false;\n            }\n            if (lastSuballocItem->type == VMA_SUBALLOCATION_TYPE_FREE) {\n               *pSumFreeSize += lastSuballocItem->size;\n            } else {\n               VMA_ASSERT(lastSuballocItem->hAllocation != VK_NULL_HANDLE);\n               if (lastSuballocItem->hAllocation->CanBecomeLost() &&\n                   lastSuballocItem->hAllocation->GetLastUseFrameIndex() + frameInUseCount < currentFrameIndex) {\n                  ++* itemsToMakeLostCount;\n                  *pSumItemSize += lastSuballocItem->size;\n               } else {\n                  return false;\n               }\n            }\n            remainingSize = (lastSuballocItem->size < remainingSize) ?\n               remainingSize - lastSuballocItem->size : 0;\n         }\n      }\n\n      // Check next suballocations for BufferImageGranularity conflicts.\n      // If conflict exists, we must mark more allocations lost or fail.\n      if (bufferImageGranularity > 1) {\n         VmaSuballocationList::const_iterator nextSuballocItem = lastSuballocItem;\n         ++nextSuballocItem;\n         while (nextSuballocItem != m_Suballocations.cend()) {\n            const VmaSuballocation& nextSuballoc = *nextSuballocItem;\n            if (VmaBlocksOnSamePage(*pOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) {\n               if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type)) {\n                  VMA_ASSERT(nextSuballoc.hAllocation != VK_NULL_HANDLE);\n                  if (nextSuballoc.hAllocation->CanBecomeLost() &&\n                      nextSuballoc.hAllocation->GetLastUseFrameIndex() + frameInUseCount < currentFrameIndex) {\n                     ++* itemsToMakeLostCount;\n                  } else {\n                     return false;\n                  }\n               }\n            } else {\n               // Already on next page.\n               break;\n            }\n            ++nextSuballocItem;\n         }\n      }\n   } else {\n      const VmaSuballocation& suballoc = *suballocItem;\n      VMA_ASSERT(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);\n\n      *pSumFreeSize = suballoc.size;\n\n      // Size of this suballocation is too small for this request: Early return.\n      if (suballoc.size < allocSize) {\n         return false;\n      }\n\n      // Start from offset equal to beginning of this suballocation.\n      *pOffset = suballoc.offset;\n\n      // Apply VMA_DEBUG_MARGIN at the beginning.\n      if (VMA_DEBUG_MARGIN > 0) {\n         *pOffset += VMA_DEBUG_MARGIN;\n      }\n\n      // Apply alignment.\n      *pOffset = VmaAlignUp(*pOffset, allocAlignment);\n\n      // Check previous suballocations for BufferImageGranularity conflicts.\n      // Make bigger alignment if necessary.\n      if (bufferImageGranularity > 1) {\n         bool bufferImageGranularityConflict = false;\n         VmaSuballocationList::const_iterator prevSuballocItem = suballocItem;\n         while (prevSuballocItem != m_Suballocations.cbegin()) {\n            --prevSuballocItem;\n            const VmaSuballocation& prevSuballoc = *prevSuballocItem;\n            if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, *pOffset, bufferImageGranularity)) {\n               if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType)) {\n                  bufferImageGranularityConflict = true;\n                  break;\n               }\n            } else\n               // Already on previous page.\n               break;\n         }\n         if (bufferImageGranularityConflict) {\n            *pOffset = VmaAlignUp(*pOffset, bufferImageGranularity);\n         }\n      }\n\n      // Calculate padding at the beginning based on current offset.\n      const VkDeviceSize paddingBegin = *pOffset - suballoc.offset;\n\n      // Calculate required margin at the end.\n      const VkDeviceSize requiredEndMargin = VMA_DEBUG_MARGIN;\n\n      // Fail if requested size plus margin before and after is bigger than size of this suballocation.\n      if (paddingBegin + allocSize + requiredEndMargin > suballoc.size) {\n         return false;\n      }\n\n      // Check next suballocations for BufferImageGranularity conflicts.\n      // If conflict exists, allocation cannot be made here.\n      if (bufferImageGranularity > 1) {\n         VmaSuballocationList::const_iterator nextSuballocItem = suballocItem;\n         ++nextSuballocItem;\n         while (nextSuballocItem != m_Suballocations.cend()) {\n            const VmaSuballocation& nextSuballoc = *nextSuballocItem;\n            if (VmaBlocksOnSamePage(*pOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) {\n               if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type)) {\n                  return false;\n               }\n            } else {\n               // Already on next page.\n               break;\n            }\n            ++nextSuballocItem;\n         }\n      }\n   }\n\n   // All tests passed: Success. pOffset is already filled.\n   return true;\n}\n\nvoid VmaBlockMetadata_Generic::MergeFreeWithNext(VmaSuballocationList::iterator item)\n{\n   VMA_ASSERT(item != m_Suballocations.end());\n   VMA_ASSERT(item->type == VMA_SUBALLOCATION_TYPE_FREE);\n\n   VmaSuballocationList::iterator nextItem = item;\n   ++nextItem;\n   VMA_ASSERT(nextItem != m_Suballocations.end());\n   VMA_ASSERT(nextItem->type == VMA_SUBALLOCATION_TYPE_FREE);\n\n   item->size += nextItem->size;\n   --m_FreeCount;\n   m_Suballocations.erase(nextItem);\n}\n\nVmaSuballocationList::iterator VmaBlockMetadata_Generic::FreeSuballocation(VmaSuballocationList::iterator suballocItem)\n{\n   // Change this suballocation to be marked as free.\n   VmaSuballocation& suballoc = *suballocItem;\n   suballoc.type = VMA_SUBALLOCATION_TYPE_FREE;\n   suballoc.hAllocation = VK_NULL_HANDLE;\n\n   // Update totals.\n   ++m_FreeCount;\n   m_SumFreeSize += suballoc.size;\n\n   // Merge with previous and/or next suballocation if it's also free.\n   bool mergeWithNext = false;\n   bool mergeWithPrev = false;\n\n   VmaSuballocationList::iterator nextItem = suballocItem;\n   ++nextItem;\n   if ((nextItem != m_Suballocations.end()) && (nextItem->type == VMA_SUBALLOCATION_TYPE_FREE)) {\n      mergeWithNext = true;\n   }\n\n   VmaSuballocationList::iterator prevItem = suballocItem;\n   if (suballocItem != m_Suballocations.begin()) {\n      --prevItem;\n      if (prevItem->type == VMA_SUBALLOCATION_TYPE_FREE) {\n         mergeWithPrev = true;\n      }\n   }\n\n   if (mergeWithNext) {\n      UnregisterFreeSuballocation(nextItem);\n      MergeFreeWithNext(suballocItem);\n   }\n\n   if (mergeWithPrev) {\n      UnregisterFreeSuballocation(prevItem);\n      MergeFreeWithNext(prevItem);\n      RegisterFreeSuballocation(prevItem);\n      return prevItem;\n   } else {\n      RegisterFreeSuballocation(suballocItem);\n      return suballocItem;\n   }\n}\n\nvoid VmaBlockMetadata_Generic::RegisterFreeSuballocation(VmaSuballocationList::iterator item)\n{\n   VMA_ASSERT(item->type == VMA_SUBALLOCATION_TYPE_FREE);\n   VMA_ASSERT(item->size > 0);\n\n   // You may want to enable this validation at the beginning or at the end of\n   // this function, depending on what do you want to check.\n   VMA_HEAVY_ASSERT(ValidateFreeSuballocationList());\n\n   if (item->size >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) {\n      if (m_FreeSuballocationsBySize.empty()) {\n         m_FreeSuballocationsBySize.push_back(item);\n      } else {\n         VmaVectorInsertSorted<VmaSuballocationItemSizeLess>(m_FreeSuballocationsBySize, item);\n      }\n   }\n\n   //VMA_HEAVY_ASSERT(ValidateFreeSuballocationList());\n}\n\n\nvoid VmaBlockMetadata_Generic::UnregisterFreeSuballocation(VmaSuballocationList::iterator item)\n{\n   VMA_ASSERT(item->type == VMA_SUBALLOCATION_TYPE_FREE);\n   VMA_ASSERT(item->size > 0);\n\n   // You may want to enable this validation at the beginning or at the end of\n   // this function, depending on what do you want to check.\n   VMA_HEAVY_ASSERT(ValidateFreeSuballocationList());\n\n   if (item->size >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) {\n      VmaSuballocationList::iterator* const it = VmaBinaryFindFirstNotLess(\n         m_FreeSuballocationsBySize.data(),\n         m_FreeSuballocationsBySize.data() + m_FreeSuballocationsBySize.size(),\n         item,\n         VmaSuballocationItemSizeLess());\n      for (size_t index = it - m_FreeSuballocationsBySize.data();\n           index < m_FreeSuballocationsBySize.size();\n           ++index) {\n         if (m_FreeSuballocationsBySize[index] == item) {\n            VmaVectorRemove(m_FreeSuballocationsBySize, index);\n            return;\n         }\n         VMA_ASSERT((m_FreeSuballocationsBySize[index]->size == item->size) && \"Not found.\");\n      }\n      VMA_ASSERT(0 && \"Not found.\");\n   }\n\n   //VMA_HEAVY_ASSERT(ValidateFreeSuballocationList());\n}\n\nbool VmaBlockMetadata_Generic::IsBufferImageGranularityConflictPossible(\n   VkDeviceSize bufferImageGranularity,\n   VmaSuballocationType& inOutPrevSuballocType) const\n{\n   if (bufferImageGranularity == 1 || IsEmpty()) {\n      return false;\n   }\n\n   VkDeviceSize minAlignment = VK_WHOLE_SIZE;\n   bool typeConflictFound = false;\n   for (VmaSuballocationList::const_iterator it = m_Suballocations.cbegin();\n        it != m_Suballocations.cend();\n        ++it) {\n      const VmaSuballocationType suballocType = it->type;\n      if (suballocType != VMA_SUBALLOCATION_TYPE_FREE) {\n         minAlignment = VMA_MIN(minAlignment, it->hAllocation->GetAlignment());\n         if (VmaIsBufferImageGranularityConflict(inOutPrevSuballocType, suballocType)) {\n            typeConflictFound = true;\n         }\n         inOutPrevSuballocType = suballocType;\n      }\n   }\n\n   return typeConflictFound || minAlignment >= bufferImageGranularity;\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// class VmaBlockMetadata_Linear\n\nVmaBlockMetadata_Linear::VmaBlockMetadata_Linear(VmaAllocator hAllocator) :\n   VmaBlockMetadata(hAllocator),\n   m_SumFreeSize(0),\n   m_Suballocations0(VmaStlAllocator<VmaSuballocation>(hAllocator->GetAllocationCallbacks())),\n   m_Suballocations1(VmaStlAllocator<VmaSuballocation>(hAllocator->GetAllocationCallbacks())),\n   m_1stVectorIndex(0),\n   m_2ndVectorMode(SECOND_VECTOR_EMPTY),\n   m_1stNullItemsBeginCount(0),\n   m_1stNullItemsMiddleCount(0),\n   m_2ndNullItemsCount(0)\n{\n}\n\nVmaBlockMetadata_Linear::~VmaBlockMetadata_Linear()\n{\n}\n\nvoid VmaBlockMetadata_Linear::Init(VkDeviceSize size)\n{\n   VmaBlockMetadata::Init(size);\n   m_SumFreeSize = size;\n}\n\nbool VmaBlockMetadata_Linear::Validate() const\n{\n   const SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n   const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n\n   VMA_VALIDATE(suballocations2nd.empty() == (m_2ndVectorMode == SECOND_VECTOR_EMPTY));\n   VMA_VALIDATE(!suballocations1st.empty() ||\n                suballocations2nd.empty() ||\n                m_2ndVectorMode != SECOND_VECTOR_RING_BUFFER);\n\n   if (!suballocations1st.empty()) {\n      // Null item at the beginning should be accounted into m_1stNullItemsBeginCount.\n      VMA_VALIDATE(suballocations1st[m_1stNullItemsBeginCount].hAllocation != VK_NULL_HANDLE);\n      // Null item at the end should be just pop_back().\n      VMA_VALIDATE(suballocations1st.back().hAllocation != VK_NULL_HANDLE);\n   }\n   if (!suballocations2nd.empty()) {\n      // Null item at the end should be just pop_back().\n      VMA_VALIDATE(suballocations2nd.back().hAllocation != VK_NULL_HANDLE);\n   }\n\n   VMA_VALIDATE(m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount <= suballocations1st.size());\n   VMA_VALIDATE(m_2ndNullItemsCount <= suballocations2nd.size());\n\n   VkDeviceSize sumUsedSize = 0;\n   const size_t suballoc1stCount = suballocations1st.size();\n   VkDeviceSize offset = VMA_DEBUG_MARGIN;\n\n   if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) {\n      const size_t suballoc2ndCount = suballocations2nd.size();\n      size_t nullItem2ndCount = 0;\n      for (size_t i = 0; i < suballoc2ndCount; ++i) {\n         const VmaSuballocation& suballoc = suballocations2nd[i];\n         const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);\n\n         VMA_VALIDATE(currFree == (suballoc.hAllocation == VK_NULL_HANDLE));\n         VMA_VALIDATE(suballoc.offset >= offset);\n\n         if (!currFree) {\n            VMA_VALIDATE(suballoc.hAllocation->GetOffset() == suballoc.offset);\n            VMA_VALIDATE(suballoc.hAllocation->GetSize() == suballoc.size);\n            sumUsedSize += suballoc.size;\n         } else {\n            ++nullItem2ndCount;\n         }\n\n         offset = suballoc.offset + suballoc.size + VMA_DEBUG_MARGIN;\n      }\n\n      VMA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount);\n   }\n\n   for (size_t i = 0; i < m_1stNullItemsBeginCount; ++i) {\n      const VmaSuballocation& suballoc = suballocations1st[i];\n      VMA_VALIDATE(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE &&\n                   suballoc.hAllocation == VK_NULL_HANDLE);\n   }\n\n   size_t nullItem1stCount = m_1stNullItemsBeginCount;\n\n   for (size_t i = m_1stNullItemsBeginCount; i < suballoc1stCount; ++i) {\n      const VmaSuballocation& suballoc = suballocations1st[i];\n      const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);\n\n      VMA_VALIDATE(currFree == (suballoc.hAllocation == VK_NULL_HANDLE));\n      VMA_VALIDATE(suballoc.offset >= offset);\n      VMA_VALIDATE(i >= m_1stNullItemsBeginCount || currFree);\n\n      if (!currFree) {\n         VMA_VALIDATE(suballoc.hAllocation->GetOffset() == suballoc.offset);\n         VMA_VALIDATE(suballoc.hAllocation->GetSize() == suballoc.size);\n         sumUsedSize += suballoc.size;\n      } else {\n         ++nullItem1stCount;\n      }\n\n      offset = suballoc.offset + suballoc.size + VMA_DEBUG_MARGIN;\n   }\n   VMA_VALIDATE(nullItem1stCount == m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount);\n\n   if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) {\n      const size_t suballoc2ndCount = suballocations2nd.size();\n      size_t nullItem2ndCount = 0;\n      for (size_t i = suballoc2ndCount; i--; ) {\n         const VmaSuballocation& suballoc = suballocations2nd[i];\n         const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);\n\n         VMA_VALIDATE(currFree == (suballoc.hAllocation == VK_NULL_HANDLE));\n         VMA_VALIDATE(suballoc.offset >= offset);\n\n         if (!currFree) {\n            VMA_VALIDATE(suballoc.hAllocation->GetOffset() == suballoc.offset);\n            VMA_VALIDATE(suballoc.hAllocation->GetSize() == suballoc.size);\n            sumUsedSize += suballoc.size;\n         } else {\n            ++nullItem2ndCount;\n         }\n\n         offset = suballoc.offset + suballoc.size + VMA_DEBUG_MARGIN;\n      }\n\n      VMA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount);\n   }\n\n   VMA_VALIDATE(offset <= GetSize());\n   VMA_VALIDATE(m_SumFreeSize == GetSize() - sumUsedSize);\n\n   return true;\n}\n\nsize_t VmaBlockMetadata_Linear::GetAllocationCount() const\n{\n   return AccessSuballocations1st().size() - (m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount) +\n      AccessSuballocations2nd().size() - m_2ndNullItemsCount;\n}\n\nVkDeviceSize VmaBlockMetadata_Linear::GetUnusedRangeSizeMax() const\n{\n   const VkDeviceSize size = GetSize();\n\n   /*\n   We don't consider gaps inside allocation vectors with freed allocations because\n   they are not suitable for reuse in linear allocator. We consider only space that\n   is available for new allocations.\n   */\n   if (IsEmpty()) {\n      return size;\n   }\n\n   const SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n\n   switch (m_2ndVectorMode) {\n   case SECOND_VECTOR_EMPTY:\n      /*\n      Available space is after end of 1st, as well as before beginning of 1st (which\n      whould make it a ring buffer).\n      */\n   {\n      const size_t suballocations1stCount = suballocations1st.size();\n      VMA_ASSERT(suballocations1stCount > m_1stNullItemsBeginCount);\n      const VmaSuballocation& firstSuballoc = suballocations1st[m_1stNullItemsBeginCount];\n      const VmaSuballocation& lastSuballoc = suballocations1st[suballocations1stCount - 1];\n      return VMA_MAX(\n         firstSuballoc.offset,\n         size - (lastSuballoc.offset + lastSuballoc.size));\n   }\n   break;\n\n   case SECOND_VECTOR_RING_BUFFER:\n      /*\n      Available space is only between end of 2nd and beginning of 1st.\n      */\n   {\n      const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n      const VmaSuballocation& lastSuballoc2nd = suballocations2nd.back();\n      const VmaSuballocation& firstSuballoc1st = suballocations1st[m_1stNullItemsBeginCount];\n      return firstSuballoc1st.offset - (lastSuballoc2nd.offset + lastSuballoc2nd.size);\n   }\n   break;\n\n   case SECOND_VECTOR_DOUBLE_STACK:\n      /*\n      Available space is only between end of 1st and top of 2nd.\n      */\n   {\n      const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n      const VmaSuballocation& topSuballoc2nd = suballocations2nd.back();\n      const VmaSuballocation& lastSuballoc1st = suballocations1st.back();\n      return topSuballoc2nd.offset - (lastSuballoc1st.offset + lastSuballoc1st.size);\n   }\n   break;\n\n   default:\n      VMA_ASSERT(0);\n      return 0;\n   }\n}\n\nvoid VmaBlockMetadata_Linear::CalcAllocationStatInfo(VmaStatInfo& outInfo) const\n{\n   const VkDeviceSize size = GetSize();\n   const SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n   const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n   const size_t suballoc1stCount = suballocations1st.size();\n   const size_t suballoc2ndCount = suballocations2nd.size();\n\n   outInfo.blockCount = 1;\n   outInfo.allocationCount = (uint32_t)GetAllocationCount();\n   outInfo.unusedRangeCount = 0;\n   outInfo.usedBytes = 0;\n   outInfo.allocationSizeMin = UINT64_MAX;\n   outInfo.allocationSizeMax = 0;\n   outInfo.unusedRangeSizeMin = UINT64_MAX;\n   outInfo.unusedRangeSizeMax = 0;\n\n   VkDeviceSize lastOffset = 0;\n\n   if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) {\n      const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset;\n      size_t nextAlloc2ndIndex = 0;\n      while (lastOffset < freeSpace2ndTo1stEnd) {\n         // Find next non-null allocation or move nextAllocIndex to the end.\n         while (nextAlloc2ndIndex < suballoc2ndCount &&\n                suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) {\n            ++nextAlloc2ndIndex;\n         }\n\n         // Found non-null allocation.\n         if (nextAlloc2ndIndex < suballoc2ndCount) {\n            const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n            // 1. Process free space before this allocation.\n            if (lastOffset < suballoc.offset) {\n               // There is free space from lastOffset to suballoc.offset.\n               const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n               ++outInfo.unusedRangeCount;\n               outInfo.unusedBytes += unusedRangeSize;\n               outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize);\n               outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize);\n            }\n\n            // 2. Process this allocation.\n            // There is allocation with suballoc.offset, suballoc.size.\n            outInfo.usedBytes += suballoc.size;\n            outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size);\n            outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size);\n\n            // 3. Prepare for next iteration.\n            lastOffset = suballoc.offset + suballoc.size;\n            ++nextAlloc2ndIndex;\n         }\n         // We are at the end.\n         else {\n            // There is free space from lastOffset to freeSpace2ndTo1stEnd.\n            if (lastOffset < freeSpace2ndTo1stEnd) {\n               const VkDeviceSize unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset;\n               ++outInfo.unusedRangeCount;\n               outInfo.unusedBytes += unusedRangeSize;\n               outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize);\n               outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize);\n            }\n\n            // End of loop.\n            lastOffset = freeSpace2ndTo1stEnd;\n         }\n      }\n   }\n\n   size_t nextAlloc1stIndex = m_1stNullItemsBeginCount;\n   const VkDeviceSize freeSpace1stTo2ndEnd =\n      m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size;\n   while (lastOffset < freeSpace1stTo2ndEnd) {\n      // Find next non-null allocation or move nextAllocIndex to the end.\n      while (nextAlloc1stIndex < suballoc1stCount &&\n             suballocations1st[nextAlloc1stIndex].hAllocation == VK_NULL_HANDLE) {\n         ++nextAlloc1stIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc1stIndex < suballoc1stCount) {\n         const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex];\n\n         // 1. Process free space before this allocation.\n         if (lastOffset < suballoc.offset) {\n            // There is free space from lastOffset to suballoc.offset.\n            const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n            ++outInfo.unusedRangeCount;\n            outInfo.unusedBytes += unusedRangeSize;\n            outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize);\n            outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize);\n         }\n\n         // 2. Process this allocation.\n         // There is allocation with suballoc.offset, suballoc.size.\n         outInfo.usedBytes += suballoc.size;\n         outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size);\n         outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size);\n\n         // 3. Prepare for next iteration.\n         lastOffset = suballoc.offset + suballoc.size;\n         ++nextAlloc1stIndex;\n      }\n      // We are at the end.\n      else {\n         // There is free space from lastOffset to freeSpace1stTo2ndEnd.\n         if (lastOffset < freeSpace1stTo2ndEnd) {\n            const VkDeviceSize unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset;\n            ++outInfo.unusedRangeCount;\n            outInfo.unusedBytes += unusedRangeSize;\n            outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize);\n            outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize);\n         }\n\n         // End of loop.\n         lastOffset = freeSpace1stTo2ndEnd;\n      }\n   }\n\n   if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) {\n      size_t nextAlloc2ndIndex = suballocations2nd.size() - 1;\n      while (lastOffset < size) {\n         // Find next non-null allocation or move nextAllocIndex to the end.\n         while (nextAlloc2ndIndex != SIZE_MAX &&\n                suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) {\n            --nextAlloc2ndIndex;\n         }\n\n         // Found non-null allocation.\n         if (nextAlloc2ndIndex != SIZE_MAX) {\n            const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n            // 1. Process free space before this allocation.\n            if (lastOffset < suballoc.offset) {\n               // There is free space from lastOffset to suballoc.offset.\n               const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n               ++outInfo.unusedRangeCount;\n               outInfo.unusedBytes += unusedRangeSize;\n               outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize);\n               outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize);\n            }\n\n            // 2. Process this allocation.\n            // There is allocation with suballoc.offset, suballoc.size.\n            outInfo.usedBytes += suballoc.size;\n            outInfo.allocationSizeMin = VMA_MIN(outInfo.allocationSizeMin, suballoc.size);\n            outInfo.allocationSizeMax = VMA_MIN(outInfo.allocationSizeMax, suballoc.size);\n\n            // 3. Prepare for next iteration.\n            lastOffset = suballoc.offset + suballoc.size;\n            --nextAlloc2ndIndex;\n         }\n         // We are at the end.\n         else {\n            // There is free space from lastOffset to size.\n            if (lastOffset < size) {\n               const VkDeviceSize unusedRangeSize = size - lastOffset;\n               ++outInfo.unusedRangeCount;\n               outInfo.unusedBytes += unusedRangeSize;\n               outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusedRangeSize);\n               outInfo.unusedRangeSizeMax = VMA_MIN(outInfo.unusedRangeSizeMax, unusedRangeSize);\n            }\n\n            // End of loop.\n            lastOffset = size;\n         }\n      }\n   }\n\n   outInfo.unusedBytes = size - outInfo.usedBytes;\n}\n\nvoid VmaBlockMetadata_Linear::AddPoolStats(VmaPoolStats& inoutStats) const\n{\n   const SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n   const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n   const VkDeviceSize size = GetSize();\n   const size_t suballoc1stCount = suballocations1st.size();\n   const size_t suballoc2ndCount = suballocations2nd.size();\n\n   inoutStats.size += size;\n\n   VkDeviceSize lastOffset = 0;\n\n   if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) {\n      const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset;\n      size_t nextAlloc2ndIndex = m_1stNullItemsBeginCount;\n      while (lastOffset < freeSpace2ndTo1stEnd) {\n         // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n         while (nextAlloc2ndIndex < suballoc2ndCount &&\n                suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) {\n            ++nextAlloc2ndIndex;\n         }\n\n         // Found non-null allocation.\n         if (nextAlloc2ndIndex < suballoc2ndCount) {\n            const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n            // 1. Process free space before this allocation.\n            if (lastOffset < suballoc.offset) {\n               // There is free space from lastOffset to suballoc.offset.\n               const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n               inoutStats.unusedSize += unusedRangeSize;\n               ++inoutStats.unusedRangeCount;\n               inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize);\n            }\n\n            // 2. Process this allocation.\n            // There is allocation with suballoc.offset, suballoc.size.\n            ++inoutStats.allocationCount;\n\n            // 3. Prepare for next iteration.\n            lastOffset = suballoc.offset + suballoc.size;\n            ++nextAlloc2ndIndex;\n         }\n         // We are at the end.\n         else {\n            if (lastOffset < freeSpace2ndTo1stEnd) {\n               // There is free space from lastOffset to freeSpace2ndTo1stEnd.\n               const VkDeviceSize unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset;\n               inoutStats.unusedSize += unusedRangeSize;\n               ++inoutStats.unusedRangeCount;\n               inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize);\n            }\n\n            // End of loop.\n            lastOffset = freeSpace2ndTo1stEnd;\n         }\n      }\n   }\n\n   size_t nextAlloc1stIndex = m_1stNullItemsBeginCount;\n   const VkDeviceSize freeSpace1stTo2ndEnd =\n      m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size;\n   while (lastOffset < freeSpace1stTo2ndEnd) {\n      // Find next non-null allocation or move nextAllocIndex to the end.\n      while (nextAlloc1stIndex < suballoc1stCount &&\n             suballocations1st[nextAlloc1stIndex].hAllocation == VK_NULL_HANDLE) {\n         ++nextAlloc1stIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc1stIndex < suballoc1stCount) {\n         const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex];\n\n         // 1. Process free space before this allocation.\n         if (lastOffset < suballoc.offset) {\n            // There is free space from lastOffset to suballoc.offset.\n            const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n            inoutStats.unusedSize += unusedRangeSize;\n            ++inoutStats.unusedRangeCount;\n            inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize);\n         }\n\n         // 2. Process this allocation.\n         // There is allocation with suballoc.offset, suballoc.size.\n         ++inoutStats.allocationCount;\n\n         // 3. Prepare for next iteration.\n         lastOffset = suballoc.offset + suballoc.size;\n         ++nextAlloc1stIndex;\n      }\n      // We are at the end.\n      else {\n         if (lastOffset < freeSpace1stTo2ndEnd) {\n            // There is free space from lastOffset to freeSpace1stTo2ndEnd.\n            const VkDeviceSize unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset;\n            inoutStats.unusedSize += unusedRangeSize;\n            ++inoutStats.unusedRangeCount;\n            inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize);\n         }\n\n         // End of loop.\n         lastOffset = freeSpace1stTo2ndEnd;\n      }\n   }\n\n   if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) {\n      size_t nextAlloc2ndIndex = suballocations2nd.size() - 1;\n      while (lastOffset < size) {\n         // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n         while (nextAlloc2ndIndex != SIZE_MAX &&\n                suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) {\n            --nextAlloc2ndIndex;\n         }\n\n         // Found non-null allocation.\n         if (nextAlloc2ndIndex != SIZE_MAX) {\n            const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n            // 1. Process free space before this allocation.\n            if (lastOffset < suballoc.offset) {\n               // There is free space from lastOffset to suballoc.offset.\n               const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n               inoutStats.unusedSize += unusedRangeSize;\n               ++inoutStats.unusedRangeCount;\n               inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize);\n            }\n\n            // 2. Process this allocation.\n            // There is allocation with suballoc.offset, suballoc.size.\n            ++inoutStats.allocationCount;\n\n            // 3. Prepare for next iteration.\n            lastOffset = suballoc.offset + suballoc.size;\n            --nextAlloc2ndIndex;\n         }\n         // We are at the end.\n         else {\n            if (lastOffset < size) {\n               // There is free space from lastOffset to size.\n               const VkDeviceSize unusedRangeSize = size - lastOffset;\n               inoutStats.unusedSize += unusedRangeSize;\n               ++inoutStats.unusedRangeCount;\n               inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, unusedRangeSize);\n            }\n\n            // End of loop.\n            lastOffset = size;\n         }\n      }\n   }\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const\n{\n   const VkDeviceSize size = GetSize();\n   const SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n   const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n   const size_t suballoc1stCount = suballocations1st.size();\n   const size_t suballoc2ndCount = suballocations2nd.size();\n\n   // FIRST PASS\n\n   size_t unusedRangeCount = 0;\n   VkDeviceSize usedBytes = 0;\n\n   VkDeviceSize lastOffset = 0;\n\n   size_t alloc2ndCount = 0;\n   if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) {\n      const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset;\n      size_t nextAlloc2ndIndex = 0;\n      while (lastOffset < freeSpace2ndTo1stEnd) {\n         // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n         while (nextAlloc2ndIndex < suballoc2ndCount &&\n                suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) {\n            ++nextAlloc2ndIndex;\n         }\n\n         // Found non-null allocation.\n         if (nextAlloc2ndIndex < suballoc2ndCount) {\n            const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n            // 1. Process free space before this allocation.\n            if (lastOffset < suballoc.offset) {\n               // There is free space from lastOffset to suballoc.offset.\n               ++unusedRangeCount;\n            }\n\n            // 2. Process this allocation.\n            // There is allocation with suballoc.offset, suballoc.size.\n            ++alloc2ndCount;\n            usedBytes += suballoc.size;\n\n            // 3. Prepare for next iteration.\n            lastOffset = suballoc.offset + suballoc.size;\n            ++nextAlloc2ndIndex;\n         }\n         // We are at the end.\n         else {\n            if (lastOffset < freeSpace2ndTo1stEnd) {\n               // There is free space from lastOffset to freeSpace2ndTo1stEnd.\n               ++unusedRangeCount;\n            }\n\n            // End of loop.\n            lastOffset = freeSpace2ndTo1stEnd;\n         }\n      }\n   }\n\n   size_t nextAlloc1stIndex = m_1stNullItemsBeginCount;\n   size_t alloc1stCount = 0;\n   const VkDeviceSize freeSpace1stTo2ndEnd =\n      m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size;\n   while (lastOffset < freeSpace1stTo2ndEnd) {\n      // Find next non-null allocation or move nextAllocIndex to the end.\n      while (nextAlloc1stIndex < suballoc1stCount &&\n             suballocations1st[nextAlloc1stIndex].hAllocation == VK_NULL_HANDLE) {\n         ++nextAlloc1stIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc1stIndex < suballoc1stCount) {\n         const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex];\n\n         // 1. Process free space before this allocation.\n         if (lastOffset < suballoc.offset) {\n            // There is free space from lastOffset to suballoc.offset.\n            ++unusedRangeCount;\n         }\n\n         // 2. Process this allocation.\n         // There is allocation with suballoc.offset, suballoc.size.\n         ++alloc1stCount;\n         usedBytes += suballoc.size;\n\n         // 3. Prepare for next iteration.\n         lastOffset = suballoc.offset + suballoc.size;\n         ++nextAlloc1stIndex;\n      }\n      // We are at the end.\n      else {\n         if (lastOffset < size) {\n            // There is free space from lastOffset to freeSpace1stTo2ndEnd.\n            ++unusedRangeCount;\n         }\n\n         // End of loop.\n         lastOffset = freeSpace1stTo2ndEnd;\n      }\n   }\n\n   if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) {\n      size_t nextAlloc2ndIndex = suballocations2nd.size() - 1;\n      while (lastOffset < size) {\n         // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n         while (nextAlloc2ndIndex != SIZE_MAX &&\n                suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) {\n            --nextAlloc2ndIndex;\n         }\n\n         // Found non-null allocation.\n         if (nextAlloc2ndIndex != SIZE_MAX) {\n            const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n            // 1. Process free space before this allocation.\n            if (lastOffset < suballoc.offset) {\n               // There is free space from lastOffset to suballoc.offset.\n               ++unusedRangeCount;\n            }\n\n            // 2. Process this allocation.\n            // There is allocation with suballoc.offset, suballoc.size.\n            ++alloc2ndCount;\n            usedBytes += suballoc.size;\n\n            // 3. Prepare for next iteration.\n            lastOffset = suballoc.offset + suballoc.size;\n            --nextAlloc2ndIndex;\n         }\n         // We are at the end.\n         else {\n            if (lastOffset < size) {\n               // There is free space from lastOffset to size.\n               ++unusedRangeCount;\n            }\n\n            // End of loop.\n            lastOffset = size;\n         }\n      }\n   }\n\n   const VkDeviceSize unusedBytes = size - usedBytes;\n   PrintDetailedMap_Begin(json, unusedBytes, alloc1stCount + alloc2ndCount, unusedRangeCount);\n\n   // SECOND PASS\n   lastOffset = 0;\n\n   if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) {\n      const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset;\n      size_t nextAlloc2ndIndex = 0;\n      while (lastOffset < freeSpace2ndTo1stEnd) {\n         // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n         while (nextAlloc2ndIndex < suballoc2ndCount &&\n                suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) {\n            ++nextAlloc2ndIndex;\n         }\n\n         // Found non-null allocation.\n         if (nextAlloc2ndIndex < suballoc2ndCount) {\n            const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n            // 1. Process free space before this allocation.\n            if (lastOffset < suballoc.offset) {\n               // There is free space from lastOffset to suballoc.offset.\n               const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n               PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize);\n            }\n\n            // 2. Process this allocation.\n            // There is allocation with suballoc.offset, suballoc.size.\n            PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation);\n\n            // 3. Prepare for next iteration.\n            lastOffset = suballoc.offset + suballoc.size;\n            ++nextAlloc2ndIndex;\n         }\n         // We are at the end.\n         else {\n            if (lastOffset < freeSpace2ndTo1stEnd) {\n               // There is free space from lastOffset to freeSpace2ndTo1stEnd.\n               const VkDeviceSize unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset;\n               PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize);\n            }\n\n            // End of loop.\n            lastOffset = freeSpace2ndTo1stEnd;\n         }\n      }\n   }\n\n   nextAlloc1stIndex = m_1stNullItemsBeginCount;\n   while (lastOffset < freeSpace1stTo2ndEnd) {\n      // Find next non-null allocation or move nextAllocIndex to the end.\n      while (nextAlloc1stIndex < suballoc1stCount &&\n             suballocations1st[nextAlloc1stIndex].hAllocation == VK_NULL_HANDLE) {\n         ++nextAlloc1stIndex;\n      }\n\n      // Found non-null allocation.\n      if (nextAlloc1stIndex < suballoc1stCount) {\n         const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex];\n\n         // 1. Process free space before this allocation.\n         if (lastOffset < suballoc.offset) {\n            // There is free space from lastOffset to suballoc.offset.\n            const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n            PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize);\n         }\n\n         // 2. Process this allocation.\n         // There is allocation with suballoc.offset, suballoc.size.\n         PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation);\n\n         // 3. Prepare for next iteration.\n         lastOffset = suballoc.offset + suballoc.size;\n         ++nextAlloc1stIndex;\n      }\n      // We are at the end.\n      else {\n         if (lastOffset < freeSpace1stTo2ndEnd) {\n            // There is free space from lastOffset to freeSpace1stTo2ndEnd.\n            const VkDeviceSize unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset;\n            PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize);\n         }\n\n         // End of loop.\n         lastOffset = freeSpace1stTo2ndEnd;\n      }\n   }\n\n   if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) {\n      size_t nextAlloc2ndIndex = suballocations2nd.size() - 1;\n      while (lastOffset < size) {\n         // Find next non-null allocation or move nextAlloc2ndIndex to the end.\n         while (nextAlloc2ndIndex != SIZE_MAX &&\n                suballocations2nd[nextAlloc2ndIndex].hAllocation == VK_NULL_HANDLE) {\n            --nextAlloc2ndIndex;\n         }\n\n         // Found non-null allocation.\n         if (nextAlloc2ndIndex != SIZE_MAX) {\n            const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex];\n\n            // 1. Process free space before this allocation.\n            if (lastOffset < suballoc.offset) {\n               // There is free space from lastOffset to suballoc.offset.\n               const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset;\n               PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize);\n            }\n\n            // 2. Process this allocation.\n            // There is allocation with suballoc.offset, suballoc.size.\n            PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.hAllocation);\n\n            // 3. Prepare for next iteration.\n            lastOffset = suballoc.offset + suballoc.size;\n            --nextAlloc2ndIndex;\n         }\n         // We are at the end.\n         else {\n            if (lastOffset < size) {\n               // There is free space from lastOffset to size.\n               const VkDeviceSize unusedRangeSize = size - lastOffset;\n               PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize);\n            }\n\n            // End of loop.\n            lastOffset = size;\n         }\n      }\n   }\n\n   PrintDetailedMap_End(json);\n}\n#endif // #if VMA_STATS_STRING_ENABLED\n\nbool VmaBlockMetadata_Linear::CreateAllocationRequest(\n   uint32_t currentFrameIndex,\n   uint32_t frameInUseCount,\n   VkDeviceSize bufferImageGranularity,\n   VkDeviceSize allocSize,\n   VkDeviceSize allocAlignment,\n   bool upperAddress,\n   VmaSuballocationType allocType,\n   bool canMakeOtherLost,\n   uint32_t strategy,\n   VmaAllocationRequest* pAllocationRequest)\n{\n   VMA_ASSERT(allocSize > 0);\n   VMA_ASSERT(allocType != VMA_SUBALLOCATION_TYPE_FREE);\n   VMA_ASSERT(pAllocationRequest != VMA_NULL);\n   VMA_HEAVY_ASSERT(Validate());\n   return upperAddress ?\n      CreateAllocationRequest_UpperAddress(\n         currentFrameIndex, frameInUseCount, bufferImageGranularity,\n         allocSize, allocAlignment, allocType, canMakeOtherLost, strategy, pAllocationRequest) :\n      CreateAllocationRequest_LowerAddress(\n         currentFrameIndex, frameInUseCount, bufferImageGranularity,\n         allocSize, allocAlignment, allocType, canMakeOtherLost, strategy, pAllocationRequest);\n}\n\nbool VmaBlockMetadata_Linear::CreateAllocationRequest_UpperAddress(\n   uint32_t currentFrameIndex,\n   uint32_t frameInUseCount,\n   VkDeviceSize bufferImageGranularity,\n   VkDeviceSize allocSize,\n   VkDeviceSize allocAlignment,\n   VmaSuballocationType allocType,\n   bool canMakeOtherLost,\n   uint32_t strategy,\n   VmaAllocationRequest* pAllocationRequest)\n{\n   const VkDeviceSize size = GetSize();\n   SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n   SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n\n   if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) {\n      VMA_ASSERT(0 && \"Trying to use pool with linear algorithm as double stack, while it is already being used as ring buffer.\");\n      return false;\n   }\n\n   // Try to allocate before 2nd.back(), or end of block if 2nd.empty().\n   if (allocSize > size) {\n      return false;\n   }\n   VkDeviceSize resultBaseOffset = size - allocSize;\n   if (!suballocations2nd.empty()) {\n      const VmaSuballocation& lastSuballoc = suballocations2nd.back();\n      resultBaseOffset = lastSuballoc.offset - allocSize;\n      if (allocSize > lastSuballoc.offset) {\n         return false;\n      }\n   }\n\n   // Start from offset equal to end of free space.\n   VkDeviceSize resultOffset = resultBaseOffset;\n\n   // Apply VMA_DEBUG_MARGIN at the end.\n   if (VMA_DEBUG_MARGIN > 0) {\n      if (resultOffset < VMA_DEBUG_MARGIN) {\n         return false;\n      }\n      resultOffset -= VMA_DEBUG_MARGIN;\n   }\n\n   // Apply alignment.\n   resultOffset = VmaAlignDown(resultOffset, allocAlignment);\n\n   // Check next suballocations from 2nd for BufferImageGranularity conflicts.\n   // Make bigger alignment if necessary.\n   if (bufferImageGranularity > 1 && !suballocations2nd.empty()) {\n      bool bufferImageGranularityConflict = false;\n      for (size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; ) {\n         const VmaSuballocation& nextSuballoc = suballocations2nd[nextSuballocIndex];\n         if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) {\n            if (VmaIsBufferImageGranularityConflict(nextSuballoc.type, allocType)) {\n               bufferImageGranularityConflict = true;\n               break;\n            }\n         } else\n            // Already on previous page.\n            break;\n      }\n      if (bufferImageGranularityConflict) {\n         resultOffset = VmaAlignDown(resultOffset, bufferImageGranularity);\n      }\n   }\n\n   // There is enough free space.\n   const VkDeviceSize endOf1st = !suballocations1st.empty() ?\n      suballocations1st.back().offset + suballocations1st.back().size :\n      0;\n   if (endOf1st + VMA_DEBUG_MARGIN <= resultOffset) {\n      // Check previous suballocations for BufferImageGranularity conflicts.\n      // If conflict exists, allocation cannot be made here.\n      if (bufferImageGranularity > 1) {\n         for (size_t prevSuballocIndex = suballocations1st.size(); prevSuballocIndex--; ) {\n            const VmaSuballocation& prevSuballoc = suballocations1st[prevSuballocIndex];\n            if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity)) {\n               if (VmaIsBufferImageGranularityConflict(allocType, prevSuballoc.type)) {\n                  return false;\n               }\n            } else {\n               // Already on next page.\n               break;\n            }\n         }\n      }\n\n      // All tests passed: Success.\n      pAllocationRequest->offset = resultOffset;\n      pAllocationRequest->sumFreeSize = resultBaseOffset + allocSize - endOf1st;\n      pAllocationRequest->sumItemSize = 0;\n      // pAllocationRequest->item unused.\n      pAllocationRequest->itemsToMakeLostCount = 0;\n      pAllocationRequest->type = VmaAllocationRequestType::UpperAddress;\n      return true;\n   }\n\n   return false;\n}\n\nbool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress(\n   uint32_t currentFrameIndex,\n   uint32_t frameInUseCount,\n   VkDeviceSize bufferImageGranularity,\n   VkDeviceSize allocSize,\n   VkDeviceSize allocAlignment,\n   VmaSuballocationType allocType,\n   bool canMakeOtherLost,\n   uint32_t strategy,\n   VmaAllocationRequest* pAllocationRequest)\n{\n   const VkDeviceSize size = GetSize();\n   SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n   SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n\n   if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) {\n      // Try to allocate at the end of 1st vector.\n\n      VkDeviceSize resultBaseOffset = 0;\n      if (!suballocations1st.empty()) {\n         const VmaSuballocation& lastSuballoc = suballocations1st.back();\n         resultBaseOffset = lastSuballoc.offset + lastSuballoc.size;\n      }\n\n      // Start from offset equal to beginning of free space.\n      VkDeviceSize resultOffset = resultBaseOffset;\n\n      // Apply VMA_DEBUG_MARGIN at the beginning.\n      if (VMA_DEBUG_MARGIN > 0) {\n         resultOffset += VMA_DEBUG_MARGIN;\n      }\n\n      // Apply alignment.\n      resultOffset = VmaAlignUp(resultOffset, allocAlignment);\n\n      // Check previous suballocations for BufferImageGranularity conflicts.\n      // Make bigger alignment if necessary.\n      if (bufferImageGranularity > 1 && !suballocations1st.empty()) {\n         bool bufferImageGranularityConflict = false;\n         for (size_t prevSuballocIndex = suballocations1st.size(); prevSuballocIndex--; ) {\n            const VmaSuballocation& prevSuballoc = suballocations1st[prevSuballocIndex];\n            if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity)) {\n               if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType)) {\n                  bufferImageGranularityConflict = true;\n                  break;\n               }\n            } else\n               // Already on previous page.\n               break;\n         }\n         if (bufferImageGranularityConflict) {\n            resultOffset = VmaAlignUp(resultOffset, bufferImageGranularity);\n         }\n      }\n\n      const VkDeviceSize freeSpaceEnd = m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ?\n         suballocations2nd.back().offset : size;\n\n      // There is enough free space at the end after alignment.\n      if (resultOffset + allocSize + VMA_DEBUG_MARGIN <= freeSpaceEnd) {\n         // Check next suballocations for BufferImageGranularity conflicts.\n         // If conflict exists, allocation cannot be made here.\n         if (bufferImageGranularity > 1 && m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) {\n            for (size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; ) {\n               const VmaSuballocation& nextSuballoc = suballocations2nd[nextSuballocIndex];\n               if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) {\n                  if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type)) {\n                     return false;\n                  }\n               } else {\n                  // Already on previous page.\n                  break;\n               }\n            }\n         }\n\n         // All tests passed: Success.\n         pAllocationRequest->offset = resultOffset;\n         pAllocationRequest->sumFreeSize = freeSpaceEnd - resultBaseOffset;\n         pAllocationRequest->sumItemSize = 0;\n         // pAllocationRequest->item, customData unused.\n         pAllocationRequest->type = VmaAllocationRequestType::EndOf1st;\n         pAllocationRequest->itemsToMakeLostCount = 0;\n         return true;\n      }\n   }\n\n   // Wrap-around to end of 2nd vector. Try to allocate there, watching for the\n   // beginning of 1st vector as the end of free space.\n   if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) {\n      VMA_ASSERT(!suballocations1st.empty());\n\n      VkDeviceSize resultBaseOffset = 0;\n      if (!suballocations2nd.empty()) {\n         const VmaSuballocation& lastSuballoc = suballocations2nd.back();\n         resultBaseOffset = lastSuballoc.offset + lastSuballoc.size;\n      }\n\n      // Start from offset equal to beginning of free space.\n      VkDeviceSize resultOffset = resultBaseOffset;\n\n      // Apply VMA_DEBUG_MARGIN at the beginning.\n      if (VMA_DEBUG_MARGIN > 0) {\n         resultOffset += VMA_DEBUG_MARGIN;\n      }\n\n      // Apply alignment.\n      resultOffset = VmaAlignUp(resultOffset, allocAlignment);\n\n      // Check previous suballocations for BufferImageGranularity conflicts.\n      // Make bigger alignment if necessary.\n      if (bufferImageGranularity > 1 && !suballocations2nd.empty()) {\n         bool bufferImageGranularityConflict = false;\n         for (size_t prevSuballocIndex = suballocations2nd.size(); prevSuballocIndex--; ) {\n            const VmaSuballocation& prevSuballoc = suballocations2nd[prevSuballocIndex];\n            if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity)) {\n               if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType)) {\n                  bufferImageGranularityConflict = true;\n                  break;\n               }\n            } else\n               // Already on previous page.\n               break;\n         }\n         if (bufferImageGranularityConflict) {\n            resultOffset = VmaAlignUp(resultOffset, bufferImageGranularity);\n         }\n      }\n\n      pAllocationRequest->itemsToMakeLostCount = 0;\n      pAllocationRequest->sumItemSize = 0;\n      size_t index1st = m_1stNullItemsBeginCount;\n\n      if (canMakeOtherLost) {\n         while (index1st < suballocations1st.size() &&\n                resultOffset + allocSize + VMA_DEBUG_MARGIN > suballocations1st[index1st].offset) {\n            // Next colliding allocation at the beginning of 1st vector found. Try to make it lost.\n            const VmaSuballocation& suballoc = suballocations1st[index1st];\n            if (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE) {\n               // No problem.\n            } else {\n               VMA_ASSERT(suballoc.hAllocation != VK_NULL_HANDLE);\n               if (suballoc.hAllocation->CanBecomeLost() &&\n                   suballoc.hAllocation->GetLastUseFrameIndex() + frameInUseCount < currentFrameIndex) {\n                  ++pAllocationRequest->itemsToMakeLostCount;\n                  pAllocationRequest->sumItemSize += suballoc.size;\n               } else {\n                  return false;\n               }\n            }\n            ++index1st;\n         }\n\n         // Check next suballocations for BufferImageGranularity conflicts.\n         // If conflict exists, we must mark more allocations lost or fail.\n         if (bufferImageGranularity > 1) {\n            while (index1st < suballocations1st.size()) {\n               const VmaSuballocation& suballoc = suballocations1st[index1st];\n               if (VmaBlocksOnSamePage(resultOffset, allocSize, suballoc.offset, bufferImageGranularity)) {\n                  if (suballoc.hAllocation != VK_NULL_HANDLE) {\n                     // Not checking actual VmaIsBufferImageGranularityConflict(allocType, suballoc.type).\n                     if (suballoc.hAllocation->CanBecomeLost() &&\n                         suballoc.hAllocation->GetLastUseFrameIndex() + frameInUseCount < currentFrameIndex) {\n                        ++pAllocationRequest->itemsToMakeLostCount;\n                        pAllocationRequest->sumItemSize += suballoc.size;\n                     } else {\n                        return false;\n                     }\n                  }\n               } else {\n                  // Already on next page.\n                  break;\n               }\n               ++index1st;\n            }\n         }\n\n         // Special case: There is not enough room at the end for this allocation, even after making all from the 1st lost.\n         if (index1st == suballocations1st.size() &&\n             resultOffset + allocSize + VMA_DEBUG_MARGIN > size) {\n            // TODO: This is a known bug that it's not yet implemented and the allocation is failing.\n            VMA_DEBUG_LOG(\"Unsupported special case in custom pool with linear allocation algorithm used as ring buffer with allocations that can be lost.\");\n         }\n      }\n\n      // There is enough free space at the end after alignment.\n      if ((index1st == suballocations1st.size() && resultOffset + allocSize + VMA_DEBUG_MARGIN <= size) ||\n         (index1st < suballocations1st.size() && resultOffset + allocSize + VMA_DEBUG_MARGIN <= suballocations1st[index1st].offset)) {\n         // Check next suballocations for BufferImageGranularity conflicts.\n         // If conflict exists, allocation cannot be made here.\n         if (bufferImageGranularity > 1) {\n            for (size_t nextSuballocIndex = index1st;\n                 nextSuballocIndex < suballocations1st.size();\n                 nextSuballocIndex++) {\n               const VmaSuballocation& nextSuballoc = suballocations1st[nextSuballocIndex];\n               if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) {\n                  if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type)) {\n                     return false;\n                  }\n               } else {\n                  // Already on next page.\n                  break;\n               }\n            }\n         }\n\n         // All tests passed: Success.\n         pAllocationRequest->offset = resultOffset;\n         pAllocationRequest->sumFreeSize =\n            (index1st < suballocations1st.size() ? suballocations1st[index1st].offset : size)\n            - resultBaseOffset\n            - pAllocationRequest->sumItemSize;\n         pAllocationRequest->type = VmaAllocationRequestType::EndOf2nd;\n         // pAllocationRequest->item, customData unused.\n         return true;\n      }\n   }\n\n   return false;\n}\n\nbool VmaBlockMetadata_Linear::MakeRequestedAllocationsLost(\n   uint32_t currentFrameIndex,\n   uint32_t frameInUseCount,\n   VmaAllocationRequest* pAllocationRequest)\n{\n   if (pAllocationRequest->itemsToMakeLostCount == 0) {\n      return true;\n   }\n\n   VMA_ASSERT(m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER);\n\n   // We always start from 1st.\n   SuballocationVectorType* suballocations = &AccessSuballocations1st();\n   size_t index = m_1stNullItemsBeginCount;\n   size_t madeLostCount = 0;\n   while (madeLostCount < pAllocationRequest->itemsToMakeLostCount) {\n      if (index == suballocations->size()) {\n         index = 0;\n         // If we get to the end of 1st, we wrap around to beginning of 2nd of 1st.\n         if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) {\n            suballocations = &AccessSuballocations2nd();\n         }\n         // else: m_2ndVectorMode == SECOND_VECTOR_EMPTY:\n         // suballocations continues pointing at AccessSuballocations1st().\n         VMA_ASSERT(!suballocations->empty());\n      }\n      VmaSuballocation& suballoc = (*suballocations)[index];\n      if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE) {\n         VMA_ASSERT(suballoc.hAllocation != VK_NULL_HANDLE);\n         VMA_ASSERT(suballoc.hAllocation->CanBecomeLost());\n         if (suballoc.hAllocation->MakeLost(currentFrameIndex, frameInUseCount)) {\n            suballoc.type = VMA_SUBALLOCATION_TYPE_FREE;\n            suballoc.hAllocation = VK_NULL_HANDLE;\n            m_SumFreeSize += suballoc.size;\n            if (suballocations == &AccessSuballocations1st()) {\n               ++m_1stNullItemsMiddleCount;\n            } else {\n               ++m_2ndNullItemsCount;\n            }\n            ++madeLostCount;\n         } else {\n            return false;\n         }\n      }\n      ++index;\n   }\n\n   CleanupAfterFree();\n   //VMA_HEAVY_ASSERT(Validate()); // Already called by ClanupAfterFree().\n\n   return true;\n}\n\nuint32_t VmaBlockMetadata_Linear::MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount)\n{\n   uint32_t lostAllocationCount = 0;\n\n   SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n   for (size_t i = m_1stNullItemsBeginCount, count = suballocations1st.size(); i < count; ++i) {\n      VmaSuballocation& suballoc = suballocations1st[i];\n      if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE &&\n          suballoc.hAllocation->CanBecomeLost() &&\n          suballoc.hAllocation->MakeLost(currentFrameIndex, frameInUseCount)) {\n         suballoc.type = VMA_SUBALLOCATION_TYPE_FREE;\n         suballoc.hAllocation = VK_NULL_HANDLE;\n         ++m_1stNullItemsMiddleCount;\n         m_SumFreeSize += suballoc.size;\n         ++lostAllocationCount;\n      }\n   }\n\n   SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n   for (size_t i = 0, count = suballocations2nd.size(); i < count; ++i) {\n      VmaSuballocation& suballoc = suballocations2nd[i];\n      if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE &&\n          suballoc.hAllocation->CanBecomeLost() &&\n          suballoc.hAllocation->MakeLost(currentFrameIndex, frameInUseCount)) {\n         suballoc.type = VMA_SUBALLOCATION_TYPE_FREE;\n         suballoc.hAllocation = VK_NULL_HANDLE;\n         ++m_2ndNullItemsCount;\n         m_SumFreeSize += suballoc.size;\n         ++lostAllocationCount;\n      }\n   }\n\n   if (lostAllocationCount) {\n      CleanupAfterFree();\n   }\n\n   return lostAllocationCount;\n}\n\nVkResult VmaBlockMetadata_Linear::CheckCorruption(const void* pBlockData)\n{\n   SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n   for (size_t i = m_1stNullItemsBeginCount, count = suballocations1st.size(); i < count; ++i) {\n      const VmaSuballocation& suballoc = suballocations1st[i];\n      if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE) {\n         if (!VmaValidateMagicValue(pBlockData, suballoc.offset - VMA_DEBUG_MARGIN)) {\n            VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED BEFORE VALIDATED ALLOCATION!\");\n            return VK_ERROR_VALIDATION_FAILED_EXT;\n         }\n         if (!VmaValidateMagicValue(pBlockData, suballoc.offset + suballoc.size)) {\n            VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!\");\n            return VK_ERROR_VALIDATION_FAILED_EXT;\n         }\n      }\n   }\n\n   SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n   for (size_t i = 0, count = suballocations2nd.size(); i < count; ++i) {\n      const VmaSuballocation& suballoc = suballocations2nd[i];\n      if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE) {\n         if (!VmaValidateMagicValue(pBlockData, suballoc.offset - VMA_DEBUG_MARGIN)) {\n            VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED BEFORE VALIDATED ALLOCATION!\");\n            return VK_ERROR_VALIDATION_FAILED_EXT;\n         }\n         if (!VmaValidateMagicValue(pBlockData, suballoc.offset + suballoc.size)) {\n            VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!\");\n            return VK_ERROR_VALIDATION_FAILED_EXT;\n         }\n      }\n   }\n\n   return VK_SUCCESS;\n}\n\nvoid VmaBlockMetadata_Linear::Alloc(\n   const VmaAllocationRequest& request,\n   VmaSuballocationType type,\n   VkDeviceSize allocSize,\n   VmaAllocation hAllocation)\n{\n   const VmaSuballocation newSuballoc = { request.offset, allocSize, hAllocation, type };\n\n   switch (request.type) {\n   case VmaAllocationRequestType::UpperAddress:\n   {\n      VMA_ASSERT(m_2ndVectorMode != SECOND_VECTOR_RING_BUFFER &&\n                 \"CRITICAL ERROR: Trying to use linear allocator as double stack while it was already used as ring buffer.\");\n      SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n      suballocations2nd.push_back(newSuballoc);\n      m_2ndVectorMode = SECOND_VECTOR_DOUBLE_STACK;\n   }\n   break;\n   case VmaAllocationRequestType::EndOf1st:\n   {\n      SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n\n      VMA_ASSERT(suballocations1st.empty() ||\n                 request.offset >= suballocations1st.back().offset + suballocations1st.back().size);\n      // Check if it fits before the end of the block.\n      VMA_ASSERT(request.offset + allocSize <= GetSize());\n\n      suballocations1st.push_back(newSuballoc);\n   }\n   break;\n   case VmaAllocationRequestType::EndOf2nd:\n   {\n      SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n      // New allocation at the end of 2-part ring buffer, so before first allocation from 1st vector.\n      VMA_ASSERT(!suballocations1st.empty() &&\n                 request.offset + allocSize <= suballocations1st[m_1stNullItemsBeginCount].offset);\n      SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n\n      switch (m_2ndVectorMode) {\n      case SECOND_VECTOR_EMPTY:\n         // First allocation from second part ring buffer.\n         VMA_ASSERT(suballocations2nd.empty());\n         m_2ndVectorMode = SECOND_VECTOR_RING_BUFFER;\n         break;\n      case SECOND_VECTOR_RING_BUFFER:\n         // 2-part ring buffer is already started.\n         VMA_ASSERT(!suballocations2nd.empty());\n         break;\n      case SECOND_VECTOR_DOUBLE_STACK:\n         VMA_ASSERT(0 && \"CRITICAL ERROR: Trying to use linear allocator as ring buffer while it was already used as double stack.\");\n         break;\n      default:\n         VMA_ASSERT(0);\n      }\n\n      suballocations2nd.push_back(newSuballoc);\n   }\n   break;\n   default:\n      VMA_ASSERT(0 && \"CRITICAL INTERNAL ERROR.\");\n   }\n\n   m_SumFreeSize -= newSuballoc.size;\n}\n\nvoid VmaBlockMetadata_Linear::Free(const VmaAllocation allocation)\n{\n   FreeAtOffset(allocation->GetOffset());\n}\n\nvoid VmaBlockMetadata_Linear::FreeAtOffset(VkDeviceSize offset)\n{\n   SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n   SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n\n   if (!suballocations1st.empty()) {\n      // First allocation: Mark it as next empty at the beginning.\n      VmaSuballocation& firstSuballoc = suballocations1st[m_1stNullItemsBeginCount];\n      if (firstSuballoc.offset == offset) {\n         firstSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE;\n         firstSuballoc.hAllocation = VK_NULL_HANDLE;\n         m_SumFreeSize += firstSuballoc.size;\n         ++m_1stNullItemsBeginCount;\n         CleanupAfterFree();\n         return;\n      }\n   }\n\n   // Last allocation in 2-part ring buffer or top of upper stack (same logic).\n   if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ||\n       m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) {\n      VmaSuballocation& lastSuballoc = suballocations2nd.back();\n      if (lastSuballoc.offset == offset) {\n         m_SumFreeSize += lastSuballoc.size;\n         suballocations2nd.pop_back();\n         CleanupAfterFree();\n         return;\n      }\n   }\n   // Last allocation in 1st vector.\n   else if (m_2ndVectorMode == SECOND_VECTOR_EMPTY) {\n      VmaSuballocation& lastSuballoc = suballocations1st.back();\n      if (lastSuballoc.offset == offset) {\n         m_SumFreeSize += lastSuballoc.size;\n         suballocations1st.pop_back();\n         CleanupAfterFree();\n         return;\n      }\n   }\n\n   // Item from the middle of 1st vector.\n   {\n      VmaSuballocation refSuballoc;\n      refSuballoc.offset = offset;\n      // Rest of members stays uninitialized intentionally for better performance.\n      SuballocationVectorType::iterator it = VmaBinaryFindSorted(\n         suballocations1st.begin() + m_1stNullItemsBeginCount,\n         suballocations1st.end(),\n         refSuballoc,\n         VmaSuballocationOffsetLess());\n      if (it != suballocations1st.end()) {\n         it->type = VMA_SUBALLOCATION_TYPE_FREE;\n         it->hAllocation = VK_NULL_HANDLE;\n         ++m_1stNullItemsMiddleCount;\n         m_SumFreeSize += it->size;\n         CleanupAfterFree();\n         return;\n      }\n   }\n\n   if (m_2ndVectorMode != SECOND_VECTOR_EMPTY) {\n      // Item from the middle of 2nd vector.\n      VmaSuballocation refSuballoc;\n      refSuballoc.offset = offset;\n      // Rest of members stays uninitialized intentionally for better performance.\n      SuballocationVectorType::iterator it = m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ?\n         VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetLess()) :\n         VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetGreater());\n      if (it != suballocations2nd.end()) {\n         it->type = VMA_SUBALLOCATION_TYPE_FREE;\n         it->hAllocation = VK_NULL_HANDLE;\n         ++m_2ndNullItemsCount;\n         m_SumFreeSize += it->size;\n         CleanupAfterFree();\n         return;\n      }\n   }\n\n   VMA_ASSERT(0 && \"Allocation to free not found in linear allocator!\");\n}\n\nbool VmaBlockMetadata_Linear::ShouldCompact1st() const\n{\n   const size_t nullItemCount = m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount;\n   const size_t suballocCount = AccessSuballocations1st().size();\n   return suballocCount > 32 && nullItemCount * 2 >= (suballocCount - nullItemCount) * 3;\n}\n\nvoid VmaBlockMetadata_Linear::CleanupAfterFree()\n{\n   SuballocationVectorType& suballocations1st = AccessSuballocations1st();\n   SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();\n\n   if (IsEmpty()) {\n      suballocations1st.clear();\n      suballocations2nd.clear();\n      m_1stNullItemsBeginCount = 0;\n      m_1stNullItemsMiddleCount = 0;\n      m_2ndNullItemsCount = 0;\n      m_2ndVectorMode = SECOND_VECTOR_EMPTY;\n   } else {\n      const size_t suballoc1stCount = suballocations1st.size();\n      const size_t nullItem1stCount = m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount;\n      VMA_ASSERT(nullItem1stCount <= suballoc1stCount);\n\n      // Find more null items at the beginning of 1st vector.\n      while (m_1stNullItemsBeginCount < suballoc1stCount &&\n             suballocations1st[m_1stNullItemsBeginCount].hAllocation == VK_NULL_HANDLE) {\n         ++m_1stNullItemsBeginCount;\n         --m_1stNullItemsMiddleCount;\n      }\n\n      // Find more null items at the end of 1st vector.\n      while (m_1stNullItemsMiddleCount > 0 &&\n             suballocations1st.back().hAllocation == VK_NULL_HANDLE) {\n         --m_1stNullItemsMiddleCount;\n         suballocations1st.pop_back();\n      }\n\n      // Find more null items at the end of 2nd vector.\n      while (m_2ndNullItemsCount > 0 &&\n             suballocations2nd.back().hAllocation == VK_NULL_HANDLE) {\n         --m_2ndNullItemsCount;\n         suballocations2nd.pop_back();\n      }\n\n      // Find more null items at the beginning of 2nd vector.\n      while (m_2ndNullItemsCount > 0 &&\n             suballocations2nd[0].hAllocation == VK_NULL_HANDLE) {\n         --m_2ndNullItemsCount;\n         VmaVectorRemove(suballocations2nd, 0);\n      }\n\n      if (ShouldCompact1st()) {\n         const size_t nonNullItemCount = suballoc1stCount - nullItem1stCount;\n         size_t srcIndex = m_1stNullItemsBeginCount;\n         for (size_t dstIndex = 0; dstIndex < nonNullItemCount; ++dstIndex) {\n            while (suballocations1st[srcIndex].hAllocation == VK_NULL_HANDLE) {\n               ++srcIndex;\n            }\n            if (dstIndex != srcIndex) {\n               suballocations1st[dstIndex] = suballocations1st[srcIndex];\n            }\n            ++srcIndex;\n         }\n         suballocations1st.resize(nonNullItemCount);\n         m_1stNullItemsBeginCount = 0;\n         m_1stNullItemsMiddleCount = 0;\n      }\n\n      // 2nd vector became empty.\n      if (suballocations2nd.empty()) {\n         m_2ndVectorMode = SECOND_VECTOR_EMPTY;\n      }\n\n      // 1st vector became empty.\n      if (suballocations1st.size() - m_1stNullItemsBeginCount == 0) {\n         suballocations1st.clear();\n         m_1stNullItemsBeginCount = 0;\n\n         if (!suballocations2nd.empty() && m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) {\n            // Swap 1st with 2nd. Now 2nd is empty.\n            m_2ndVectorMode = SECOND_VECTOR_EMPTY;\n            m_1stNullItemsMiddleCount = m_2ndNullItemsCount;\n            while (m_1stNullItemsBeginCount < suballocations2nd.size() &&\n                   suballocations2nd[m_1stNullItemsBeginCount].hAllocation == VK_NULL_HANDLE) {\n               ++m_1stNullItemsBeginCount;\n               --m_1stNullItemsMiddleCount;\n            }\n            m_2ndNullItemsCount = 0;\n            m_1stVectorIndex ^= 1;\n         }\n      }\n   }\n\n   VMA_HEAVY_ASSERT(Validate());\n}\n\n\n////////////////////////////////////////////////////////////////////////////////\n// class VmaBlockMetadata_Buddy\n\nVmaBlockMetadata_Buddy::VmaBlockMetadata_Buddy(VmaAllocator hAllocator) :\n   VmaBlockMetadata(hAllocator),\n   m_Root(VMA_NULL),\n   m_AllocationCount(0),\n   m_FreeCount(1),\n   m_SumFreeSize(0)\n{\n   memset(m_FreeList, 0, sizeof(m_FreeList));\n}\n\nVmaBlockMetadata_Buddy::~VmaBlockMetadata_Buddy()\n{\n   DeleteNode(m_Root);\n}\n\nvoid VmaBlockMetadata_Buddy::Init(VkDeviceSize size)\n{\n   VmaBlockMetadata::Init(size);\n\n   m_UsableSize = VmaPrevPow2(size);\n   m_SumFreeSize = m_UsableSize;\n\n   // Calculate m_LevelCount.\n   m_LevelCount = 1;\n   while (m_LevelCount < MAX_LEVELS &&\n          LevelToNodeSize(m_LevelCount) >= MIN_NODE_SIZE) {\n      ++m_LevelCount;\n   }\n\n   Node* rootNode = vma_new(GetAllocationCallbacks(), Node)();\n   rootNode->offset = 0;\n   rootNode->type = Node::TYPE_FREE;\n   rootNode->parent = VMA_NULL;\n   rootNode->buddy = VMA_NULL;\n\n   m_Root = rootNode;\n   AddToFreeListFront(0, rootNode);\n}\n\nbool VmaBlockMetadata_Buddy::Validate() const\n{\n   // Validate tree.\n   ValidationContext ctx;\n   if (!ValidateNode(ctx, VMA_NULL, m_Root, 0, LevelToNodeSize(0))) {\n      VMA_VALIDATE(false && \"ValidateNode failed.\");\n   }\n   VMA_VALIDATE(m_AllocationCount == ctx.calculatedAllocationCount);\n   VMA_VALIDATE(m_SumFreeSize == ctx.calculatedSumFreeSize);\n\n   // Validate free node lists.\n   for (uint32_t level = 0; level < m_LevelCount; ++level) {\n      VMA_VALIDATE(m_FreeList[level].front == VMA_NULL ||\n                   m_FreeList[level].front->free.prev == VMA_NULL);\n\n      for (Node* node = m_FreeList[level].front;\n           node != VMA_NULL;\n           node = node->free.next) {\n         VMA_VALIDATE(node->type == Node::TYPE_FREE);\n\n         if (node->free.next == VMA_NULL) {\n            VMA_VALIDATE(m_FreeList[level].back == node);\n         } else {\n            VMA_VALIDATE(node->free.next->free.prev == node);\n         }\n      }\n   }\n\n   // Validate that free lists ar higher levels are empty.\n   for (uint32_t level = m_LevelCount; level < MAX_LEVELS; ++level) {\n      VMA_VALIDATE(m_FreeList[level].front == VMA_NULL && m_FreeList[level].back == VMA_NULL);\n   }\n\n   return true;\n}\n\nVkDeviceSize VmaBlockMetadata_Buddy::GetUnusedRangeSizeMax() const\n{\n   for (uint32_t level = 0; level < m_LevelCount; ++level) {\n      if (m_FreeList[level].front != VMA_NULL) {\n         return LevelToNodeSize(level);\n      }\n   }\n   return 0;\n}\n\nvoid VmaBlockMetadata_Buddy::CalcAllocationStatInfo(VmaStatInfo& outInfo) const\n{\n   const VkDeviceSize unusableSize = GetUnusableSize();\n\n   outInfo.blockCount = 1;\n\n   outInfo.allocationCount = outInfo.unusedRangeCount = 0;\n   outInfo.usedBytes = outInfo.unusedBytes = 0;\n\n   outInfo.allocationSizeMax = outInfo.unusedRangeSizeMax = 0;\n   outInfo.allocationSizeMin = outInfo.unusedRangeSizeMin = UINT64_MAX;\n   outInfo.allocationSizeAvg = outInfo.unusedRangeSizeAvg = 0; // Unused.\n\n   CalcAllocationStatInfoNode(outInfo, m_Root, LevelToNodeSize(0));\n\n   if (unusableSize > 0) {\n      ++outInfo.unusedRangeCount;\n      outInfo.unusedBytes += unusableSize;\n      outInfo.unusedRangeSizeMax = VMA_MAX(outInfo.unusedRangeSizeMax, unusableSize);\n      outInfo.unusedRangeSizeMin = VMA_MIN(outInfo.unusedRangeSizeMin, unusableSize);\n   }\n}\n\nvoid VmaBlockMetadata_Buddy::AddPoolStats(VmaPoolStats& inoutStats) const\n{\n   const VkDeviceSize unusableSize = GetUnusableSize();\n\n   inoutStats.size += GetSize();\n   inoutStats.unusedSize += m_SumFreeSize + unusableSize;\n   inoutStats.allocationCount += m_AllocationCount;\n   inoutStats.unusedRangeCount += m_FreeCount;\n   inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, GetUnusedRangeSizeMax());\n\n   if (unusableSize > 0) {\n      ++inoutStats.unusedRangeCount;\n      // Not updating inoutStats.unusedRangeSizeMax with unusableSize because this space is not available for allocations.\n   }\n}\n\n#if VMA_STATS_STRING_ENABLED\n\nvoid VmaBlockMetadata_Buddy::PrintDetailedMap(class VmaJsonWriter& json) const\n{\n   // TODO optimize\n   VmaStatInfo stat;\n   CalcAllocationStatInfo(stat);\n\n   PrintDetailedMap_Begin(\n      json,\n      stat.unusedBytes,\n      stat.allocationCount,\n      stat.unusedRangeCount);\n\n   PrintDetailedMapNode(json, m_Root, LevelToNodeSize(0));\n\n   const VkDeviceSize unusableSize = GetUnusableSize();\n   if (unusableSize > 0) {\n      PrintDetailedMap_UnusedRange(json,\n                                   m_UsableSize, // offset\n                                   unusableSize); // size\n   }\n\n   PrintDetailedMap_End(json);\n}\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\nbool VmaBlockMetadata_Buddy::CreateAllocationRequest(\n   uint32_t currentFrameIndex,\n   uint32_t frameInUseCount,\n   VkDeviceSize bufferImageGranularity,\n   VkDeviceSize allocSize,\n   VkDeviceSize allocAlignment,\n   bool upperAddress,\n   VmaSuballocationType allocType,\n   bool canMakeOtherLost,\n   uint32_t strategy,\n   VmaAllocationRequest* pAllocationRequest)\n{\n   VMA_ASSERT(!upperAddress && \"VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT can be used only with linear algorithm.\");\n\n   // Simple way to respect bufferImageGranularity. May be optimized some day.\n   // Whenever it might be an OPTIMAL image...\n   if (allocType == VMA_SUBALLOCATION_TYPE_UNKNOWN ||\n       allocType == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN ||\n       allocType == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL) {\n      allocAlignment = VMA_MAX(allocAlignment, bufferImageGranularity);\n      allocSize = VMA_MAX(allocSize, bufferImageGranularity);\n   }\n\n   if (allocSize > m_UsableSize) {\n      return false;\n   }\n\n   const uint32_t targetLevel = AllocSizeToLevel(allocSize);\n   for (uint32_t level = targetLevel + 1; level--; ) {\n      for (Node* freeNode = m_FreeList[level].front;\n           freeNode != VMA_NULL;\n           freeNode = freeNode->free.next) {\n         if (freeNode->offset % allocAlignment == 0) {\n            pAllocationRequest->type = VmaAllocationRequestType::Normal;\n            pAllocationRequest->offset = freeNode->offset;\n            pAllocationRequest->sumFreeSize = LevelToNodeSize(level);\n            pAllocationRequest->sumItemSize = 0;\n            pAllocationRequest->itemsToMakeLostCount = 0;\n            pAllocationRequest->customData = (void*)(uintptr_t)level;\n            return true;\n         }\n      }\n   }\n\n   return false;\n}\n\nbool VmaBlockMetadata_Buddy::MakeRequestedAllocationsLost(\n   uint32_t currentFrameIndex,\n   uint32_t frameInUseCount,\n   VmaAllocationRequest* pAllocationRequest)\n{\n   /*\n   Lost allocations are not supported in buddy allocator at the moment.\n   Support might be added in the future.\n   */\n   return pAllocationRequest->itemsToMakeLostCount == 0;\n}\n\nuint32_t VmaBlockMetadata_Buddy::MakeAllocationsLost(uint32_t currentFrameIndex, uint32_t frameInUseCount)\n{\n   /*\n   Lost allocations are not supported in buddy allocator at the moment.\n   Support might be added in the future.\n   */\n   return 0;\n}\n\nvoid VmaBlockMetadata_Buddy::Alloc(\n   const VmaAllocationRequest& request,\n   VmaSuballocationType type,\n   VkDeviceSize allocSize,\n   VmaAllocation hAllocation)\n{\n   VMA_ASSERT(request.type == VmaAllocationRequestType::Normal);\n\n   const uint32_t targetLevel = AllocSizeToLevel(allocSize);\n   uint32_t currLevel = (uint32_t)(uintptr_t)request.customData;\n\n   Node* currNode = m_FreeList[currLevel].front;\n   VMA_ASSERT(currNode != VMA_NULL && currNode->type == Node::TYPE_FREE);\n   while (currNode->offset != request.offset) {\n      currNode = currNode->free.next;\n      VMA_ASSERT(currNode != VMA_NULL && currNode->type == Node::TYPE_FREE);\n   }\n\n   // Go down, splitting free nodes.\n   while (currLevel < targetLevel) {\n      // currNode is already first free node at currLevel.\n      // Remove it from list of free nodes at this currLevel.\n      RemoveFromFreeList(currLevel, currNode);\n\n      const uint32_t childrenLevel = currLevel + 1;\n\n      // Create two free sub-nodes.\n      Node* leftChild = vma_new(GetAllocationCallbacks(), Node)();\n      Node* rightChild = vma_new(GetAllocationCallbacks(), Node)();\n\n      leftChild->offset = currNode->offset;\n      leftChild->type = Node::TYPE_FREE;\n      leftChild->parent = currNode;\n      leftChild->buddy = rightChild;\n\n      rightChild->offset = currNode->offset + LevelToNodeSize(childrenLevel);\n      rightChild->type = Node::TYPE_FREE;\n      rightChild->parent = currNode;\n      rightChild->buddy = leftChild;\n\n      // Convert current currNode to split type.\n      currNode->type = Node::TYPE_SPLIT;\n      currNode->split.leftChild = leftChild;\n\n      // Add child nodes to free list. Order is important!\n      AddToFreeListFront(childrenLevel, rightChild);\n      AddToFreeListFront(childrenLevel, leftChild);\n\n      ++m_FreeCount;\n      //m_SumFreeSize -= LevelToNodeSize(currLevel) % 2; // Useful only when level node sizes can be non power of 2.\n      ++currLevel;\n      currNode = m_FreeList[currLevel].front;\n\n      /*\n      We can be sure that currNode, as left child of node previously split,\n      also fullfills the alignment requirement.\n      */\n   }\n\n   // Remove from free list.\n   VMA_ASSERT(currLevel == targetLevel &&\n              currNode != VMA_NULL &&\n              currNode->type == Node::TYPE_FREE);\n   RemoveFromFreeList(currLevel, currNode);\n\n   // Convert to allocation node.\n   currNode->type = Node::TYPE_ALLOCATION;\n   currNode->allocation.alloc = hAllocation;\n\n   ++m_AllocationCount;\n   --m_FreeCount;\n   m_SumFreeSize -= allocSize;\n}\n\nvoid VmaBlockMetadata_Buddy::DeleteNode(Node* node)\n{\n   if (node->type == Node::TYPE_SPLIT) {\n      DeleteNode(node->split.leftChild->buddy);\n      DeleteNode(node->split.leftChild);\n   }\n\n   vma_delete(GetAllocationCallbacks(), node);\n}\n\nbool VmaBlockMetadata_Buddy::ValidateNode(ValidationContext& ctx, const Node* parent, const Node* curr, uint32_t level, VkDeviceSize levelNodeSize) const\n{\n   VMA_VALIDATE(level < m_LevelCount);\n   VMA_VALIDATE(curr->parent == parent);\n   VMA_VALIDATE((curr->buddy == VMA_NULL) == (parent == VMA_NULL));\n   VMA_VALIDATE(curr->buddy == VMA_NULL || curr->buddy->buddy == curr);\n   switch (curr->type) {\n   case Node::TYPE_FREE:\n      // curr->free.prev, next are validated separately.\n      ctx.calculatedSumFreeSize += levelNodeSize;\n      ++ctx.calculatedFreeCount;\n      break;\n   case Node::TYPE_ALLOCATION:\n      ++ctx.calculatedAllocationCount;\n      ctx.calculatedSumFreeSize += levelNodeSize - curr->allocation.alloc->GetSize();\n      VMA_VALIDATE(curr->allocation.alloc != VK_NULL_HANDLE);\n      break;\n   case Node::TYPE_SPLIT:\n   {\n      const uint32_t childrenLevel = level + 1;\n      const VkDeviceSize childrenLevelNodeSize = levelNodeSize / 2;\n      const Node* const leftChild = curr->split.leftChild;\n      VMA_VALIDATE(leftChild != VMA_NULL);\n      VMA_VALIDATE(leftChild->offset == curr->offset);\n      if (!ValidateNode(ctx, curr, leftChild, childrenLevel, childrenLevelNodeSize)) {\n         VMA_VALIDATE(false && \"ValidateNode for left child failed.\");\n      }\n      const Node* const rightChild = leftChild->buddy;\n      VMA_VALIDATE(rightChild->offset == curr->offset + childrenLevelNodeSize);\n      if (!ValidateNode(ctx, curr, rightChild, childrenLevel, childrenLevelNodeSize)) {\n         VMA_VALIDATE(false && \"ValidateNode for right child failed.\");\n      }\n   }\n   break;\n   default:\n      return false;\n   }\n\n   return true;\n}\n\nuint32_t VmaBlockMetadata_Buddy::AllocSizeToLevel(VkDeviceSize allocSize) const\n{\n   // I know this could be optimized somehow e.g. by using std::log2p1 from C++20.\n   uint32_t level = 0;\n   VkDeviceSize currLevelNodeSize = m_UsableSize;\n   VkDeviceSize nextLevelNodeSize = currLevelNodeSize >> 1;\n   while (allocSize <= nextLevelNodeSize && level + 1 < m_LevelCount) {\n      ++level;\n      currLevelNodeSize = nextLevelNodeSize;\n      nextLevelNodeSize = currLevelNodeSize >> 1;\n   }\n   return level;\n}\n\nvoid VmaBlockMetadata_Buddy::FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset)\n{\n   // Find node and level.\n   Node* node = m_Root;\n   VkDeviceSize nodeOffset = 0;\n   uint32_t level = 0;\n   VkDeviceSize levelNodeSize = LevelToNodeSize(0);\n   while (node->type == Node::TYPE_SPLIT) {\n      const VkDeviceSize nextLevelSize = levelNodeSize >> 1;\n      if (offset < nodeOffset + nextLevelSize) {\n         node = node->split.leftChild;\n      } else {\n         node = node->split.leftChild->buddy;\n         nodeOffset += nextLevelSize;\n      }\n      ++level;\n      levelNodeSize = nextLevelSize;\n   }\n\n   VMA_ASSERT(node != VMA_NULL && node->type == Node::TYPE_ALLOCATION);\n   VMA_ASSERT(alloc == VK_NULL_HANDLE || node->allocation.alloc == alloc);\n\n   ++m_FreeCount;\n   --m_AllocationCount;\n   m_SumFreeSize += alloc->GetSize();\n\n   node->type = Node::TYPE_FREE;\n\n   // Join free nodes if possible.\n   while (level > 0 && node->buddy->type == Node::TYPE_FREE) {\n      RemoveFromFreeList(level, node->buddy);\n      Node* const parent = node->parent;\n\n      vma_delete(GetAllocationCallbacks(), node->buddy);\n      vma_delete(GetAllocationCallbacks(), node);\n      parent->type = Node::TYPE_FREE;\n\n      node = parent;\n      --level;\n      //m_SumFreeSize += LevelToNodeSize(level) % 2; // Useful only when level node sizes can be non power of 2.\n      --m_FreeCount;\n   }\n\n   AddToFreeListFront(level, node);\n}\n\nvoid VmaBlockMetadata_Buddy::CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const\n{\n   switch (node->type) {\n   case Node::TYPE_FREE:\n      ++outInfo.unusedRangeCount;\n      outInfo.unusedBytes += levelNodeSize;\n      outInfo.unusedRangeSizeMax = VMA_MAX(outInfo.unusedRangeSizeMax, levelNodeSize);\n      outInfo.unusedRangeSizeMin = VMA_MAX(outInfo.unusedRangeSizeMin, levelNodeSize);\n      break;\n   case Node::TYPE_ALLOCATION:\n   {\n      const VkDeviceSize allocSize = node->allocation.alloc->GetSize();\n      ++outInfo.allocationCount;\n      outInfo.usedBytes += allocSize;\n      outInfo.allocationSizeMax = VMA_MAX(outInfo.allocationSizeMax, allocSize);\n      outInfo.allocationSizeMin = VMA_MAX(outInfo.allocationSizeMin, allocSize);\n\n      const VkDeviceSize unusedRangeSize = levelNodeSize - allocSize;\n      if (unusedRangeSize > 0) {\n         ++outInfo.unusedRangeCount;\n         outInfo.unusedBytes += unusedRangeSize;\n         outInfo.unusedRangeSizeMax = VMA_MAX(outInfo.unusedRangeSizeMax, unusedRangeSize);\n         outInfo.unusedRangeSizeMin = VMA_MAX(outInfo.unusedRangeSizeMin, unusedRangeSize);\n      }\n   }\n   break;\n   case Node::TYPE_SPLIT:\n   {\n      const VkDeviceSize childrenNodeSize = levelNodeSize / 2;\n      const Node* const leftChild = node->split.leftChild;\n      CalcAllocationStatInfoNode(outInfo, leftChild, childrenNodeSize);\n      const Node* const rightChild = leftChild->buddy;\n      CalcAllocationStatInfoNode(outInfo, rightChild, childrenNodeSize);\n   }\n   break;\n   default:\n      VMA_ASSERT(0);\n   }\n}\n\nvoid VmaBlockMetadata_Buddy::AddToFreeListFront(uint32_t level, Node* node)\n{\n   VMA_ASSERT(node->type == Node::TYPE_FREE);\n\n   // List is empty.\n   Node* const frontNode = m_FreeList[level].front;\n   if (frontNode == VMA_NULL) {\n      VMA_ASSERT(m_FreeList[level].back == VMA_NULL);\n      node->free.prev = node->free.next = VMA_NULL;\n      m_FreeList[level].front = m_FreeList[level].back = node;\n   } else {\n      VMA_ASSERT(frontNode->free.prev == VMA_NULL);\n      node->free.prev = VMA_NULL;\n      node->free.next = frontNode;\n      frontNode->free.prev = node;\n      m_FreeList[level].front = node;\n   }\n}\n\nvoid VmaBlockMetadata_Buddy::RemoveFromFreeList(uint32_t level, Node* node)\n{\n   VMA_ASSERT(m_FreeList[level].front != VMA_NULL);\n\n   // It is at the front.\n   if (node->free.prev == VMA_NULL) {\n      VMA_ASSERT(m_FreeList[level].front == node);\n      m_FreeList[level].front = node->free.next;\n   } else {\n      Node* const prevFreeNode = node->free.prev;\n      VMA_ASSERT(prevFreeNode->free.next == node);\n      prevFreeNode->free.next = node->free.next;\n   }\n\n   // It is at the back.\n   if (node->free.next == VMA_NULL) {\n      VMA_ASSERT(m_FreeList[level].back == node);\n      m_FreeList[level].back = node->free.prev;\n   } else {\n      Node* const nextFreeNode = node->free.next;\n      VMA_ASSERT(nextFreeNode->free.prev == node);\n      nextFreeNode->free.prev = node->free.prev;\n   }\n}\n\n#if VMA_STATS_STRING_ENABLED\nvoid VmaBlockMetadata_Buddy::PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const\n{\n   switch (node->type) {\n   case Node::TYPE_FREE:\n      PrintDetailedMap_UnusedRange(json, node->offset, levelNodeSize);\n      break;\n   case Node::TYPE_ALLOCATION:\n   {\n      PrintDetailedMap_Allocation(json, node->offset, node->allocation.alloc);\n      const VkDeviceSize allocSize = node->allocation.alloc->GetSize();\n      if (allocSize < levelNodeSize) {\n         PrintDetailedMap_UnusedRange(json, node->offset + allocSize, levelNodeSize - allocSize);\n      }\n   }\n   break;\n   case Node::TYPE_SPLIT:\n   {\n      const VkDeviceSize childrenNodeSize = levelNodeSize / 2;\n      const Node* const leftChild = node->split.leftChild;\n      PrintDetailedMapNode(json, leftChild, childrenNodeSize);\n      const Node* const rightChild = leftChild->buddy;\n      PrintDetailedMapNode(json, rightChild, childrenNodeSize);\n   }\n   break;\n   default:\n      VMA_ASSERT(0);\n   }\n}\n#endif // #if VMA_STATS_STRING_ENABLED\n\n\n////////////////////////////////////////////////////////////////////////////////\n// class VmaDeviceMemoryBlock\n\nVmaDeviceMemoryBlock::VmaDeviceMemoryBlock(VmaAllocator hAllocator) :\n   m_pMetadata(VMA_NULL),\n   m_MemoryTypeIndex(UINT32_MAX),\n   m_Id(0),\n   m_hMemory(VK_NULL_HANDLE),\n   m_MapCount(0),\n   m_pMappedData(VMA_NULL)\n{\n}\n\nvoid VmaDeviceMemoryBlock::Init(\n   VmaAllocator hAllocator,\n   VmaPool hParentPool,\n   uint32_t newMemoryTypeIndex,\n   VkDeviceMemory newMemory,\n   VkDeviceSize newSize,\n   uint32_t id,\n   uint32_t algorithm)\n{\n   VMA_ASSERT(m_hMemory == VK_NULL_HANDLE);\n\n   m_hParentPool = hParentPool;\n   m_MemoryTypeIndex = newMemoryTypeIndex;\n   m_Id = id;\n   m_hMemory = newMemory;\n\n   switch (algorithm) {\n   case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT:\n      m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Linear)(hAllocator);\n      break;\n   case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT:\n      m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Buddy)(hAllocator);\n      break;\n   default:\n      VMA_ASSERT(0);\n      // Fall-through.\n   case 0:\n      m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Generic)(hAllocator);\n   }\n   m_pMetadata->Init(newSize);\n}\n\nvoid VmaDeviceMemoryBlock::Destroy(VmaAllocator allocator)\n{\n   // This is the most important assert in the entire library.\n   // Hitting it means you have some memory leak - unreleased VmaAllocation objects.\n   VMA_ASSERT(m_pMetadata->IsEmpty() && \"Some allocations were not freed before destruction of this memory block!\");\n\n   VMA_ASSERT(m_hMemory != VK_NULL_HANDLE);\n   allocator->FreeVulkanMemory(m_MemoryTypeIndex, m_pMetadata->GetSize(), m_hMemory);\n   m_hMemory = VK_NULL_HANDLE;\n\n   vma_delete(allocator, m_pMetadata);\n   m_pMetadata = VMA_NULL;\n}\n\nbool VmaDeviceMemoryBlock::Validate() const\n{\n   VMA_VALIDATE((m_hMemory != VK_NULL_HANDLE) &&\n      (m_pMetadata->GetSize() != 0));\n\n   return m_pMetadata->Validate();\n}\n\nVkResult VmaDeviceMemoryBlock::CheckCorruption(VmaAllocator hAllocator)\n{\n   void* pData = nullptr;\n   VkResult res = Map(hAllocator, 1, &pData);\n   if (res != VK_SUCCESS) {\n      return res;\n   }\n\n   res = m_pMetadata->CheckCorruption(pData);\n\n   Unmap(hAllocator, 1);\n\n   return res;\n}\n\nVkResult VmaDeviceMemoryBlock::Map(VmaAllocator hAllocator, uint32_t count, void** ppData)\n{\n   if (count == 0) {\n      return VK_SUCCESS;\n   }\n\n   VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex);\n   if (m_MapCount != 0) {\n      m_MapCount += count;\n      VMA_ASSERT(m_pMappedData != VMA_NULL);\n      if (ppData != VMA_NULL) {\n         *ppData = m_pMappedData;\n      }\n      return VK_SUCCESS;\n   } else {\n      VkResult result = (*hAllocator->GetVulkanFunctions().vkMapMemory)(\n         hAllocator->m_hDevice,\n         m_hMemory,\n         0, // offset\n         VK_WHOLE_SIZE,\n         0, // flags\n         &m_pMappedData);\n      if (result == VK_SUCCESS) {\n         if (ppData != VMA_NULL) {\n            *ppData = m_pMappedData;\n         }\n         m_MapCount = count;\n      }\n      return result;\n   }\n}\n\nvoid VmaDeviceMemoryBlock::Unmap(VmaAllocator hAllocator, uint32_t count)\n{\n   if (count == 0) {\n      return;\n   }\n\n   VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex);\n   if (m_MapCount >= count) {\n      m_MapCount -= count;\n      if (m_MapCount == 0) {\n         m_pMappedData = VMA_NULL;\n         (*hAllocator->GetVulkanFunctions().vkUnmapMemory)(hAllocator->m_hDevice, m_hMemory);\n      }\n   } else {\n      VMA_ASSERT(0 && \"VkDeviceMemory block is being unmapped while it was not previously mapped.\");\n   }\n}\n\nVkResult VmaDeviceMemoryBlock::WriteMagicValueAroundAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize)\n{\n   VMA_ASSERT(VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_MARGIN % 4 == 0 && VMA_DEBUG_DETECT_CORRUPTION);\n   VMA_ASSERT(allocOffset >= VMA_DEBUG_MARGIN);\n\n   void* pData;\n   VkResult res = Map(hAllocator, 1, &pData);\n   if (res != VK_SUCCESS) {\n      return res;\n   }\n\n   VmaWriteMagicValue(pData, allocOffset - VMA_DEBUG_MARGIN);\n   VmaWriteMagicValue(pData, allocOffset + allocSize);\n\n   Unmap(hAllocator, 1);\n\n   return VK_SUCCESS;\n}\n\nVkResult VmaDeviceMemoryBlock::ValidateMagicValueAroundAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize)\n{\n   VMA_ASSERT(VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_MARGIN % 4 == 0 && VMA_DEBUG_DETECT_CORRUPTION);\n   VMA_ASSERT(allocOffset >= VMA_DEBUG_MARGIN);\n\n   void* pData;\n   VkResult res = Map(hAllocator, 1, &pData);\n   if (res != VK_SUCCESS) {\n      return res;\n   }\n\n   if (!VmaValidateMagicValue(pData, allocOffset - VMA_DEBUG_MARGIN)) {\n      VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED BEFORE FREED ALLOCATION!\");\n   } else if (!VmaValidateMagicValue(pData, allocOffset + allocSize)) {\n      VMA_ASSERT(0 && \"MEMORY CORRUPTION DETECTED AFTER FREED ALLOCATION!\");\n   }\n\n   Unmap(hAllocator, 1);\n\n   return VK_SUCCESS;\n}\n\nVkResult VmaDeviceMemoryBlock::BindBufferMemory(\n   const VmaAllocator hAllocator,\n   const VmaAllocation hAllocation,\n   VkDeviceSize allocationLocalOffset,\n   VkBuffer hBuffer,\n   const void* pNext)\n{\n   VMA_ASSERT(hAllocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK &&\n              hAllocation->GetBlock() == this);\n   VMA_ASSERT(allocationLocalOffset < hAllocation->GetSize() &&\n              \"Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?\");\n   const VkDeviceSize memoryOffset = hAllocation->GetOffset() + allocationLocalOffset;\n   // This lock is important so that we don't call vkBind... and/or vkMap... simultaneously on the same VkDeviceMemory from multiple threads.\n   VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex);\n   return hAllocator->BindVulkanBuffer(m_hMemory, memoryOffset, hBuffer, pNext);\n}\n\nVkResult VmaDeviceMemoryBlock::BindImageMemory(\n   const VmaAllocator hAllocator,\n   const VmaAllocation hAllocation,\n   VkDeviceSize allocationLocalOffset,\n   VkImage hImage,\n   const void* pNext)\n{\n   VMA_ASSERT(hAllocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK &&\n              hAllocation->GetBlock() == this);\n   VMA_ASSERT(allocationLocalOffset < hAllocation->GetSize() &&\n              \"Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?\");\n   const VkDeviceSize memoryOffset = hAllocation->GetOffset() + allocationLocalOffset;\n   // This lock is important so that we don't call vkBind... and/or vkMap... simultaneously on the same VkDeviceMemory from multiple threads.\n   VmaMutexLock lock(m_Mutex, hAllocator->m_UseMutex);\n   return hAllocator->BindVulkanImage(m_hMemory, memoryOffset, hImage, pNext);\n}\n\nstatic void InitStatInfo(VmaStatInfo& outInfo)\n{\n   memset(&outInfo, 0, sizeof(outInfo));\n   outInfo.allocationSizeMin = UINT64_MAX;\n   outInfo.unusedRangeSizeMin = UINT64_MAX;\n}\n\n// Adds statistics srcInfo into inoutInfo, like: inoutInfo += srcInfo.\nstatic void VmaAddStatInfo(VmaStatInfo& inoutInfo, const VmaStatInfo& srcInfo)\n{\n   inoutInfo.blockCount += srcInfo.blockCount;\n   inoutInfo.allocationCount += srcInfo.allocationCount;\n   inoutInfo.unusedRangeCount += srcInfo.unusedRangeCount;\n   inoutInfo.usedBytes += srcInfo.usedBytes;\n   inoutInfo.unusedBytes += srcInfo.unusedBytes;\n   inoutInfo.allocationSizeMin = VMA_MIN(inoutInfo.allocationSizeMin, srcInfo.allocationSizeMin);\n   inoutInfo.allocationSizeMax = VMA_MAX(inoutInfo.allocationSizeMax, srcInfo.allocationSizeMax);\n   inoutInfo.unusedRangeSizeMin = VMA_MIN(inoutInfo.unusedRangeSizeMin, srcInfo.unusedRangeSizeMin);\n   inoutInfo.unusedRangeSizeMax = VMA_MAX(inoutInfo.unusedRangeSizeMax, srcInfo.unusedRangeSizeMax);\n}\n\nstatic void VmaPostprocessCalcStatInfo(VmaStatInfo& inoutInfo)\n{\n   inoutInfo.allocationSizeAvg = (inoutInfo.allocationCount > 0) ?\n      VmaRoundDiv<VkDeviceSize>(inoutInfo.usedBytes, inoutInfo.allocationCount) : 0;\n   inoutInfo.unusedRangeSizeAvg = (inoutInfo.unusedRangeCount > 0) ?\n      VmaRoundDiv<VkDeviceSize>(inoutInfo.unusedBytes, inoutInfo.unusedRangeCount) : 0;\n}\n\nVmaPool_T::VmaPool_T(\n   VmaAllocator hAllocator,\n   const VmaPoolCreateInfo& createInfo,\n   VkDeviceSize preferredBlockSize) :\n   m_BlockVector(\n      hAllocator,\n      this, // hParentPool\n      createInfo.memoryTypeIndex,\n      createInfo.blockSize != 0 ? createInfo.blockSize : preferredBlockSize,\n      createInfo.minBlockCount,\n      createInfo.maxBlockCount,\n      (createInfo.flags& VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT) != 0 ? 1 : hAllocator->GetBufferImageGranularity(),\n      createInfo.frameInUseCount,\n      createInfo.blockSize != 0, // explicitBlockSize\n      createInfo.flags& VMA_POOL_CREATE_ALGORITHM_MASK), // algorithm\n   m_Id(0),\n   m_Name(VMA_NULL)\n{\n}\n\nVmaPool_T::~VmaPool_T()\n{\n}\n\nvoid VmaPool_T::SetName(const char* pName)\n{\n   const VkAllocationCallbacks* allocs = m_BlockVector.GetAllocator()->GetAllocationCallbacks();\n   VmaFreeString(allocs, m_Name);\n\n   if (pName != VMA_NULL) {\n      m_Name = VmaCreateStringCopy(allocs, pName);\n   } else {\n      m_Name = VMA_NULL;\n   }\n}\n\n#if VMA_STATS_STRING_ENABLED\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\nVmaBlockVector::VmaBlockVector(\n   VmaAllocator hAllocator,\n   VmaPool hParentPool,\n   uint32_t memoryTypeIndex,\n   VkDeviceSize preferredBlockSize,\n   size_t minBlockCount,\n   size_t maxBlockCount,\n   VkDeviceSize bufferImageGranularity,\n   uint32_t frameInUseCount,\n   bool explicitBlockSize,\n   uint32_t algorithm) :\n   m_hAllocator(hAllocator),\n   m_hParentPool(hParentPool),\n   m_MemoryTypeIndex(memoryTypeIndex),\n   m_PreferredBlockSize(preferredBlockSize),\n   m_MinBlockCount(minBlockCount),\n   m_MaxBlockCount(maxBlockCount),\n   m_BufferImageGranularity(bufferImageGranularity),\n   m_FrameInUseCount(frameInUseCount),\n   m_ExplicitBlockSize(explicitBlockSize),\n   m_Algorithm(algorithm),\n   m_HasEmptyBlock(false),\n   m_Blocks(VmaStlAllocator<VmaDeviceMemoryBlock*>(hAllocator->GetAllocationCallbacks())),\n   m_NextBlockId(0)\n{\n}\n\nVmaBlockVector::~VmaBlockVector()\n{\n   for (size_t i = m_Blocks.size(); i--; ) {\n      m_Blocks[i]->Destroy(m_hAllocator);\n      vma_delete(m_hAllocator, m_Blocks[i]);\n   }\n}\n\nVkResult VmaBlockVector::CreateMinBlocks()\n{\n   for (size_t i = 0; i < m_MinBlockCount; ++i) {\n      VkResult res = CreateBlock(m_PreferredBlockSize, VMA_NULL);\n      if (res != VK_SUCCESS) {\n         return res;\n      }\n   }\n   return VK_SUCCESS;\n}\n\nvoid VmaBlockVector::GetPoolStats(VmaPoolStats* pStats)\n{\n   VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex);\n\n   const size_t blockCount = m_Blocks.size();\n\n   pStats->size = 0;\n   pStats->unusedSize = 0;\n   pStats->allocationCount = 0;\n   pStats->unusedRangeCount = 0;\n   pStats->unusedRangeSizeMax = 0;\n   pStats->blockCount = blockCount;\n\n   for (uint32_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) {\n      const VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex];\n      VMA_ASSERT(pBlock);\n      VMA_HEAVY_ASSERT(pBlock->Validate());\n      pBlock->m_pMetadata->AddPoolStats(*pStats);\n   }\n}\n\nbool VmaBlockVector::IsEmpty()\n{\n   VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex);\n   return m_Blocks.empty();\n}\n\nbool VmaBlockVector::IsCorruptionDetectionEnabled() const\n{\n   const uint32_t requiredMemFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;\n   return (VMA_DEBUG_DETECT_CORRUPTION != 0) &&\n      (VMA_DEBUG_MARGIN > 0) &&\n      (m_Algorithm == 0 || m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) &&\n      (m_hAllocator->m_MemProps.memoryTypes[m_MemoryTypeIndex].propertyFlags & requiredMemFlags) == requiredMemFlags;\n}\n\nstatic const uint32_t VMA_ALLOCATION_TRY_COUNT = 32;\n\nVkResult VmaBlockVector::Allocate(\n   uint32_t currentFrameIndex,\n   VkDeviceSize size,\n   VkDeviceSize alignment,\n   const VmaAllocationCreateInfo& createInfo,\n   VmaSuballocationType suballocType,\n   size_t allocationCount,\n   VmaAllocation* pAllocations)\n{\n   size_t allocIndex;\n   VkResult res = VK_SUCCESS;\n\n   if (IsCorruptionDetectionEnabled()) {\n      size = VmaAlignUp<VkDeviceSize>(size, sizeof(VMA_CORRUPTION_DETECTION_MAGIC_VALUE));\n      alignment = VmaAlignUp<VkDeviceSize>(alignment, sizeof(VMA_CORRUPTION_DETECTION_MAGIC_VALUE));\n   }\n\n   {\n      VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex);\n      for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex) {\n         res = AllocatePage(\n            currentFrameIndex,\n            size,\n            alignment,\n            createInfo,\n            suballocType,\n            pAllocations + allocIndex);\n         if (res != VK_SUCCESS) {\n            break;\n         }\n      }\n   }\n\n   if (res != VK_SUCCESS) {\n      // Free all already created allocations.\n      while (allocIndex--) {\n         Free(pAllocations[allocIndex]);\n      }\n      memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount);\n   }\n\n   return res;\n}\n\nVkResult VmaBlockVector::AllocatePage(\n   uint32_t currentFrameIndex,\n   VkDeviceSize size,\n   VkDeviceSize alignment,\n   const VmaAllocationCreateInfo& createInfo,\n   VmaSuballocationType suballocType,\n   VmaAllocation* pAllocation)\n{\n   const bool isUpperAddress = (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0;\n   bool canMakeOtherLost = (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT) != 0;\n   const bool mapped = (createInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0;\n   const bool isUserDataString = (createInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0;\n\n   const bool withinBudget = (createInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0;\n   VkDeviceSize freeMemory;\n   {\n      const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex);\n      VmaBudget heapBudget = {};\n      m_hAllocator->GetBudget(&heapBudget, heapIndex, 1);\n      freeMemory = (heapBudget.usage < heapBudget.budget) ? (heapBudget.budget - heapBudget.usage) : 0;\n   }\n\n   const bool canFallbackToDedicated = !IsCustomPool();\n   const bool canCreateNewBlock =\n      ((createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0) &&\n      (m_Blocks.size() < m_MaxBlockCount) &&\n      (freeMemory >= size || !canFallbackToDedicated);\n   uint32_t strategy = createInfo.flags & VMA_ALLOCATION_CREATE_STRATEGY_MASK;\n\n   // If linearAlgorithm is used, canMakeOtherLost is available only when used as ring buffer.\n   // Which in turn is available only when maxBlockCount = 1.\n   if (m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT && m_MaxBlockCount > 1) {\n      canMakeOtherLost = false;\n   }\n\n   // Upper address can only be used with linear allocator and within single memory block.\n   if (isUpperAddress &&\n      (m_Algorithm != VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT || m_MaxBlockCount > 1)) {\n      return VK_ERROR_FEATURE_NOT_PRESENT;\n   }\n\n   // Validate strategy.\n   switch (strategy) {\n   case 0:\n      strategy = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT;\n      break;\n   case VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT:\n   case VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT:\n   case VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT:\n      break;\n   default:\n      return VK_ERROR_FEATURE_NOT_PRESENT;\n   }\n\n   // Early reject: requested allocation size is larger that maximum block size for this block vector.\n   if (size + 2 * VMA_DEBUG_MARGIN > m_PreferredBlockSize) {\n      return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n   }\n\n   /*\n   Under certain condition, this whole section can be skipped for optimization, so\n   we move on directly to trying to allocate with canMakeOtherLost. That's the case\n   e.g. for custom pools with linear algorithm.\n   */\n   if (!canMakeOtherLost || canCreateNewBlock) {\n      // 1. Search existing allocations. Try to allocate without making other allocations lost.\n      VmaAllocationCreateFlags allocFlagsCopy = createInfo.flags;\n      allocFlagsCopy &= ~VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;\n\n      if (m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) {\n         // Use only last block.\n         if (!m_Blocks.empty()) {\n            VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks.back();\n            VMA_ASSERT(pCurrBlock);\n            VkResult res = AllocateFromBlock(\n               pCurrBlock,\n               currentFrameIndex,\n               size,\n               alignment,\n               allocFlagsCopy,\n               createInfo.pUserData,\n               suballocType,\n               strategy,\n               pAllocation);\n            if (res == VK_SUCCESS) {\n               VMA_DEBUG_LOG(\"    Returned from last block #%u\", pCurrBlock->GetId());\n               return VK_SUCCESS;\n            }\n         }\n      } else {\n         if (strategy == VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT) {\n            // Forward order in m_Blocks - prefer blocks with smallest amount of free space.\n            for (size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) {\n               VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex];\n               VMA_ASSERT(pCurrBlock);\n               VkResult res = AllocateFromBlock(\n                  pCurrBlock,\n                  currentFrameIndex,\n                  size,\n                  alignment,\n                  allocFlagsCopy,\n                  createInfo.pUserData,\n                  suballocType,\n                  strategy,\n                  pAllocation);\n               if (res == VK_SUCCESS) {\n                  VMA_DEBUG_LOG(\"    Returned from existing block #%u\", pCurrBlock->GetId());\n                  return VK_SUCCESS;\n               }\n            }\n         } else // WORST_FIT, FIRST_FIT\n         {\n            // Backward order in m_Blocks - prefer blocks with largest amount of free space.\n            for (size_t blockIndex = m_Blocks.size(); blockIndex--; ) {\n               VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex];\n               VMA_ASSERT(pCurrBlock);\n               VkResult res = AllocateFromBlock(\n                  pCurrBlock,\n                  currentFrameIndex,\n                  size,\n                  alignment,\n                  allocFlagsCopy,\n                  createInfo.pUserData,\n                  suballocType,\n                  strategy,\n                  pAllocation);\n               if (res == VK_SUCCESS) {\n                  VMA_DEBUG_LOG(\"    Returned from existing block #%u\", pCurrBlock->GetId());\n                  return VK_SUCCESS;\n               }\n            }\n         }\n      }\n\n      // 2. Try to create new block.\n      if (canCreateNewBlock) {\n         // Calculate optimal size for new block.\n         VkDeviceSize newBlockSize = m_PreferredBlockSize;\n         uint32_t newBlockSizeShift = 0;\n         const uint32_t NEW_BLOCK_SIZE_SHIFT_MAX = 3;\n\n         if (!m_ExplicitBlockSize) {\n            // Allocate 1/8, 1/4, 1/2 as first blocks.\n            const VkDeviceSize maxExistingBlockSize = CalcMaxBlockSize();\n            for (uint32_t i = 0; i < NEW_BLOCK_SIZE_SHIFT_MAX; ++i) {\n               const VkDeviceSize smallerNewBlockSize = newBlockSize / 2;\n               if (smallerNewBlockSize > maxExistingBlockSize&& smallerNewBlockSize >= size * 2) {\n                  newBlockSize = smallerNewBlockSize;\n                  ++newBlockSizeShift;\n               } else {\n                  break;\n               }\n            }\n         }\n\n         size_t newBlockIndex = 0;\n         VkResult res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ?\n            CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY;\n         // Allocation of this size failed? Try 1/2, 1/4, 1/8 of m_PreferredBlockSize.\n         if (!m_ExplicitBlockSize) {\n            while (res < 0 && newBlockSizeShift < NEW_BLOCK_SIZE_SHIFT_MAX) {\n               const VkDeviceSize smallerNewBlockSize = newBlockSize / 2;\n               if (smallerNewBlockSize >= size) {\n                  newBlockSize = smallerNewBlockSize;\n                  ++newBlockSizeShift;\n                  res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ?\n                     CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY;\n               } else {\n                  break;\n               }\n            }\n         }\n\n         if (res == VK_SUCCESS) {\n            VmaDeviceMemoryBlock* const pBlock = m_Blocks[newBlockIndex];\n            VMA_ASSERT(pBlock->m_pMetadata->GetSize() >= size);\n\n            res = AllocateFromBlock(\n               pBlock,\n               currentFrameIndex,\n               size,\n               alignment,\n               allocFlagsCopy,\n               createInfo.pUserData,\n               suballocType,\n               strategy,\n               pAllocation);\n            if (res == VK_SUCCESS) {\n               VMA_DEBUG_LOG(\"    Created new block #%u Size=%llu\", pBlock->GetId(), newBlockSize);\n               return VK_SUCCESS;\n            } else {\n               // Allocation from new block failed, possibly due to VMA_DEBUG_MARGIN or alignment.\n               return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n            }\n         }\n      }\n   }\n\n   // 3. Try to allocate from existing blocks with making other allocations lost.\n   if (canMakeOtherLost) {\n      uint32_t tryIndex = 0;\n      for (; tryIndex < VMA_ALLOCATION_TRY_COUNT; ++tryIndex) {\n         VmaDeviceMemoryBlock* pBestRequestBlock = VMA_NULL;\n         VmaAllocationRequest bestRequest = {};\n         VkDeviceSize bestRequestCost = VK_WHOLE_SIZE;\n\n         // 1. Search existing allocations.\n         if (strategy == VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT) {\n            // Forward order in m_Blocks - prefer blocks with smallest amount of free space.\n            for (size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) {\n               VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex];\n               VMA_ASSERT(pCurrBlock);\n               VmaAllocationRequest currRequest = {};\n               if (pCurrBlock->m_pMetadata->CreateAllocationRequest(\n                  currentFrameIndex,\n                  m_FrameInUseCount,\n                  m_BufferImageGranularity,\n                  size,\n                  alignment,\n                  (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0,\n                  suballocType,\n                  canMakeOtherLost,\n                  strategy,\n                  &currRequest)) {\n                  const VkDeviceSize currRequestCost = currRequest.CalcCost();\n                  if (pBestRequestBlock == VMA_NULL ||\n                      currRequestCost < bestRequestCost) {\n                     pBestRequestBlock = pCurrBlock;\n                     bestRequest = currRequest;\n                     bestRequestCost = currRequestCost;\n\n                     if (bestRequestCost == 0) {\n                        break;\n                     }\n                  }\n               }\n            }\n         } else // WORST_FIT, FIRST_FIT\n         {\n            // Backward order in m_Blocks - prefer blocks with largest amount of free space.\n            for (size_t blockIndex = m_Blocks.size(); blockIndex--; ) {\n               VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex];\n               VMA_ASSERT(pCurrBlock);\n               VmaAllocationRequest currRequest = {};\n               if (pCurrBlock->m_pMetadata->CreateAllocationRequest(\n                  currentFrameIndex,\n                  m_FrameInUseCount,\n                  m_BufferImageGranularity,\n                  size,\n                  alignment,\n                  (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0,\n                  suballocType,\n                  canMakeOtherLost,\n                  strategy,\n                  &currRequest)) {\n                  const VkDeviceSize currRequestCost = currRequest.CalcCost();\n                  if (pBestRequestBlock == VMA_NULL ||\n                      currRequestCost < bestRequestCost ||\n                      strategy == VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT) {\n                     pBestRequestBlock = pCurrBlock;\n                     bestRequest = currRequest;\n                     bestRequestCost = currRequestCost;\n\n                     if (bestRequestCost == 0 ||\n                         strategy == VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT) {\n                        break;\n                     }\n                  }\n               }\n            }\n         }\n\n         if (pBestRequestBlock != VMA_NULL) {\n            if (mapped) {\n               VkResult res = pBestRequestBlock->Map(m_hAllocator, 1, VMA_NULL);\n               if (res != VK_SUCCESS) {\n                  return res;\n               }\n            }\n\n            if (pBestRequestBlock->m_pMetadata->MakeRequestedAllocationsLost(\n               currentFrameIndex,\n               m_FrameInUseCount,\n               &bestRequest)) {\n               // Allocate from this pBlock.\n               *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate();\n               (*pAllocation)->Ctor(currentFrameIndex, isUserDataString);\n               pBestRequestBlock->m_pMetadata->Alloc(bestRequest, suballocType, size, *pAllocation);\n               UpdateHasEmptyBlock();\n               (*pAllocation)->InitBlockAllocation(\n                  pBestRequestBlock,\n                  bestRequest.offset,\n                  alignment,\n                  size,\n                  m_MemoryTypeIndex,\n                  suballocType,\n                  mapped,\n                  (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0);\n               VMA_HEAVY_ASSERT(pBestRequestBlock->Validate());\n               VMA_DEBUG_LOG(\"    Returned from existing block\");\n               (*pAllocation)->SetUserData(m_hAllocator, createInfo.pUserData);\n               m_hAllocator->m_Budget.AddAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), size);\n               if (VMA_DEBUG_INITIALIZE_ALLOCATIONS) {\n                  m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED);\n               }\n               if (IsCorruptionDetectionEnabled()) {\n                  VkResult res = pBestRequestBlock->WriteMagicValueAroundAllocation(m_hAllocator, bestRequest.offset, size);\n                  VMA_ASSERT(res == VK_SUCCESS && \"Couldn't map block memory to write magic value.\");\n               }\n               return VK_SUCCESS;\n            }\n            // else: Some allocations must have been touched while we are here. Next try.\n         } else {\n            // Could not find place in any of the blocks - break outer loop.\n            break;\n         }\n      }\n      /* Maximum number of tries exceeded - a very unlike event when many other\n      threads are simultaneously touching allocations making it impossible to make\n      lost at the same time as we try to allocate. */\n      if (tryIndex == VMA_ALLOCATION_TRY_COUNT) {\n         return VK_ERROR_TOO_MANY_OBJECTS;\n      }\n   }\n\n   return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n}\n\nvoid VmaBlockVector::Free(\n   const VmaAllocation hAllocation)\n{\n   VmaDeviceMemoryBlock* pBlockToDelete = VMA_NULL;\n\n   bool budgetExceeded = false;\n   {\n      const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex);\n      VmaBudget heapBudget = {};\n      m_hAllocator->GetBudget(&heapBudget, heapIndex, 1);\n      budgetExceeded = heapBudget.usage >= heapBudget.budget;\n   }\n\n   // Scope for lock.\n   {\n      VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex);\n\n      VmaDeviceMemoryBlock* pBlock = hAllocation->GetBlock();\n\n      if (IsCorruptionDetectionEnabled()) {\n         VkResult res = pBlock->ValidateMagicValueAroundAllocation(m_hAllocator, hAllocation->GetOffset(), hAllocation->GetSize());\n         VMA_ASSERT(res == VK_SUCCESS && \"Couldn't map block memory to validate magic value.\");\n      }\n\n      if (hAllocation->IsPersistentMap()) {\n         pBlock->Unmap(m_hAllocator, 1);\n      }\n\n      pBlock->m_pMetadata->Free(hAllocation);\n      VMA_HEAVY_ASSERT(pBlock->Validate());\n\n      VMA_DEBUG_LOG(\"  Freed from MemoryTypeIndex=%u\", m_MemoryTypeIndex);\n\n      const bool canDeleteBlock = m_Blocks.size() > m_MinBlockCount;\n      // pBlock became empty after this deallocation.\n      if (pBlock->m_pMetadata->IsEmpty()) {\n         // Already has empty block. We don't want to have two, so delete this one.\n         if ((m_HasEmptyBlock || budgetExceeded) && canDeleteBlock) {\n            pBlockToDelete = pBlock;\n            Remove(pBlock);\n         }\n         // else: We now have an empty block - leave it.\n      }\n      // pBlock didn't become empty, but we have another empty block - find and free that one.\n      // (This is optional, heuristics.)\n      else if (m_HasEmptyBlock && canDeleteBlock) {\n         VmaDeviceMemoryBlock* pLastBlock = m_Blocks.back();\n         if (pLastBlock->m_pMetadata->IsEmpty()) {\n            pBlockToDelete = pLastBlock;\n            m_Blocks.pop_back();\n         }\n      }\n\n      UpdateHasEmptyBlock();\n      IncrementallySortBlocks();\n   }\n\n   // Destruction of a free block. Deferred until this point, outside of mutex\n   // lock, for performance reason.\n   if (pBlockToDelete != VMA_NULL) {\n      VMA_DEBUG_LOG(\"    Deleted empty block\");\n      pBlockToDelete->Destroy(m_hAllocator);\n      vma_delete(m_hAllocator, pBlockToDelete);\n   }\n}\n\nVkDeviceSize VmaBlockVector::CalcMaxBlockSize() const\n{\n   VkDeviceSize result = 0;\n   for (size_t i = m_Blocks.size(); i--; ) {\n      result = VMA_MAX(result, m_Blocks[i]->m_pMetadata->GetSize());\n      if (result >= m_PreferredBlockSize) {\n         break;\n      }\n   }\n   return result;\n}\n\nvoid VmaBlockVector::Remove(VmaDeviceMemoryBlock* pBlock)\n{\n   for (uint32_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) {\n      if (m_Blocks[blockIndex] == pBlock) {\n         VmaVectorRemove(m_Blocks, blockIndex);\n         return;\n      }\n   }\n   VMA_ASSERT(0);\n}\n\nvoid VmaBlockVector::IncrementallySortBlocks()\n{\n   if (m_Algorithm != VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) {\n      // Bubble sort only until first swap.\n      for (size_t i = 1; i < m_Blocks.size(); ++i) {\n         if (m_Blocks[i - 1]->m_pMetadata->GetSumFreeSize() > m_Blocks[i]->m_pMetadata->GetSumFreeSize()) {\n            VMA_SWAP(m_Blocks[i - 1], m_Blocks[i]);\n            return;\n         }\n      }\n   }\n}\n\nVkResult VmaBlockVector::AllocateFromBlock(\n   VmaDeviceMemoryBlock* pBlock,\n   uint32_t currentFrameIndex,\n   VkDeviceSize size,\n   VkDeviceSize alignment,\n   VmaAllocationCreateFlags allocFlags,\n   void* pUserData,\n   VmaSuballocationType suballocType,\n   uint32_t strategy,\n   VmaAllocation* pAllocation)\n{\n   VMA_ASSERT((allocFlags & VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT) == 0);\n   const bool isUpperAddress = (allocFlags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0;\n   const bool mapped = (allocFlags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0;\n   const bool isUserDataString = (allocFlags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0;\n\n   VmaAllocationRequest currRequest = {};\n   if (pBlock->m_pMetadata->CreateAllocationRequest(\n      currentFrameIndex,\n      m_FrameInUseCount,\n      m_BufferImageGranularity,\n      size,\n      alignment,\n      isUpperAddress,\n      suballocType,\n      false, // canMakeOtherLost\n      strategy,\n      &currRequest)) {\n      // Allocate from pCurrBlock.\n      VMA_ASSERT(currRequest.itemsToMakeLostCount == 0);\n\n      if (mapped) {\n         VkResult res = pBlock->Map(m_hAllocator, 1, VMA_NULL);\n         if (res != VK_SUCCESS) {\n            return res;\n         }\n      }\n\n      *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate();\n      (*pAllocation)->Ctor(currentFrameIndex, isUserDataString);\n      pBlock->m_pMetadata->Alloc(currRequest, suballocType, size, *pAllocation);\n      UpdateHasEmptyBlock();\n      (*pAllocation)->InitBlockAllocation(\n         pBlock,\n         currRequest.offset,\n         alignment,\n         size,\n         m_MemoryTypeIndex,\n         suballocType,\n         mapped,\n         (allocFlags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0);\n      VMA_HEAVY_ASSERT(pBlock->Validate());\n      (*pAllocation)->SetUserData(m_hAllocator, pUserData);\n      m_hAllocator->m_Budget.AddAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), size);\n      if (VMA_DEBUG_INITIALIZE_ALLOCATIONS) {\n         m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED);\n      }\n      if (IsCorruptionDetectionEnabled()) {\n         VkResult res = pBlock->WriteMagicValueAroundAllocation(m_hAllocator, currRequest.offset, size);\n         VMA_ASSERT(res == VK_SUCCESS && \"Couldn't map block memory to write magic value.\");\n      }\n      return VK_SUCCESS;\n   }\n   return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n}\n\nVkResult VmaBlockVector::CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex)\n{\n   VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };\n   allocInfo.memoryTypeIndex = m_MemoryTypeIndex;\n   allocInfo.allocationSize = blockSize;\n   VkDeviceMemory mem = VK_NULL_HANDLE;\n   VkResult res = m_hAllocator->AllocateVulkanMemory(&allocInfo, &mem);\n   if (res < 0) {\n      return res;\n   }\n\n   // New VkDeviceMemory successfully created.\n\n   // Create new Allocation for it.\n   VmaDeviceMemoryBlock* const pBlock = vma_new(m_hAllocator, VmaDeviceMemoryBlock)(m_hAllocator);\n   pBlock->Init(\n      m_hAllocator,\n      m_hParentPool,\n      m_MemoryTypeIndex,\n      mem,\n      allocInfo.allocationSize,\n      m_NextBlockId++,\n      m_Algorithm);\n\n   m_Blocks.push_back(pBlock);\n   if (pNewBlockIndex != VMA_NULL) {\n      *pNewBlockIndex = m_Blocks.size() - 1;\n   }\n\n   return VK_SUCCESS;\n}\n\nvoid VmaBlockVector::ApplyDefragmentationMovesCpu(\n   class VmaBlockVectorDefragmentationContext* pDefragCtx,\n   const VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves)\n{\n   const size_t blockCount = m_Blocks.size();\n   const bool isNonCoherent = m_hAllocator->IsMemoryTypeNonCoherent(m_MemoryTypeIndex);\n\n   enum BLOCK_FLAG\n   {\n      BLOCK_FLAG_USED = 0x00000001,\n      BLOCK_FLAG_MAPPED_FOR_DEFRAGMENTATION = 0x00000002,\n   };\n\n   struct BlockInfo\n   {\n      uint32_t flags;\n      void* pMappedData;\n   };\n   VmaVector< BlockInfo, VmaStlAllocator<BlockInfo> >\n      blockInfo(blockCount, BlockInfo(), VmaStlAllocator<BlockInfo>(m_hAllocator->GetAllocationCallbacks()));\n   memset(blockInfo.data(), 0, blockCount * sizeof(BlockInfo));\n\n   // Go over all moves. Mark blocks that are used with BLOCK_FLAG_USED.\n   const size_t moveCount = moves.size();\n   for (size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) {\n      const VmaDefragmentationMove& move = moves[moveIndex];\n      blockInfo[move.srcBlockIndex].flags |= BLOCK_FLAG_USED;\n      blockInfo[move.dstBlockIndex].flags |= BLOCK_FLAG_USED;\n   }\n\n   VMA_ASSERT(pDefragCtx->res == VK_SUCCESS);\n\n   // Go over all blocks. Get mapped pointer or map if necessary.\n   for (size_t blockIndex = 0; pDefragCtx->res == VK_SUCCESS && blockIndex < blockCount; ++blockIndex) {\n      BlockInfo& currBlockInfo = blockInfo[blockIndex];\n      VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex];\n      if ((currBlockInfo.flags & BLOCK_FLAG_USED) != 0) {\n         currBlockInfo.pMappedData = pBlock->GetMappedData();\n         // It is not originally mapped - map it.\n         if (currBlockInfo.pMappedData == VMA_NULL) {\n            pDefragCtx->res = pBlock->Map(m_hAllocator, 1, &currBlockInfo.pMappedData);\n            if (pDefragCtx->res == VK_SUCCESS) {\n               currBlockInfo.flags |= BLOCK_FLAG_MAPPED_FOR_DEFRAGMENTATION;\n            }\n         }\n      }\n   }\n\n   // Go over all moves. Do actual data transfer.\n   if (pDefragCtx->res == VK_SUCCESS) {\n      const VkDeviceSize nonCoherentAtomSize = m_hAllocator->m_PhysicalDeviceProperties.limits.nonCoherentAtomSize;\n      VkMappedMemoryRange memRange = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE };\n\n      for (size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) {\n         const VmaDefragmentationMove& move = moves[moveIndex];\n\n         const BlockInfo& srcBlockInfo = blockInfo[move.srcBlockIndex];\n         const BlockInfo& dstBlockInfo = blockInfo[move.dstBlockIndex];\n\n         VMA_ASSERT(srcBlockInfo.pMappedData && dstBlockInfo.pMappedData);\n\n         // Invalidate source.\n         if (isNonCoherent) {\n            VmaDeviceMemoryBlock* const pSrcBlock = m_Blocks[move.srcBlockIndex];\n            memRange.memory = pSrcBlock->GetDeviceMemory();\n            memRange.offset = VmaAlignDown(move.srcOffset, nonCoherentAtomSize);\n            memRange.size = VMA_MIN(\n               VmaAlignUp(move.size + (move.srcOffset - memRange.offset), nonCoherentAtomSize),\n               pSrcBlock->m_pMetadata->GetSize() - memRange.offset);\n            (*m_hAllocator->GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hAllocator->m_hDevice, 1, &memRange);\n         }\n\n         // THE PLACE WHERE ACTUAL DATA COPY HAPPENS.\n         memmove(\n            reinterpret_cast<char*>(dstBlockInfo.pMappedData) + move.dstOffset,\n            reinterpret_cast<char*>(srcBlockInfo.pMappedData) + move.srcOffset,\n            static_cast<size_t>(move.size));\n\n         if (IsCorruptionDetectionEnabled()) {\n            VmaWriteMagicValue(dstBlockInfo.pMappedData, move.dstOffset - VMA_DEBUG_MARGIN);\n            VmaWriteMagicValue(dstBlockInfo.pMappedData, move.dstOffset + move.size);\n         }\n\n         // Flush destination.\n         if (isNonCoherent) {\n            VmaDeviceMemoryBlock* const pDstBlock = m_Blocks[move.dstBlockIndex];\n            memRange.memory = pDstBlock->GetDeviceMemory();\n            memRange.offset = VmaAlignDown(move.dstOffset, nonCoherentAtomSize);\n            memRange.size = VMA_MIN(\n               VmaAlignUp(move.size + (move.dstOffset - memRange.offset), nonCoherentAtomSize),\n               pDstBlock->m_pMetadata->GetSize() - memRange.offset);\n            (*m_hAllocator->GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hAllocator->m_hDevice, 1, &memRange);\n         }\n      }\n   }\n\n   // Go over all blocks in reverse order. Unmap those that were mapped just for defragmentation.\n   // Regardless of pCtx->res == VK_SUCCESS.\n   for (size_t blockIndex = blockCount; blockIndex--; ) {\n      const BlockInfo& currBlockInfo = blockInfo[blockIndex];\n      if ((currBlockInfo.flags & BLOCK_FLAG_MAPPED_FOR_DEFRAGMENTATION) != 0) {\n         VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex];\n         pBlock->Unmap(m_hAllocator, 1);\n      }\n   }\n}\n\nvoid VmaBlockVector::ApplyDefragmentationMovesGpu(\n   class VmaBlockVectorDefragmentationContext* pDefragCtx,\n   const VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves,\n   VkCommandBuffer commandBuffer)\n{\n   const size_t blockCount = m_Blocks.size();\n\n   pDefragCtx->blockContexts.resize(blockCount);\n   memset(pDefragCtx->blockContexts.data(), 0, blockCount * sizeof(VmaBlockDefragmentationContext));\n\n   // Go over all moves. Mark blocks that are used with BLOCK_FLAG_USED.\n   const size_t moveCount = moves.size();\n   for (size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) {\n      const VmaDefragmentationMove& move = moves[moveIndex];\n      pDefragCtx->blockContexts[move.srcBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED;\n      pDefragCtx->blockContexts[move.dstBlockIndex].flags |= VmaBlockDefragmentationContext::BLOCK_FLAG_USED;\n   }\n\n   VMA_ASSERT(pDefragCtx->res == VK_SUCCESS);\n\n   // Go over all blocks. Create and bind buffer for whole block if necessary.\n   {\n      VkBufferCreateInfo bufCreateInfo;\n      VmaFillGpuDefragmentationBufferCreateInfo(bufCreateInfo);\n\n      for (size_t blockIndex = 0; pDefragCtx->res == VK_SUCCESS && blockIndex < blockCount; ++blockIndex) {\n         VmaBlockDefragmentationContext& currBlockCtx = pDefragCtx->blockContexts[blockIndex];\n         VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex];\n         if ((currBlockCtx.flags & VmaBlockDefragmentationContext::BLOCK_FLAG_USED) != 0) {\n            bufCreateInfo.size = pBlock->m_pMetadata->GetSize();\n            pDefragCtx->res = (*m_hAllocator->GetVulkanFunctions().vkCreateBuffer)(\n               m_hAllocator->m_hDevice, &bufCreateInfo, m_hAllocator->GetAllocationCallbacks(), &currBlockCtx.hBuffer);\n            if (pDefragCtx->res == VK_SUCCESS) {\n               pDefragCtx->res = (*m_hAllocator->GetVulkanFunctions().vkBindBufferMemory)(\n                  m_hAllocator->m_hDevice, currBlockCtx.hBuffer, pBlock->GetDeviceMemory(), 0);\n            }\n         }\n      }\n   }\n\n   // Go over all moves. Post data transfer commands to command buffer.\n   if (pDefragCtx->res == VK_SUCCESS) {\n      for (size_t moveIndex = 0; moveIndex < moveCount; ++moveIndex) {\n         const VmaDefragmentationMove& move = moves[moveIndex];\n\n         const VmaBlockDefragmentationContext& srcBlockCtx = pDefragCtx->blockContexts[move.srcBlockIndex];\n         const VmaBlockDefragmentationContext& dstBlockCtx = pDefragCtx->blockContexts[move.dstBlockIndex];\n\n         VMA_ASSERT(srcBlockCtx.hBuffer && dstBlockCtx.hBuffer);\n\n         VkBufferCopy region = {\n             move.srcOffset,\n             move.dstOffset,\n             move.size };\n         (*m_hAllocator->GetVulkanFunctions().vkCmdCopyBuffer)(\n            commandBuffer, srcBlockCtx.hBuffer, dstBlockCtx.hBuffer, 1, &region);\n      }\n   }\n\n   // Save buffers to defrag context for later destruction.\n   if (pDefragCtx->res == VK_SUCCESS && moveCount > 0) {\n      pDefragCtx->res = VK_NOT_READY;\n   }\n}\n\nvoid VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats)\n{\n   for (size_t blockIndex = m_Blocks.size(); blockIndex--; ) {\n      VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex];\n      if (pBlock->m_pMetadata->IsEmpty()) {\n         if (m_Blocks.size() > m_MinBlockCount) {\n            if (pDefragmentationStats != VMA_NULL) {\n               ++pDefragmentationStats->deviceMemoryBlocksFreed;\n               pDefragmentationStats->bytesFreed += pBlock->m_pMetadata->GetSize();\n            }\n\n            VmaVectorRemove(m_Blocks, blockIndex);\n            pBlock->Destroy(m_hAllocator);\n            vma_delete(m_hAllocator, pBlock);\n         } else {\n            break;\n         }\n      }\n   }\n   UpdateHasEmptyBlock();\n}\n\nvoid VmaBlockVector::UpdateHasEmptyBlock()\n{\n   m_HasEmptyBlock = false;\n   for (size_t index = 0, count = m_Blocks.size(); index < count; ++index) {\n      VmaDeviceMemoryBlock* const pBlock = m_Blocks[index];\n      if (pBlock->m_pMetadata->IsEmpty()) {\n         m_HasEmptyBlock = true;\n         break;\n      }\n   }\n}\n\n#if VMA_STATS_STRING_ENABLED\n\nvoid VmaBlockVector::PrintDetailedMap(class VmaJsonWriter& json)\n{\n   VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex);\n\n   json.BeginObject();\n\n   if (IsCustomPool()) {\n      const char* poolName = m_hParentPool->GetName();\n      if (poolName != VMA_NULL && poolName[0] != '\\0') {\n         json.WriteString(\"Name\");\n         json.WriteString(poolName);\n      }\n\n      json.WriteString(\"MemoryTypeIndex\");\n      json.WriteNumber(m_MemoryTypeIndex);\n\n      json.WriteString(\"BlockSize\");\n      json.WriteNumber(m_PreferredBlockSize);\n\n      json.WriteString(\"BlockCount\");\n      json.BeginObject(true);\n      if (m_MinBlockCount > 0) {\n         json.WriteString(\"Min\");\n         json.WriteNumber((uint64_t)m_MinBlockCount);\n      }\n      if (m_MaxBlockCount < SIZE_MAX) {\n         json.WriteString(\"Max\");\n         json.WriteNumber((uint64_t)m_MaxBlockCount);\n      }\n      json.WriteString(\"Cur\");\n      json.WriteNumber((uint64_t)m_Blocks.size());\n      json.EndObject();\n\n      if (m_FrameInUseCount > 0) {\n         json.WriteString(\"FrameInUseCount\");\n         json.WriteNumber(m_FrameInUseCount);\n      }\n\n      if (m_Algorithm != 0) {\n         json.WriteString(\"Algorithm\");\n         json.WriteString(VmaAlgorithmToStr(m_Algorithm));\n      }\n   } else {\n      json.WriteString(\"PreferredBlockSize\");\n      json.WriteNumber(m_PreferredBlockSize);\n   }\n\n   json.WriteString(\"Blocks\");\n   json.BeginObject();\n   for (size_t i = 0; i < m_Blocks.size(); ++i) {\n      json.BeginString();\n      json.ContinueString(m_Blocks[i]->GetId());\n      json.EndString();\n\n      m_Blocks[i]->m_pMetadata->PrintDetailedMap(json);\n   }\n   json.EndObject();\n\n   json.EndObject();\n}\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\nvoid VmaBlockVector::Defragment(\n   class VmaBlockVectorDefragmentationContext* pCtx,\n   VmaDefragmentationStats* pStats,\n   VkDeviceSize& maxCpuBytesToMove, uint32_t& maxCpuAllocationsToMove,\n   VkDeviceSize& maxGpuBytesToMove, uint32_t& maxGpuAllocationsToMove,\n   VkCommandBuffer commandBuffer)\n{\n   pCtx->res = VK_SUCCESS;\n\n   const VkMemoryPropertyFlags memPropFlags =\n      m_hAllocator->m_MemProps.memoryTypes[m_MemoryTypeIndex].propertyFlags;\n   const bool isHostVisible = (memPropFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0;\n\n   const bool canDefragmentOnCpu = maxCpuBytesToMove > 0 && maxCpuAllocationsToMove > 0 &&\n      isHostVisible;\n   const bool canDefragmentOnGpu = maxGpuBytesToMove > 0 && maxGpuAllocationsToMove > 0 &&\n      !IsCorruptionDetectionEnabled() &&\n      ((1u << m_MemoryTypeIndex) & m_hAllocator->GetGpuDefragmentationMemoryTypeBits()) != 0;\n\n   // There are options to defragment this memory type.\n   if (canDefragmentOnCpu || canDefragmentOnGpu) {\n      bool defragmentOnGpu;\n      // There is only one option to defragment this memory type.\n      if (canDefragmentOnGpu != canDefragmentOnCpu) {\n         defragmentOnGpu = canDefragmentOnGpu;\n      }\n      // Both options are available: Heuristics to choose the best one.\n      else {\n         defragmentOnGpu = (memPropFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0 ||\n            m_hAllocator->IsIntegratedGpu();\n      }\n\n      bool overlappingMoveSupported = !defragmentOnGpu;\n\n      if (m_hAllocator->m_UseMutex) {\n         m_Mutex.LockWrite();\n         pCtx->mutexLocked = true;\n      }\n\n      pCtx->Begin(overlappingMoveSupported);\n\n      // Defragment.\n\n      const VkDeviceSize maxBytesToMove = defragmentOnGpu ? maxGpuBytesToMove : maxCpuBytesToMove;\n      const uint32_t maxAllocationsToMove = defragmentOnGpu ? maxGpuAllocationsToMove : maxCpuAllocationsToMove;\n      VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> > moves =\n         VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >(VmaStlAllocator<VmaDefragmentationMove>(m_hAllocator->GetAllocationCallbacks()));\n      pCtx->res = pCtx->GetAlgorithm()->Defragment(moves, maxBytesToMove, maxAllocationsToMove);\n\n      // Accumulate statistics.\n      if (pStats != VMA_NULL) {\n         const VkDeviceSize bytesMoved = pCtx->GetAlgorithm()->GetBytesMoved();\n         const uint32_t allocationsMoved = pCtx->GetAlgorithm()->GetAllocationsMoved();\n         pStats->bytesMoved += bytesMoved;\n         pStats->allocationsMoved += allocationsMoved;\n         VMA_ASSERT(bytesMoved <= maxBytesToMove);\n         VMA_ASSERT(allocationsMoved <= maxAllocationsToMove);\n         if (defragmentOnGpu) {\n            maxGpuBytesToMove -= bytesMoved;\n            maxGpuAllocationsToMove -= allocationsMoved;\n         } else {\n            maxCpuBytesToMove -= bytesMoved;\n            maxCpuAllocationsToMove -= allocationsMoved;\n         }\n      }\n\n      if (pCtx->res >= VK_SUCCESS) {\n         if (defragmentOnGpu) {\n            ApplyDefragmentationMovesGpu(pCtx, moves, commandBuffer);\n         } else {\n            ApplyDefragmentationMovesCpu(pCtx, moves);\n         }\n      }\n   }\n}\n\nvoid VmaBlockVector::DefragmentationEnd(\n   class VmaBlockVectorDefragmentationContext* pCtx,\n   VmaDefragmentationStats* pStats)\n{\n   // Destroy buffers.\n   for (size_t blockIndex = pCtx->blockContexts.size(); blockIndex--; ) {\n      VmaBlockDefragmentationContext& blockCtx = pCtx->blockContexts[blockIndex];\n      if (blockCtx.hBuffer) {\n         (*m_hAllocator->GetVulkanFunctions().vkDestroyBuffer)(\n            m_hAllocator->m_hDevice, blockCtx.hBuffer, m_hAllocator->GetAllocationCallbacks());\n      }\n   }\n\n   if (pCtx->res >= VK_SUCCESS) {\n      FreeEmptyBlocks(pStats);\n   }\n\n   if (pCtx->mutexLocked) {\n      VMA_ASSERT(m_hAllocator->m_UseMutex);\n      m_Mutex.UnlockWrite();\n   }\n}\n\nsize_t VmaBlockVector::CalcAllocationCount() const\n{\n   size_t result = 0;\n   for (size_t i = 0; i < m_Blocks.size(); ++i) {\n      result += m_Blocks[i]->m_pMetadata->GetAllocationCount();\n   }\n   return result;\n}\n\nbool VmaBlockVector::IsBufferImageGranularityConflictPossible() const\n{\n   if (m_BufferImageGranularity == 1) {\n      return false;\n   }\n   VmaSuballocationType lastSuballocType = VMA_SUBALLOCATION_TYPE_FREE;\n   for (size_t i = 0, count = m_Blocks.size(); i < count; ++i) {\n      VmaDeviceMemoryBlock* const pBlock = m_Blocks[i];\n      VMA_ASSERT(m_Algorithm == 0);\n      VmaBlockMetadata_Generic* const pMetadata = (VmaBlockMetadata_Generic*)pBlock->m_pMetadata;\n      if (pMetadata->IsBufferImageGranularityConflictPossible(m_BufferImageGranularity, lastSuballocType)) {\n         return true;\n      }\n   }\n   return false;\n}\n\nvoid VmaBlockVector::MakePoolAllocationsLost(\n   uint32_t currentFrameIndex,\n   size_t* pLostAllocationCount)\n{\n   VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex);\n   size_t lostAllocationCount = 0;\n   for (uint32_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) {\n      VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex];\n      VMA_ASSERT(pBlock);\n      lostAllocationCount += pBlock->m_pMetadata->MakeAllocationsLost(currentFrameIndex, m_FrameInUseCount);\n   }\n   if (pLostAllocationCount != VMA_NULL) {\n      *pLostAllocationCount = lostAllocationCount;\n   }\n}\n\nVkResult VmaBlockVector::CheckCorruption()\n{\n   if (!IsCorruptionDetectionEnabled()) {\n      return VK_ERROR_FEATURE_NOT_PRESENT;\n   }\n\n   VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex);\n   for (uint32_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) {\n      VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex];\n      VMA_ASSERT(pBlock);\n      VkResult res = pBlock->CheckCorruption(m_hAllocator);\n      if (res != VK_SUCCESS) {\n         return res;\n      }\n   }\n   return VK_SUCCESS;\n}\n\nvoid VmaBlockVector::AddStats(VmaStats* pStats)\n{\n   const uint32_t memTypeIndex = m_MemoryTypeIndex;\n   const uint32_t memHeapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(memTypeIndex);\n\n   VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex);\n\n   for (uint32_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) {\n      const VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex];\n      VMA_ASSERT(pBlock);\n      VMA_HEAVY_ASSERT(pBlock->Validate());\n      VmaStatInfo allocationStatInfo;\n      pBlock->m_pMetadata->CalcAllocationStatInfo(allocationStatInfo);\n      VmaAddStatInfo(pStats->total, allocationStatInfo);\n      VmaAddStatInfo(pStats->memoryType[memTypeIndex], allocationStatInfo);\n      VmaAddStatInfo(pStats->memoryHeap[memHeapIndex], allocationStatInfo);\n   }\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// VmaDefragmentationAlgorithm_Generic members definition\n\nVmaDefragmentationAlgorithm_Generic::VmaDefragmentationAlgorithm_Generic(\n   VmaAllocator hAllocator,\n   VmaBlockVector* pBlockVector,\n   uint32_t currentFrameIndex,\n   bool overlappingMoveSupported) :\n   VmaDefragmentationAlgorithm(hAllocator, pBlockVector, currentFrameIndex),\n   m_AllocationCount(0),\n   m_AllAllocations(false),\n   m_BytesMoved(0),\n   m_AllocationsMoved(0),\n   m_Blocks(VmaStlAllocator<BlockInfo*>(hAllocator->GetAllocationCallbacks()))\n{\n   // Create block info for each block.\n   const size_t blockCount = m_pBlockVector->m_Blocks.size();\n   for (size_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) {\n      BlockInfo* pBlockInfo = vma_new(m_hAllocator, BlockInfo)(m_hAllocator->GetAllocationCallbacks());\n      pBlockInfo->m_OriginalBlockIndex = blockIndex;\n      pBlockInfo->m_pBlock = m_pBlockVector->m_Blocks[blockIndex];\n      m_Blocks.push_back(pBlockInfo);\n   }\n\n   // Sort them by m_pBlock pointer value.\n   VMA_SORT(m_Blocks.begin(), m_Blocks.end(), BlockPointerLess());\n}\n\nVmaDefragmentationAlgorithm_Generic::~VmaDefragmentationAlgorithm_Generic()\n{\n   for (size_t i = m_Blocks.size(); i--; ) {\n      vma_delete(m_hAllocator, m_Blocks[i]);\n   }\n}\n\nvoid VmaDefragmentationAlgorithm_Generic::AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged)\n{\n   // Now as we are inside VmaBlockVector::m_Mutex, we can make final check if this allocation was not lost.\n   if (hAlloc->GetLastUseFrameIndex() != VMA_FRAME_INDEX_LOST) {\n      VmaDeviceMemoryBlock* pBlock = hAlloc->GetBlock();\n      BlockInfoVector::iterator it = VmaBinaryFindFirstNotLess(m_Blocks.begin(), m_Blocks.end(), pBlock, BlockPointerLess());\n      if (it != m_Blocks.end() && (*it)->m_pBlock == pBlock) {\n         AllocationInfo allocInfo = AllocationInfo(hAlloc, pChanged);\n         (*it)->m_Allocations.push_back(allocInfo);\n      } else {\n         VMA_ASSERT(0);\n      }\n\n      ++m_AllocationCount;\n   }\n}\n\nVkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound(\n   VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves,\n   VkDeviceSize maxBytesToMove,\n   uint32_t maxAllocationsToMove)\n{\n   if (m_Blocks.empty()) {\n      return VK_SUCCESS;\n   }\n\n   // This is a choice based on research.\n   // Option 1:\n   uint32_t strategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT;\n   // Option 2:\n   //uint32_t strategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT;\n   // Option 3:\n   //uint32_t strategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT;\n\n   size_t srcBlockMinIndex = 0;\n   // When FAST_ALGORITHM, move allocations from only last out of blocks that contain non-movable allocations.\n   /*\n   if(m_AlgorithmFlags & VMA_DEFRAGMENTATION_FAST_ALGORITHM_BIT)\n   {\n       const size_t blocksWithNonMovableCount = CalcBlocksWithNonMovableCount();\n       if(blocksWithNonMovableCount > 0)\n       {\n           srcBlockMinIndex = blocksWithNonMovableCount - 1;\n       }\n   }\n   */\n\n   size_t srcBlockIndex = m_Blocks.size() - 1;\n   size_t srcAllocIndex = SIZE_MAX;\n   for (;;) {\n      // 1. Find next allocation to move.\n      // 1.1. Start from last to first m_Blocks - they are sorted from most \"destination\" to most \"source\".\n      // 1.2. Then start from last to first m_Allocations.\n      while (srcAllocIndex >= m_Blocks[srcBlockIndex]->m_Allocations.size()) {\n         if (m_Blocks[srcBlockIndex]->m_Allocations.empty()) {\n            // Finished: no more allocations to process.\n            if (srcBlockIndex == srcBlockMinIndex) {\n               return VK_SUCCESS;\n            } else {\n               --srcBlockIndex;\n               srcAllocIndex = SIZE_MAX;\n            }\n         } else {\n            srcAllocIndex = m_Blocks[srcBlockIndex]->m_Allocations.size() - 1;\n         }\n      }\n\n      BlockInfo* pSrcBlockInfo = m_Blocks[srcBlockIndex];\n      AllocationInfo& allocInfo = pSrcBlockInfo->m_Allocations[srcAllocIndex];\n\n      const VkDeviceSize size = allocInfo.m_hAllocation->GetSize();\n      const VkDeviceSize srcOffset = allocInfo.m_hAllocation->GetOffset();\n      const VkDeviceSize alignment = allocInfo.m_hAllocation->GetAlignment();\n      const VmaSuballocationType suballocType = allocInfo.m_hAllocation->GetSuballocationType();\n\n      // 2. Try to find new place for this allocation in preceding or current block.\n      for (size_t dstBlockIndex = 0; dstBlockIndex <= srcBlockIndex; ++dstBlockIndex) {\n         BlockInfo* pDstBlockInfo = m_Blocks[dstBlockIndex];\n         VmaAllocationRequest dstAllocRequest;\n         if (pDstBlockInfo->m_pBlock->m_pMetadata->CreateAllocationRequest(\n            m_CurrentFrameIndex,\n            m_pBlockVector->GetFrameInUseCount(),\n            m_pBlockVector->GetBufferImageGranularity(),\n            size,\n            alignment,\n            false, // upperAddress\n            suballocType,\n            false, // canMakeOtherLost\n            strategy,\n            &dstAllocRequest) &&\n            MoveMakesSense(\n               dstBlockIndex, dstAllocRequest.offset, srcBlockIndex, srcOffset)) {\n            VMA_ASSERT(dstAllocRequest.itemsToMakeLostCount == 0);\n\n            // Reached limit on number of allocations or bytes to move.\n            if ((m_AllocationsMoved + 1 > maxAllocationsToMove) ||\n               (m_BytesMoved + size > maxBytesToMove)) {\n               return VK_SUCCESS;\n            }\n\n            VmaDefragmentationMove move;\n            move.srcBlockIndex = pSrcBlockInfo->m_OriginalBlockIndex;\n            move.dstBlockIndex = pDstBlockInfo->m_OriginalBlockIndex;\n            move.srcOffset = srcOffset;\n            move.dstOffset = dstAllocRequest.offset;\n            move.size = size;\n            moves.push_back(move);\n\n            pDstBlockInfo->m_pBlock->m_pMetadata->Alloc(\n               dstAllocRequest,\n               suballocType,\n               size,\n               allocInfo.m_hAllocation);\n            pSrcBlockInfo->m_pBlock->m_pMetadata->FreeAtOffset(srcOffset);\n\n            allocInfo.m_hAllocation->ChangeBlockAllocation(m_hAllocator, pDstBlockInfo->m_pBlock, dstAllocRequest.offset);\n\n            if (allocInfo.m_pChanged != VMA_NULL) {\n               *allocInfo.m_pChanged = VK_TRUE;\n            }\n\n            ++m_AllocationsMoved;\n            m_BytesMoved += size;\n\n            VmaVectorRemove(pSrcBlockInfo->m_Allocations, srcAllocIndex);\n\n            break;\n         }\n      }\n\n      // If not processed, this allocInfo remains in pBlockInfo->m_Allocations for next round.\n\n      if (srcAllocIndex > 0) {\n         --srcAllocIndex;\n      } else {\n         if (srcBlockIndex > 0) {\n            --srcBlockIndex;\n            srcAllocIndex = SIZE_MAX;\n         } else {\n            return VK_SUCCESS;\n         }\n      }\n   }\n}\n\nsize_t VmaDefragmentationAlgorithm_Generic::CalcBlocksWithNonMovableCount() const\n{\n   size_t result = 0;\n   for (size_t i = 0; i < m_Blocks.size(); ++i) {\n      if (m_Blocks[i]->m_HasNonMovableAllocations) {\n         ++result;\n      }\n   }\n   return result;\n}\n\nVkResult VmaDefragmentationAlgorithm_Generic::Defragment(\n   VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves,\n   VkDeviceSize maxBytesToMove,\n   uint32_t maxAllocationsToMove)\n{\n   if (!m_AllAllocations && m_AllocationCount == 0) {\n      return VK_SUCCESS;\n   }\n\n   const size_t blockCount = m_Blocks.size();\n   for (size_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) {\n      BlockInfo* pBlockInfo = m_Blocks[blockIndex];\n\n      if (m_AllAllocations) {\n         VmaBlockMetadata_Generic* pMetadata = (VmaBlockMetadata_Generic*)pBlockInfo->m_pBlock->m_pMetadata;\n         for (VmaSuballocationList::const_iterator it = pMetadata->m_Suballocations.begin();\n              it != pMetadata->m_Suballocations.end();\n              ++it) {\n            if (it->type != VMA_SUBALLOCATION_TYPE_FREE) {\n               AllocationInfo allocInfo = AllocationInfo(it->hAllocation, VMA_NULL);\n               pBlockInfo->m_Allocations.push_back(allocInfo);\n            }\n         }\n      }\n\n      pBlockInfo->CalcHasNonMovableAllocations();\n\n      // This is a choice based on research.\n      // Option 1:\n      pBlockInfo->SortAllocationsByOffsetDescending();\n      // Option 2:\n      //pBlockInfo->SortAllocationsBySizeDescending();\n   }\n\n   // Sort m_Blocks this time by the main criterium, from most \"destination\" to most \"source\" blocks.\n   VMA_SORT(m_Blocks.begin(), m_Blocks.end(), BlockInfoCompareMoveDestination());\n\n   // This is a choice based on research.\n   const uint32_t roundCount = 2;\n\n   // Execute defragmentation rounds (the main part).\n   VkResult result = VK_SUCCESS;\n   for (uint32_t round = 0; (round < roundCount) && (result == VK_SUCCESS); ++round) {\n      result = DefragmentRound(moves, maxBytesToMove, maxAllocationsToMove);\n   }\n\n   return result;\n}\n\nbool VmaDefragmentationAlgorithm_Generic::MoveMakesSense(\n   size_t dstBlockIndex, VkDeviceSize dstOffset,\n   size_t srcBlockIndex, VkDeviceSize srcOffset)\n{\n   if (dstBlockIndex < srcBlockIndex) {\n      return true;\n   }\n   if (dstBlockIndex > srcBlockIndex) {\n      return false;\n   }\n   if (dstOffset < srcOffset) {\n      return true;\n   }\n   return false;\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// VmaDefragmentationAlgorithm_Fast\n\nVmaDefragmentationAlgorithm_Fast::VmaDefragmentationAlgorithm_Fast(\n   VmaAllocator hAllocator,\n   VmaBlockVector* pBlockVector,\n   uint32_t currentFrameIndex,\n   bool overlappingMoveSupported) :\n   VmaDefragmentationAlgorithm(hAllocator, pBlockVector, currentFrameIndex),\n   m_OverlappingMoveSupported(overlappingMoveSupported),\n   m_AllocationCount(0),\n   m_AllAllocations(false),\n   m_BytesMoved(0),\n   m_AllocationsMoved(0),\n   m_BlockInfos(VmaStlAllocator<BlockInfo>(hAllocator->GetAllocationCallbacks()))\n{\n   VMA_ASSERT(VMA_DEBUG_MARGIN == 0);\n\n}\n\nVmaDefragmentationAlgorithm_Fast::~VmaDefragmentationAlgorithm_Fast()\n{\n}\n\nVkResult VmaDefragmentationAlgorithm_Fast::Defragment(\n   VmaVector< VmaDefragmentationMove, VmaStlAllocator<VmaDefragmentationMove> >& moves,\n   VkDeviceSize maxBytesToMove,\n   uint32_t maxAllocationsToMove)\n{\n   VMA_ASSERT(m_AllAllocations || m_pBlockVector->CalcAllocationCount() == m_AllocationCount);\n\n   const size_t blockCount = m_pBlockVector->GetBlockCount();\n   if (blockCount == 0 || maxBytesToMove == 0 || maxAllocationsToMove == 0) {\n      return VK_SUCCESS;\n   }\n\n   PreprocessMetadata();\n\n   // Sort blocks in order from most destination.\n\n   m_BlockInfos.resize(blockCount);\n   for (size_t i = 0; i < blockCount; ++i) {\n      m_BlockInfos[i].origBlockIndex = i;\n   }\n\n   VMA_SORT(m_BlockInfos.begin(), m_BlockInfos.end(), [this](const BlockInfo& lhs, const BlockInfo& rhs) -> bool\n            {\n               return m_pBlockVector->GetBlock(lhs.origBlockIndex)->m_pMetadata->GetSumFreeSize() <\n                  m_pBlockVector->GetBlock(rhs.origBlockIndex)->m_pMetadata->GetSumFreeSize();\n            });\n\n   // THE MAIN ALGORITHM\n\n   FreeSpaceDatabase freeSpaceDb;\n\n   size_t dstBlockInfoIndex = 0;\n   size_t dstOrigBlockIndex = m_BlockInfos[dstBlockInfoIndex].origBlockIndex;\n   VmaDeviceMemoryBlock* pDstBlock = m_pBlockVector->GetBlock(dstOrigBlockIndex);\n   VmaBlockMetadata_Generic* pDstMetadata = (VmaBlockMetadata_Generic*)pDstBlock->m_pMetadata;\n   VkDeviceSize dstBlockSize = pDstMetadata->GetSize();\n   VkDeviceSize dstOffset = 0;\n\n   bool end = false;\n   for (size_t srcBlockInfoIndex = 0; !end && srcBlockInfoIndex < blockCount; ++srcBlockInfoIndex) {\n      const size_t srcOrigBlockIndex = m_BlockInfos[srcBlockInfoIndex].origBlockIndex;\n      VmaDeviceMemoryBlock* const pSrcBlock = m_pBlockVector->GetBlock(srcOrigBlockIndex);\n      VmaBlockMetadata_Generic* const pSrcMetadata = (VmaBlockMetadata_Generic*)pSrcBlock->m_pMetadata;\n      for (VmaSuballocationList::iterator srcSuballocIt = pSrcMetadata->m_Suballocations.begin();\n           !end && srcSuballocIt != pSrcMetadata->m_Suballocations.end(); ) {\n         VmaAllocation_T* const pAlloc = srcSuballocIt->hAllocation;\n         const VkDeviceSize srcAllocAlignment = pAlloc->GetAlignment();\n         const VkDeviceSize srcAllocSize = srcSuballocIt->size;\n         if (m_AllocationsMoved == maxAllocationsToMove ||\n             m_BytesMoved + srcAllocSize > maxBytesToMove) {\n            end = true;\n            break;\n         }\n         const VkDeviceSize srcAllocOffset = srcSuballocIt->offset;\n\n         // Try to place it in one of free spaces from the database.\n         size_t freeSpaceInfoIndex;\n         VkDeviceSize dstAllocOffset;\n         if (freeSpaceDb.Fetch(srcAllocAlignment, srcAllocSize,\n                               freeSpaceInfoIndex, dstAllocOffset)) {\n            size_t freeSpaceOrigBlockIndex = m_BlockInfos[freeSpaceInfoIndex].origBlockIndex;\n            VmaDeviceMemoryBlock* pFreeSpaceBlock = m_pBlockVector->GetBlock(freeSpaceOrigBlockIndex);\n            VmaBlockMetadata_Generic* pFreeSpaceMetadata = (VmaBlockMetadata_Generic*)pFreeSpaceBlock->m_pMetadata;\n\n            // Same block\n            if (freeSpaceInfoIndex == srcBlockInfoIndex) {\n               VMA_ASSERT(dstAllocOffset <= srcAllocOffset);\n\n               // MOVE OPTION 1: Move the allocation inside the same block by decreasing offset.\n\n               VmaSuballocation suballoc = *srcSuballocIt;\n               suballoc.offset = dstAllocOffset;\n               suballoc.hAllocation->ChangeOffset(dstAllocOffset);\n               m_BytesMoved += srcAllocSize;\n               ++m_AllocationsMoved;\n\n               VmaSuballocationList::iterator nextSuballocIt = srcSuballocIt;\n               ++nextSuballocIt;\n               pSrcMetadata->m_Suballocations.erase(srcSuballocIt);\n               srcSuballocIt = nextSuballocIt;\n\n               InsertSuballoc(pFreeSpaceMetadata, suballoc);\n\n               VmaDefragmentationMove move = {\n                   srcOrigBlockIndex, freeSpaceOrigBlockIndex,\n                   srcAllocOffset, dstAllocOffset,\n                   srcAllocSize };\n               moves.push_back(move);\n            }\n            // Different block\n            else {\n               // MOVE OPTION 2: Move the allocation to a different block.\n\n               VMA_ASSERT(freeSpaceInfoIndex < srcBlockInfoIndex);\n\n               VmaSuballocation suballoc = *srcSuballocIt;\n               suballoc.offset = dstAllocOffset;\n               suballoc.hAllocation->ChangeBlockAllocation(m_hAllocator, pFreeSpaceBlock, dstAllocOffset);\n               m_BytesMoved += srcAllocSize;\n               ++m_AllocationsMoved;\n\n               VmaSuballocationList::iterator nextSuballocIt = srcSuballocIt;\n               ++nextSuballocIt;\n               pSrcMetadata->m_Suballocations.erase(srcSuballocIt);\n               srcSuballocIt = nextSuballocIt;\n\n               InsertSuballoc(pFreeSpaceMetadata, suballoc);\n\n               VmaDefragmentationMove move = {\n                   srcOrigBlockIndex, freeSpaceOrigBlockIndex,\n                   srcAllocOffset, dstAllocOffset,\n                   srcAllocSize };\n               moves.push_back(move);\n            }\n         } else {\n            dstAllocOffset = VmaAlignUp(dstOffset, srcAllocAlignment);\n\n            // If the allocation doesn't fit before the end of dstBlock, forward to next block.\n            while (dstBlockInfoIndex < srcBlockInfoIndex &&\n                   dstAllocOffset + srcAllocSize > dstBlockSize) {\n               // But before that, register remaining free space at the end of dst block.\n               freeSpaceDb.Register(dstBlockInfoIndex, dstOffset, dstBlockSize - dstOffset);\n\n               ++dstBlockInfoIndex;\n               dstOrigBlockIndex = m_BlockInfos[dstBlockInfoIndex].origBlockIndex;\n               pDstBlock = m_pBlockVector->GetBlock(dstOrigBlockIndex);\n               pDstMetadata = (VmaBlockMetadata_Generic*)pDstBlock->m_pMetadata;\n               dstBlockSize = pDstMetadata->GetSize();\n               dstOffset = 0;\n               dstAllocOffset = 0;\n            }\n\n            // Same block\n            if (dstBlockInfoIndex == srcBlockInfoIndex) {\n               VMA_ASSERT(dstAllocOffset <= srcAllocOffset);\n\n               const bool overlap = dstAllocOffset + srcAllocSize > srcAllocOffset;\n\n               bool skipOver = overlap;\n               if (overlap && m_OverlappingMoveSupported && dstAllocOffset < srcAllocOffset) {\n                  // If destination and source place overlap, skip if it would move it\n                  // by only < 1/64 of its size.\n                  skipOver = (srcAllocOffset - dstAllocOffset) * 64 < srcAllocSize;\n               }\n\n               if (skipOver) {\n                  freeSpaceDb.Register(dstBlockInfoIndex, dstOffset, srcAllocOffset - dstOffset);\n\n                  dstOffset = srcAllocOffset + srcAllocSize;\n                  ++srcSuballocIt;\n               }\n               // MOVE OPTION 1: Move the allocation inside the same block by decreasing offset.\n               else {\n                  srcSuballocIt->offset = dstAllocOffset;\n                  srcSuballocIt->hAllocation->ChangeOffset(dstAllocOffset);\n                  dstOffset = dstAllocOffset + srcAllocSize;\n                  m_BytesMoved += srcAllocSize;\n                  ++m_AllocationsMoved;\n                  ++srcSuballocIt;\n                  VmaDefragmentationMove move = {\n                      srcOrigBlockIndex, dstOrigBlockIndex,\n                      srcAllocOffset, dstAllocOffset,\n                      srcAllocSize };\n                  moves.push_back(move);\n               }\n            }\n            // Different block\n            else {\n               // MOVE OPTION 2: Move the allocation to a different block.\n\n               VMA_ASSERT(dstBlockInfoIndex < srcBlockInfoIndex);\n               VMA_ASSERT(dstAllocOffset + srcAllocSize <= dstBlockSize);\n\n               VmaSuballocation suballoc = *srcSuballocIt;\n               suballoc.offset = dstAllocOffset;\n               suballoc.hAllocation->ChangeBlockAllocation(m_hAllocator, pDstBlock, dstAllocOffset);\n               dstOffset = dstAllocOffset + srcAllocSize;\n               m_BytesMoved += srcAllocSize;\n               ++m_AllocationsMoved;\n\n               VmaSuballocationList::iterator nextSuballocIt = srcSuballocIt;\n               ++nextSuballocIt;\n               pSrcMetadata->m_Suballocations.erase(srcSuballocIt);\n               srcSuballocIt = nextSuballocIt;\n\n               pDstMetadata->m_Suballocations.push_back(suballoc);\n\n               VmaDefragmentationMove move = {\n                   srcOrigBlockIndex, dstOrigBlockIndex,\n                   srcAllocOffset, dstAllocOffset,\n                   srcAllocSize };\n               moves.push_back(move);\n            }\n         }\n      }\n   }\n\n   m_BlockInfos.clear();\n\n   PostprocessMetadata();\n\n   return VK_SUCCESS;\n}\n\nvoid VmaDefragmentationAlgorithm_Fast::PreprocessMetadata()\n{\n   const size_t blockCount = m_pBlockVector->GetBlockCount();\n   for (size_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) {\n      VmaBlockMetadata_Generic* const pMetadata =\n         (VmaBlockMetadata_Generic*)m_pBlockVector->GetBlock(blockIndex)->m_pMetadata;\n      pMetadata->m_FreeCount = 0;\n      pMetadata->m_SumFreeSize = pMetadata->GetSize();\n      pMetadata->m_FreeSuballocationsBySize.clear();\n      for (VmaSuballocationList::iterator it = pMetadata->m_Suballocations.begin();\n           it != pMetadata->m_Suballocations.end(); ) {\n         if (it->type == VMA_SUBALLOCATION_TYPE_FREE) {\n            VmaSuballocationList::iterator nextIt = it;\n            ++nextIt;\n            pMetadata->m_Suballocations.erase(it);\n            it = nextIt;\n         } else {\n            ++it;\n         }\n      }\n   }\n}\n\nvoid VmaDefragmentationAlgorithm_Fast::PostprocessMetadata()\n{\n   const size_t blockCount = m_pBlockVector->GetBlockCount();\n   for (size_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) {\n      VmaBlockMetadata_Generic* const pMetadata =\n         (VmaBlockMetadata_Generic*)m_pBlockVector->GetBlock(blockIndex)->m_pMetadata;\n      const VkDeviceSize blockSize = pMetadata->GetSize();\n\n      // No allocations in this block - entire area is free.\n      if (pMetadata->m_Suballocations.empty()) {\n         pMetadata->m_FreeCount = 1;\n         //pMetadata->m_SumFreeSize is already set to blockSize.\n         VmaSuballocation suballoc = {\n             0, // offset\n             blockSize, // size\n             VMA_NULL, // hAllocation\n             VMA_SUBALLOCATION_TYPE_FREE };\n         pMetadata->m_Suballocations.push_back(suballoc);\n         pMetadata->RegisterFreeSuballocation(pMetadata->m_Suballocations.begin());\n      }\n      // There are some allocations in this block.\n      else {\n         VkDeviceSize offset = 0;\n         VmaSuballocationList::iterator it;\n         for (it = pMetadata->m_Suballocations.begin();\n              it != pMetadata->m_Suballocations.end();\n              ++it) {\n            VMA_ASSERT(it->type != VMA_SUBALLOCATION_TYPE_FREE);\n            VMA_ASSERT(it->offset >= offset);\n\n            // Need to insert preceding free space.\n            if (it->offset > offset) {\n               ++pMetadata->m_FreeCount;\n               const VkDeviceSize freeSize = it->offset - offset;\n               VmaSuballocation suballoc = {\n                   offset, // offset\n                   freeSize, // size\n                   VMA_NULL, // hAllocation\n                   VMA_SUBALLOCATION_TYPE_FREE };\n               VmaSuballocationList::iterator precedingFreeIt = pMetadata->m_Suballocations.insert(it, suballoc);\n               if (freeSize >= VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) {\n                  pMetadata->m_FreeSuballocationsBySize.push_back(precedingFreeIt);\n               }\n            }\n\n            pMetadata->m_SumFreeSize -= it->size;\n            offset = it->offset + it->size;\n         }\n\n         // Need to insert trailing free space.\n         if (offset < blockSize) {\n            ++pMetadata->m_FreeCount;\n            const VkDeviceSize freeSize = blockSize - offset;\n            VmaSuballocation suballoc = {\n                offset, // offset\n                freeSize, // size\n                VMA_NULL, // hAllocation\n                VMA_SUBALLOCATION_TYPE_FREE };\n            VMA_ASSERT(it == pMetadata->m_Suballocations.end());\n            VmaSuballocationList::iterator trailingFreeIt = pMetadata->m_Suballocations.insert(it, suballoc);\n            if (freeSize > VMA_MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) {\n               pMetadata->m_FreeSuballocationsBySize.push_back(trailingFreeIt);\n            }\n         }\n\n         VMA_SORT(\n            pMetadata->m_FreeSuballocationsBySize.begin(),\n            pMetadata->m_FreeSuballocationsBySize.end(),\n            VmaSuballocationItemSizeLess());\n      }\n\n      VMA_HEAVY_ASSERT(pMetadata->Validate());\n   }\n}\n\nvoid VmaDefragmentationAlgorithm_Fast::InsertSuballoc(VmaBlockMetadata_Generic* pMetadata, const VmaSuballocation& suballoc)\n{\n   // TODO: Optimize somehow. Remember iterator instead of searching for it linearly.\n   VmaSuballocationList::iterator it = pMetadata->m_Suballocations.begin();\n   while (it != pMetadata->m_Suballocations.end()) {\n      if (it->offset < suballoc.offset) {\n         ++it;\n      }\n   }\n   pMetadata->m_Suballocations.insert(it, suballoc);\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// VmaBlockVectorDefragmentationContext\n\nVmaBlockVectorDefragmentationContext::VmaBlockVectorDefragmentationContext(\n   VmaAllocator hAllocator,\n   VmaPool hCustomPool,\n   VmaBlockVector* pBlockVector,\n   uint32_t currFrameIndex) :\n   res(VK_SUCCESS),\n   mutexLocked(false),\n   blockContexts(VmaStlAllocator<VmaBlockDefragmentationContext>(hAllocator->GetAllocationCallbacks())),\n   m_hAllocator(hAllocator),\n   m_hCustomPool(hCustomPool),\n   m_pBlockVector(pBlockVector),\n   m_CurrFrameIndex(currFrameIndex),\n   m_pAlgorithm(VMA_NULL),\n   m_Allocations(VmaStlAllocator<AllocInfo>(hAllocator->GetAllocationCallbacks())),\n   m_AllAllocations(false)\n{\n}\n\nVmaBlockVectorDefragmentationContext::~VmaBlockVectorDefragmentationContext()\n{\n   vma_delete(m_hAllocator, m_pAlgorithm);\n}\n\nvoid VmaBlockVectorDefragmentationContext::AddAllocation(VmaAllocation hAlloc, VkBool32* pChanged)\n{\n   AllocInfo info = { hAlloc, pChanged };\n   m_Allocations.push_back(info);\n}\n\nvoid VmaBlockVectorDefragmentationContext::Begin(bool overlappingMoveSupported)\n{\n   const bool allAllocations = m_AllAllocations ||\n      m_Allocations.size() == m_pBlockVector->CalcAllocationCount();\n\n   /********************************\n   HERE IS THE CHOICE OF DEFRAGMENTATION ALGORITHM.\n   ********************************/\n\n   /*\n   Fast algorithm is supported only when certain criteria are met:\n   - VMA_DEBUG_MARGIN is 0.\n   - All allocations in this block vector are moveable.\n   - There is no possibility of image/buffer granularity conflict.\n   */\n   if (VMA_DEBUG_MARGIN == 0 &&\n       allAllocations &&\n       !m_pBlockVector->IsBufferImageGranularityConflictPossible()) {\n      m_pAlgorithm = vma_new(m_hAllocator, VmaDefragmentationAlgorithm_Fast)(\n         m_hAllocator, m_pBlockVector, m_CurrFrameIndex, overlappingMoveSupported);\n   } else {\n      m_pAlgorithm = vma_new(m_hAllocator, VmaDefragmentationAlgorithm_Generic)(\n         m_hAllocator, m_pBlockVector, m_CurrFrameIndex, overlappingMoveSupported);\n   }\n\n   if (allAllocations) {\n      m_pAlgorithm->AddAll();\n   } else {\n      for (size_t i = 0, count = m_Allocations.size(); i < count; ++i) {\n         m_pAlgorithm->AddAllocation(m_Allocations[i].hAlloc, m_Allocations[i].pChanged);\n      }\n   }\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// VmaDefragmentationContext\n\nVmaDefragmentationContext_T::VmaDefragmentationContext_T(\n   VmaAllocator hAllocator,\n   uint32_t currFrameIndex,\n   uint32_t flags,\n   VmaDefragmentationStats* pStats) :\n   m_hAllocator(hAllocator),\n   m_CurrFrameIndex(currFrameIndex),\n   m_Flags(flags),\n   m_pStats(pStats),\n   m_CustomPoolContexts(VmaStlAllocator<VmaBlockVectorDefragmentationContext*>(hAllocator->GetAllocationCallbacks()))\n{\n   memset(m_DefaultPoolContexts, 0, sizeof(m_DefaultPoolContexts));\n}\n\nVmaDefragmentationContext_T::~VmaDefragmentationContext_T()\n{\n   for (size_t i = m_CustomPoolContexts.size(); i--; ) {\n      VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_CustomPoolContexts[i];\n      pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_pStats);\n      vma_delete(m_hAllocator, pBlockVectorCtx);\n   }\n   for (size_t i = m_hAllocator->m_MemProps.memoryTypeCount; i--; ) {\n      VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_DefaultPoolContexts[i];\n      if (pBlockVectorCtx) {\n         pBlockVectorCtx->GetBlockVector()->DefragmentationEnd(pBlockVectorCtx, m_pStats);\n         vma_delete(m_hAllocator, pBlockVectorCtx);\n      }\n   }\n}\n\nvoid VmaDefragmentationContext_T::AddPools(uint32_t poolCount, VmaPool* pPools)\n{\n   for (uint32_t poolIndex = 0; poolIndex < poolCount; ++poolIndex) {\n      VmaPool pool = pPools[poolIndex];\n      VMA_ASSERT(pool);\n      // Pools with algorithm other than default are not defragmented.\n      if (pool->m_BlockVector.GetAlgorithm() == 0) {\n         VmaBlockVectorDefragmentationContext* pBlockVectorDefragCtx = VMA_NULL;\n\n         for (size_t i = m_CustomPoolContexts.size(); i--; ) {\n            if (m_CustomPoolContexts[i]->GetCustomPool() == pool) {\n               pBlockVectorDefragCtx = m_CustomPoolContexts[i];\n               break;\n            }\n         }\n\n         if (!pBlockVectorDefragCtx) {\n            pBlockVectorDefragCtx = vma_new(m_hAllocator, VmaBlockVectorDefragmentationContext)(\n               m_hAllocator,\n               pool,\n               &pool->m_BlockVector,\n               m_CurrFrameIndex);\n            m_CustomPoolContexts.push_back(pBlockVectorDefragCtx);\n         }\n\n         pBlockVectorDefragCtx->AddAll();\n      }\n   }\n}\n\nvoid VmaDefragmentationContext_T::AddAllocations(\n   uint32_t allocationCount,\n   VmaAllocation* pAllocations,\n   VkBool32* pAllocationsChanged)\n{\n   // Dispatch pAllocations among defragmentators. Create them when necessary.\n   for (uint32_t allocIndex = 0; allocIndex < allocationCount; ++allocIndex) {\n      const VmaAllocation hAlloc = pAllocations[allocIndex];\n      VMA_ASSERT(hAlloc);\n      // DedicatedAlloc cannot be defragmented.\n      if ((hAlloc->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK) &&\n          // Lost allocation cannot be defragmented.\n         (hAlloc->GetLastUseFrameIndex() != VMA_FRAME_INDEX_LOST)) {\n         VmaBlockVectorDefragmentationContext* pBlockVectorDefragCtx = VMA_NULL;\n\n         const VmaPool hAllocPool = hAlloc->GetBlock()->GetParentPool();\n         // This allocation belongs to custom pool.\n         if (hAllocPool != VK_NULL_HANDLE) {\n            // Pools with algorithm other than default are not defragmented.\n            if (hAllocPool->m_BlockVector.GetAlgorithm() == 0) {\n               for (size_t i = m_CustomPoolContexts.size(); i--; ) {\n                  if (m_CustomPoolContexts[i]->GetCustomPool() == hAllocPool) {\n                     pBlockVectorDefragCtx = m_CustomPoolContexts[i];\n                     break;\n                  }\n               }\n               if (!pBlockVectorDefragCtx) {\n                  pBlockVectorDefragCtx = vma_new(m_hAllocator, VmaBlockVectorDefragmentationContext)(\n                     m_hAllocator,\n                     hAllocPool,\n                     &hAllocPool->m_BlockVector,\n                     m_CurrFrameIndex);\n                  m_CustomPoolContexts.push_back(pBlockVectorDefragCtx);\n               }\n            }\n         }\n         // This allocation belongs to default pool.\n         else {\n            const uint32_t memTypeIndex = hAlloc->GetMemoryTypeIndex();\n            pBlockVectorDefragCtx = m_DefaultPoolContexts[memTypeIndex];\n            if (!pBlockVectorDefragCtx) {\n               pBlockVectorDefragCtx = vma_new(m_hAllocator, VmaBlockVectorDefragmentationContext)(\n                  m_hAllocator,\n                  VMA_NULL, // hCustomPool\n                  m_hAllocator->m_pBlockVectors[memTypeIndex],\n                  m_CurrFrameIndex);\n               m_DefaultPoolContexts[memTypeIndex] = pBlockVectorDefragCtx;\n            }\n         }\n\n         if (pBlockVectorDefragCtx) {\n            VkBool32* const pChanged = (pAllocationsChanged != VMA_NULL) ?\n               &pAllocationsChanged[allocIndex] : VMA_NULL;\n            pBlockVectorDefragCtx->AddAllocation(hAlloc, pChanged);\n         }\n      }\n   }\n}\n\nVkResult VmaDefragmentationContext_T::Defragment(\n   VkDeviceSize maxCpuBytesToMove, uint32_t maxCpuAllocationsToMove,\n   VkDeviceSize maxGpuBytesToMove, uint32_t maxGpuAllocationsToMove,\n   VkCommandBuffer commandBuffer, VmaDefragmentationStats* pStats)\n{\n   if (pStats) {\n      memset(pStats, 0, sizeof(VmaDefragmentationStats));\n   }\n\n   if (commandBuffer == VK_NULL_HANDLE) {\n      maxGpuBytesToMove = 0;\n      maxGpuAllocationsToMove = 0;\n   }\n\n   VkResult res = VK_SUCCESS;\n\n   // Process default pools.\n   for (uint32_t memTypeIndex = 0;\n        memTypeIndex < m_hAllocator->GetMemoryTypeCount() && res >= VK_SUCCESS;\n        ++memTypeIndex) {\n      VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_DefaultPoolContexts[memTypeIndex];\n      if (pBlockVectorCtx) {\n         VMA_ASSERT(pBlockVectorCtx->GetBlockVector());\n         pBlockVectorCtx->GetBlockVector()->Defragment(\n            pBlockVectorCtx,\n            pStats,\n            maxCpuBytesToMove, maxCpuAllocationsToMove,\n            maxGpuBytesToMove, maxGpuAllocationsToMove,\n            commandBuffer);\n         if (pBlockVectorCtx->res != VK_SUCCESS) {\n            res = pBlockVectorCtx->res;\n         }\n      }\n   }\n\n   // Process custom pools.\n   for (size_t customCtxIndex = 0, customCtxCount = m_CustomPoolContexts.size();\n        customCtxIndex < customCtxCount && res >= VK_SUCCESS;\n        ++customCtxIndex) {\n      VmaBlockVectorDefragmentationContext* pBlockVectorCtx = m_CustomPoolContexts[customCtxIndex];\n      VMA_ASSERT(pBlockVectorCtx && pBlockVectorCtx->GetBlockVector());\n      pBlockVectorCtx->GetBlockVector()->Defragment(\n         pBlockVectorCtx,\n         pStats,\n         maxCpuBytesToMove, maxCpuAllocationsToMove,\n         maxGpuBytesToMove, maxGpuAllocationsToMove,\n         commandBuffer);\n      if (pBlockVectorCtx->res != VK_SUCCESS) {\n         res = pBlockVectorCtx->res;\n      }\n   }\n\n   return res;\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// VmaRecorder\n\n#if VMA_RECORDING_ENABLED\n\nVmaRecorder::VmaRecorder() :\n   m_UseMutex(true),\n   m_Flags(0),\n   m_File(VMA_NULL),\n   m_Freq(INT64_MAX),\n   m_StartCounter(INT64_MAX)\n{\n}\n\nVkResult VmaRecorder::Init(const VmaRecordSettings& settings, bool useMutex)\n{\n   m_UseMutex = useMutex;\n   m_Flags = settings.flags;\n\n   QueryPerformanceFrequency((LARGE_INTEGER*)&m_Freq);\n   QueryPerformanceCounter((LARGE_INTEGER*)&m_StartCounter);\n\n   // Open file for writing.\n   errno_t err = fopen_s(&m_File, settings.pFilePath, \"wb\");\n   if (err != 0) {\n      return VK_ERROR_INITIALIZATION_FAILED;\n   }\n\n   // Write header.\n   fprintf(m_File, \"%s\\n\", \"Vulkan Memory Allocator,Calls recording\");\n   fprintf(m_File, \"%s\\n\", \"1,8\");\n\n   return VK_SUCCESS;\n}\n\nVmaRecorder::~VmaRecorder()\n{\n   if (m_File != VMA_NULL) {\n      fclose(m_File);\n   }\n}\n\nvoid VmaRecorder::RecordCreateAllocator(uint32_t frameIndex)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaCreateAllocator\\n\", callParams.threadId, callParams.time, frameIndex);\n   Flush();\n}\n\nvoid VmaRecorder::RecordDestroyAllocator(uint32_t frameIndex)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaDestroyAllocator\\n\", callParams.threadId, callParams.time, frameIndex);\n   Flush();\n}\n\nvoid VmaRecorder::RecordCreatePool(uint32_t frameIndex, const VmaPoolCreateInfo& createInfo, VmaPool pool)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaCreatePool,%u,%u,%llu,%llu,%llu,%u,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           createInfo.memoryTypeIndex,\n           createInfo.flags,\n           createInfo.blockSize,\n           (uint64_t)createInfo.minBlockCount,\n           (uint64_t)createInfo.maxBlockCount,\n           createInfo.frameInUseCount,\n           pool);\n   Flush();\n}\n\nvoid VmaRecorder::RecordDestroyPool(uint32_t frameIndex, VmaPool pool)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaDestroyPool,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           pool);\n   Flush();\n}\n\nvoid VmaRecorder::RecordAllocateMemory(uint32_t frameIndex,\n                                       const VkMemoryRequirements& vkMemReq,\n                                       const VmaAllocationCreateInfo& createInfo,\n                                       VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   UserDataString userDataStr(createInfo.flags, createInfo.pUserData);\n   fprintf(m_File, \"%u,%.3f,%u,vmaAllocateMemory,%llu,%llu,%u,%u,%u,%u,%u,%u,%p,%p,%s\\n\", callParams.threadId, callParams.time, frameIndex,\n           vkMemReq.size,\n           vkMemReq.alignment,\n           vkMemReq.memoryTypeBits,\n           createInfo.flags,\n           createInfo.usage,\n           createInfo.requiredFlags,\n           createInfo.preferredFlags,\n           createInfo.memoryTypeBits,\n           createInfo.pool,\n           allocation,\n           userDataStr.GetString());\n   Flush();\n}\n\nvoid VmaRecorder::RecordAllocateMemoryPages(uint32_t frameIndex,\n                                            const VkMemoryRequirements& vkMemReq,\n                                            const VmaAllocationCreateInfo& createInfo,\n                                            uint64_t allocationCount,\n                                            const VmaAllocation* pAllocations)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   UserDataString userDataStr(createInfo.flags, createInfo.pUserData);\n   fprintf(m_File, \"%u,%.3f,%u,vmaAllocateMemoryPages,%llu,%llu,%u,%u,%u,%u,%u,%u,%p,\", callParams.threadId, callParams.time, frameIndex,\n           vkMemReq.size,\n           vkMemReq.alignment,\n           vkMemReq.memoryTypeBits,\n           createInfo.flags,\n           createInfo.usage,\n           createInfo.requiredFlags,\n           createInfo.preferredFlags,\n           createInfo.memoryTypeBits,\n           createInfo.pool);\n   PrintPointerList(allocationCount, pAllocations);\n   fprintf(m_File, \",%s\\n\", userDataStr.GetString());\n   Flush();\n}\n\nvoid VmaRecorder::RecordAllocateMemoryForBuffer(uint32_t frameIndex,\n                                                const VkMemoryRequirements& vkMemReq,\n                                                bool requiresDedicatedAllocation,\n                                                bool prefersDedicatedAllocation,\n                                                const VmaAllocationCreateInfo& createInfo,\n                                                VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   UserDataString userDataStr(createInfo.flags, createInfo.pUserData);\n   fprintf(m_File, \"%u,%.3f,%u,vmaAllocateMemoryForBuffer,%llu,%llu,%u,%u,%u,%u,%u,%u,%u,%u,%p,%p,%s\\n\", callParams.threadId, callParams.time, frameIndex,\n           vkMemReq.size,\n           vkMemReq.alignment,\n           vkMemReq.memoryTypeBits,\n           requiresDedicatedAllocation ? 1 : 0,\n           prefersDedicatedAllocation ? 1 : 0,\n           createInfo.flags,\n           createInfo.usage,\n           createInfo.requiredFlags,\n           createInfo.preferredFlags,\n           createInfo.memoryTypeBits,\n           createInfo.pool,\n           allocation,\n           userDataStr.GetString());\n   Flush();\n}\n\nvoid VmaRecorder::RecordAllocateMemoryForImage(uint32_t frameIndex,\n                                               const VkMemoryRequirements& vkMemReq,\n                                               bool requiresDedicatedAllocation,\n                                               bool prefersDedicatedAllocation,\n                                               const VmaAllocationCreateInfo& createInfo,\n                                               VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   UserDataString userDataStr(createInfo.flags, createInfo.pUserData);\n   fprintf(m_File, \"%u,%.3f,%u,vmaAllocateMemoryForImage,%llu,%llu,%u,%u,%u,%u,%u,%u,%u,%u,%p,%p,%s\\n\", callParams.threadId, callParams.time, frameIndex,\n           vkMemReq.size,\n           vkMemReq.alignment,\n           vkMemReq.memoryTypeBits,\n           requiresDedicatedAllocation ? 1 : 0,\n           prefersDedicatedAllocation ? 1 : 0,\n           createInfo.flags,\n           createInfo.usage,\n           createInfo.requiredFlags,\n           createInfo.preferredFlags,\n           createInfo.memoryTypeBits,\n           createInfo.pool,\n           allocation,\n           userDataStr.GetString());\n   Flush();\n}\n\nvoid VmaRecorder::RecordFreeMemory(uint32_t frameIndex,\n                                   VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaFreeMemory,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation);\n   Flush();\n}\n\nvoid VmaRecorder::RecordFreeMemoryPages(uint32_t frameIndex,\n                                        uint64_t allocationCount,\n                                        const VmaAllocation* pAllocations)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaFreeMemoryPages,\", callParams.threadId, callParams.time, frameIndex);\n   PrintPointerList(allocationCount, pAllocations);\n   fprintf(m_File, \"\\n\");\n   Flush();\n}\n\nvoid VmaRecorder::RecordSetAllocationUserData(uint32_t frameIndex,\n                                              VmaAllocation allocation,\n                                              const void* pUserData)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   UserDataString userDataStr(\n      allocation->IsUserDataString() ? VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT : 0,\n      pUserData);\n   fprintf(m_File, \"%u,%.3f,%u,vmaSetAllocationUserData,%p,%s\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation,\n           userDataStr.GetString());\n   Flush();\n}\n\nvoid VmaRecorder::RecordCreateLostAllocation(uint32_t frameIndex,\n                                             VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaCreateLostAllocation,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation);\n   Flush();\n}\n\nvoid VmaRecorder::RecordMapMemory(uint32_t frameIndex,\n                                  VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaMapMemory,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation);\n   Flush();\n}\n\nvoid VmaRecorder::RecordUnmapMemory(uint32_t frameIndex,\n                                    VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaUnmapMemory,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation);\n   Flush();\n}\n\nvoid VmaRecorder::RecordFlushAllocation(uint32_t frameIndex,\n                                        VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaFlushAllocation,%p,%llu,%llu\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation,\n           offset,\n           size);\n   Flush();\n}\n\nvoid VmaRecorder::RecordInvalidateAllocation(uint32_t frameIndex,\n                                             VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaInvalidateAllocation,%p,%llu,%llu\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation,\n           offset,\n           size);\n   Flush();\n}\n\nvoid VmaRecorder::RecordCreateBuffer(uint32_t frameIndex,\n                                     const VkBufferCreateInfo& bufCreateInfo,\n                                     const VmaAllocationCreateInfo& allocCreateInfo,\n                                     VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   UserDataString userDataStr(allocCreateInfo.flags, allocCreateInfo.pUserData);\n   fprintf(m_File, \"%u,%.3f,%u,vmaCreateBuffer,%u,%llu,%u,%u,%u,%u,%u,%u,%u,%p,%p,%s\\n\", callParams.threadId, callParams.time, frameIndex,\n           bufCreateInfo.flags,\n           bufCreateInfo.size,\n           bufCreateInfo.usage,\n           bufCreateInfo.sharingMode,\n           allocCreateInfo.flags,\n           allocCreateInfo.usage,\n           allocCreateInfo.requiredFlags,\n           allocCreateInfo.preferredFlags,\n           allocCreateInfo.memoryTypeBits,\n           allocCreateInfo.pool,\n           allocation,\n           userDataStr.GetString());\n   Flush();\n}\n\nvoid VmaRecorder::RecordCreateImage(uint32_t frameIndex,\n                                    const VkImageCreateInfo& imageCreateInfo,\n                                    const VmaAllocationCreateInfo& allocCreateInfo,\n                                    VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   UserDataString userDataStr(allocCreateInfo.flags, allocCreateInfo.pUserData);\n   fprintf(m_File, \"%u,%.3f,%u,vmaCreateImage,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%p,%p,%s\\n\", callParams.threadId, callParams.time, frameIndex,\n           imageCreateInfo.flags,\n           imageCreateInfo.imageType,\n           imageCreateInfo.format,\n           imageCreateInfo.extent.width,\n           imageCreateInfo.extent.height,\n           imageCreateInfo.extent.depth,\n           imageCreateInfo.mipLevels,\n           imageCreateInfo.arrayLayers,\n           imageCreateInfo.samples,\n           imageCreateInfo.tiling,\n           imageCreateInfo.usage,\n           imageCreateInfo.sharingMode,\n           imageCreateInfo.initialLayout,\n           allocCreateInfo.flags,\n           allocCreateInfo.usage,\n           allocCreateInfo.requiredFlags,\n           allocCreateInfo.preferredFlags,\n           allocCreateInfo.memoryTypeBits,\n           allocCreateInfo.pool,\n           allocation,\n           userDataStr.GetString());\n   Flush();\n}\n\nvoid VmaRecorder::RecordDestroyBuffer(uint32_t frameIndex,\n                                      VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaDestroyBuffer,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation);\n   Flush();\n}\n\nvoid VmaRecorder::RecordDestroyImage(uint32_t frameIndex,\n                                     VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaDestroyImage,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation);\n   Flush();\n}\n\nvoid VmaRecorder::RecordTouchAllocation(uint32_t frameIndex,\n                                        VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaTouchAllocation,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation);\n   Flush();\n}\n\nvoid VmaRecorder::RecordGetAllocationInfo(uint32_t frameIndex,\n                                          VmaAllocation allocation)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaGetAllocationInfo,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           allocation);\n   Flush();\n}\n\nvoid VmaRecorder::RecordMakePoolAllocationsLost(uint32_t frameIndex,\n                                                VmaPool pool)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaMakePoolAllocationsLost,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           pool);\n   Flush();\n}\n\nvoid VmaRecorder::RecordDefragmentationBegin(uint32_t frameIndex,\n                                             const VmaDefragmentationInfo2& info,\n                                             VmaDefragmentationContext ctx)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaDefragmentationBegin,%u,\", callParams.threadId, callParams.time, frameIndex,\n           info.flags);\n   PrintPointerList(info.allocationCount, info.pAllocations);\n   fprintf(m_File, \",\");\n   PrintPointerList(info.poolCount, info.pPools);\n   fprintf(m_File, \",%llu,%u,%llu,%u,%p,%p\\n\",\n           info.maxCpuBytesToMove,\n           info.maxCpuAllocationsToMove,\n           info.maxGpuBytesToMove,\n           info.maxGpuAllocationsToMove,\n           info.commandBuffer,\n           ctx);\n   Flush();\n}\n\nvoid VmaRecorder::RecordDefragmentationEnd(uint32_t frameIndex,\n                                           VmaDefragmentationContext ctx)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaDefragmentationEnd,%p\\n\", callParams.threadId, callParams.time, frameIndex,\n           ctx);\n   Flush();\n}\n\nvoid VmaRecorder::RecordSetPoolName(uint32_t frameIndex,\n                                    VmaPool pool,\n                                    const char* name)\n{\n   CallParams callParams;\n   GetBasicParams(callParams);\n\n   VmaMutexLock lock(m_FileMutex, m_UseMutex);\n   fprintf(m_File, \"%u,%.3f,%u,vmaSetPoolName,%p,%s\\n\", callParams.threadId, callParams.time, frameIndex,\n           pool, name != VMA_NULL ? name : \"\");\n   Flush();\n}\n\nVmaRecorder::UserDataString::UserDataString(VmaAllocationCreateFlags allocFlags, const void* pUserData)\n{\n   if (pUserData != VMA_NULL) {\n      if ((allocFlags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0) {\n         m_Str = (const char*)pUserData;\n      } else {\n         sprintf_s(m_PtrStr, \"%p\", pUserData);\n         m_Str = m_PtrStr;\n      }\n   } else {\n      m_Str = \"\";\n   }\n}\n\nvoid VmaRecorder::WriteConfiguration(\n   const VkPhysicalDeviceProperties& devProps,\n   const VkPhysicalDeviceMemoryProperties& memProps,\n   uint32_t vulkanApiVersion,\n   bool dedicatedAllocationExtensionEnabled,\n   bool bindMemory2ExtensionEnabled,\n   bool memoryBudgetExtensionEnabled)\n{\n   fprintf(m_File, \"Config,Begin\\n\");\n\n   fprintf(m_File, \"VulkanApiVersion,%u,%u\\n\", VK_VERSION_MAJOR(vulkanApiVersion), VK_VERSION_MINOR(vulkanApiVersion));\n\n   fprintf(m_File, \"PhysicalDevice,apiVersion,%u\\n\", devProps.apiVersion);\n   fprintf(m_File, \"PhysicalDevice,driverVersion,%u\\n\", devProps.driverVersion);\n   fprintf(m_File, \"PhysicalDevice,vendorID,%u\\n\", devProps.vendorID);\n   fprintf(m_File, \"PhysicalDevice,deviceID,%u\\n\", devProps.deviceID);\n   fprintf(m_File, \"PhysicalDevice,deviceType,%u\\n\", devProps.deviceType);\n   fprintf(m_File, \"PhysicalDevice,deviceName,%s\\n\", devProps.deviceName);\n\n   fprintf(m_File, \"PhysicalDeviceLimits,maxMemoryAllocationCount,%u\\n\", devProps.limits.maxMemoryAllocationCount);\n   fprintf(m_File, \"PhysicalDeviceLimits,bufferImageGranularity,%llu\\n\", devProps.limits.bufferImageGranularity);\n   fprintf(m_File, \"PhysicalDeviceLimits,nonCoherentAtomSize,%llu\\n\", devProps.limits.nonCoherentAtomSize);\n\n   fprintf(m_File, \"PhysicalDeviceMemory,HeapCount,%u\\n\", memProps.memoryHeapCount);\n   for (uint32_t i = 0; i < memProps.memoryHeapCount; ++i) {\n      fprintf(m_File, \"PhysicalDeviceMemory,Heap,%u,size,%llu\\n\", i, memProps.memoryHeaps[i].size);\n      fprintf(m_File, \"PhysicalDeviceMemory,Heap,%u,flags,%u\\n\", i, memProps.memoryHeaps[i].flags);\n   }\n   fprintf(m_File, \"PhysicalDeviceMemory,TypeCount,%u\\n\", memProps.memoryTypeCount);\n   for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {\n      fprintf(m_File, \"PhysicalDeviceMemory,Type,%u,heapIndex,%u\\n\", i, memProps.memoryTypes[i].heapIndex);\n      fprintf(m_File, \"PhysicalDeviceMemory,Type,%u,propertyFlags,%u\\n\", i, memProps.memoryTypes[i].propertyFlags);\n   }\n\n   fprintf(m_File, \"Extension,VK_KHR_dedicated_allocation,%u\\n\", dedicatedAllocationExtensionEnabled ? 1 : 0);\n   fprintf(m_File, \"Extension,VK_KHR_bind_memory2,%u\\n\", bindMemory2ExtensionEnabled ? 1 : 0);\n   fprintf(m_File, \"Extension,VK_EXT_memory_budget,%u\\n\", memoryBudgetExtensionEnabled ? 1 : 0);\n\n   fprintf(m_File, \"Macro,VMA_DEBUG_ALWAYS_DEDICATED_MEMORY,%u\\n\", VMA_DEBUG_ALWAYS_DEDICATED_MEMORY ? 1 : 0);\n   fprintf(m_File, \"Macro,VMA_DEBUG_ALIGNMENT,%llu\\n\", (VkDeviceSize)VMA_DEBUG_ALIGNMENT);\n   fprintf(m_File, \"Macro,VMA_DEBUG_MARGIN,%llu\\n\", (VkDeviceSize)VMA_DEBUG_MARGIN);\n   fprintf(m_File, \"Macro,VMA_DEBUG_INITIALIZE_ALLOCATIONS,%u\\n\", VMA_DEBUG_INITIALIZE_ALLOCATIONS ? 1 : 0);\n   fprintf(m_File, \"Macro,VMA_DEBUG_DETECT_CORRUPTION,%u\\n\", VMA_DEBUG_DETECT_CORRUPTION ? 1 : 0);\n   fprintf(m_File, \"Macro,VMA_DEBUG_GLOBAL_MUTEX,%u\\n\", VMA_DEBUG_GLOBAL_MUTEX ? 1 : 0);\n   fprintf(m_File, \"Macro,VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY,%llu\\n\", (VkDeviceSize)VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY);\n   fprintf(m_File, \"Macro,VMA_SMALL_HEAP_MAX_SIZE,%llu\\n\", (VkDeviceSize)VMA_SMALL_HEAP_MAX_SIZE);\n   fprintf(m_File, \"Macro,VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE,%llu\\n\", (VkDeviceSize)VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE);\n\n   fprintf(m_File, \"Config,End\\n\");\n}\n\nvoid VmaRecorder::GetBasicParams(CallParams& outParams)\n{\n   outParams.threadId = GetCurrentThreadId();\n\n   LARGE_INTEGER counter;\n   QueryPerformanceCounter(&counter);\n   outParams.time = (double)(counter.QuadPart - m_StartCounter) / (double)m_Freq;\n}\n\nvoid VmaRecorder::PrintPointerList(uint64_t count, const VmaAllocation* pItems)\n{\n   if (count) {\n      fprintf(m_File, \"%p\", pItems[0]);\n      for (uint64_t i = 1; i < count; ++i) {\n         fprintf(m_File, \" %p\", pItems[i]);\n      }\n   }\n}\n\nvoid VmaRecorder::Flush()\n{\n   if ((m_Flags & VMA_RECORD_FLUSH_AFTER_CALL_BIT) != 0) {\n      fflush(m_File);\n   }\n}\n\n#endif // #if VMA_RECORDING_ENABLED\n\n////////////////////////////////////////////////////////////////////////////////\n// VmaAllocationObjectAllocator\n\nVmaAllocationObjectAllocator::VmaAllocationObjectAllocator(const VkAllocationCallbacks* pAllocationCallbacks) :\n   m_Allocator(pAllocationCallbacks, 1024)\n{\n}\n\nVmaAllocation VmaAllocationObjectAllocator::Allocate()\n{\n   VmaMutexLock mutexLock(m_Mutex);\n   return m_Allocator.Alloc();\n}\n\nvoid VmaAllocationObjectAllocator::Free(VmaAllocation hAlloc)\n{\n   VmaMutexLock mutexLock(m_Mutex);\n   m_Allocator.Free(hAlloc);\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// VmaAllocator_T\n\nVmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) :\n   m_UseMutex((pCreateInfo->flags& VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT) == 0),\n   m_VulkanApiVersion(pCreateInfo->vulkanApiVersion != 0 ? pCreateInfo->vulkanApiVersion : VK_API_VERSION_1_0),\n   m_UseKhrDedicatedAllocation((pCreateInfo->flags& VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0),\n   m_UseKhrBindMemory2((pCreateInfo->flags& VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT) != 0),\n   m_UseExtMemoryBudget((pCreateInfo->flags& VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0),\n   m_hDevice(pCreateInfo->device),\n   m_hInstance(pCreateInfo->instance),\n   m_AllocationCallbacksSpecified(pCreateInfo->pAllocationCallbacks != VMA_NULL),\n   m_AllocationCallbacks(pCreateInfo->pAllocationCallbacks ?\n                         *pCreateInfo->pAllocationCallbacks : VmaEmptyAllocationCallbacks),\n   m_AllocationObjectAllocator(&m_AllocationCallbacks),\n   m_HeapSizeLimitMask(0),\n   m_PreferredLargeHeapBlockSize(0),\n   m_PhysicalDevice(pCreateInfo->physicalDevice),\n   m_CurrentFrameIndex(0),\n   m_GpuDefragmentationMemoryTypeBits(UINT32_MAX),\n   m_Pools(VmaStlAllocator<VmaPool>(GetAllocationCallbacks())),\n   m_NextPoolId(0)\n#if VMA_RECORDING_ENABLED\n   , m_pRecorder(VMA_NULL)\n#endif\n{\n   if (m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) {\n      m_UseKhrDedicatedAllocation = false;\n      m_UseKhrBindMemory2 = false;\n   }\n\n   if (VMA_DEBUG_DETECT_CORRUPTION) {\n      // Needs to be multiply of uint32_t size because we are going to write VMA_CORRUPTION_DETECTION_MAGIC_VALUE to it.\n      VMA_ASSERT(VMA_DEBUG_MARGIN % sizeof(uint32_t) == 0);\n   }\n\n   VMA_ASSERT(pCreateInfo->physicalDevice && pCreateInfo->device);\n\n   if (m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0)) {\n#if !(VMA_DEDICATED_ALLOCATION)\n      if ((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0) {\n         VMA_ASSERT(0 && \"VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT set but required extensions are disabled by preprocessor macros.\");\n      }\n#endif\n#if !(VMA_BIND_MEMORY2)\n      if ((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT) != 0) {\n         VMA_ASSERT(0 && \"VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT set but required extension is disabled by preprocessor macros.\");\n      }\n#endif\n   }\n#if !(VMA_MEMORY_BUDGET)\n   if ((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0) {\n      VMA_ASSERT(0 && \"VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT set but required extension is disabled by preprocessor macros.\");\n   }\n#endif\n#if VMA_VULKAN_VERSION < 1001000\n   if (m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) {\n      VMA_ASSERT(0 && \"vulkanApiVersion >= VK_API_VERSION_1_1 but required Vulkan version is disabled by preprocessor macros.\");\n   }\n#endif\n\n   memset(&m_DeviceMemoryCallbacks, 0, sizeof(m_DeviceMemoryCallbacks));\n   memset(&m_PhysicalDeviceProperties, 0, sizeof(m_PhysicalDeviceProperties));\n   memset(&m_MemProps, 0, sizeof(m_MemProps));\n\n   memset(&m_pBlockVectors, 0, sizeof(m_pBlockVectors));\n   memset(&m_pDedicatedAllocations, 0, sizeof(m_pDedicatedAllocations));\n   memset(&m_VulkanFunctions, 0, sizeof(m_VulkanFunctions));\n\n   if (pCreateInfo->pDeviceMemoryCallbacks != VMA_NULL) {\n      m_DeviceMemoryCallbacks.pfnAllocate = pCreateInfo->pDeviceMemoryCallbacks->pfnAllocate;\n      m_DeviceMemoryCallbacks.pfnFree = pCreateInfo->pDeviceMemoryCallbacks->pfnFree;\n   }\n\n   ImportVulkanFunctions(pCreateInfo->pVulkanFunctions);\n\n   (*m_VulkanFunctions.vkGetPhysicalDeviceProperties)(m_PhysicalDevice, &m_PhysicalDeviceProperties);\n   (*m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties)(m_PhysicalDevice, &m_MemProps);\n\n   VMA_ASSERT(VmaIsPow2(VMA_DEBUG_ALIGNMENT));\n   VMA_ASSERT(VmaIsPow2(VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY));\n   VMA_ASSERT(VmaIsPow2(m_PhysicalDeviceProperties.limits.bufferImageGranularity));\n   VMA_ASSERT(VmaIsPow2(m_PhysicalDeviceProperties.limits.nonCoherentAtomSize));\n\n   m_PreferredLargeHeapBlockSize = (pCreateInfo->preferredLargeHeapBlockSize != 0) ?\n      pCreateInfo->preferredLargeHeapBlockSize : static_cast<VkDeviceSize>(VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE);\n\n   if (pCreateInfo->pHeapSizeLimit != VMA_NULL) {\n      for (uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex) {\n         const VkDeviceSize limit = pCreateInfo->pHeapSizeLimit[heapIndex];\n         if (limit != VK_WHOLE_SIZE) {\n            m_HeapSizeLimitMask |= 1u << heapIndex;\n            if (limit < m_MemProps.memoryHeaps[heapIndex].size) {\n               m_MemProps.memoryHeaps[heapIndex].size = limit;\n            }\n         }\n      }\n   }\n\n   for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) {\n      const VkDeviceSize preferredBlockSize = CalcPreferredBlockSize(memTypeIndex);\n\n      m_pBlockVectors[memTypeIndex] = vma_new(this, VmaBlockVector)(\n         this,\n         VK_NULL_HANDLE, // hParentPool\n         memTypeIndex,\n         preferredBlockSize,\n         0,\n         SIZE_MAX,\n         GetBufferImageGranularity(),\n         pCreateInfo->frameInUseCount,\n         false, // explicitBlockSize\n         false); // linearAlgorithm\n     // No need to call m_pBlockVectors[memTypeIndex][blockVectorTypeIndex]->CreateMinBlocks here,\n     // becase minBlockCount is 0.\n      m_pDedicatedAllocations[memTypeIndex] = vma_new(this, AllocationVectorType)(VmaStlAllocator<VmaAllocation>(GetAllocationCallbacks()));\n\n   }\n}\n\nVkResult VmaAllocator_T::Init(const VmaAllocatorCreateInfo* pCreateInfo)\n{\n   VkResult res = VK_SUCCESS;\n\n   if (pCreateInfo->pRecordSettings != VMA_NULL &&\n       !VmaStrIsEmpty(pCreateInfo->pRecordSettings->pFilePath)) {\n#if VMA_RECORDING_ENABLED\n      m_pRecorder = vma_new(this, VmaRecorder)();\n      res = m_pRecorder->Init(*pCreateInfo->pRecordSettings, m_UseMutex);\n      if (res != VK_SUCCESS) {\n         return res;\n      }\n      m_pRecorder->WriteConfiguration(\n         m_PhysicalDeviceProperties,\n         m_MemProps,\n         m_VulkanApiVersion,\n         m_UseKhrDedicatedAllocation,\n         m_UseKhrBindMemory2,\n         m_UseExtMemoryBudget);\n      m_pRecorder->RecordCreateAllocator(GetCurrentFrameIndex());\n#else\n      VMA_ASSERT(0 && \"VmaAllocatorCreateInfo::pRecordSettings used, but not supported due to VMA_RECORDING_ENABLED not defined to 1.\");\n      return VK_ERROR_FEATURE_NOT_PRESENT;\n#endif\n   }\n\n#if VMA_MEMORY_BUDGET\n   if (m_UseExtMemoryBudget) {\n      UpdateVulkanBudget();\n   }\n#endif // #if VMA_MEMORY_BUDGET\n\n   return res;\n}\n\nVmaAllocator_T::~VmaAllocator_T()\n{\n#if VMA_RECORDING_ENABLED\n   if (m_pRecorder != VMA_NULL) {\n      m_pRecorder->RecordDestroyAllocator(GetCurrentFrameIndex());\n      vma_delete(this, m_pRecorder);\n   }\n#endif\n\n   VMA_ASSERT(m_Pools.empty());\n\n   for (size_t i = GetMemoryTypeCount(); i--; ) {\n      if (m_pDedicatedAllocations[i] != VMA_NULL && !m_pDedicatedAllocations[i]->empty()) {\n         VMA_ASSERT(0 && \"Unfreed dedicated allocations found.\");\n      }\n\n      vma_delete(this, m_pDedicatedAllocations[i]);\n      vma_delete(this, m_pBlockVectors[i]);\n   }\n}\n\nvoid VmaAllocator_T::ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions)\n{\n#if VMA_STATIC_VULKAN_FUNCTIONS == 1\n   m_VulkanFunctions.vkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties)vkGetPhysicalDeviceProperties;\n   m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties)vkGetPhysicalDeviceMemoryProperties;\n   m_VulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkAllocateMemory;\n   m_VulkanFunctions.vkFreeMemory = (PFN_vkFreeMemory)vkFreeMemory;\n   m_VulkanFunctions.vkMapMemory = (PFN_vkMapMemory)vkMapMemory;\n   m_VulkanFunctions.vkUnmapMemory = (PFN_vkUnmapMemory)vkUnmapMemory;\n   m_VulkanFunctions.vkFlushMappedMemoryRanges = (PFN_vkFlushMappedMemoryRanges)vkFlushMappedMemoryRanges;\n   m_VulkanFunctions.vkInvalidateMappedMemoryRanges = (PFN_vkInvalidateMappedMemoryRanges)vkInvalidateMappedMemoryRanges;\n   m_VulkanFunctions.vkBindBufferMemory = (PFN_vkBindBufferMemory)vkBindBufferMemory;\n   m_VulkanFunctions.vkBindImageMemory = (PFN_vkBindImageMemory)vkBindImageMemory;\n   m_VulkanFunctions.vkGetBufferMemoryRequirements = (PFN_vkGetBufferMemoryRequirements)vkGetBufferMemoryRequirements;\n   m_VulkanFunctions.vkGetImageMemoryRequirements = (PFN_vkGetImageMemoryRequirements)vkGetImageMemoryRequirements;\n   m_VulkanFunctions.vkCreateBuffer = (PFN_vkCreateBuffer)vkCreateBuffer;\n   m_VulkanFunctions.vkDestroyBuffer = (PFN_vkDestroyBuffer)vkDestroyBuffer;\n   m_VulkanFunctions.vkCreateImage = (PFN_vkCreateImage)vkCreateImage;\n   m_VulkanFunctions.vkDestroyImage = (PFN_vkDestroyImage)vkDestroyImage;\n   m_VulkanFunctions.vkCmdCopyBuffer = (PFN_vkCmdCopyBuffer)vkCmdCopyBuffer;\n#if VMA_VULKAN_VERSION >= 1001000\n   if (m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) {\n      VMA_ASSERT(m_hInstance != VK_NULL_HANDLE);\n      m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR =\n         (PFN_vkGetBufferMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, \"vkGetBufferMemoryRequirements2\");\n      m_VulkanFunctions.vkGetImageMemoryRequirements2KHR =\n         (PFN_vkGetImageMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, \"vkGetImageMemoryRequirements2\");\n      m_VulkanFunctions.vkBindBufferMemory2KHR =\n         (PFN_vkBindBufferMemory2KHR)vkGetDeviceProcAddr(m_hDevice, \"vkBindBufferMemory2\");\n      m_VulkanFunctions.vkBindImageMemory2KHR =\n         (PFN_vkBindImageMemory2KHR)vkGetDeviceProcAddr(m_hDevice, \"vkBindImageMemory2\");\n      m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR =\n         (PFN_vkGetPhysicalDeviceMemoryProperties2KHR)vkGetInstanceProcAddr(m_hInstance, \"vkGetPhysicalDeviceMemoryProperties2\");\n   }\n#endif\n#if VMA_DEDICATED_ALLOCATION\n   if (m_UseKhrDedicatedAllocation) {\n      m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR =\n         (PFN_vkGetBufferMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, \"vkGetBufferMemoryRequirements2KHR\");\n      m_VulkanFunctions.vkGetImageMemoryRequirements2KHR =\n         (PFN_vkGetImageMemoryRequirements2KHR)vkGetDeviceProcAddr(m_hDevice, \"vkGetImageMemoryRequirements2KHR\");\n   }\n#endif\n#if VMA_BIND_MEMORY2\n   if (m_UseKhrBindMemory2) {\n      m_VulkanFunctions.vkBindBufferMemory2KHR =\n         (PFN_vkBindBufferMemory2KHR)vkGetDeviceProcAddr(m_hDevice, \"vkBindBufferMemory2KHR\");\n      m_VulkanFunctions.vkBindImageMemory2KHR =\n         (PFN_vkBindImageMemory2KHR)vkGetDeviceProcAddr(m_hDevice, \"vkBindImageMemory2KHR\");\n   }\n#endif // #if VMA_BIND_MEMORY2\n#if VMA_MEMORY_BUDGET\n   if (m_UseExtMemoryBudget && m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0)) {\n      VMA_ASSERT(m_hInstance != VK_NULL_HANDLE);\n      m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR =\n         (PFN_vkGetPhysicalDeviceMemoryProperties2KHR)vkGetInstanceProcAddr(m_hInstance, \"vkGetPhysicalDeviceMemoryProperties2KHR\");\n   }\n#endif // #if VMA_MEMORY_BUDGET\n#endif // #if VMA_STATIC_VULKAN_FUNCTIONS == 1\n\n#define VMA_COPY_IF_NOT_NULL(funcName) \\\n    if(pVulkanFunctions->funcName != VMA_NULL) m_VulkanFunctions.funcName = pVulkanFunctions->funcName;\n\n   if (pVulkanFunctions != VMA_NULL) {\n      VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceProperties);\n      VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties);\n      VMA_COPY_IF_NOT_NULL(vkAllocateMemory);\n      VMA_COPY_IF_NOT_NULL(vkFreeMemory);\n      VMA_COPY_IF_NOT_NULL(vkMapMemory);\n      VMA_COPY_IF_NOT_NULL(vkUnmapMemory);\n      VMA_COPY_IF_NOT_NULL(vkFlushMappedMemoryRanges);\n      VMA_COPY_IF_NOT_NULL(vkInvalidateMappedMemoryRanges);\n      VMA_COPY_IF_NOT_NULL(vkBindBufferMemory);\n      VMA_COPY_IF_NOT_NULL(vkBindImageMemory);\n      VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements);\n      VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements);\n      VMA_COPY_IF_NOT_NULL(vkCreateBuffer);\n      VMA_COPY_IF_NOT_NULL(vkDestroyBuffer);\n      VMA_COPY_IF_NOT_NULL(vkCreateImage);\n      VMA_COPY_IF_NOT_NULL(vkDestroyImage);\n      VMA_COPY_IF_NOT_NULL(vkCmdCopyBuffer);\n#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n      VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements2KHR);\n      VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements2KHR);\n#endif\n#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000\n      VMA_COPY_IF_NOT_NULL(vkBindBufferMemory2KHR);\n      VMA_COPY_IF_NOT_NULL(vkBindImageMemory2KHR);\n#endif\n#if VMA_MEMORY_BUDGET\n      VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties2KHR);\n#endif\n   }\n\n#undef VMA_COPY_IF_NOT_NULL\n\n   // If these asserts are hit, you must either #define VMA_STATIC_VULKAN_FUNCTIONS 1\n   // or pass valid pointers as VmaAllocatorCreateInfo::pVulkanFunctions.\n   VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceProperties != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkAllocateMemory != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkFreeMemory != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkMapMemory != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkUnmapMemory != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkFlushMappedMemoryRanges != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkInvalidateMappedMemoryRanges != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkBindBufferMemory != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkBindImageMemory != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkGetBufferMemoryRequirements != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkGetImageMemoryRequirements != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkCreateBuffer != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkDestroyBuffer != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkCreateImage != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkDestroyImage != VMA_NULL);\n   VMA_ASSERT(m_VulkanFunctions.vkCmdCopyBuffer != VMA_NULL);\n#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n   if (m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrDedicatedAllocation) {\n      VMA_ASSERT(m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR != VMA_NULL);\n      VMA_ASSERT(m_VulkanFunctions.vkGetImageMemoryRequirements2KHR != VMA_NULL);\n   }\n#endif\n#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000\n   if (m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrBindMemory2) {\n      VMA_ASSERT(m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL);\n      VMA_ASSERT(m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL);\n   }\n#endif\n#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000\n   if (m_UseExtMemoryBudget || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) {\n      VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR != VMA_NULL);\n   }\n#endif\n}\n\nVkDeviceSize VmaAllocator_T::CalcPreferredBlockSize(uint32_t memTypeIndex)\n{\n   const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex);\n   const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size;\n   const bool isSmallHeap = heapSize <= VMA_SMALL_HEAP_MAX_SIZE;\n   return VmaAlignUp(isSmallHeap ? (heapSize / 8) : m_PreferredLargeHeapBlockSize, (VkDeviceSize)32);\n}\n\nVkResult VmaAllocator_T::AllocateMemoryOfType(\n   VkDeviceSize size,\n   VkDeviceSize alignment,\n   bool dedicatedAllocation,\n   VkBuffer dedicatedBuffer,\n   VkImage dedicatedImage,\n   const VmaAllocationCreateInfo& createInfo,\n   uint32_t memTypeIndex,\n   VmaSuballocationType suballocType,\n   size_t allocationCount,\n   VmaAllocation* pAllocations)\n{\n   VMA_ASSERT(pAllocations != VMA_NULL);\n   VMA_DEBUG_LOG(\"  AllocateMemory: MemoryTypeIndex=%u, AllocationCount=%zu, Size=%llu\", memTypeIndex, allocationCount, size);\n\n   VmaAllocationCreateInfo finalCreateInfo = createInfo;\n\n   // If memory type is not HOST_VISIBLE, disable MAPPED.\n   if ((finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0 &&\n      (m_MemProps.memoryTypes[memTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {\n      finalCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT;\n   }\n   // If memory is lazily allocated, it should be always dedicated.\n   if (finalCreateInfo.usage == VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED) {\n      finalCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;\n   }\n\n   VmaBlockVector* const blockVector = m_pBlockVectors[memTypeIndex];\n   VMA_ASSERT(blockVector);\n\n   const VkDeviceSize preferredBlockSize = blockVector->GetPreferredBlockSize();\n   bool preferDedicatedMemory =\n      VMA_DEBUG_ALWAYS_DEDICATED_MEMORY ||\n      dedicatedAllocation ||\n      // Heuristics: Allocate dedicated memory if requested size if greater than half of preferred block size.\n      size > preferredBlockSize / 2;\n\n   if (preferDedicatedMemory &&\n      (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0 &&\n       finalCreateInfo.pool == VK_NULL_HANDLE) {\n      finalCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;\n   }\n\n   if ((finalCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0) {\n      if ((finalCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) {\n         return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n      } else {\n         return AllocateDedicatedMemory(\n            size,\n            suballocType,\n            memTypeIndex,\n            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0,\n            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0,\n            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0,\n            finalCreateInfo.pUserData,\n            dedicatedBuffer,\n            dedicatedImage,\n            allocationCount,\n            pAllocations);\n      }\n   } else {\n      VkResult res = blockVector->Allocate(\n         m_CurrentFrameIndex.load(),\n         size,\n         alignment,\n         finalCreateInfo,\n         suballocType,\n         allocationCount,\n         pAllocations);\n      if (res == VK_SUCCESS) {\n         return res;\n      }\n\n      // 5. Try dedicated memory.\n      if ((finalCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) {\n         return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n      } else {\n         res = AllocateDedicatedMemory(\n            size,\n            suballocType,\n            memTypeIndex,\n            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0,\n            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0,\n            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0,\n            finalCreateInfo.pUserData,\n            dedicatedBuffer,\n            dedicatedImage,\n            allocationCount,\n            pAllocations);\n         if (res == VK_SUCCESS) {\n            // Succeeded: AllocateDedicatedMemory function already filld pMemory, nothing more to do here.\n            VMA_DEBUG_LOG(\"    Allocated as DedicatedMemory\");\n            return VK_SUCCESS;\n         } else {\n            // Everything failed: Return error code.\n            VMA_DEBUG_LOG(\"    vkAllocateMemory FAILED\");\n            return res;\n         }\n      }\n   }\n}\n\nVkResult VmaAllocator_T::AllocateDedicatedMemory(\n   VkDeviceSize size,\n   VmaSuballocationType suballocType,\n   uint32_t memTypeIndex,\n   bool withinBudget,\n   bool map,\n   bool isUserDataString,\n   void* pUserData,\n   VkBuffer dedicatedBuffer,\n   VkImage dedicatedImage,\n   size_t allocationCount,\n   VmaAllocation* pAllocations)\n{\n   VMA_ASSERT(allocationCount > 0 && pAllocations);\n\n   if (withinBudget) {\n      const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex);\n      VmaBudget heapBudget = {};\n      GetBudget(&heapBudget, heapIndex, 1);\n      if (heapBudget.usage + size * allocationCount > heapBudget.budget) {\n         return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n      }\n   }\n\n   VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };\n   allocInfo.memoryTypeIndex = memTypeIndex;\n   allocInfo.allocationSize = size;\n\n#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n   VkMemoryDedicatedAllocateInfoKHR dedicatedAllocInfo = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR };\n   if (m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) {\n      if (dedicatedBuffer != VK_NULL_HANDLE) {\n         VMA_ASSERT(dedicatedImage == VK_NULL_HANDLE);\n         dedicatedAllocInfo.buffer = dedicatedBuffer;\n         allocInfo.pNext = &dedicatedAllocInfo;\n      } else if (dedicatedImage != VK_NULL_HANDLE) {\n         dedicatedAllocInfo.image = dedicatedImage;\n         allocInfo.pNext = &dedicatedAllocInfo;\n      }\n   }\n#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n\n   size_t allocIndex;\n   VkResult res = VK_SUCCESS;\n   for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex) {\n      res = AllocateDedicatedMemoryPage(\n         size,\n         suballocType,\n         memTypeIndex,\n         allocInfo,\n         map,\n         isUserDataString,\n         pUserData,\n         pAllocations + allocIndex);\n      if (res != VK_SUCCESS) {\n         break;\n      }\n   }\n\n   if (res == VK_SUCCESS) {\n      // Register them in m_pDedicatedAllocations.\n      {\n         VmaMutexLockWrite lock(m_DedicatedAllocationsMutex[memTypeIndex], m_UseMutex);\n         AllocationVectorType* pDedicatedAllocations = m_pDedicatedAllocations[memTypeIndex];\n         VMA_ASSERT(pDedicatedAllocations);\n         for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex) {\n            VmaVectorInsertSorted<VmaPointerLess>(*pDedicatedAllocations, pAllocations[allocIndex]);\n         }\n      }\n\n      VMA_DEBUG_LOG(\"    Allocated DedicatedMemory Count=%zu, MemoryTypeIndex=#%u\", allocationCount, memTypeIndex);\n   } else {\n      // Free all already created allocations.\n      while (allocIndex--) {\n         VmaAllocation currAlloc = pAllocations[allocIndex];\n         VkDeviceMemory hMemory = currAlloc->GetMemory();\n\n         /*\n         There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory\n         before vkFreeMemory.\n\n         if(currAlloc->GetMappedData() != VMA_NULL)\n         {\n             (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory);\n         }\n         */\n\n         FreeVulkanMemory(memTypeIndex, currAlloc->GetSize(), hMemory);\n         m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), currAlloc->GetSize());\n         currAlloc->SetUserData(this, VMA_NULL);\n         currAlloc->Dtor();\n         m_AllocationObjectAllocator.Free(currAlloc);\n      }\n\n      memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount);\n   }\n\n   return res;\n}\n\nVkResult VmaAllocator_T::AllocateDedicatedMemoryPage(\n   VkDeviceSize size,\n   VmaSuballocationType suballocType,\n   uint32_t memTypeIndex,\n   const VkMemoryAllocateInfo& allocInfo,\n   bool map,\n   bool isUserDataString,\n   void* pUserData,\n   VmaAllocation* pAllocation)\n{\n   VkDeviceMemory hMemory = VK_NULL_HANDLE;\n   VkResult res = AllocateVulkanMemory(&allocInfo, &hMemory);\n   if (res < 0) {\n      VMA_DEBUG_LOG(\"    vkAllocateMemory FAILED\");\n      return res;\n   }\n\n   void* pMappedData = VMA_NULL;\n   if (map) {\n      res = (*m_VulkanFunctions.vkMapMemory)(\n         m_hDevice,\n         hMemory,\n         0,\n         VK_WHOLE_SIZE,\n         0,\n         &pMappedData);\n      if (res < 0) {\n         VMA_DEBUG_LOG(\"    vkMapMemory FAILED\");\n         FreeVulkanMemory(memTypeIndex, size, hMemory);\n         return res;\n      }\n   }\n\n   *pAllocation = m_AllocationObjectAllocator.Allocate();\n   (*pAllocation)->Ctor(m_CurrentFrameIndex.load(), isUserDataString);\n   (*pAllocation)->InitDedicatedAllocation(memTypeIndex, hMemory, suballocType, pMappedData, size);\n   (*pAllocation)->SetUserData(this, pUserData);\n   m_Budget.AddAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), size);\n   if (VMA_DEBUG_INITIALIZE_ALLOCATIONS) {\n      FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED);\n   }\n\n   return VK_SUCCESS;\n}\n\nvoid VmaAllocator_T::GetBufferMemoryRequirements(\n   VkBuffer hBuffer,\n   VkMemoryRequirements& memReq,\n   bool& requiresDedicatedAllocation,\n   bool& prefersDedicatedAllocation) const\n{\n#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n   if (m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) {\n      VkBufferMemoryRequirementsInfo2KHR memReqInfo = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2_KHR };\n      memReqInfo.buffer = hBuffer;\n\n      VkMemoryDedicatedRequirementsKHR memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR };\n\n      VkMemoryRequirements2KHR memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR };\n      memReq2.pNext = &memDedicatedReq;\n\n      (*m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR)(m_hDevice, &memReqInfo, &memReq2);\n\n      memReq = memReq2.memoryRequirements;\n      requiresDedicatedAllocation = (memDedicatedReq.requiresDedicatedAllocation != VK_FALSE);\n      prefersDedicatedAllocation = (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE);\n   } else\n#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n   {\n      (*m_VulkanFunctions.vkGetBufferMemoryRequirements)(m_hDevice, hBuffer, &memReq);\n      requiresDedicatedAllocation = false;\n      prefersDedicatedAllocation = false;\n   }\n}\n\nvoid VmaAllocator_T::GetImageMemoryRequirements(\n   VkImage hImage,\n   VkMemoryRequirements& memReq,\n   bool& requiresDedicatedAllocation,\n   bool& prefersDedicatedAllocation) const\n{\n#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n   if (m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) {\n      VkImageMemoryRequirementsInfo2KHR memReqInfo = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR };\n      memReqInfo.image = hImage;\n\n      VkMemoryDedicatedRequirementsKHR memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR };\n\n      VkMemoryRequirements2KHR memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR };\n      memReq2.pNext = &memDedicatedReq;\n\n      (*m_VulkanFunctions.vkGetImageMemoryRequirements2KHR)(m_hDevice, &memReqInfo, &memReq2);\n\n      memReq = memReq2.memoryRequirements;\n      requiresDedicatedAllocation = (memDedicatedReq.requiresDedicatedAllocation != VK_FALSE);\n      prefersDedicatedAllocation = (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE);\n   } else\n#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000\n   {\n      (*m_VulkanFunctions.vkGetImageMemoryRequirements)(m_hDevice, hImage, &memReq);\n      requiresDedicatedAllocation = false;\n      prefersDedicatedAllocation = false;\n   }\n}\n\nVkResult VmaAllocator_T::AllocateMemory(\n   const VkMemoryRequirements& vkMemReq,\n   bool requiresDedicatedAllocation,\n   bool prefersDedicatedAllocation,\n   VkBuffer dedicatedBuffer,\n   VkImage dedicatedImage,\n   const VmaAllocationCreateInfo& createInfo,\n   VmaSuballocationType suballocType,\n   size_t allocationCount,\n   VmaAllocation* pAllocations)\n{\n   memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount);\n\n   VMA_ASSERT(VmaIsPow2(vkMemReq.alignment));\n\n   if (vkMemReq.size == 0) {\n      return VK_ERROR_VALIDATION_FAILED_EXT;\n   }\n   if ((createInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0 &&\n      (createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) {\n      VMA_ASSERT(0 && \"Specifying VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT together with VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT makes no sense.\");\n      return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n   }\n   if ((createInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0 &&\n      (createInfo.flags & VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT) != 0) {\n      VMA_ASSERT(0 && \"Specifying VMA_ALLOCATION_CREATE_MAPPED_BIT together with VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT is invalid.\");\n      return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n   }\n   if (requiresDedicatedAllocation) {\n      if ((createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) {\n         VMA_ASSERT(0 && \"VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT specified while dedicated allocation is required.\");\n         return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n      }\n      if (createInfo.pool != VK_NULL_HANDLE) {\n         VMA_ASSERT(0 && \"Pool specified while dedicated allocation is required.\");\n         return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n      }\n   }\n   if ((createInfo.pool != VK_NULL_HANDLE) &&\n      ((createInfo.flags & (VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT)) != 0)) {\n      VMA_ASSERT(0 && \"Specifying VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT when pool != null is invalid.\");\n      return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n   }\n\n   if (createInfo.pool != VK_NULL_HANDLE) {\n      const VkDeviceSize alignmentForPool = VMA_MAX(\n         vkMemReq.alignment,\n         GetMemoryTypeMinAlignment(createInfo.pool->m_BlockVector.GetMemoryTypeIndex()));\n\n      VmaAllocationCreateInfo createInfoForPool = createInfo;\n      // If memory type is not HOST_VISIBLE, disable MAPPED.\n      if ((createInfoForPool.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0 &&\n         (m_MemProps.memoryTypes[createInfo.pool->m_BlockVector.GetMemoryTypeIndex()].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {\n         createInfoForPool.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT;\n      }\n\n      return createInfo.pool->m_BlockVector.Allocate(\n         m_CurrentFrameIndex.load(),\n         vkMemReq.size,\n         alignmentForPool,\n         createInfoForPool,\n         suballocType,\n         allocationCount,\n         pAllocations);\n   } else {\n      // Bit mask of memory Vulkan types acceptable for this allocation.\n      uint32_t memoryTypeBits = vkMemReq.memoryTypeBits;\n      uint32_t memTypeIndex = UINT32_MAX;\n      VkResult res = vmaFindMemoryTypeIndex(this, memoryTypeBits, &createInfo, &memTypeIndex);\n      if (res == VK_SUCCESS) {\n         VkDeviceSize alignmentForMemType = VMA_MAX(\n            vkMemReq.alignment,\n            GetMemoryTypeMinAlignment(memTypeIndex));\n\n         res = AllocateMemoryOfType(\n            vkMemReq.size,\n            alignmentForMemType,\n            requiresDedicatedAllocation || prefersDedicatedAllocation,\n            dedicatedBuffer,\n            dedicatedImage,\n            createInfo,\n            memTypeIndex,\n            suballocType,\n            allocationCount,\n            pAllocations);\n         // Succeeded on first try.\n         if (res == VK_SUCCESS) {\n            return res;\n         }\n         // Allocation from this memory type failed. Try other compatible memory types.\n         else {\n            for (;;) {\n               // Remove old memTypeIndex from list of possibilities.\n               memoryTypeBits &= ~(1u << memTypeIndex);\n               // Find alternative memTypeIndex.\n               res = vmaFindMemoryTypeIndex(this, memoryTypeBits, &createInfo, &memTypeIndex);\n               if (res == VK_SUCCESS) {\n                  alignmentForMemType = VMA_MAX(\n                     vkMemReq.alignment,\n                     GetMemoryTypeMinAlignment(memTypeIndex));\n\n                  res = AllocateMemoryOfType(\n                     vkMemReq.size,\n                     alignmentForMemType,\n                     requiresDedicatedAllocation || prefersDedicatedAllocation,\n                     dedicatedBuffer,\n                     dedicatedImage,\n                     createInfo,\n                     memTypeIndex,\n                     suballocType,\n                     allocationCount,\n                     pAllocations);\n                  // Allocation from this alternative memory type succeeded.\n                  if (res == VK_SUCCESS) {\n                     return res;\n                  }\n                  // else: Allocation from this memory type failed. Try next one - next loop iteration.\n               }\n               // No other matching memory type index could be found.\n               else {\n                  // Not returning res, which is VK_ERROR_FEATURE_NOT_PRESENT, because we already failed to allocate once.\n                  return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n               }\n            }\n         }\n      }\n      // Can't find any single memory type maching requirements. res is VK_ERROR_FEATURE_NOT_PRESENT.\n      else\n         return res;\n   }\n}\n\nvoid VmaAllocator_T::FreeMemory(\n   size_t allocationCount,\n   const VmaAllocation* pAllocations)\n{\n   VMA_ASSERT(pAllocations);\n\n   for (size_t allocIndex = allocationCount; allocIndex--; ) {\n      VmaAllocation allocation = pAllocations[allocIndex];\n\n      if (allocation != VK_NULL_HANDLE) {\n         if (TouchAllocation(allocation)) {\n            if (VMA_DEBUG_INITIALIZE_ALLOCATIONS) {\n               FillAllocation(allocation, VMA_ALLOCATION_FILL_PATTERN_DESTROYED);\n            }\n\n            switch (allocation->GetType()) {\n            case VmaAllocation_T::ALLOCATION_TYPE_BLOCK:\n            {\n               VmaBlockVector* pBlockVector = VMA_NULL;\n               VmaPool hPool = allocation->GetBlock()->GetParentPool();\n               if (hPool != VK_NULL_HANDLE) {\n                  pBlockVector = &hPool->m_BlockVector;\n               } else {\n                  const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex();\n                  pBlockVector = m_pBlockVectors[memTypeIndex];\n               }\n               pBlockVector->Free(allocation);\n            }\n            break;\n            case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED:\n               FreeDedicatedMemory(allocation);\n               break;\n            default:\n               VMA_ASSERT(0);\n            }\n         }\n\n         // Do this regardless of whether the allocation is lost. Lost allocations still account to Budget.AllocationBytes.\n         m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(allocation->GetMemoryTypeIndex()), allocation->GetSize());\n         allocation->SetUserData(this, VMA_NULL);\n         allocation->Dtor();\n         m_AllocationObjectAllocator.Free(allocation);\n      }\n   }\n}\n\nVkResult VmaAllocator_T::ResizeAllocation(\n   const VmaAllocation alloc,\n   VkDeviceSize newSize)\n{\n   // This function is deprecated and so it does nothing. It's left for backward compatibility.\n   if (newSize == 0 || alloc->GetLastUseFrameIndex() == VMA_FRAME_INDEX_LOST) {\n      return VK_ERROR_VALIDATION_FAILED_EXT;\n   }\n   if (newSize == alloc->GetSize()) {\n      return VK_SUCCESS;\n   }\n   return VK_ERROR_OUT_OF_POOL_MEMORY;\n}\n\nvoid VmaAllocator_T::CalculateStats(VmaStats* pStats)\n{\n   // Initialize.\n   InitStatInfo(pStats->total);\n   for (size_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i)\n      InitStatInfo(pStats->memoryType[i]);\n   for (size_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i)\n      InitStatInfo(pStats->memoryHeap[i]);\n\n   // Process default pools.\n   for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) {\n      VmaBlockVector* const pBlockVector = m_pBlockVectors[memTypeIndex];\n      VMA_ASSERT(pBlockVector);\n      pBlockVector->AddStats(pStats);\n   }\n\n   // Process custom pools.\n   {\n      VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex);\n      for (size_t poolIndex = 0, poolCount = m_Pools.size(); poolIndex < poolCount; ++poolIndex) {\n         m_Pools[poolIndex]->m_BlockVector.AddStats(pStats);\n      }\n   }\n\n   // Process dedicated allocations.\n   for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) {\n      const uint32_t memHeapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex);\n      VmaMutexLockRead dedicatedAllocationsLock(m_DedicatedAllocationsMutex[memTypeIndex], m_UseMutex);\n      AllocationVectorType* const pDedicatedAllocVector = m_pDedicatedAllocations[memTypeIndex];\n      VMA_ASSERT(pDedicatedAllocVector);\n      for (size_t allocIndex = 0, allocCount = pDedicatedAllocVector->size(); allocIndex < allocCount; ++allocIndex) {\n         VmaStatInfo allocationStatInfo;\n         (*pDedicatedAllocVector)[allocIndex]->DedicatedAllocCalcStatsInfo(allocationStatInfo);\n         VmaAddStatInfo(pStats->total, allocationStatInfo);\n         VmaAddStatInfo(pStats->memoryType[memTypeIndex], allocationStatInfo);\n         VmaAddStatInfo(pStats->memoryHeap[memHeapIndex], allocationStatInfo);\n      }\n   }\n\n   // Postprocess.\n   VmaPostprocessCalcStatInfo(pStats->total);\n   for (size_t i = 0; i < GetMemoryTypeCount(); ++i)\n      VmaPostprocessCalcStatInfo(pStats->memoryType[i]);\n   for (size_t i = 0; i < GetMemoryHeapCount(); ++i)\n      VmaPostprocessCalcStatInfo(pStats->memoryHeap[i]);\n}\n\nvoid VmaAllocator_T::GetBudget(VmaBudget* outBudget, uint32_t firstHeap, uint32_t heapCount)\n{\n#if VMA_MEMORY_BUDGET\n   if (m_UseExtMemoryBudget) {\n      if (m_Budget.m_OperationsSinceBudgetFetch < 30) {\n         VmaMutexLockRead lockRead(m_Budget.m_BudgetMutex, m_UseMutex);\n         for (uint32_t i = 0; i < heapCount; ++i, ++outBudget) {\n            const uint32_t heapIndex = firstHeap + i;\n\n            outBudget->blockBytes = m_Budget.m_BlockBytes[heapIndex];\n            outBudget->allocationBytes = m_Budget.m_AllocationBytes[heapIndex];\n\n            if (m_Budget.m_VulkanUsage[heapIndex] + outBudget->blockBytes > m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]) {\n               outBudget->usage = m_Budget.m_VulkanUsage[heapIndex] +\n                  outBudget->blockBytes - m_Budget.m_BlockBytesAtBudgetFetch[heapIndex];\n            } else {\n               outBudget->usage = 0;\n            }\n\n            // Have to take MIN with heap size because explicit HeapSizeLimit is included in it.\n            outBudget->budget = VMA_MIN(\n               m_Budget.m_VulkanBudget[heapIndex], m_MemProps.memoryHeaps[heapIndex].size);\n         }\n      } else {\n         UpdateVulkanBudget(); // Outside of mutex lock\n         GetBudget(outBudget, firstHeap, heapCount); // Recursion\n      }\n   } else\n#endif\n   {\n      for (uint32_t i = 0; i < heapCount; ++i, ++outBudget) {\n         const uint32_t heapIndex = firstHeap + i;\n\n         outBudget->blockBytes = m_Budget.m_BlockBytes[heapIndex];\n         outBudget->allocationBytes = m_Budget.m_AllocationBytes[heapIndex];\n\n         outBudget->usage = outBudget->blockBytes;\n         outBudget->budget = m_MemProps.memoryHeaps[heapIndex].size * 8 / 10; // 80% heuristics.\n      }\n   }\n}\n\nstatic const uint32_t VMA_VENDOR_ID_AMD = 4098;\n\nVkResult VmaAllocator_T::DefragmentationBegin(\n   const VmaDefragmentationInfo2& info,\n   VmaDefragmentationStats* pStats,\n   VmaDefragmentationContext* pContext)\n{\n   if (info.pAllocationsChanged != VMA_NULL) {\n      memset(info.pAllocationsChanged, 0, info.allocationCount * sizeof(VkBool32));\n   }\n\n   *pContext = vma_new(this, VmaDefragmentationContext_T)(\n      this, m_CurrentFrameIndex.load(), info.flags, pStats);\n\n   (*pContext)->AddPools(info.poolCount, info.pPools);\n   (*pContext)->AddAllocations(\n      info.allocationCount, info.pAllocations, info.pAllocationsChanged);\n\n   VkResult res = (*pContext)->Defragment(\n      info.maxCpuBytesToMove, info.maxCpuAllocationsToMove,\n      info.maxGpuBytesToMove, info.maxGpuAllocationsToMove,\n      info.commandBuffer, pStats);\n\n   if (res != VK_NOT_READY) {\n      vma_delete(this, *pContext);\n      *pContext = VMA_NULL;\n   }\n\n   return res;\n}\n\nVkResult VmaAllocator_T::DefragmentationEnd(\n   VmaDefragmentationContext context)\n{\n   vma_delete(this, context);\n   return VK_SUCCESS;\n}\n\nvoid VmaAllocator_T::GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo)\n{\n   if (hAllocation->CanBecomeLost()) {\n      /*\n      Warning: This is a carefully designed algorithm.\n      Do not modify unless you really know what you're doing :)\n      */\n      const uint32_t localCurrFrameIndex = m_CurrentFrameIndex.load();\n      uint32_t localLastUseFrameIndex = hAllocation->GetLastUseFrameIndex();\n      for (;;) {\n         if (localLastUseFrameIndex == VMA_FRAME_INDEX_LOST) {\n            pAllocationInfo->memoryType = UINT32_MAX;\n            pAllocationInfo->deviceMemory = VK_NULL_HANDLE;\n            pAllocationInfo->offset = 0;\n            pAllocationInfo->size = hAllocation->GetSize();\n            pAllocationInfo->pMappedData = VMA_NULL;\n            pAllocationInfo->pUserData = hAllocation->GetUserData();\n            return;\n         } else if (localLastUseFrameIndex == localCurrFrameIndex) {\n            pAllocationInfo->memoryType = hAllocation->GetMemoryTypeIndex();\n            pAllocationInfo->deviceMemory = hAllocation->GetMemory();\n            pAllocationInfo->offset = hAllocation->GetOffset();\n            pAllocationInfo->size = hAllocation->GetSize();\n            pAllocationInfo->pMappedData = VMA_NULL;\n            pAllocationInfo->pUserData = hAllocation->GetUserData();\n            return;\n         } else // Last use time earlier than current time.\n         {\n            if (hAllocation->CompareExchangeLastUseFrameIndex(localLastUseFrameIndex, localCurrFrameIndex)) {\n               localLastUseFrameIndex = localCurrFrameIndex;\n            }\n         }\n      }\n   } else {\n#if VMA_STATS_STRING_ENABLED\n      uint32_t localCurrFrameIndex = m_CurrentFrameIndex.load();\n      uint32_t localLastUseFrameIndex = hAllocation->GetLastUseFrameIndex();\n      for (;;) {\n         VMA_ASSERT(localLastUseFrameIndex != VMA_FRAME_INDEX_LOST);\n         if (localLastUseFrameIndex == localCurrFrameIndex) {\n            break;\n         } else // Last use time earlier than current time.\n         {\n            if (hAllocation->CompareExchangeLastUseFrameIndex(localLastUseFrameIndex, localCurrFrameIndex)) {\n               localLastUseFrameIndex = localCurrFrameIndex;\n            }\n         }\n      }\n#endif\n\n      pAllocationInfo->memoryType = hAllocation->GetMemoryTypeIndex();\n      pAllocationInfo->deviceMemory = hAllocation->GetMemory();\n      pAllocationInfo->offset = hAllocation->GetOffset();\n      pAllocationInfo->size = hAllocation->GetSize();\n      pAllocationInfo->pMappedData = hAllocation->GetMappedData();\n      pAllocationInfo->pUserData = hAllocation->GetUserData();\n   }\n}\n\nbool VmaAllocator_T::TouchAllocation(VmaAllocation hAllocation)\n{\n   // This is a stripped-down version of VmaAllocator_T::GetAllocationInfo.\n   if (hAllocation->CanBecomeLost()) {\n      uint32_t localCurrFrameIndex = m_CurrentFrameIndex.load();\n      uint32_t localLastUseFrameIndex = hAllocation->GetLastUseFrameIndex();\n      for (;;) {\n         if (localLastUseFrameIndex == VMA_FRAME_INDEX_LOST) {\n            return false;\n         } else if (localLastUseFrameIndex == localCurrFrameIndex) {\n            return true;\n         } else // Last use time earlier than current time.\n         {\n            if (hAllocation->CompareExchangeLastUseFrameIndex(localLastUseFrameIndex, localCurrFrameIndex)) {\n               localLastUseFrameIndex = localCurrFrameIndex;\n            }\n         }\n      }\n   } else {\n#if VMA_STATS_STRING_ENABLED\n      uint32_t localCurrFrameIndex = m_CurrentFrameIndex.load();\n      uint32_t localLastUseFrameIndex = hAllocation->GetLastUseFrameIndex();\n      for (;;) {\n         VMA_ASSERT(localLastUseFrameIndex != VMA_FRAME_INDEX_LOST);\n         if (localLastUseFrameIndex == localCurrFrameIndex) {\n            break;\n         } else // Last use time earlier than current time.\n         {\n            if (hAllocation->CompareExchangeLastUseFrameIndex(localLastUseFrameIndex, localCurrFrameIndex)) {\n               localLastUseFrameIndex = localCurrFrameIndex;\n            }\n         }\n      }\n#endif\n\n      return true;\n   }\n}\n\nVkResult VmaAllocator_T::CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPool* pPool)\n{\n   VMA_DEBUG_LOG(\"  CreatePool: MemoryTypeIndex=%u, flags=%u\", pCreateInfo->memoryTypeIndex, pCreateInfo->flags);\n\n   VmaPoolCreateInfo newCreateInfo = *pCreateInfo;\n\n   if (newCreateInfo.maxBlockCount == 0) {\n      newCreateInfo.maxBlockCount = SIZE_MAX;\n   }\n   if (newCreateInfo.minBlockCount > newCreateInfo.maxBlockCount) {\n      return VK_ERROR_INITIALIZATION_FAILED;\n   }\n\n   const VkDeviceSize preferredBlockSize = CalcPreferredBlockSize(newCreateInfo.memoryTypeIndex);\n\n   *pPool = vma_new(this, VmaPool_T)(this, newCreateInfo, preferredBlockSize);\n\n   VkResult res = (*pPool)->m_BlockVector.CreateMinBlocks();\n   if (res != VK_SUCCESS) {\n      vma_delete(this, *pPool);\n      *pPool = VMA_NULL;\n      return res;\n   }\n\n   // Add to m_Pools.\n   {\n      VmaMutexLockWrite lock(m_PoolsMutex, m_UseMutex);\n      (*pPool)->SetId(m_NextPoolId++);\n      VmaVectorInsertSorted<VmaPointerLess>(m_Pools, *pPool);\n   }\n\n   return VK_SUCCESS;\n}\n\nvoid VmaAllocator_T::DestroyPool(VmaPool pool)\n{\n   // Remove from m_Pools.\n   {\n      VmaMutexLockWrite lock(m_PoolsMutex, m_UseMutex);\n      bool success = VmaVectorRemoveSorted<VmaPointerLess>(m_Pools, pool);\n      VMA_ASSERT(success && \"Pool not found in Allocator.\");\n   }\n\n   vma_delete(this, pool);\n}\n\nvoid VmaAllocator_T::GetPoolStats(VmaPool pool, VmaPoolStats* pPoolStats)\n{\n   pool->m_BlockVector.GetPoolStats(pPoolStats);\n}\n\nvoid VmaAllocator_T::SetCurrentFrameIndex(uint32_t frameIndex)\n{\n   m_CurrentFrameIndex.store(frameIndex);\n\n#if VMA_MEMORY_BUDGET\n   if (m_UseExtMemoryBudget) {\n      UpdateVulkanBudget();\n   }\n#endif // #if VMA_MEMORY_BUDGET\n}\n\nvoid VmaAllocator_T::MakePoolAllocationsLost(\n   VmaPool hPool,\n   size_t* pLostAllocationCount)\n{\n   hPool->m_BlockVector.MakePoolAllocationsLost(\n      m_CurrentFrameIndex.load(),\n      pLostAllocationCount);\n}\n\nVkResult VmaAllocator_T::CheckPoolCorruption(VmaPool hPool)\n{\n   return hPool->m_BlockVector.CheckCorruption();\n}\n\nVkResult VmaAllocator_T::CheckCorruption(uint32_t memoryTypeBits)\n{\n   VkResult finalRes = VK_ERROR_FEATURE_NOT_PRESENT;\n\n   // Process default pools.\n   for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) {\n      if (((1u << memTypeIndex) & memoryTypeBits) != 0) {\n         VmaBlockVector* const pBlockVector = m_pBlockVectors[memTypeIndex];\n         VMA_ASSERT(pBlockVector);\n         VkResult localRes = pBlockVector->CheckCorruption();\n         switch (localRes) {\n         case VK_ERROR_FEATURE_NOT_PRESENT:\n            break;\n         case VK_SUCCESS:\n            finalRes = VK_SUCCESS;\n            break;\n         default:\n            return localRes;\n         }\n      }\n   }\n\n   // Process custom pools.\n   {\n      VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex);\n      for (size_t poolIndex = 0, poolCount = m_Pools.size(); poolIndex < poolCount; ++poolIndex) {\n         if (((1u << m_Pools[poolIndex]->m_BlockVector.GetMemoryTypeIndex()) & memoryTypeBits) != 0) {\n            VkResult localRes = m_Pools[poolIndex]->m_BlockVector.CheckCorruption();\n            switch (localRes) {\n            case VK_ERROR_FEATURE_NOT_PRESENT:\n               break;\n            case VK_SUCCESS:\n               finalRes = VK_SUCCESS;\n               break;\n            default:\n               return localRes;\n            }\n         }\n      }\n   }\n\n   return finalRes;\n}\n\nvoid VmaAllocator_T::CreateLostAllocation(VmaAllocation* pAllocation)\n{\n   *pAllocation = m_AllocationObjectAllocator.Allocate();\n   (*pAllocation)->Ctor(VMA_FRAME_INDEX_LOST, false);\n   (*pAllocation)->InitLost();\n}\n\nVkResult VmaAllocator_T::AllocateVulkanMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory)\n{\n   const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(pAllocateInfo->memoryTypeIndex);\n\n   // HeapSizeLimit is in effect for this heap.\n   if ((m_HeapSizeLimitMask & (1u << heapIndex)) != 0) {\n      const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size;\n      VkDeviceSize blockBytes = m_Budget.m_BlockBytes[heapIndex];\n      for (;;) {\n         const VkDeviceSize blockBytesAfterAllocation = blockBytes + pAllocateInfo->allocationSize;\n         if (blockBytesAfterAllocation > heapSize) {\n            return VK_ERROR_OUT_OF_DEVICE_MEMORY;\n         }\n         if (m_Budget.m_BlockBytes[heapIndex].compare_exchange_strong(blockBytes, blockBytesAfterAllocation)) {\n            break;\n         }\n      }\n   } else {\n      m_Budget.m_BlockBytes[heapIndex] += pAllocateInfo->allocationSize;\n   }\n\n   // VULKAN CALL vkAllocateMemory.\n   VkResult res = (*m_VulkanFunctions.vkAllocateMemory)(m_hDevice, pAllocateInfo, GetAllocationCallbacks(), pMemory);\n\n   if (res == VK_SUCCESS) {\n#if VMA_MEMORY_BUDGET\n      ++m_Budget.m_OperationsSinceBudgetFetch;\n#endif\n\n      // Informative callback.\n      if (m_DeviceMemoryCallbacks.pfnAllocate != VMA_NULL) {\n         (*m_DeviceMemoryCallbacks.pfnAllocate)(this, pAllocateInfo->memoryTypeIndex, *pMemory, pAllocateInfo->allocationSize);\n      }\n   } else {\n      m_Budget.m_BlockBytes[heapIndex] -= pAllocateInfo->allocationSize;\n   }\n\n   return res;\n}\n\nvoid VmaAllocator_T::FreeVulkanMemory(uint32_t memoryType, VkDeviceSize size, VkDeviceMemory hMemory)\n{\n   // Informative callback.\n   if (m_DeviceMemoryCallbacks.pfnFree != VMA_NULL) {\n      (*m_DeviceMemoryCallbacks.pfnFree)(this, memoryType, hMemory, size);\n   }\n\n   // VULKAN CALL vkFreeMemory.\n   (*m_VulkanFunctions.vkFreeMemory)(m_hDevice, hMemory, GetAllocationCallbacks());\n\n   m_Budget.m_BlockBytes[MemoryTypeIndexToHeapIndex(memoryType)] -= size;\n}\n\nVkResult VmaAllocator_T::BindVulkanBuffer(\n   VkDeviceMemory memory,\n   VkDeviceSize memoryOffset,\n   VkBuffer buffer,\n   const void* pNext)\n{\n   if (pNext != VMA_NULL) {\n#if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2\n      if ((m_UseKhrBindMemory2 || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) &&\n          m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL) {\n         VkBindBufferMemoryInfoKHR bindBufferMemoryInfo = { VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO_KHR };\n         bindBufferMemoryInfo.pNext = pNext;\n         bindBufferMemoryInfo.buffer = buffer;\n         bindBufferMemoryInfo.memory = memory;\n         bindBufferMemoryInfo.memoryOffset = memoryOffset;\n         return (*m_VulkanFunctions.vkBindBufferMemory2KHR)(m_hDevice, 1, &bindBufferMemoryInfo);\n      } else\n#endif // #if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2\n      {\n         return VK_ERROR_EXTENSION_NOT_PRESENT;\n      }\n   } else {\n      return (*m_VulkanFunctions.vkBindBufferMemory)(m_hDevice, buffer, memory, memoryOffset);\n   }\n}\n\nVkResult VmaAllocator_T::BindVulkanImage(\n   VkDeviceMemory memory,\n   VkDeviceSize memoryOffset,\n   VkImage image,\n   const void* pNext)\n{\n   if (pNext != VMA_NULL) {\n#if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2\n      if ((m_UseKhrBindMemory2 || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) &&\n          m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL) {\n         VkBindImageMemoryInfoKHR bindBufferMemoryInfo = { VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO_KHR };\n         bindBufferMemoryInfo.pNext = pNext;\n         bindBufferMemoryInfo.image = image;\n         bindBufferMemoryInfo.memory = memory;\n         bindBufferMemoryInfo.memoryOffset = memoryOffset;\n         return (*m_VulkanFunctions.vkBindImageMemory2KHR)(m_hDevice, 1, &bindBufferMemoryInfo);\n      } else\n#endif // #if VMA_BIND_MEMORY2\n      {\n         return VK_ERROR_EXTENSION_NOT_PRESENT;\n      }\n   } else {\n      return (*m_VulkanFunctions.vkBindImageMemory)(m_hDevice, image, memory, memoryOffset);\n   }\n}\n\nVkResult VmaAllocator_T::Map(VmaAllocation hAllocation, void** ppData)\n{\n   if (hAllocation->CanBecomeLost()) {\n      return VK_ERROR_MEMORY_MAP_FAILED;\n   }\n\n   switch (hAllocation->GetType()) {\n   case VmaAllocation_T::ALLOCATION_TYPE_BLOCK:\n   {\n      VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock();\n      char* pBytes = VMA_NULL;\n      VkResult res = pBlock->Map(this, 1, (void**)&pBytes);\n      if (res == VK_SUCCESS) {\n         *ppData = pBytes + (ptrdiff_t)hAllocation->GetOffset();\n         hAllocation->BlockAllocMap();\n      }\n      return res;\n   }\n   case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED:\n      return hAllocation->DedicatedAllocMap(this, ppData);\n   default:\n      VMA_ASSERT(0);\n      return VK_ERROR_MEMORY_MAP_FAILED;\n   }\n}\n\nvoid VmaAllocator_T::Unmap(VmaAllocation hAllocation)\n{\n   switch (hAllocation->GetType()) {\n   case VmaAllocation_T::ALLOCATION_TYPE_BLOCK:\n   {\n      VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock();\n      hAllocation->BlockAllocUnmap();\n      pBlock->Unmap(this, 1);\n   }\n   break;\n   case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED:\n      hAllocation->DedicatedAllocUnmap(this);\n      break;\n   default:\n      VMA_ASSERT(0);\n   }\n}\n\nVkResult VmaAllocator_T::BindBufferMemory(\n   VmaAllocation hAllocation,\n   VkDeviceSize allocationLocalOffset,\n   VkBuffer hBuffer,\n   const void* pNext)\n{\n   VkResult res = VK_SUCCESS;\n   switch (hAllocation->GetType()) {\n   case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED:\n      res = BindVulkanBuffer(hAllocation->GetMemory(), allocationLocalOffset, hBuffer, pNext);\n      break;\n   case VmaAllocation_T::ALLOCATION_TYPE_BLOCK:\n   {\n      VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock();\n      VMA_ASSERT(pBlock && \"Binding buffer to allocation that doesn't belong to any block. Is the allocation lost?\");\n      res = pBlock->BindBufferMemory(this, hAllocation, allocationLocalOffset, hBuffer, pNext);\n      break;\n   }\n   default:\n      VMA_ASSERT(0);\n   }\n   return res;\n}\n\nVkResult VmaAllocator_T::BindImageMemory(\n   VmaAllocation hAllocation,\n   VkDeviceSize allocationLocalOffset,\n   VkImage hImage,\n   const void* pNext)\n{\n   VkResult res = VK_SUCCESS;\n   switch (hAllocation->GetType()) {\n   case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED:\n      res = BindVulkanImage(hAllocation->GetMemory(), allocationLocalOffset, hImage, pNext);\n      break;\n   case VmaAllocation_T::ALLOCATION_TYPE_BLOCK:\n   {\n      VmaDeviceMemoryBlock* pBlock = hAllocation->GetBlock();\n      VMA_ASSERT(pBlock && \"Binding image to allocation that doesn't belong to any block. Is the allocation lost?\");\n      res = pBlock->BindImageMemory(this, hAllocation, allocationLocalOffset, hImage, pNext);\n      break;\n   }\n   default:\n      VMA_ASSERT(0);\n   }\n   return res;\n}\n\nvoid VmaAllocator_T::FlushOrInvalidateAllocation(\n   VmaAllocation hAllocation,\n   VkDeviceSize offset, VkDeviceSize size,\n   VMA_CACHE_OPERATION op)\n{\n   const uint32_t memTypeIndex = hAllocation->GetMemoryTypeIndex();\n   if (size > 0 && IsMemoryTypeNonCoherent(memTypeIndex)) {\n      const VkDeviceSize allocationSize = hAllocation->GetSize();\n      VMA_ASSERT(offset <= allocationSize);\n\n      const VkDeviceSize nonCoherentAtomSize = m_PhysicalDeviceProperties.limits.nonCoherentAtomSize;\n\n      VkMappedMemoryRange memRange = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE };\n      memRange.memory = hAllocation->GetMemory();\n\n      switch (hAllocation->GetType()) {\n      case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED:\n         memRange.offset = VmaAlignDown(offset, nonCoherentAtomSize);\n         if (size == VK_WHOLE_SIZE) {\n            memRange.size = allocationSize - memRange.offset;\n         } else {\n            VMA_ASSERT(offset + size <= allocationSize);\n            memRange.size = VMA_MIN(\n               VmaAlignUp(size + (offset - memRange.offset), nonCoherentAtomSize),\n               allocationSize - memRange.offset);\n         }\n         break;\n\n      case VmaAllocation_T::ALLOCATION_TYPE_BLOCK:\n      {\n         // 1. Still within this allocation.\n         memRange.offset = VmaAlignDown(offset, nonCoherentAtomSize);\n         if (size == VK_WHOLE_SIZE) {\n            size = allocationSize - offset;\n         } else {\n            VMA_ASSERT(offset + size <= allocationSize);\n         }\n         memRange.size = VmaAlignUp(size + (offset - memRange.offset), nonCoherentAtomSize);\n\n         // 2. Adjust to whole block.\n         const VkDeviceSize allocationOffset = hAllocation->GetOffset();\n         VMA_ASSERT(allocationOffset % nonCoherentAtomSize == 0);\n         const VkDeviceSize blockSize = hAllocation->GetBlock()->m_pMetadata->GetSize();\n         memRange.offset += allocationOffset;\n         memRange.size = VMA_MIN(memRange.size, blockSize - memRange.offset);\n\n         break;\n      }\n\n      default:\n         VMA_ASSERT(0);\n      }\n\n      switch (op) {\n      case VMA_CACHE_FLUSH:\n         (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, 1, &memRange);\n         break;\n      case VMA_CACHE_INVALIDATE:\n         (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, 1, &memRange);\n         break;\n      default:\n         VMA_ASSERT(0);\n      }\n   }\n   // else: Just ignore this call.\n}\n\nvoid VmaAllocator_T::FreeDedicatedMemory(const VmaAllocation allocation)\n{\n   VMA_ASSERT(allocation && allocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED);\n\n   const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex();\n   {\n      VmaMutexLockWrite lock(m_DedicatedAllocationsMutex[memTypeIndex], m_UseMutex);\n      AllocationVectorType* const pDedicatedAllocations = m_pDedicatedAllocations[memTypeIndex];\n      VMA_ASSERT(pDedicatedAllocations);\n      bool success = VmaVectorRemoveSorted<VmaPointerLess>(*pDedicatedAllocations, allocation);\n      VMA_ASSERT(success);\n   }\n\n   VkDeviceMemory hMemory = allocation->GetMemory();\n\n   /*\n   There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory\n   before vkFreeMemory.\n\n   if(allocation->GetMappedData() != VMA_NULL)\n   {\n       (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory);\n   }\n   */\n\n   FreeVulkanMemory(memTypeIndex, allocation->GetSize(), hMemory);\n\n   VMA_DEBUG_LOG(\"    Freed DedicatedMemory MemoryTypeIndex=%u\", memTypeIndex);\n}\n\nuint32_t VmaAllocator_T::CalculateGpuDefragmentationMemoryTypeBits() const\n{\n   VkBufferCreateInfo dummyBufCreateInfo;\n   VmaFillGpuDefragmentationBufferCreateInfo(dummyBufCreateInfo);\n\n   uint32_t memoryTypeBits = 0;\n\n   // Create buffer.\n   VkBuffer buf = VK_NULL_HANDLE;\n   VkResult res = (*GetVulkanFunctions().vkCreateBuffer)(\n      m_hDevice, &dummyBufCreateInfo, GetAllocationCallbacks(), &buf);\n   if (res == VK_SUCCESS) {\n      // Query for supported memory types.\n      VkMemoryRequirements memReq;\n      (*GetVulkanFunctions().vkGetBufferMemoryRequirements)(m_hDevice, buf, &memReq);\n      memoryTypeBits = memReq.memoryTypeBits;\n\n      // Destroy buffer.\n      (*GetVulkanFunctions().vkDestroyBuffer)(m_hDevice, buf, GetAllocationCallbacks());\n   }\n\n   return memoryTypeBits;\n}\n\n#if VMA_MEMORY_BUDGET\n\nvoid VmaAllocator_T::UpdateVulkanBudget()\n{\n   VMA_ASSERT(m_UseExtMemoryBudget);\n\n   VkPhysicalDeviceMemoryProperties2KHR memProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2_KHR };\n\n   VkPhysicalDeviceMemoryBudgetPropertiesEXT budgetProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT };\n   memProps.pNext = &budgetProps;\n\n   GetVulkanFunctions().vkGetPhysicalDeviceMemoryProperties2KHR(m_PhysicalDevice, &memProps);\n\n   {\n      VmaMutexLockWrite lockWrite(m_Budget.m_BudgetMutex, m_UseMutex);\n\n      for (uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex) {\n         m_Budget.m_VulkanUsage[heapIndex] = budgetProps.heapUsage[heapIndex];\n         m_Budget.m_VulkanBudget[heapIndex] = budgetProps.heapBudget[heapIndex];\n         m_Budget.m_BlockBytesAtBudgetFetch[heapIndex] = m_Budget.m_BlockBytes[heapIndex].load();\n      }\n      m_Budget.m_OperationsSinceBudgetFetch = 0;\n   }\n}\n\n#endif // #if VMA_MEMORY_BUDGET\n\nvoid VmaAllocator_T::FillAllocation(const VmaAllocation hAllocation, uint8_t pattern)\n{\n   if (VMA_DEBUG_INITIALIZE_ALLOCATIONS &&\n       !hAllocation->CanBecomeLost() &&\n       (m_MemProps.memoryTypes[hAllocation->GetMemoryTypeIndex()].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0) {\n      void* pData = VMA_NULL;\n      VkResult res = Map(hAllocation, &pData);\n      if (res == VK_SUCCESS) {\n         memset(pData, (int)pattern, (size_t)hAllocation->GetSize());\n         FlushOrInvalidateAllocation(hAllocation, 0, VK_WHOLE_SIZE, VMA_CACHE_FLUSH);\n         Unmap(hAllocation);\n      } else {\n         VMA_ASSERT(0 && \"VMA_DEBUG_INITIALIZE_ALLOCATIONS is enabled, but couldn't map memory to fill allocation.\");\n      }\n   }\n}\n\nuint32_t VmaAllocator_T::GetGpuDefragmentationMemoryTypeBits()\n{\n   uint32_t memoryTypeBits = m_GpuDefragmentationMemoryTypeBits.load();\n   if (memoryTypeBits == UINT32_MAX) {\n      memoryTypeBits = CalculateGpuDefragmentationMemoryTypeBits();\n      m_GpuDefragmentationMemoryTypeBits.store(memoryTypeBits);\n   }\n   return memoryTypeBits;\n}\n\n#if VMA_STATS_STRING_ENABLED\n\nvoid VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json)\n{\n   bool dedicatedAllocationsStarted = false;\n   for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) {\n      VmaMutexLockRead dedicatedAllocationsLock(m_DedicatedAllocationsMutex[memTypeIndex], m_UseMutex);\n      AllocationVectorType* const pDedicatedAllocVector = m_pDedicatedAllocations[memTypeIndex];\n      VMA_ASSERT(pDedicatedAllocVector);\n      if (pDedicatedAllocVector->empty() == false) {\n         if (dedicatedAllocationsStarted == false) {\n            dedicatedAllocationsStarted = true;\n            json.WriteString(\"DedicatedAllocations\");\n            json.BeginObject();\n         }\n\n         json.BeginString(\"Type \");\n         json.ContinueString(memTypeIndex);\n         json.EndString();\n\n         json.BeginArray();\n\n         for (size_t i = 0; i < pDedicatedAllocVector->size(); ++i) {\n            json.BeginObject(true);\n            const VmaAllocation hAlloc = (*pDedicatedAllocVector)[i];\n            hAlloc->PrintParameters(json);\n            json.EndObject();\n         }\n\n         json.EndArray();\n      }\n   }\n   if (dedicatedAllocationsStarted) {\n      json.EndObject();\n   }\n\n   {\n      bool allocationsStarted = false;\n      for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) {\n         if (m_pBlockVectors[memTypeIndex]->IsEmpty() == false) {\n            if (allocationsStarted == false) {\n               allocationsStarted = true;\n               json.WriteString(\"DefaultPools\");\n               json.BeginObject();\n            }\n\n            json.BeginString(\"Type \");\n            json.ContinueString(memTypeIndex);\n            json.EndString();\n\n            m_pBlockVectors[memTypeIndex]->PrintDetailedMap(json);\n         }\n      }\n      if (allocationsStarted) {\n         json.EndObject();\n      }\n   }\n\n   // Custom pools\n   {\n      VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex);\n      const size_t poolCount = m_Pools.size();\n      if (poolCount > 0) {\n         json.WriteString(\"Pools\");\n         json.BeginObject();\n         for (size_t poolIndex = 0; poolIndex < poolCount; ++poolIndex) {\n            json.BeginString();\n            json.ContinueString(m_Pools[poolIndex]->GetId());\n            json.EndString();\n\n            m_Pools[poolIndex]->m_BlockVector.PrintDetailedMap(json);\n         }\n         json.EndObject();\n      }\n   }\n}\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\n////////////////////////////////////////////////////////////////////////////////\n// Public interface\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator(\n   const VmaAllocatorCreateInfo* pCreateInfo,\n   VmaAllocator* pAllocator)\n{\n   VMA_ASSERT(pCreateInfo && pAllocator);\n   VMA_ASSERT(pCreateInfo->vulkanApiVersion == 0 ||\n      (VK_VERSION_MAJOR(pCreateInfo->vulkanApiVersion) == 1 && VK_VERSION_MINOR(pCreateInfo->vulkanApiVersion) <= 1));\n   VMA_DEBUG_LOG(\"vmaCreateAllocator\");\n   *pAllocator = vma_new(pCreateInfo->pAllocationCallbacks, VmaAllocator_T)(pCreateInfo);\n   return (*pAllocator)->Init(pCreateInfo);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator(\n   VmaAllocator allocator)\n{\n   if (allocator != VK_NULL_HANDLE) {\n      VMA_DEBUG_LOG(\"vmaDestroyAllocator\");\n      VkAllocationCallbacks allocationCallbacks = allocator->m_AllocationCallbacks;\n      vma_delete(&allocationCallbacks, allocator);\n   }\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties(\n   VmaAllocator allocator,\n   const VkPhysicalDeviceProperties** ppPhysicalDeviceProperties)\n{\n   VMA_ASSERT(allocator && ppPhysicalDeviceProperties);\n   *ppPhysicalDeviceProperties = &allocator->m_PhysicalDeviceProperties;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties(\n   VmaAllocator allocator,\n   const VkPhysicalDeviceMemoryProperties** ppPhysicalDeviceMemoryProperties)\n{\n   VMA_ASSERT(allocator && ppPhysicalDeviceMemoryProperties);\n   *ppPhysicalDeviceMemoryProperties = &allocator->m_MemProps;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties(\n   VmaAllocator allocator,\n   uint32_t memoryTypeIndex,\n   VkMemoryPropertyFlags* pFlags)\n{\n   VMA_ASSERT(allocator && pFlags);\n   VMA_ASSERT(memoryTypeIndex < allocator->GetMemoryTypeCount());\n   *pFlags = allocator->m_MemProps.memoryTypes[memoryTypeIndex].propertyFlags;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex(\n   VmaAllocator allocator,\n   uint32_t frameIndex)\n{\n   VMA_ASSERT(allocator);\n   VMA_ASSERT(frameIndex != VMA_FRAME_INDEX_LOST);\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      allocator->SetCurrentFrameIndex(frameIndex);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaCalculateStats(\n   VmaAllocator allocator,\n   VmaStats* pStats)\n{\n   VMA_ASSERT(allocator && pStats);\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n      allocator->CalculateStats(pStats);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetBudget(\n   VmaAllocator allocator,\n   VmaBudget* pBudget)\n{\n   VMA_ASSERT(allocator && pBudget);\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n      allocator->GetBudget(pBudget, 0, allocator->GetMemoryHeapCount());\n}\n\n#if VMA_STATS_STRING_ENABLED\n\nVMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString(\n   VmaAllocator allocator,\n   char** ppStatsString,\n   VkBool32 detailedMap)\n{\n   VMA_ASSERT(allocator && ppStatsString);\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      VmaStringBuilder sb(allocator);\n   {\n      VmaJsonWriter json(allocator->GetAllocationCallbacks(), sb);\n      json.BeginObject();\n\n      VmaBudget budget[VK_MAX_MEMORY_HEAPS];\n      allocator->GetBudget(budget, 0, allocator->GetMemoryHeapCount());\n\n      VmaStats stats;\n      allocator->CalculateStats(&stats);\n\n      json.WriteString(\"Total\");\n      VmaPrintStatInfo(json, stats.total);\n\n      for (uint32_t heapIndex = 0; heapIndex < allocator->GetMemoryHeapCount(); ++heapIndex) {\n         json.BeginString(\"Heap \");\n         json.ContinueString(heapIndex);\n         json.EndString();\n         json.BeginObject();\n\n         json.WriteString(\"Size\");\n         json.WriteNumber(allocator->m_MemProps.memoryHeaps[heapIndex].size);\n\n         json.WriteString(\"Flags\");\n         json.BeginArray(true);\n         if ((allocator->m_MemProps.memoryHeaps[heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) {\n            json.WriteString(\"DEVICE_LOCAL\");\n         }\n         json.EndArray();\n\n         json.WriteString(\"Budget\");\n         json.BeginObject();\n         {\n            json.WriteString(\"BlockBytes\");\n            json.WriteNumber(budget[heapIndex].blockBytes);\n            json.WriteString(\"AllocationBytes\");\n            json.WriteNumber(budget[heapIndex].allocationBytes);\n            json.WriteString(\"Usage\");\n            json.WriteNumber(budget[heapIndex].usage);\n            json.WriteString(\"Budget\");\n            json.WriteNumber(budget[heapIndex].budget);\n         }\n         json.EndObject();\n\n         if (stats.memoryHeap[heapIndex].blockCount > 0) {\n            json.WriteString(\"Stats\");\n            VmaPrintStatInfo(json, stats.memoryHeap[heapIndex]);\n         }\n\n         for (uint32_t typeIndex = 0; typeIndex < allocator->GetMemoryTypeCount(); ++typeIndex) {\n            if (allocator->MemoryTypeIndexToHeapIndex(typeIndex) == heapIndex) {\n               json.BeginString(\"Type \");\n               json.ContinueString(typeIndex);\n               json.EndString();\n\n               json.BeginObject();\n\n               json.WriteString(\"Flags\");\n               json.BeginArray(true);\n               VkMemoryPropertyFlags flags = allocator->m_MemProps.memoryTypes[typeIndex].propertyFlags;\n               if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {\n                  json.WriteString(\"DEVICE_LOCAL\");\n               }\n               if ((flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0) {\n                  json.WriteString(\"HOST_VISIBLE\");\n               }\n               if ((flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0) {\n                  json.WriteString(\"HOST_COHERENT\");\n               }\n               if ((flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0) {\n                  json.WriteString(\"HOST_CACHED\");\n               }\n               if ((flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) != 0) {\n                  json.WriteString(\"LAZILY_ALLOCATED\");\n               }\n               json.EndArray();\n\n               if (stats.memoryType[typeIndex].blockCount > 0) {\n                  json.WriteString(\"Stats\");\n                  VmaPrintStatInfo(json, stats.memoryType[typeIndex]);\n               }\n\n               json.EndObject();\n            }\n         }\n\n         json.EndObject();\n      }\n      if (detailedMap == VK_TRUE) {\n         allocator->PrintDetailedMap(json);\n      }\n\n      json.EndObject();\n   }\n\n   const size_t len = sb.GetLength();\n   char* const pChars = vma_new_array(allocator, char, len + 1);\n   if (len > 0) {\n      memcpy(pChars, sb.GetData(), len);\n   }\n   pChars[len] = '\\0';\n   *ppStatsString = pChars;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString(\n   VmaAllocator allocator,\n   char* pStatsString)\n{\n   if (pStatsString != VMA_NULL) {\n      VMA_ASSERT(allocator);\n      size_t len = strlen(pStatsString);\n      vma_delete_array(allocator, pStatsString, len + 1);\n   }\n}\n\n#endif // #if VMA_STATS_STRING_ENABLED\n\n/*\nThis function is not protected by any mutex because it just reads immutable data.\n*/\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex(\n   VmaAllocator allocator,\n   uint32_t memoryTypeBits,\n   const VmaAllocationCreateInfo* pAllocationCreateInfo,\n   uint32_t* pMemoryTypeIndex)\n{\n   VMA_ASSERT(allocator != VK_NULL_HANDLE);\n   VMA_ASSERT(pAllocationCreateInfo != VMA_NULL);\n   VMA_ASSERT(pMemoryTypeIndex != VMA_NULL);\n\n   if (pAllocationCreateInfo->memoryTypeBits != 0) {\n      memoryTypeBits &= pAllocationCreateInfo->memoryTypeBits;\n   }\n\n   uint32_t requiredFlags = pAllocationCreateInfo->requiredFlags;\n   uint32_t preferredFlags = pAllocationCreateInfo->preferredFlags;\n   uint32_t notPreferredFlags = 0;\n\n   // Convert usage to requiredFlags and preferredFlags.\n   switch (pAllocationCreateInfo->usage) {\n   case VMA_MEMORY_USAGE_UNKNOWN:\n      break;\n   case VMA_MEMORY_USAGE_GPU_ONLY:\n      if (!allocator->IsIntegratedGpu() || (preferredFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {\n         preferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n      }\n      break;\n   case VMA_MEMORY_USAGE_CPU_ONLY:\n      requiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;\n      break;\n   case VMA_MEMORY_USAGE_CPU_TO_GPU:\n      requiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;\n      if (!allocator->IsIntegratedGpu() || (preferredFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {\n         preferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n      }\n      break;\n   case VMA_MEMORY_USAGE_GPU_TO_CPU:\n      requiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;\n      preferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT;\n      break;\n   case VMA_MEMORY_USAGE_CPU_COPY:\n      notPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;\n      break;\n   case VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED:\n      requiredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;\n      break;\n   default:\n      VMA_ASSERT(0);\n      break;\n   }\n\n   *pMemoryTypeIndex = UINT32_MAX;\n   uint32_t minCost = UINT32_MAX;\n   for (uint32_t memTypeIndex = 0, memTypeBit = 1;\n        memTypeIndex < allocator->GetMemoryTypeCount();\n        ++memTypeIndex, memTypeBit <<= 1) {\n      // This memory type is acceptable according to memoryTypeBits bitmask.\n      if ((memTypeBit & memoryTypeBits) != 0) {\n         const VkMemoryPropertyFlags currFlags =\n            allocator->m_MemProps.memoryTypes[memTypeIndex].propertyFlags;\n         // This memory type contains requiredFlags.\n         if ((requiredFlags & ~currFlags) == 0) {\n            // Calculate cost as number of bits from preferredFlags not present in this memory type.\n            uint32_t currCost = VmaCountBitsSet(preferredFlags & ~currFlags) +\n               VmaCountBitsSet(currFlags & notPreferredFlags);\n            // Remember memory type with lowest cost.\n            if (currCost < minCost) {\n               *pMemoryTypeIndex = memTypeIndex;\n               if (currCost == 0) {\n                  return VK_SUCCESS;\n               }\n               minCost = currCost;\n            }\n         }\n      }\n   }\n   return (*pMemoryTypeIndex != UINT32_MAX) ? VK_SUCCESS : VK_ERROR_FEATURE_NOT_PRESENT;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo(\n   VmaAllocator allocator,\n   const VkBufferCreateInfo* pBufferCreateInfo,\n   const VmaAllocationCreateInfo* pAllocationCreateInfo,\n   uint32_t* pMemoryTypeIndex)\n{\n   VMA_ASSERT(allocator != VK_NULL_HANDLE);\n   VMA_ASSERT(pBufferCreateInfo != VMA_NULL);\n   VMA_ASSERT(pAllocationCreateInfo != VMA_NULL);\n   VMA_ASSERT(pMemoryTypeIndex != VMA_NULL);\n\n   const VkDevice hDev = allocator->m_hDevice;\n   VkBuffer hBuffer = VK_NULL_HANDLE;\n   VkResult res = allocator->GetVulkanFunctions().vkCreateBuffer(\n      hDev, pBufferCreateInfo, allocator->GetAllocationCallbacks(), &hBuffer);\n   if (res == VK_SUCCESS) {\n      VkMemoryRequirements memReq = {};\n      allocator->GetVulkanFunctions().vkGetBufferMemoryRequirements(\n         hDev, hBuffer, &memReq);\n\n      res = vmaFindMemoryTypeIndex(\n         allocator,\n         memReq.memoryTypeBits,\n         pAllocationCreateInfo,\n         pMemoryTypeIndex);\n\n      allocator->GetVulkanFunctions().vkDestroyBuffer(\n         hDev, hBuffer, allocator->GetAllocationCallbacks());\n   }\n   return res;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo(\n   VmaAllocator allocator,\n   const VkImageCreateInfo* pImageCreateInfo,\n   const VmaAllocationCreateInfo* pAllocationCreateInfo,\n   uint32_t* pMemoryTypeIndex)\n{\n   VMA_ASSERT(allocator != VK_NULL_HANDLE);\n   VMA_ASSERT(pImageCreateInfo != VMA_NULL);\n   VMA_ASSERT(pAllocationCreateInfo != VMA_NULL);\n   VMA_ASSERT(pMemoryTypeIndex != VMA_NULL);\n\n   const VkDevice hDev = allocator->m_hDevice;\n   VkImage hImage = VK_NULL_HANDLE;\n   VkResult res = allocator->GetVulkanFunctions().vkCreateImage(\n      hDev, pImageCreateInfo, allocator->GetAllocationCallbacks(), &hImage);\n   if (res == VK_SUCCESS) {\n      VkMemoryRequirements memReq = {};\n      allocator->GetVulkanFunctions().vkGetImageMemoryRequirements(\n         hDev, hImage, &memReq);\n\n      res = vmaFindMemoryTypeIndex(\n         allocator,\n         memReq.memoryTypeBits,\n         pAllocationCreateInfo,\n         pMemoryTypeIndex);\n\n      allocator->GetVulkanFunctions().vkDestroyImage(\n         hDev, hImage, allocator->GetAllocationCallbacks());\n   }\n   return res;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool(\n   VmaAllocator allocator,\n   const VmaPoolCreateInfo* pCreateInfo,\n   VmaPool* pPool)\n{\n   VMA_ASSERT(allocator && pCreateInfo && pPool);\n\n   VMA_DEBUG_LOG(\"vmaCreatePool\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      VkResult res = allocator->CreatePool(pCreateInfo, pPool);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordCreatePool(allocator->GetCurrentFrameIndex(), *pCreateInfo, *pPool);\n   }\n#endif\n\n   return res;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool(\n   VmaAllocator allocator,\n   VmaPool pool)\n{\n   VMA_ASSERT(allocator);\n\n   if (pool == VK_NULL_HANDLE) {\n      return;\n   }\n\n   VMA_DEBUG_LOG(\"vmaDestroyPool\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordDestroyPool(allocator->GetCurrentFrameIndex(), pool);\n      }\n#endif\n\n   allocator->DestroyPool(pool);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStats(\n   VmaAllocator allocator,\n   VmaPool pool,\n   VmaPoolStats* pPoolStats)\n{\n   VMA_ASSERT(allocator && pool && pPoolStats);\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      allocator->GetPoolStats(pool, pPoolStats);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaMakePoolAllocationsLost(\n   VmaAllocator allocator,\n   VmaPool pool,\n   size_t* pLostAllocationCount)\n{\n   VMA_ASSERT(allocator && pool);\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordMakePoolAllocationsLost(allocator->GetCurrentFrameIndex(), pool);\n      }\n#endif\n\n   allocator->MakePoolAllocationsLost(pool, pLostAllocationCount);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool)\n{\n   VMA_ASSERT(allocator && pool);\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      VMA_DEBUG_LOG(\"vmaCheckPoolCorruption\");\n\n   return allocator->CheckPoolCorruption(pool);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName(\n   VmaAllocator allocator,\n   VmaPool pool,\n   const char** ppName)\n{\n   VMA_ASSERT(allocator && pool);\n\n   VMA_DEBUG_LOG(\"vmaGetPoolName\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      * ppName = pool->GetName();\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName(\n   VmaAllocator allocator,\n   VmaPool pool,\n   const char* pName)\n{\n   VMA_ASSERT(allocator && pool);\n\n   VMA_DEBUG_LOG(\"vmaSetPoolName\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      pool->SetName(pName);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordSetPoolName(allocator->GetCurrentFrameIndex(), pool, pName);\n   }\n#endif\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory(\n   VmaAllocator allocator,\n   const VkMemoryRequirements* pVkMemoryRequirements,\n   const VmaAllocationCreateInfo* pCreateInfo,\n   VmaAllocation* pAllocation,\n   VmaAllocationInfo* pAllocationInfo)\n{\n   VMA_ASSERT(allocator && pVkMemoryRequirements && pCreateInfo && pAllocation);\n\n   VMA_DEBUG_LOG(\"vmaAllocateMemory\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      VkResult result = allocator->AllocateMemory(\n         *pVkMemoryRequirements,\n         false, // requiresDedicatedAllocation\n         false, // prefersDedicatedAllocation\n         VK_NULL_HANDLE, // dedicatedBuffer\n         VK_NULL_HANDLE, // dedicatedImage\n         *pCreateInfo,\n         VMA_SUBALLOCATION_TYPE_UNKNOWN,\n         1, // allocationCount\n         pAllocation);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordAllocateMemory(\n         allocator->GetCurrentFrameIndex(),\n         *pVkMemoryRequirements,\n         *pCreateInfo,\n         *pAllocation);\n   }\n#endif\n\n   if (pAllocationInfo != VMA_NULL && result == VK_SUCCESS) {\n      allocator->GetAllocationInfo(*pAllocation, pAllocationInfo);\n   }\n\n   return result;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages(\n   VmaAllocator allocator,\n   const VkMemoryRequirements* pVkMemoryRequirements,\n   const VmaAllocationCreateInfo* pCreateInfo,\n   size_t allocationCount,\n   VmaAllocation* pAllocations,\n   VmaAllocationInfo* pAllocationInfo)\n{\n   if (allocationCount == 0) {\n      return VK_SUCCESS;\n   }\n\n   VMA_ASSERT(allocator && pVkMemoryRequirements && pCreateInfo && pAllocations);\n\n   VMA_DEBUG_LOG(\"vmaAllocateMemoryPages\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      VkResult result = allocator->AllocateMemory(\n         *pVkMemoryRequirements,\n         false, // requiresDedicatedAllocation\n         false, // prefersDedicatedAllocation\n         VK_NULL_HANDLE, // dedicatedBuffer\n         VK_NULL_HANDLE, // dedicatedImage\n         *pCreateInfo,\n         VMA_SUBALLOCATION_TYPE_UNKNOWN,\n         allocationCount,\n         pAllocations);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordAllocateMemoryPages(\n         allocator->GetCurrentFrameIndex(),\n         *pVkMemoryRequirements,\n         *pCreateInfo,\n         (uint64_t)allocationCount,\n         pAllocations);\n   }\n#endif\n\n   if (pAllocationInfo != VMA_NULL && result == VK_SUCCESS) {\n      for (size_t i = 0; i < allocationCount; ++i) {\n         allocator->GetAllocationInfo(pAllocations[i], pAllocationInfo + i);\n      }\n   }\n\n   return result;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer(\n   VmaAllocator allocator,\n   VkBuffer buffer,\n   const VmaAllocationCreateInfo* pCreateInfo,\n   VmaAllocation* pAllocation,\n   VmaAllocationInfo* pAllocationInfo)\n{\n   VMA_ASSERT(allocator && buffer != VK_NULL_HANDLE && pCreateInfo && pAllocation);\n\n   VMA_DEBUG_LOG(\"vmaAllocateMemoryForBuffer\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      VkMemoryRequirements vkMemReq = {};\n   bool requiresDedicatedAllocation = false;\n   bool prefersDedicatedAllocation = false;\n   allocator->GetBufferMemoryRequirements(buffer, vkMemReq,\n                                          requiresDedicatedAllocation,\n                                          prefersDedicatedAllocation);\n\n   VkResult result = allocator->AllocateMemory(\n      vkMemReq,\n      requiresDedicatedAllocation,\n      prefersDedicatedAllocation,\n      buffer, // dedicatedBuffer\n      VK_NULL_HANDLE, // dedicatedImage\n      *pCreateInfo,\n      VMA_SUBALLOCATION_TYPE_BUFFER,\n      1, // allocationCount\n      pAllocation);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordAllocateMemoryForBuffer(\n         allocator->GetCurrentFrameIndex(),\n         vkMemReq,\n         requiresDedicatedAllocation,\n         prefersDedicatedAllocation,\n         *pCreateInfo,\n         *pAllocation);\n   }\n#endif\n\n   if (pAllocationInfo && result == VK_SUCCESS) {\n      allocator->GetAllocationInfo(*pAllocation, pAllocationInfo);\n   }\n\n   return result;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage(\n   VmaAllocator allocator,\n   VkImage image,\n   const VmaAllocationCreateInfo* pCreateInfo,\n   VmaAllocation* pAllocation,\n   VmaAllocationInfo* pAllocationInfo)\n{\n   VMA_ASSERT(allocator && image != VK_NULL_HANDLE && pCreateInfo && pAllocation);\n\n   VMA_DEBUG_LOG(\"vmaAllocateMemoryForImage\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      VkMemoryRequirements vkMemReq = {};\n   bool requiresDedicatedAllocation = false;\n   bool prefersDedicatedAllocation = false;\n   allocator->GetImageMemoryRequirements(image, vkMemReq,\n                                         requiresDedicatedAllocation, prefersDedicatedAllocation);\n\n   VkResult result = allocator->AllocateMemory(\n      vkMemReq,\n      requiresDedicatedAllocation,\n      prefersDedicatedAllocation,\n      VK_NULL_HANDLE, // dedicatedBuffer\n      image, // dedicatedImage\n      *pCreateInfo,\n      VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN,\n      1, // allocationCount\n      pAllocation);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordAllocateMemoryForImage(\n         allocator->GetCurrentFrameIndex(),\n         vkMemReq,\n         requiresDedicatedAllocation,\n         prefersDedicatedAllocation,\n         *pCreateInfo,\n         *pAllocation);\n   }\n#endif\n\n   if (pAllocationInfo && result == VK_SUCCESS) {\n      allocator->GetAllocationInfo(*pAllocation, pAllocationInfo);\n   }\n\n   return result;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory(\n   VmaAllocator allocator,\n   VmaAllocation allocation)\n{\n   VMA_ASSERT(allocator);\n\n   if (allocation == VK_NULL_HANDLE) {\n      return;\n   }\n\n   VMA_DEBUG_LOG(\"vmaFreeMemory\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordFreeMemory(\n            allocator->GetCurrentFrameIndex(),\n            allocation);\n      }\n#endif\n\n   allocator->FreeMemory(\n      1, // allocationCount\n      &allocation);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages(\n   VmaAllocator allocator,\n   size_t allocationCount,\n   VmaAllocation* pAllocations)\n{\n   if (allocationCount == 0) {\n      return;\n   }\n\n   VMA_ASSERT(allocator);\n\n   VMA_DEBUG_LOG(\"vmaFreeMemoryPages\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordFreeMemoryPages(\n            allocator->GetCurrentFrameIndex(),\n            (uint64_t)allocationCount,\n            pAllocations);\n      }\n#endif\n\n   allocator->FreeMemory(allocationCount, pAllocations);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaResizeAllocation(\n   VmaAllocator allocator,\n   VmaAllocation allocation,\n   VkDeviceSize newSize)\n{\n   VMA_ASSERT(allocator && allocation);\n\n   VMA_DEBUG_LOG(\"vmaResizeAllocation\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      return allocator->ResizeAllocation(allocation, newSize);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo(\n   VmaAllocator allocator,\n   VmaAllocation allocation,\n   VmaAllocationInfo* pAllocationInfo)\n{\n   VMA_ASSERT(allocator && allocation && pAllocationInfo);\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordGetAllocationInfo(\n            allocator->GetCurrentFrameIndex(),\n            allocation);\n      }\n#endif\n\n   allocator->GetAllocationInfo(allocation, pAllocationInfo);\n}\n\nVMA_CALL_PRE VkBool32 VMA_CALL_POST vmaTouchAllocation(\n   VmaAllocator allocator,\n   VmaAllocation allocation)\n{\n   VMA_ASSERT(allocator && allocation);\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordTouchAllocation(\n            allocator->GetCurrentFrameIndex(),\n            allocation);\n      }\n#endif\n\n   return allocator->TouchAllocation(allocation);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData(\n   VmaAllocator allocator,\n   VmaAllocation allocation,\n   void* pUserData)\n{\n   VMA_ASSERT(allocator && allocation);\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      allocation->SetUserData(allocator, pUserData);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordSetAllocationUserData(\n         allocator->GetCurrentFrameIndex(),\n         allocation,\n         pUserData);\n   }\n#endif\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaCreateLostAllocation(\n   VmaAllocator allocator,\n   VmaAllocation* pAllocation)\n{\n   VMA_ASSERT(allocator && pAllocation);\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK;\n\n   allocator->CreateLostAllocation(pAllocation);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordCreateLostAllocation(\n         allocator->GetCurrentFrameIndex(),\n         *pAllocation);\n   }\n#endif\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory(\n   VmaAllocator allocator,\n   VmaAllocation allocation,\n   void** ppData)\n{\n   VMA_ASSERT(allocator && allocation && ppData);\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      VkResult res = allocator->Map(allocation, ppData);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordMapMemory(\n         allocator->GetCurrentFrameIndex(),\n         allocation);\n   }\n#endif\n\n   return res;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory(\n   VmaAllocator allocator,\n   VmaAllocation allocation)\n{\n   VMA_ASSERT(allocator && allocation);\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordUnmapMemory(\n            allocator->GetCurrentFrameIndex(),\n            allocation);\n      }\n#endif\n\n   allocator->Unmap(allocation);\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size)\n{\n   VMA_ASSERT(allocator && allocation);\n\n   VMA_DEBUG_LOG(\"vmaFlushAllocation\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_FLUSH);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordFlushAllocation(\n         allocator->GetCurrentFrameIndex(),\n         allocation, offset, size);\n   }\n#endif\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size)\n{\n   VMA_ASSERT(allocator && allocation);\n\n   VMA_DEBUG_LOG(\"vmaInvalidateAllocation\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_INVALIDATE);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordInvalidateAllocation(\n         allocator->GetCurrentFrameIndex(),\n         allocation, offset, size);\n   }\n#endif\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits)\n{\n   VMA_ASSERT(allocator);\n\n   VMA_DEBUG_LOG(\"vmaCheckCorruption\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      return allocator->CheckCorruption(memoryTypeBits);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragment(\n   VmaAllocator allocator,\n   VmaAllocation* pAllocations,\n   size_t allocationCount,\n   VkBool32* pAllocationsChanged,\n   const VmaDefragmentationInfo* pDefragmentationInfo,\n   VmaDefragmentationStats* pDefragmentationStats)\n{\n   // Deprecated interface, reimplemented using new one.\n\n   VmaDefragmentationInfo2 info2 = {};\n   info2.allocationCount = (uint32_t)allocationCount;\n   info2.pAllocations = pAllocations;\n   info2.pAllocationsChanged = pAllocationsChanged;\n   if (pDefragmentationInfo != VMA_NULL) {\n      info2.maxCpuAllocationsToMove = pDefragmentationInfo->maxAllocationsToMove;\n      info2.maxCpuBytesToMove = pDefragmentationInfo->maxBytesToMove;\n   } else {\n      info2.maxCpuAllocationsToMove = UINT32_MAX;\n      info2.maxCpuBytesToMove = VK_WHOLE_SIZE;\n   }\n   // info2.flags, maxGpuAllocationsToMove, maxGpuBytesToMove, commandBuffer deliberately left zero.\n\n   VmaDefragmentationContext ctx;\n   VkResult res = vmaDefragmentationBegin(allocator, &info2, pDefragmentationStats, &ctx);\n   if (res == VK_NOT_READY) {\n      res = vmaDefragmentationEnd(allocator, ctx);\n   }\n   return res;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationBegin(\n   VmaAllocator allocator,\n   const VmaDefragmentationInfo2* pInfo,\n   VmaDefragmentationStats* pStats,\n   VmaDefragmentationContext* pContext)\n{\n   VMA_ASSERT(allocator && pInfo && pContext);\n\n   // Degenerate case: Nothing to defragment.\n   if (pInfo->allocationCount == 0 && pInfo->poolCount == 0) {\n      return VK_SUCCESS;\n   }\n\n   VMA_ASSERT(pInfo->allocationCount == 0 || pInfo->pAllocations != VMA_NULL);\n   VMA_ASSERT(pInfo->poolCount == 0 || pInfo->pPools != VMA_NULL);\n   VMA_HEAVY_ASSERT(VmaValidatePointerArray(pInfo->allocationCount, pInfo->pAllocations));\n   VMA_HEAVY_ASSERT(VmaValidatePointerArray(pInfo->poolCount, pInfo->pPools));\n\n   VMA_DEBUG_LOG(\"vmaDefragmentationBegin\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      VkResult res = allocator->DefragmentationBegin(*pInfo, pStats, pContext);\n\n#if VMA_RECORDING_ENABLED\n   if (allocator->GetRecorder() != VMA_NULL) {\n      allocator->GetRecorder()->RecordDefragmentationBegin(\n         allocator->GetCurrentFrameIndex(), *pInfo, *pContext);\n   }\n#endif\n\n   return res;\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaDefragmentationEnd(\n   VmaAllocator allocator,\n   VmaDefragmentationContext context)\n{\n   VMA_ASSERT(allocator);\n\n   VMA_DEBUG_LOG(\"vmaDefragmentationEnd\");\n\n   if (context != VK_NULL_HANDLE) {\n      VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n#if VMA_RECORDING_ENABLED\n         if (allocator->GetRecorder() != VMA_NULL) {\n            allocator->GetRecorder()->RecordDefragmentationEnd(\n               allocator->GetCurrentFrameIndex(), context);\n         }\n#endif\n\n      return allocator->DefragmentationEnd(context);\n   } else {\n      return VK_SUCCESS;\n   }\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory(\n   VmaAllocator allocator,\n   VmaAllocation allocation,\n   VkBuffer buffer)\n{\n   VMA_ASSERT(allocator && allocation && buffer);\n\n   VMA_DEBUG_LOG(\"vmaBindBufferMemory\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      return allocator->BindBufferMemory(allocation, 0, buffer, VMA_NULL);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2(\n   VmaAllocator allocator,\n   VmaAllocation allocation,\n   VkDeviceSize allocationLocalOffset,\n   VkBuffer buffer,\n   const void* pNext)\n{\n   VMA_ASSERT(allocator && allocation && buffer);\n\n   VMA_DEBUG_LOG(\"vmaBindBufferMemory2\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      return allocator->BindBufferMemory(allocation, allocationLocalOffset, buffer, pNext);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory(\n   VmaAllocator allocator,\n   VmaAllocation allocation,\n   VkImage image)\n{\n   VMA_ASSERT(allocator && allocation && image);\n\n   VMA_DEBUG_LOG(\"vmaBindImageMemory\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      return allocator->BindImageMemory(allocation, 0, image, VMA_NULL);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2(\n   VmaAllocator allocator,\n   VmaAllocation allocation,\n   VkDeviceSize allocationLocalOffset,\n   VkImage image,\n   const void* pNext)\n{\n   VMA_ASSERT(allocator && allocation && image);\n\n   VMA_DEBUG_LOG(\"vmaBindImageMemory2\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      return allocator->BindImageMemory(allocation, allocationLocalOffset, image, pNext);\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(\n   VmaAllocator allocator,\n   const VkBufferCreateInfo* pBufferCreateInfo,\n   const VmaAllocationCreateInfo* pAllocationCreateInfo,\n   VkBuffer* pBuffer,\n   VmaAllocation* pAllocation,\n   VmaAllocationInfo* pAllocationInfo)\n{\n   VMA_ASSERT(allocator && pBufferCreateInfo && pAllocationCreateInfo && pBuffer && pAllocation);\n\n   if (pBufferCreateInfo->size == 0) {\n      return VK_ERROR_VALIDATION_FAILED_EXT;\n   }\n\n   VMA_DEBUG_LOG(\"vmaCreateBuffer\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      * pBuffer = VK_NULL_HANDLE;\n   *pAllocation = VK_NULL_HANDLE;\n\n   // 1. Create VkBuffer.\n   VkResult res = (*allocator->GetVulkanFunctions().vkCreateBuffer)(\n      allocator->m_hDevice,\n      pBufferCreateInfo,\n      allocator->GetAllocationCallbacks(),\n      pBuffer);\n   if (res >= 0) {\n      // 2. vkGetBufferMemoryRequirements.\n      VkMemoryRequirements vkMemReq = {};\n      bool requiresDedicatedAllocation = false;\n      bool prefersDedicatedAllocation = false;\n      allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq,\n                                             requiresDedicatedAllocation, prefersDedicatedAllocation);\n\n      // Make sure alignment requirements for specific buffer usages reported\n      // in Physical Device Properties are included in alignment reported by memory requirements.\n      if ((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0) {\n         VMA_ASSERT(vkMemReq.alignment %\n                    allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0);\n      }\n      if ((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0) {\n         VMA_ASSERT(vkMemReq.alignment %\n                    allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0);\n      }\n      if ((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0) {\n         VMA_ASSERT(vkMemReq.alignment %\n                    allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0);\n      }\n\n      // 3. Allocate memory using allocator.\n      res = allocator->AllocateMemory(\n         vkMemReq,\n         requiresDedicatedAllocation,\n         prefersDedicatedAllocation,\n         *pBuffer, // dedicatedBuffer\n         VK_NULL_HANDLE, // dedicatedImage\n         *pAllocationCreateInfo,\n         VMA_SUBALLOCATION_TYPE_BUFFER,\n         1, // allocationCount\n         pAllocation);\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordCreateBuffer(\n            allocator->GetCurrentFrameIndex(),\n            *pBufferCreateInfo,\n            *pAllocationCreateInfo,\n            *pAllocation);\n      }\n#endif\n\n      if (res >= 0) {\n         // 3. Bind buffer with memory.\n         if ((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) {\n            res = allocator->BindBufferMemory(*pAllocation, 0, *pBuffer, VMA_NULL);\n         }\n         if (res >= 0) {\n            // All steps succeeded.\n#if VMA_STATS_STRING_ENABLED\n            (*pAllocation)->InitBufferImageUsage(pBufferCreateInfo->usage);\n#endif\n            if (pAllocationInfo != VMA_NULL) {\n               allocator->GetAllocationInfo(*pAllocation, pAllocationInfo);\n            }\n\n            return VK_SUCCESS;\n         }\n         allocator->FreeMemory(\n            1, // allocationCount\n            pAllocation);\n         *pAllocation = VK_NULL_HANDLE;\n         (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks());\n         *pBuffer = VK_NULL_HANDLE;\n         return res;\n      }\n      (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks());\n      *pBuffer = VK_NULL_HANDLE;\n      return res;\n   }\n   return res;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer(\n   VmaAllocator allocator,\n   VkBuffer buffer,\n   VmaAllocation allocation)\n{\n   VMA_ASSERT(allocator);\n\n   if (buffer == VK_NULL_HANDLE && allocation == VK_NULL_HANDLE) {\n      return;\n   }\n\n   VMA_DEBUG_LOG(\"vmaDestroyBuffer\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordDestroyBuffer(\n            allocator->GetCurrentFrameIndex(),\n            allocation);\n      }\n#endif\n\n   if (buffer != VK_NULL_HANDLE) {\n      (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, buffer, allocator->GetAllocationCallbacks());\n   }\n\n   if (allocation != VK_NULL_HANDLE) {\n      allocator->FreeMemory(\n         1, // allocationCount\n         &allocation);\n   }\n}\n\nVMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage(\n   VmaAllocator allocator,\n   const VkImageCreateInfo* pImageCreateInfo,\n   const VmaAllocationCreateInfo* pAllocationCreateInfo,\n   VkImage* pImage,\n   VmaAllocation* pAllocation,\n   VmaAllocationInfo* pAllocationInfo)\n{\n   VMA_ASSERT(allocator && pImageCreateInfo && pAllocationCreateInfo && pImage && pAllocation);\n\n   if (pImageCreateInfo->extent.width == 0 ||\n       pImageCreateInfo->extent.height == 0 ||\n       pImageCreateInfo->extent.depth == 0 ||\n       pImageCreateInfo->mipLevels == 0 ||\n       pImageCreateInfo->arrayLayers == 0) {\n      return VK_ERROR_VALIDATION_FAILED_EXT;\n   }\n\n   VMA_DEBUG_LOG(\"vmaCreateImage\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n      * pImage = VK_NULL_HANDLE;\n   *pAllocation = VK_NULL_HANDLE;\n\n   // 1. Create VkImage.\n   VkResult res = (*allocator->GetVulkanFunctions().vkCreateImage)(\n      allocator->m_hDevice,\n      pImageCreateInfo,\n      allocator->GetAllocationCallbacks(),\n      pImage);\n   if (res >= 0) {\n      VmaSuballocationType suballocType = pImageCreateInfo->tiling == VK_IMAGE_TILING_OPTIMAL ?\n         VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL :\n         VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR;\n\n      // 2. Allocate memory using allocator.\n      VkMemoryRequirements vkMemReq = {};\n      bool requiresDedicatedAllocation = false;\n      bool prefersDedicatedAllocation = false;\n      allocator->GetImageMemoryRequirements(*pImage, vkMemReq,\n                                            requiresDedicatedAllocation, prefersDedicatedAllocation);\n\n      res = allocator->AllocateMemory(\n         vkMemReq,\n         requiresDedicatedAllocation,\n         prefersDedicatedAllocation,\n         VK_NULL_HANDLE, // dedicatedBuffer\n         *pImage, // dedicatedImage\n         *pAllocationCreateInfo,\n         suballocType,\n         1, // allocationCount\n         pAllocation);\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordCreateImage(\n            allocator->GetCurrentFrameIndex(),\n            *pImageCreateInfo,\n            *pAllocationCreateInfo,\n            *pAllocation);\n      }\n#endif\n\n      if (res >= 0) {\n         // 3. Bind image with memory.\n         if ((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) {\n            res = allocator->BindImageMemory(*pAllocation, 0, *pImage, VMA_NULL);\n         }\n         if (res >= 0) {\n            // All steps succeeded.\n#if VMA_STATS_STRING_ENABLED\n            (*pAllocation)->InitBufferImageUsage(pImageCreateInfo->usage);\n#endif\n            if (pAllocationInfo != VMA_NULL) {\n               allocator->GetAllocationInfo(*pAllocation, pAllocationInfo);\n            }\n\n            return VK_SUCCESS;\n         }\n         allocator->FreeMemory(\n            1, // allocationCount\n            pAllocation);\n         *pAllocation = VK_NULL_HANDLE;\n         (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, *pImage, allocator->GetAllocationCallbacks());\n         *pImage = VK_NULL_HANDLE;\n         return res;\n      }\n      (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, *pImage, allocator->GetAllocationCallbacks());\n      *pImage = VK_NULL_HANDLE;\n      return res;\n   }\n   return res;\n}\n\nVMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage(\n   VmaAllocator allocator,\n   VkImage image,\n   VmaAllocation allocation)\n{\n   VMA_ASSERT(allocator);\n\n   if (image == VK_NULL_HANDLE && allocation == VK_NULL_HANDLE) {\n      return;\n   }\n\n   VMA_DEBUG_LOG(\"vmaDestroyImage\");\n\n   VMA_DEBUG_GLOBAL_MUTEX_LOCK\n\n#if VMA_RECORDING_ENABLED\n      if (allocator->GetRecorder() != VMA_NULL) {\n         allocator->GetRecorder()->RecordDestroyImage(\n            allocator->GetCurrentFrameIndex(),\n            allocation);\n      }\n#endif\n\n   if (image != VK_NULL_HANDLE) {\n      (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, image, allocator->GetAllocationCallbacks());\n   }\n   if (allocation != VK_NULL_HANDLE) {\n      allocator->FreeMemory(\n         1, // allocationCount\n         &allocation);\n   }\n}\n\n#endif // #ifdef VMA_IMPLEMENTATION\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vk_mem_alloc_decaf.h",
    "content": "#ifdef _MSC_VER\n#  pragma warning(push)\n#  pragma warning(disable:4127)\n#  pragma warning(disable:4189)\n#endif\n\n#include \"vk_mem_alloc.h\"\n\n#ifdef _MSC_VER\n#  pragma warning(pop)\n#endif\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_attribbuffers.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\nnamespace vulkan\n{\n\nVertexBufferDesc\nDriver::getAttribBufferDesc(uint32_t bufferIndex)\n{\n   VertexBufferDesc desc;\n\n   auto resourceOffset = (latte::SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 + bufferIndex) * 7;\n   auto sq_vtx_constant_word0 = getRegister<latte::SQ_VTX_CONSTANT_WORD0_N>(latte::Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset);\n   auto sq_vtx_constant_word1 = getRegister<latte::SQ_VTX_CONSTANT_WORD1_N>(latte::Register::SQ_RESOURCE_WORD1_0 + 4 * resourceOffset);\n   auto sq_vtx_constant_word2 = getRegister<latte::SQ_VTX_CONSTANT_WORD2_N>(latte::Register::SQ_RESOURCE_WORD2_0 + 4 * resourceOffset);\n   auto sq_vtx_constant_word6 = getRegister<latte::SQ_VTX_CONSTANT_WORD6_N>(latte::Register::SQ_RESOURCE_WORD6_0 + 4 * resourceOffset);\n\n   decaf_check(sq_vtx_constant_word2.BASE_ADDRESS_HI() == 0);\n\n   if (sq_vtx_constant_word6.TYPE() != latte::SQ_TEX_VTX_TYPE::VALID_BUFFER) {\n      desc.baseAddress = phys_addr(0);\n      desc.size = 0;\n      desc.stride = 0;\n      return desc;\n   }\n\n   desc.baseAddress = phys_addr(sq_vtx_constant_word0.BASE_ADDRESS());\n   desc.size = sq_vtx_constant_word1.SIZE() + 1;\n   desc.stride = sq_vtx_constant_word2.STRIDE();\n\n   return desc;\n}\n\nbool\nDriver::checkCurrentAttribBuffers()\n{\n   // Must have a vertex shader to describe what to upload...\n   decaf_check(mCurrentDraw->vertexShader);\n\n   for (auto i = 0u; i < latte::MaxAttribBuffers; ++i) {\n      if (!mCurrentDraw->vertexShader->shader.meta.attribBuffers[i].isUsed) {\n         mCurrentDraw->attribBuffers[i] = nullptr;\n         continue;\n      }\n\n      auto desc = getAttribBufferDesc(i);\n\n      if (!desc.baseAddress || !desc.size) {\n         // If the vertex shader takes this as an input, but there is no\n         // actual buffer specified, we should fail our draw entirely.\n         return false;\n      }\n\n      auto &currentAttribBuffer = mCurrentDraw->attribBuffers[i];\n      if (currentAttribBuffer &&\n          currentAttribBuffer->address == desc.baseAddress &&\n          currentAttribBuffer->size == desc.size) {\n         // If we are already set to the correct attribute buffer, we only need\n         // to check that the buffer has not changed since we last looked.\n         continue;\n      }\n\n      auto memCache = getDataMemCache(desc.baseAddress, desc.size);\n\n      transitionMemCache(memCache, ResourceUsage::AttributeBuffer);\n\n      currentAttribBuffer = memCache;\n   }\n\n   return true;\n}\n\nvoid\nDriver::bindAttribBuffers()\n{\n   for (auto i = 0u; i < latte::MaxAttribBuffers; ++i) {\n      auto buffer = mCurrentDraw->attribBuffers[i];\n      if (buffer) {\n         mActiveCommandBuffer.bindVertexBuffers(i, { buffer->buffer }, { 0 });\n      }\n   }\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_debug.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\nnamespace vulkan\n{\n\ngpu::GraphicsDriverType\nDriver::type()\n{\n   return gpu::GraphicsDriverType::Vulkan;\n}\n\ngpu::GraphicsDriverDebugInfo *\nDriver::getDebugInfo()\n{\n   // TODO: This is not thread safe wrt updateDebuggerInfo, maybe it should\n   // be some sort of double buffered thing with a std atomic pointer to the\n   // latest filled out one\n   return &mDebugInfo;\n}\n\nvoid\nDriver::updateDebuggerInfo()\n{\n   auto averageFrameTime = std::chrono::duration_cast<duration_ms>(mAverageFrameTime).count();\n   mDebugInfo.averageFrameTimeMS = averageFrameTime;\n\n   if (averageFrameTime > 0.0) {\n      mDebugInfo.averageFps = 1000.0 / mDebugInfo.averageFrameTimeMS;\n   } else {\n      mDebugInfo.averageFps = 0.0;\n   }\n\n   mDebugInfo.numVertexShaders = mVertexShaders.size();\n   mDebugInfo.numGeometryShaders = mVertexShaders.size();\n   mDebugInfo.numPixelShaders = mPixelShaders.size();\n   mDebugInfo.numRenderPasses = mRenderPasses.size();\n   mDebugInfo.numPipelines = mPipelines.size();\n   mDebugInfo.numSamplers = mSamplers.size();\n   mDebugInfo.numSurfaces = mSurfaceGroups.size();\n   mDebugInfo.numDataBuffers = mMemCaches.size();\n}\n\nvoid\nDriver::insertVkMarker(const std::string& text)\n{\n   if (mVkDynLoader.vkCmdDebugMarkerInsertEXT) {\n      vk::DebugMarkerMarkerInfoEXT testMarker;\n      testMarker.pMarkerName = text.c_str();\n      mActiveCommandBuffer.debugMarkerInsertEXT(testMarker, mVkDynLoader);\n   }\n}\n\ntemplate <typename ObjType>\nstatic void\n_setVkObjectName(vk::Device device, ObjType object, vk::DebugReportObjectTypeEXT type, const char *name, const vk::DispatchLoaderDynamic& dispatch)\n{\n   if (dispatch.vkDebugMarkerSetObjectNameEXT) {\n      vk::DebugMarkerObjectNameInfoEXT nameInfo;\n      nameInfo.object = *reinterpret_cast<uint64_t*>(&object);\n      nameInfo.objectType = type;\n      nameInfo.pObjectName = name;\n      device.debugMarkerSetObjectNameEXT(nameInfo, dispatch);\n   }\n}\n\nvoid\nDriver::setVkObjectName(VkBuffer object, const char *name)\n{\n   _setVkObjectName(mDevice, object, vk::DebugReportObjectTypeEXT::eBuffer, name, mVkDynLoader);\n}\n\nvoid\nDriver::setVkObjectName(VkSampler object, const char *name)\n{\n   _setVkObjectName(mDevice, object, vk::DebugReportObjectTypeEXT::eSampler, name, mVkDynLoader);\n}\n\nvoid\nDriver::setVkObjectName(VkImage object, const char *name)\n{\n   _setVkObjectName(mDevice, object, vk::DebugReportObjectTypeEXT::eImage, name, mVkDynLoader);\n}\n\nvoid\nDriver::setVkObjectName(VkImageView object, const char *name)\n{\n   _setVkObjectName(mDevice, object, vk::DebugReportObjectTypeEXT::eImageView, name, mVkDynLoader);\n}\n\nvoid\nDriver::setVkObjectName(VkShaderModule object, const char *name)\n{\n   _setVkObjectName(mDevice, object, vk::DebugReportObjectTypeEXT::eShaderModule, name, mVkDynLoader);\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_descs.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n#include \"latte/latte_formats.h\"\n#include \"latte/latte_constants.h\"\n#include \"latte/latte_registers_pa.h\"\n#include \"latte/latte_registers_sq.h\"\n#include \"latte/latte_registers_vgt.h\"\n\n#include <common/datahash.h>\n#include <common/vulkan_hpp.h>\n#include <libcpu/be2_struct.h>\n\nnamespace vulkan\n{\n\nstruct RenderPassObject;\nstruct VertexShaderObject;\nstruct GeometryShaderObject;\nstruct PixelShaderObject;\nstruct RectStubShaderObject;\n\n#pragma pack(push, 1)\n\n// This is a specialized class for holding floats inside our hashable structures.\n// This allows us to pull float data out of our registers and store it in our\n// description structures without breaking the hashability required by DataHash.\nstruct hashableFloat\n{\n   static_assert(sizeof(float) == sizeof(uint32_t),\n                 \"hashable float implementation requires uint32_t and float to be the same size\");\n\n   hashableFloat()\n      : value(0)\n   {\n   }\n\n   hashableFloat(float val)\n      : value(*reinterpret_cast<uint32_t*>(&val))\n   {\n   }\n\n   operator float() const\n   {\n      return *reinterpret_cast<const float*>(&value);\n   }\n\n   float & operator=(const float & rhs)\n   {\n      return *reinterpret_cast<float*>(&value) = rhs;\n   }\n\n   uint32_t value;\n};\n\nstruct ColorBufferDesc\n{\n   uint32_t base256b;\n   uint32_t pitchTileMax;\n   uint32_t sliceTileMax;\n   latte::CB_FORMAT format;\n   latte::CB_NUMBER_TYPE numberType;\n   latte::BUFFER_ARRAY_MODE arrayMode;\n   uint32_t sliceStart;\n   uint32_t sliceEnd;\n};\n\nstruct DepthStencilBufferDesc\n{\n   uint32_t base256b;\n   uint32_t pitchTileMax;\n   uint32_t sliceTileMax;\n   latte::DB_FORMAT format;\n   latte::BUFFER_ARRAY_MODE arrayMode;\n   uint32_t sliceStart;\n   uint32_t sliceEnd;\n};\n\nstruct VertexBufferDesc\n{\n   phys_addr baseAddress;\n   uint32_t size;\n   uint32_t stride;\n};\n\nstruct SurfaceDesc\n{\n   // BaseAddress is a uint32 rather than a phys_addr as it actually\n   // encodes information other than just the base address (swizzle).\n\n   uint32_t baseAddress;\n   uint32_t pitch;\n   uint32_t width;\n   uint32_t height;\n   uint32_t depth;\n   uint32_t samples;\n   latte::SQ_TEX_DIM dim;\n   latte::SQ_TILE_TYPE tileType;\n   latte::SQ_TILE_MODE tileMode;\n   latte::SurfaceFormat format;\n\n   inline uint32_t calcAlignedBaseAddress() const\n   {\n      if (tileMode >= latte::SQ_TILE_MODE::TILED_2D_THIN1) {\n         return baseAddress & ~(0x800 - 1);\n      } else {\n         return baseAddress & ~(0x100 - 1);\n      }\n   }\n\n   inline uint32_t calcSwizzle() const\n   {\n      return baseAddress & 0x00000F00;\n   }\n\n   inline DataHash hash(bool byCompat = false) const\n   {\n      // tileMode and swizzle are intentionally omited as they\n      // do not affect data placement or size, but only upload/downloads.\n      // It is possible that a tile-type switch may invalidate\n      // old data though...\n\n      // TODO: Handle tiling changes, major memory reuse issues...\n\n      struct\n      {\n         uint32_t address;\n         uint32_t format;\n         uint32_t dim;\n         uint32_t samples;\n         uint32_t tileType;\n         uint32_t tileMode;\n         uint32_t width;\n         uint32_t pitch;\n         uint32_t height;\n         uint32_t depth;\n      } _dataHash;\n      memset(&_dataHash, 0xFF, sizeof(_dataHash));\n\n      _dataHash.address = calcAlignedBaseAddress();\n      _dataHash.format = format;\n      _dataHash.samples = samples;\n      _dataHash.tileType = tileType;\n      _dataHash.tileMode = tileMode;\n\n      if (!byCompat) {\n         // TODO: Figure out if we can emulate 2D_ARRAY surfaces\n         // as 3D surfaces so we can get both kinds of views from\n         // them?\n         // Figure out which surfaces are compatible\n         // at a DIM level...  Basically, anything we\n         // can generate a view of.\n         switch (dim) {\n         case latte::SQ_TEX_DIM::DIM_3D:\n            _dataHash.dim = 3;\n            break;\n         case latte::SQ_TEX_DIM::DIM_2D:\n         case latte::SQ_TEX_DIM::DIM_2D_MSAA:\n         case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n         case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n         case latte::SQ_TEX_DIM::DIM_CUBEMAP:\n            _dataHash.dim = 2;\n            break;\n         case latte::SQ_TEX_DIM::DIM_1D:\n         case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n            _dataHash.dim = 1;\n            break;\n         default:\n            decaf_abort(fmt::format(\"Unsupported texture dim: {}\", dim));\n         }\n         //_dataHash.dim = dim;\n      }\n\n      switch (dim) {\n      case latte::SQ_TEX_DIM::DIM_3D:\n         if (!byCompat) {\n            _dataHash.depth = depth;\n         }\n         // fallthrough\n      case latte::SQ_TEX_DIM::DIM_2D:\n      case latte::SQ_TEX_DIM::DIM_2D_MSAA:\n      case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n      case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n      case latte::SQ_TEX_DIM::DIM_CUBEMAP:\n         _dataHash.height = height;\n         // fallthrough\n      case latte::SQ_TEX_DIM::DIM_1D:\n      case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n         _dataHash.pitch = pitch;\n         if (!byCompat) {\n            _dataHash.width = width;\n         }\n         break;\n      default:\n         decaf_abort(fmt::format(\"Unsupported texture dim: {}\", dim));\n      }\n\n      return DataHash {}.write(_dataHash);\n   }\n};\n\nstruct SurfaceViewDesc\n{\n   SurfaceDesc surfaceDesc;\n   uint32_t sliceStart;\n   uint32_t sliceEnd;\n   std::array<latte::SQ_SEL, 4> channels;\n\n   inline DataHash hash() const\n   {\n      struct\n      {\n         uint32_t sliceStart;\n         uint32_t sliceEnd;\n         std::array<latte::SQ_SEL, 4> channels;\n      } _dataHash;\n      memset(&_dataHash, 0xFF, sizeof(_dataHash));\n\n      _dataHash.sliceStart = sliceStart;\n      _dataHash.sliceEnd = sliceEnd;\n      _dataHash.channels = channels;\n\n      return surfaceDesc.hash().write(_dataHash);\n   }\n};\n\nstruct FramebufferDesc\n{\n   std::array<ColorBufferDesc, latte::MaxRenderTargets> colorTargets;\n   DepthStencilBufferDesc depthTarget;\n\n   inline DataHash hash() const\n   {\n      return DataHash {}.write(*this);\n   }\n};\n\nstruct SamplerDesc\n{\n   latte::SQ_TEX_SAMPLER_WORD0_N texSamplerWord0;\n   latte::SQ_TEX_SAMPLER_WORD1_N texSamplerWord1;\n   latte::SQ_TEX_SAMPLER_WORD2_N texSamplerWord2;\n\n   inline DataHash hash() const\n   {\n      return DataHash {}.write(*this);\n   }\n};\n\nstruct SwapChainDesc\n{\n   phys_addr baseAddress;\n   uint32_t width;\n   uint32_t height;\n};\n\nstruct RenderPassDesc\n{\n   struct ColorTarget\n   {\n      bool isEnabled;\n      latte::CB_FORMAT format;\n      latte::CB_NUMBER_TYPE numberType;\n      uint32_t samples;\n   };\n\n   struct DepthStencilTarget\n   {\n      bool isEnabled;\n      latte::DB_FORMAT format;\n   };\n\n   std::array<ColorTarget, 8> colorTargets;\n   DepthStencilTarget depthTarget;\n\n   inline DataHash hash() const\n   {\n      return DataHash {}.write(*this);\n   }\n};\n\nstruct PipelineLayoutDesc\n{\n   std::array<bool, latte::MaxSamplers> vsSamplerUsed;\n   std::array<bool, latte::MaxTextures> vsTextureUsed;\n   std::array<bool, latte::MaxUniformBlocks> vsBufferUsed;\n   std::array<bool, latte::MaxSamplers> gsSamplerUsed;\n   std::array<bool, latte::MaxTextures> gsTextureUsed;\n   std::array<bool, latte::MaxUniformBlocks> gsBufferUsed;\n   std::array<bool, latte::MaxSamplers> psSamplerUsed;\n   std::array<bool, latte::MaxTextures> psTextureUsed;\n   std::array<bool, latte::MaxUniformBlocks> psBufferUsed;\n\n   uint32_t numDescriptors = 0;\n\n   inline DataHash hash() const\n   {\n      return DataHash {}.write(*this);\n   }\n};\n\nstruct PipelineDesc\n{\n   RenderPassObject *renderPass;\n   VertexShaderObject *vertexShader;\n   GeometryShaderObject *geometryShader;\n   PixelShaderObject *pixelShader;\n   RectStubShaderObject *rectStubShader;\n\n   std::array<uint32_t, latte::MaxAttribBuffers> attribBufferStride;\n   std::array<uint32_t, 2> attribBufferDivisor;\n   latte::VGT_DI_PRIMITIVE_TYPE primitiveType;\n   bool primitiveResetEnabled;\n   uint32_t primitiveResetIndex;\n   bool dx9Consts;\n\n   struct StencilOpState\n   {\n      latte::REF_FUNC compareFunc;\n      latte::DB_STENCIL_FUNC failOp;\n      latte::DB_STENCIL_FUNC zPassOp;\n      latte::DB_STENCIL_FUNC zFailOp;\n      uint8_t ref;\n      uint8_t mask;\n      uint8_t writeMask;\n   };\n   bool stencilEnable;\n   StencilOpState stencilFront;\n   StencilOpState stencilBack;\n\n   bool zEnable;\n   bool zWriteEnable;\n   latte::REF_FUNC zFunc;\n   bool rasteriserDisable;\n   uint32_t lineWidth;\n   latte::PA_FACE paFace;\n   bool cullFront;\n   bool cullBack;\n   uint32_t polyPType;\n   bool polyBiasEnabled;\n   hashableFloat polyBiasClamp;\n   hashableFloat polyBiasOffset;\n   hashableFloat polyBiasScale;\n   bool zclipDisabled;\n\n   uint32_t rop3;\n\n   struct BlendControl\n   {\n      uint8_t targetMask;\n      bool blendingEnabled;\n      bool opacityWeight;\n      latte::CB_COMB_FUNC colorCombFcn;\n      latte::CB_BLEND_FUNC colorSrcBlend;\n      latte::CB_BLEND_FUNC colorDstBlend;\n      latte::CB_COMB_FUNC alphaCombFcn;\n      latte::CB_BLEND_FUNC alphaSrcBlend;\n      latte::CB_BLEND_FUNC alphaDstBlend;\n   };\n\n   std::array<BlendControl, latte::MaxRenderTargets> cbBlendControls;\n   std::array<hashableFloat, 4> cbBlendConstants;\n\n   latte::REF_FUNC alphaFunc;\n   hashableFloat alphaRef;\n\n   inline DataHash hash() const\n   {\n      return DataHash {}.write(*this);\n   }\n};\n\nstruct StreamOutBufferDesc\n{\n   phys_addr baseAddress;\n   uint32_t size;\n   uint32_t stride;\n};\n\n#pragma pack(pop)\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_display.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"vulkan_displayshaders.h\"\n\n#include \"gpu_config.h\"\n#include \"gpu7_displaylayout.h\"\n\n\n#include <common/log.h>\n#include <common/platform_debug.h>\n#include <common/strutils.h>\n#include <cstring>\n#include <fmt/format.h>\n#include <iterator>\n#include <optional>\n#include <string_view>\n#include <tuple>\n\nnamespace vulkan\n{\n\nstatic VKAPI_ATTR VkBool32 VKAPI_CALL\ndebugMessageCallback(VkDebugReportFlagsEXT flags,\n                     VkDebugReportObjectTypeEXT objectType,\n                     uint64_t object,\n                     size_t location,\n                     int32_t messageCode,\n                     const char* pLayerPrefix,\n                     const char* pMessage,\n                     void *pUserData)\n{\n   // Consider doing additional debugger behaviours based on various attributes.\n   // This is to improve the chances that we don't accidentally miss incorrect\n   // Vulkan-specific behaviours.\n\n   // We keep track of known issues so we can log slightly differently, and also\n   // avoid breaking to the debugger here.\n   bool isKnownIssue = false;\n\n   // There is currently a bug where the validation layers report issues with using\n   // VkPipelineColorBlendStateCreateInfo in spite of our legal usage of it.\n   // TODO: Remove this once validation correctly supports VkPipelineColorBlendAdvancedStateCreateInfoEXT\n   if (strstr(pMessage, \"VkPipelineColorBlendStateCreateInfo-pNext\") != nullptr) {\n      static uint64_t seenAdvancedBlendWarning = 0;\n      if (seenAdvancedBlendWarning++) {\n         return VK_FALSE;\n      }\n      isKnownIssue = true;\n   }\n\n   // We intentionally mirror the behaviour of GPU7 where a shader writes to an attachement which is not bound.\n   // The validation layer gives us a warning, but we should ignore it for this known case.\n   if (strstr(pMessage, \"Shader-OutputNotConsumed\") != nullptr) {\n      static uint64_t seenOutputNotConsumed = 0;\n      if (seenOutputNotConsumed++) {\n         return VK_FALSE;\n      }\n      isKnownIssue = true;\n   }\n\n   // Some games rebind the same texture as an input and output at the same time.  This\n   // is technically illegal, even for GPU7, but it works... so...\n   if (strstr(pMessage, \"VkDescriptorImageInfo-imageLayout\") != nullptr) {\n      static uint64_t seenImageLayoutError = 0;\n      if (seenImageLayoutError++) {\n         return VK_FALSE;\n      }\n      isKnownIssue = true;\n   }\n   if (strstr(pMessage, \"DrawState-DescriptorSetNotUpdated\") != nullptr) {\n      static uint64_t seenDescriptorSetError = 0;\n      if (seenDescriptorSetError++) {\n         return VK_FALSE;\n      }\n      isKnownIssue = true;\n   }\n\n   // There is an issue with the validation layers and handling of transform feedback.\n   if (strstr(pMessage, \"VUID-vkCmdPipelineBarrier-pMemoryBarriers-01184\") != nullptr) {\n      static uint64_t seenXfbBarrier01184Error = 0;\n      if (seenXfbBarrier01184Error++) {\n         return VK_FALSE;\n      }\n      isKnownIssue = true;\n   }\n   if (strstr(pMessage, \"VUID-vkCmdPipelineBarrier-pMemoryBarriers-01185\") != nullptr) {\n      static uint64_t seenXfbBarrier01185Error = 0;\n      if (seenXfbBarrier01185Error++) {\n         return VK_FALSE;\n      }\n      isKnownIssue = true;\n   }\n\n   // Write this message to our normal logging\n   if (!isKnownIssue) {\n      gLog->warn(\"Vulkan Debug Report: {}, {}, {}, {}, {}, {}, {}\",\n                 vk::to_string(static_cast<vk::DebugReportFlagsEXT>(flags)),\n                 vk::to_string(static_cast<vk::DebugReportObjectTypeEXT>(objectType)),\n                 object,\n                 location,\n                 messageCode,\n                 pLayerPrefix,\n                 pMessage);\n   } else {\n      gLog->warn(\"Vulkan Debug Report (Known Case): {}\", pMessage);\n   }\n\n   if (!isKnownIssue) {\n      platform::debugLog(fmt::format(\"vk-dbg: {}\\n\", pMessage));\n   } else {\n      platform::debugLog(fmt::format(\"vk-dbg-ignored: {}\\n\", pMessage));\n   }\n\n   // We should break to the debugger on unexpected situations.\n   if (flags == VK_DEBUG_REPORT_WARNING_BIT_EXT || flags == VK_DEBUG_REPORT_ERROR_BIT_EXT) {\n      if (!isKnownIssue) {\n         platform::debugBreak();\n      }\n   }\n\n   return VK_FALSE;\n}\n\nstatic void\nregisterDebugCallback(vk::Instance &instance,\n                      vk::DispatchLoaderDynamic &dispatchLoaderDynamic,\n                      void *pUserData)\n{\n   if (!dispatchLoaderDynamic.vkCreateDebugReportCallbackEXT) {\n      return;\n   }\n\n   auto dbgReportDesc = vk::DebugReportCallbackCreateInfoEXT { };\n   dbgReportDesc.flags =\n      vk::DebugReportFlagBitsEXT::eDebug |\n      vk::DebugReportFlagBitsEXT::eWarning |\n      vk::DebugReportFlagBitsEXT::eError |\n      vk::DebugReportFlagBitsEXT::ePerformanceWarning;\n   dbgReportDesc.pfnCallback = debugMessageCallback;\n   dbgReportDesc.pUserData = pUserData;\n   instance.createDebugReportCallbackEXT(dbgReportDesc, nullptr, dispatchLoaderDynamic);\n}\n\nstatic bool\ngetWindowSystemExtensions(gpu::WindowSystemType wsiType, std::vector<const char*> &extensions)\n{\n   /*\n    VK_USE_PLATFORM_ANDROID_KHR - Android\n    VK_USE_PLATFORM_MIR_KHR - Mir\n    */\n   switch (wsiType)\n   {\n#if defined(VK_USE_PLATFORM_WIN32_KHR)\n   case gpu::WindowSystemType::Windows:\n      extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);\n      extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);\n      break;\n#endif\n#if defined(VK_USE_PLATFORM_XLIB_KHR)\n   case gpu::WindowSystemType::X11:\n      extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);\n      extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);\n      break;\n#endif\n#if defined(VK_USE_PLATFORM_XCB_KHR)\n   case gpu::WindowSystemType::Xcb:\n      extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);\n      extensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);\n      break;\n#endif\n#if defined(VK_USE_PLATFORM_WAYLAND_KHR)\n   case gpu::WindowSystemType::Wayland:\n      extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);\n      extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);\n      break;\n#endif\n#if defined(VK_USE_PLATFORM_MACOS_MVK)\n   case gpu::WindowSystemType::Cocoa:\n      extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);\n      extensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);\n      break;\n#endif\n   default:\n      return false;\n   }\n\n   return true;\n}\n\nstatic vk::Instance\ncreateVulkanInstance(const gpu::WindowSystemInfo &wsi)\n{\n   auto appInfo =\n      vk::ApplicationInfo {\n         \"Decaf\",\n         VK_MAKE_VERSION(1, 0, 0),\n         \"DecafSDL\",\n         VK_MAKE_VERSION(1, 0, 0),\n         VK_API_VERSION_1_0\n      };\n\n   std::vector<const char*> layers =\n   {\n   };\n\n   std::vector<const char*> extensions =\n   {\n       VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME\n   };\n\n   if (gpu::config()->debug.debug_enabled) {\n      layers.push_back(\"VK_LAYER_KHRONOS_validation\");\n      extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);\n   }\n\n   if (!getWindowSystemExtensions(wsi.type, extensions)) {\n      gLog->error(\"createVulkanInstance: Failed to get window system extensions for type {}\", wsi.type);\n      return { };\n   }\n\n   if (!extensions.empty()) {\n      fmt::memory_buffer msg;\n      fmt::format_to(std::back_inserter(msg), \"Creating instance with extensions:\");\n      for (auto ext : extensions) {\n         fmt::format_to(std::back_inserter(msg), \" {}\", ext);\n      }\n      gLog->debug({ msg.data(), msg.size() });\n   }\n\n   if (!layers.empty()) {\n      fmt::memory_buffer msg;\n      fmt::format_to(std::back_inserter(msg), \"Creating instance with layers:\");\n      for (auto layer : layers) {\n         fmt::format_to(std::back_inserter(msg), \" {}\", layer);\n      }\n      gLog->debug({ msg.data(), msg.size() });\n   }\n\n   auto instanceCreateInfo = vk::InstanceCreateInfo { };\n   instanceCreateInfo.pApplicationInfo = &appInfo;\n   instanceCreateInfo.enabledLayerCount = static_cast<uint32_t>(layers.size());\n   instanceCreateInfo.ppEnabledLayerNames = layers.data();\n   instanceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());\n   instanceCreateInfo.ppEnabledExtensionNames = extensions.data();\n   return vk::createInstance(instanceCreateInfo);\n}\n\nstatic vk::PhysicalDevice\nchoosePhysicalDevice(vk::Instance &instance)\n{\n   auto physicalDevices = instance.enumeratePhysicalDevices();\n   return physicalDevices[0];\n}\n\nstatic vk::SurfaceKHR\ncreateVulkanSurface(vk::Instance &instance, const gpu::WindowSystemInfo &wsi)\n{\n#if defined(VK_USE_PLATFORM_WIN32_KHR)\n   if (wsi.type == gpu::WindowSystemType::Windows) {\n      auto surfaceCreateInfo = vk::Win32SurfaceCreateInfoKHR { };\n      surfaceCreateInfo.hinstance = nullptr;\n      surfaceCreateInfo.hwnd = reinterpret_cast<HWND>(wsi.renderSurface);\n      return instance.createWin32SurfaceKHR(surfaceCreateInfo);\n   }\n#endif\n\n#if defined(VK_USE_PLATFORM_XCB_KHR)\n   if (wsi.type == gpu::WindowSystemType::Xcb) {\n      auto surfaceCreateInfo = vk::XcbSurfaceCreateInfoKHR { };\n      surfaceCreateInfo.connection = static_cast<xcb_connection_t *>(wsi.displayConnection);\n      surfaceCreateInfo.window = static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(wsi.renderSurface));\n      return instance.createXcbSurfaceKHR(surfaceCreateInfo);\n   }\n#endif\n\n#if defined(VK_USE_PLATFORM_XLIB_KHR)\n   if (wsi.type == gpu::WindowSystemType::X11) {\n      auto surfaceCreateInfo = vk::XlibSurfaceCreateInfoKHR { };\n      surfaceCreateInfo.dpy = static_cast<Display *>(wsi.displayConnection);\n      surfaceCreateInfo.window = reinterpret_cast<Window>(wsi.renderSurface);\n      return instance.createXlibSurfaceKHR(surfaceCreateInfo);\n   }\n#endif\n\n#if defined(VK_USE_PLATFORM_WAYLAND_KHR)\n   if (wsi.type == gpu::WindowSystemType::Wayland) {\n      auto surfaceCreateInfo = vk::WaylandSurfaceCreateInfoKHR { };\n      surfaceCreateInfo.display = static_cast<wl_display *>(wsi.displayConnection);\n      surfaceCreateInfo.surface = static_cast<wl_surface *>(wsi.renderSurface);\n      return instance.createWaylandSurfaceKHR(surfaceCreateInfo);\n   }\n#endif\n\n#if defined(VK_USE_PLATFORM_MACOS_MVK)\n   if (wsi.type == gpu::WindowSystemType::Cocoa) {\n      auto surfaceCreateInfo = vk::MacOSSurfaceCreateInfoMVK { };\n      surfaceCreateInfo.pView = static_cast<const void *>(wsi.renderSurface);\n      return instance.createMacOSSurfaceMVK(surfaceCreateInfo);\n   }\n#endif\n\n   return { };\n}\n\nstatic vk::Format\nchooseSurfaceFormat(vk::PhysicalDevice &physicalDevice, vk::SurfaceKHR &surface)\n{\n   auto formats = physicalDevice.getSurfaceFormatsKHR(surface);\n   auto selected = formats[0];\n   for (auto &format : formats) {\n      switch (format.format) {\n      case vk::Format::eR8G8B8A8Srgb:\n      case vk::Format::eB8G8R8A8Srgb:\n         selected = format;\n         break;\n      }\n   }\n\n   return selected.format;\n}\n\nstatic std::tuple<vk::Device, uint32_t, vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceTransformFeedbackFeaturesEXT>\ncreateDevice(vk::PhysicalDevice &physicalDevice, vk::SurfaceKHR &surface)\n{\n   std::vector<const char*> deviceLayers =\n   {\n   };\n\n   std::vector<const char *> requiredExtensions = {\n      VK_KHR_SWAPCHAIN_EXTENSION_NAME,\n      VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME,\n      VK_KHR_MAINTENANCE1_EXTENSION_NAME,\n      VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,\n      VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME,\n   };\n   std::vector<const char *> missingRequiredExtensions = {};\n\n   std::vector<const char *> optionalExtensions = {\n      VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME,\n      VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME,\n   };\n   std::vector<const char *> missingOptionalExtensions = {};\n\n   std::vector<const char *> deviceExtensions = requiredExtensions;\n\n   if (gpu::config()->debug.debug_enabled) {\n      deviceLayers.push_back(\"VK_LAYER_KHRONOS_validation\");\n      optionalExtensions.push_back(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);\n   }\n\n   auto supportedDeviceExtensions = physicalDevice.enumerateDeviceExtensionProperties();\n   for (auto &name : requiredExtensions) {\n      auto hasExtension = false;\n      for (auto &ext : supportedDeviceExtensions) {\n         if (iequals(name, ext.extensionName.data())) {\n            hasExtension = true;\n            break;\n         }\n      }\n\n      if (!hasExtension) {\n         missingRequiredExtensions.push_back(name);\n      }\n   }\n\n   for (auto name : optionalExtensions) {\n      auto hasExtension = false;\n      for (auto &ext : supportedDeviceExtensions) {\n         if (iequals(name, ext.extensionName.data())) {\n            hasExtension = true;\n            break;\n         }\n      }\n\n      if (hasExtension) {\n         deviceExtensions.push_back(name);\n      } else {\n         missingOptionalExtensions.push_back(name);\n      }\n   }\n\n   if (!missingRequiredExtensions.empty() || !missingOptionalExtensions.empty()) {\n      fmt::memory_buffer msg;\n      fmt::format_to(std::back_inserter(msg), \"Not all Vulkan {} extensions supported:\\n\", !missingRequiredExtensions.empty() ? \"optional\" : \"required\");\n      fmt::format_to(std::back_inserter(msg), \"  Required:\\n\");\n      for (auto name : requiredExtensions) {\n         fmt::format_to(std::back_inserter(msg), \"  - {}: {}\\n\",\n            name,\n            std::find(missingRequiredExtensions.begin(), missingRequiredExtensions.end(), name) == missingRequiredExtensions.end());\n      }\n\n      fmt::format_to(std::back_inserter(msg), \"  Optional:\\n\");\n      for (auto name : optionalExtensions) {\n         fmt::format_to(std::back_inserter(msg), \"  - {}: {}\\n\",\n            name,\n            std::find(missingOptionalExtensions.begin(), missingOptionalExtensions.end(), name) == missingOptionalExtensions.end());\n      }\n\n      if (!missingRequiredExtensions.empty()) {\n         gLog->error(std::string_view{ msg.data(), msg.size() });\n      } else {\n         gLog->warn(std::string_view{ msg.data(), msg.size() });\n      }\n   }\n\n   auto features = physicalDevice.getFeatures2<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceTransformFeedbackFeaturesEXT>();\n   auto supportedFeatures = features.get<vk::PhysicalDeviceFeatures2>();\n   auto supportedFeaturesTransformFeedback = features.get<vk::PhysicalDeviceTransformFeedbackFeaturesEXT>();\n\n   auto hasRequiredFeatures =\n      supportedFeatures.features.depthClamp &&\n      supportedFeatures.features.textureCompressionBC &&\n      supportedFeatures.features.independentBlend &&\n      supportedFeatures.features.fillModeNonSolid &&\n      supportedFeatures.features.samplerAnisotropy;\n\n   auto hasOptionalFeatures =\n      supportedFeatures.features.geometryShader &&\n      supportedFeatures.features.wideLines &&\n      supportedFeatures.features.logicOp &&\n      supportedFeaturesTransformFeedback.transformFeedback &&\n      supportedFeaturesTransformFeedback.geometryStreams;\n\n   if (!hasRequiredFeatures || !hasOptionalFeatures) {\n      fmt::memory_buffer msg;\n      fmt::format_to(std::back_inserter(msg), \"Not all Vulkan {} features supported:\\n\", hasRequiredFeatures ? \"optional\" : \"required\");\n      fmt::format_to(std::back_inserter(msg), \"  Required:\\n\");\n      fmt::format_to(std::back_inserter(msg), \"  - depthClamp: {}\\n\", supportedFeatures.features.depthClamp);\n      fmt::format_to(std::back_inserter(msg), \"  - textureCompressionBC: {}\\n\", supportedFeatures.features.textureCompressionBC);\n      fmt::format_to(std::back_inserter(msg), \"  - independentBlend: {}\\n\", supportedFeatures.features.independentBlend);\n      fmt::format_to(std::back_inserter(msg), \"  - fillModeNonSolid: {}\\n\", supportedFeatures.features.fillModeNonSolid);\n      fmt::format_to(std::back_inserter(msg), \"  - samplerAnisotropy: {}\\n\", supportedFeatures.features.samplerAnisotropy);\n      fmt::format_to(std::back_inserter(msg), \"  Optional:\\n\");\n      fmt::format_to(std::back_inserter(msg), \"  - geometryShader: {}\\n\", supportedFeatures.features.geometryShader);\n      fmt::format_to(std::back_inserter(msg), \"  - wideLines: {}\\n\", supportedFeatures.features.wideLines);\n      fmt::format_to(std::back_inserter(msg), \"  - logicOp: {}\\n\", supportedFeatures.features.logicOp);\n      fmt::format_to(std::back_inserter(msg), \"  - transformFeedback: {}\\n\", supportedFeaturesTransformFeedback.transformFeedback);\n      fmt::format_to(std::back_inserter(msg), \"  - geometryStreams: {}\\n\", supportedFeaturesTransformFeedback.geometryStreams);\n\n      if (!hasRequiredFeatures) {\n         gLog->error(std::string_view { msg.data(), msg.size() });\n      } else {\n         gLog->warn(std::string_view{ msg.data(), msg.size() });\n      }\n   }\n\n   if (!missingRequiredExtensions.empty() || !hasRequiredFeatures) {\n      return {};\n   }\n\n   auto queueFamilyProps = physicalDevice.getQueueFamilyProperties();\n   auto queueFamilyIndex = uint32_t { 0 };\n   for (; queueFamilyIndex < queueFamilyProps.size(); ++queueFamilyIndex) {\n      auto &qfp = queueFamilyProps[queueFamilyIndex];\n\n      if (!physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, surface)) {\n         continue;\n      }\n\n      if (!(qfp.queueFlags & (vk::QueueFlagBits::eGraphics |\n                              vk::QueueFlagBits::eTransfer |\n                              vk::QueueFlagBits::eCompute))) {\n         continue;\n      }\n\n      break;\n   }\n\n   if (queueFamilyIndex >= queueFamilyProps.size()) {\n      gLog->error(\"Could not find compatible vulkan queue family\");\n      return { };\n   }\n\n   auto queuePriorities = std::array<float, 1> { 0.0f };\n   auto deviceQueueCreateInfo =\n      vk::DeviceQueueCreateInfo {\n         vk::DeviceQueueCreateFlags { },\n         queueFamilyIndex,\n         static_cast<uint32_t>(queuePriorities.size()),\n         queuePriorities.data()\n      };\n\n   auto createDeviceChain = vk::StructureChain<vk::DeviceCreateInfo, vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceTransformFeedbackFeaturesEXT> { };\n   auto &deviceCreateInfo = createDeviceChain.get<vk::DeviceCreateInfo>();\n   deviceCreateInfo.queueCreateInfoCount = 1;\n   deviceCreateInfo.pQueueCreateInfos = &deviceQueueCreateInfo;\n   deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(deviceLayers.size());\n   deviceCreateInfo.ppEnabledLayerNames = deviceLayers.data();\n   deviceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());\n   deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();\n\n   auto &deviceCreateFeatures = createDeviceChain.get<vk::PhysicalDeviceFeatures2>();\n   deviceCreateFeatures.features.depthClamp = true;\n   deviceCreateFeatures.features.textureCompressionBC = true;\n   deviceCreateFeatures.features.independentBlend = true;\n   deviceCreateFeatures.features.fillModeNonSolid = true;\n   deviceCreateFeatures.features.samplerAnisotropy = true;\n   deviceCreateFeatures.features.geometryShader = supportedFeatures.features.geometryShader;\n   deviceCreateFeatures.features.wideLines = supportedFeatures.features.wideLines;\n   deviceCreateFeatures.features.logicOp = supportedFeatures.features.logicOp;\n\n   auto &deviceCreateFeaturesTransformFeedback = createDeviceChain.get<vk::PhysicalDeviceTransformFeedbackFeaturesEXT>();\n   deviceCreateFeaturesTransformFeedback.transformFeedback = supportedFeaturesTransformFeedback.transformFeedback;\n   deviceCreateFeaturesTransformFeedback.geometryStreams = supportedFeaturesTransformFeedback.geometryStreams;\n\n   auto device = physicalDevice.createDevice(deviceCreateInfo);\n   return { device, queueFamilyIndex, supportedFeatures, supportedFeaturesTransformFeedback };\n}\n\nstatic bool\ncreateRenderPass(VulkanDisplayPipeline &displayPipeline,\n                 vk::Device &device)\n{\n   // Create our render pass that targets this attachement\n   auto colorAttachmentDesc = vk::AttachmentDescription { };\n   colorAttachmentDesc.format = displayPipeline.windowSurfaceFormat;\n   colorAttachmentDesc.samples = vk::SampleCountFlagBits::e1;\n   colorAttachmentDesc.loadOp = vk::AttachmentLoadOp::eClear;\n   colorAttachmentDesc.storeOp = vk::AttachmentStoreOp::eStore;\n   colorAttachmentDesc.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;\n   colorAttachmentDesc.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;\n   colorAttachmentDesc.initialLayout = vk::ImageLayout::eUndefined;\n   colorAttachmentDesc.finalLayout = vk::ImageLayout::ePresentSrcKHR;\n\n   auto colorAttachmentRef = vk::AttachmentReference { };\n   colorAttachmentRef.attachment = 0;\n   colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal;\n\n   auto genericSubpass = vk::SubpassDescription { };\n   genericSubpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;\n   genericSubpass.inputAttachmentCount = 0;\n   genericSubpass.pInputAttachments = nullptr;\n   genericSubpass.colorAttachmentCount = 1;\n   genericSubpass.pColorAttachments = &colorAttachmentRef;\n   genericSubpass.pResolveAttachments = 0;\n   genericSubpass.pDepthStencilAttachment = nullptr;\n   genericSubpass.preserveAttachmentCount = 0;\n   genericSubpass.pPreserveAttachments = nullptr;\n\n   auto renderPassDesc = vk::RenderPassCreateInfo { };\n   renderPassDesc.attachmentCount = 1;\n   renderPassDesc.pAttachments = &colorAttachmentDesc;\n   renderPassDesc.subpassCount = 1;\n   renderPassDesc.pSubpasses = &genericSubpass;\n   renderPassDesc.dependencyCount = 0;\n   renderPassDesc.pDependencies = nullptr;\n   displayPipeline.renderPass = device.createRenderPass(renderPassDesc);\n   return !!displayPipeline.renderPass;\n}\n\nstatic vk::PresentModeKHR\nchoosePresentMode(vk::PhysicalDevice &physicalDevice,\n                  vk::SurfaceKHR &surface)\n{\n   auto presentModes = physicalDevice.getSurfacePresentModesKHR(surface);\n   auto hasPresentMode = [&](vk::PresentModeKHR mode) {\n      return std::find(presentModes.begin(), presentModes.end(), mode) != presentModes.end();\n   };\n\n   if (hasPresentMode(vk::PresentModeKHR::eMailbox)) {\n      return vk::PresentModeKHR::eMailbox;\n   }\n\n   if (hasPresentMode(vk::PresentModeKHR::eImmediate)) {\n      return vk::PresentModeKHR::eImmediate;\n   }\n\n   if (hasPresentMode(vk::PresentModeKHR::eFifo)) {\n      return vk::PresentModeKHR::eFifo;\n   }\n\n   return presentModes[0];\n}\n\nstatic bool\ncreateSwapchain(VulkanDisplayPipeline &displayPipeline,\n                vk::PhysicalDevice &physicalDevice,\n                vk::Device &device)\n{\n   auto surfaceCaps = physicalDevice.getSurfaceCapabilitiesKHR(displayPipeline.windowSurface);\n   displayPipeline.swapchainExtents = surfaceCaps.currentExtent;\n\n   // Create the swap chain itself\n   auto swapchainCreateInfo = vk::SwapchainCreateInfoKHR { };\n   swapchainCreateInfo.surface = displayPipeline.windowSurface;\n   swapchainCreateInfo.minImageCount = surfaceCaps.minImageCount;\n   swapchainCreateInfo.imageFormat = displayPipeline.windowSurfaceFormat;\n   swapchainCreateInfo.imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear;\n   swapchainCreateInfo.imageExtent = displayPipeline.swapchainExtents;\n   swapchainCreateInfo.imageArrayLayers = 1;\n   swapchainCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc;\n   swapchainCreateInfo.imageSharingMode = vk::SharingMode::eExclusive;\n   swapchainCreateInfo.queueFamilyIndexCount = 0;\n   swapchainCreateInfo.pQueueFamilyIndices = nullptr;\n   swapchainCreateInfo.preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity;\n   swapchainCreateInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;\n   swapchainCreateInfo.presentMode = displayPipeline.presentMode;\n   swapchainCreateInfo.clipped = true;\n   swapchainCreateInfo.oldSwapchain = nullptr;\n   displayPipeline.swapchain = device.createSwapchainKHR(swapchainCreateInfo);\n\n   // Create our framebuffers\n   auto swapChainImages = device.getSwapchainImagesKHR(displayPipeline.swapchain);\n   displayPipeline.swapchainImageViews.resize(swapChainImages.size());\n   displayPipeline.framebuffers.resize(swapChainImages.size());\n\n   for (auto i = 0u; i < swapChainImages.size(); ++i) {\n      auto imageViewCreateInfo = vk::ImageViewCreateInfo { };\n      imageViewCreateInfo.image = swapChainImages[i];\n      imageViewCreateInfo.viewType = vk::ImageViewType::e2D;\n      imageViewCreateInfo.format = displayPipeline.windowSurfaceFormat;\n      imageViewCreateInfo.components = vk::ComponentMapping();\n      imageViewCreateInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;\n      imageViewCreateInfo.subresourceRange.baseMipLevel = 0;\n      imageViewCreateInfo.subresourceRange.levelCount = 1;\n      imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;\n      imageViewCreateInfo.subresourceRange.layerCount = 1;\n      displayPipeline.swapchainImageViews[i] = device.createImageView(imageViewCreateInfo);\n\n      auto framebufferCreateInfo = vk::FramebufferCreateInfo { };\n      framebufferCreateInfo.renderPass = displayPipeline.renderPass;\n      framebufferCreateInfo.attachmentCount = 1;\n      framebufferCreateInfo.pAttachments = &displayPipeline.swapchainImageViews[i];\n      framebufferCreateInfo.width = displayPipeline.swapchainExtents.width;\n      framebufferCreateInfo.height = displayPipeline.swapchainExtents.height;\n      framebufferCreateInfo.layers = 1;\n      displayPipeline.framebuffers[i] = device.createFramebuffer(framebufferCreateInfo);\n   }\n\n   return true;\n}\n\nstatic void\ndestroySwapchain(VulkanDisplayPipeline &displayPipeline,\n                 vk::Device &device)\n{\n   for (const auto &framebuffer : displayPipeline.framebuffers) {\n      device.destroyFramebuffer(framebuffer);\n   }\n   displayPipeline.framebuffers.clear();\n\n   for (const auto &imageView : displayPipeline.swapchainImageViews) {\n      device.destroyImageView(imageView);\n   }\n   displayPipeline.swapchainImageViews.clear();\n\n   device.destroySwapchainKHR(displayPipeline.swapchain);\n   displayPipeline.swapchain = vk::SwapchainKHR { };\n}\n\nstatic void\nrecreateSwapchain(VulkanDisplayPipeline &displayPipeline,\n                  vk::PhysicalDevice &physicalDevice,\n                  vk::Device &device)\n{\n   device.waitIdle();\n   destroySwapchain(displayPipeline, device);\n   createSwapchain(displayPipeline, physicalDevice, device);\n}\n\nstatic bool\ncreatePipelineLayout(VulkanDisplayPipeline &displayPipeline,\n                     vk::Device &device)\n{\n   auto samplerCreateInfo = vk::SamplerCreateInfo { };\n   samplerCreateInfo.magFilter = vk::Filter::eLinear;\n   samplerCreateInfo.minFilter = vk::Filter::eLinear;\n   samplerCreateInfo.mipmapMode = vk::SamplerMipmapMode::eNearest;\n   samplerCreateInfo.addressModeU = vk::SamplerAddressMode::eRepeat;\n   samplerCreateInfo.addressModeV = vk::SamplerAddressMode::eRepeat;\n   samplerCreateInfo.addressModeW = vk::SamplerAddressMode::eRepeat;\n   samplerCreateInfo.mipLodBias = 0.0f;\n   samplerCreateInfo.anisotropyEnable = false;\n   samplerCreateInfo.maxAnisotropy = 0.0f;\n   samplerCreateInfo.compareEnable = false;\n   samplerCreateInfo.compareOp = vk::CompareOp::eAlways;\n   samplerCreateInfo.minLod = 0.0f;\n   samplerCreateInfo.maxLod = 0.0f;\n   samplerCreateInfo.borderColor = vk::BorderColor::eFloatTransparentBlack;\n   samplerCreateInfo.unnormalizedCoordinates = false;\n   displayPipeline.trivialSampler = device.createSampler(samplerCreateInfo);\n\n   auto immutableSamplers = std::array<vk::Sampler, 1> {\n      displayPipeline.trivialSampler,\n   };\n\n   auto bindings = std::array<vk::DescriptorSetLayoutBinding, 2> {\n      vk::DescriptorSetLayoutBinding {\n         0, vk::DescriptorType::eSampler,\n         1, vk::ShaderStageFlagBits::eFragment,\n         immutableSamplers.data()\n      },\n      vk::DescriptorSetLayoutBinding {\n         1, vk::DescriptorType::eSampledImage,\n         1, vk::ShaderStageFlagBits::eFragment,\n         nullptr\n      },\n   };\n\n   auto descriptorSetLayoutCreateInfo = vk::DescriptorSetLayoutCreateInfo { };\n   descriptorSetLayoutCreateInfo.bindingCount = static_cast<uint32_t>(bindings.size());\n   descriptorSetLayoutCreateInfo.pBindings = bindings.data();\n   displayPipeline.descriptorSetLayout = device.createDescriptorSetLayout(descriptorSetLayoutCreateInfo);\n\n   auto layoutBindings = std::array<vk::DescriptorSetLayout, 1> { displayPipeline.descriptorSetLayout };\n   auto pipelineLayoutCreateInfo = vk::PipelineLayoutCreateInfo { };\n   pipelineLayoutCreateInfo.setLayoutCount = static_cast<uint32_t>(layoutBindings.size());\n   pipelineLayoutCreateInfo.pSetLayouts = layoutBindings.data();\n   pipelineLayoutCreateInfo.pushConstantRangeCount = 0;\n   pipelineLayoutCreateInfo.pPushConstantRanges = nullptr;\n   displayPipeline.pipelineLayout = device.createPipelineLayout(pipelineLayoutCreateInfo);\n   return true;\n}\n\nstatic bool\ncreateRenderPipeline(VulkanDisplayPipeline &displayPipeline,\n                     vk::Device &device)\n{\n   auto scanbufferVertBytesSize = sizeof(scanbufferVertBytes) / sizeof(scanbufferVertBytes[0]);\n   displayPipeline.vertexShader =\n      device.createShaderModule(\n         vk::ShaderModuleCreateInfo {\n            {},\n            scanbufferVertBytesSize,\n            reinterpret_cast<const uint32_t*>(scanbufferVertBytes)\n         });\n\n   auto scanbufferFragBytesSize = sizeof(scanbufferFragBytes) / sizeof(scanbufferFragBytes[0]);\n   displayPipeline.fragmentShader =\n      device.createShaderModule(\n         vk::ShaderModuleCreateInfo {\n            {},\n            scanbufferFragBytesSize,\n            reinterpret_cast<const uint32_t*>(scanbufferFragBytes)\n         });\n\n   auto shaderStages = std::array<vk::PipelineShaderStageCreateInfo, 2> {\n      vk::PipelineShaderStageCreateInfo {\n         {},\n         vk::ShaderStageFlagBits::eVertex,\n         displayPipeline.vertexShader,\n         \"main\",\n         nullptr,\n      },\n      vk::PipelineShaderStageCreateInfo {\n         {},\n         vk::ShaderStageFlagBits::eFragment,\n         displayPipeline.fragmentShader,\n         \"main\",\n         nullptr,\n      },\n   };\n\n   auto vtxBindings = std::array<vk::VertexInputBindingDescription, 1> {\n      vk::VertexInputBindingDescription { 0, 16, vk::VertexInputRate::eVertex },\n   };\n\n   auto vtxAttribs = std::array<vk::VertexInputAttributeDescription, 2> {\n      vk::VertexInputAttributeDescription { 0, 0, vk::Format::eR32G32Sfloat, 0 },\n      vk::VertexInputAttributeDescription { 1, 0, vk::Format::eR32G32Sfloat, 8 },\n   };\n\n   // Vertex input stage, we store all our vertices in the actual shaders\n   auto vertexInputInfo = vk::PipelineVertexInputStateCreateInfo { };\n   vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(vtxBindings.size());\n   vertexInputInfo.pVertexBindingDescriptions = vtxBindings.data();\n   vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(vtxAttribs.size());\n   vertexInputInfo.pVertexAttributeDescriptions = vtxAttribs.data();\n\n   auto inputAssembly = vk::PipelineInputAssemblyStateCreateInfo { };\n   inputAssembly.topology = vk::PrimitiveTopology::eTriangleList;\n   inputAssembly.primitiveRestartEnable = false;\n\n   auto viewport = vk::Viewport {\n      0.0f, 0.0f,\n      static_cast<float>(displayPipeline.swapchainExtents.width),\n      static_cast<float>(displayPipeline.swapchainExtents.height),\n      0.0f, 0.0f,\n   };\n   auto scissor = vk::Rect2D { { 0,0 }, displayPipeline.swapchainExtents };\n   auto viewportState = vk::PipelineViewportStateCreateInfo { };\n   viewportState.viewportCount = 1;\n   viewportState.pViewports = &viewport;\n   viewportState.scissorCount = 1;\n   viewportState.pScissors = &scissor;\n\n   auto rasterizer = vk::PipelineRasterizationStateCreateInfo { };\n   rasterizer.depthClampEnable = false;\n   rasterizer.rasterizerDiscardEnable = false;\n   rasterizer.polygonMode = vk::PolygonMode::eFill;\n   rasterizer.cullMode = vk::CullModeFlagBits::eNone;\n   rasterizer.frontFace = vk::FrontFace::eClockwise;\n   rasterizer.depthBiasEnable = false;\n   rasterizer.depthBiasConstantFactor = 0.0f;\n   rasterizer.depthBiasClamp = 0.0f;\n   rasterizer.depthBiasSlopeFactor = 0.0f;\n   rasterizer.lineWidth = 1.0f;\n\n   auto multisampling = vk::PipelineMultisampleStateCreateInfo { };\n   multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1;\n   multisampling.sampleShadingEnable = false;\n   multisampling.minSampleShading = 1.0f;\n   multisampling.pSampleMask = nullptr;\n   multisampling.alphaToCoverageEnable = false;\n   multisampling.alphaToOneEnable = false;\n\n   auto colorBlendAttachement0 = vk::PipelineColorBlendAttachmentState { };\n   colorBlendAttachement0.blendEnable = false;\n   colorBlendAttachement0.srcColorBlendFactor = vk::BlendFactor::eOne;\n   colorBlendAttachement0.dstColorBlendFactor = vk::BlendFactor::eZero;\n   colorBlendAttachement0.colorBlendOp = vk::BlendOp::eAdd;\n   colorBlendAttachement0.srcAlphaBlendFactor = vk::BlendFactor::eOne;\n   colorBlendAttachement0.dstAlphaBlendFactor = vk::BlendFactor::eZero;\n   colorBlendAttachement0.alphaBlendOp = vk::BlendOp::eAdd;\n   colorBlendAttachement0.colorWriteMask =\n      vk::ColorComponentFlagBits::eR |\n      vk::ColorComponentFlagBits::eG |\n      vk::ColorComponentFlagBits::eB |\n      vk::ColorComponentFlagBits::eA;\n\n   auto colorBlendAttachments = std::vector<vk::PipelineColorBlendAttachmentState> {\n      colorBlendAttachement0\n   };\n\n   auto colorBlendState = vk::PipelineColorBlendStateCreateInfo { };\n   colorBlendState.logicOpEnable = false;\n   colorBlendState.logicOp = vk::LogicOp::eCopy;\n   colorBlendState.attachmentCount = static_cast<uint32_t>(colorBlendAttachments.size());\n   colorBlendState.pAttachments = colorBlendAttachments.data();\n   colorBlendState.blendConstants[0] = 0.0f;\n   colorBlendState.blendConstants[1] = 0.0f;\n   colorBlendState.blendConstants[2] = 0.0f;\n   colorBlendState.blendConstants[3] = 0.0f;\n\n   auto dynamicStates = std::vector<vk::DynamicState> {\n      vk::DynamicState::eViewport,\n      vk::DynamicState::eScissor,\n   };\n\n   auto dynamicState = vk::PipelineDynamicStateCreateInfo { };\n   dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());\n   dynamicState.pDynamicStates = dynamicStates.data();\n\n   auto pipelineInfo = vk::GraphicsPipelineCreateInfo { };\n   pipelineInfo.pStages = shaderStages.data();\n   pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size());\n   pipelineInfo.pVertexInputState = &vertexInputInfo;\n   pipelineInfo.pInputAssemblyState = &inputAssembly;\n   pipelineInfo.pTessellationState = nullptr;\n   pipelineInfo.pViewportState = &viewportState;\n   pipelineInfo.pRasterizationState = &rasterizer;\n   pipelineInfo.pMultisampleState = &multisampling;\n   pipelineInfo.pDepthStencilState = nullptr;\n   pipelineInfo.pColorBlendState = &colorBlendState;\n   pipelineInfo.pDynamicState = &dynamicState;\n   pipelineInfo.layout = displayPipeline.pipelineLayout;\n   pipelineInfo.renderPass = displayPipeline.renderPass;\n   pipelineInfo.subpass = 0;\n   pipelineInfo.basePipelineHandle = vk::Pipeline { };\n   pipelineInfo.basePipelineIndex = -1;\n\n   auto result = device.createGraphicsPipeline(vk::PipelineCache { },\n                                               pipelineInfo);\n   if (result.result != vk::Result::eSuccess) {\n      return false;\n   }\n\n   displayPipeline.graphicsPipeline = result.value;\n   return true;\n}\n\nstatic bool\ncreateDescriptorPools(VulkanDisplayPipeline &displayPipeline,\n                      vk::Device &device)\n{\n   auto descriptorPoolSizes = std::vector<vk::DescriptorPoolSize> {\n      vk::DescriptorPoolSize { vk::DescriptorType::eSampler, 100 },\n      vk::DescriptorPoolSize { vk::DescriptorType::eSampledImage, 100 },\n      vk::DescriptorPoolSize { vk::DescriptorType::eCombinedImageSampler, 100 },\n   };\n\n   auto createInfo = vk::DescriptorPoolCreateInfo { };\n   createInfo.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size());\n   createInfo.pPoolSizes = descriptorPoolSizes.data();\n   createInfo.maxSets = static_cast<uint32_t>(descriptorPoolSizes.size() * 100);\n   displayPipeline.descriptorPool = device.createDescriptorPool(createInfo);\n\n   return true;\n}\n\nstatic std::optional<uint32_t>\nchooseMemoryTypeIndex(vk::PhysicalDevice &physicalDevice,\n                      uint32_t typeFilter,\n                      vk::MemoryPropertyFlags propertyFlags)\n{\n   auto memoryProperties = physicalDevice.getMemoryProperties();\n   for (auto i = uint32_t { 0 }; i < memoryProperties.memoryTypeCount; i++) {\n      if ((typeFilter & (1 << i)) == 0) {\n         continue;\n      }\n\n      if ((memoryProperties.memoryTypes[i].propertyFlags & propertyFlags) != propertyFlags) {\n         continue;\n      }\n\n      return i;\n   }\n\n   return { };\n}\n\nstatic bool\ncreateBuffers(VulkanDisplayPipeline &displayPipeline,\n              vk::PhysicalDevice &physicalDevice,\n              vk::Device &device)\n{\n   static constexpr std::array<float, 24> vertices = {\n      -1.0f,  1.0f,  0.0f,  1.0f,\n       1.0f,  1.0f,  1.0f,  1.0f,\n       1.0f, -1.0f,  1.0f,  0.0f,\n\n       1.0f, -1.0f,  1.0f,  0.0f,\n      -1.0f, -1.0f,  0.0f,  0.0f,\n      -1.0f,  1.0f,  0.0f,  1.0f,\n   };\n\n   // Allocate buffer\n   auto bufferDesc = vk::BufferCreateInfo { };\n   bufferDesc.size = static_cast<uint32_t>(sizeof(float) * vertices.size());\n   bufferDesc.usage = vk::BufferUsageFlagBits::eVertexBuffer;\n   bufferDesc.sharingMode = vk::SharingMode::eExclusive;\n   bufferDesc.queueFamilyIndexCount = 1;\n   bufferDesc.pQueueFamilyIndices = &displayPipeline.queueFamilyIndex;\n   displayPipeline.vertexBuffer = device.createBuffer(bufferDesc);\n\n   auto bufferMemoryRequirements =\n      device.getBufferMemoryRequirements(displayPipeline.vertexBuffer);\n\n   auto memoryTypeIndex =\n      chooseMemoryTypeIndex(physicalDevice,\n                            bufferMemoryRequirements.memoryTypeBits,\n                            vk::MemoryPropertyFlagBits::eHostVisible);\n   if (!memoryTypeIndex) {\n      return false;\n   }\n\n   auto allocateInfo = vk::MemoryAllocateInfo { };\n   allocateInfo.allocationSize = bufferMemoryRequirements.size;\n   allocateInfo.memoryTypeIndex = *memoryTypeIndex;\n\n   displayPipeline.vertexBufferMemory = device.allocateMemory(allocateInfo);\n   device.bindBufferMemory(displayPipeline.vertexBuffer, displayPipeline.vertexBufferMemory, 0);\n\n   // Upload vertices\n   auto mappedMemory = device.mapMemory(displayPipeline.vertexBufferMemory, 0, VK_WHOLE_SIZE);\n   std::memcpy(mappedMemory, vertices.data(), bufferMemoryRequirements.size);\n\n   device.flushMappedMemoryRanges({\n      vk::MappedMemoryRange { displayPipeline.vertexBufferMemory, 0, VK_WHOLE_SIZE }\n   });\n   device.unmapMemory(displayPipeline.vertexBufferMemory);\n\n   return true;\n}\n\nstatic bool\ncreateDescriptorSets(VulkanDisplayPipeline &displayPipeline,\n                     vk::Device &device)\n{\n   displayPipeline.descriptorSets.resize(displayPipeline.framebuffers.size() * 2);\n   for (auto i = 0u; i < displayPipeline.descriptorSets.size(); ++i) {\n      auto allocateInfo = vk::DescriptorSetAllocateInfo { };\n      allocateInfo.descriptorPool = displayPipeline.descriptorPool;\n      allocateInfo.descriptorSetCount = 1;\n      allocateInfo.pSetLayouts = &displayPipeline.descriptorSetLayout;\n      displayPipeline.descriptorSets[i] = device.allocateDescriptorSets(allocateInfo)[0];\n   }\n\n   return true;\n}\n\nstatic bool\ncreateFences(VulkanDisplayPipeline &displayPipeline,\n             vk::Device &device)\n{\n   displayPipeline.imageAvailableSemaphores.resize(displayPipeline.framebuffers.size());\n   displayPipeline.renderFinishedSemaphores.resize(displayPipeline.framebuffers.size());\n   displayPipeline.renderFences.resize(displayPipeline.framebuffers.size());\n\n   for (auto i = 0u; i < displayPipeline.framebuffers.size(); ++i) {\n      displayPipeline.imageAvailableSemaphores[i] =\n         device.createSemaphore(vk::SemaphoreCreateInfo { });\n\n      displayPipeline.renderFinishedSemaphores[i] =\n         device.createSemaphore(vk::SemaphoreCreateInfo { });\n\n      displayPipeline.renderFences[i] =\n         device.createFence(vk::FenceCreateInfo { vk::FenceCreateFlagBits::eSignaled });\n   }\n\n   displayPipeline.frameIndex = 0;\n   return true;\n}\n\nvoid\nDriver::setWindowSystemInfo(const gpu::WindowSystemInfo &wsi)\n{\n   auto instance = createVulkanInstance(wsi);\n   if (!instance) {\n      decaf_abort(\"createVulkanInstance failed\");\n   }\n\n   mVkDynLoader.init(instance, ::vkGetInstanceProcAddr);\n   registerDebugCallback(instance, mVkDynLoader, reinterpret_cast<void *>(this));\n\n   auto physicalDevice = choosePhysicalDevice(instance);\n   if (!physicalDevice) {\n      decaf_abort(\"choosePhysicalDevice failed\");\n   }\n\n   auto windowSurface = createVulkanSurface(instance, wsi);\n   if (!windowSurface) {\n      decaf_abort(\"createVulkanSurface failed\");\n   }\n\n   auto surfaceFormat = chooseSurfaceFormat(physicalDevice, windowSurface);\n   if (!windowSurface) {\n      decaf_abort(\"chooseSurfaceFormat failed\");\n   }\n\n\n   auto [device, queueFamilyIndex, supportedFeatures, supportedFeaturesTransformFeedback] =\n      createDevice(physicalDevice, windowSurface);\n   if (!device) {\n      decaf_abort(\"createDevice failed\");\n   }\n\n   auto queue = device.getQueue(queueFamilyIndex, 0);\n   if (!queue) {\n      decaf_abort(\"device.getQueue failed\");\n   }\n\n   mSupportedFeatures = supportedFeatures;\n   mSupportedFeaturesTransformFeedback = supportedFeaturesTransformFeedback;\n\n   initialise(instance, physicalDevice, device, queue, queueFamilyIndex);\n\n   // Create our full display pipeline\n   mDisplayPipeline.windowSurface = windowSurface;\n   mDisplayPipeline.windowSurfaceFormat = surfaceFormat;\n   mDisplayPipeline.queueFamilyIndex = queueFamilyIndex;\n   mDisplayPipeline.presentMode = choosePresentMode(physicalDevice, windowSurface);\n\n   if (!createRenderPass(mDisplayPipeline, device)) {\n      decaf_abort(\"createRenderPass failed\");\n   }\n\n   if (!createSwapchain(mDisplayPipeline, physicalDevice, device)) {\n      decaf_abort(\"createSwapchain failed\");\n   }\n\n   if (!createPipelineLayout(mDisplayPipeline, device)) {\n      decaf_abort(\"createPipelineLayout failed\");\n   }\n\n   if (!createRenderPipeline(mDisplayPipeline, device)) {\n      decaf_abort(\"createRenderPipeline failed\");\n   }\n\n   if (!createDescriptorPools(mDisplayPipeline, device)) {\n      decaf_abort(\"createDescriptorPools failed\");\n   }\n\n   if (!createBuffers(mDisplayPipeline, physicalDevice, device)) {\n      decaf_abort(\"createBuffers failed\");\n\n   }\n   if (!createDescriptorSets(mDisplayPipeline, device)) {\n      decaf_abort(\"createDescriptorSets failed\");\n   }\n\n   if (!createFences(mDisplayPipeline, device)) {\n      decaf_abort(\"createFences failed\");\n   }\n}\n\nvoid\nDriver::destroyDisplayPipeline()\n{\n   // createFences\n   for (auto &semaphore : mDisplayPipeline.imageAvailableSemaphores) {\n      mDevice.destroySemaphore(semaphore);\n   }\n   mDisplayPipeline.imageAvailableSemaphores.clear();\n\n   for (auto &semaphore : mDisplayPipeline.renderFinishedSemaphores) {\n      mDevice.destroySemaphore(semaphore);\n   }\n   mDisplayPipeline.renderFinishedSemaphores.clear();\n\n   for (auto &fence : mDisplayPipeline.renderFences) {\n      mDevice.destroyFence(fence);\n   }\n   mDisplayPipeline.renderFences.clear();\n\n   // createDescriptorSets\n   mDevice.freeDescriptorSets(mDisplayPipeline.descriptorPool, mDisplayPipeline.descriptorSets);\n   mDisplayPipeline.descriptorSets.clear();\n\n   // createBuffers\n   mDevice.destroyBuffer(mDisplayPipeline.vertexBuffer);\n   mDevice.freeMemory(mDisplayPipeline.vertexBufferMemory);\n   mDisplayPipeline.vertexBuffer = nullptr;\n   mDisplayPipeline.vertexBufferMemory = nullptr;\n\n   // createDescriptorPools\n   mDevice.destroyDescriptorPool(mDisplayPipeline.descriptorPool);\n   mDisplayPipeline.descriptorPool = nullptr;\n\n   // createRenderPipeline\n   mDevice.destroyShaderModule(mDisplayPipeline.vertexShader);\n   mDevice.destroyShaderModule(mDisplayPipeline.fragmentShader);\n   mDevice.destroyPipeline(mDisplayPipeline.graphicsPipeline);\n   mDisplayPipeline.vertexShader = nullptr;\n   mDisplayPipeline.fragmentShader = nullptr;\n   mDisplayPipeline.graphicsPipeline = nullptr;\n\n   // createPipelineLayout\n   mDevice.destroySampler(mDisplayPipeline.trivialSampler);\n   mDevice.destroyDescriptorSetLayout(mDisplayPipeline.descriptorSetLayout);\n   mDevice.destroyPipelineLayout(mDisplayPipeline.pipelineLayout);\n   mDisplayPipeline.trivialSampler = nullptr;\n   mDisplayPipeline.descriptorSetLayout = nullptr;\n   mDisplayPipeline.pipelineLayout = nullptr;\n\n   // createSwapchain\n   destroySwapchain(mDisplayPipeline, mDevice);\n\n   // createRenderPass\n   mDevice.destroyRenderPass(mDisplayPipeline.renderPass);\n   mDisplayPipeline.renderPass = nullptr;\n}\n\nstatic void\nacquireScanBuffer(vk::Device &device,\n                  vk::CommandBuffer cmdBuffer,\n                  vk::DescriptorSet descriptorSet,\n                  vk::Image image,\n                  vk::ImageView imageView)\n{\n   auto imageBarrier = vk::ImageMemoryBarrier { };\n   imageBarrier.srcAccessMask = vk::AccessFlags { };\n   imageBarrier.dstAccessMask = vk::AccessFlags { };\n   imageBarrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;\n   imageBarrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;\n   imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   imageBarrier.image = image;\n   imageBarrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;\n   imageBarrier.subresourceRange.baseMipLevel = 0;\n   imageBarrier.subresourceRange.levelCount = 1;\n   imageBarrier.subresourceRange.baseArrayLayer = 0;\n   imageBarrier.subresourceRange.layerCount = 1;\n\n   cmdBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllGraphics,\n                             vk::PipelineStageFlagBits::eAllGraphics,\n                             vk::DependencyFlagBits::eByRegion,\n                             {},\n                             {},\n                             { imageBarrier });\n\n   auto descriptorImageInfo = vk::DescriptorImageInfo { };\n   descriptorImageInfo.imageView = imageView;\n   descriptorImageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;\n\n   auto writeDescriptorSet = vk::WriteDescriptorSet { };\n   writeDescriptorSet.dstSet = descriptorSet;\n   writeDescriptorSet.dstBinding = 1;\n   writeDescriptorSet.dstArrayElement = 0;\n   writeDescriptorSet.descriptorCount = 1;\n   writeDescriptorSet.descriptorType = vk::DescriptorType::eSampledImage;\n   writeDescriptorSet.pImageInfo = &descriptorImageInfo;\n   device.updateDescriptorSets({ writeDescriptorSet }, {});\n}\n\nstatic void\nrenderScanBuffer(VulkanDisplayPipeline &displayPipeline,\n                 vk::Viewport viewport,\n                 vk::CommandBuffer cmdBuffer,\n                 vk::DescriptorSet descriptorSet,\n                 vk::Image image,\n                 vk::ImageView imageView)\n{\n   cmdBuffer.setViewport(0, { viewport });\n   cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,\n                                displayPipeline.pipelineLayout,\n                                0,\n                                { descriptorSet },\n                                {});\n   cmdBuffer.bindVertexBuffers(0, { displayPipeline.vertexBuffer }, { 0 });\n   cmdBuffer.draw(6, 1, 0, 0);\n}\n\nstatic void\nreleaseScanBuffer(vk::CommandBuffer cmdBuffer,\n                  vk::DescriptorSet descriptorSet,\n                  vk::Image image,\n                  vk::ImageView imageView)\n{\n   auto imageBarrier = vk::ImageMemoryBarrier { };\n   imageBarrier.srcAccessMask = vk::AccessFlags { };\n   imageBarrier.dstAccessMask = vk::AccessFlags { };\n   imageBarrier.oldLayout = vk::ImageLayout::eShaderReadOnlyOptimal;\n   imageBarrier.newLayout = vk::ImageLayout::eTransferDstOptimal;\n   imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   imageBarrier.image = image;\n   imageBarrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;\n   imageBarrier.subresourceRange.baseMipLevel = 0;\n   imageBarrier.subresourceRange.levelCount = 1;\n   imageBarrier.subresourceRange.baseArrayLayer = 0;\n   imageBarrier.subresourceRange.layerCount = 1;\n\n   cmdBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllGraphics,\n                             vk::PipelineStageFlagBits::eAllGraphics,\n                             vk::DependencyFlagBits::eByRegion,\n                             {},\n                             {},\n                             { imageBarrier });\n}\n\nvoid\nDriver::renderDisplay()\n{\n   auto frameIndex = mDisplayPipeline.frameIndex;\n   auto &renderFence = mDisplayPipeline.renderFences[frameIndex];\n   auto &imageAvailableSemaphore = mDisplayPipeline.imageAvailableSemaphores[frameIndex];\n   auto &renderFinishedSemaphore = mDisplayPipeline.renderFinishedSemaphores[frameIndex];\n\n   // Wait for the previous frame to finish\n   auto result = mDevice.waitForFences({ renderFence }, true,\n                                       std::numeric_limits<uint64_t>::max());\n   mDevice.resetFences({ renderFence });\n\n   // Acquire the next frame to render into\n   auto nextSwapImage = uint32_t { 0 };\n   try {\n      result = mDevice.acquireNextImageKHR(mDisplayPipeline.swapchain,\n                                           std::numeric_limits<uint64_t>::max(),\n                                           imageAvailableSemaphore,\n                                           vk::Fence {},\n                                           &nextSwapImage);\n   } catch (vk::OutOfDateKHRError err) {\n      result = vk::Result::eErrorOutOfDateKHR;\n   }\n\n   if (result == vk::Result::eErrorOutOfDateKHR) {\n      // Recreate swapchain\n      recreateSwapchain(mDisplayPipeline, mPhysDevice, mDevice);\n\n      // Try acquire again, this one will die if it fails :D\n      result = mDevice.acquireNextImageKHR(mDisplayPipeline.swapchain,\n                                           std::numeric_limits<uint64_t>::max(),\n                                           imageAvailableSemaphore, vk::Fence {},\n                                           &nextSwapImage);\n   }\n\n   // Allocate a command buffer to use\n   auto renderCmdBuf = mDevice.allocateCommandBuffers(\n      vk::CommandBufferAllocateInfo {\n         mCommandPool,\n         vk::CommandBufferLevel::ePrimary, 1\n      })[0];\n\n   // Select some descriptors to use\n   auto descriptorSetTv = mDisplayPipeline.descriptorSets[frameIndex * 2 + 0];\n   auto descriptorSetDrc = mDisplayPipeline.descriptorSets[frameIndex * 2 + 1];\n\n   // Setup render layout\n   const auto displayWidth = mDisplayPipeline.swapchainExtents.width;\n   const auto displayHeight = mDisplayPipeline.swapchainExtents.height;\n   auto layout = gpu7::DisplayLayout { };\n   gpu7::updateDisplayLayout(\n      layout,\n      static_cast<float>(displayWidth),\n      static_cast<float>(displayHeight));\n\n   if (layout.tv.visible) {\n      layout.tv.visible = (mTvSwapChain && mTvSwapChain->presentable);\n   }\n\n   if (layout.drc.visible) {\n      layout.drc.visible = (mDrcSwapChain && mDrcSwapChain->presentable);\n   }\n\n   renderCmdBuf.begin(vk::CommandBufferBeginInfo({}, nullptr));\n   {\n      renderCmdBuf.bindPipeline(vk::PipelineBindPoint::eGraphics, mDisplayPipeline.graphicsPipeline);\n      renderCmdBuf.setScissor(0, { vk::Rect2D { { 0, 0 }, mDisplayPipeline.swapchainExtents } });\n\n      if (layout.tv.visible) {\n         acquireScanBuffer(mDevice, renderCmdBuf, descriptorSetTv,\n                           mTvSwapChain->image, mTvSwapChain->imageView);\n      }\n\n      if (layout.drc.visible) {\n         acquireScanBuffer(mDevice, renderCmdBuf, descriptorSetDrc,\n                           mDrcSwapChain->image, mDrcSwapChain->imageView);\n      }\n\n      auto renderPassBeginInfo = vk::RenderPassBeginInfo { };\n      renderPassBeginInfo.renderPass = mDisplayPipeline.renderPass;\n      renderPassBeginInfo.framebuffer = mDisplayPipeline.framebuffers[nextSwapImage];\n      renderPassBeginInfo.renderArea =\n         vk::Rect2D {\n            { 0, 0 },\n            { displayWidth, displayHeight },\n         };\n\n      auto clearValue = vk::ClearValue { layout.backgroundColour };\n      renderPassBeginInfo.clearValueCount = 1;\n      renderPassBeginInfo.pClearValues = &clearValue;\n\n      renderCmdBuf.beginRenderPass(renderPassBeginInfo, vk::SubpassContents::eInline);\n\n      if (layout.tv.visible) {\n         auto tvViewport =\n            vk::Viewport {\n               layout.tv.x, layout.tv.y,\n               layout.tv.width, layout.tv.height\n            };\n\n         renderScanBuffer(mDisplayPipeline, tvViewport, renderCmdBuf,\n                           descriptorSetTv, mTvSwapChain->image,\n                           mTvSwapChain->imageView);\n      }\n\n      if (layout.drc.visible) {\n         auto drcViewport =\n            vk::Viewport {\n               layout.drc.x, layout.drc.y,\n               layout.drc.width, layout.drc.height\n            };\n\n         renderScanBuffer(mDisplayPipeline, drcViewport, renderCmdBuf,\n                           descriptorSetDrc, mDrcSwapChain->image,\n                           mDrcSwapChain->imageView);\n      }\n\n      renderCmdBuf.endRenderPass();\n\n      if (layout.tv.visible) {\n         releaseScanBuffer(renderCmdBuf, descriptorSetTv, mTvSwapChain->image,\n                           mTvSwapChain->imageView);\n      }\n\n      if (layout.drc.visible) {\n         releaseScanBuffer(renderCmdBuf, descriptorSetDrc, mDrcSwapChain->image,\n                           mDrcSwapChain->imageView);\n      }\n   }\n   renderCmdBuf.end();\n\n   {\n      auto submitInfo = vk::SubmitInfo { };\n\n      auto waitSemaphores = std::array<vk::Semaphore, 1> { imageAvailableSemaphore };\n      submitInfo.waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size());\n      submitInfo.pWaitSemaphores = waitSemaphores.data();\n\n      auto waitStage = vk::PipelineStageFlags { vk::PipelineStageFlagBits::eColorAttachmentOutput };\n      submitInfo.pWaitDstStageMask = &waitStage;\n\n      submitInfo.commandBufferCount = 1;\n      submitInfo.pCommandBuffers = &renderCmdBuf;\n\n      auto signalSemaphores = std::array<vk::Semaphore, 1> { renderFinishedSemaphore };\n      submitInfo.signalSemaphoreCount = static_cast<uint32_t>(signalSemaphores.size());\n      submitInfo.pSignalSemaphores = signalSemaphores.data();\n\n      mQueue.submit({ submitInfo }, renderFence);\n   }\n\n   {\n      auto presentInfo = vk::PresentInfoKHR { };\n\n      auto waitSemaphores = std::array<vk::Semaphore, 1> { renderFinishedSemaphore };\n      presentInfo.waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size());\n      presentInfo.pWaitSemaphores = waitSemaphores.data();\n\n      presentInfo.pImageIndices = &nextSwapImage;\n\n      presentInfo.swapchainCount = 1;\n      presentInfo.pSwapchains = &mDisplayPipeline.swapchain;\n\n      try {\n         result = mQueue.presentKHR(presentInfo);\n      } catch (vk::OutOfDateKHRError err) {\n         result = vk::Result::eErrorOutOfDateKHR;\n      }\n\n      if (result == vk::Result::eErrorOutOfDateKHR) {\n         recreateSwapchain(mDisplayPipeline, mPhysDevice, mDevice);\n      }\n   }\n\n   mDisplayPipeline.frameIndex = (frameIndex + 1) % 2;\n}\n\nvoid\nDriver::windowHandleChanged(void *handle)\n{\n}\n\nvoid\nDriver::windowSizeChanged(int width, int height)\n{\n   // Nothing to do, this will be handled during drawing\n}\n\n} // namespace vulkan\n\n#endif\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_displayshaders.h",
    "content": "#pragma once\n#include <array>\n\n/*\n#version 450\n#extension GL_ARB_separate_shader_objects : enable\n\nlayout(location = 0) in vec2 inPosition;\nlayout(location = 1) in vec2 inTexCoord;\n\nout gl_PerVertex {\n    vec4 gl_Position;\n};\nlayout(location = 0) out vec2 fragTexCoord;\n\nvoid main() {\n    gl_Position = vec4(inPosition, 0.0, 1.0);\n    fragTexCoord = inTexCoord;\n}\n*/\nstatic constexpr unsigned char scanbufferVertBytes[] = {\n    0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x1C, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,\n    0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,\n    0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n    0x0F, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,\n    0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,\n    0x1A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00,\n    0x04, 0x00, 0x09, 0x00, 0x47, 0x4C, 0x5F, 0x41, 0x52, 0x42, 0x5F, 0x73, 0x65, 0x70, 0x61, 0x72,\n    0x61, 0x74, 0x65, 0x5F, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x6F, 0x62, 0x6A, 0x65, 0x63,\n    0x74, 0x73, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,\n    0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50,\n    0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00,\n    0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x73, 0x69, 0x74,\n    0x69, 0x6F, 0x6E, 0x00, 0x05, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x05, 0x00, 0x05, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x50, 0x6F, 0x73, 0x69, 0x74, 0x69,\n    0x6F, 0x6E, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x19, 0x00, 0x00, 0x00, 0x66, 0x72, 0x61, 0x67,\n    0x54, 0x65, 0x78, 0x43, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,\n    0x1A, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x54, 0x65, 0x78, 0x43, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00,\n    0x48, 0x00, 0x05, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n    0x47, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x47, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x47, 0x00, 0x04, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n    0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,\n    0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,\n    0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n    0x1E, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,\n    0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,\n    0x09, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,\n    0x0B, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,\n    0x0B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00,\n    0x0D, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,\n    0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,\n    0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,\n    0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,\n    0x06, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x20, 0x00, 0x04, 0x00,\n    0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,\n    0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,\n    0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,\n    0x0E, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,\n    0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,\n    0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00,\n    0x10, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,\n    0x13, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00,\n    0x06, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n    0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,\n    0x14, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,\n    0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,\n    0x3E, 0x00, 0x03, 0x00, 0x17, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,\n    0x0D, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,\n    0x19, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,\n};\n\n\n/*\n#version 450\n#extension GL_ARB_separate_shader_objects : enable\n\nlayout(binding = 0) uniform sampler texSampler;\nlayout(binding = 1) uniform texture2D colorTex;\n\nlayout(location = 0) in vec2 fragTexCoord;\n\nlayout(location = 0) out vec4 outFragColor;\n\nvoid main() {\n    outFragColor = texture(sampler2D(colorTex, texSampler), fragTexCoord);\n}\n*/\nstatic constexpr unsigned char scanbufferFragBytes[] = {\n    0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x19, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,\n    0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,\n    0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n    0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,\n    0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,\n    0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,\n    0xC2, 0x01, 0x00, 0x00, 0x04, 0x00, 0x09, 0x00, 0x47, 0x4C, 0x5F, 0x41, 0x52, 0x42, 0x5F, 0x73,\n    0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x5F, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x6F,\n    0x62, 0x6A, 0x65, 0x63, 0x74, 0x73, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00,\n    0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00,\n    0x6F, 0x75, 0x74, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6F, 0x6C, 0x6F, 0x72, 0x00, 0x00, 0x00, 0x00,\n    0x05, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F, 0x72, 0x54, 0x65, 0x78,\n    0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x53,\n    0x61, 0x6D, 0x70, 0x6C, 0x65, 0x72, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00,\n    0x66, 0x72, 0x61, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00, 0x00, 0x00,\n    0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x47, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x47, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n    0x47, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x47, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x47, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,\n    0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,\n    0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n    0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,\n    0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,\n    0x19, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x0A, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x02, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,\n    0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,\n    0x0F, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x03, 0x00,\n    0x12, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00,\n    0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00,\n    0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00,\n    0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00,\n    0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,\n    0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,\n    0x0C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,\n    0x10, 0x00, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,\n    0x0D, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00,\n    0x17, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00,\n    0x18, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,\n    0x09, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,\n};\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_draw.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\n#include <common/log.h>\n\nnamespace vulkan\n{\n\nvoid\nDriver::bindDescriptors()\n{\n   bool dSetHasValues = false;\n\n   std::array<std::array<vk::DescriptorImageInfo, latte::MaxTextures>, 3> texSampInfos;\n   std::array<std::array<vk::DescriptorBufferInfo, latte::MaxUniformBlocks>, 3 > bufferInfos;\n\n   for (auto shaderStage = 0u; shaderStage < 3u; ++shaderStage) {\n      auto shaderStageTyped = static_cast<ShaderStage>(shaderStage);\n\n      const spirv::ShaderMeta *shaderMeta;\n      if (shaderStageTyped == ShaderStage::Vertex) {\n         if (!mCurrentDraw->vertexShader) {\n            continue;\n         }\n\n         shaderMeta = &mCurrentDraw->vertexShader->shader.meta;\n      } else if (shaderStageTyped == ShaderStage::Geometry) {\n         if (!mCurrentDraw->geometryShader) {\n            continue;\n         }\n\n         shaderMeta = &mCurrentDraw->geometryShader->shader.meta;\n      } else if (shaderStageTyped == ShaderStage::Pixel) {\n         if (!mCurrentDraw->pixelShader) {\n            continue;\n         }\n\n         shaderMeta = &mCurrentDraw->pixelShader->shader.meta;\n      } else {\n         decaf_abort(\"Unexpected shader stage during descriptor build\");\n      }\n\n      for (auto i = 0u; i < latte::MaxSamplers; ++i) {\n         if (shaderMeta->samplerUsed[i]) {\n            auto &sampler = mCurrentDraw->samplers[shaderStage][i];\n            if (sampler) {\n               texSampInfos[shaderStage][i].sampler = sampler->sampler;\n            } else {\n               texSampInfos[shaderStage][i].sampler = mBlankSampler;\n            }\n\n            dSetHasValues = true;\n         }\n      }\n\n      for (auto i = 0u; i < latte::MaxTextures; ++i) {\n         if (shaderMeta->textureUsed[i]) {\n            auto &texture = mCurrentDraw->textures[shaderStage][i];\n            if (texture) {\n               texSampInfos[shaderStage][i].imageView = texture->imageView;\n            } else {\n               texSampInfos[shaderStage][i].imageView = mBlankImageView;\n            }\n\n            texSampInfos[shaderStage][i].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;\n\n            dSetHasValues = true;\n         }\n      }\n\n      if (mCurrentDraw->gprBuffers[shaderStage]) {\n         if (shaderMeta->cfileUsed) {\n            auto gprBuffer = mCurrentDraw->gprBuffers[shaderStage];\n\n            bufferInfos[shaderStage][0].buffer = gprBuffer->buffer;\n            bufferInfos[shaderStage][0].offset = 0;\n            bufferInfos[shaderStage][0].range = gprBuffer->size;\n\n            dSetHasValues = true;\n         }\n      } else {\n         for (auto i = 0u; i < latte::MaxUniformBlocks; ++i) {\n            if (shaderMeta->cbufferUsed[i]) {\n               auto& uniformBuffer = mCurrentDraw->uniformBlocks[shaderStage][i];\n               if (uniformBuffer) {\n                  bufferInfos[shaderStage][i].buffer = uniformBuffer->buffer;\n                  bufferInfos[shaderStage][i].offset = 0;\n                  bufferInfos[shaderStage][i].range = uniformBuffer->size;\n               } else {\n                  bufferInfos[shaderStage][i].buffer = mBlankBuffer;\n                  bufferInfos[shaderStage][i].offset = 0;\n                  bufferInfos[shaderStage][i].range = 1024;\n               }\n\n               dSetHasValues = true;\n            }\n         }\n      }\n   }\n\n   // If this shader stage has nothing bound, there is no need to\n   // actually generate our descriptor sets or anything.\n   if (!dSetHasValues) {\n      return;\n   }\n\n   /*\n   for (auto shaderStage = 0u; shaderStage < 3u; ++shaderStage) {\n      for (auto &samplerInfo : samplerInfos[shaderStage]) {\n         if (!samplerInfo.sampler) {\n            samplerInfo.sampler = mBlankSampler;\n         }\n      }\n      for (auto &textureInfo : textureInfos[shaderStage]) {\n         if (!textureInfo.imageView) {\n            textureInfo.imageView = mBlankImageView;\n            textureInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;\n         }\n      }\n      for (auto &bufferInfo : bufferInfos[shaderStage]) {\n         if (!bufferInfo.buffer) {\n            bufferInfo.buffer = mBlankBuffer;\n            bufferInfo.offset = 0;\n            bufferInfo.range = 1;\n         }\n      }\n   }\n   */\n\n   vk::DescriptorSet dSet;\n   if (!mCurrentDraw->pipeline->pipelineLayout) {\n      // If there is no custom pipeline layout configured, this means we have to use\n      // standard descriptor sets rather than being able to take advantage of push.\n\n      dSet = allocateGenericDescriptorSet();\n   }\n\n   mScratchDescriptorWrites.clear();\n   auto &descWrites = mScratchDescriptorWrites;\n\n   for (auto shaderStage = 0u; shaderStage < 3u; ++shaderStage) {\n      auto bindingBase = 32 * shaderStage;\n\n      for (auto i = 0u; i < latte::MaxTextures; ++i) {\n         if (!texSampInfos[shaderStage][i].sampler && !texSampInfos[shaderStage][i].imageView) {\n            continue;\n         }\n\n         descWrites.push_back({});\n         vk::WriteDescriptorSet& writeDesc = descWrites.back();\n         writeDesc.dstSet = dSet;\n         writeDesc.dstBinding = bindingBase + i;\n         writeDesc.dstArrayElement = 0;\n         writeDesc.descriptorCount = 1;\n         if (texSampInfos[shaderStage][i].sampler && texSampInfos[shaderStage][i].imageView) {\n            writeDesc.descriptorType = vk::DescriptorType::eCombinedImageSampler;\n         } else if (texSampInfos[shaderStage][i].sampler) {\n            writeDesc.descriptorType = vk::DescriptorType::eSampler;\n         } else if (texSampInfos[shaderStage][i].imageView) {\n            writeDesc.descriptorType = vk::DescriptorType::eSampledImage;\n         }\n         writeDesc.pImageInfo = &texSampInfos[shaderStage][i];\n         writeDesc.pBufferInfo = nullptr;\n         writeDesc.pTexelBufferView = nullptr;\n      }\n\n      bindingBase += latte::MaxTextures;\n\n      for (auto i = 0u; i < latte::MaxUniformBlocks; ++i) {\n         if (i >= 15) {\n            // Refer to the descriptor layout creation code for information\n            // on why this code is neccessary...\n            break;\n         }\n\n         if (!bufferInfos[shaderStage][i].buffer) {\n            continue;\n         }\n\n         descWrites.push_back({});\n         vk::WriteDescriptorSet& writeDesc = descWrites.back();\n         writeDesc.dstSet = dSet;\n         writeDesc.dstBinding = bindingBase + i;\n         writeDesc.dstArrayElement = 0;\n         writeDesc.descriptorCount = 1;\n         writeDesc.descriptorType = vk::DescriptorType::eStorageBuffer;\n         writeDesc.pImageInfo = nullptr;\n         writeDesc.pBufferInfo = &bufferInfos[shaderStage][i];\n         writeDesc.pTexelBufferView = nullptr;\n      }\n   }\n\n   if (!dSet) {\n      mActiveCommandBuffer.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics,\n                                                mCurrentDraw->pipeline->pipelineLayout->pipelineLayout,\n                                                0,\n                                                descWrites,\n                                                mVkDynLoader);\n   } else {\n      mDevice.updateDescriptorSets(descWrites, {});\n\n      mActiveCommandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,\n                                              mPipelineLayout,\n                                              0,\n                                              { dSet }, {});\n   }\n}\n\nvoid\nDriver::bindShaderParams()\n{\n\n   // This should probably be split to its own function\n   if (mCurrentDraw->vertexShader) {\n      spirv::VertexPushConstants vsConstData;\n      vsConstData.posMulAdd.x = mCurrentDraw->shaderViewportData.xMul;\n      vsConstData.posMulAdd.y = mCurrentDraw->shaderViewportData.yMul;\n      vsConstData.posMulAdd.z = mCurrentDraw->shaderViewportData.xAdd;\n      vsConstData.posMulAdd.w = mCurrentDraw->shaderViewportData.yAdd;\n      vsConstData.zSpaceMul.x = mCurrentDraw->shaderViewportData.zAdd;\n      vsConstData.zSpaceMul.y = mCurrentDraw->shaderViewportData.zMul;\n      *reinterpret_cast<uint32_t*>(&vsConstData.zSpaceMul.z) = mCurrentDraw->baseVertex;\n      *reinterpret_cast<uint32_t*>(&vsConstData.zSpaceMul.w) = mCurrentDraw->baseInstance;\n      vsConstData.pointSize = mCurrentDraw->pointSize / 8.0f;\n\n      if (!mActiveVsConstantsSet ||\n          memcmp(&vsConstData, &mActiveVsConstants, sizeof(vsConstData)) != 0) {\n         mActiveVsConstants = vsConstData;\n         mActiveVsConstantsSet = true;\n\n         mActiveCommandBuffer.pushConstants<spirv::VertexPushConstants>(\n            mPipelineLayout,\n            vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eGeometry,\n            spirv::VertexPushConstantsOffset, { vsConstData });\n      }\n   }\n\n   if (mCurrentDraw->pixelShader) {\n      auto lopMode = mCurrentDraw->pipeline->shaderLopMode;\n      auto alphaFunc = mCurrentDraw->pipeline->shaderAlphaFunc;\n      auto alphaRef = mCurrentDraw->pipeline->shaderAlphaRef;\n\n      spirv::FragmentPushConstants psConstData;\n      psConstData.alphaFunc = (lopMode << 8) | static_cast<uint32_t>(alphaFunc);\n      psConstData.alphaRef = alphaRef;\n      psConstData.needsPremultiply = 0;\n      for (auto i = 0; i < latte::MaxRenderTargets; ++i) {\n         if (mCurrentDraw->pipeline->needsPremultipliedTargets &&\n             !mCurrentDraw->pipeline->targetIsPremultiplied[i]) {\n            psConstData.needsPremultiply |= (1 << i);\n         }\n      }\n\n      if (!mActivePsConstantsSet ||\n          memcmp(&psConstData, &mActivePsConstants, sizeof(psConstData)) != 0) {\n         mActivePsConstants = psConstData;\n         mActivePsConstantsSet = true;\n\n         mActiveCommandBuffer.pushConstants<spirv::FragmentPushConstants>(\n            mPipelineLayout, vk::ShaderStageFlagBits::eFragment,\n            spirv::FragmentPushConstantsOffset, { psConstData });\n      }\n   }\n}\n\nvoid\nDriver::drawGenericIndexed(latte::VGT_DRAW_INITIATOR drawInit, uint32_t numIndices, void *indices)\n{\n   // First lets set up our draw description for everyone\n   auto pa_su_point_size = getRegister<latte::PA_SU_POINT_SIZE>(latte::Register::PA_SU_POINT_SIZE);\n   auto vgt_index_type = getRegister<latte::VGT_NODMA_INDEX_TYPE>(latte::Register::VGT_INDEX_TYPE);\n   auto vgt_primitive_type = getRegister<latte::VGT_PRIMITIVE_TYPE>(latte::Register::VGT_PRIMITIVE_TYPE);\n   auto sq_vtx_base_vtx_loc = getRegister<latte::SQ_VTX_BASE_VTX_LOC>(latte::Register::SQ_VTX_BASE_VTX_LOC);\n   auto sq_vtx_start_inst_loc = getRegister<latte::SQ_VTX_START_INST_LOC>(latte::Register::SQ_VTX_START_INST_LOC);\n   auto vgt_dma_num_instances = getRegister<latte::VGT_DMA_NUM_INSTANCES>(latte::Register::VGT_DMA_NUM_INSTANCES);\n   auto vgt_dma_index_type = getRegister<latte::VGT_DMA_INDEX_TYPE>(latte::Register::VGT_DMA_INDEX_TYPE);\n   auto vgt_strmout_en = getRegister<latte::VGT_STRMOUT_EN>(latte::Register::VGT_STRMOUT_EN);\n\n   bool useStreamOut = vgt_strmout_en.STREAMOUT();\n   bool useOpaque = drawInit.USE_OPAQUE();\n\n   if (useOpaque) {\n      decaf_check(numIndices == 0);\n      decaf_check(!indices);\n   }\n\n   DrawDesc& drawDesc = mDrawCache;\n   drawDesc.indices = indices;\n   drawDesc.indexType = vgt_index_type.INDEX_TYPE();\n   drawDesc.indexSwapMode = latte::VGT_DMA_SWAP::NONE;\n   drawDesc.primitiveType = vgt_primitive_type.PRIM_TYPE();\n   drawDesc.numIndices = numIndices;\n   drawDesc.baseVertex = sq_vtx_base_vtx_loc.OFFSET();\n   drawDesc.numInstances = 1;\n   drawDesc.baseInstance = sq_vtx_start_inst_loc.OFFSET();\n   drawDesc.streamOutEnabled = useStreamOut;\n   drawDesc.streamOutContext = mStreamOutContext;\n   drawDesc.pointSize = pa_su_point_size.WIDTH();\n\n   if (drawInit.SOURCE_SELECT() == latte::VGT_DI_SRC_SEL::DMA) {\n      drawDesc.indexType = vgt_dma_index_type.INDEX_TYPE();\n      drawDesc.indexSwapMode = vgt_dma_index_type.SWAP_MODE();\n      drawDesc.numInstances = vgt_dma_num_instances.NUM_INSTANCES();\n   }\n\n   mCurrentDraw = &drawDesc;\n\n   // Set up all the required state, ordering here is very important\n   if (!checkCurrentVertexShader()) {\n      gLog->debug(\"Skipped draw due to a vertex shader error\");\n      return;\n   }\n   if (!checkCurrentGeometryShader()) {\n      gLog->debug(\"Skipped draw due to a geometry shader error\");\n      return;\n   }\n   if (!checkCurrentPixelShader()) {\n      gLog->debug(\"Skipped draw due to a pixel shader error\");\n      return;\n   }\n   if (!checkCurrentRectStubShader()) {\n      gLog->debug(\"Skipped draw due to a rect stub shader error\");\n      return;\n   }\n   if (!checkCurrentRenderPass()) {\n      gLog->debug(\"Skipped draw due to a render pass error\");\n      return;\n   }\n   if (!checkCurrentFramebuffer()) {\n      gLog->debug(\"Skipped draw due to a framebuffer error\");\n      return;\n   }\n   if (!checkCurrentPipeline()) {\n      gLog->debug(\"Skipped draw due to a pipeline error\");\n      return;\n   }\n   if (!checkCurrentSamplers()) {\n      gLog->debug(\"Skipped draw due to a samplers error\");\n      return;\n   }\n   if (!checkCurrentTextures()) {\n      gLog->debug(\"Skipped draw due to a textures error\");\n      return;\n   }\n   if (!checkCurrentAttribBuffers()) {\n      gLog->debug(\"Skipped draw due to an attribute buffers error\");\n      return;\n   }\n   if (!checkCurrentShaderBuffers()) {\n      gLog->debug(\"Skipped draw due to a shader buffers error\");\n      return;\n   }\n   if (!checkCurrentIndices()) {\n      gLog->debug(\"Skipped draw due to an index buffer error\");\n      return;\n   }\n   if (!checkCurrentViewportAndScissor()) {\n      gLog->debug(\"Skipped draw due to a viewport or scissor area error\");\n      return;\n   }\n   if (!checkCurrentStreamOut()) {\n      gLog->debug(\"Skipped draw due to a stream out buffers error\");\n      return;\n   }\n\n   // These things need to happen up here before we enter the render pass, otherwise\n   // the pipeline barrier triggered by the memory cache transition will fail.\n   uint32_t opaqueStride = 0;\n   MemCacheObject *opaqueCounter = nullptr;\n   if (useOpaque) {\n      auto vgt_strmout_draw_opaque_vertex_stride = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_DRAW_OPAQUE_VERTEX_STRIDE);\n      opaqueStride = vgt_strmout_draw_opaque_vertex_stride << 2;\n\n      auto soBufferSizeAddr = getRegisterAddr(latte::Register::VGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE);\n      opaqueCounter = getDataMemCache(soBufferSizeAddr, 4);\n\n      transitionMemCache(opaqueCounter, ResourceUsage::StreamOutCounterRead);\n\n      drawDesc.opaqueBuffer = opaqueCounter;\n      drawDesc.opaqueStride = opaqueStride;\n   } else {\n      drawDesc.opaqueBuffer = nullptr;\n   }\n\n   // Finish this draw\n   mCurrentDraw = nullptr;\n\n   // Perform our pending draws if the renderpass has changed\n   if (mActiveRenderPass != drawDesc.renderPass || mActiveFramebuffer != drawDesc.framebuffer) {\n      flushPendingDraws();\n\n      mActiveRenderPass = drawDesc.renderPass;\n      mActiveFramebuffer = drawDesc.framebuffer;\n   }\n\n   // If this is a stream-out draw, we have to flush it as a singular draw call.\n   if (useStreamOut) {\n      flushPendingDraws();\n   }\n\n   // Now that we have potentially flushed our draws, lets prepare this draw.  We need to do it in\n   // this order, since this draw might use the last renderpasses framebuffer as a texture, and we\n   // need to barrier AFTER the previous draws are flushed.\n   mCurrentDraw = &drawDesc;\n   prepareCurrentTextures();\n   prepareCurrentFramebuffer();\n   mCurrentDraw = nullptr;\n\n   // Record this pending draw\n   mPendingDraws.push_back(drawDesc);\n\n   // Finish with this draw call\n   mCurrentDraw = nullptr;\n\n   // If this is a stream-out draw, we have to flush it as a singular draw call.\n   if (useStreamOut) {\n      flushPendingDraws();\n   }\n}\n\nvoid\nDriver::flushPendingDraws()\n{\n   if (mPendingDraws.empty()) {\n      return;\n   }\n\n   auto& fbRa = mActiveFramebuffer->renderArea;\n   auto renderArea = vk::Rect2D { { 0, 0 }, fbRa };\n\n   // Bind and set up everything, and then do our draw\n   auto passBeginDesc = vk::RenderPassBeginInfo {};\n   passBeginDesc.renderPass = mActiveRenderPass->renderPass;\n   passBeginDesc.framebuffer = mActiveFramebuffer->framebuffer;\n   passBeginDesc.renderArea = renderArea;\n   passBeginDesc.clearValueCount = 0;\n   passBeginDesc.pClearValues = nullptr;\n   mActiveCommandBuffer.beginRenderPass(passBeginDesc, vk::SubpassContents::eInline);\n\n   for (auto &drawDesc : mPendingDraws) {\n      // Make sure that the draw descriptions match our expectations\n      decaf_check(mActiveRenderPass == drawDesc.renderPass);\n      decaf_check(mActiveFramebuffer == drawDesc.framebuffer);\n\n      mCurrentDraw = &drawDesc;\n      drawCurrentState();\n      mCurrentDraw = nullptr;\n   }\n   mPendingDraws.clear();\n\n   mActiveCommandBuffer.endRenderPass();\n}\n\nvoid\nDriver::drawCurrentState()\n{\n   auto &drawDesc = *mCurrentDraw;\n\n   if (mActivePipeline != drawDesc.pipeline) {\n      mActiveCommandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, drawDesc.pipeline->pipeline);\n\n      mActivePipeline = drawDesc.pipeline;\n   }\n\n   bindAttribBuffers();\n   bindDescriptors();\n   bindShaderParams();\n   bindViewportAndScissor();\n   bindIndexBuffer();\n   bindStreamOutBuffers();\n\n   if (drawDesc.streamOutEnabled) {\n      beginStreamOut();\n   }\n\n   if (drawDesc.opaqueBuffer) {\n      mActiveCommandBuffer.drawIndirectByteCountEXT(1, 0, drawDesc.opaqueBuffer->buffer, 0, 0, drawDesc.opaqueStride, mVkDynLoader);\n   } else if (drawDesc.indexBuffer) {\n      mActiveCommandBuffer.drawIndexed(drawDesc.numIndices, drawDesc.numInstances, 0, drawDesc.baseVertex, drawDesc.baseInstance);\n   } else {\n      mActiveCommandBuffer.draw(drawDesc.numIndices, drawDesc.numInstances, drawDesc.baseVertex, drawDesc.baseInstance);\n   }\n\n   if (drawDesc.streamOutEnabled) {\n      endStreamOut();\n   }\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_driver.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"gpu_config.h\"\n#include \"gpu_configstorage.h\"\n#include \"gpu_event.h\"\n#include \"gpu_graphicsdriver.h\"\n#include \"gpu_ringbuffer.h\"\n\nnamespace vulkan\n{\n\nDriver::Driver()\n{\n}\n\nDriver::~Driver()\n{\n}\n\nvoid\nDriver::notifyCpuFlush(phys_addr address,\n                       uint32_t size)\n{\n}\n\nvoid\nDriver::notifyGpuFlush(phys_addr address,\n                       uint32_t size)\n{\n}\n\nvoid\nDriver::initialise(vk::Instance instance,\n                   vk::PhysicalDevice physDevice,\n                   vk::Device device,\n                   vk::Queue queue,\n                   uint32_t queueFamilyIndex)\n{\n   if (mRunState != RunState::None) {\n      return;\n   }\n\n   // Register config change handler\n   static std::once_flag sRegisteredConfigChangeListener;\n   std::call_once(sRegisteredConfigChangeListener,\n      [this]() {\n         gpu::registerConfigChangeListener(\n            [this](const gpu::Settings &settings) {\n               mDebug = settings.debug.debug_enabled;\n               mDumpShaders = settings.debug.dump_shaders;\n               mDumpShaderBinariesOnly = settings.debug.dump_shader_binaries_only;\n            });\n      });\n\n   // Read config\n   auto gpuConfig = gpu::config();\n   mDebug = gpuConfig->debug.debug_enabled;\n   mDumpShaders = gpuConfig->debug.dump_shaders;\n   mDumpShaderBinariesOnly = gpuConfig->debug.dump_shader_binaries_only;\n\n   mPhysDevice = physDevice;\n   mDevice = device;\n   mQueue = queue;\n   mRunState = RunState::Running;\n\n   validateDevice();\n\n   // Initialize the dynamic loader we use for extensions\n   mVkDynLoader.init(instance, ::vkGetInstanceProcAddr, mDevice, ::vkGetDeviceProcAddr);\n\n   // Initialize our GPU retiler\n   mGpuRetiler.initialise(mDevice);\n\n   // Allocate a command pool to use\n   auto commandPoolCreateInfo = vk::CommandPoolCreateInfo { };\n   commandPoolCreateInfo.flags =\n      vk::CommandPoolCreateFlagBits::eTransient |\n      vk::CommandPoolCreateFlagBits::eResetCommandBuffer;\n   commandPoolCreateInfo.queueFamilyIndex = queueFamilyIndex;\n   mCommandPool = mDevice.createCommandPool(commandPoolCreateInfo);\n\n   // Start our fence thread\n   mFenceThread = std::thread { std::bind(&Driver::fenceWaiterThread, this) };\n\n   // Set up the VMA\n   auto allocatorCreateInfo = VmaAllocatorCreateInfo { };\n   allocatorCreateInfo.physicalDevice = mPhysDevice;\n   allocatorCreateInfo.device = mDevice;\n   CHECK_VK_RESULT(vmaCreateAllocator(&allocatorCreateInfo, &mAllocator));\n\n   // Set up the default pipeline layout and descriptor set\n   auto basePlDesc = PipelineLayoutDesc { };\n   memset(&basePlDesc, 0xFF, sizeof(basePlDesc));\n   auto basePl = getPipelineLayout(basePlDesc);\n   mBaseDescriptorSetLayout = basePl->descriptorLayout;\n   mPipelineLayout = basePl->pipelineLayout;\n\n   // Set up the pipeline cache\n   auto pipelineCacheCreateInfo = vk::PipelineCacheCreateInfo { };\n   pipelineCacheCreateInfo.flags = vk::PipelineCacheCreateFlags { };\n   pipelineCacheCreateInfo.pInitialData = nullptr;\n   pipelineCacheCreateInfo.initialDataSize = 0;\n   mPipelineCache = mDevice.createPipelineCache(pipelineCacheCreateInfo);\n\n   initialiseBlankSampler();\n   initialiseBlankImage();\n   initialiseBlankBuffer();\n\n   setupResources();\n}\n\nvoid\nDriver::destroy()\n{\n   mFenceSignal.notify_all();\n   mFenceThread.join();\n\n   destroyDisplayPipeline();\n}\n\nvoid\nDriver::initialiseBlankSampler()\n{\n   vk::SamplerCreateInfo samplerDesc;\n   samplerDesc.magFilter = vk::Filter::eLinear;\n   samplerDesc.minFilter = vk::Filter::eLinear;\n   samplerDesc.mipmapMode = vk::SamplerMipmapMode::eLinear;\n   samplerDesc.addressModeU = vk::SamplerAddressMode::eRepeat;\n   samplerDesc.addressModeV = vk::SamplerAddressMode::eRepeat;\n   samplerDesc.addressModeW = vk::SamplerAddressMode::eRepeat;\n   samplerDesc.mipLodBias = 0.0f;\n   samplerDesc.anisotropyEnable = false;\n   samplerDesc.maxAnisotropy = 0.0f;\n   samplerDesc.compareEnable = false;\n   samplerDesc.compareOp = vk::CompareOp::eAlways;\n   samplerDesc.minLod = 0.0f;\n   samplerDesc.maxLod = 0.0f;\n   samplerDesc.borderColor = vk::BorderColor::eFloatTransparentBlack;\n   samplerDesc.unnormalizedCoordinates = VK_FALSE;\n   auto emptySampler = mDevice.createSampler(samplerDesc);\n\n   setVkObjectName(emptySampler, \"PlaceholderSampler\");\n\n   mBlankSampler = emptySampler;\n}\n\nvoid\nDriver::initialiseBlankImage()\n{\n   // Create a random image to use for sampling\n   vk::ImageCreateInfo createImageDesc;\n   createImageDesc.imageType = vk::ImageType::e2D;\n   createImageDesc.format = vk::Format::eR8G8B8A8Snorm;\n   createImageDesc.extent = vk::Extent3D(1, 1, 1);\n   createImageDesc.mipLevels = 1;\n   createImageDesc.arrayLayers = 1;\n   createImageDesc.samples = vk::SampleCountFlagBits::e1;\n   createImageDesc.tiling = vk::ImageTiling::eOptimal;\n   createImageDesc.usage = vk::ImageUsageFlagBits::eSampled;\n   createImageDesc.sharingMode = vk::SharingMode::eExclusive;\n   createImageDesc.initialLayout = vk::ImageLayout::eUndefined;\n   auto emptyImage = mDevice.createImage(createImageDesc);\n\n   setVkObjectName(emptyImage, \"PlaceholderSurface\");\n\n   auto imageMemReqs = mDevice.getImageMemoryRequirements(emptyImage);\n\n   vk::MemoryAllocateInfo allocDesc;\n   allocDesc.allocationSize = imageMemReqs.size;\n   allocDesc.memoryTypeIndex = findMemoryType(imageMemReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal);\n   auto imageMem = mDevice.allocateMemory(allocDesc);\n\n   mDevice.bindImageMemory(emptyImage, imageMem, 0);\n\n   vk::ImageViewCreateInfo imageViewDesc;\n   imageViewDesc.image = emptyImage;\n   imageViewDesc.viewType = vk::ImageViewType::e2D;\n   imageViewDesc.format = vk::Format::eR8G8B8A8Snorm;\n   imageViewDesc.components = vk::ComponentMapping();\n   imageViewDesc.subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 };\n   auto emptyImageView = mDevice.createImageView(imageViewDesc);\n\n   setVkObjectName(emptyImageView, \"PlaceholderView\");\n\n   mBlankImage = emptyImage;\n   mBlankImageView = emptyImageView;\n}\n\nvoid\nDriver::initialiseBlankBuffer()\n{\n   vk::BufferCreateInfo bufferDesc;\n   bufferDesc.size = 1024;\n   bufferDesc.usage =\n      vk::BufferUsageFlagBits::eUniformBuffer |\n      vk::BufferUsageFlagBits::eTransferDst |\n      vk::BufferUsageFlagBits::eTransferSrc;\n   bufferDesc.sharingMode = vk::SharingMode::eExclusive;\n   bufferDesc.queueFamilyIndexCount = 0;\n   bufferDesc.pQueueFamilyIndices = nullptr;\n\n   VmaAllocationCreateInfo allocInfo = {};\n   allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n\n   VkBuffer emptyBuffer;\n   VmaAllocation allocation;\n   vmaCreateBuffer(mAllocator,\n                   reinterpret_cast<VkBufferCreateInfo*>(&bufferDesc),\n                   &allocInfo,\n                   &emptyBuffer,\n                   &allocation,\n                   nullptr);\n\n   setVkObjectName(emptyBuffer, \"PlaceholderBuffer\");\n\n   mBlankBuffer = emptyBuffer;\n}\n\nvoid\nDriver::setupResources()\n{\n   vk::CommandBufferAllocateInfo cmdBufferAllocDesc(mCommandPool, vk::CommandBufferLevel::ePrimary, 1);\n   auto cmdBuffer = mDevice.allocateCommandBuffers(cmdBufferAllocDesc)[0];\n\n   cmdBuffer.begin(vk::CommandBufferBeginInfo {});\n\n   {\n      vk::ImageMemoryBarrier imageBarrier;\n      imageBarrier.srcAccessMask = vk::AccessFlags();\n      imageBarrier.dstAccessMask = vk::AccessFlags();\n      imageBarrier.oldLayout = vk::ImageLayout::eUndefined;\n      imageBarrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;\n      imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n      imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n      imageBarrier.image = mBlankImage;\n      imageBarrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;\n      imageBarrier.subresourceRange.baseMipLevel = 0;\n      imageBarrier.subresourceRange.levelCount = 1;\n      imageBarrier.subresourceRange.baseArrayLayer = 0;\n      imageBarrier.subresourceRange.layerCount = 1;\n\n      cmdBuffer.pipelineBarrier(\n         vk::PipelineStageFlagBits::eAllCommands,\n         vk::PipelineStageFlagBits::eAllCommands,\n         vk::DependencyFlags(),\n         {},\n         {},\n         { imageBarrier });\n   }\n\n   cmdBuffer.end();\n\n   vk::SubmitInfo submitInfo;\n   submitInfo.commandBufferCount = 1;\n   submitInfo.pCommandBuffers = &cmdBuffer;\n   mQueue.submit({ submitInfo }, vk::Fence());\n}\n\nvk::DescriptorPool\nDriver::allocateDescriptorPool(uint32_t numDraws)\n{\n   vk::DescriptorPool descriptorPool;\n\n   if (!descriptorPool) {\n      if (!mDescriptorPools.empty()) {\n         descriptorPool = mDescriptorPools.back();\n         mDescriptorPools.pop_back();\n      }\n   }\n\n   if (!descriptorPool) {\n      std::vector<vk::DescriptorPoolSize> descriptorPoolSizes = {\n         vk::DescriptorPoolSize(vk::DescriptorType::eSampler, latte::MaxSamplers * 3 * numDraws),\n         vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, latte::MaxTextures * 3 * numDraws),\n         vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, latte::MaxUniformBlocks * 3 * numDraws),\n      };\n\n      vk::DescriptorPoolCreateInfo descriptorPoolInfo;\n      descriptorPoolInfo.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size());\n      descriptorPoolInfo.pPoolSizes = descriptorPoolSizes.data();\n      descriptorPoolInfo.maxSets = static_cast<uint32_t>(numDraws);\n      descriptorPool = mDevice.createDescriptorPool(descriptorPoolInfo);\n   }\n\n   mActiveSyncWaiter->descriptorPools.push_back(descriptorPool);\n\n   return descriptorPool;\n}\n\nvk::DescriptorSet\nDriver::allocateGenericDescriptorSet()\n{\n   if (mAvailableDescriptorSets.empty()) {\n      std::array<vk::DescriptorSetLayout, 32> setLayouts = {\n         mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout,\n         mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout,\n         mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout,\n         mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout,\n         mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout,\n         mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout,\n         mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout,\n         mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout, mBaseDescriptorSetLayout\n      };\n      auto numSetLayouts = static_cast<uint32_t>(setLayouts.size());\n\n      auto newPool = allocateDescriptorPool(numSetLayouts);\n\n      vk::DescriptorSetAllocateInfo allocInfo;\n      allocInfo.descriptorSetCount = numSetLayouts;\n      allocInfo.pSetLayouts = setLayouts.data();\n      allocInfo.descriptorPool = newPool;\n      mAvailableDescriptorSets = mDevice.allocateDescriptorSets(allocInfo);\n   }\n\n   auto descriptorSet = mAvailableDescriptorSets.back();\n   mAvailableDescriptorSets.pop_back();\n\n   return descriptorSet;\n}\n\nvoid\nDriver::retireDescriptorPool(vk::DescriptorPool descriptorPool)\n{\n   mDevice.resetDescriptorPool(descriptorPool, vk::DescriptorPoolResetFlags());\n   mDescriptorPools.push_back(descriptorPool);\n}\n\nvk::QueryPool\nDriver::allocateOccQueryPool()\n{\n   vk::QueryPool occPool;\n\n   if (!occPool) {\n      if (!mOccQueryPools.empty()) {\n         occPool = mOccQueryPools.back();\n         mOccQueryPools.pop_back();\n      }\n   }\n\n   if (!occPool) {\n      vk::QueryPoolCreateInfo queryDesc;\n      queryDesc.queryType = vk::QueryType::eOcclusion;\n      queryDesc.queryCount = 1;\n      occPool  = mDevice.createQueryPool(queryDesc);\n   }\n\n   mActiveCommandBuffer.resetQueryPool(occPool, 0, 1);\n\n   mActiveSyncWaiter->occQueryPools.push_back(occPool);\n\n   return occPool;\n}\n\nvoid\nDriver::retireOccQueryPool(vk::QueryPool pool)\n{\n   mOccQueryPools.push_back(pool);\n}\n\nvoid\nDriver::run()\n{\n   while (mRunState == RunState::Running) {\n      // Grab the next buffer\n      gpu::ringbuffer::wait();\n      auto buffer = gpu::ringbuffer::read();\n\n      // Check for any fences completing\n      checkSyncFences();\n\n      // Process the buffer if there is anything new\n      if (!buffer.empty()) {\n         executeBuffer(buffer);\n      }\n   }\n\n   destroy();\n}\n\nvoid\nDriver::runUntilFlip()\n{\n   auto startingSwap = mLastSwap;\n\n   while (mRunState == RunState::Running) {\n      // Grab the next item\n      gpu::ringbuffer::wait();\n\n      // Check for any fences completing\n      checkSyncFences();\n\n      // Process the buffer if there is anything new\n      auto buffer = gpu::ringbuffer::read();\n      if (!buffer.empty()) {\n         executeBuffer(buffer);\n      }\n\n      if (mLastSwap > startingSwap) {\n         break;\n      }\n   }\n}\n\nvoid\nDriver::stop()\n{\n   mRunState = RunState::Stopped;\n   gpu::ringbuffer::wake();\n}\n\nvoid\nDriver::beginCommandGroup()\n{\n   mActiveBatchIndex++;\n   mMemTracker.nextBatch();\n\n   mActiveSyncWaiter = allocateSyncWaiter();\n   mActiveCommandBuffer = mActiveSyncWaiter->cmdBuffer;\n}\n\nvoid\nDriver::endCommandGroup()\n{\n   // Submit the active waiter to the queue\n   submitSyncWaiter(mActiveSyncWaiter);\n\n   // Clear our state in between command buffers for safety\n   mActiveCommandBuffer = nullptr;\n   mActiveSyncWaiter = nullptr;\n   mAvailableDescriptorSets.clear();\n}\n\nvoid\nDriver::beginCommandBuffer()\n{\n   // Begin recording our host command buffer\n   mActiveCommandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit));\n}\n\nvoid\nDriver::endCommandBuffer()\n{\n   // Flush our pending draws\n   flushPendingDraws();\n\n   // We have to force our memcache objects to be downloaded at the\n   // end of every PM4 buffer.\n   downloadPendingMemCache();\n\n   // Clear our per-command-buffer state\n   mActivePipeline = nullptr;\n   mActiveRenderPass = nullptr;\n   mActiveFramebuffer = nullptr;\n   mActiveVsConstantsSet = false;\n   mActivePsConstantsSet = false;\n   mLastIndexBufferSet = false;\n   mDrawCache = DrawDesc{};\n\n   // Stop recording this host command buffer\n   mActiveCommandBuffer.end();\n}\n\nint32_t\nDriver::findMemoryType(uint32_t memTypeBits, vk::MemoryPropertyFlags requestProps)\n{\n   auto memoryProps = mPhysDevice.getMemoryProperties();\n\n   const uint32_t memoryCount = memoryProps.memoryTypeCount;\n   for (uint32_t memoryIndex = 0; memoryIndex < memoryCount; ++memoryIndex) {\n      const uint32_t memoryTypeBits = (1 << memoryIndex);\n      const bool isRequiredMemoryType = memTypeBits & memoryTypeBits;\n\n      const auto properties = memoryProps.memoryTypes[memoryIndex].propertyFlags;\n      const bool hasRequiredProperties = (properties & requestProps) == requestProps;\n\n      if (isRequiredMemoryType && hasRequiredProperties)\n         return static_cast<int32_t>(memoryIndex);\n   }\n\n   throw std::logic_error(\"failed to find suitable memory type\");\n}\n\nResourceUsageMeta\nDriver::getResourceUsageMeta(ResourceUsage usage)\n{\n   switch (usage) {\n   case ResourceUsage::Undefined:\n      return { false,\n               vk::AccessFlags(),\n               vk::PipelineStageFlagBits::eBottomOfPipe,\n               vk::ImageLayout::eUndefined };\n\n   case ResourceUsage::VertexTexture:\n      return { false,\n               vk::AccessFlagBits::eShaderRead,\n               vk::PipelineStageFlagBits::eVertexShader,\n               vk::ImageLayout::eShaderReadOnlyOptimal };\n   case ResourceUsage::GeometryTexture:\n      return { false,\n               vk::AccessFlagBits::eShaderRead,\n               vk::PipelineStageFlagBits::eGeometryShader,\n               vk::ImageLayout::eShaderReadOnlyOptimal };\n   case ResourceUsage::PixelTexture:\n      return { false,\n               vk::AccessFlagBits::eShaderRead,\n               vk::PipelineStageFlagBits::eFragmentShader,\n               vk::ImageLayout::eShaderReadOnlyOptimal };\n   case ResourceUsage::IndexBuffer:\n      return { false,\n               vk::AccessFlagBits::eIndexRead,\n               vk::PipelineStageFlagBits::eVertexInput,\n               vk::ImageLayout::eUndefined };\n   case ResourceUsage::VertexUniforms:\n      return { false,\n               vk::AccessFlagBits::eUniformRead,\n               vk::PipelineStageFlagBits::eVertexShader,\n               vk::ImageLayout::eUndefined };\n   case ResourceUsage::GeometryUniforms:\n      return { false,\n               vk::AccessFlagBits::eUniformRead,\n               vk::PipelineStageFlagBits::eGeometryShader,\n               vk::ImageLayout::eUndefined };\n   case ResourceUsage::PixelUniforms:\n      return { false,\n               vk::AccessFlagBits::eUniformRead,\n               vk::PipelineStageFlagBits::eFragmentShader,\n               vk::ImageLayout::eUndefined };\n   case ResourceUsage::AttributeBuffer:\n      return { false,\n               vk::AccessFlagBits::eVertexAttributeRead,\n               vk::PipelineStageFlagBits::eVertexInput,\n               vk::ImageLayout::eUndefined };\n   case ResourceUsage::StreamOutCounterRead:\n      return { false,\n               vk::AccessFlagBits::eTransformFeedbackCounterReadEXT,\n               vk::PipelineStageFlagBits::eDrawIndirect,\n               vk::ImageLayout::eUndefined };\n   case ResourceUsage::ComputeSsboRead:\n      return { false,\n               vk::AccessFlagBits::eShaderRead,\n               vk::PipelineStageFlagBits::eComputeShader,\n               vk::ImageLayout::eUndefined };\n   case ResourceUsage::TransferSrc:\n      return { false,\n               vk::AccessFlagBits::eTransferRead,\n               vk::PipelineStageFlagBits::eTransfer,\n               vk::ImageLayout::eTransferSrcOptimal };\n   case ResourceUsage::HostRead:\n      return { false,\n               vk::AccessFlagBits::eHostRead,\n               vk::PipelineStageFlagBits::eHost,\n               vk::ImageLayout::eUndefined };\n\n   case ResourceUsage::ColorAttachment:\n      return { true,\n               vk::AccessFlagBits::eColorAttachmentWrite,\n               vk::PipelineStageFlagBits::eColorAttachmentOutput,\n               vk::ImageLayout::eColorAttachmentOptimal };\n   case ResourceUsage::DepthStencilAttachment:\n      return { true,\n               vk::AccessFlagBits::eDepthStencilAttachmentWrite,\n               vk::PipelineStageFlagBits::eLateFragmentTests,\n               vk::ImageLayout::eDepthStencilAttachmentOptimal };\n   case ResourceUsage::StreamOutBuffer:\n      return { true,\n               vk::AccessFlagBits::eTransformFeedbackWriteEXT,\n               vk::PipelineStageFlagBits::eTransformFeedbackEXT,\n               vk::ImageLayout::eUndefined };\n   case ResourceUsage::StreamOutCounterWrite:\n      return { true,\n               vk::AccessFlagBits::eTransformFeedbackCounterWriteEXT,\n               vk::PipelineStageFlagBits::eTransformFeedbackEXT,\n               vk::ImageLayout::eUndefined };\n   case ResourceUsage::ComputeSsboWrite:\n      return { true,\n               vk::AccessFlagBits::eShaderWrite,\n               vk::PipelineStageFlagBits::eComputeShader,\n               vk::ImageLayout::eUndefined };\n   case ResourceUsage::TransferDst:\n      return { true,\n               vk::AccessFlagBits::eTransferWrite,\n               vk::PipelineStageFlagBits::eTransfer,\n               vk::ImageLayout::eTransferDstOptimal };\n   case ResourceUsage::HostWrite:\n      return { true,\n               vk::AccessFlagBits::eHostWrite,\n               vk::PipelineStageFlagBits::eHost,\n               vk::ImageLayout::eUndefined };\n   default:\n      decaf_abort(\"Unexpected resource usage\");\n   }\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_driver.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n#include \"gpu_graphicsdriver.h\"\n#include \"gpu_ringbuffer.h\"\n#include \"gpu_vulkandriver.h\"\n#include \"gpu7_tiling.h\"\n#include \"gpu7_tiling_vulkan.h\"\n#include \"latte/latte_formats.h\"\n#include \"latte/latte_constants.h\"\n#include \"spirv/spirv_translate.h\"\n#include \"spirv/spirv_pushconstants.h\"\n#include \"pm4_processor.h\"\n#include \"vk_mem_alloc_decaf.h\"\n#include \"vulkan_descs.h\"\n#include \"vulkan_memtracker.h\"\n\n#include <atomic>\n#include <common/vulkan_hpp.h>\n#include <condition_variable>\n#include <functional>\n#include <gsl/gsl-lite.hpp>\n#include <list>\n#include <map>\n#include <mutex>\n#include <thread>\n#include <unordered_map>\n\n#define STRINGIFY(x) #x\n#define TOSTRING(x) STRINGIFY(x)\n\n// Evaluate f and if result is not a success throw proper vk exception.\n#define CHECK_VK_RESULT(x) do { \\\n   vk::resultCheck(static_cast<vk::Result>(x), __FILE__ \":\" TOSTRING(__LINE__)); \\\n} while (0)\n\nnamespace vulkan\n{\n\ntemplate<typename T>\nclass HashedDesc\n{\npublic:\n   HashedDesc()\n   {\n   }\n\n   HashedDesc(const T& desc)\n      : mHash(desc.hash()), mDesc(desc)\n   {\n   }\n\n   const T& operator*() const\n   {\n      return mDesc;\n   }\n\n   const T * operator->() const\n   {\n      return &mDesc;\n   }\n\n   DataHash hash() const\n   {\n      return mHash;\n   }\n\n   bool operator==(const HashedDesc<T>& other) const\n   {\n      return hash() == other.hash();\n   }\n\nprivate:\n   DataHash mHash;\n   T mDesc;\n\n};\n\nenum class ResourceUsage : uint32_t\n{\n   Undefined,\n   ColorAttachment,\n   DepthStencilAttachment,\n   VertexTexture,\n   GeometryTexture,\n   PixelTexture,\n   VertexUniforms,\n   GeometryUniforms,\n   PixelUniforms,\n   IndexBuffer,\n   AttributeBuffer,\n   StreamOutBuffer,\n   StreamOutCounterRead,\n   StreamOutCounterWrite,\n   ComputeSsboRead,\n   ComputeSsboWrite,\n   TransferSrc,\n   TransferDst,\n   HostWrite,\n   HostRead\n};\n\nstruct ResourceUsageMeta\n{\n   bool isWrite;\n   vk::AccessFlags accessFlags;\n   vk::PipelineStageFlags stageFlags;\n   vk::ImageLayout imageLayout;\n};\n\ntypedef std::function<void()> DelayedMemWriteFunc;\n\nstruct MemCacheObject;\ntypedef MemoryTracker<MemCacheObject*> DriverMemoryTracker;\ntypedef DriverMemoryTracker::SegmentRef MemSegmentRef;\ntypedef DriverMemoryTracker::Segment MemSegment;\n\nstruct SectionRange\n{\n   uint32_t start = 0;\n   uint32_t count = 0;\n\n   inline bool covers(const SectionRange& other) const\n   {\n      auto end = start + count;\n      auto other_end = other.start + other.count;\n      return start <= other.start && end >= other_end;\n   }\n\n   inline bool intersects(const SectionRange& other) const\n   {\n      auto end = start + count;\n      auto other_end = other.start + other.count;\n      return !(start >= other_end || end <= other.start);\n   }\n};\n\nstruct MemCacheSection\n{\n   // Pointer into the segments map for this memory range\n   MemSegmentRef firstSegment;\n\n   // Records the last change index for this data\n   uint64_t lastChangeIndex;\n\n   // Records some information used to optimize the uploading and\n   // copying of segments between different cache objects.\n   uint64_t wantedChangeIndex;\n   bool needsUpload;\n};\n\nstruct MemCacheObject\n{\n   // Meta-data about what this cache object represents.\n   phys_addr address;\n   uint32_t size;\n   uint32_t numSections;\n   uint32_t sectionSize;\n   ResourceUsage activeUsage;\n\n   // The buffer data.\n   VmaAllocation allocation;\n   vk::Buffer buffer;\n\n   // The various sections that make up this buffer\n   std::vector<MemCacheSection> sections;\n\n   // Stores a function used to update this buffer when the cost of\n   // generating the new data is significant and we want to avoid it\n   // unless the data ends up actually being needed.\n   DelayedMemWriteFunc delayedWriteFunc;\n   SectionRange delayedWriteRange;\n\n   // Records the last PM4 context which refers to this data.\n   uint64_t lastUsageIndex;\n\n   // Records the number of external objects relying on this...\n   uint64_t refCount;\n\n   // Intrusive linked list to enable us to chain together multiple\n   // objects with different section layouts for faster lookup.\n   MemCacheObject *nextObject;\n};\n\nstruct MemChangeRecord\n{\n   uint64_t changeIndex;\n   MemCacheObject *cache;\n   SectionRange sections;\n};\n\nstruct DataBufferObject : MemCacheObject { };\n\nenum class ShaderStage\n{\n   Vertex = 0,\n   Geometry = 1,\n   Pixel = 2\n};\n\nstruct VertexShaderObject\n{\n   HashedDesc<spirv::VertexShaderDesc> desc;\n   spirv::VertexShader shader;\n   vk::ShaderModule module;\n};\n\nstruct GeometryShaderObject\n{\n   HashedDesc<spirv::GeometryShaderDesc> desc;\n   spirv::GeometryShader shader;\n   vk::ShaderModule module;\n};\n\nstruct PixelShaderObject\n{\n   HashedDesc<spirv::PixelShaderDesc> desc;\n   spirv::PixelShader shader;\n   vk::ShaderModule module;\n};\n\nstruct RectStubShaderObject\n{\n   HashedDesc<spirv::RectStubShaderDesc> desc;\n   spirv::RectStubShader shader;\n   vk::ShaderModule module;\n};\n\nenum class StagingBufferType : uint32_t\n{\n   CpuToGpu = 0,\n   GpuToCpu = 1,\n   GpuToGpu = 2\n};\n\nstruct StagingBuffer\n{\n   StagingBufferType type;\n   uint32_t poolIndex;\n   uint32_t maximumSize;\n   uint32_t size;\n   ResourceUsage activeUsage;\n   vk::Buffer buffer;\n   VmaAllocation memory;\n   void *mappedPtr;\n};\n\nstruct SyncWaiter\n{\n   bool isCompleted = false;\n   vk::Fence fence;\n   std::vector<vk::DescriptorPool> descriptorPools;\n   std::vector<vk::QueryPool> occQueryPools;\n   std::vector<gpu7::tiling::vulkan::RetileHandle> retileHandles;\n   std::vector<StagingBuffer *> stagingBuffers;\n   std::vector<std::function<void()>> callbacks;\n\n   vk::CommandBuffer cmdBuffer;\n};\n\nstruct SurfaceSubRange\n{\n   uint32_t firstSlice;\n   uint32_t numSlices;\n};\n\nstruct SurfaceObject;\n\nstruct SurfaceGroupObject\n{\n   SurfaceDesc desc;\n\n   std::list<SurfaceObject *> surfaces;\n   std::vector<SurfaceObject *> sliceOwners;\n};\n\nstruct SurfaceSlice\n{\n   uint64_t lastChangeIndex;\n};\n\nstruct SurfaceObject\n{\n   SurfaceDesc desc;\n\n   SurfaceGroupObject *group;\n   uint64_t lastUsageIndex;\n\n   uint32_t pitch;\n   uint32_t width;\n   uint32_t height;\n   uint32_t depth;\n   uint32_t arrayLayers;\n\n   gpu7::tiling::SurfaceDescription tilingDesc;\n   gpu7::tiling::SurfaceInfo tilingInfo;\n\n   std::vector<SurfaceSlice> slices;\n\n   MemCacheObject *memCache;\n   ResourceUsage activeUsage;\n\n   vk::Image image;\n   vk::DeviceMemory imageMem;\n   vk::BufferImageCopy bufferRegion;\n   vk::ImageSubresourceRange subresRange;\n};\n\nstruct SurfaceViewObject\n{\n   HashedDesc<SurfaceViewDesc> desc;\n   SurfaceObject *surface;\n\n   SurfaceSubRange surfaceRange;\n\n   vk::Image boundImage;\n   vk::ImageView imageView;\n   vk::ImageSubresourceRange subresRange;\n};\n\nstruct FramebufferObject\n{\n   HashedDesc<FramebufferDesc> desc;\n\n   std::array<SurfaceViewObject*, latte::MaxRenderTargets> colorSurfaces;\n   SurfaceViewObject *depthSurface;\n\n   vk::Extent2D renderArea;\n   std::array<vk::ImageView, 9> boundViews;\n   vk::Framebuffer framebuffer;\n};\n\nstruct SamplerObject\n{\n   HashedDesc<SamplerDesc> desc;\n   vk::Sampler sampler;\n};\n\n// SwapChainObjects are backed by a surface, but expose less things\n// as there are more specific guarentees provided, such as:\n//  - layout is always TransferDstOptimal\nstruct SwapChainObject\n{\n   SwapChainDesc desc;\n   bool presentable;\n   vk::Image image;\n   vk::ImageView imageView;\n   vk::ImageSubresourceRange subresRange;\n   SurfaceObject *_surface;\n};\n\nstruct RenderPassObject\n{\n   HashedDesc<RenderPassDesc> desc;\n   vk::RenderPass renderPass;\n\n   std::array<int, latte::MaxRenderTargets> colorAttachmentIndexes;\n   int depthAttachmentIndex;\n};\n\nstruct PipelineLayoutObject\n{\n   HashedDesc<PipelineLayoutDesc> desc;\n   vk::DescriptorSetLayout descriptorLayout;\n   vk::PipelineLayout pipelineLayout;\n};\n\nstruct PipelineObject\n{\n   HashedDesc<PipelineDesc> desc;\n   PipelineLayoutObject *pipelineLayout;\n   vk::Pipeline pipeline;\n   bool needsPremultipliedTargets;\n   std::array<bool, latte::MaxRenderTargets> targetIsPremultiplied;\n   uint32_t shaderLopMode;\n   uint32_t shaderAlphaFunc;\n   float shaderAlphaRef;\n};\n\nstruct StreamContextObject\n{\n   VmaAllocation allocation;\n   vk::Buffer buffer;\n};\n\nstruct ShaderViewportData\n{\n   float xAdd, xMul;\n   float yAdd, yMul;\n   float zAdd, zMul;\n};\n\nstruct IndexBufferCache\n{\n   latte::VGT_DI_PRIMITIVE_TYPE primitiveType;\n   latte::VGT_INDEX_TYPE indexType;\n   latte::VGT_DMA_SWAP swapMode;\n   uint32_t numIndices;\n   void *indexData;\n\n   latte::VGT_DI_PRIMITIVE_TYPE newPrimitiveType;\n   uint32_t newNumIndices;\n   StagingBuffer *indexBuffer;\n};\n\nstruct DrawDesc\n{\n   void *indices;\n   latte::VGT_INDEX_TYPE indexType;\n   latte::VGT_DMA_SWAP indexSwapMode;\n   latte::VGT_DI_PRIMITIVE_TYPE primitiveType;\n   uint32_t pointSize;\n   uint32_t numIndices;\n   uint32_t baseVertex;\n   uint32_t numInstances;\n   uint32_t baseInstance;\n\n   bool streamOutEnabled = false;\n   MemCacheObject *opaqueBuffer = nullptr;\n   uint32_t opaqueStride = 0;\n\n   vk::Viewport viewport;\n   ShaderViewportData shaderViewportData;\n   vk::Rect2D scissor;\n   StagingBuffer *indexBuffer = nullptr;\n   VertexShaderObject *vertexShader = nullptr;\n   GeometryShaderObject *geometryShader = nullptr;\n   PixelShaderObject *pixelShader = nullptr;\n   RectStubShaderObject *rectStubShader = nullptr;\n   FramebufferObject *framebuffer = nullptr;\n   RenderPassObject *renderPass = nullptr;\n   PipelineObject *pipeline = nullptr;\n   bool framebufferDirty = true;\n   std::array<std::array<bool, latte::MaxTextures>, 3> textureDirty = { { true } };\n   std::array<DataBufferObject*, latte::MaxAttribBuffers> attribBuffers = { nullptr };\n   std::array<std::array<SamplerObject*, latte::MaxSamplers>, 3> samplers = { { nullptr } };\n   std::array<std::array<SurfaceViewObject*, latte::MaxTextures>, 3> textures = { { nullptr } };\n   std::array<StagingBuffer*, 3> gprBuffers = { nullptr };\n   std::array<std::array<DataBufferObject*, latte::MaxUniformBlocks>, 3> uniformBlocks = { { nullptr } };\n   std::array<StreamContextObject*, latte::MaxStreamOutBuffers> streamOutContext = { nullptr };\n   std::array<DataBufferObject*, latte::MaxStreamOutBuffers> streamOutBuffers = { nullptr };\n};\n\nstruct VulkanDisplayPipeline\n{\n   vk::SurfaceKHR windowSurface;\n   vk::Format windowSurfaceFormat;\n   uint32_t queueFamilyIndex;\n\n   vk::RenderPass renderPass;\n\n   // Swapchain\n   vk::PresentModeKHR presentMode;\n   vk::SwapchainKHR swapchain;\n   vk::Extent2D swapchainExtents;\n   std::vector<vk::ImageView> swapchainImageViews;\n   std::vector<vk::Framebuffer> framebuffers;\n\n   vk::Sampler trivialSampler;\n   vk::DescriptorSetLayout descriptorSetLayout;\n   vk::PipelineLayout pipelineLayout;\n   vk::Pipeline graphicsPipeline;\n\n   vk::ShaderModule vertexShader;\n   vk::ShaderModule fragmentShader;\n\n   vk::Buffer vertexBuffer;\n   vk::DeviceMemory vertexBufferMemory;\n\n   vk::DescriptorPool descriptorPool;\n   std::vector<vk::DescriptorSet> descriptorSets;\n\n   int frameIndex;\n   std::vector<vk::Fence> renderFences;\n   std::vector<vk::Semaphore> imageAvailableSemaphores;\n   std::vector<vk::Semaphore> renderFinishedSemaphores;\n};\n\nclass Driver : public gpu::GraphicsDriver, public Pm4Processor\n{\npublic:\n   Driver();\n   virtual ~Driver();\n\n   virtual void setWindowSystemInfo(const gpu::WindowSystemInfo &wsi) override;\n   virtual void windowHandleChanged(void *handle) override;\n   virtual void windowSizeChanged(int width, int height) override;\n\n   virtual void run() override;\n   virtual void runUntilFlip() override;\n   virtual void stop() override;\n\n   virtual gpu::GraphicsDriverType type() override;\n   virtual gpu::GraphicsDriverDebugInfo *getDebugInfo() override;\n\n   virtual void notifyCpuFlush(phys_addr address, uint32_t size) override;\n   virtual void notifyGpuFlush(phys_addr address, uint32_t size) override;\n\nprotected:\n   void initialise(vk::Instance instance, vk::PhysicalDevice physDevice,\n                   vk::Device device, vk::Queue queue,\n                   uint32_t queueFamilyIndex);\n   void destroy();\n   void initialiseBlankSampler();\n   void initialiseBlankImage();\n   void initialiseBlankBuffer();\n   void setupResources();\n   void updateDebuggerInfo();\n   void validateDevice();\n\n   ResourceUsageMeta getResourceUsageMeta(ResourceUsage usage);\n   void renderDisplay();\n   void destroyDisplayPipeline();\n\n   // Command Buffer Stuff\n   void beginCommandGroup();\n   void endCommandGroup();\n   void beginCommandBuffer();\n   void endCommandBuffer();\n\n   // Descriptor Sets\n   vk::DescriptorPool allocateDescriptorPool(uint32_t numDraws);\n   vk::DescriptorSet allocateGenericDescriptorSet();\n   void retireDescriptorPool(vk::DescriptorPool descriptorPool);\n\n   // Fences\n   SyncWaiter * allocateSyncWaiter();\n   void releaseSyncWaiter(SyncWaiter *syncWaiter);\n   void submitSyncWaiter(SyncWaiter *syncWaiter);\n   void executeSyncWaiter(SyncWaiter *syncWaiter);\n   void fenceWaiterThread();\n   void checkSyncFences();\n   void addRetireTask(std::function<void()> fn);\n\n   // Retiling\n   void dispatchGpuTile(const gpu7::tiling::RetileInfo& retileInfo,\n                        vk::CommandBuffer &commandBuffer,\n                        vk::Buffer dstBuffer, uint32_t dstOffset,\n                        vk::Buffer srcBuffer, uint32_t srcOffset,\n                        uint32_t firstSlice, uint32_t numSlices);\n   void dispatchGpuUntile(const gpu7::tiling::RetileInfo& retileInfo,\n                          vk::CommandBuffer& commandBuffer,\n                          vk::Buffer dstBuffer, uint32_t dstOffset,\n                          vk::Buffer srcBuffer, uint32_t srcOffset,\n                          uint32_t firstSlice, uint32_t numSlices);\n\n   // Query Pools\n   vk::QueryPool allocateOccQueryPool();\n   void retireOccQueryPool(vk::QueryPool pool);\n\n   // Driver\n   void executeBuffer(const gpu::ringbuffer::Buffer &buffer);\n   int32_t findMemoryType(uint32_t memoryTypeBits, vk::MemoryPropertyFlags props);\n\n   // Viewports\n   bool checkCurrentViewportAndScissor();\n   void bindViewportAndScissor();\n\n   // Samplers\n   SamplerDesc getSamplerDesc(ShaderStage shaderStage, uint32_t samplerIdx);\n   void updateDrawSampler(ShaderStage shaderStage, uint32_t samplerIdx);\n   bool checkCurrentSamplers();\n\n   // Textures\n   SurfaceViewDesc getTextureDesc(ShaderStage shaderStage, uint32_t textureIdx);\n   void updateDrawTexture(ShaderStage shaderStage, uint32_t textureIdx);\n   bool checkCurrentTextures();\n   void prepareCurrentTextures();\n\n   // CBuffers\n   void updateDrawUniformBuffer(ShaderStage shaderStage, uint32_t cbufferIdx);\n   void updateDrawGprBuffer(ShaderStage shaderStage);\n   bool checkCurrentShaderBuffers();\n\n   MemCacheObject * _allocMemCache(phys_addr address, uint32_t numSections, uint32_t sectionSize);\n   void _uploadMemCache(MemCacheObject *cache, SectionRange sections);\n   void _downloadMemCache(MemCacheObject *cache, SectionRange sections);\n   void _refreshMemCache_Check(MemCacheObject *cache, SectionRange sections);\n   void _refreshMemCache_Update(MemCacheObject *cache, SectionRange sections);\n   void _refreshMemCache(MemCacheObject *cache, SectionRange sections);\n   void _invalidateMemCache(MemCacheObject *cache, SectionRange sections, const DelayedMemWriteFunc& delayedWriteHandler);\n   void _barrierMemCache(MemCacheObject *cache, ResourceUsage usage, SectionRange sections);\n   SectionRange _sectionsFromOffsets(MemCacheObject *cache, uint32_t begin, uint32_t end);\n   MemCacheObject * getMemCache(phys_addr address, uint32_t numSections, uint32_t sectionSize);\n   void invalidateMemCacheDelayed(MemCacheObject *cache, uint32_t offset, uint32_t size, const DelayedMemWriteFunc& delayedWriteHandler);\n   void transitionMemCache(MemCacheObject *cache, ResourceUsage usage, uint32_t offset = 0, uint32_t size = 0);\n   DataBufferObject * getDataMemCache(phys_addr baseAddress, uint32_t size);\n   void downloadPendingMemCache();\n\n   // Staging\n   StagingBuffer * _allocStagingBuffer(uint32_t size, StagingBufferType type);\n   StagingBuffer * getStagingBuffer(uint32_t size, StagingBufferType type);\n   void retireStagingBuffer(StagingBuffer *sbuffer);\n   void transitionStagingBuffer(StagingBuffer *sbuffer, ResourceUsage usage);\n   void copyToStagingBuffer(StagingBuffer *sbuffer, uint32_t offset, const void *data, uint32_t size);\n   void copyFromStagingBuffer(StagingBuffer *sbuffer, uint32_t offset, void *data, uint32_t size);\n\n   // Surfaces\n   MemCacheObject * _getSurfaceMemCache(const SurfaceDesc &info, const gpu7::tiling::SurfaceInfo& tilingInfo);\n   void _copySurface(SurfaceObject *dst, SurfaceObject *src, SurfaceSubRange range);\n\n   SurfaceGroupObject * _allocateSurfaceGroup(const SurfaceDesc &info);\n   void _releaseSurfaceGroup(SurfaceGroupObject *surfaceGroup);\n   void _addSurfaceGroupSurface(SurfaceGroupObject *surfaceGroup, SurfaceObject *surface);\n   void _removeSurfaceGroupSurface(SurfaceGroupObject *surfaceGroup, SurfaceObject *surface);\n   void _updateSurfaceGroupSlice(SurfaceGroupObject *surfaceGroup, uint32_t sliceId, SurfaceObject *surface);\n   SurfaceObject * _getSurfaceGroupOwner(SurfaceGroupObject *surfaceGroup, uint32_t sliceId, uint64_t minChangeIndex);\n   SurfaceGroupObject * _getSurfaceGroup(const SurfaceDesc &info);\n\n   SurfaceObject * _allocateSurface(const SurfaceDesc &info);\n   void _releaseSurface(SurfaceObject *surface);\n   void _upgradeSurface(SurfaceObject *surface, const SurfaceDesc &info);\n   void _readSurfaceData(SurfaceObject *surface, SurfaceSubRange range);\n   void _writeSurfaceData(SurfaceObject *surface, SurfaceSubRange range);\n   void _refreshSurface(SurfaceObject *surface, SurfaceSubRange range);\n   void _invalidateSurface(SurfaceObject *surface, SurfaceSubRange range);\n   void _barrierSurface(SurfaceObject *surface, ResourceUsage usage, vk::ImageLayout layout, SurfaceSubRange range);\n   SurfaceObject * getSurface(const SurfaceDesc& info);\n   void transitionSurface(SurfaceObject *surface, ResourceUsage usage, vk::ImageLayout layout, SurfaceSubRange range, bool skipChangeCheck = false);\n\n   SurfaceViewObject * _allocateSurfaceView(const SurfaceViewDesc& info);\n   void _releaseSurfaceView(SurfaceViewObject *surfaceView);\n   SurfaceViewObject * getSurfaceView(const SurfaceViewDesc& info);\n   void transitionSurfaceView(SurfaceViewObject *surfaceView, ResourceUsage usage, vk::ImageLayout layout, bool skipChangeCheck = false);\n\n   // Vertex Buffers\n   VertexBufferDesc getAttribBufferDesc(uint32_t bufferIndex);\n   bool checkCurrentAttribBuffers();\n   void bindAttribBuffers();\n\n   // Indices\n   void maybeSwapIndices();\n   void maybeUnpackPrimitiveIndices();\n   bool checkCurrentIndices();\n   void bindIndexBuffer();\n\n   // Draws\n   void bindDescriptors();\n   void bindShaderParams();\n   void drawGenericIndexed(latte::VGT_DRAW_INITIATOR drawInit, uint32_t numIndices, void *indices);\n   void flushPendingDraws();\n   void drawCurrentState();\n\n   // Framebuffers\n   FramebufferDesc getFramebufferDesc();\n   bool checkCurrentFramebuffer();\n   SurfaceViewObject * getColorBuffer(const ColorBufferDesc& info);\n   SurfaceViewObject * getDepthStencilBuffer(const DepthStencilBufferDesc& info);\n   void prepareCurrentFramebuffer();\n\n   // Swap Chains\n   SwapChainObject * allocateSwapChain(const SwapChainDesc &desc);\n   void releaseSwapChain(SwapChainObject *swapChain);\n\n   // Shaders\n   spirv::VertexShaderDesc getVertexShaderDesc();\n   spirv::GeometryShaderDesc getGeometryShaderDesc();\n   spirv::PixelShaderDesc getPixelShaderDesc();\n   bool checkCurrentVertexShader();\n   bool checkCurrentGeometryShader();\n   bool checkCurrentPixelShader();\n   bool checkCurrentRectStubShader();\n\n   // Render Passes\n   RenderPassDesc getRenderPassDesc();\n   bool checkCurrentRenderPass();\n\n   // Pipeline Layouts\n   PipelineLayoutDesc generatePipelineLayoutDesc(const PipelineDesc& pipelineDesc);\n   PipelineLayoutObject * getPipelineLayout(const HashedDesc<PipelineLayoutDesc>& desc, bool forPush = false);\n\n   // Pipelines\n   PipelineDesc getPipelineDesc();\n   bool checkCurrentPipeline();\n\n   // Stream Out\n   StreamContextObject * allocateStreamContext(uint32_t initialOffset);\n   void releaseStreamContext(StreamContextObject* stream);\n   void readbackStreamContext(StreamContextObject *stream, phys_addr writeAddr);\n   StreamOutBufferDesc getStreamOutBufferDesc(uint32_t bufferIndex);\n   bool checkCurrentStreamOut();\n   void bindStreamOutBuffers();\n   void beginStreamOut();\n   void endStreamOut();\n\n   // Debug\n   void insertVkMarker(const std::string& text);\n   void setVkObjectName(VkBuffer object, const char *name);\n   void setVkObjectName(VkSampler object, const char *name);\n   void setVkObjectName(VkImage object, const char *name);\n   void setVkObjectName(VkImageView object, const char *name);\n   void setVkObjectName(VkShaderModule object, const char *name);\n\nprivate:\n   virtual void decafSetBuffer(const latte::pm4::DecafSetBuffer &data) override;\n   virtual void decafCopyColorToScan(const latte::pm4::DecafCopyColorToScan &data) override;\n   virtual void decafSwapBuffers(const latte::pm4::DecafSwapBuffers &data) override;\n   virtual void decafClearColor(const latte::pm4::DecafClearColor &data) override;\n   virtual void decafClearDepthStencil(const latte::pm4::DecafClearDepthStencil &data) override;\n   virtual void decafOSScreenFlip(const latte::pm4::DecafOSScreenFlip &data) override;\n   virtual void decafCopySurface(const latte::pm4::DecafCopySurface &data) override;\n   virtual void decafExpandColorBuffer(const latte::pm4::DecafExpandColorBuffer &data) override;\n   virtual void drawIndexAuto(const latte::pm4::DrawIndexAuto &data) override;\n   virtual void drawIndex2(const latte::pm4::DrawIndex2 &data) override;\n   virtual void drawIndexImmd(const latte::pm4::DrawIndexImmd &data) override;\n   virtual void memWrite(const latte::pm4::MemWrite &data) override;\n   virtual void eventWrite(const latte::pm4::EventWrite &data) override;\n   virtual void eventWriteEOP(const latte::pm4::EventWriteEOP &data) override;\n   virtual void pfpSyncMe(const latte::pm4::PfpSyncMe &data) override;\n   virtual void setPredication(const latte::pm4::SetPredication &data) override;\n   virtual void streamOutBaseUpdate(const latte::pm4::StreamOutBaseUpdate &data) override;\n   virtual void streamOutBufferUpdate(const latte::pm4::StreamOutBufferUpdate &data) override;\n   virtual void surfaceSync(const latte::pm4::SurfaceSync &data) override;\n   virtual void waitMem(const latte::pm4::WaitMem &data) override;\n\nprivate:\n   enum class RunState\n   {\n      None,\n      Running,\n      Stopped\n   };\n\n   VulkanDisplayPipeline mDisplayPipeline =  { };\n   vk::PhysicalDeviceTransformFeedbackFeaturesEXT mSupportedFeaturesTransformFeedback;\n   vk::PhysicalDeviceFeatures2 mSupportedFeatures;\n\n   std::atomic<RunState> mRunState = RunState::None;\n   gpu::VulkanDriverDebugInfo mDebugInfo;\n   std::thread mFenceThread;\n   std::mutex mFenceMutex;\n   std::list<SyncWaiter *> mFencesWaiting;\n   std::list<SyncWaiter *> mFencesPending;\n   std::condition_variable mFenceSignal;\n   std::vector<SyncWaiter *> mWaiterPool;\n   VmaAllocator mAllocator;\n   uint64_t mMemChangeCounter = 0;\n   uint64_t *mLastOccQueryAddr = nullptr;\n   vk::QueryPool mLastOccQuery;\n   vk::PipelineCache mPipelineCache;\n\n   SyncWaiter *mActiveSyncWaiter = nullptr;\n   vk::CommandBuffer mActiveCommandBuffer;\n   std::vector<vk::DescriptorSet> mAvailableDescriptorSets;\n   RenderPassObject *mActiveRenderPass = nullptr;\n   FramebufferObject *mActiveFramebuffer = nullptr;\n   PipelineObject *mActivePipeline = nullptr;\n   uint64_t mActiveBatchIndex = 0;\n\n   bool mActiveVsConstantsSet = false;\n   spirv::VertexPushConstants mActiveVsConstants;\n   bool mActivePsConstantsSet = false;\n   spirv::FragmentPushConstants mActivePsConstants;\n\n   bool mLastIndexBufferSet = false;\n   IndexBufferCache mLastIndexBuffer;\n\n   vk::DescriptorSetLayout mBaseDescriptorSetLayout;\n   vk::PipelineLayout mPipelineLayout;\n\n   std::array<StreamContextObject *, latte::MaxStreamOutBuffers> mStreamOutContext = { nullptr };\n   std::vector<DrawDesc> mPendingDraws;\n   DrawDesc *mCurrentDraw = nullptr;\n\n   DrawDesc mDrawCache;\n\n   std::vector<MemChangeRecord> mDirtyMemCaches;\n\n   std::vector<uint8_t> mScratchRetiling;\n   std::vector<uint8_t> mScratchIdxSwap;\n   std::vector<uint8_t> mScratchIdxPrim;\n   std::vector<vk::WriteDescriptorSet> mScratchDescriptorWrites;\n\n   using duration_system_clock = std::chrono::duration<double, std::chrono::system_clock::period>;\n   using duration_ms = std::chrono::duration<double, std::chrono::milliseconds::period>;\n   std::chrono::time_point<std::chrono::system_clock> mLastSwap;\n   duration_system_clock mAverageFrameTime { 0.0 };\n\n   vk::PhysicalDevice mPhysDevice;\n   vk::Device mDevice;\n   vk::Queue mQueue;\n   vk::DispatchLoaderDynamic mVkDynLoader;\n   vk::CommandPool mCommandPool;\n   vk::Sampler mBlankSampler;\n   vk::Image mBlankImage;\n   vk::ImageView mBlankImageView;\n   vk::Buffer mBlankBuffer;\n   SwapChainObject *mTvSwapChain = nullptr;\n   SwapChainObject *mDrcSwapChain = nullptr;\n   RenderPassObject *mRenderPass = nullptr;\n   std::array<std::array<std::vector<StagingBuffer *>, 20>, 3> mStagingBuffers;\n   std::vector<StreamContextObject *> mStreamOutContextPool;\n   std::vector<vk::DescriptorPool> mDescriptorPools;\n   std::vector<vk::QueryPool> mOccQueryPools;\n   std::unordered_map<DataHash, SurfaceGroupObject*> mSurfaceGroups;\n   std::unordered_map<DataHash, SurfaceObject*> mSurfaces;\n   std::unordered_map<DataHash, SurfaceViewObject*> mSurfaceViews;\n   std::unordered_map<DataHash, VertexShaderObject*> mVertexShaders;\n   std::unordered_map<DataHash, GeometryShaderObject*> mGeometryShaders;\n   std::unordered_map<DataHash, FramebufferObject*> mFramebuffers;\n   std::unordered_map<DataHash, PixelShaderObject*> mPixelShaders;\n   std::unordered_map<DataHash, RectStubShaderObject*> mRectStubShaders;\n   std::unordered_map<DataHash, RenderPassObject*> mRenderPasses;\n   std::unordered_map<DataHash, PipelineLayoutObject *> mPipelineLayouts;\n   std::unordered_map<DataHash, PipelineObject*> mPipelines;\n   std::unordered_map<DataHash, SamplerObject*> mSamplers;\n   std::unordered_map<uint64_t, MemCacheObject *> mMemCaches;\n\n   gpu7::tiling::vulkan::Retiler mGpuRetiler;\n   DriverMemoryTracker mMemTracker;\n\n   bool mDebug = false;\n   bool mDumpShaders = false;\n   bool mDumpShaderBinariesOnly = false;\n   bool mDumpTextures = false;\n};\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_fences.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\nnamespace vulkan\n{\n\nSyncWaiter *\nDriver::allocateSyncWaiter()\n{\n   if (!mWaiterPool.empty()) {\n      auto syncWaiter = mWaiterPool.back();\n      mWaiterPool.pop_back();\n      return syncWaiter;\n   }\n\n   auto syncWaiter = new SyncWaiter();\n\n   // Allocate a fence\n   syncWaiter->fence = mDevice.createFence(vk::FenceCreateInfo());\n\n   // Allocate a command buffer\n   vk::CommandBufferAllocateInfo cmdBufferAllocDesc(mCommandPool, vk::CommandBufferLevel::ePrimary, 1);\n   syncWaiter->cmdBuffer = mDevice.allocateCommandBuffers(cmdBufferAllocDesc)[0];\n\n   return syncWaiter;\n}\n\nvoid\nDriver::releaseSyncWaiter(SyncWaiter *syncWaiter)\n{\n   // Reset Vulkan state for this buffer resource thing\n   mDevice.resetFences({ syncWaiter->fence });\n   syncWaiter->cmdBuffer.reset(vk::CommandBufferResetFlags());\n\n   // Reset our local state for this buffer resource thing\n   syncWaiter->isCompleted = false;\n   syncWaiter->callbacks.clear();\n   syncWaiter->stagingBuffers.clear();\n   syncWaiter->retileHandles.clear();\n   syncWaiter->descriptorPools.clear();\n   syncWaiter->occQueryPools.clear();\n\n   // Put this fence back in the pool\n   mWaiterPool.push_back(syncWaiter);\n}\n\nvoid\nDriver::submitSyncWaiter(SyncWaiter *syncWaiter)\n{\n   std::unique_lock lock(mFenceMutex);\n   mFencesWaiting.push_back(syncWaiter);\n   mFencesPending.push_back(syncWaiter);\n   mFenceSignal.notify_all();\n}\n\nvoid\nDriver::executeSyncWaiter(SyncWaiter *syncWaiter)\n{\n   for (auto &callback : syncWaiter->callbacks) {\n      callback();\n   }\n\n   for (auto &buffer : syncWaiter->stagingBuffers) {\n      retireStagingBuffer(buffer);\n   }\n\n   for (auto &handle : syncWaiter->retileHandles) {\n      mGpuRetiler.releaseHandle(handle);\n   }\n\n   for (auto &pool : syncWaiter->descriptorPools) {\n      retireDescriptorPool(pool);\n   }\n\n   for (auto &pool : syncWaiter->occQueryPools) {\n      retireOccQueryPool(pool);\n   }\n}\n\nvoid\nDriver::fenceWaiterThread()\n{\n   std::unique_lock lock(mFenceMutex);\n\n   while (mRunState == RunState::Running) {\n      if (mFencesWaiting.size() == 0) {\n         mFenceSignal.wait(lock);\n         continue;\n      }\n\n      auto waiter = mFencesWaiting.front();\n      lock.unlock();\n\n      // Wake up every 100ms to check if we are no longer running\n      auto waitTimeInNs = 10000000u;\n\n      if (mDevice.waitForFences(1, &waiter->fence, false, waitTimeInNs) == vk::Result::eSuccess) {\n         lock.lock();\n\n         waiter->isCompleted = true;\n         mFencesWaiting.pop_front();\n         gpu::ringbuffer::wake();\n      } else {\n         lock.lock();\n      }\n   }\n}\n\nvoid\nDriver::checkSyncFences()\n{\n   std::unique_lock lock(mFenceMutex);\n\n   while (true) {\n      // If there are no pending fences, return immediately\n      if (mFencesPending.empty()) {\n         break;\n      }\n\n      // Grab the oldest pending fence and see if its completed\n      auto oldestPending = mFencesPending.front();\n      if (!oldestPending->isCompleted) {\n         break;\n      }\n      mFencesPending.pop_front();\n\n      // Perform any actions this sync waiter has queued\n      executeSyncWaiter(oldestPending);\n\n      // Release the sync waiter back to our pool\n      releaseSyncWaiter(oldestPending);\n   }\n}\n\nvoid\nDriver::addRetireTask(std::function<void()> fn)\n{\n   mActiveSyncWaiter->callbacks.push_back(fn);\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_framebuffer.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"latte/latte_formats.h\"\n\nnamespace vulkan\n{\n\nFramebufferDesc\nDriver::getFramebufferDesc()\n{\n   decaf_check(mCurrentDraw->renderPass);\n\n   auto desc = FramebufferDesc {};\n\n   for (auto i = 0u; i < latte::MaxRenderTargets; ++i) {\n      auto cb_color_base = getRegister<latte::CB_COLORN_BASE>(latte::Register::CB_COLOR0_BASE + i * 4);\n      auto cb_color_size = getRegister<latte::CB_COLORN_SIZE>(latte::Register::CB_COLOR0_SIZE + i * 4);\n      auto cb_color_info = getRegister<latte::CB_COLORN_INFO>(latte::Register::CB_COLOR0_INFO + i * 4);\n      auto cb_color_view = getRegister<latte::CB_COLORN_VIEW>(latte::Register::CB_COLOR0_VIEW + i * 4);\n\n      if (mCurrentDraw->renderPass->colorAttachmentIndexes[i] == -1) {\n         // If the RenderPass doesn't want this attachment, skip it...\n         desc.colorTargets[i] = ColorBufferDesc {\n            0,\n            0,\n            0,\n            latte::CB_FORMAT::COLOR_INVALID,\n            latte::CB_NUMBER_TYPE::UNORM,\n            latte::BUFFER_ARRAY_MODE::LINEAR_GENERAL,\n            0,\n            0\n         };\n\n         continue;\n      }\n\n      decaf_check(cb_color_base.BASE_256B());\n\n      desc.colorTargets[i] = ColorBufferDesc {\n         cb_color_base.BASE_256B(),\n         cb_color_size.PITCH_TILE_MAX(),\n         cb_color_size.SLICE_TILE_MAX(),\n         cb_color_info.FORMAT(),\n         cb_color_info.NUMBER_TYPE(),\n         cb_color_info.ARRAY_MODE(),\n         cb_color_view.SLICE_START(),\n         cb_color_view.SLICE_MAX() + 1\n      };\n   }\n\n   do {\n      auto db_depth_base = getRegister<latte::DB_DEPTH_BASE>(latte::Register::DB_DEPTH_BASE);\n      auto db_depth_size = getRegister<latte::DB_DEPTH_SIZE>(latte::Register::DB_DEPTH_SIZE);\n      auto db_depth_info = getRegister<latte::DB_DEPTH_INFO>(latte::Register::DB_DEPTH_INFO);\n      auto db_depth_view = getRegister<latte::DB_DEPTH_VIEW>(latte::Register::DB_DEPTH_VIEW);\n\n      if (mCurrentDraw->renderPass->depthAttachmentIndex == -1) {\n         // If the RenderPass doesn't want depth, skip it...\n         desc.depthTarget = DepthStencilBufferDesc {\n            0,\n            0,\n            0,\n            latte::DB_FORMAT::DEPTH_INVALID,\n            latte::BUFFER_ARRAY_MODE::LINEAR_GENERAL,\n            0,\n            0\n         };\n\n         break;\n      }\n\n      decaf_check(db_depth_base.BASE_256B());\n\n      desc.depthTarget = DepthStencilBufferDesc {\n         db_depth_base.BASE_256B(),\n         db_depth_size.PITCH_TILE_MAX(),\n         db_depth_size.SLICE_TILE_MAX(),\n         db_depth_info.FORMAT(),\n         db_depth_info.ARRAY_MODE(),\n         db_depth_view.SLICE_START(),\n         db_depth_view.SLICE_MAX() + 1\n      };\n   } while (false);\n\n   return desc;\n}\n\nbool\nDriver::checkCurrentFramebuffer()\n{\n   decaf_check(mCurrentDraw->renderPass);\n\n   HashedDesc<FramebufferDesc> currentDesc = getFramebufferDesc();\n\n   if (mCurrentDraw->framebuffer && mCurrentDraw->framebuffer->desc == currentDesc) {\n      // Already active, nothing to do.\n      return true;\n   }\n\n   auto& foundFb = mFramebuffers[currentDesc.hash()];\n   if (foundFb) {\n      mCurrentDraw->framebuffer = foundFb;\n      mCurrentDraw->framebufferDirty = true;\n      return true;\n   }\n\n   foundFb = new FramebufferObject();\n   foundFb->desc = currentDesc;\n\n   vk::Extent2D overallSize;\n\n   for (auto i = 0u; i < latte::MaxRenderTargets; ++i) {\n      auto colorTarget = currentDesc->colorTargets[i];\n\n      if (!colorTarget.base256b) {\n         // If the RenderPass doesn't want this attachment, skip it...\n         foundFb->colorSurfaces[i] = nullptr;\n         continue;\n      }\n\n      auto surfaceView = getColorBuffer(colorTarget);\n      foundFb->colorSurfaces[i] = surfaceView;\n\n      auto surface = surfaceView->surface;\n      if (overallSize.width == 0 && overallSize.height == 0) {\n         overallSize.width = surface->desc.width;\n         overallSize.height = surface->desc.height;\n      } else {\n         overallSize.width = std::min(overallSize.width, surface->desc.width);\n         overallSize.height = std::min(overallSize.height, surface->desc.height);\n      }\n   }\n\n   do {\n      auto depthTarget = currentDesc->depthTarget;\n\n      if (!depthTarget.base256b) {\n         // If the RenderPass doesn't want this attachment, skip it...\n         foundFb->depthSurface = nullptr;\n         continue;\n      }\n\n      auto surfaceView = getDepthStencilBuffer(depthTarget);\n      foundFb->depthSurface = surfaceView;\n\n      auto surface = surfaceView->surface;\n      if (overallSize.width == 0 && overallSize.height == 0) {\n         overallSize.width = surface->desc.width;\n         overallSize.height = surface->desc.height;\n      } else {\n         overallSize.width = std::min(overallSize.width, surface->desc.width);\n         overallSize.height = std::min(overallSize.height, surface->desc.height);\n      }\n   } while (false);\n\n   // TODO: This currently sets up the framebuffers size to match the first\n   // actual framebuffer surface we encounter.  In reality I think we need\n   // to make sure that the framebuffer is just the min of all surfaces.\n\n   foundFb->renderArea = overallSize;\n\n   mCurrentDraw->framebuffer = foundFb;\n   mCurrentDraw->framebufferDirty = true;\n   return true;\n}\n\nvoid\nDriver::prepareCurrentFramebuffer()\n{\n   decaf_check(mCurrentDraw->renderPass);\n   decaf_check(mCurrentDraw->framebuffer);\n\n   auto& fb = mCurrentDraw->framebuffer;\n\n   if (!mCurrentDraw->framebufferDirty) {\n      // If the framebuffer is the same as the last frame, it is considered\n      // clean, and we only need to perform barriers in order to make sure\n      // that the image layout is appropriate.\n      for (auto &surfaceView : fb->colorSurfaces) {\n         if (surfaceView) {\n            transitionSurfaceView(surfaceView, ResourceUsage::ColorAttachment, vk::ImageLayout::eColorAttachmentOptimal, true);\n         }\n      }\n      if (fb->depthSurface) {\n         auto &surfaceView = fb->depthSurface;\n         transitionSurfaceView(surfaceView, ResourceUsage::DepthStencilAttachment, vk::ImageLayout::eDepthStencilAttachmentOptimal, true);\n      }\n      return;\n   }\n\n   mCurrentDraw->framebufferDirty = false;\n\n\n   // First we need to transition all the surfaces to their appropriate places.\n   for (auto &surfaceView : fb->colorSurfaces) {\n      if (surfaceView) {\n         transitionSurfaceView(surfaceView, ResourceUsage::ColorAttachment, vk::ImageLayout::eColorAttachmentOptimal);\n      }\n   }\n   if (fb->depthSurface) {\n      auto &surfaceView = fb->depthSurface;\n      transitionSurfaceView(surfaceView, ResourceUsage::DepthStencilAttachment, vk::ImageLayout::eDepthStencilAttachmentOptimal);\n   }\n\n   // Next lets grab all the appropriate attachments we are using\n   uint32_t numAttachments = 0;\n   std::array<vk::ImageView, 9> attachments;\n   bool needsRefresh = false;\n\n   for (auto i = 0u; i < latte::MaxRenderTargets; ++i) {\n      auto& surfaceView = fb->colorSurfaces[i];\n      if (!surfaceView) {\n         // nothing bound here\n         continue;\n      }\n\n      auto attachmentIndex = static_cast<uint32_t>(mCurrentDraw->renderPass->colorAttachmentIndexes[i]);\n      numAttachments = std::max(numAttachments, attachmentIndex + 1);\n\n      attachments[attachmentIndex] = surfaceView->imageView;\n\n      if (fb->boundViews[attachmentIndex] != surfaceView->imageView) {\n         needsRefresh = true;\n      }\n   }\n\n   do {\n      auto& surfaceView = fb->depthSurface;\n      if (!surfaceView) {\n         // nothing bound here\n         continue;\n      }\n\n      auto attachmentIndex = static_cast<uint32_t>(mCurrentDraw->renderPass->depthAttachmentIndex);\n      numAttachments = std::max(numAttachments, attachmentIndex + 1);\n\n      attachments[attachmentIndex] = surfaceView->imageView;\n\n      if (fb->boundViews[attachmentIndex] != surfaceView->imageView) {\n         needsRefresh = true;\n      }\n   } while (false);\n\n   if (!needsRefresh) {\n      return;\n   }\n\n   // If we have an existing framebuffer, we can destroy it on the\n   // next frame, once we are confident that nobody is using it\n   if (fb->framebuffer) {\n      auto oldFramebuffer = fb->framebuffer;\n      addRetireTask([=](){\n         mDevice.destroyFramebuffer(oldFramebuffer);\n      });\n      fb->framebuffer = nullptr;\n   }\n\n   vk::FramebufferCreateInfo framebufferDesc;\n   framebufferDesc.renderPass = mCurrentDraw->renderPass->renderPass;\n   framebufferDesc.attachmentCount = numAttachments;\n   framebufferDesc.pAttachments = attachments.data();\n   framebufferDesc.width = fb->renderArea.width;\n   framebufferDesc.height = fb->renderArea.height;\n   framebufferDesc.layers = 1;\n   auto framebuffer = mDevice.createFramebuffer(framebufferDesc);\n   fb->framebuffer = framebuffer;\n   fb->boundViews = attachments;\n}\n\nSurfaceViewObject *\nDriver::getColorBuffer(const ColorBufferDesc& info)\n{\n   auto baseAddress = phys_addr(info.base256b << 8);\n   auto pitch_tile_max = info.pitchTileMax;\n   auto slice_tile_max = info.sliceTileMax;\n\n   auto pitch = static_cast<uint32_t>((pitch_tile_max + 1) * latte::MicroTileWidth);\n   auto height = static_cast<uint32_t>(((slice_tile_max + 1) * (latte::MicroTileWidth * latte::MicroTileHeight)) / pitch);\n\n   auto surfaceFormat = latte::getColorBufferSurfaceFormat(info.format, info.numberType);\n   auto tileMode = latte::getArrayModeTileMode(info.arrayMode);\n\n   SurfaceDesc surfaceDesc;\n   surfaceDesc.baseAddress = static_cast<uint32_t>(baseAddress);\n   surfaceDesc.pitch = pitch;\n   surfaceDesc.width = pitch;\n   surfaceDesc.height = height;\n   surfaceDesc.depth = 1;\n   surfaceDesc.samples = 1u;\n   surfaceDesc.dim = latte::SQ_TEX_DIM::DIM_2D;\n   surfaceDesc.format = surfaceFormat;\n   surfaceDesc.tileType = latte::SQ_TILE_TYPE::DEFAULT;\n   surfaceDesc.tileMode = tileMode;\n\n   if (info.sliceEnd > 1) {\n      surfaceDesc.depth = info.sliceEnd;\n      surfaceDesc.dim = latte::SQ_TEX_DIM::DIM_2D_ARRAY;\n   }\n\n   SurfaceViewDesc surfaceViewDesc;\n   surfaceViewDesc.surfaceDesc = surfaceDesc;\n   surfaceViewDesc.sliceStart = info.sliceStart;\n   surfaceViewDesc.sliceEnd = info.sliceEnd;\n   surfaceViewDesc.channels = {\n      latte::SQ_SEL::SEL_X,\n      latte::SQ_SEL::SEL_Y,\n      latte::SQ_SEL::SEL_Z,\n      latte::SQ_SEL::SEL_W };\n\n   return getSurfaceView(surfaceViewDesc);\n}\n\nSurfaceViewObject *\nDriver::getDepthStencilBuffer(const DepthStencilBufferDesc& info)\n{\n   auto baseAddress = phys_addr(info.base256b << 8);\n   auto pitch_tile_max = info.pitchTileMax;\n   auto slice_tile_max = info.sliceTileMax;\n\n   auto pitch = static_cast<uint32_t>((pitch_tile_max + 1) * latte::MicroTileWidth);\n   auto height = static_cast<uint32_t>(((slice_tile_max + 1) * (latte::MicroTileWidth * latte::MicroTileHeight)) / pitch);\n\n   auto surfaceFormat = latte::getDepthBufferSurfaceFormat(info.format);\n   auto tileMode = latte::getArrayModeTileMode(info.arrayMode);\n\n   SurfaceDesc surfaceDesc;\n   surfaceDesc.baseAddress = static_cast<uint32_t>(baseAddress);\n   surfaceDesc.pitch = pitch;\n   surfaceDesc.width = pitch;\n   surfaceDesc.height = height;\n   surfaceDesc.depth = 1;\n   surfaceDesc.samples = 1u;\n   surfaceDesc.dim = latte::SQ_TEX_DIM::DIM_2D;\n   surfaceDesc.format = surfaceFormat;\n   surfaceDesc.tileType = latte::SQ_TILE_TYPE::DEPTH;\n   surfaceDesc.tileMode = tileMode;\n\n   if (info.sliceEnd > 1) {\n      surfaceDesc.depth = info.sliceEnd;\n      surfaceDesc.dim = latte::SQ_TEX_DIM::DIM_2D_ARRAY;\n   }\n\n   SurfaceViewDesc surfaceViewDesc;\n   surfaceViewDesc.sliceStart = info.sliceStart;\n   surfaceViewDesc.sliceEnd = info.sliceEnd;\n   surfaceViewDesc.surfaceDesc = surfaceDesc;\n   surfaceViewDesc.channels = {\n      latte::SQ_SEL::SEL_X,\n      latte::SQ_SEL::SEL_Y,\n      latte::SQ_SEL::SEL_Z,\n      latte::SQ_SEL::SEL_W };\n\n   return getSurfaceView(surfaceViewDesc);\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_indices.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include <common/byte_swap_array.h>\n\nnamespace vulkan\n{\n\ntemplate<typename IndexType>\nstatic void\nunpackQuadList(uint32_t count,\n               const IndexType *src,\n               IndexType *dst)\n{\n   // Unpack quad indices into triangle indices\n   if (src) {\n      for (IndexType i = 0u; i < count / 4; ++i) {\n         IndexType index_0 = *src++;\n         IndexType index_1 = *src++;\n         IndexType index_2 = *src++;\n         IndexType index_3 = *src++;\n\n         *(dst++) = index_0;\n         *(dst++) = index_1;\n         *(dst++) = index_2;\n\n         *(dst++) = index_0;\n         *(dst++) = index_2;\n         *(dst++) = index_3;\n      }\n   } else {\n      IndexType index_0 = 0u;\n      IndexType index_1 = 1u;\n      IndexType index_2 = 2u;\n      IndexType index_3 = 3u;\n\n      for (IndexType i = 0u; i < count / 4; ++i) {\n         IndexType index = i * 4;\n\n         *(dst++) = index_0 + index;\n         *(dst++) = index_1 + index;\n         *(dst++) = index_2 + index;\n\n         *(dst++) = index_0 + index;\n         *(dst++) = index_2 + index;\n         *(dst++) = index_3 + index;\n      }\n   }\n}\n\nstatic inline uint32_t\ncalculateIndexBufferSize(latte::VGT_INDEX_TYPE indexType, uint32_t numIndices)\n{\n   switch (indexType) {\n   case latte::VGT_INDEX_TYPE::INDEX_16:\n      return numIndices * 2;\n   case latte::VGT_INDEX_TYPE::INDEX_32:\n      return numIndices * 4;\n   }\n\n   decaf_abort(\"Unexpected index type\");\n}\n\nvoid\nDriver::maybeSwapIndices()\n{\n   auto& drawDesc = *mCurrentDraw;\n   auto& indices = mCurrentDraw->indices;\n\n   if (indices) {\n      if (mCurrentDraw->indexSwapMode == latte::VGT_DMA_SWAP::SWAP_16_BIT) {\n         uint32_t indexBytes = calculateIndexBufferSize(mCurrentDraw->indexType, drawDesc.numIndices);\n         indices = byte_swap_to_scratch<uint16_t>(indices, indexBytes, mScratchIdxSwap);\n      } else if (drawDesc.indexSwapMode == latte::VGT_DMA_SWAP::SWAP_32_BIT) {\n         uint32_t indexBytes = calculateIndexBufferSize(mCurrentDraw->indexType, drawDesc.numIndices);\n         indices = byte_swap_to_scratch<uint32_t>(indices, indexBytes, mScratchIdxSwap);\n      } else if (drawDesc.indexSwapMode == latte::VGT_DMA_SWAP::NONE) {\n         // Nothing to do here!\n      } else {\n         decaf_abort(fmt::format(\"Unimplemented vgt_dma_index_type.SWAP_MODE {}\", drawDesc.indexSwapMode));\n      }\n   }\n}\n\nvoid\nDriver::maybeUnpackPrimitiveIndices()\n{\n   auto &drawDesc = *mCurrentDraw;\n   auto &indices = mCurrentDraw->indices;\n\n\n   if (drawDesc.primitiveType == latte::VGT_DI_PRIMITIVE_TYPE::QUADLIST) {\n      auto indexBytes = calculateIndexBufferSize(drawDesc.indexType, drawDesc.numIndices);\n      mScratchIdxPrim.resize(indexBytes / 4 * 6);\n\n      if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_16) {\n         unpackQuadList(drawDesc.numIndices,\n                        reinterpret_cast<uint16_t*>(indices),\n                        reinterpret_cast<uint16_t*>(mScratchIdxPrim.data()));\n      } else if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_32) {\n         unpackQuadList(drawDesc.numIndices,\n                        reinterpret_cast<uint32_t*>(indices),\n                        reinterpret_cast<uint32_t*>(mScratchIdxPrim.data()));\n      } else {\n         decaf_abort(\"Unexpected index type\");\n      }\n\n      drawDesc.primitiveType = latte::VGT_DI_PRIMITIVE_TYPE::TRILIST;\n      drawDesc.numIndices = drawDesc.numIndices / 4 * 6;\n      indices = mScratchIdxPrim.data();\n   } else if (drawDesc.primitiveType == latte::VGT_DI_PRIMITIVE_TYPE::LINELOOP) {\n      auto indexBytes = calculateIndexBufferSize(drawDesc.indexType, drawDesc.numIndices + 1);\n      mScratchIdxPrim.resize(indexBytes);\n\n      if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_16) {\n         auto dst = reinterpret_cast<uint16_t *>(mScratchIdxPrim.data());\n         std::memcpy(dst, indices, indexBytes - 2);\n         dst[drawDesc.numIndices] = dst[0];\n      } else if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_32) {\n         auto dst = reinterpret_cast<uint32_t *>(mScratchIdxPrim.data());\n         std::memcpy(dst, indices, indexBytes - 4);\n         dst[drawDesc.numIndices] = dst[0];\n      } else {\n         decaf_abort(\"Unexpected index type\");\n      }\n\n      drawDesc.primitiveType = latte::VGT_DI_PRIMITIVE_TYPE::LINESTRIP;\n      drawDesc.numIndices = drawDesc.numIndices + 1;\n      indices = mScratchIdxPrim.data();\n   }\n}\n\nbool\nDriver::checkCurrentIndices()\n{\n   auto& drawDesc = *mCurrentDraw;\n\n   if (mLastIndexBufferSet) {\n      if (drawDesc.indices == mLastIndexBuffer.indexData &&\n          drawDesc.indexType == mLastIndexBuffer.indexType &&\n          drawDesc.numIndices == mLastIndexBuffer.numIndices &&\n          drawDesc.indexSwapMode == mLastIndexBuffer.swapMode &&\n          drawDesc.primitiveType == mLastIndexBuffer.primitiveType)\n      {\n         drawDesc.primitiveType = mLastIndexBuffer.newPrimitiveType;\n         drawDesc.numIndices = mLastIndexBuffer.newNumIndices;\n         drawDesc.indexBuffer = mLastIndexBuffer.indexBuffer;\n         return true;\n      }\n   }\n\n   mLastIndexBuffer.indexData = drawDesc.indices;\n   mLastIndexBuffer.indexType = drawDesc.indexType;\n   mLastIndexBuffer.numIndices = drawDesc.numIndices;\n   mLastIndexBuffer.swapMode = drawDesc.indexSwapMode;\n   mLastIndexBuffer.primitiveType = drawDesc.primitiveType;\n\n   maybeSwapIndices();\n   maybeUnpackPrimitiveIndices();\n\n   if (drawDesc.indices) {\n      auto indexBytes = calculateIndexBufferSize(drawDesc.indexType, drawDesc.numIndices);\n      auto indicesBuf = getStagingBuffer(indexBytes, StagingBufferType::CpuToGpu);\n      copyToStagingBuffer(indicesBuf, 0, drawDesc.indices, indexBytes);\n      transitionStagingBuffer(indicesBuf, ResourceUsage::IndexBuffer);\n\n      drawDesc.indexBuffer = indicesBuf;\n   } else {\n      drawDesc.indexBuffer = nullptr;\n   }\n\n   mLastIndexBuffer.newPrimitiveType = drawDesc.primitiveType;\n   mLastIndexBuffer.newNumIndices = drawDesc.numIndices;\n   mLastIndexBuffer.indexBuffer = drawDesc.indexBuffer;\n   mLastIndexBufferSet = true;\n\n   return true;\n}\n\nvoid\nDriver::bindIndexBuffer()\n{\n   if (!mCurrentDraw->indexBuffer) {\n      return;\n   }\n\n   auto& drawDesc = *mCurrentDraw;\n\n   vk::IndexType bindIndexType;\n   if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_16) {\n      bindIndexType = vk::IndexType::eUint16;\n   } else if (drawDesc.indexType == latte::VGT_INDEX_TYPE::INDEX_32) {\n      bindIndexType = vk::IndexType::eUint32;\n   } else {\n      decaf_abort(\"Unexpected index type\");\n   }\n\n   mActiveCommandBuffer.bindIndexBuffer(mCurrentDraw->indexBuffer->buffer, 0, bindIndexType);\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_memcache.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\n#include <common/rangecombiner.h>\n\nnamespace vulkan\n{\n\n// void(MemSegment&)\ntemplate<typename FunctorType>\nstatic inline void\nforEachMemSegment(MemSegmentRef begin, uint32_t size, FunctorType functor)\n{\n   auto segment = begin.get();\n   for (auto sizeLeft = size; sizeLeft > 0;) {\n      functor(*segment);\n      sizeLeft -= segment->size;\n      segment = segment->nextSegment;\n   }\n}\n\n// void(MemCacheSection&, MemSegment&)\ntemplate<typename FunctorType>\nstatic inline void\nforEachSectionSegment(MemCacheObject *cache, SectionRange range, FunctorType functor)\n{\n   auto rangeStart = range.start;\n   auto rangeEnd = range.start + range.count;\n\n   auto firstSection = cache->sections[rangeStart];\n   auto segment = firstSection.firstSegment.get();\n   for (auto i = rangeStart; i < rangeEnd; ++i) {\n      auto& section = cache->sections[i];\n\n      for (auto sizeLeft = cache->sectionSize; sizeLeft > 0;) {\n         functor(section, *segment);\n\n         sizeLeft -= segment->size;\n         segment = segment->nextSegment;\n      }\n   }\n}\n\n// void(MemSegment&)\ntemplate<typename FunctorType>\nstatic inline void\nforEachMemSegment(MemCacheObject *cache, SectionRange range, FunctorType functor)\n{\n   auto& firstSection = cache->sections[range.start];\n   auto begin = firstSection.firstSegment;\n   auto size = range.count * cache->sectionSize;\n   forEachMemSegment(begin, size, functor);\n}\n\nMemCacheObject *\nDriver::_allocMemCache(phys_addr address, uint32_t numSections, uint32_t sectionSize)\n{\n   uint32_t totalSize = 0;\n\n   std::vector<MemCacheSection> sections;\n   for (auto i = 0u; i < numSections; ++i) {\n      auto firstSegment = mMemTracker.get(address + totalSize, sectionSize);\n\n      MemCacheSection section;\n      section.lastChangeIndex = 0;\n      section.firstSegment = firstSegment;\n      section.needsUpload = false;\n      section.wantedChangeIndex = 0;\n      sections.push_back(section);\n\n      totalSize += sectionSize;\n   }\n\n   // We add 32 bytes to all our buffers because in many cases, Vulkan will\n   // error if we attempt to read past the edges of our buffers (such as can\n   // happen with vertex buffers when the stride is 12 but the read is 16.\n   // This will allow us to correctly execute these cases like hardware does\n   // which sort of has free-range over memory.  Note that this won't copy\n   // hardware behaviour precisely, since hardware actually can access the\n   // real contents after the end of the buffer whereas we just shove garbage\n   // there.  Better than not executing the draw at all though!\n\n   vk::BufferCreateInfo bufferDesc;\n   bufferDesc.size = totalSize + 32;\n   bufferDesc.usage =\n      vk::BufferUsageFlagBits::eVertexBuffer |\n      vk::BufferUsageFlagBits::eStorageBuffer |\n      vk::BufferUsageFlagBits::eTransformFeedbackBufferEXT |\n      vk::BufferUsageFlagBits::eStorageBuffer |\n      vk::BufferUsageFlagBits::eTransferDst |\n      vk::BufferUsageFlagBits::eTransferSrc;\n   bufferDesc.sharingMode = vk::SharingMode::eExclusive;\n   bufferDesc.queueFamilyIndexCount = 0;\n   bufferDesc.pQueueFamilyIndices = nullptr;\n\n   VmaAllocationCreateInfo allocInfo = {};\n   allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n\n   VkBuffer buffer;\n   VmaAllocation allocation;\n   CHECK_VK_RESULT(\n      vmaCreateBuffer(mAllocator,\n                      reinterpret_cast<VkBufferCreateInfo*>(&bufferDesc),\n                      &allocInfo,\n                      &buffer,\n                      &allocation,\n                      nullptr));\n\n   static uint64_t memCacheIndex = 0;\n   setVkObjectName(buffer, fmt::format(\"mcch_{}_{:08x}_{}\", memCacheIndex++, address.getAddress(), totalSize).c_str());\n\n   auto cache = new MemCacheObject();\n   cache->address = address;\n   cache->size = totalSize;\n   cache->numSections = numSections;\n   cache->sectionSize = sectionSize;\n   cache->allocation = allocation;\n   cache->buffer = buffer;\n   cache->sections = std::move(sections);\n   cache->delayedWriteFunc = nullptr;\n   cache->delayedWriteRange = {};\n   cache->lastUsageIndex = mActiveBatchIndex;\n   cache->refCount = 0;\n   return cache;\n}\n\nvoid\nDriver::_uploadMemCache(MemCacheObject *cache, SectionRange range)\n{\n   uint8_t *cacheBasePtr = phys_cast<uint8_t*>(cache->address).getRawPointer();\n\n   auto offsetStart = cache->sectionSize * range.start;\n   auto rangeSize = cache->sectionSize * range.count;\n\n   uint8_t *uploadData = cacheBasePtr + offsetStart;\n\n   // Upload the data to the CPU\n   auto stagingBuffer = getStagingBuffer(rangeSize, StagingBufferType::CpuToGpu);\n   copyToStagingBuffer(stagingBuffer, 0, uploadData, rangeSize);\n\n   // Transition the buffers appropriately\n   transitionStagingBuffer(stagingBuffer, ResourceUsage::TransferSrc);\n   _barrierMemCache(cache, ResourceUsage::TransferDst, range);\n\n   // Copy the data out of the staging buffer into the memory cache.\n   vk::BufferCopy copyDesc;\n   copyDesc.srcOffset = 0;\n   copyDesc.dstOffset = offsetStart;\n   copyDesc.size = rangeSize;\n   mActiveCommandBuffer.copyBuffer(stagingBuffer->buffer, cache->buffer, { copyDesc });\n}\n\nvoid\nDriver::_downloadMemCache(MemCacheObject *cache, SectionRange range)\n{\n   // If we have a pending delayed write that overlaps, we are going to need to\n   // process it here before we can actually write to the CPU.\n   if (cache->delayedWriteFunc && cache->delayedWriteRange.intersects(range)) {\n      cache->delayedWriteFunc();\n      cache->delayedWriteFunc = nullptr;\n   }\n\n   // Lets start doing the actual download!\n   auto offsetStart = cache->sectionSize * range.start;\n   auto rangeSize = cache->sectionSize * range.count;\n\n   // Create a staging buffer to use for the readback\n   auto stagingBuffer = getStagingBuffer(rangeSize, StagingBufferType::GpuToCpu);\n\n   // Transition the buffers appropriately\n   transitionStagingBuffer(stagingBuffer, ResourceUsage::TransferDst);\n   _barrierMemCache(cache, ResourceUsage::TransferSrc, range);\n\n   // Copy the data into our staging buffer from the cache object\n   vk::BufferCopy copyDesc;\n   copyDesc.srcOffset = offsetStart;\n   copyDesc.dstOffset = 0;\n   copyDesc.size = rangeSize;\n   mActiveCommandBuffer.copyBuffer(cache->buffer, stagingBuffer->buffer, { copyDesc });\n\n   // We have to pre-transition the buffer to being host-read, as it would be otherwise\n   // illegal to be doing the transition during the retire function below.\n   transitionStagingBuffer(stagingBuffer, ResourceUsage::HostRead);\n\n   // TODO: This can be optimized... A lot...\n\n   // Move the data onto the CPU on a per-section basis\n   for (auto i = range.start; i < range.start + range.count; ++i) {\n      auto& section = cache->sections[i];\n      auto changeIndex = section.lastChangeIndex;\n\n      addRetireTask([=](){\n         void *data = phys_cast<void*>(cache->address + i * cache->sectionSize).getRawPointer();\n\n         auto stagingOffset = (i - range.start) * cache->sectionSize;\n\n         // Copy the data out of the staging area into memory\n         copyFromStagingBuffer(stagingBuffer, stagingOffset, data, cache->sectionSize);\n\n         // We need to calculate new data hashes for the relevant segments that\n         // are affected by this image and are not still being GPU written.\n         forEachMemSegment(section.firstSegment, cache->sectionSize, [&](MemSegment& segment){\n            // For safety purposes, lets confirm that this write was intended.\n            decaf_check(segment.lastChangeIndex >= section.lastChangeIndex);\n\n            // Only bother recalculating the hashing if we are not already waiting\n            // for more writes to this segment...\n            if (segment.lastChangeIndex == changeIndex) {\n               mMemTracker.markSegmentGpuDone(&segment);\n            }\n         });\n      });\n   }\n}\n\nvoid\nDriver::_refreshMemCache_Check(MemCacheObject *cache, SectionRange range)\n{\n   forEachSectionSegment(cache, range, [&](MemCacheSection& section, MemSegment& segment){\n      // Refresh the segment to make sure we have up-to-date information\n      mMemTracker.refreshSegment(&segment);\n\n      // Update the wanted index\n      if (segment.lastChangeIndex > section.wantedChangeIndex) {\n         section.wantedChangeIndex = segment.lastChangeIndex;\n      }\n\n      // Check if we already have this data, if we do, there is nothing to do.\n      if (section.lastChangeIndex >= segment.lastChangeIndex) {\n         return;\n      }\n\n      // Check if we have no last-owner first, obviously an upload is needed in that\n      // case.  Additionally, we are required to perform an upload if the last owner\n      // has rearranged the data such that it might not make sense for us anymore.\n      if (!segment.lastChangeOwner) {\n         section.needsUpload = true;\n      }\n   });\n}\n\nvoid\nDriver::_refreshMemCache_Update(MemCacheObject *cache, SectionRange range)\n{\n   // This is an optimization to enable us to do bigger copies and uploads in\n   // the case of a fragmented set of underlying segments which point to the\n   // same thing contiguously.\n\n   auto uploadCombiner = makeRangeCombiner<void*, uint32_t, uint32_t>(\n   [&](void*, uint32_t start, uint32_t count){\n      _uploadMemCache(cache, { start, count });\n   });\n\n   for (auto i = range.start; i < range.start + range.count; ++i) {\n      auto& section = cache->sections[i];\n\n      if (section.needsUpload) {\n         uploadCombiner.push(nullptr, i, 1);\n      }\n   }\n\n   uploadCombiner.flush();\n\n   auto copyCombiner = makeRangeCombiner<MemCacheObject*, phys_addr, uint32_t>(\n   [&](MemCacheObject *object, phys_addr address, uint32_t size){\n      vk::BufferCopy copyDesc;\n      copyDesc.srcOffset = static_cast<uint32_t>(address - object->address);\n      copyDesc.dstOffset = static_cast<uint32_t>(address - cache->address);\n      copyDesc.size = size;\n      mActiveCommandBuffer.copyBuffer(object->buffer, cache->buffer, { copyDesc });\n   });\n\n   // We have to do this independantly, as our section updates need to\n   // happen only after all segments have been written...\n   for (auto i = range.start; i < range.start + range.count; ++i) {\n      auto& section = cache->sections[i];\n\n      forEachMemSegment(section.firstSegment, cache->sectionSize, [&](MemSegment& segment){\n         // Note that we have to do this delayed write check before we exit\n         // early as we are not actually 'up to date' until the write occurs.\n         auto& lastChangeOwner = segment.lastChangeOwner;\n         if (lastChangeOwner && lastChangeOwner->delayedWriteFunc) {\n            if (lastChangeOwner->delayedWriteRange.intersects(range)) {\n               lastChangeOwner->delayedWriteFunc();\n               lastChangeOwner->delayedWriteFunc = nullptr;\n               lastChangeOwner->delayedWriteRange = { 0, 0 };\n            }\n         }\n\n         // Check to see if we already have the latest data from this segment available\n         // to us in our buffer already (to avoid needing to copy).\n         if (section.lastChangeIndex >= segment.lastChangeIndex) {\n            return;\n         }\n\n         // Lets make sure if there was no owner, that we uploaded this previously,\n         // and that we take ownership and update the last change index.\n         if (!segment.lastChangeOwner) {\n            decaf_check(section.needsUpload);\n            segment.lastChangeOwner = cache;\n            return;\n         }\n\n         // Push this copy to our list of copies we want to do.\n         copyCombiner.push(segment.lastChangeOwner, segment.address, segment.size);\n\n         // If the segment was not GPU written, lets also take ownership since it will\n         // increase the chances of condensing buffer copies for future transfers.\n         if (!segment.gpuWritten) {\n            segment.lastChangeOwner = cache;\n         }\n      });\n\n      // Mark the section as having been updated.\n      section.lastChangeIndex = section.wantedChangeIndex;\n\n      // Mark it as no longer needing to be uploaded\n      section.needsUpload = false;\n   }\n\n   copyCombiner.flush();\n}\n\nvoid\nDriver::_refreshMemCache(MemCacheObject *cache, SectionRange range)\n{\n   _refreshMemCache_Check(cache, range);\n   _refreshMemCache_Update(cache, range);\n}\n\nvoid\nDriver::_invalidateMemCache(MemCacheObject *cache, SectionRange range, const DelayedMemWriteFunc& delayedWriteFunc)\n{\n   auto changeIndex = mMemTracker.newChangeIndex();\n\n   // If there is already a delayed write and its not covered by\n   // this particular invalidation, we will have to execute it.\n   if (cache->delayedWriteFunc) {\n      if (!range.covers(cache->delayedWriteRange)) {\n         cache->delayedWriteFunc();\n      }\n\n      cache->delayedWriteFunc = nullptr;\n      cache->delayedWriteRange = {};\n   }\n\n   if (delayedWriteFunc) {\n      cache->delayedWriteFunc = delayedWriteFunc;\n      cache->delayedWriteRange = range;\n   }\n\n   for (auto i = range.start; i < range.start + range.count; ++i) {\n      cache->sections[i].lastChangeIndex = changeIndex;\n   }\n\n   forEachMemSegment(cache, range, [&](MemSegment& segment){\n      segment.lastChangeIndex = changeIndex;\n      segment.lastChangeOwner = cache;\n      segment.gpuWritten = true;\n   });\n\n   if (delayedWriteFunc) {\n      // If there is a delayed write, we assume that this must have been a surface transition\n      // that was happening, and we don't want to copy these all back, so lets not mark it\n      // for download later...\n      return;\n   }\n\n   mDirtyMemCaches.push_back({ changeIndex, cache, range });\n}\n\nvoid\nDriver::_barrierMemCache(MemCacheObject *cache, ResourceUsage usage, SectionRange range)\n{\n   auto offsetStart = range.start * cache->sectionSize;;\n   auto memSize = range.count * cache->sectionSize;\n\n   if (cache->activeUsage == usage) {\n      return;\n   }\n\n   auto srcMeta = getResourceUsageMeta(cache->activeUsage);\n   auto dstMeta = getResourceUsageMeta(usage);\n\n   vk::BufferMemoryBarrier bufferBarrier;\n   bufferBarrier.srcAccessMask = srcMeta.accessFlags;\n   bufferBarrier.dstAccessMask = dstMeta.accessFlags;\n   bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   bufferBarrier.buffer = cache->buffer;\n   bufferBarrier.offset = offsetStart;\n   bufferBarrier.size = memSize;\n\n   mActiveCommandBuffer.pipelineBarrier(\n      srcMeta.stageFlags,\n      dstMeta.stageFlags,\n      vk::DependencyFlags(),\n      {},\n      { bufferBarrier },\n      {});\n\n   cache->activeUsage = usage;\n}\n\nSectionRange\nDriver::_sectionsFromOffsets(MemCacheObject *cache, uint32_t begin, uint32_t end)\n{\n   decaf_check(begin % cache->sectionSize == 0);\n   decaf_check(end % cache->sectionSize == 0);\n\n   SectionRange range;\n   range.start = begin / cache->sectionSize;\n   range.count = (end - begin) / cache->sectionSize;\n\n   return range;\n}\n\nMemCacheObject *\nDriver::getMemCache(phys_addr address, uint32_t numSections, uint32_t sectionSize)\n{\n   // Note: We cast here first to make sure the types are in the appropriate\n   // types, otherwise the bit operations may not behave as expected.\n   uint64_t lookupAddr = address.getAddress();\n   uint64_t lookupSize = numSections * sectionSize;\n   uint64_t lookupKey = (lookupSize << 32) | lookupAddr;\n\n   // TODO: We should implement the ability to 'grow' a MemCacheObject to\n   // contain more sections on top of the ones that already exist in the\n   // object.  This will improve memory usage, and help when new surfaces\n   // appear which just add slices.\n\n   auto& cacheRef = mMemCaches[lookupKey];\n\n   auto cache = cacheRef;\n   while (cache) {\n      if (cache->sectionSize == sectionSize) {\n         break;\n      }\n      cache = cache->nextObject;\n   }\n\n   if (!cache) {\n      // If there is not yet a cache object, we need to create it.\n      cache = _allocMemCache(address, numSections, sectionSize);\n\n      // Be warned about the fact that `cache` is a reference object,\n      // and we are putting the new object at the head of the list.\n      cache->nextObject = cacheRef;\n      cacheRef = cache;\n   }\n\n   decaf_check(cache->address == address);\n   decaf_check(cache->numSections == numSections);\n   decaf_check(cache->sectionSize == sectionSize);\n\n   return cache;\n}\n\nvoid\nDriver::invalidateMemCacheDelayed(MemCacheObject *cache, uint32_t offset, uint32_t size, const DelayedMemWriteFunc& delayedWriteHandler)\n{\n   // Calculate which sections actually apply to this...\n   auto range = _sectionsFromOffsets(cache, offset, offset + size);\n\n   // Perform the invalidation\n   _invalidateMemCache(cache, range, delayedWriteHandler);\n}\n\nvoid\nDriver::transitionMemCache(MemCacheObject *cache, ResourceUsage usage, uint32_t offset, uint32_t size)\n{\n   // If no size was specified, it means the whole buffer\n   if (size == 0) {\n      size = cache->size - offset;\n   }\n\n   // Calculate which sections actually apply to this...\n   auto range = _sectionsFromOffsets(cache, offset, offset + size);\n\n   // Check if this is for reading or writing\n   auto forWrite = getResourceUsageMeta(usage).isWrite;\n\n   // Update the last usage here\n   cache->lastUsageIndex = mActiveBatchIndex;\n\n   // If this is a write-usage, we need to register this object to be\n   // invalidated later when the batch is completed.  Otherwise we\n   // need to read the data for usage.  Note that its safe to do the\n   // invalidation before the actual write occurs since no transfers\n   // occur until the end of the batch (or when it changes again).\n   _refreshMemCache(cache, range);\n\n   if (forWrite) {\n      _invalidateMemCache(cache, range, nullptr);\n   }\n\n   _barrierMemCache(cache, usage, range);\n}\n\nvoid\nDriver::downloadPendingMemCache()\n{\n   for (auto& dirtyRecord : mDirtyMemCaches) {\n      auto& cache = dirtyRecord.cache;\n\n      // If none of the sections we are targetting still have our change\n      // index, there is no need to do any of the work.\n      bool needsDownload = false;\n      for (auto i = 0u; i < dirtyRecord.sections.count; ++i) {\n         auto sectionIndex = dirtyRecord.sections.start + i;\n         if (cache->sections[sectionIndex].lastChangeIndex <= dirtyRecord.changeIndex) {\n            needsDownload = true;\n            break;\n         }\n      }\n\n      // Download the memory cache back to the CPU.  Note that we have to\n      // keep this object marked as being written by the GPU until its\n      // actually downloaded and we can rehash it.\n      if (needsDownload) {\n         _downloadMemCache(cache, dirtyRecord.sections);\n      }\n   }\n\n   mDirtyMemCaches.clear();\n}\n\nDataBufferObject *\nDriver::getDataMemCache(phys_addr baseAddress, uint32_t size)\n{\n   auto cache = getMemCache(baseAddress, 1, size);\n   return static_cast<DataBufferObject *>(cache);\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_memtracker.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n\n#include <cstdint>\n#include <libcpu/be2_struct.h>\n#include <libcpu/memtrack.h>\n#include <map>\n#include <forward_list>\n\nnamespace vulkan\n{\n\n/*\nThis memory tracker keeps track of a range of phys_addr memory for changes.\nIt attempts to make iteration as fast as possible by trying to keep the\nsegments linearly arranged in memory, while still allowing fast insertions.\nIt does this by using a dynamic list to hold the segments initially, but\nthen quickly optimizes them into a linear vector.\n\nImportant Semantics:\n - SegmentRef's are stable\n - Pointers to segments ARE NOT stable.\n - Don't forget to call optimize periodically.\n*/\n\ntemplate<typename _DataOwnerType>\nclass MemoryTracker\n{\npublic:\n   struct Segment {\n      // We keep a linked list of segments connected to eachother for\n      // faster iteration through all the segments.  It is intentionally\n      // at the top to increase the chance of being on the cacheline of\n      // the previous segment.\n      Segment *nextSegment = nullptr;\n\n      // Meta-data about what this segment represents\n      phys_addr address = {};\n      uint32_t size = 0;\n\n      // Stores the last memory tracking state for this segment.\n      cpu::MemtrackState dataState;\n\n      // Tracks the last CPU check of this segment, to avoid checking the\n      // memory multiple times in a single batch.\n      uint64_t lastCheckIndex = 0;\n\n      // Records if there is a pending GPU write for this data.  This is to ensure\n      // that we do not overwrite a pending GPU write with random CPU data.\n      bool gpuWritten = false;\n\n      // The last change for this segment\n      uint64_t lastChangeIndex = 0;\n\n      // Represents the object owning the most up to date version of the data.\n      _DataOwnerType lastChangeOwner = {};\n   };\n\nprotected:\n   typedef std::map<phys_addr, Segment*> SegmentMap;\n   typedef typename SegmentMap::iterator SegmentMapIter;\n\npublic:\n   class SegmentRef\n   {\n      friend MemoryTracker;\n\n   public:\n      SegmentRef()\n      {\n      }\n\n      Segment * get() const\n      {\n         return mIterator->second;\n      }\n\n      Segment * operator->() const\n      {\n         return get();\n      }\n\n   protected:\n      SegmentRef(SegmentMapIter iterator)\n         : mIterator(iterator)\n      {\n      }\n\n      SegmentMapIter mIterator;\n\n   };\n\n   void nextBatch()\n   {\n      mCurrentBatchIndex++;\n   }\n\n   uint64_t newChangeIndex()\n   {\n      return ++mChangeCounter;\n   }\n\n   SegmentRef get(phys_addr address, uint32_t size)\n   {\n      auto iter = _getSegment(address, size);\n      _ensureSegments(iter, size);\n      return iter;\n   }\n\n   void markSegmentGpuDone(Segment *segment)\n   {\n      segment->dataState = cpu::getMemoryState(segment->address, segment->size);\n      segment->gpuWritten = false;\n   }\n\n   void refreshSegment(Segment *segment)\n   {\n      _refreshSegment(segment);\n   }\n\n   void optimize()\n   {\n      // TODO: Maybe avoid optimizing for low dynamic segment counts.\n\n      if (mLookupMap.empty() || mDynamicSegments.empty()) {\n         // If the map is empty, or there are no dynamic segments,\n         // there is no need to optimize the map.\n         return;\n      }\n\n      // Reserve the new linear segments\n      std::vector<Segment> newLinearSegments;\n      newLinearSegments.reserve(mLookupMap.size());\n\n      // Move all our segments\n      auto iter = mLookupMap.begin();\n      newLinearSegments.push_back(std::move(*iter->second));\n      auto lastSegment = &newLinearSegments.back();\n      iter->second = lastSegment;\n      ++iter;\n\n      for ( ; iter != mLookupMap.end(); ++iter) {\n         newLinearSegments.push_back(std::move(*iter->second));\n         auto newSegment = &newLinearSegments.back();\n\n         iter->second = newSegment;\n\n         lastSegment->nextSegment = newSegment;\n\n         lastSegment = newSegment;\n      }\n      lastSegment->nextSegment = nullptr;\n\n      // Move the new linear map into place and clear the dynamic segments\n      // that are no longer being used.\n      mLinearSegments = std::move(newLinearSegments);\n      mDynamicSegments.clear();\n   }\n\nprotected:\n   SegmentMapIter\n   _insertSegment(SegmentMapIter position, phys_addr address, uint32_t size)\n   {\n      auto &segment = mDynamicSegments.emplace_front();\n      segment.address = address;\n      segment.size = size;\n\n      auto newIter = mLookupMap.insert(position, { segment.address, &segment });\n\n      if (newIter != mLookupMap.begin()) {\n         auto prevIter = newIter;\n         prevIter--;\n         prevIter->second->nextSegment = &segment;\n      }\n\n      auto nextIter = newIter;\n      nextIter++;\n      if (nextIter != mLookupMap.end()) {\n         segment.nextSegment = nextIter->second;\n      } else {\n         segment.nextSegment = nullptr;\n      }\n\n      return newIter;\n   }\n\n   SegmentMapIter\n   _splitSegment(SegmentMapIter iter, uint32_t newSize)\n   {\n      auto& oldSegment = iter->second;\n      decaf_check(oldSegment->size > newSize);\n\n      // Save the old info so we can do the final hash check\n      auto oldSize = oldSegment->size;\n      auto oldState = oldSegment->dataState;\n\n      // Create the new segment, save it to the map and resize the\n      // old segment to not overlap the new one.\n      auto newIter = _insertSegment(iter, oldSegment->address + newSize, oldSegment->size - newSize);\n      auto &newSegment = newIter->second;\n      oldSegment->size = newSize;\n\n      // Copy over some state from the old Segment\n      newSegment->lastCheckIndex = oldSegment->lastCheckIndex;\n      newSegment->gpuWritten = oldSegment->gpuWritten;\n      newSegment->lastChangeIndex = oldSegment->lastChangeIndex;\n      newSegment->lastChangeOwner = oldSegment->lastChangeOwner;\n\n      // If the old segment was written by the GPU, there is no need to\n      // do any of the hashing work, it will be done during readback.\n      if (oldSegment->gpuWritten) {\n         newSegment->dataState = {};\n         return newIter;\n      }\n\n      // Lets calculate the new hashes for the segments after they have been\n      // split to ensure we don't do unneeded uploading after a split.  We check\n      // that the new hashes reflect the same data that previous existed in the\n      // segment following this.\n      oldSegment->dataState = cpu::getMemoryState(oldSegment->address, oldSegment->size);\n      newSegment->dataState = cpu::getMemoryState(newSegment->address, newSegment->size);\n\n      // If the segment was last checked during this batch, there is no need to do\n      // any additional work to figure out if the data changed.\n      if (oldSegment->lastCheckIndex >= mCurrentBatchIndex) {\n         return newIter;\n      }\n\n      // Now check that the data hasn't changed since we did the last hashing.\n      auto newFullState = cpu::getMemoryState(oldSegment->address, oldSize);\n      if (newFullState != oldState) {\n         auto changeIndex = newChangeIndex();\n\n         oldSegment->lastCheckIndex = mCurrentBatchIndex;\n         oldSegment->lastChangeIndex = changeIndex;\n         oldSegment->lastChangeOwner = nullptr;\n\n         newSegment->lastCheckIndex = mCurrentBatchIndex;\n         newSegment->lastChangeIndex = changeIndex;\n         newSegment->lastChangeOwner = nullptr;\n      }\n\n      return newIter;\n   }\n\n   SegmentMapIter\n   _getSegment(phys_addr address, uint32_t maxSize)\n   {\n      auto iter = mLookupMap.lower_bound(address);\n\n      if (iter != mLookupMap.end()) {\n         // Check if we found an exact match.  If so, return that.\n         if (iter->second->address == address) {\n            return iter;\n         }\n\n         // Otherwise we need to bound our maxSize not to tramble this.\n         auto gapSize = static_cast<uint32_t>(iter->second->address - address);\n         maxSize = std::min(maxSize, gapSize);\n      }\n\n      // Check that we are not at the beginning, if we are and it wasn't an\n      // exact match, we know we need to make a new segment before this one.\n      if (iter != mLookupMap.begin()) {\n         --iter;\n\n         auto& foundSegment = iter->second;\n\n         // If the previous segment covers this new segments range, we\n         // need to split it and return that.\n         if (address < foundSegment->address + foundSegment->size) {\n            auto newSize = static_cast<uint32_t>(address - foundSegment->address);\n            return _splitSegment(iter, newSize);\n         }\n      }\n\n      // maxSize being 0 indicates that we need to ensure there is a split\n      // point in the map, but we don't need to generate anything for it.\n      if (maxSize == 0) {\n         return mLookupMap.end();\n      }\n\n      // Allocate a new segment for this at the end\n      return _insertSegment(mLookupMap.end(), address, maxSize);\n   }\n\n   void\n   _ensureSegments(SegmentMapIter firstSegment, uint32_t size)\n   {\n      auto curAddress = firstSegment->second->address;\n      auto sizeLeft = size;\n\n      // We ensure there is a split point at the end for us to hit.\n      // TODO: Do this below as it will be slightly faster\n      _getSegment(curAddress + size, 0);\n\n      auto iter = firstSegment;\n      while (sizeLeft > 0) {\n         if (iter == mLookupMap.end()) {\n            _insertSegment(iter, curAddress, sizeLeft);\n            break;\n         }\n\n         if (iter->second->address != curAddress) {\n            auto gapSize = static_cast<uint32_t>(iter->second->address - curAddress);\n            auto newSize = std::min(gapSize, sizeLeft);\n\n            iter = _insertSegment(iter, curAddress, newSize);\n         }\n\n         auto& segment = iter->second;\n\n         decaf_check(segment->address == curAddress);\n         decaf_check(segment->size <= sizeLeft);\n\n         curAddress += segment->size;\n         sizeLeft -= segment->size;\n\n         iter++;\n      }\n   }\n\n   void\n   _refreshSegment(Segment *segment)\n   {\n      // If this segment was last written by the GPU, then we are guarenteed\n      // to already have the most up to date data in a GPU buffer somewhere.\n      if (segment->gpuWritten) {\n         return;\n      }\n\n      // If this segment was already checked during this batch, there is no\n      // need to go check it again..\n      if (segment->lastCheckIndex >= mCurrentBatchIndex) {\n         return;\n      }\n\n      // Rehash all our data\n      auto dataState = cpu::getMemoryState(segment->address, segment->size);\n\n      // If we already have a hash, and the hash already matches, we can\n      // simply mark it as checked without any more downloads.\n      if (segment->lastCheckIndex > 0 && segment->dataState == dataState) {\n         segment->lastCheckIndex = mCurrentBatchIndex;\n         return;\n      }\n\n      // The data has changed, lets update our internal hashes and create\n      // a new memory change event to represent this.\n      auto changeIndex = newChangeIndex();\n\n      segment->dataState = dataState;\n      segment->lastCheckIndex = mCurrentBatchIndex;\n      segment->lastChangeIndex = changeIndex;\n      segment->lastChangeOwner = nullptr;\n   }\n\n   uint64_t mCurrentBatchIndex = 0;\n   uint64_t mChangeCounter = 0;\n   SegmentMap mLookupMap;\n   std::vector<Segment> mLinearSegments;\n   std::forward_list<Segment> mDynamicSegments;\n\n};\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_pipelinelayouts.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"vulkan_utils.h\"\n#include \"spirv/spirv_pushconstants.h\"\n\nnamespace vulkan\n{\n\nPipelineLayoutDesc\nDriver::generatePipelineLayoutDesc(const PipelineDesc& pipelineDesc)\n{\n   PipelineLayoutDesc layoutDesc;\n\n   if (pipelineDesc.vertexShader) {\n      const auto& shaderMeta = pipelineDesc.vertexShader->shader.meta;\n      if (shaderMeta.cfileUsed) {\n         layoutDesc.vsBufferUsed[0] = true;\n         for (auto i = 1; i < latte::MaxUniformBlocks; ++i) {\n            layoutDesc.vsBufferUsed[i] = false;\n         }\n      } else {\n         for (auto i = 0; i < latte::MaxUniformBlocks; ++i) {\n            layoutDesc.vsBufferUsed[i] = shaderMeta.cbufferUsed[i];\n         }\n      }\n      for (auto i = 0; i < latte::MaxSamplers; ++i) {\n         layoutDesc.vsSamplerUsed[i] = shaderMeta.samplerUsed[i];\n      }\n      for (auto i = 0; i < latte::MaxTextures; ++i) {\n         layoutDesc.vsTextureUsed[i] = shaderMeta.textureUsed[i];\n      }\n   } else {\n      for (auto i = 0; i < latte::MaxUniformBlocks; ++i) {\n         layoutDesc.vsBufferUsed[i] = false;\n      }\n      for (auto i = 0; i < latte::MaxSamplers; ++i) {\n         layoutDesc.vsSamplerUsed[i] = false;\n      }\n      for (auto i = 0; i < latte::MaxTextures; ++i) {\n         layoutDesc.vsTextureUsed[i] = false;\n      }\n   }\n\n   if (pipelineDesc.geometryShader) {\n      const auto& shaderMeta = pipelineDesc.geometryShader->shader.meta;\n      if (shaderMeta.cfileUsed) {\n         layoutDesc.vsBufferUsed[0] = true;\n         for (auto i = 1; i < latte::MaxUniformBlocks; ++i) {\n            layoutDesc.gsBufferUsed[i] = false;\n         }\n      } else {\n         for (auto i = 0; i < latte::MaxUniformBlocks; ++i) {\n            layoutDesc.gsBufferUsed[i] = shaderMeta.cbufferUsed[i];\n         }\n      }\n      for (auto i = 0; i < latte::MaxSamplers; ++i) {\n         layoutDesc.gsSamplerUsed[i] = shaderMeta.samplerUsed[i];\n      }\n      for (auto i = 0; i < latte::MaxTextures; ++i) {\n         layoutDesc.gsTextureUsed[i] = shaderMeta.textureUsed[i];\n      }\n   } else {\n      for (auto i = 0; i < latte::MaxUniformBlocks; ++i) {\n         layoutDesc.gsBufferUsed[i] = false;\n      }\n      for (auto i = 0; i < latte::MaxSamplers; ++i) {\n         layoutDesc.gsSamplerUsed[i] = false;\n      }\n      for (auto i = 0; i < latte::MaxTextures; ++i) {\n         layoutDesc.gsTextureUsed[i] = false;\n      }\n   }\n\n   if (pipelineDesc.pixelShader) {\n      const auto& shaderMeta = pipelineDesc.pixelShader->shader.meta;\n      if (shaderMeta.cfileUsed) {\n         layoutDesc.psBufferUsed[0] = true;\n         for (auto i = 1; i < latte::MaxUniformBlocks; ++i) {\n            layoutDesc.psBufferUsed[i] = false;\n         }\n      } else {\n         for (auto i = 0; i < latte::MaxUniformBlocks; ++i) {\n            layoutDesc.psBufferUsed[i] = shaderMeta.cbufferUsed[i];\n         }\n      }\n      for (auto i = 0; i < latte::MaxSamplers; ++i) {\n         layoutDesc.psSamplerUsed[i] = shaderMeta.samplerUsed[i];\n      }\n      for (auto i = 0; i < latte::MaxTextures; ++i) {\n         layoutDesc.psTextureUsed[i] = shaderMeta.textureUsed[i];\n      }\n   } else {\n      for (auto i = 0; i < latte::MaxUniformBlocks; ++i) {\n         layoutDesc.psBufferUsed[i] = false;\n      }\n      for (auto i = 0; i < latte::MaxSamplers; ++i) {\n         layoutDesc.psSamplerUsed[i] = false;\n      }\n      for (auto i = 0; i < latte::MaxTextures; ++i) {\n         layoutDesc.psTextureUsed[i] = false;\n      }\n   }\n\n   // Calculate the descriptor count\n\n   for (auto i = 0; i < latte::MaxTextures; ++i) {\n      if (layoutDesc.vsSamplerUsed[i] || layoutDesc.vsTextureUsed[i]) {\n         layoutDesc.numDescriptors++;\n      }\n      if (layoutDesc.gsSamplerUsed[i] || layoutDesc.gsTextureUsed[i]) {\n         layoutDesc.numDescriptors++;\n      }\n      if (layoutDesc.psSamplerUsed[i] || layoutDesc.psTextureUsed[i]) {\n         layoutDesc.numDescriptors++;\n      }\n   }\n   for (auto i = 0; i < latte::MaxUniformBlocks; ++i) {\n      if (layoutDesc.vsBufferUsed[i]) {\n         layoutDesc.numDescriptors++;\n      }\n      if (layoutDesc.gsBufferUsed[i]) {\n         layoutDesc.numDescriptors++;\n      }\n      if (layoutDesc.psBufferUsed[i]) {\n         layoutDesc.numDescriptors++;\n      }\n   }\n\n   return layoutDesc;\n}\n\nPipelineLayoutObject *\nDriver::getPipelineLayout(const HashedDesc<PipelineLayoutDesc>& desc, bool forPush)\n{\n   const auto& currentDesc = desc;\n\n   // We do not check if this pipeline layout matches the currently bound one\n   // since that is pretty rare to happen, and this is only invoked whenever\n   // a pipeline is being generated anyways...\n\n   auto& foundPl = mPipelineLayouts[currentDesc.hash()];\n   if (foundPl) {\n      return foundPl;\n   }\n\n   foundPl = new PipelineLayoutObject();\n   foundPl->desc = currentDesc;\n\n   // -- Descriptor Layout\n   std::vector<vk::DescriptorSetLayoutBinding> bindings;\n\n   // We combine the samplers and textures into a single descriptor in the\n   // pipeline layout, so we need to make sure this always matches!\n   static_assert(latte::MaxTextures == latte::MaxSamplers);\n\n\n   for (auto i = 0; i < latte::MaxTextures; ++i) {\n      if (currentDesc->vsTextureUsed[i] || currentDesc->vsSamplerUsed[i]) {\n         vk::DescriptorSetLayoutBinding texSampBindingDesc;\n         texSampBindingDesc.binding = (0 * 32) + 0 + i;\n         if (currentDesc->vsSamplerUsed[i] && currentDesc->vsTextureUsed[i]) {\n            texSampBindingDesc.descriptorType = vk::DescriptorType::eCombinedImageSampler;\n         } else if (currentDesc->vsSamplerUsed[i]) {\n            texSampBindingDesc.descriptorType = vk::DescriptorType::eSampler;\n         } else if (currentDesc->vsTextureUsed[i]) {\n            texSampBindingDesc.descriptorType = vk::DescriptorType::eSampledImage;\n         }\n\n         texSampBindingDesc.descriptorCount = 1;\n         texSampBindingDesc.stageFlags = vk::ShaderStageFlagBits::eVertex;\n         texSampBindingDesc.pImmutableSamplers = nullptr;\n         bindings.push_back(texSampBindingDesc);\n      }\n\n      if (currentDesc->gsTextureUsed[i] || currentDesc->gsSamplerUsed[i]) {\n         vk::DescriptorSetLayoutBinding texSampBindingDesc;\n         texSampBindingDesc.binding = (1 * 32) + 0 + i;\n         if (currentDesc->gsSamplerUsed[i] && currentDesc->gsTextureUsed[i]) {\n            texSampBindingDesc.descriptorType = vk::DescriptorType::eCombinedImageSampler;\n         } else if (currentDesc->gsSamplerUsed[i]) {\n            texSampBindingDesc.descriptorType = vk::DescriptorType::eSampler;\n         } else if (currentDesc->gsTextureUsed[i]) {\n            texSampBindingDesc.descriptorType = vk::DescriptorType::eSampledImage;\n         }\n         texSampBindingDesc.descriptorCount = 1;\n         texSampBindingDesc.stageFlags = vk::ShaderStageFlagBits::eGeometry;\n         texSampBindingDesc.pImmutableSamplers = nullptr;\n         bindings.push_back(texSampBindingDesc);\n      }\n\n      if (currentDesc->psTextureUsed[i] || currentDesc->psSamplerUsed[i]) {\n         vk::DescriptorSetLayoutBinding texSampBindingDesc;\n         texSampBindingDesc.binding = (2 * 32) + 0 + i;\n         if (currentDesc->psSamplerUsed[i] && currentDesc->psTextureUsed[i]) {\n            texSampBindingDesc.descriptorType = vk::DescriptorType::eCombinedImageSampler;\n         } else if (currentDesc->psSamplerUsed[i]) {\n            texSampBindingDesc.descriptorType = vk::DescriptorType::eSampler;\n         } else if (currentDesc->psTextureUsed[i]) {\n            texSampBindingDesc.descriptorType = vk::DescriptorType::eSampledImage;\n         }\n         texSampBindingDesc.descriptorCount = 1;\n         texSampBindingDesc.stageFlags = vk::ShaderStageFlagBits::eFragment;\n         texSampBindingDesc.pImmutableSamplers = nullptr;\n         bindings.push_back(texSampBindingDesc);\n      }\n   }\n\n   for (auto i = 0; i < latte::MaxUniformBlocks; ++i) {\n      if (i >= 15) {\n         // Vulkan does not support more than 15 uniform blocks unfortunately,\n         // if we ever encounter a game needing all 15, we will need to do block\n         // splitting or utilize SSBO's.\n         break;\n      }\n\n      if (currentDesc->vsBufferUsed[i]) {\n         vk::DescriptorSetLayoutBinding cbufferBindingDesc;\n         cbufferBindingDesc.binding = (0 * 32) + 16 + i;\n         cbufferBindingDesc.descriptorType = vk::DescriptorType::eStorageBuffer;\n         cbufferBindingDesc.descriptorCount = 1;\n         cbufferBindingDesc.stageFlags = vk::ShaderStageFlagBits::eVertex;\n         cbufferBindingDesc.pImmutableSamplers = nullptr;\n         bindings.push_back(cbufferBindingDesc);\n      }\n\n      if (currentDesc->gsBufferUsed[i]) {\n         vk::DescriptorSetLayoutBinding cbufferBindingDesc;\n         cbufferBindingDesc.binding = (1 * 32) + 16 + i;\n         cbufferBindingDesc.descriptorType = vk::DescriptorType::eStorageBuffer;\n         cbufferBindingDesc.descriptorCount = 1;\n         cbufferBindingDesc.stageFlags = vk::ShaderStageFlagBits::eGeometry;\n         cbufferBindingDesc.pImmutableSamplers = nullptr;\n         bindings.push_back(cbufferBindingDesc);\n      }\n\n      if (currentDesc->psBufferUsed[i]) {\n         vk::DescriptorSetLayoutBinding cbufferBindingDesc;\n         cbufferBindingDesc.binding = (2 * 32) + 16 + i;\n         cbufferBindingDesc.descriptorType = vk::DescriptorType::eStorageBuffer;\n         cbufferBindingDesc.descriptorCount = 1;\n         cbufferBindingDesc.stageFlags = vk::ShaderStageFlagBits::eFragment;\n         cbufferBindingDesc.pImmutableSamplers = nullptr;\n         bindings.push_back(cbufferBindingDesc);\n      }\n   }\n\n   if (forPush) {\n      decaf_check(bindings.size() == currentDesc->numDescriptors);\n   }\n\n   vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutDesc;\n\n   if (forPush) {\n      descriptorSetLayoutDesc.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;\n   }\n\n   descriptorSetLayoutDesc.bindingCount = static_cast<uint32_t>(bindings.size());\n   descriptorSetLayoutDesc.pBindings = bindings.data();\n   auto descriptorLayout = mDevice.createDescriptorSetLayout(descriptorSetLayoutDesc);\n\n   // -- Push Descriptors\n   std::array<vk::PushConstantRange, 2> pushConstants;\n   pushConstants[0].stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eGeometry;\n   pushConstants[0].offset = spirv::VertexPushConstantsOffset;\n   pushConstants[0].size = spirv::VertexPushConstantsSize;\n\n   pushConstants[1].stageFlags = vk::ShaderStageFlagBits::eFragment;\n   pushConstants[1].offset = spirv::FragmentPushConstantsOffset;\n   pushConstants[1].size = spirv::FragmentPushConstantsSize;\n\n   // -- Pipeline Layout\n   vk::PipelineLayoutCreateInfo pipelineLayoutDesc;\n   pipelineLayoutDesc.setLayoutCount = 1;\n   pipelineLayoutDesc.pSetLayouts = &descriptorLayout;\n   pipelineLayoutDesc.pushConstantRangeCount = static_cast<uint32_t>(pushConstants.size());\n   pipelineLayoutDesc.pPushConstantRanges = pushConstants.data();\n   auto pipelineLayout = mDevice.createPipelineLayout(pipelineLayoutDesc);\n\n   foundPl->descriptorLayout = descriptorLayout;\n   foundPl->pipelineLayout = pipelineLayout;\n\n   return foundPl;\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_pipelines.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"vulkan_utils.h\"\n\n#include <common/log.h>\n\nstatic constexpr bool ForceDescriptorSets = false;\n\nnamespace vulkan\n{\n\nPipelineDesc\nDriver::getPipelineDesc()\n{\n   PipelineDesc desc;\n\n   desc.renderPass = mCurrentDraw->renderPass;\n   desc.vertexShader = mCurrentDraw->vertexShader;\n   desc.geometryShader = mCurrentDraw->geometryShader;\n   desc.pixelShader = mCurrentDraw->pixelShader;\n   desc.rectStubShader = mCurrentDraw->rectStubShader;\n\n   // -- Vertex Strides\n   for (auto i = 0u; i < latte::MaxAttribBuffers; ++i) {\n      // Skip unused input buffers\n      if (!desc.vertexShader->shader.meta.attribBuffers[i].isUsed) {\n         desc.attribBufferStride[i] = 0;\n         continue;\n      }\n\n      auto resourceOffset = (latte::SQ_RES_OFFSET::VS_ATTRIB_RESOURCE_0 + i) * 7;\n      auto sq_vtx_constant_word2 = getRegister<latte::SQ_VTX_CONSTANT_WORD2_N>(latte::Register::SQ_RESOURCE_WORD2_0 + 4 * resourceOffset);\n      desc.attribBufferStride[i] = sq_vtx_constant_word2.STRIDE();\n   }\n\n   // -- Vertex Buffer Divisors\n   desc.attribBufferDivisor[0] = 1;\n   desc.attribBufferDivisor[1] = 1;\n\n   uint32_t instanceStepRate0 = getRegister<uint32_t>(latte::Register::VGT_INSTANCE_STEP_RATE_0);\n   uint32_t instanceStepRate1 = getRegister<uint32_t>(latte::Register::VGT_INSTANCE_STEP_RATE_1);\n   for (auto &attribBuffer : desc.vertexShader->shader.meta.attribBuffers) {\n      if (attribBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::REGISTER_0) {\n         desc.attribBufferDivisor[0] = instanceStepRate0;\n      } else if (attribBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::REGISTER_1) {\n         desc.attribBufferDivisor[1] = instanceStepRate1;\n      }\n   }\n\n   // -- Primitive Type\n   auto vgt_primitive_type = getRegister<latte::VGT_PRIMITIVE_TYPE>(latte::Register::VGT_PRIMITIVE_TYPE);\n   desc.primitiveType = vgt_primitive_type.PRIM_TYPE();\n\n   // -- Primitive Reset Stuff\n   switch (desc.primitiveType) {\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINESTRIP:\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRIFAN:\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRISTRIP:\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINESTRIP_ADJ:\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRISTRIP_ADJ:\n   case latte::VGT_DI_PRIMITIVE_TYPE::POLYGON:\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINE_STRIP_2D:\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRI_STRIP_2D:\n   case latte::VGT_DI_PRIMITIVE_TYPE::QUADSTRIP:\n   {\n      auto vgt_multi_prim_ib_reset_en = getRegister<latte::VGT_MULTI_PRIM_IB_RESET_EN>(latte::Register::VGT_MULTI_PRIM_IB_RESET_EN);\n      auto vgt_multi_prim_ib_reset_idx = getRegister<latte::VGT_MULTI_PRIM_IB_RESET_INDX>(latte::Register::VGT_MULTI_PRIM_IB_RESET_INDX);\n      desc.primitiveResetEnabled = vgt_multi_prim_ib_reset_en.RESET_EN();\n      if (desc.primitiveResetEnabled) {\n         desc.primitiveResetIndex = vgt_multi_prim_ib_reset_idx.RESET_INDX();\n      }\n      break;\n   }\n   case latte::VGT_DI_PRIMITIVE_TYPE::POINTLIST:\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINELIST:\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINELIST_ADJ:\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINELOOP:\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRILIST:\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRILIST_ADJ:\n   case latte::VGT_DI_PRIMITIVE_TYPE::RECTLIST:\n   case latte::VGT_DI_PRIMITIVE_TYPE::QUADLIST:\n      desc.primitiveResetEnabled = false;\n      desc.primitiveResetIndex = 0;\n      break;\n   default:\n      decaf_abort(\"Unexpected VGT primitive type\");\n   }\n\n   // -- Constants mode\n   auto sq_config = getRegister<latte::SQ_CONFIG>(latte::Register::SQ_CONFIG);\n   desc.dx9Consts = sq_config.DX9_CONSTS();\n\n   // -- Rasterization stuff\n   auto pa_cl_clip_cntl = getRegister<latte::PA_CL_CLIP_CNTL>(latte::Register::PA_CL_CLIP_CNTL);\n   auto pa_su_line_cntl = getRegister<latte::PA_SU_LINE_CNTL>(latte::Register::PA_SU_LINE_CNTL);\n   auto pa_su_sc_mode_cntl = getRegister<latte::PA_SU_SC_MODE_CNTL>(latte::Register::PA_SU_SC_MODE_CNTL);\n   auto pa_su_poly_offset_front_offset = getRegister<latte::PA_SU_POLY_OFFSET_FRONT_OFFSET>(latte::Register::PA_SU_POLY_OFFSET_FRONT_OFFSET);\n   auto pa_su_poly_offset_front_scale = getRegister<latte::PA_SU_POLY_OFFSET_FRONT_SCALE>(latte::Register::PA_SU_POLY_OFFSET_FRONT_SCALE);\n   auto pa_su_poly_offset_back_offset = getRegister<latte::PA_SU_POLY_OFFSET_FRONT_OFFSET>(latte::Register::PA_SU_POLY_OFFSET_BACK_OFFSET);\n   auto pa_su_poly_offset_back_scale = getRegister<latte::PA_SU_POLY_OFFSET_FRONT_SCALE>(latte::Register::PA_SU_POLY_OFFSET_BACK_SCALE);\n   auto pa_su_poly_offset_clamp = getRegister<latte::PA_SU_POLY_OFFSET_CLAMP>(latte::Register::PA_SU_POLY_OFFSET_CLAMP);\n\n   decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_0());\n   decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_1());\n   decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_2());\n   decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_3());\n   decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_4());\n   decaf_check_warn_once(!pa_cl_clip_cntl.UCP_ENA_5());\n   decaf_check_warn_once(!pa_cl_clip_cntl.PS_UCP_Y_SCALE_NEG());\n   decaf_check_warn_once(pa_cl_clip_cntl.PS_UCP_MODE() == latte::PA_PS_UCP_MODE::CULL_DISTANCE);\n   decaf_check_warn_once(!pa_cl_clip_cntl.UCP_CULL_ONLY_ENA());\n   decaf_check_warn_once(!pa_cl_clip_cntl.BOUNDARY_EDGE_FLAG_ENA());\n   decaf_check_warn_once(!pa_cl_clip_cntl.DIS_CLIP_ERR_DETECT());\n   decaf_check_warn_once(!pa_cl_clip_cntl.VTX_KILL_OR());\n   decaf_check_warn_once(!pa_cl_clip_cntl.DX_LINEAR_ATTR_CLIP_ENA());\n   decaf_check_warn_once(!pa_cl_clip_cntl.VTE_VPORT_PROVOKE_DISABLE());\n\n   // pa_cl_clip_cntl.CLIP_DISABLE() is really an optimization which\n   // indicates that there will be no draws outside the boundary of\n   // the framebuffer.  We don't need to handle this.\n\n   // pa_cl_clip_cntl.DX_CLIP_SPACE_DEF() is handled by the shaders,\n   // so it is uploaded in the push constants buffer in the shader\n   // resources binding\n\n   desc.rasteriserDisable = pa_cl_clip_cntl.RASTERISER_DISABLE();\n\n   // Line widths\n   desc.lineWidth = pa_su_line_cntl.WIDTH();\n\n   desc.cullFront = pa_su_sc_mode_cntl.CULL_FRONT();\n   desc.cullBack = pa_su_sc_mode_cntl.CULL_BACK();\n   desc.paFace = pa_su_sc_mode_cntl.FACE();\n\n   // We do not support split front/back mode, but we make a best-effort\n   // to avoid needing to configure the two differently.  In most cases\n   // that there is divergence between front/back configuration, its due\n   // to one of them being culled away anyways.  I'm not 100% confident\n   // that this is the correct behaviour though, it would be better if\n   // we supported splitting the polygons as needed.\n\n   // TODO: Use decaf_check_warn_once here instead...\n\n   desc.polyPType = latte::PA_PTYPE::TRIANGLES;\n\n   auto polyMode = pa_su_sc_mode_cntl.POLY_MODE();\n   if (polyMode == 0) {\n      // POLY_MODE is disabled\n   } else if (polyMode == 1) {\n      // POLY_MODE is dual-triangle\n      if (desc.cullBack) {\n         desc.polyPType = pa_su_sc_mode_cntl.POLYMODE_FRONT_PTYPE();\n      } else if (desc.cullFront) {\n         desc.polyPType = pa_su_sc_mode_cntl.POLYMODE_BACK_PTYPE();\n      } else {\n         decaf_check_warn_once(pa_su_sc_mode_cntl.POLYMODE_FRONT_PTYPE() == pa_su_sc_mode_cntl.POLYMODE_BACK_PTYPE());\n         desc.polyPType = pa_su_sc_mode_cntl.POLYMODE_FRONT_PTYPE();\n      }\n   } else {\n      gLog->warn(\"Unexpected POLY_MODE value {}.\", polyMode);\n   }\n\n   desc.polyBiasEnabled = false;\n   if (desc.polyPType == latte::PA_PTYPE::TRIANGLES) {\n      if (desc.cullBack) {\n         desc.polyBiasEnabled = pa_su_sc_mode_cntl.POLY_OFFSET_FRONT_ENABLE();\n      } else if (desc.cullFront) {\n         desc.polyBiasEnabled = pa_su_sc_mode_cntl.POLY_OFFSET_BACK_ENABLE();\n      } else {\n         decaf_check_warn_once(pa_su_sc_mode_cntl.POLY_OFFSET_FRONT_ENABLE() == pa_su_sc_mode_cntl.POLY_OFFSET_BACK_ENABLE());\n         desc.polyBiasEnabled = pa_su_sc_mode_cntl.POLY_OFFSET_FRONT_ENABLE();\n      }\n   } else {\n      desc.polyBiasEnabled = pa_su_sc_mode_cntl.POLY_OFFSET_PARA_ENABLE();\n   }\n\n   if (desc.polyBiasEnabled) {\n      desc.polyBiasClamp = pa_su_poly_offset_clamp.CLAMP();\n\n      if (desc.cullBack) {\n         desc.polyBiasOffset = pa_su_poly_offset_front_offset.OFFSET();\n         desc.polyBiasScale = pa_su_poly_offset_front_scale.SCALE();\n      } else if (desc.cullFront) {\n         desc.polyBiasOffset = pa_su_poly_offset_back_offset.OFFSET();\n         desc.polyBiasScale = pa_su_poly_offset_back_scale.SCALE();\n      } else {\n         decaf_check_warn_once(pa_su_poly_offset_front_offset.value == pa_su_poly_offset_back_offset.value);\n         decaf_check_warn_once(pa_su_poly_offset_front_scale.value == pa_su_poly_offset_back_scale.value);\n         desc.polyBiasOffset = pa_su_poly_offset_front_offset.OFFSET();\n         desc.polyBiasScale = pa_su_poly_offset_front_scale.SCALE();\n      }\n   } else {\n      desc.polyBiasClamp = 0.0f;\n      desc.polyBiasOffset = 0.0f;\n      desc.polyBiasScale = 0.0f;\n   }\n\n   // We only support zclip being on or off, not individually for near/far.\n   decaf_check_warn_once(pa_cl_clip_cntl.ZCLIP_NEAR_DISABLE() == pa_cl_clip_cntl.ZCLIP_FAR_DISABLE());\n   desc.zclipDisabled = pa_cl_clip_cntl.ZCLIP_NEAR_DISABLE();\n\n   // -- Depth/Stencil control stuff\n   auto db_depth_control = getRegister<latte::DB_DEPTH_CONTROL>(latte::Register::DB_DEPTH_CONTROL);\n   auto db_stencilrefmask = getRegister<latte::DB_STENCILREFMASK>(latte::Register::DB_STENCILREFMASK);\n   auto db_stencilrefmask_bf = getRegister<latte::DB_STENCILREFMASK_BF>(latte::Register::DB_STENCILREFMASK_BF);\n\n   desc.stencilEnable = db_depth_control.STENCIL_ENABLE();\n   if (desc.stencilEnable) {\n      desc.stencilFront.compareFunc = db_depth_control.STENCILFUNC();\n      desc.stencilFront.failOp = db_depth_control.STENCILFAIL();\n      desc.stencilFront.zPassOp = db_depth_control.STENCILZPASS();\n      desc.stencilFront.zFailOp = db_depth_control.STENCILZFAIL();\n      desc.stencilFront.ref = db_stencilrefmask.STENCILREF();\n      desc.stencilFront.mask = db_stencilrefmask.STENCILMASK();\n      desc.stencilFront.writeMask = db_stencilrefmask.STENCILWRITEMASK();\n\n      if (db_depth_control.BACKFACE_ENABLE()) {\n         desc.stencilBack.compareFunc = db_depth_control.STENCILFUNC_BF();\n         desc.stencilBack.failOp = db_depth_control.STENCILFAIL_BF();\n         desc.stencilBack.zPassOp = db_depth_control.STENCILZPASS_BF();\n         desc.stencilBack.zFailOp = db_depth_control.STENCILZFAIL_BF();\n         desc.stencilBack.ref = db_stencilrefmask_bf.STENCILREF_BF();\n         desc.stencilBack.mask = db_stencilrefmask_bf.STENCILMASK_BF();\n         desc.stencilBack.writeMask = db_stencilrefmask_bf.STENCILWRITEMASK_BF();\n      } else {\n         desc.stencilBack = desc.stencilFront;\n      }\n   } else {\n      desc.stencilFront.compareFunc = latte::REF_FUNC::NEVER;\n      desc.stencilFront.failOp = latte::DB_STENCIL_FUNC::KEEP;\n      desc.stencilFront.zPassOp = latte::DB_STENCIL_FUNC::KEEP;\n      desc.stencilFront.zFailOp = latte::DB_STENCIL_FUNC::KEEP;\n      desc.stencilFront.ref = 0;\n      desc.stencilFront.mask = 0;\n      desc.stencilFront.writeMask = 0;\n      desc.stencilBack = desc.stencilFront;\n   }\n\n   desc.zEnable = db_depth_control.Z_ENABLE();\n   desc.zWriteEnable = db_depth_control.Z_WRITE_ENABLE();\n   if (desc.zEnable) {\n      desc.zFunc = db_depth_control.ZFUNC();\n   } else {\n      desc.zFunc = latte::REF_FUNC::ALWAYS;\n   }\n\n\n   // -- Color control stuff\n   auto makeBlendControl = [&](PipelineDesc::BlendControl& blend, const latte::CB_BLENDN_CONTROL& cb_blend_control)\n   {\n      if (blend.blendingEnabled) {\n         blend.opacityWeight = cb_blend_control.OPACITY_WEIGHT();\n         blend.colorCombFcn = cb_blend_control.COLOR_COMB_FCN();\n         blend.colorSrcBlend = cb_blend_control.COLOR_SRCBLEND();\n         blend.colorDstBlend = cb_blend_control.COLOR_DESTBLEND();\n         if (cb_blend_control.SEPARATE_ALPHA_BLEND()) {\n            blend.alphaCombFcn = cb_blend_control.ALPHA_COMB_FCN();\n            blend.alphaSrcBlend = cb_blend_control.ALPHA_SRCBLEND();\n            blend.alphaDstBlend = cb_blend_control.ALPHA_DESTBLEND();\n         } else {\n            blend.alphaCombFcn = blend.colorCombFcn;\n            blend.alphaSrcBlend = blend.colorSrcBlend;\n            blend.alphaDstBlend = blend.colorDstBlend;\n         }\n      } else {\n         blend.colorCombFcn = latte::CB_COMB_FUNC::DST_PLUS_SRC;\n         blend.colorSrcBlend = latte::CB_BLEND_FUNC::ZERO;\n         blend.colorDstBlend = latte::CB_BLEND_FUNC::ZERO;\n         blend.alphaCombFcn = latte::CB_COMB_FUNC::DST_PLUS_SRC;\n         blend.alphaSrcBlend = latte::CB_BLEND_FUNC::ZERO;\n         blend.alphaDstBlend = latte::CB_BLEND_FUNC::ZERO;\n         blend.opacityWeight = false;\n      }\n\n      return blend;\n   };\n\n   auto cb_color_control = getRegister<latte::CB_COLOR_CONTROL>(latte::Register::CB_COLOR_CONTROL);\n\n   // TODO: Implement cb_color_control.FOG_ENABLE()\n   // TODO: Implement cb_color_control.MULTIWRITE_ENABLE()\n   // TODO: Implement cb_color_control.DITHER_ENABLE()\n   // TODO: Implement cb_color_control.DEGAMMA_ENABLE()\n\n   desc.rop3 = cb_color_control.ROP3();\n\n   bool shouldUseBlendEnable = false;\n   bool shouldUseBlendControls = false;\n   bool shouldUseBlendMasks = false;\n\n   if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::NORMAL) {\n      shouldUseBlendEnable = true;\n      shouldUseBlendControls = true;\n      shouldUseBlendMasks = true;\n   } else if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::DISABLE) {\n      // We disable all backend state with this op...\n   } else if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::FAST_CLEAR) {\n      shouldUseBlendMasks = true;\n   } else if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::FORCE_CLEAR) {\n      shouldUseBlendMasks = true;\n   } else {\n      decaf_abort(\"Encountered unexpected CB_SPECIAL_OP\");\n   }\n\n   if (shouldUseBlendEnable) {\n      for (auto id = 0u; id < latte::MaxRenderTargets; ++id) {\n         desc.cbBlendControls[id].blendingEnabled = cb_color_control.TARGET_BLEND_ENABLE() & (1 << id);\n      }\n   } else {\n      for (auto id = 0u; id < latte::MaxRenderTargets; ++id) {\n         desc.cbBlendControls[id].blendingEnabled = false;\n      }\n   }\n\n   if (shouldUseBlendControls) {\n      if (!cb_color_control.PER_MRT_BLEND()) {\n         // For some reason, even though there is a CB_BLEND_CONTROL register, it is\n         // not used here like the docs indicate, and BLEND0 is used instead...\n         auto cb_blend_control = getRegister<latte::CB_BLENDN_CONTROL>(latte::Register::CB_BLEND0_CONTROL);\n\n         // We need to iterate still, since blending could be off on specific targets\n         for (auto id = 0u; id < latte::MaxRenderTargets; ++id) {\n            makeBlendControl(desc.cbBlendControls[id], cb_blend_control);\n         }\n      } else {\n         for (auto id = 0u; id < latte::MaxRenderTargets; ++id) {\n            auto cb_blend_control = getRegister<latte::CB_BLENDN_CONTROL>(latte::Register::CB_BLEND0_CONTROL + 4 * id);\n            makeBlendControl(desc.cbBlendControls[id], cb_blend_control);\n         }\n      }\n   } else {\n      for (auto id = 0u; id < latte::MaxRenderTargets; ++id) {\n         auto& blend = desc.cbBlendControls[id];\n         blend.colorCombFcn = latte::CB_COMB_FUNC::DST_PLUS_SRC;\n         blend.colorSrcBlend = latte::CB_BLEND_FUNC::ZERO;\n         blend.colorDstBlend = latte::CB_BLEND_FUNC::ZERO;\n         blend.alphaCombFcn = latte::CB_COMB_FUNC::DST_PLUS_SRC;\n         blend.alphaSrcBlend = latte::CB_BLEND_FUNC::ZERO;\n         blend.alphaDstBlend = latte::CB_BLEND_FUNC::ZERO;\n         blend.opacityWeight = false;\n      }\n   }\n\n   if (shouldUseBlendMasks) {\n      auto cb_target_mask = getRegister<latte::CB_TARGET_MASK>(latte::Register::CB_TARGET_MASK);\n      decaf_check(latte::MaxRenderTargets == 8);\n      desc.cbBlendControls[0].targetMask = cb_target_mask.TARGET0_ENABLE();\n      desc.cbBlendControls[1].targetMask = cb_target_mask.TARGET1_ENABLE();\n      desc.cbBlendControls[2].targetMask = cb_target_mask.TARGET2_ENABLE();\n      desc.cbBlendControls[3].targetMask = cb_target_mask.TARGET3_ENABLE();\n      desc.cbBlendControls[4].targetMask = cb_target_mask.TARGET4_ENABLE();\n      desc.cbBlendControls[5].targetMask = cb_target_mask.TARGET5_ENABLE();\n      desc.cbBlendControls[6].targetMask = cb_target_mask.TARGET6_ENABLE();\n      desc.cbBlendControls[7].targetMask = cb_target_mask.TARGET7_ENABLE();\n   } else {\n      for (auto id = 0u; id < latte::MaxRenderTargets; ++id) {\n         desc.cbBlendControls[id].targetMask = 1 | 2 | 4 | 8;\n      }\n   }\n\n   auto cb_blend_red = getRegister<latte::CB_BLEND_RED>(latte::Register::CB_BLEND_RED);\n   auto cb_blend_green = getRegister<latte::CB_BLEND_GREEN>(latte::Register::CB_BLEND_GREEN);\n   auto cb_blend_blue = getRegister<latte::CB_BLEND_BLUE>(latte::Register::CB_BLEND_BLUE);\n   auto cb_blend_alpha = getRegister<latte::CB_BLEND_ALPHA>(latte::Register::CB_BLEND_ALPHA);\n   desc.cbBlendConstants = {\n      cb_blend_red.BLEND_RED(),\n      cb_blend_green.BLEND_GREEN(),\n      cb_blend_blue.BLEND_BLUE(),\n      cb_blend_alpha.BLEND_ALPHA() };\n\n   auto sx_alpha_test_control = getRegister<latte::SX_ALPHA_TEST_CONTROL>(latte::Register::SX_ALPHA_TEST_CONTROL);\n   auto sx_alpha_ref = getRegister<latte::SX_ALPHA_REF>(latte::Register::SX_ALPHA_REF);\n   desc.alphaFunc = sx_alpha_test_control.ALPHA_FUNC();\n   if (!sx_alpha_test_control.ALPHA_TEST_ENABLE()) {\n      desc.alphaFunc = latte::REF_FUNC::ALWAYS;\n   }\n   if (sx_alpha_test_control.ALPHA_TEST_BYPASS()) {\n      desc.alphaFunc = latte::REF_FUNC::ALWAYS;\n   }\n   desc.alphaRef = sx_alpha_ref.ALPHA_REF();\n\n   return desc;\n}\n\nbool\nDriver::checkCurrentPipeline()\n{\n   decaf_check(mCurrentDraw->vertexShader);\n   decaf_check(mCurrentDraw->renderPass);\n\n   HashedDesc<PipelineDesc> currentDesc = getPipelineDesc();\n\n   if (mCurrentDraw->pipeline && mCurrentDraw->pipeline->desc == currentDesc) {\n      // Already active, nothing to do.\n      return true;\n   }\n\n   auto& foundPipeline = mPipelines[currentDesc.hash()];\n   if (foundPipeline) {\n      mCurrentDraw->pipeline = foundPipeline;\n      return true;\n   }\n\n   foundPipeline = new PipelineObject();\n   foundPipeline->desc = currentDesc;\n\n   // ------------------------------------------------------------\n   // Pipeline Layout\n   // ------------------------------------------------------------\n\n   vk::PipelineLayout pipelineLayout;\n\n   HashedDesc<PipelineLayoutDesc> pipelineLayoutDesc = generatePipelineLayoutDesc(*currentDesc);\n\n   if (!ForceDescriptorSets && pipelineLayoutDesc->numDescriptors < 32) {\n      auto pipelineLayoutObj = getPipelineLayout(pipelineLayoutDesc, true);\n      foundPipeline->pipelineLayout = pipelineLayoutObj;\n      pipelineLayout = pipelineLayoutObj->pipelineLayout;\n   } else {\n      // Too many descriptors to take advantage of using push descriptors, we have to\n      // fall back to using dynamically generated descriptor sets.\n      foundPipeline->pipelineLayout = nullptr;\n      pipelineLayout = mPipelineLayout;\n   }\n\n\n   // ------------------------------------------------------------\n   // Shader Stages\n   // ------------------------------------------------------------\n\n   std::vector<vk::PipelineShaderStageCreateInfo> shaderStages;\n   if (currentDesc->vertexShader) {\n      vk::PipelineShaderStageCreateInfo shaderStageDesc;\n      shaderStageDesc.stage = vk::ShaderStageFlagBits::eVertex;\n      shaderStageDesc.module = currentDesc->vertexShader->module;\n      shaderStageDesc.pName = \"main\";\n      shaderStageDesc.pSpecializationInfo = nullptr;\n      shaderStages.push_back(shaderStageDesc);\n   }\n   if (currentDesc->geometryShader) {\n      vk::PipelineShaderStageCreateInfo shaderStageDesc;\n      shaderStageDesc.stage = vk::ShaderStageFlagBits::eGeometry;\n      shaderStageDesc.module = currentDesc->geometryShader->module;\n      shaderStageDesc.pName = \"main\";\n      shaderStageDesc.pSpecializationInfo = nullptr;\n      shaderStages.push_back(shaderStageDesc);\n   }\n   if (currentDesc->pixelShader) {\n      vk::PipelineShaderStageCreateInfo shaderStageDesc;\n      shaderStageDesc.stage = vk::ShaderStageFlagBits::eFragment;\n      shaderStageDesc.module = currentDesc->pixelShader->module;\n      shaderStageDesc.pName = \"main\";\n      shaderStageDesc.pSpecializationInfo = nullptr;\n      shaderStages.push_back(shaderStageDesc);\n   }\n   if (currentDesc->rectStubShader) {\n      decaf_check(!currentDesc->geometryShader);\n\n      vk::PipelineShaderStageCreateInfo rectStageDesc;\n      rectStageDesc.stage = vk::ShaderStageFlagBits::eGeometry;\n      rectStageDesc.module = currentDesc->rectStubShader->module;\n      rectStageDesc.pName = \"main\";\n      rectStageDesc.pSpecializationInfo = nullptr;\n      shaderStages.push_back(rectStageDesc);\n   }\n\n\n   // ------------------------------------------------------------\n   // Attribute buffers and shader attributes\n   // ------------------------------------------------------------\n\n   std::vector<vk::VertexInputBindingDescription> bindingDescs;\n   std::vector<vk::VertexInputBindingDivisorDescriptionEXT> divisorDescs;\n\n   const auto& inputBuffers = currentDesc->vertexShader->shader.meta.attribBuffers;\n   for (auto i = 0u; i < latte::MaxAttribBuffers; ++i) {\n      const auto &inputBuffer = inputBuffers[i];\n\n      if (!inputBuffer.isUsed) {\n         continue;\n      }\n\n      vk::VertexInputBindingDescription bindingDesc;\n      bindingDesc.binding = i;\n      bindingDesc.stride = currentDesc->attribBufferStride[i];\n      if (inputBuffer.indexMode == spirv::AttribBuffer::IndexMode::PerVertex) {\n         decaf_check(inputBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::CONST_1);\n         bindingDesc.inputRate = vk::VertexInputRate::eVertex;\n      } else if (inputBuffer.indexMode == spirv::AttribBuffer::IndexMode::PerInstance) {\n         uint32_t divisorValue;\n         if (inputBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::CONST_1) {\n            divisorValue = 1;\n         } else if (inputBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::REGISTER_0) {\n            divisorValue = currentDesc->attribBufferDivisor[0];\n         } else if (inputBuffer.divisorMode == spirv::AttribBuffer::DivisorMode::REGISTER_1) {\n            divisorValue = currentDesc->attribBufferDivisor[1];\n         } else {\n            decaf_abort(\"Unexpected divisor mode in vertex shader attributes\");\n         }\n\n         bindingDesc.inputRate = vk::VertexInputRate::eInstance;\n\n         if (divisorValue != 1) {\n            vk::VertexInputBindingDivisorDescriptionEXT divisorDesc;\n            divisorDesc.binding = bindingDesc.binding;\n            divisorDesc.divisor = divisorValue;\n            divisorDescs.push_back(divisorDesc);\n         }\n      } else {\n         decaf_abort(\"Unexpected indexing mode for buffer\");\n      }\n      bindingDescs.push_back(bindingDesc);\n   }\n\n   std::vector<vk::VertexInputAttributeDescription> attribDescs;\n\n   const auto& inputAttribs = currentDesc->vertexShader->shader.meta.attribElems;\n   for (auto i = 0u; i < inputAttribs.size(); ++i) {\n      const auto &inputAttrib = inputAttribs[i];\n\n      vk::VertexInputAttributeDescription attribDesc;\n      attribDesc.location = i;\n      attribDesc.binding = inputAttrib.bufferIndex;\n\n      if (inputAttrib.elemWidth == 8) {\n         if (inputAttrib.elemCount == 1) {\n            attribDesc.format = vk::Format::eR8Uint;\n         } else if (inputAttrib.elemCount == 2) {\n            attribDesc.format = vk::Format::eR8G8Uint;\n         } else if (inputAttrib.elemCount == 3) {\n            attribDesc.format = vk::Format::eR8G8B8Uint;\n         } else if (inputAttrib.elemCount == 4) {\n            attribDesc.format = vk::Format::eR8G8B8A8Uint;\n         } else {\n            decaf_abort(\"Unexpected vertex attribute element count\");\n         }\n      } else if (inputAttrib.elemWidth == 16) {\n         if (inputAttrib.elemCount == 1) {\n            attribDesc.format = vk::Format::eR16Uint;\n         } else if (inputAttrib.elemCount == 2) {\n            attribDesc.format = vk::Format::eR16G16Uint;\n         } else if (inputAttrib.elemCount == 3) {\n            attribDesc.format = vk::Format::eR16G16B16Uint;\n         } else if (inputAttrib.elemCount == 4) {\n            attribDesc.format = vk::Format::eR16G16B16A16Uint;\n         } else {\n            decaf_abort(\"Unexpected vertex attribute element count\");\n         }\n      } else if (inputAttrib.elemWidth == 32) {\n         if (inputAttrib.elemCount == 1) {\n            attribDesc.format = vk::Format::eR32Uint;\n         } else if (inputAttrib.elemCount == 2) {\n            attribDesc.format = vk::Format::eR32G32Uint;\n         } else if (inputAttrib.elemCount == 3) {\n            attribDesc.format = vk::Format::eR32G32B32Uint;\n         } else if (inputAttrib.elemCount == 4) {\n            attribDesc.format = vk::Format::eR32G32B32A32Uint;\n         } else {\n            decaf_abort(\"Unexpected vertex attribute element count\");\n         }\n      } else {\n         decaf_abort(\"Unexpected vertex attribute element width\");\n      }\n\n      attribDesc.offset = inputAttrib.offset;\n      attribDescs.push_back(attribDesc);\n   }\n\n   vk::PipelineVertexInputStateCreateInfo vertexInputInfo;\n   vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(bindingDescs.size());\n   vertexInputInfo.pVertexBindingDescriptions = bindingDescs.data();\n   vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attribDescs.size());\n   vertexInputInfo.pVertexAttributeDescriptions = attribDescs.data();\n\n   if (divisorDescs.size() > 0) {\n      vk::PipelineVertexInputDivisorStateCreateInfoEXT divisorBindingDesc;\n      divisorBindingDesc.vertexBindingDivisorCount = static_cast<uint32_t>(divisorDescs.size());\n      divisorBindingDesc.pVertexBindingDivisors = divisorDescs.data();\n\n      vertexInputInfo.pNext = &divisorBindingDesc;\n   }\n\n   // ------------------------------------------------------------\n   // Input assembly\n   // ------------------------------------------------------------\n\n   vk::PipelineInputAssemblyStateCreateInfo inputAssembly;\n   switch (currentDesc->primitiveType) {\n   case latte::VGT_DI_PRIMITIVE_TYPE::POINTLIST:\n      inputAssembly.topology = vk::PrimitiveTopology::ePointList;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINELIST:\n      inputAssembly.topology = vk::PrimitiveTopology::eLineList;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINELOOP:\n      // We handle translation of this during draw\n      inputAssembly.topology = vk::PrimitiveTopology::eLineStrip;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINESTRIP:\n      inputAssembly.topology = vk::PrimitiveTopology::eLineStrip;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINELIST_ADJ:\n      inputAssembly.topology = vk::PrimitiveTopology::eLineListWithAdjacency;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::LINESTRIP_ADJ:\n      inputAssembly.topology = vk::PrimitiveTopology::eLineStripWithAdjacency;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRILIST:\n      inputAssembly.topology = vk::PrimitiveTopology::eTriangleList;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRIFAN:\n      inputAssembly.topology = vk::PrimitiveTopology::eTriangleFan;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRISTRIP:\n      inputAssembly.topology = vk::PrimitiveTopology::eTriangleStrip;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRILIST_ADJ:\n      inputAssembly.topology = vk::PrimitiveTopology::eTriangleListWithAdjacency;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::TRISTRIP_ADJ:\n      inputAssembly.topology = vk::PrimitiveTopology::eTriangleStripWithAdjacency;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::RECTLIST:\n      // We handle translation of this during draw\n      inputAssembly.topology = vk::PrimitiveTopology::eTriangleList;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::QUADLIST:\n      // We handle translation of this during draw\n      inputAssembly.topology = vk::PrimitiveTopology::eTriangleList;\n      break;\n   case latte::VGT_DI_PRIMITIVE_TYPE::QUADSTRIP:\n      // We handle translation of this during draw\n      inputAssembly.topology = vk::PrimitiveTopology::eTriangleStrip;\n      break;\n      //case latte::VGT_DI_PRIMITIVE_TYPE::NONE:\n      //case latte::VGT_DI_PRIMITIVE_TYPE::TRI_WITH_WFLAGS:\n      //case latte::VGT_DI_PRIMITIVE_TYPE::POLYGON:\n      //case latte::VGT_DI_PRIMITIVE_TYPE::COPY_RECT_LIST_2D_V0:\n      //case latte::VGT_DI_PRIMITIVE_TYPE::COPY_RECT_LIST_2D_V1:\n      //case latte::VGT_DI_PRIMITIVE_TYPE::COPY_RECT_LIST_2D_V2:\n      //case latte::VGT_DI_PRIMITIVE_TYPE::COPY_RECT_LIST_2D_V3:\n      //case latte::VGT_DI_PRIMITIVE_TYPE::FILL_RECT_LIST_2D:\n      //case latte::VGT_DI_PRIMITIVE_TYPE::LINE_STRIP_2D:\n      //case latte::VGT_DI_PRIMITIVE_TYPE::TRI_STRIP_2D:\n   default:\n      decaf_abort(\"Unexpected VGT primitive type\");\n   }\n\n   // Technically there is another valid configuration using 32-bit indices and a reset of 0xFFFF,\n   // but checking for this requires more state input to the pipeline...\n   inputAssembly.primitiveRestartEnable = currentDesc->primitiveResetEnabled;\n   decaf_check(!currentDesc->primitiveResetEnabled ||\n               (currentDesc->primitiveResetIndex == 0xFFFF ||\n                currentDesc->primitiveResetIndex == 0xFFFFFFFF));\n\n   // ------------------------------------------------------------\n   // Viewports and Scissors\n   // ------------------------------------------------------------\n\n   vk::PipelineViewportStateCreateInfo viewportState;\n   viewportState.viewportCount = 1;\n   viewportState.pViewports = nullptr;\n   viewportState.scissorCount = 1;\n   viewportState.pScissors = nullptr;\n\n\n   // ------------------------------------------------------------\n   // Rasterizer controls\n   // ------------------------------------------------------------\n   // TODO: Implement support for doing multi-sampled rendering.\n\n   vk::PipelineRasterizationStateCreateInfo rasterizer;\n   rasterizer.depthClampEnable = currentDesc->zclipDisabled;\n   rasterizer.rasterizerDiscardEnable = currentDesc->rasteriserDisable;\n\n   if (currentDesc->polyPType == latte::PA_PTYPE::LINES) {\n      rasterizer.polygonMode = vk::PolygonMode::eLine;\n   } else if (currentDesc->polyPType == latte::PA_PTYPE::POINTS) {\n      rasterizer.polygonMode = vk::PolygonMode::ePoint;\n   } else if (currentDesc->polyPType == latte::PA_PTYPE::TRIANGLES) {\n      rasterizer.polygonMode = vk::PolygonMode::eFill;\n   } else {\n      decaf_abort(\"Unexpected rasterization ptype\");\n   }\n\n   if (currentDesc->cullFront && currentDesc->cullBack) {\n      rasterizer.cullMode = vk::CullModeFlagBits::eFrontAndBack;\n   } else if (currentDesc->cullFront) {\n      rasterizer.cullMode = vk::CullModeFlagBits::eFront;\n   } else if (currentDesc->cullBack) {\n      rasterizer.cullMode = vk::CullModeFlagBits::eBack;\n   } else {\n      rasterizer.cullMode = vk::CullModeFlagBits::eNone;\n   }\n\n   if (currentDesc->paFace == latte::PA_FACE::CW) {\n      rasterizer.frontFace = vk::FrontFace::eClockwise;\n   } else if (currentDesc->paFace == latte::PA_FACE::CCW) {\n      rasterizer.frontFace = vk::FrontFace::eCounterClockwise;\n   } else {\n      decaf_abort(\"Unexpected pipeline cull mode\");\n   }\n\n   rasterizer.depthBiasEnable = currentDesc->polyBiasEnabled;\n   if (rasterizer.depthBiasEnable) {\n      rasterizer.depthBiasConstantFactor = currentDesc->polyBiasOffset;\n      rasterizer.depthBiasClamp = currentDesc->polyBiasClamp;\n      rasterizer.depthBiasSlopeFactor = currentDesc->polyBiasScale;\n   }\n\n   rasterizer.lineWidth = static_cast<float>(currentDesc->lineWidth) / 8.0f;\n\n   vk::PipelineMultisampleStateCreateInfo multisampling;\n   multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1;\n   multisampling.sampleShadingEnable = false;\n   multisampling.minSampleShading = 1.0f;\n   multisampling.pSampleMask = nullptr;\n   multisampling.alphaToCoverageEnable = false;\n   multisampling.alphaToOneEnable = false;\n\n\n   // ------------------------------------------------------------\n   // Color Blending\n   // ------------------------------------------------------------\n\n   std::array<vk::PipelineColorBlendAttachmentState, latte::MaxRenderTargets> colorBlendAttachments;\n   std::array<bool, latte::MaxRenderTargets> targetIsPremultiplied = { false };\n   auto needsPremultipliedTargets = false;\n\n   for (auto i = 0u; i < latte::MaxRenderTargets; ++i) {\n      const auto& blendData = currentDesc->cbBlendControls[i];\n\n      vk::PipelineColorBlendAttachmentState colorBlendAttachment;\n      colorBlendAttachment.blendEnable = blendData.blendingEnabled;\n      if (colorBlendAttachment.blendEnable) {\n         colorBlendAttachment.srcColorBlendFactor = getVkBlendFactor(blendData.colorSrcBlend);\n         colorBlendAttachment.dstColorBlendFactor = getVkBlendFactor(blendData.colorDstBlend);\n         colorBlendAttachment.colorBlendOp = getVkBlendOp(blendData.colorCombFcn);\n         colorBlendAttachment.srcAlphaBlendFactor = getVkBlendFactor(blendData.alphaSrcBlend);\n         colorBlendAttachment.dstAlphaBlendFactor = getVkBlendFactor(blendData.alphaDstBlend);\n         colorBlendAttachment.alphaBlendOp = getVkBlendOp(blendData.alphaCombFcn);\n\n         if (blendData.opacityWeight) {\n            needsPremultipliedTargets = true;\n            targetIsPremultiplied[i] = true;\n         }\n      }\n\n      if (blendData.targetMask & 1) {\n         colorBlendAttachment.colorWriteMask |= vk::ColorComponentFlagBits::eR;\n      }\n      if (blendData.targetMask & 2) {\n         colorBlendAttachment.colorWriteMask |= vk::ColorComponentFlagBits::eG;\n      }\n      if (blendData.targetMask & 4) {\n         colorBlendAttachment.colorWriteMask |= vk::ColorComponentFlagBits::eB;\n      }\n      if (blendData.targetMask & 8) {\n         colorBlendAttachment.colorWriteMask |= vk::ColorComponentFlagBits::eA;\n      }\n\n      colorBlendAttachments[i] = colorBlendAttachment;\n   }\n\n   vk::PipelineColorBlendStateCreateInfo colorBlendState;\n\n   if (currentDesc->rop3 == 0xCC) {\n      // COPY\n      colorBlendState.logicOpEnable = false;\n      colorBlendState.logicOp = vk::LogicOp::eCopy;\n   } else {\n      colorBlendState.logicOpEnable = true;\n      switch (currentDesc->rop3) {\n      case 0x00: // BLACKNESS\n         colorBlendState.logicOp = vk::LogicOp::eClear;\n         break;\n      case 0x11: // NOTSRCERASE\n         colorBlendState.logicOp = vk::LogicOp::eNor;\n         break;\n      case 0x22:\n         colorBlendState.logicOp = vk::LogicOp::eAndInverted;\n         break;\n      case 0x33: // NOTSRCCOPY\n         colorBlendState.logicOp = vk::LogicOp::eCopyInverted;\n         break;\n      case 0x44: // SRCERASE\n         colorBlendState.logicOp = vk::LogicOp::eAndReverse;\n         break;\n      case 0x55: // DSTINVERT\n         colorBlendState.logicOp = vk::LogicOp::eInvert;\n         break;\n      case 0x66: // SRCINVERT\n         colorBlendState.logicOp = vk::LogicOp::eXor;\n         break;\n      case 0x77:\n         colorBlendState.logicOp = vk::LogicOp::eNand;\n         break;\n      case 0x88: // SRCAND\n         colorBlendState.logicOp = vk::LogicOp::eAnd;\n         break;\n      case 0x99:\n         colorBlendState.logicOp = vk::LogicOp::eEquivalent;\n         break;\n      case 0xAA:\n         colorBlendState.logicOp = vk::LogicOp::eNoOp;\n         break;\n      case 0xBB: // MERGEPAINT\n         colorBlendState.logicOp = vk::LogicOp::eOrInverted;\n         break;\n      case 0xDD:\n         colorBlendState.logicOp = vk::LogicOp::eOrReverse;\n         break;\n      case 0xEE: // SRCPAINT\n         colorBlendState.logicOp = vk::LogicOp::eOr;\n         break;\n      case 0xFF: // WHITENESS\n         colorBlendState.logicOp = vk::LogicOp::eSet;\n         break;\n      case 0x5A: // PATINVERT\n      case 0xF0: // PATCOPY\n      default:\n         decaf_abort(\"Unexpected ROP3 operation\");\n      }\n   }\n\n   colorBlendState.attachmentCount = static_cast<uint32_t>(colorBlendAttachments.size());\n   colorBlendState.pAttachments = colorBlendAttachments.data();\n   colorBlendState.blendConstants[0] = currentDesc->cbBlendConstants[0];\n   colorBlendState.blendConstants[1] = currentDesc->cbBlendConstants[1];\n   colorBlendState.blendConstants[2] = currentDesc->cbBlendConstants[2];\n   colorBlendState.blendConstants[3] = currentDesc->cbBlendConstants[3];\n\n   vk::PipelineColorBlendAdvancedStateCreateInfoEXT advancedColorBlendState;\n   if (needsPremultipliedTargets) {\n      advancedColorBlendState.dstPremultiplied = true;\n      advancedColorBlendState.srcPremultiplied = false;\n      advancedColorBlendState.blendOverlap = vk::BlendOverlapEXT::eUncorrelated;\n\n      colorBlendState.pNext = &advancedColorBlendState;\n   }\n\n   // ------------------------------------------------------------\n   // Depth/Stencil State\n   // ------------------------------------------------------------\n   vk::PipelineDepthStencilStateCreateInfo depthStencil;\n   depthStencil.depthBoundsTestEnable = false;\n\n   depthStencil.depthTestEnable = currentDesc->zEnable;\n   depthStencil.depthWriteEnable = currentDesc->zWriteEnable;\n   depthStencil.depthCompareOp = getVkCompareOp(currentDesc->zFunc);\n\n   depthStencil.stencilTestEnable = currentDesc->stencilEnable;\n   depthStencil.front.compareOp = getVkCompareOp(currentDesc->stencilFront.compareFunc);\n   depthStencil.front.failOp = getVkStencilOp(currentDesc->stencilFront.failOp);\n   depthStencil.front.passOp = getVkStencilOp(currentDesc->stencilFront.zPassOp);\n   depthStencil.front.depthFailOp = getVkStencilOp(currentDesc->stencilFront.zFailOp);\n   depthStencil.front.reference = static_cast<uint32_t>(currentDesc->stencilFront.ref);\n   depthStencil.front.compareMask = static_cast<uint32_t>(currentDesc->stencilFront.mask);\n   depthStencil.front.writeMask = static_cast<uint32_t>(currentDesc->stencilFront.writeMask);\n   depthStencil.back.compareOp = getVkCompareOp(currentDesc->stencilBack.compareFunc);\n   depthStencil.back.failOp = getVkStencilOp(currentDesc->stencilBack.failOp);\n   depthStencil.back.passOp = getVkStencilOp(currentDesc->stencilBack.zPassOp);\n   depthStencil.back.depthFailOp = getVkStencilOp(currentDesc->stencilBack.zFailOp);\n   depthStencil.back.reference = static_cast<uint32_t>(currentDesc->stencilBack.ref);\n   depthStencil.back.compareMask = static_cast<uint32_t>(currentDesc->stencilBack.mask);\n   depthStencil.back.writeMask = static_cast<uint32_t>(currentDesc->stencilBack.writeMask);\n\n\n   // ------------------------------------------------------------\n   // Dynamic states\n   // ------------------------------------------------------------\n\n   auto dynamicStates = {\n      vk::DynamicState::eViewport,\n      vk::DynamicState::eScissor,\n   };\n\n   vk::PipelineDynamicStateCreateInfo dynamicDesc;\n   dynamicDesc.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());\n   dynamicDesc.pDynamicStates = dynamicStates.begin();\n\n\n   // ------------------------------------------------------------\n   // Shader stuff\n   // ------------------------------------------------------------\n\n   // TODO: I don't understand why the vulkan drivers use the color from the\n   // shader when rendering this stuff... We have to do this for now.\n   auto shaderLopMode = 0;\n   if (currentDesc->rop3 == 0xFF) {\n      shaderLopMode = 1;\n   } else if (currentDesc->rop3 == 0x00) {\n      shaderLopMode = 2;\n   }\n\n\n   // ------------------------------------------------------------\n   // Pipeline\n   // ------------------------------------------------------------\n\n   vk::GraphicsPipelineCreateInfo pipelineInfo;\n   pipelineInfo.pStages = shaderStages.data();\n   pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size());\n   pipelineInfo.pVertexInputState = &vertexInputInfo;\n   pipelineInfo.pInputAssemblyState = &inputAssembly;\n   pipelineInfo.pTessellationState = nullptr;\n   pipelineInfo.pViewportState = &viewportState;\n   pipelineInfo.pRasterizationState = &rasterizer;\n   pipelineInfo.pMultisampleState = &multisampling;\n   pipelineInfo.pDepthStencilState = &depthStencil;\n   pipelineInfo.pColorBlendState = &colorBlendState;\n   pipelineInfo.pDynamicState = &dynamicDesc;\n   pipelineInfo.layout = pipelineLayout;\n   pipelineInfo.renderPass = mCurrentDraw->renderPass->renderPass;\n   pipelineInfo.subpass = 0;\n   pipelineInfo.basePipelineHandle = vk::Pipeline();\n   pipelineInfo.basePipelineIndex = -1;\n   auto pipeline = mDevice.createGraphicsPipeline(mPipelineCache, pipelineInfo);\n\n   foundPipeline->pipeline = pipeline.value;\n   foundPipeline->needsPremultipliedTargets = needsPremultipliedTargets;\n   foundPipeline->targetIsPremultiplied = targetIsPremultiplied;\n   foundPipeline->shaderLopMode = shaderLopMode;\n   foundPipeline->shaderAlphaFunc = currentDesc->alphaFunc;\n   foundPipeline->shaderAlphaRef = currentDesc->alphaRef;\n   mCurrentDraw->pipeline = foundPipeline;\n   return true;\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_pm4.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"gpu_clock.h\"\n#include \"gpu_event.h\"\n#include \"gpu_ih.h\"\n#include \"gpu_memory.h\"\n\n#include \"latte/latte_endian.h\"\n#include \"latte/latte_enum_as_string.h\"\n\n#include <common/decaf_assert.h>\n#include <common/log.h>\n\nnamespace vulkan\n{\n\nvoid\nDriver::decafSetBuffer(const latte::pm4::DecafSetBuffer &data)\n{\n   SwapChainObject **swapChain;\n   if (data.scanTarget == latte::pm4::ScanTarget::TV) {\n      swapChain = &mTvSwapChain;\n   } else if (data.scanTarget == latte::pm4::ScanTarget::DRC) {\n      swapChain = &mDrcSwapChain;\n   } else {\n      decaf_abort(\"Unexpected decafSetBuffer target\");\n   }\n\n   // Release the existing swap chain if we had it...\n   if (*swapChain) {\n      releaseSwapChain(*swapChain);\n      *swapChain = nullptr;\n   }\n\n   // Allocate a new swap chain\n   auto swapChainDesc = SwapChainDesc {\n      data.buffer,\n      data.width,\n      data.height\n   };\n   auto newSwapChain = allocateSwapChain(swapChainDesc);\n\n   // Give the swapchain a name so its easy to see.\n   if (data.scanTarget == latte::pm4::ScanTarget::TV) {\n      setVkObjectName(newSwapChain->image, fmt::format(\"swapchain_tv\").c_str());\n   } else if (data.scanTarget == latte::pm4::ScanTarget::DRC) {\n      setVkObjectName(newSwapChain->image, fmt::format(\"swapchain_drc\").c_str());\n   } else {\n         decaf_abort(\"Unexpected decafSetBuffer target\");\n   }\n\n   // Assign the new swap chain\n   *swapChain = newSwapChain;\n\n   // Only make this swapchain presentable after this frame has completed\n   // (we need to run the frame at least once for setup to complete before use).\n   addRetireTask([=](){\n      newSwapChain->presentable = true;\n   });\n}\n\nvoid\nDriver::decafCopyColorToScan(const latte::pm4::DecafCopyColorToScan &data)\n{\n   flushPendingDraws();\n\n   ColorBufferDesc colorBuffer;\n   colorBuffer.base256b = data.cb_color_base.BASE_256B();\n   colorBuffer.pitchTileMax = data.cb_color_size.PITCH_TILE_MAX();\n   colorBuffer.sliceTileMax = data.cb_color_size.SLICE_TILE_MAX();\n   colorBuffer.format = data.cb_color_info.FORMAT();\n   colorBuffer.numberType = data.cb_color_info.NUMBER_TYPE();\n   colorBuffer.arrayMode = data.cb_color_info.ARRAY_MODE();\n   colorBuffer.sliceStart = 0;\n   colorBuffer.sliceEnd = 1;\n   auto surfaceView = getColorBuffer(colorBuffer);\n   auto surface = surfaceView->surface;\n\n   SwapChainObject *target = nullptr;\n   if (data.scanTarget == latte::pm4::ScanTarget::TV) {\n      target = mTvSwapChain;\n   } else if (data.scanTarget == latte::pm4::ScanTarget::DRC) {\n      target = mDrcSwapChain;\n   } else {\n      decaf_abort(\"decafCopyColorToScan called for unknown scanTarget\");\n   }\n\n   transitionSurface(surface, ResourceUsage::TransferSrc, vk::ImageLayout::eTransferSrcOptimal, { 0, 1 });\n\n   // TODO: We actually need to call AVMSetTVScale inside of the SetBuffer functions\n   // and then pass that data all the way down to here so we can scale correctly.\n   auto copyWidth = target->desc.width;\n   auto copyHeight = target->desc.height;\n   if (surface->desc.width < target->desc.width) {\n      copyWidth = surface->desc.width;\n   }\n   if (surface->desc.height < target->desc.height) {\n      copyHeight = surface->desc.height;\n   }\n\n   vk::ImageBlit blitRegion(\n      vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1),\n      { vk::Offset3D(0, 0, 0), vk::Offset3D(copyWidth, copyHeight, 1) },\n      vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1),\n      { vk::Offset3D(0, 0, 0), vk::Offset3D(target->desc.width, target->desc.height, 1) });\n\n   mActiveCommandBuffer.blitImage(\n      surface->image,\n      vk::ImageLayout::eTransferSrcOptimal,\n      target->image,\n      vk::ImageLayout::eTransferDstOptimal,\n      { blitRegion },\n      vk::Filter::eNearest);\n}\n\nvoid\nDriver::decafSwapBuffers(const latte::pm4::DecafSwapBuffers &data)\n{\n   static const auto weight = 0.9;\n\n   addRetireTask([=](){\n      // Send out the flip event\n      gpu::onFlip();\n\n      // Update our frametime and last swap times\n      auto now = std::chrono::system_clock::now();\n\n      if (mLastSwap.time_since_epoch().count()) {\n         mAverageFrameTime = weight * mAverageFrameTime + (1.0 - weight) * (now - mLastSwap);\n      }\n\n      mLastSwap = now;\n\n      // Update our debugging info every flip\n      updateDebuggerInfo();\n\n      // Render the display!\n      renderDisplay();\n   });\n}\n\nvoid\nDriver::decafClearColor(const latte::pm4::DecafClearColor &data)\n{\n   flushPendingDraws();\n\n   // Find our colorbuffer to clear\n   ColorBufferDesc colorBuffer;\n   colorBuffer.base256b = data.cb_color_base.BASE_256B();\n   colorBuffer.pitchTileMax = data.cb_color_size.PITCH_TILE_MAX();\n   colorBuffer.sliceTileMax = data.cb_color_size.SLICE_TILE_MAX();\n   colorBuffer.format = data.cb_color_info.FORMAT();\n   colorBuffer.numberType = data.cb_color_info.NUMBER_TYPE();\n   colorBuffer.arrayMode = data.cb_color_info.ARRAY_MODE();\n   colorBuffer.sliceStart = data.cb_color_view.SLICE_START();\n   colorBuffer.sliceEnd = data.cb_color_view.SLICE_MAX() + 1;\n   auto surfaceView = getColorBuffer(colorBuffer);\n\n   transitionSurfaceView(surfaceView, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal);\n\n   std::array<float, 4> clearColor = { data.red, data.green, data.blue, data.alpha };\n   mActiveCommandBuffer.clearColorImage(surfaceView->surface->image, vk::ImageLayout::eTransferDstOptimal, clearColor, { surfaceView->subresRange });\n}\n\nvoid\nDriver::decafClearDepthStencil(const latte::pm4::DecafClearDepthStencil &data)\n{\n   flushPendingDraws();\n\n   // Find our depthbuffer to clear\n   DepthStencilBufferDesc depthBuffer;\n   depthBuffer.base256b = data.db_depth_base.BASE_256B();\n   depthBuffer.pitchTileMax = data.db_depth_size.PITCH_TILE_MAX();\n   depthBuffer.sliceTileMax = data.db_depth_size.SLICE_TILE_MAX();\n   depthBuffer.format = data.db_depth_info.FORMAT();\n   depthBuffer.arrayMode = data.db_depth_info.ARRAY_MODE();\n   depthBuffer.sliceStart = data.db_depth_view.SLICE_START();\n   depthBuffer.sliceEnd = data.db_depth_view.SLICE_MAX() + 1;\n   auto surfaceView = getDepthStencilBuffer(depthBuffer);\n\n   transitionSurfaceView(surfaceView, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal);\n\n   auto db_depth_clear = getRegister<latte::DB_DEPTH_CLEAR>(latte::Register::DB_DEPTH_CLEAR);\n   auto db_stencil_clear = getRegister<latte::DB_STENCIL_CLEAR>(latte::Register::DB_STENCIL_CLEAR);\n\n   vk::ClearDepthStencilValue clearDepthStencil;\n   clearDepthStencil.depth = db_depth_clear.DEPTH_CLEAR();\n   clearDepthStencil.stencil = db_stencil_clear.CLEAR();\n   mActiveCommandBuffer.clearDepthStencilImage(\n      surfaceView->surface->image, vk::ImageLayout::eTransferDstOptimal, clearDepthStencil, { surfaceView->subresRange });\n}\n\nvoid\nDriver::decafOSScreenFlip(const latte::pm4::DecafOSScreenFlip &data)\n{\n   decaf_abort(\"Unsupported pm4 decafOSScreenFlip\");\n}\n\nvoid\nDriver::decafCopySurface(const latte::pm4::DecafCopySurface &data)\n{\n   flushPendingDraws();\n\n   if (data.dstImage.getAddress() == 0 || data.srcImage.getAddress() == 0) {\n      return;\n   }\n   //decaf_check(data.dstPitch <= data.srcPitch);\n   //decaf_check(data.dstWidth == data.srcWidth);\n   //decaf_check(data.dstHeight == data.srcHeight);\n   //decaf_check(data.dstDepth == data.srcDepth);\n   //decaf_check(data.dstDim == data.srcDim);\n   // Commented the above because slice-wise accross different things is fine...\n\n   if (data.dstLevel > 0) {\n      // We do not currently support mip mapping levels.\n      return;\n   }\n\n   // Fetch the source surface\n   SurfaceDesc sourceDataDesc;\n   sourceDataDesc.baseAddress = static_cast<uint32_t>(data.srcImage);\n   sourceDataDesc.pitch = data.srcPitch;\n   sourceDataDesc.width = data.srcWidth;\n   sourceDataDesc.height = data.srcHeight;\n   sourceDataDesc.depth = data.srcDepth;\n   sourceDataDesc.samples = 1;\n   sourceDataDesc.dim = data.srcDim;\n   sourceDataDesc.format = latte::getSurfaceFormat(\n      data.srcFormat,\n      data.srcNumFormat,\n      data.srcFormatComp,\n      data.srcForceDegamma);\n   sourceDataDesc.tileType = data.srcTileType;\n   sourceDataDesc.tileMode = data.srcTileMode;\n   auto sourceImage = getSurface(sourceDataDesc);\n\n   // Fetch the destination surface\n   SurfaceDesc destDataDesc;\n   destDataDesc.baseAddress = static_cast<uint32_t>(data.dstImage);\n   destDataDesc.pitch = data.dstPitch;\n   destDataDesc.width = data.dstWidth;\n   destDataDesc.height = data.dstHeight;\n   destDataDesc.depth = data.dstDepth;\n   destDataDesc.samples = 1;\n   destDataDesc.dim = data.dstDim;\n   destDataDesc.format = latte::getSurfaceFormat(\n      data.dstFormat,\n      data.dstNumFormat,\n      data.dstFormatComp,\n      data.dstForceDegamma);\n   destDataDesc.tileType = data.dstTileType;\n   destDataDesc.tileMode = data.dstTileMode;\n   auto destImage = getSurface(destDataDesc);\n\n   // Transition the surfaces to the appropriate layouts\n   transitionSurface(sourceImage, ResourceUsage::TransferSrc, vk::ImageLayout::eTransferSrcOptimal, { data.srcSlice, 1 });\n   transitionSurface(destImage, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal, { data.dstSlice, 1 });\n\n   // Calculate the bounds of the copy\n   auto copyWidth = data.srcWidth;\n   auto copyHeight = data.srcHeight;\n   auto copyDepth = data.srcDepth;\n   if (data.srcDim == latte::SQ_TEX_DIM::DIM_CUBEMAP) {\n      copyDepth *= 6;\n   }\n\n   // Perform the copy, note that CopySurface only supports 1:1 copies\n   vk::ImageBlit blitRegion(\n      vk::ImageSubresourceLayers(sourceImage->subresRange.aspectMask, 0, data.srcSlice, 1),\n      { vk::Offset3D(0, 0, 0), vk::Offset3D(copyWidth, copyHeight, copyDepth) },\n      vk::ImageSubresourceLayers(sourceImage->subresRange.aspectMask, 0, data.dstSlice, 1),\n      { vk::Offset3D(0, 0, 0), vk::Offset3D(copyWidth, copyHeight, copyDepth) });\n\n   mActiveCommandBuffer.blitImage(\n      sourceImage->image,\n      vk::ImageLayout::eTransferSrcOptimal,\n      destImage->image,\n      vk::ImageLayout::eTransferDstOptimal,\n      { blitRegion },\n      vk::Filter::eNearest);\n}\n\nvoid\nDriver::decafExpandColorBuffer(const latte::pm4::DecafExpandColorBuffer &data)\n{\n   flushPendingDraws();\n\n   // We do not actually support MSAA in our Vulkan backend, so we simply\n   // need to translate this to a series of surface copies.\n\n   for (auto i = 0u; i < data.numSlices; ++i) {\n      decafCopySurface({\n         data.dstImage,\n         data.dstMipmaps,\n         data.dstLevel,\n         data.dstSlice + i,\n         data.dstPitch,\n         data.dstWidth,\n         data.dstHeight,\n         data.dstDepth,\n         data.dstSamples,\n         data.dstDim,\n         data.dstFormat,\n         data.dstNumFormat,\n         data.dstFormatComp,\n         data.dstForceDegamma,\n         data.dstTileType,\n         data.dstTileMode,\n         data.srcImage,\n         data.srcMipmaps,\n         data.srcLevel,\n         data.srcSlice + i,\n         data.srcPitch,\n         data.srcWidth,\n         data.srcHeight,\n         data.srcDepth,\n         data.srcSamples,\n         data.srcDim,\n         data.srcFormat,\n         data.srcNumFormat,\n         data.srcFormatComp,\n         data.srcForceDegamma,\n         data.srcTileType,\n         data.srcTileMode\n      });\n   }\n}\n\nvoid\nDriver::drawIndexAuto(const latte::pm4::DrawIndexAuto &data)\n{\n   drawGenericIndexed(data.drawInitiator, data.count, nullptr);\n}\n\nvoid\nDriver::drawIndex2(const latte::pm4::DrawIndex2 &data)\n{\n   drawGenericIndexed(data.drawInitiator, data.count, phys_cast<void*>(data.addr).getRawPointer());\n}\n\nvoid\nDriver::drawIndexImmd(const latte::pm4::DrawIndexImmd &data)\n{\n   drawGenericIndexed(data.drawInitiator, data.count, data.indices.data());\n}\n\nvoid\nDriver::memWrite(const latte::pm4::MemWrite &data)\n{\n   auto addr = phys_addr { data.addrLo.ADDR_LO() << 2 };\n   auto ptr = gpu::internal::translateAddress(addr);\n   auto value = uint64_t { 0 };\n\n   // Read value\n   if (data.addrHi.CNTR_SEL() == latte::pm4::MW_WRITE_CLOCK) {\n      value = gpu::clock::now();\n   } else if (data.addrHi.DATA32()) {\n      value = static_cast<uint64_t>(data.dataLo);\n   } else {\n      value = static_cast<uint64_t>(data.dataLo) |\n              (static_cast<uint64_t>(data.dataHi) << 32);\n   }\n\n   // Swap value\n   value = latte::applyEndianSwap(value, data.addrLo.ENDIAN_SWAP());\n\n   // Write value\n   addRetireTask([=](){\n      if (data.addrHi.DATA32()) {\n         *reinterpret_cast<uint32_t *>(ptr) = static_cast<uint32_t>(value);\n      } else {\n         *reinterpret_cast<uint64_t *>(ptr) = value;\n      }\n   });\n}\n\nvoid\nDriver::eventWrite(const latte::pm4::EventWrite &data)\n{\n   auto addr = phys_addr { data.addrLo.ADDR_LO() << 2 };\n   auto dataPtr = phys_cast<uint64_t*>(addr).getRawPointer();\n\n   if (data.eventInitiator.EVENT_TYPE() == latte::VGT_EVENT_TYPE::ZPASS_DONE) {\n      // Check if this is the first query, or a second query (as part\n      // of a single occlusion query grouping).\n      if (mLastOccQueryAddr == 0) {\n         auto dstBuffer = getDataMemCache(addr, 8);\n         transitionMemCache(dstBuffer, ResourceUsage::TransferDst);\n\n         // Write `0` into the beginning zpass count asynchronously.\n         mActiveCommandBuffer.fillBuffer(dstBuffer->buffer, 0, 8, 0);\n\n         // Start our occlusion query\n         auto queryPool = allocateOccQueryPool();\n         mActiveCommandBuffer.beginQuery(queryPool, 0, vk::QueryControlFlagBits::ePrecise);\n\n         mLastOccQuery = queryPool;\n         mLastOccQueryAddr = dataPtr;\n      } else {\n         // Make sure this is the result value of the same query\n         decaf_check(dataPtr == mLastOccQueryAddr + 1);\n\n         auto dstBuffer = getDataMemCache(addr, 8);\n         transitionMemCache(dstBuffer, ResourceUsage::TransferDst);\n\n         mActiveCommandBuffer.endQuery(mLastOccQuery, 0);\n         mActiveCommandBuffer.copyQueryPoolResults(mLastOccQuery, 0, 1,\n                                                   dstBuffer->buffer, 0, 8,\n                                                   vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);\n\n         mLastOccQuery = vk::QueryPool();\n         mLastOccQueryAddr = nullptr;\n      }\n   } else if (data.eventInitiator.EVENT_TYPE() == latte::VGT_EVENT_TYPE::CACHE_FLUSH) {\n      // This should flush all GPU-written data back to the CPU immediately.\n      decaf_check_warn_once(!\"Use of VGT_EVENT_TYPE::CACHE_FLUSH\");\n   } else if (data.eventInitiator.EVENT_TYPE() == latte::VGT_EVENT_TYPE::CACHE_FLUSH_AND_INV_EVENT) {\n      // This should flush all GPU-written data back to the CPU immediately\n      // and then invalidate all GPU caches of textures so they are re-read.\n      decaf_check_warn_once(!\"Use of VGT_EVENT_TYPE::CACHE_FLUSH_AND_INV_EVENT\");\n   } else if (data.eventInitiator.EVENT_TYPE() == latte::VGT_EVENT_TYPE::VS_PARTIAL_FLUSH) {\n      // This should flush all data out of VS-specific pipelines, such as stream-out.\n      decaf_check_warn_once(!\"Use of VGT_EVENT_TYPE::VS_PARTIAL_FLUSH\");\n   } else {\n      gLog->warn(\"Unexpected eventWrite event type {}\", latte::to_string(data.eventInitiator.EVENT_TYPE()));\n   }\n}\n\nvoid\nDriver::eventWriteEOP(const latte::pm4::EventWriteEOP &data)\n{\n   // Write event data to memory if required\n   if (data.addrHi.DATA_SEL() != latte::pm4::EWP_DATA_DISCARD) {\n      auto addr = phys_addr { data.addrLo.ADDR_LO() << 2 };\n      auto ptr = gpu::internal::translateAddress(addr);\n      decaf_assert(data.addrHi.ADDR_HI() == 0, \"Invalid event write address (high word not zero)\");\n\n      // Read value\n      auto value = uint64_t { 0u };\n      switch (data.addrHi.DATA_SEL()) {\n      case latte::pm4::EWP_DATA_32:\n         value = data.dataLo;\n         break;\n      case latte::pm4::EWP_DATA_64:\n         value = static_cast<uint64_t>(data.dataLo) |\n                 (static_cast<uint64_t>(data.dataHi) << 32);\n         break;\n      case latte::pm4::EWP_DATA_CLOCK:\n         value = gpu::clock::now();\n         break;\n      }\n\n      // Swap value\n      value = latte::applyEndianSwap(value, data.addrLo.ENDIAN_SWAP());\n\n      addRetireTask([=](){\n         // Write value\n         switch (data.addrHi.DATA_SEL()) {\n         case latte::pm4::EWP_DATA_32:\n            *reinterpret_cast<uint32_t *>(ptr) = static_cast<uint32_t>(value);\n            break;\n         case latte::pm4::EWP_DATA_64:\n         case latte::pm4::EWP_DATA_CLOCK:\n            *reinterpret_cast<uint64_t *>(ptr) = value;\n            break;\n         }\n      });\n   }\n\n   // Generate interrupt if required\n   if (data.addrHi.INT_SEL() != latte::pm4::EWP_INT_NONE) {\n      addRetireTask([=](){\n         auto interrupt = gpu::ih::Entry { };\n         interrupt.word0 = latte::CP_INT_SRC_ID::CP_EOP_EVENT;\n         gpu::ih::write(interrupt);\n      });\n   }\n}\n\nvoid\nDriver::pfpSyncMe(const latte::pm4::PfpSyncMe &data)\n{\n   // Due to the specialized implementation of queries, there is\n   // no need to implement any special behaviours here.\n}\n\nvoid\nDriver::setPredication(const latte::pm4::SetPredication &data)\n{\n   // We do not currently implement GPU-side predication of drawing.  Instead\n   // we simply allow all draws to proceed as if they were successful.\n}\n\nvoid\nDriver::streamOutBaseUpdate(const latte::pm4::StreamOutBaseUpdate &data)\n{\n   // This is ignored as we don't need to do anything special when the base is\n   // updated.  Instead we detect that the buffer registers have changed and\n   // use that as the indication to switch.\n}\n\nvoid\nDriver::streamOutBufferUpdate(const latte::pm4::StreamOutBufferUpdate &data)\n{\n   auto bufferIdx = data.control.SELECT_BUFFER();\n\n   if (data.control.STORE_BUFFER_FILLED_SIZE()) {\n      auto stream = mStreamOutContext[bufferIdx];\n\n      decaf_check(data.dstLo);\n      decaf_check(stream);\n\n      readbackStreamContext(stream, data.dstLo);\n   }\n\n   StreamContextObject *newStreamOut = nullptr;\n   if (data.control.OFFSET_SOURCE() == STRMOUT_OFFSET_SOURCE::STRMOUT_OFFSET_FROM_MEM) {\n      auto srcPtr = phys_cast<uint32_t*>(data.srcLo);\n      decaf_check(srcPtr);\n      newStreamOut = allocateStreamContext(*srcPtr);\n   } else if (data.control.OFFSET_SOURCE() == STRMOUT_OFFSET_SOURCE::STRMOUT_OFFSET_FROM_PACKET) {\n      auto offset = static_cast<uint32_t>(data.srcLo);\n      newStreamOut = allocateStreamContext(offset);\n   } else if (data.control.OFFSET_SOURCE() == STRMOUT_OFFSET_SOURCE::STRMOUT_OFFSET_NONE) {\n      // Nothing to do here, as they didn't want to load the offset from anywhere...\n   } else {\n      decaf_abort(\"Unexpected offset source during stream out buffer update\");\n   }\n\n   if (newStreamOut) {\n      auto oldStreamOut = mStreamOutContext[bufferIdx];\n      mStreamOutContext[bufferIdx] = newStreamOut;\n\n      if (oldStreamOut) {\n         // We have to defer the destruction of this buffer until the end of\n         // the current context or we may destroy it while a pending read is\n         // in the contexts callback list.\n\n         addRetireTask([=](){\n            releaseStreamContext(oldStreamOut);\n         });\n      }\n   }\n}\n\nvoid\nDriver::surfaceSync(const latte::pm4::SurfaceSync &data)\n{\n   // TODO: Handle surface syncs when using non-coherent surface optimizations.\n}\n\nvoid\nDriver::waitMem(const latte::pm4::WaitMem &data)\n{\n   auto addr = phys_addr { data.addrLo.ADDR_LO() << 2 };\n   auto ptr = gpu::internal::translateAddress(addr);\n\n   while (true) {\n      auto value = *reinterpret_cast<volatile uint32_t *>(ptr);\n      value = static_cast<uint32_t>(\n         latte::applyEndianSwap(value, data.addrLo.ENDIAN_SWAP()));\n      value &= data.mask;\n\n      bool result;\n      switch (data.memSpaceFunction.FUNCTION()) {\n      case WRM_FUNCTION::FUNCTION_ALWAYS:\n         result = true;\n         break;\n      case WRM_FUNCTION::FUNCTION_LESS_THAN:\n         result = value < data.reference;\n         break;\n      case WRM_FUNCTION::FUNCTION_LESS_THAN_EQUAL:\n         result = value <= data.reference;\n         break;\n      case WRM_FUNCTION::FUNCTION_EQUAL:\n         result = value == data.reference;\n         break;\n      case WRM_FUNCTION::FUNCTION_NOT_EQUAL:\n         result = value != data.reference;\n         break;\n      case WRM_FUNCTION::FUNCTION_GREATER_THAN_EQUAL:\n         result = value >= data.reference;\n         break;\n      case WRM_FUNCTION::FUNCTION_GREATER_THAN:\n         result = value > data.reference;\n         break;\n      default:\n         result = true;\n      }\n\n      if (result) {\n         break;\n      }\n\n      std::this_thread::sleep_for(std::chrono::milliseconds(1));\n   }\n}\n\nvoid\nDriver::executeBuffer(const gpu::ringbuffer::Buffer &buffer)\n{\n   decaf_check(!mActiveSyncWaiter);\n\n   // Begin our command group (sync waiter)\n   beginCommandGroup();\n\n   // Begin preparing our command buffer\n   beginCommandBuffer();\n\n   // Execute guest PM4 command buffer\n   runCommandBuffer(buffer);\n\n   // End preparing our command buffer\n   endCommandBuffer();\n\n   // Submit the generated command buffer to the host GPU queue\n   vk::SubmitInfo submitInfo;\n   submitInfo.commandBufferCount = 1;\n   submitInfo.pCommandBuffers = &mActiveCommandBuffer;\n   mQueue.submit({ submitInfo }, mActiveSyncWaiter->fence);\n\n   // End our command group\n   endCommandGroup();\n\n   // Optimize the memory layout of our segments every 10 frames.\n   if (mActiveBatchIndex % 10 == 0) {\n      mMemTracker.optimize();\n   }\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_renderpass.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"vulkan_utils.h\"\n\nnamespace vulkan\n{\n\nRenderPassDesc\nDriver::getRenderPassDesc()\n{\n   auto desc = RenderPassDesc {};\n\n   // TODO: Maybe move the cb_shader_masking into the framebuffer creation to\n   // reduce the number of renderpasses that we need to create.  SM3DW is a good\n   // game to check the shader masking behaviours.\n\n   auto cb_target_mask = getRegister<latte::CB_TARGET_MASK>(latte::Register::CB_TARGET_MASK);\n   auto cb_color_control = getRegister<latte::CB_COLOR_CONTROL>(latte::Register::CB_COLOR_CONTROL);\n   auto cb_shader_mask = getRegister<latte::CB_SHADER_MASK>(latte::Register::CB_SHADER_MASK);\n\n   auto colorWritesEnabled = true;\n   if (cb_color_control.SPECIAL_OP() == latte::CB_SPECIAL_OP::DISABLE) {\n      colorWritesEnabled = false;\n   }\n\n   for (auto i = 0u; i < latte::MaxRenderTargets; ++i) {\n      auto targetMask = (cb_target_mask.value >> (i * 4)) & 0xF;\n      auto shaderMask = (cb_shader_mask.value >> (i * 4)) & 0xF;\n\n      auto cb_color_base = getRegister<latte::CB_COLORN_BASE>(latte::Register::CB_COLOR0_BASE + i * 4);\n      auto cb_color_info = getRegister<latte::CB_COLORN_INFO>(latte::Register::CB_COLOR0_INFO + i * 4);\n\n      auto isValidBuffer = true;\n      isValidBuffer &= !!cb_color_base.BASE_256B();\n      isValidBuffer &= (cb_color_info.FORMAT() != latte::CB_FORMAT::COLOR_INVALID);\n\n      if (!!targetMask && !!shaderMask && colorWritesEnabled) {\n         if (isValidBuffer) {\n            desc.colorTargets[i] = RenderPassDesc::ColorTarget {\n               true,\n               cb_color_info.FORMAT(),\n               cb_color_info.NUMBER_TYPE(),\n               1\n            };\n         } else {\n            // This export is valid, but the color write is ignored\n            desc.colorTargets[i] = RenderPassDesc::ColorTarget {\n               true,\n               latte::CB_FORMAT::COLOR_INVALID,\n               latte::CB_NUMBER_TYPE::UNORM,\n               0\n            };\n         }\n      } else {\n         desc.colorTargets[i] = RenderPassDesc::ColorTarget {\n            false,\n            latte::CB_FORMAT::COLOR_INVALID,\n            latte::CB_NUMBER_TYPE::UNORM,\n            0\n         };\n      }\n   }\n\n   {\n      auto db_depth_control = getRegister<latte::DB_DEPTH_CONTROL>(latte::Register::DB_DEPTH_CONTROL);\n      auto zEnable = db_depth_control.Z_ENABLE();\n      auto stencilEnable = db_depth_control.STENCIL_ENABLE();\n      auto depthEnabled = zEnable || stencilEnable;\n\n      auto db_depth_base = getRegister<latte::DB_DEPTH_BASE>(latte::Register::DB_DEPTH_BASE);\n      auto db_depth_info = getRegister<latte::DB_DEPTH_INFO>(latte::Register::DB_DEPTH_INFO);\n\n      auto isValidBuffer = true;\n      isValidBuffer &= !!db_depth_base.BASE_256B();\n      isValidBuffer &= (db_depth_info.FORMAT() != latte::DB_FORMAT::DEPTH_INVALID);\n\n      if (depthEnabled) {\n         if (isValidBuffer) {\n            desc.depthTarget = RenderPassDesc::DepthStencilTarget {\n               true,\n               db_depth_info.FORMAT()\n            };\n         } else {\n            desc.depthTarget = {\n               true,\n               latte::DB_FORMAT::DEPTH_INVALID\n            };\n         }\n      } else {\n         desc.depthTarget = {\n            false,\n            latte::DB_FORMAT::DEPTH_INVALID\n         };\n      }\n   }\n\n   return desc;\n}\n\nbool\nDriver::checkCurrentRenderPass()\n{\n   HashedDesc<RenderPassDesc> currentDesc = getRenderPassDesc();\n\n   if (!currentDesc->colorTargets[0].isEnabled &&\n       !currentDesc->colorTargets[1].isEnabled &&\n       !currentDesc->colorTargets[2].isEnabled &&\n       !currentDesc->colorTargets[3].isEnabled &&\n       !currentDesc->colorTargets[4].isEnabled &&\n       !currentDesc->colorTargets[5].isEnabled &&\n       !currentDesc->colorTargets[6].isEnabled &&\n       !currentDesc->colorTargets[7].isEnabled &&\n       !currentDesc->depthTarget.isEnabled) {\n      decaf_check_warn_once(!\"Draw executed with no render targets\");\n      return false;\n   }\n\n   if (mCurrentDraw->renderPass && mCurrentDraw->renderPass->desc == currentDesc) {\n      // Already active, nothing to do.\n      return true;\n   }\n\n   auto& foundRp = mRenderPasses[currentDesc.hash()];\n   if (foundRp) {\n      mCurrentDraw->renderPass = foundRp;\n      return true;\n   }\n\n   foundRp = new RenderPassObject();\n   foundRp->desc = currentDesc;\n\n   std::vector<vk::AttachmentDescription> attachmentDescs;\n   std::array<vk::AttachmentReference, latte::MaxRenderTargets> colorAttachmentRefs;\n   vk::AttachmentReference depthAttachmentRef;\n\n   for (auto i = 0u; i < latte::MaxRenderTargets; ++i) {\n      auto &colorTarget = currentDesc->colorTargets[i];\n\n      if (!colorTarget.isEnabled || colorTarget.format == latte::CB_FORMAT::COLOR_INVALID) {\n         colorAttachmentRefs[i].attachment = VK_ATTACHMENT_UNUSED;\n         colorAttachmentRefs[i].layout = vk::ImageLayout::eColorAttachmentOptimal;\n\n         foundRp->colorAttachmentIndexes[i] = -1;\n         continue;\n      }\n\n      auto surfaceFormat = latte::getColorBufferSurfaceFormat(colorTarget.format, colorTarget.numberType);\n      auto hostFormat = getVkSurfaceFormat(surfaceFormat, latte::SQ_TILE_TYPE::DEFAULT);\n\n      vk::AttachmentDescription colorAttachmentDesc;\n      colorAttachmentDesc.flags = vk::AttachmentDescriptionFlagBits::eMayAlias;\n      colorAttachmentDesc.format = hostFormat;\n      colorAttachmentDesc.samples = vk::SampleCountFlagBits::e1;\n      colorAttachmentDesc.loadOp = vk::AttachmentLoadOp::eLoad;\n      colorAttachmentDesc.storeOp = vk::AttachmentStoreOp::eStore;\n      colorAttachmentDesc.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;\n      colorAttachmentDesc.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;\n      colorAttachmentDesc.initialLayout = vk::ImageLayout::eColorAttachmentOptimal;\n      colorAttachmentDesc.finalLayout = vk::ImageLayout::eColorAttachmentOptimal;\n      attachmentDescs.push_back(colorAttachmentDesc);\n      auto attachmentIndex = static_cast<uint32_t>(attachmentDescs.size() - 1);\n\n      colorAttachmentRefs[i].attachment = attachmentIndex;\n      colorAttachmentRefs[i].layout = vk::ImageLayout::eColorAttachmentOptimal;\n\n      foundRp->colorAttachmentIndexes[i] = attachmentIndex;\n   }\n\n   do {\n      auto depthTarget = currentDesc->depthTarget;\n      if (!depthTarget.isEnabled || depthTarget.format == latte::DB_FORMAT::DEPTH_INVALID) {\n         depthAttachmentRef.attachment = VK_ATTACHMENT_UNUSED;\n         depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;\n\n         foundRp->depthAttachmentIndex = -1;\n         continue;\n      }\n\n      auto surfaceFormat = latte::getDepthBufferSurfaceFormat(depthTarget.format);\n      auto hostFormat = getVkSurfaceFormat(surfaceFormat, latte::SQ_TILE_TYPE::DEPTH);\n\n      vk::AttachmentDescription depthAttachmentDesc;\n      depthAttachmentDesc.format = hostFormat;\n      depthAttachmentDesc.samples = vk::SampleCountFlagBits::e1;\n      depthAttachmentDesc.loadOp = vk::AttachmentLoadOp::eLoad;\n      depthAttachmentDesc.storeOp = vk::AttachmentStoreOp::eStore;\n      depthAttachmentDesc.stencilLoadOp = vk::AttachmentLoadOp::eLoad;\n      depthAttachmentDesc.stencilStoreOp = vk::AttachmentStoreOp::eStore;\n      depthAttachmentDesc.initialLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;\n      depthAttachmentDesc.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;\n      attachmentDescs.push_back(depthAttachmentDesc);\n      auto attachmentIndex = static_cast<uint32_t>(attachmentDescs.size() - 1);\n\n      depthAttachmentRef.attachment = attachmentIndex;\n      depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;\n\n      foundRp->depthAttachmentIndex = attachmentIndex;\n   } while (false);\n\n   vk::SubpassDescription genericSubpass;\n   genericSubpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;\n   genericSubpass.inputAttachmentCount = 0;\n   genericSubpass.pInputAttachments = nullptr;\n   genericSubpass.colorAttachmentCount = static_cast<uint32_t>(colorAttachmentRefs.size());\n   genericSubpass.pColorAttachments = colorAttachmentRefs.data();\n   genericSubpass.pResolveAttachments = 0;\n   genericSubpass.pDepthStencilAttachment = &depthAttachmentRef;\n   genericSubpass.preserveAttachmentCount = 0;\n   genericSubpass.pPreserveAttachments = nullptr;\n\n   vk::RenderPassCreateInfo renderPassDesc;\n   renderPassDesc.attachmentCount = static_cast<uint32_t>(attachmentDescs.size());\n   renderPassDesc.pAttachments = attachmentDescs.data();\n   renderPassDesc.subpassCount = 1;\n   renderPassDesc.pSubpasses = &genericSubpass;\n   renderPassDesc.dependencyCount = 0;\n   renderPassDesc.pDependencies = nullptr;\n   auto renderPass = mDevice.createRenderPass(renderPassDesc);\n   foundRp->renderPass = renderPass;\n\n   mCurrentDraw->renderPass = foundRp;\n   return true;\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_samplers.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"vulkan_utils.h\"\n\nnamespace vulkan\n{\n\nSamplerDesc\nDriver::getSamplerDesc(ShaderStage shaderStage, uint32_t samplerIdx)\n{\n   uint32_t samplerBaseIdx;\n   if (shaderStage == ShaderStage::Vertex) {\n      samplerBaseIdx = 18;\n   } else if (shaderStage == ShaderStage::Geometry) {\n      samplerBaseIdx = 36;\n   } else if (shaderStage == ShaderStage::Pixel) {\n      samplerBaseIdx = 0;\n   } else {\n      decaf_abort(\"Unknown shader stage\");\n   }\n\n   SamplerDesc desc;\n\n   auto i = samplerBaseIdx + samplerIdx;\n   desc.texSamplerWord0 = getRegister<latte::SQ_TEX_SAMPLER_WORD0_N>(latte::Register::SQ_TEX_SAMPLER_WORD0_0 + 4 * (i * 3));\n   desc.texSamplerWord1 = getRegister<latte::SQ_TEX_SAMPLER_WORD1_N>(latte::Register::SQ_TEX_SAMPLER_WORD1_0 + 4 * (i * 3));\n   desc.texSamplerWord2 = getRegister<latte::SQ_TEX_SAMPLER_WORD2_N>(latte::Register::SQ_TEX_SAMPLER_WORD2_0 + 4 * (i * 3));\n\n   return desc;\n}\n\nvoid\nDriver::updateDrawSampler(ShaderStage shaderStage, uint32_t samplerIdx)\n{\n   uint32_t shaderStageIdx = static_cast<uint32_t>(shaderStage);\n   auto &drawSampler = mCurrentDraw->samplers[shaderStageIdx][samplerIdx];\n\n   HashedDesc<SamplerDesc> currentDesc = getSamplerDesc(shaderStage, samplerIdx);\n\n   if (drawSampler && drawSampler->desc == currentDesc) {\n      // We already have the correct sampler set\n      return;\n   }\n\n   auto &foundSamp = mSamplers[currentDesc.hash()];\n   if (foundSamp) {\n      drawSampler = foundSamp;\n      return;\n   }\n\n   vk::SamplerCreateInfo samplerDesc;\n   samplerDesc.magFilter = getVkXyTextureFilter(currentDesc->texSamplerWord0.XY_MAG_FILTER());\n   samplerDesc.minFilter = getVkXyTextureFilter(currentDesc->texSamplerWord0.XY_MIN_FILTER());\n   samplerDesc.mipmapMode = getVkZTextureFilter(currentDesc->texSamplerWord0.MIP_FILTER());\n   samplerDesc.addressModeU = getVkTextureAddressMode(currentDesc->texSamplerWord0.CLAMP_X());\n   samplerDesc.addressModeV = getVkTextureAddressMode(currentDesc->texSamplerWord0.CLAMP_Y());\n   samplerDesc.addressModeW = getVkTextureAddressMode(currentDesc->texSamplerWord0.CLAMP_Z());\n   samplerDesc.mipLodBias = static_cast<float>(currentDesc->texSamplerWord1.LOD_BIAS());\n   samplerDesc.anisotropyEnable = getVkAnisotropyEnabled(currentDesc->texSamplerWord0.MAX_ANISO_RATIO());\n   samplerDesc.maxAnisotropy = getVkMaxAnisotropy(currentDesc->texSamplerWord0.MAX_ANISO_RATIO());\n   samplerDesc.compareEnable = getVkCompareOpEnabled(currentDesc->texSamplerWord0.DEPTH_COMPARE_FUNCTION());\n   samplerDesc.compareOp = getVkCompareOp(currentDesc->texSamplerWord0.DEPTH_COMPARE_FUNCTION());\n   samplerDesc.minLod = static_cast<float>(currentDesc->texSamplerWord1.MIN_LOD());\n   samplerDesc.maxLod = static_cast<float>(currentDesc->texSamplerWord1.MAX_LOD());\n   samplerDesc.borderColor = getVkBorderColor(currentDesc->texSamplerWord0.BORDER_COLOR_TYPE());\n   samplerDesc.unnormalizedCoordinates = VK_FALSE;\n   auto sampler = mDevice.createSampler(samplerDesc);\n\n   foundSamp = new SamplerObject();\n   foundSamp->desc = currentDesc;\n   foundSamp->sampler = sampler;\n\n   drawSampler = foundSamp;\n}\n\nbool\nDriver::checkCurrentSamplers()\n{\n   if (mCurrentDraw->vertexShader) {\n      for (auto samplerIdx = 0u; samplerIdx < latte::MaxSamplers; ++samplerIdx) {\n         if (mCurrentDraw->vertexShader->shader.meta.samplerUsed[samplerIdx]) {\n            updateDrawSampler(ShaderStage::Vertex, samplerIdx);\n         } else {\n            mCurrentDraw->samplers[0][samplerIdx] = nullptr;\n         }\n      }\n   } else {\n      for (auto samplerIdx = 0; samplerIdx < latte::MaxSamplers; ++samplerIdx) {\n         mCurrentDraw->samplers[0][samplerIdx] = nullptr;\n      }\n   }\n\n   if (mCurrentDraw->geometryShader) {\n      for (auto samplerIdx = 0u; samplerIdx < latte::MaxSamplers; ++samplerIdx) {\n         if (mCurrentDraw->geometryShader->shader.meta.samplerUsed[samplerIdx]) {\n            updateDrawSampler(ShaderStage::Geometry, samplerIdx);\n         } else {\n            mCurrentDraw->samplers[1][samplerIdx] = nullptr;\n         }\n      }\n   } else {\n      for (auto samplerIdx = 0; samplerIdx < latte::MaxSamplers; ++samplerIdx) {\n         mCurrentDraw->samplers[1][samplerIdx] = nullptr;\n      }\n   }\n\n   if (mCurrentDraw->pixelShader) {\n      for (auto samplerIdx = 0u; samplerIdx < latte::MaxSamplers; ++samplerIdx) {\n         if (mCurrentDraw->pixelShader->shader.meta.samplerUsed[samplerIdx]) {\n            updateDrawSampler(ShaderStage::Pixel, samplerIdx);\n         } else {\n            mCurrentDraw->samplers[2][samplerIdx] = nullptr;\n         }\n      }\n   } else {\n      for (auto samplerIdx = 0; samplerIdx < latte::MaxSamplers; ++samplerIdx) {\n         mCurrentDraw->samplers[2][samplerIdx] = nullptr;\n      }\n   }\n\n   return true;\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_shaderbuffers.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\nnamespace vulkan\n{\n\nvoid\nDriver::updateDrawGprBuffer(ShaderStage shaderStage)\n{\n   auto shaderStageInt = static_cast<uint32_t>(shaderStage);\n   const void *registerVals = nullptr;\n\n   if (shaderStage == ShaderStage::Vertex) {\n      registerVals = &mRegisters[latte::Register::SQ_ALU_CONSTANT0_256 / 4];\n   } else if (shaderStage == ShaderStage::Geometry) {\n      // No registers are reserved for GS\n      return;\n   } else if (shaderStage == ShaderStage::Pixel) {\n      registerVals = &mRegisters[latte::Register::SQ_ALU_CONSTANT0_0 / 4];\n   } else {\n      decaf_abort(\"Unknown shader stage\");\n   }\n\n   auto gprsBuffer = getStagingBuffer(256 * 4 * 4, StagingBufferType::CpuToGpu);\n   copyToStagingBuffer(gprsBuffer, 0, registerVals, 256 * 4 * 4);\n\n   if (shaderStage == ShaderStage::Vertex) {\n      transitionStagingBuffer(gprsBuffer, ResourceUsage::VertexUniforms);\n   } else if (shaderStage == ShaderStage::Geometry) {\n      transitionStagingBuffer(gprsBuffer, ResourceUsage::GeometryUniforms);\n   } else if (shaderStage == ShaderStage::Pixel) {\n      transitionStagingBuffer(gprsBuffer, ResourceUsage::PixelUniforms);\n   } else {\n      decaf_abort(\"Unexpected shader stage in GPR buffer setup\");\n   }\n\n   mCurrentDraw->gprBuffers[shaderStageInt] = gprsBuffer;\n}\n\nvoid\nDriver::updateDrawUniformBuffer(ShaderStage shaderStage, uint32_t cbufferIdx)\n{\n   auto shaderStageInt = static_cast<uint32_t>(shaderStage);\n\n   uint32_t cacheBase;\n   uint32_t sizeBase;\n   if (shaderStage == ShaderStage::Vertex) {\n      cacheBase = latte::Register::SQ_ALU_CONST_CACHE_VS_0;\n      sizeBase = latte::Register::SQ_ALU_CONST_BUFFER_SIZE_VS_0;\n   } else if (shaderStage == ShaderStage::Geometry) {\n      cacheBase = latte::Register::SQ_ALU_CONST_CACHE_GS_0;\n      sizeBase = latte::Register::SQ_ALU_CONST_BUFFER_SIZE_GS_0;\n   } else if (shaderStage == ShaderStage::Pixel) {\n      cacheBase = latte::Register::SQ_ALU_CONST_CACHE_PS_0;\n      sizeBase = latte::Register::SQ_ALU_CONST_BUFFER_SIZE_PS_0;\n   } else {\n      decaf_abort(\"Unexpected shader stage\");\n   }\n\n   auto sq_alu_const_cache_vs = getRegister<uint32_t>(cacheBase + 4 * cbufferIdx);\n   auto sq_alu_const_buffer_size_vs = getRegister<uint32_t>(sizeBase + 4 * cbufferIdx);\n\n   auto bufferPtr = phys_addr(sq_alu_const_cache_vs << 8);\n   auto bufferSize = sq_alu_const_buffer_size_vs << 8;\n\n   if (!bufferPtr || bufferSize == 0) {\n      mCurrentDraw->uniformBlocks[shaderStageInt][cbufferIdx] = nullptr;\n      return;\n   }\n\n   auto &currentUniformBlock = mCurrentDraw->uniformBlocks[shaderStageInt][cbufferIdx];\n   if (currentUniformBlock &&\n       currentUniformBlock->address == bufferPtr &&\n       currentUniformBlock->size == bufferSize) {\n      // We already have the correct buffer, no need to look it up again.\n      return;\n   }\n\n   auto memCache = getDataMemCache(bufferPtr, bufferSize);\n\n   if (shaderStage == ShaderStage::Vertex) {\n      transitionMemCache(memCache, ResourceUsage::VertexUniforms);\n   } else if (shaderStage == ShaderStage::Geometry) {\n      transitionMemCache(memCache, ResourceUsage::GeometryUniforms);\n   } else if (shaderStage == ShaderStage::Pixel) {\n      transitionMemCache(memCache, ResourceUsage::PixelUniforms);\n   } else {\n      decaf_abort(\"Unexpected shader stage\");\n   }\n\n   currentUniformBlock = memCache;\n}\n\nbool\nDriver::checkCurrentShaderBuffers()\n{\n   auto sq_config = getRegister<latte::SQ_CONFIG>(latte::Register::SQ_CONFIG);\n   bool isDx9Consts = sq_config.DX9_CONSTS();\n\n   if (!isDx9Consts) {\n      for (auto shaderStage = 0; shaderStage < 3; ++shaderStage) {\n         mCurrentDraw->gprBuffers[shaderStage] = nullptr;\n      }\n\n      if (mCurrentDraw->vertexShader) {\n         for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) {\n            if (mCurrentDraw->vertexShader->shader.meta.cbufferUsed[blockIdx]) {\n               updateDrawUniformBuffer(ShaderStage::Vertex, blockIdx);\n            } else {\n               mCurrentDraw->uniformBlocks[0][blockIdx] = nullptr;\n            }\n         }\n      } else {\n         for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) {\n            mCurrentDraw->uniformBlocks[0][blockIdx] = nullptr;\n         }\n      }\n\n      if (mCurrentDraw->geometryShader) {\n         for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) {\n            if (mCurrentDraw->geometryShader->shader.meta.cbufferUsed[blockIdx]) {\n               updateDrawUniformBuffer(ShaderStage::Geometry, blockIdx);\n            } else {\n               mCurrentDraw->uniformBlocks[1][blockIdx] = nullptr;\n            }\n         }\n      } else {\n         for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) {\n            mCurrentDraw->uniformBlocks[1][blockIdx] = nullptr;\n         }\n      }\n\n      if (mCurrentDraw->pixelShader) {\n         for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) {\n            if (mCurrentDraw->pixelShader->shader.meta.cbufferUsed[blockIdx]) {\n               updateDrawUniformBuffer(ShaderStage::Pixel, blockIdx);\n            } else {\n               mCurrentDraw->uniformBlocks[2][blockIdx] = nullptr;\n            }\n         }\n      } else {\n         for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) {\n            mCurrentDraw->uniformBlocks[2][blockIdx] = nullptr;\n         }\n      }\n   } else {\n      for (auto shaderStage = 0; shaderStage < 3; ++shaderStage) {\n         for (auto blockIdx = 0; blockIdx < latte::MaxUniformBlocks; ++blockIdx) {\n            mCurrentDraw->uniformBlocks[shaderStage][blockIdx] = nullptr;\n         }\n      }\n\n      if (mCurrentDraw->vertexShader) {\n         if (mCurrentDraw->vertexShader->shader.meta.cfileUsed) {\n            updateDrawGprBuffer(ShaderStage::Vertex);\n         } else {\n            mCurrentDraw->gprBuffers[0] = nullptr;\n         }\n      } else {\n         mCurrentDraw->gprBuffers[0] = nullptr;\n      }\n\n      if (mCurrentDraw->geometryShader) {\n         if (mCurrentDraw->geometryShader->shader.meta.cfileUsed) {\n            updateDrawGprBuffer(ShaderStage::Geometry);\n         } else {\n            mCurrentDraw->gprBuffers[1] = nullptr;\n         }\n      } else {\n         mCurrentDraw->gprBuffers[1] = nullptr;\n      }\n\n      if (mCurrentDraw->pixelShader) {\n         if (mCurrentDraw->pixelShader->shader.meta.cfileUsed) {\n            updateDrawGprBuffer(ShaderStage::Pixel);\n         } else {\n            mCurrentDraw->gprBuffers[2] = nullptr;\n         }\n      } else {\n         mCurrentDraw->gprBuffers[2] = nullptr;\n      }\n   }\n\n   return true;\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_shaders.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"gpu_config.h\"\n#include \"latte/latte_disassembler.h\"\n\n#include <common/log.h>\n#include <common/platform_dir.h>\n#include <fstream>\n#include <vector>\n\nnamespace vulkan\n{\n\nspirv::TextureInputType\nspirvTextureTypeFromLatte(latte::SQ_NUM_FORMAT format)\n{\n   if (format == latte::SQ_NUM_FORMAT::INT) {\n      return spirv::TextureInputType::INT;\n   }\n   return spirv::TextureInputType::FLOAT;\n}\n\nspirv::PixelOutputType\nspirvPixelTypeFromLatte(latte::CB_NUMBER_TYPE format)\n{\n   switch (format) {\n   case latte::CB_NUMBER_TYPE::SINT:\n      return spirv::PixelOutputType::SINT;\n   case latte::CB_NUMBER_TYPE::UINT:\n      return spirv::PixelOutputType::UINT;\n   default:\n      return spirv::PixelOutputType::FLOAT;\n   }\n}\n\nspirv::VertexShaderDesc\nDriver::getVertexShaderDesc()\n{\n   gsl::span<uint8_t> fsShaderBinary;\n   gsl::span<uint8_t> vsShaderBinary;\n\n   auto pgm_start_fs = getRegister<latte::SQ_PGM_START_FS>(latte::Register::SQ_PGM_START_FS);\n   auto pgm_offset_fs = getRegister<latte::SQ_PGM_CF_OFFSET_FS>(latte::Register::SQ_PGM_CF_OFFSET_FS);\n   auto pgm_size_fs = getRegister<latte::SQ_PGM_SIZE_FS>(latte::Register::SQ_PGM_SIZE_FS);\n   fsShaderBinary = gsl::make_span(\n      phys_cast<uint8_t*>(phys_addr(pgm_start_fs.PGM_START() << 8)).getRawPointer(),\n      pgm_size_fs.PGM_SIZE() << 3);\n   decaf_check(pgm_offset_fs.PGM_OFFSET() == 0);\n\n   auto vgt_gs_mode = getRegister<latte::VGT_GS_MODE>(latte::Register::VGT_GS_MODE);\n   if (vgt_gs_mode.MODE() == latte::VGT_GS_ENABLE_MODE::OFF) {\n      // When GS is disabled, vertex shader comes from vertex shader register\n      auto pgm_start_vs = getRegister<latte::SQ_PGM_START_VS>(latte::Register::SQ_PGM_START_VS);\n      auto pgm_offset_vs = getRegister<latte::SQ_PGM_CF_OFFSET_VS>(latte::Register::SQ_PGM_CF_OFFSET_VS);\n      auto pgm_size_vs = getRegister<latte::SQ_PGM_SIZE_VS>(latte::Register::SQ_PGM_SIZE_VS);\n      vsShaderBinary = gsl::make_span(\n         phys_cast<uint8_t*>(phys_addr(pgm_start_vs.PGM_START() << 8)).getRawPointer(),\n         pgm_size_vs.PGM_SIZE() << 3);\n      decaf_check(pgm_offset_vs.PGM_OFFSET() == 0);\n   } else {\n      // When GS is enabled, vertex shader comes from export shader register\n      auto pgm_start_es = getRegister<latte::SQ_PGM_START_ES>(latte::Register::SQ_PGM_START_ES);\n      auto pgm_offset_es = getRegister<latte::SQ_PGM_CF_OFFSET_ES>(latte::Register::SQ_PGM_CF_OFFSET_ES);\n      auto pgm_size_es = getRegister<latte::SQ_PGM_SIZE_ES>(latte::Register::SQ_PGM_SIZE_ES);\n\n      vsShaderBinary = gsl::make_span(\n         phys_cast<uint8_t*>(phys_addr(pgm_start_es.PGM_START() << 8)).getRawPointer(),\n         pgm_size_es.PGM_SIZE() << 3);\n      decaf_check(pgm_offset_es.PGM_OFFSET() == 0);\n   }\n\n   auto shaderDesc = spirv::VertexShaderDesc { };\n   shaderDesc.type = spirv::ShaderType::Vertex;\n   shaderDesc.binary = vsShaderBinary;\n   shaderDesc.fsBinary = fsShaderBinary;\n\n   auto sq_config = getRegister<latte::SQ_CONFIG>(latte::Register::SQ_CONFIG);\n   shaderDesc.aluInstPreferVector = sq_config.ALU_INST_PREFER_VECTOR();\n\n   for (auto i = 0; i < latte::MaxTextures; ++i) {\n      auto resourceOffset = (latte::SQ_RES_OFFSET::VS_TEX_RESOURCE_0 + i) * 7;\n      auto sq_tex_resource_word0 = getRegister<latte::SQ_TEX_RESOURCE_WORD0_N>(latte::Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset);\n      auto sq_tex_resource_word4 = getRegister<latte::SQ_TEX_RESOURCE_WORD4_N>(latte::Register::SQ_RESOURCE_WORD4_0 + 4 * resourceOffset);\n      shaderDesc.texDims[i] = sq_tex_resource_word0.DIM();\n      shaderDesc.texFormat[i] = spirvTextureTypeFromLatte(sq_tex_resource_word4.NUM_FORMAT_ALL());\n   }\n\n   shaderDesc.regs.sq_pgm_resources_vs = getRegister<latte::SQ_PGM_RESOURCES_VS>(latte::Register::SQ_PGM_RESOURCES_VS);\n   shaderDesc.regs.pa_cl_vs_out_cntl = getRegister<latte::PA_CL_VS_OUT_CNTL>(latte::Register::PA_CL_VS_OUT_CNTL);\n\n   for (auto i = 0u; i < 32; ++i) {\n      shaderDesc.regs.sq_vtx_semantics[i] = getRegister<latte::SQ_VTX_SEMANTIC_N>(latte::Register::SQ_VTX_SEMANTIC_0 + i * 4);\n   }\n\n   for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) {\n      // Note that these registers are not contiguous!\n      shaderDesc.streamOutStride[i] = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_VTX_STRIDE_0 + i * 16) << 2;\n   }\n\n   return shaderDesc;\n}\n\nspirv::GeometryShaderDesc\nDriver::getGeometryShaderDesc()\n{\n   // Do not generate geometry shaders if they are disabled\n   auto vgt_gs_mode = getRegister<latte::VGT_GS_MODE>(latte::Register::VGT_GS_MODE);\n   if (vgt_gs_mode.MODE() == latte::VGT_GS_ENABLE_MODE::OFF) {\n      return spirv::GeometryShaderDesc();\n   }\n\n   decaf_check(mCurrentDraw->vertexShader);\n\n   // Geometry shader comes from geometry shader register\n   auto pgm_start_gs = getRegister<latte::SQ_PGM_START_VS>(latte::Register::SQ_PGM_START_GS);\n   auto pgm_offset_gs = getRegister<latte::SQ_PGM_CF_OFFSET_GS>(latte::Register::SQ_PGM_CF_OFFSET_GS);\n   auto pgm_size_gs = getRegister<latte::SQ_PGM_SIZE_GS>(latte::Register::SQ_PGM_SIZE_GS);\n   auto gsShaderBinary = gsl::make_span(\n      phys_cast<uint8_t*>(phys_addr(pgm_start_gs.PGM_START() << 8)).getRawPointer(),\n      pgm_size_gs.PGM_SIZE() << 3);\n   decaf_check(pgm_offset_gs.PGM_OFFSET() == 0);\n\n   // Data cache shader comes from vertex shader register\n   auto pgm_start_vs = getRegister<latte::SQ_PGM_START_VS>(latte::Register::SQ_PGM_START_VS);\n   auto pgm_offset_vs = getRegister<latte::SQ_PGM_CF_OFFSET_VS>(latte::Register::SQ_PGM_CF_OFFSET_VS);\n   auto pgm_size_vs = getRegister<latte::SQ_PGM_SIZE_VS>(latte::Register::SQ_PGM_SIZE_VS);\n   auto dcShaderBinary = gsl::make_span(\n      phys_cast<uint8_t*>(phys_addr(pgm_start_vs.PGM_START() << 8)).getRawPointer(),\n      pgm_size_vs.PGM_SIZE() << 3);\n   decaf_check(pgm_offset_vs.PGM_OFFSET() == 0);\n\n   // If Geometry shading is enabled, we need to have a geometry shader, and data-cache\n   //  shaders must always be set if a geometry shader is used.\n   decaf_check(!gsShaderBinary.empty());\n   decaf_check(!dcShaderBinary.empty());\n\n   // Need to generate the shader here...\n   auto shaderDesc = spirv::GeometryShaderDesc { };\n   shaderDesc.type = spirv::ShaderType::Geometry;\n   shaderDesc.binary = gsShaderBinary;\n   shaderDesc.dcBinary = dcShaderBinary;\n\n   auto sq_config = getRegister<latte::SQ_CONFIG>(latte::Register::SQ_CONFIG);\n   shaderDesc.aluInstPreferVector = sq_config.ALU_INST_PREFER_VECTOR();\n\n   for (auto i = 0; i < latte::MaxTextures; ++i) {\n      auto resourceOffset = (latte::SQ_RES_OFFSET::GS_TEX_RESOURCE_0 + i) * 7;\n      auto sq_tex_resource_word0 = getRegister<latte::SQ_TEX_RESOURCE_WORD0_N>(latte::Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset);\n      auto sq_tex_resource_word4 = getRegister<latte::SQ_TEX_RESOURCE_WORD4_N>(latte::Register::SQ_RESOURCE_WORD4_0 + 4 * resourceOffset);\n      shaderDesc.texDims[i] = sq_tex_resource_word0.DIM();\n      shaderDesc.texFormat[i] = spirvTextureTypeFromLatte(sq_tex_resource_word4.NUM_FORMAT_ALL());\n   }\n\n   shaderDesc.regs.sq_gs_vert_itemsize = getRegister<latte::SQ_GS_VERT_ITEMSIZE>(latte::Register::SQ_GS_VERT_ITEMSIZE);\n   shaderDesc.regs.vgt_gs_out_prim_type = getRegister<latte::VGT_GS_OUT_PRIMITIVE_TYPE>(latte::Register::VGT_GS_OUT_PRIM_TYPE);\n   shaderDesc.regs.vgt_gs_mode = getRegister<latte::VGT_GS_MODE>(latte::Register::VGT_GS_MODE);\n   shaderDesc.regs.sq_gsvs_ring_itemsize = getRegister<uint32_t>(latte::Register::SQ_GSVS_RING_ITEMSIZE);\n   shaderDesc.regs.pa_cl_vs_out_cntl = getRegister<latte::PA_CL_VS_OUT_CNTL>(latte::Register::PA_CL_VS_OUT_CNTL);\n\n   for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) {\n      // Note that these registers are not contiguous!\n      shaderDesc.streamOutStride[i] = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_VTX_STRIDE_0 + i * 16) << 2;\n   }\n\n   return shaderDesc;\n}\n\nspirv::PixelShaderDesc\nDriver::getPixelShaderDesc()\n{\n   // Do not generate pixel shaders if rasterization is disabled\n   auto pa_cl_clip_cntl = getRegister<latte::PA_CL_CLIP_CNTL>(latte::Register::PA_CL_CLIP_CNTL);\n   if (pa_cl_clip_cntl.RASTERISER_DISABLE()) {\n      return spirv::PixelShaderDesc();\n   }\n\n   decaf_check(mCurrentDraw->vertexShader);\n\n   auto pgm_start_ps = getRegister<latte::SQ_PGM_START_PS>(latte::Register::SQ_PGM_START_PS);\n   auto pgm_offset_ps = getRegister<latte::SQ_PGM_CF_OFFSET_PS>(latte::Register::SQ_PGM_CF_OFFSET_PS);\n   auto pgm_size_ps = getRegister<latte::SQ_PGM_SIZE_PS>(latte::Register::SQ_PGM_SIZE_PS);\n   auto psShaderBinary = gsl::make_span(\n      phys_cast<uint8_t*>(phys_addr(pgm_start_ps.PGM_START() << 8)).getRawPointer(),\n      pgm_size_ps.PGM_SIZE() << 3);\n   decaf_check(pgm_offset_ps.PGM_OFFSET() == 0);\n\n   auto shaderDesc = spirv::PixelShaderDesc { };\n   shaderDesc.type = spirv::ShaderType::Pixel;\n   shaderDesc.binary = psShaderBinary;\n\n   auto sq_config = getRegister<latte::SQ_CONFIG>(latte::Register::SQ_CONFIG);\n   shaderDesc.aluInstPreferVector = sq_config.ALU_INST_PREFER_VECTOR();\n\n   for (auto i = 0; i < latte::MaxRenderTargets; ++i) {\n      auto cb_color_info = getRegister<latte::CB_COLORN_INFO>(latte::Register::CB_COLOR0_INFO + i * 4);\n      shaderDesc.pixelOutType[i] = spirvPixelTypeFromLatte(cb_color_info.NUMBER_TYPE());\n   }\n\n   for (auto i = 0; i < latte::MaxTextures; ++i) {\n      auto resourceOffset = (latte::SQ_RES_OFFSET::PS_TEX_RESOURCE_0 + i) * 7;\n      auto sq_tex_resource_word0 = getRegister<latte::SQ_TEX_RESOURCE_WORD0_N>(latte::Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset);\n      auto sq_tex_resource_word4 = getRegister<latte::SQ_TEX_RESOURCE_WORD4_N>(latte::Register::SQ_RESOURCE_WORD4_0 + 4 * resourceOffset);\n      shaderDesc.texDims[i] = sq_tex_resource_word0.DIM();\n      shaderDesc.texFormat[i] = spirvTextureTypeFromLatte(sq_tex_resource_word4.NUM_FORMAT_ALL());\n   }\n\n   shaderDesc.regs.sq_pgm_resources_ps = getRegister<latte::SQ_PGM_RESOURCES_PS>(latte::Register::SQ_PGM_RESOURCES_PS);\n   shaderDesc.regs.sq_pgm_exports_ps = getRegister<latte::SQ_PGM_EXPORTS_PS>(latte::Register::SQ_PGM_EXPORTS_PS);\n\n   shaderDesc.regs.spi_ps_in_control_0 = getRegister<latte::SPI_PS_IN_CONTROL_0>(latte::Register::SPI_PS_IN_CONTROL_0);\n   shaderDesc.regs.spi_ps_in_control_1 = getRegister<latte::SPI_PS_IN_CONTROL_1>(latte::Register::SPI_PS_IN_CONTROL_1);\n   shaderDesc.regs.spi_vs_out_config = getRegister<latte::SPI_VS_OUT_CONFIG>(latte::Register::SPI_VS_OUT_CONFIG);\n\n   shaderDesc.regs.cb_shader_control = getRegister<latte::CB_SHADER_CONTROL>(latte::Register::CB_SHADER_CONTROL);\n   shaderDesc.regs.cb_shader_mask = getRegister<latte::CB_SHADER_MASK>(latte::Register::CB_SHADER_MASK);\n   shaderDesc.regs.db_shader_control = getRegister<latte::DB_SHADER_CONTROL>(latte::Register::DB_SHADER_CONTROL);\n\n   for (auto i = 0; i < 32; ++i) {\n      shaderDesc.regs.spi_ps_input_cntls[i] = getRegister<latte::SPI_PS_INPUT_CNTL_N>(latte::Register::SPI_PS_INPUT_CNTL_0 + i * 4);\n   }\n\n   for (auto i = 0; i < 10; ++i) {\n      shaderDesc.regs.spi_vs_out_ids[i] = getRegister<latte::SPI_VS_OUT_ID_N>(latte::Register::SPI_VS_OUT_ID_0 + i * 4);\n   }\n\n   return shaderDesc;\n}\n\nstruct ShaderBinaryEntry\n{\n   ShaderBinaryEntry(std::string name, gsl::span<const uint8_t> binary) :\n      name(name), binary(binary)\n   {\n   }\n\n   std::string name;\n   gsl::span<const uint8_t> binary;\n};\n\nusing ShaderBinaries = std::vector<ShaderBinaryEntry>;\n\nstatic void\ndumpRawShaderBinaries(const ShaderBinaries &shaderBinaries,\n                      std::string shaderName)\n{\n   // Write binary shaders to dump file\n   for (auto shaderBinary : shaderBinaries) {\n      auto filePathBinarySuffix = shaderBinary.name.empty() ?\n         \"\" : fmt::format(\"_{}\", shaderBinary.name);\n      auto filePathBinary = fmt::format(\"dump/{}{}.bin\", shaderName,\n                                        filePathBinarySuffix);\n      if (!platform::fileExists(filePathBinary)) {\n         platform::createDirectory(\"dump\");\n\n         // Write Binary Output\n         auto file = std::ofstream{ filePathBinary,\n            std::ofstream::out | std::ofstream::binary };\n         file.write(reinterpret_cast<const char *>(shaderBinary.binary.data()),\n                    shaderBinary.binary.size());\n      }\n   }\n}\n\nstatic void\ndumpRawShader(const spirv::ShaderDesc *desc,\n              bool onlyDumpBinaries)\n{\n   auto shaderBinaries = ShaderBinaries { };\n   auto shaderName = std::string { };\n\n   if (desc->type == spirv::ShaderType::Vertex) {\n      auto vsDesc = reinterpret_cast<const spirv::VertexShaderDesc*>(desc);\n      auto vsAddr = static_cast<uint32_t>(\n         reinterpret_cast<uintptr_t>(vsDesc->binary.data()));\n      auto fsAddr = static_cast<uint32_t>(\n         reinterpret_cast<uintptr_t>(vsDesc->fsBinary.data()));\n\n      shaderName = fmt::format(\"vs_{:08x}_{:08x}\", vsAddr, fsAddr);\n      if ((reinterpret_cast<uintptr_t>(vsDesc->fsBinary.data()) & 0xFFFFFFFF) != 0) {\n         shaderBinaries.emplace_back(\"fs\", vsDesc->fsBinary);\n      }\n      if ((reinterpret_cast<uintptr_t>(vsDesc->binary.data()) & 0xFFFFFFFF) != 0) {\n         shaderBinaries.emplace_back(\"\", vsDesc->binary);\n      }\n   } else if (desc->type == spirv::ShaderType::Geometry) {\n      auto gsDesc = reinterpret_cast<const spirv::GeometryShaderDesc*>(desc);\n      auto gsAddr = static_cast<uint32_t>(\n         reinterpret_cast<uintptr_t>(gsDesc->binary.data()));\n      auto dcAddr = static_cast<uint32_t>(\n         reinterpret_cast<uintptr_t>(gsDesc->dcBinary.data()));\n\n      shaderName = fmt::format(\"gs_{:08x}_{:08x}\", gsAddr, dcAddr);\n      if ((reinterpret_cast<uintptr_t>(gsDesc->dcBinary.data()) & 0xFFFFFFFF) != 0) {\n         shaderBinaries.emplace_back(\"dc\", gsDesc->dcBinary);\n      }\n      if ((reinterpret_cast<uintptr_t>(gsDesc->binary.data()) & 0xFFFFFFFF) != 0) {\n         shaderBinaries.emplace_back(\"\", gsDesc->binary);\n      }\n   } else if (desc->type == spirv::ShaderType::Pixel) {\n      auto psDesc = reinterpret_cast<const spirv::PixelShaderDesc*>(desc);\n      auto psAddr = static_cast<uint32_t>(\n         reinterpret_cast<uintptr_t>(psDesc->binary.data()));\n\n      shaderName = fmt::format(\"ps_{:08x}\", psAddr);\n      if ((reinterpret_cast<uintptr_t>(psDesc->binary.data()) & 0xFFFFFFFF) != 0) {\n         shaderBinaries.emplace_back(\"\", psDesc->binary);\n      }\n   } else {\n      decaf_abort(\"Unexpected shader type\");\n   }\n\n   // Dump binaries\n   dumpRawShaderBinaries(shaderBinaries, shaderName);\n   if (onlyDumpBinaries) {\n      return;\n   }\n\n   // Dump disassembly\n   auto outputStr = std::string { };\n   if (desc->type == spirv::ShaderType::Vertex) {\n      auto vsDesc = reinterpret_cast<const spirv::VertexShaderDesc*>(desc);\n      auto fsDisasm = std::string { };\n      auto vsDisasm = std::string { };\n\n      if (!vsDesc->fsBinary.empty()) {\n         fsDisasm = latte::disassemble(vsDesc->fsBinary, true);\n      }\n\n      if (!vsDesc->binary.empty()) {\n         vsDisasm = latte::disassemble(vsDesc->binary, false);\n      }\n\n      outputStr += \"Fetch Shader:\\n\";\n      outputStr += fsDisasm + \"\\n\\n\";\n      outputStr += \"Vertex Shader:\\n\";\n      outputStr += vsDisasm + \"\\n\\n\";\n   } else if (desc->type == spirv::ShaderType::Geometry) {\n      auto gsDesc = reinterpret_cast<const spirv::GeometryShaderDesc*>(desc);\n      auto gsDisasm = std::string { };\n      auto dcDisasm = std::string { };\n\n      if (!gsDesc->dcBinary.empty()) {\n         dcDisasm = latte::disassemble(gsDesc->dcBinary, false);\n      }\n\n      if (!gsDesc->binary.empty()) {\n         gsDisasm = latte::disassemble(gsDesc->binary, false);\n      }\n\n      outputStr += \"Geometry Shader:\\n\";\n      outputStr += gsDisasm + \"\\n\\n\";\n      outputStr += \"DMA Copy Shader:\\n\";\n      outputStr += dcDisasm + \"\\n\\n\";\n   } else if (desc->type == spirv::ShaderType::Pixel) {\n      auto psDesc = reinterpret_cast<const spirv::PixelShaderDesc*>(desc);\n      auto psDisasm = std::string { };\n\n      if (!psDesc->binary.empty()) {\n         psDisasm = latte::disassemble(psDesc->binary, false);\n      }\n\n      outputStr += \"Pixel Shader:\\n\";\n      outputStr += psDisasm + \"\\n\\n\";\n   } else {\n      decaf_abort(\"Unexpected shader type\");\n   }\n\n   // Write shader disassembly to dump file\n   auto filePath = fmt::format(\"dump/{}.txt\", shaderName);\n   if (!platform::fileExists(filePath)) {\n      platform::createDirectory(\"dump\");\n\n      // Write Text Output\n      auto file = std::ofstream { filePath, std::ofstream::out };\n      file << outputStr << std::endl;\n   }\n}\n\nstatic void\ndumpTranslatedShader(const spirv::ShaderDesc *desc,\n                     const spirv::Shader *shader)\n{\n   auto shaderText = spirv::shaderToString(shader);\n   auto shaderName = std::string { };\n   auto outputStr = std::string { };\n\n   if (desc->type == spirv::ShaderType::Vertex) {\n      auto vsDesc = reinterpret_cast<const spirv::VertexShaderDesc*>(desc);\n      auto vsAddr = static_cast<uint32_t>(\n         reinterpret_cast<uintptr_t>(vsDesc->binary.data()));\n      auto fsAddr = static_cast<uint32_t>(\n         reinterpret_cast<uintptr_t>(vsDesc->fsBinary.data()));\n\n      shaderName = fmt::format(\"vs_{:08x}_{:08x}\", vsAddr, fsAddr);\n   } else if (desc->type == spirv::ShaderType::Geometry) {\n      auto gsDesc = reinterpret_cast<const spirv::GeometryShaderDesc*>(desc);\n      auto gsAddr = static_cast<uint32_t>(\n         reinterpret_cast<uintptr_t>(gsDesc->binary.data()));\n      auto dcAddr = static_cast<uint32_t>(\n         reinterpret_cast<uintptr_t>(gsDesc->dcBinary.data()));\n\n      shaderName = fmt::format(\"gs_{:08x}_{:08x}\", gsAddr, dcAddr);\n   } else if (desc->type == spirv::ShaderType::Pixel) {\n      auto psDesc = reinterpret_cast<const spirv::PixelShaderDesc*>(desc);\n      auto psAddr = static_cast<uint32_t>(\n         reinterpret_cast<uintptr_t>(psDesc->binary.data()));\n\n      shaderName = fmt::format(\"ps_{:08x}\", psAddr);\n   } else {\n      decaf_abort(\"Unexpected shader type\");\n   }\n\n   if (desc->type == spirv::ShaderType::Vertex) {\n      outputStr += \"Compiled Vertex Shader:\\n\";\n      outputStr += shaderText + \"\\n\\n\";\n   } else if (desc->type == spirv::ShaderType::Geometry) {\n      outputStr += \"Compiled Geometry Shader:\\n\";\n      outputStr += shaderText + \"\\n\\n\";\n   } else if (desc->type == spirv::ShaderType::Pixel) {\n      outputStr += \"Compiled Pixel Shader:\\n\";\n      outputStr += shaderText + \"\\n\\n\";\n   } else {\n      decaf_abort(\"Unexpected shader type\");\n   }\n\n   // Write to dump file\n   auto filePath = fmt::format(\"dump/{}.spv.txt\", shaderName);\n   if (!platform::fileExists(filePath)) {\n      platform::createDirectory(\"dump\");\n\n      // Write Text Output\n      auto file = std::ofstream { filePath, std::ofstream::out };\n      file << outputStr << std::endl;\n\n      // SPIRV Binary Output\n      auto binFilePath = fmt::format(\"dump/{}.spv\", shaderName);\n      auto binFile = std::ofstream { binFilePath,\n         std::ofstream::out | std::ofstream::binary };\n      binFile.write(reinterpret_cast<const char*>(shader->binary.data()),\n                    shader->binary.size() * sizeof(shader->binary[0]));\n   }\n}\n\nbool\nDriver::checkCurrentVertexShader()\n{\n   // We defer the hashing until after we check if this shader is even\n   // actually enabled or not...  Performance !\n   auto currentDescPrehash = getVertexShaderDesc();\n\n   // Check if the shader stage is disabled\n   if (currentDescPrehash.type == spirv::ShaderType::Unknown) {\n      mCurrentDraw->vertexShader = nullptr;\n      return true;\n   }\n\n   auto currentDesc =\n      HashedDesc<spirv::VertexShaderDesc> { currentDescPrehash };\n\n   if (mCurrentDraw->vertexShader &&\n       mCurrentDraw->vertexShader->desc == currentDesc) {\n      // Already active, nothing to do.\n      return true;\n   }\n\n   auto& foundShader = mVertexShaders[currentDesc.hash()];\n   if (foundShader) {\n      mCurrentDraw->vertexShader = foundShader;\n      return true;\n   }\n\n   foundShader = new VertexShaderObject();\n   foundShader->desc = currentDesc;\n\n   if (mDumpShaders) {\n      dumpRawShader(&*currentDesc, mDumpShaderBinariesOnly);\n   }\n\n   if (!spirv::translate(*currentDesc, &foundShader->shader)) {\n      decaf_abort(\"Failed to translate vertex shader\");\n   }\n\n   if (mDumpShaders) {\n      dumpTranslatedShader(&*currentDesc, &foundShader->shader);\n   }\n\n   auto module = mDevice.createShaderModule(\n      vk::ShaderModuleCreateInfo({}, foundShader->shader.binary.size() * 4,\n                                 foundShader->shader.binary.data()));\n   foundShader->module = module;\n\n   auto shaderAddr = static_cast<uint32_t>(\n      reinterpret_cast<uintptr_t>(currentDesc->binary.data()));\n   setVkObjectName(module, fmt::format(\"vs_{:08x}\", shaderAddr).c_str());\n\n   mCurrentDraw->vertexShader = foundShader;\n   return true;\n}\n\nbool\nDriver::checkCurrentGeometryShader()\n{\n   // We defer the hashing until after we check if this shader is even\n   // actually enabled or not...  Performance !\n   auto currentDescPrehash = getGeometryShaderDesc();\n\n   // Check if the shader stage is disabled\n   if (currentDescPrehash.type == spirv::ShaderType::Unknown) {\n      mCurrentDraw->geometryShader = nullptr;\n      return true;\n   }\n\n   auto currentDesc =\n      HashedDesc<spirv::GeometryShaderDesc> { currentDescPrehash };\n\n   if (mCurrentDraw->geometryShader &&\n       mCurrentDraw->geometryShader->desc == currentDesc) {\n      // Already active, nothing to do.\n      return true;\n   }\n\n   auto& foundShader = mGeometryShaders[currentDesc.hash()];\n   if (foundShader) {\n      mCurrentDraw->geometryShader = foundShader;\n      return true;\n   }\n\n   foundShader = new GeometryShaderObject();\n   foundShader->desc = currentDesc;\n\n   if (mDumpShaders) {\n      dumpRawShader(&*currentDesc, mDumpShaderBinariesOnly);\n   }\n\n   if (!spirv::translate(*currentDesc, &foundShader->shader)) {\n      decaf_abort(\"Failed to translate geometry shader\");\n   }\n\n   if (mDumpShaders) {\n      dumpTranslatedShader(&*currentDesc, &foundShader->shader);\n   }\n\n   auto module = mDevice.createShaderModule(\n      vk::ShaderModuleCreateInfo({}, foundShader->shader.binary.size() * 4,\n                                 foundShader->shader.binary.data()));\n   foundShader->module = module;\n\n   auto shaderAddr = static_cast<uint32_t>(\n      reinterpret_cast<uintptr_t>(currentDesc->binary.data()));\n   setVkObjectName(module, fmt::format(\"gs_{:08x}\", shaderAddr).c_str());\n\n   mCurrentDraw->geometryShader = foundShader;\n   return true;\n}\n\nbool\nDriver::checkCurrentPixelShader()\n{\n   // We defer the hashing until after we check if this shader is even\n   // actually enabled or not...  Performance !\n   auto currentDescPrehash = getPixelShaderDesc();\n\n   // Check if the shader stage is disabled\n   if (currentDescPrehash.type == spirv::ShaderType::Unknown) {\n      mCurrentDraw->pixelShader = nullptr;\n      return true;\n   }\n\n   auto currentDesc = HashedDesc<spirv::PixelShaderDesc> { currentDescPrehash };\n\n   if (mCurrentDraw->pixelShader &&\n       mCurrentDraw->pixelShader->desc == currentDesc) {\n      // Already active, nothing to do.\n      return true;\n   }\n\n   auto& foundShader = mPixelShaders[currentDesc.hash()];\n   if (foundShader) {\n      mCurrentDraw->pixelShader = foundShader;\n      return true;\n   }\n\n   foundShader = new PixelShaderObject();\n   foundShader->desc = currentDesc;\n\n   if (mDumpShaders) {\n      dumpRawShader(&*currentDesc, mDumpShaderBinariesOnly);\n   }\n\n   if (!spirv::translate(*currentDesc, &foundShader->shader)) {\n      decaf_abort(\"Failed to translate pixel shader\");\n   }\n\n   if (mDumpShaders) {\n      dumpTranslatedShader(&*currentDesc, &foundShader->shader);\n   }\n\n   auto module = mDevice.createShaderModule(\n      vk::ShaderModuleCreateInfo({}, foundShader->shader.binary.size() * 4,\n                                 foundShader->shader.binary.data()));\n   foundShader->module = module;\n\n   auto shaderAddr = static_cast<uint32_t>(\n      reinterpret_cast<uintptr_t>(currentDesc->binary.data()));\n   setVkObjectName(module, fmt::format(\"ps_{:08x}\", shaderAddr).c_str());\n\n   mCurrentDraw->pixelShader = foundShader;\n   return true;\n}\n\nbool\nDriver::checkCurrentRectStubShader()\n{\n   if (mCurrentDraw->primitiveType != latte::VGT_DI_PRIMITIVE_TYPE::RECTLIST) {\n      mCurrentDraw->rectStubShader = nullptr;\n      return true;\n   }\n\n   decaf_check(mCurrentDraw->vertexShader);\n\n   auto currentDesc = HashedDesc<spirv::RectStubShaderDesc> {\n      spirv::generateRectSubShaderDesc(&mCurrentDraw->vertexShader->shader) };\n\n   if (mCurrentDraw->rectStubShader &&\n       mCurrentDraw->rectStubShader->desc == currentDesc) {\n      // Already active, nothing to do.\n      return true;\n   }\n\n   auto& foundShader = mRectStubShaders[currentDesc.hash()];\n   if (foundShader) {\n      mCurrentDraw->rectStubShader = foundShader;\n      return true;\n   }\n\n   foundShader = new RectStubShaderObject();\n   foundShader->desc = currentDesc;\n\n   if (!spirv::generateRectStub(*currentDesc, &foundShader->shader)) {\n      decaf_abort(\"Failed to generate rect stub shader\");\n   }\n\n   auto module = mDevice.createShaderModule(\n      vk::ShaderModuleCreateInfo({}, foundShader->shader.binary.size() * 4,\n                                 foundShader->shader.binary.data()));\n   foundShader->module = module;\n\n   setVkObjectName(module,\n                   fmt::format(\"rstub_{}\", currentDesc->numVsExports).c_str());\n\n   mCurrentDraw->rectStubShader = foundShader;\n   return true;\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_staging.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\nnamespace vulkan\n{\n\n/*\nStaging buffers are used for performing uploads/downloads from the host GPU.\nThese buffers will only last as long as a single host command buffer does, and\nthus all uploading must be done in the context where the buffer is created, or\nwithin a retire task of that particular command buffer.\n*/\n\nStagingBuffer *\nDriver::_allocStagingBuffer(uint32_t size, StagingBufferType type)\n{\n   // Lets at least align our staging buffers to 1kb...\n   size = align_up(size, 1024);\n\n   vk::BufferUsageFlags bufferUsage;\n   VmaMemoryUsage allocUsage;\n\n   if (type == StagingBufferType::CpuToGpu) {\n      allocUsage = VMA_MEMORY_USAGE_CPU_TO_GPU;\n      bufferUsage =\n         vk::BufferUsageFlagBits::eTransferSrc |\n         vk::BufferUsageFlagBits::eTransferDst |\n         vk::BufferUsageFlagBits::eIndexBuffer |\n         vk::BufferUsageFlagBits::eStorageBuffer;\n   } else if (type == StagingBufferType::GpuToCpu) {\n      allocUsage = VMA_MEMORY_USAGE_GPU_TO_CPU;\n      bufferUsage =\n         vk::BufferUsageFlagBits::eTransferSrc |\n         vk::BufferUsageFlagBits::eTransferDst;\n   } else if (type == StagingBufferType::GpuToGpu) {\n      allocUsage = VMA_MEMORY_USAGE_GPU_ONLY;\n      bufferUsage =\n         vk::BufferUsageFlagBits::eTransferSrc |\n         vk::BufferUsageFlagBits::eTransferDst |\n         vk::BufferUsageFlagBits::eStorageBuffer;\n   } else {\n      decaf_abort(\"Unexpected staging buffer type\");\n   }\n\n   vk::BufferCreateInfo bufferDesc;\n   bufferDesc.size = size;\n   bufferDesc.usage = bufferUsage;\n   bufferDesc.sharingMode = vk::SharingMode::eExclusive;\n   bufferDesc.queueFamilyIndexCount = 0;\n   bufferDesc.pQueueFamilyIndices = nullptr;\n\n   VmaAllocationCreateInfo allocDesc = {};\n   allocDesc.usage = allocUsage;\n\n   VkBuffer buffer;\n   VmaAllocation allocation;\n   CHECK_VK_RESULT(\n      vmaCreateBuffer(mAllocator,\n                      reinterpret_cast<VkBufferCreateInfo*>(&bufferDesc),\n                      &allocDesc,\n                      &buffer,\n                      &allocation,\n                      nullptr));\n\n   static uint64_t stagingBufferIdx = 0;\n   setVkObjectName(buffer, fmt::format(\"stg_{}_{}_{}\",\n                                       stagingBufferIdx++,\n                                       static_cast<uint32_t>(type),\n                                       size).c_str());\n\n   auto sbuffer = new StagingBuffer();\n   sbuffer->type = type;\n   sbuffer->maximumSize = size;\n   sbuffer->size = 0;\n   sbuffer->activeUsage = ResourceUsage::Undefined;\n   sbuffer->buffer = buffer;\n   sbuffer->memory = allocation;\n   sbuffer->mappedPtr = nullptr;\n\n   if (type == StagingBufferType::GpuToCpu || type == StagingBufferType::CpuToGpu) {\n      CHECK_VK_RESULT(vmaMapMemory(mAllocator, sbuffer->memory, &sbuffer->mappedPtr));\n   }\n\n   return sbuffer;\n}\n\nStagingBuffer *\nDriver::getStagingBuffer(uint32_t size, StagingBufferType type)\n{\n   StagingBuffer *sbuffer = nullptr;\n\n   auto alignedSizeBit = 0u;\n   for (auto i = size >> 12; i > 0; i >>= 1, alignedSizeBit++);\n   auto alignedSize = 1 << (alignedSizeBit + 12);\n\n   if (!sbuffer) {\n      auto typeIndex = static_cast<uint32_t>(type);\n      auto &stagingBuffers = mStagingBuffers[typeIndex][alignedSizeBit];\n\n      if (!stagingBuffers.empty()) {\n         sbuffer = stagingBuffers.back();\n         stagingBuffers.pop_back();\n\n         // This is just to double-check that our algorithm is working\n         // as it is intended to be working...\n         decaf_check(sbuffer->maximumSize >= size);\n      }\n   }\n\n   if (!sbuffer) {\n      sbuffer = _allocStagingBuffer(alignedSize, type);\n      sbuffer->poolIndex = alignedSizeBit;\n   }\n\n   sbuffer->size = size;\n\n   mActiveSyncWaiter->stagingBuffers.push_back(sbuffer);\n\n   return sbuffer;\n}\n\nvoid\nDriver::retireStagingBuffer(StagingBuffer *sbuffer)\n{\n   auto typeIndex = static_cast<uint32_t>(sbuffer->type);\n   auto poolIndex = sbuffer->poolIndex;\n   mStagingBuffers[typeIndex][poolIndex].push_back(sbuffer);\n}\n\nvoid\nDriver::transitionStagingBuffer(StagingBuffer *sbuffer, ResourceUsage usage)\n{\n   // If we are already set to the correct usage, no need to do any additional\n   // work.  It is implied that a transition would need to happen for a change\n   // to have occurred to the staging buffer.\n   if (sbuffer->activeUsage == usage) {\n      return;\n   }\n\n   auto srcMeta = getResourceUsageMeta(sbuffer->activeUsage);\n   auto dstMeta = getResourceUsageMeta(usage);\n\n   vk::BufferMemoryBarrier bufferBarrier;\n   bufferBarrier.srcAccessMask = srcMeta.accessFlags;\n   bufferBarrier.dstAccessMask = dstMeta.accessFlags;\n   bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   bufferBarrier.buffer = sbuffer->buffer;\n   bufferBarrier.offset = 0;\n   bufferBarrier.size = VK_WHOLE_SIZE;\n\n   mActiveCommandBuffer.pipelineBarrier(\n      srcMeta.stageFlags,\n      dstMeta.stageFlags,\n      vk::DependencyFlags(),\n      {},\n      { bufferBarrier },\n      {});\n\n   sbuffer->activeUsage = usage;\n}\n\nvoid\nDriver::copyToStagingBuffer(StagingBuffer *sbuffer, uint32_t offset, const void *data, uint32_t size)\n{\n   decaf_check(sbuffer->type == StagingBufferType::CpuToGpu);\n\n   // Transition the buffer to allow safe writing\n   transitionStagingBuffer(sbuffer, ResourceUsage::HostWrite);\n\n   // Copy the data into the staging buffer.\n   memcpy(static_cast<uint8_t*>(sbuffer->mappedPtr) + offset, data, size);\n\n   // Flush the allocation to make the CPU write visible to the GPU.\n   vmaFlushAllocation(mAllocator, sbuffer->memory, 0, VK_WHOLE_SIZE);\n}\n\nvoid\nDriver::copyFromStagingBuffer(StagingBuffer *sbuffer, uint32_t offset, void *data, uint32_t size)\n{\n   decaf_check(sbuffer->type == StagingBufferType::GpuToCpu);\n\n   // In the case of copying FROM the staging buffer, we actually only check that the\n   // correct usage is configured.  This is because the read occurs later after the\n   // transition, when we don't have a command buffer or a sync path to transition.\n   decaf_check(sbuffer->activeUsage == ResourceUsage::HostRead);\n\n   // Invalidate the allocation to make the GPU writes visible to the CPU\n   vmaInvalidateAllocation(mAllocator, sbuffer->memory, 0, VK_WHOLE_SIZE);\n\n   // Copy the data from the staging buffer\n   memcpy(data, static_cast<uint8_t*>(sbuffer->mappedPtr) + offset, size);\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_streamout.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\nnamespace vulkan\n{\n\n// TODO: We should consolidate the management of all 3 types of\n// buffers that we create (memory cache, staging and stream out\n// counters).  This will allow us to share the logic for the\n// management of those buffers.\n\nstatic inline void\n_barrierStreamContextBuffer(vk::CommandBuffer cmdBuffer, vk::Buffer buffer,\n                            vk::PipelineStageFlags srcStage, vk::AccessFlags srcMask,\n                            vk::PipelineStageFlags dstStage, vk::AccessFlags dstMask)\n{\n   vk::BufferMemoryBarrier bufferBarrier;\n   bufferBarrier.srcAccessMask = srcMask;\n   bufferBarrier.dstAccessMask = dstMask;\n   bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   bufferBarrier.buffer = buffer;\n   bufferBarrier.offset = 0;\n   bufferBarrier.size = VK_WHOLE_SIZE;\n\n   cmdBuffer.pipelineBarrier( srcStage, dstStage, vk::DependencyFlags(), { }, { bufferBarrier }, { });\n}\n\nStreamContextObject *\nDriver::allocateStreamContext(uint32_t initialOffset)\n{\n   StreamContextObject *context = nullptr;\n\n   if (!mStreamOutContextPool.empty()) {\n      context = mStreamOutContextPool.back();\n      mStreamOutContextPool.pop_back();\n   }\n\n   if (!context) {\n      vk::BufferCreateInfo bufferDesc;\n      bufferDesc.size = 4;\n      bufferDesc.usage =\n         vk::BufferUsageFlagBits::eTransformFeedbackCounterBufferEXT |\n         vk::BufferUsageFlagBits::eTransferSrc |\n         vk::BufferUsageFlagBits::eTransferDst;\n      bufferDesc.sharingMode = vk::SharingMode::eExclusive;\n      bufferDesc.queueFamilyIndexCount = 0;\n      bufferDesc.pQueueFamilyIndices = nullptr;\n\n      VmaAllocationCreateInfo allocInfo = {};\n      allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;\n\n      VkBuffer buffer;\n      VmaAllocation allocation = nullptr;\n      vmaCreateBuffer(mAllocator,\n                      reinterpret_cast<VkBufferCreateInfo*>(&bufferDesc),\n                      &allocInfo,\n                      &buffer,\n                      &allocation,\n                      nullptr);\n\n      static uint64_t streamOutCounterIdx = 0;\n      setVkObjectName(buffer, fmt::format(\"soctr_{}\", streamOutCounterIdx++).c_str());\n\n      context = new StreamContextObject();\n      context->allocation = allocation;\n      context->buffer = buffer;\n   }\n\n   // Transition this buffer to being filled.\n   _barrierStreamContextBuffer(mActiveCommandBuffer,\n                               context->buffer,\n                               vk::PipelineStageFlagBits::eDrawIndirect,\n                               vk::AccessFlagBits::eTransformFeedbackCounterReadEXT,\n                               vk::PipelineStageFlagBits::eTransfer,\n                               vk::AccessFlagBits::eTransferWrite);\n\n   // Fill the buffer with the initial value\n   mActiveCommandBuffer.fillBuffer(context->buffer, 0, 4, initialOffset);\n\n   // Transition the stream out context buffer to the correct state for having counter\n   // data written into it.  It is expected that all contexts will always be in a state\n   // ready to receive transform feedback, and readers will switch it in and then back\n   // out of this state.\n   _barrierStreamContextBuffer(mActiveCommandBuffer,\n                               context->buffer,\n                               vk::PipelineStageFlagBits::eTransfer,\n                               vk::AccessFlagBits::eTransferWrite,\n                               vk::PipelineStageFlagBits::eDrawIndirect,\n                               vk::AccessFlagBits::eTransformFeedbackCounterReadEXT);\n\n   return context;\n}\n\nvoid\nDriver::releaseStreamContext(StreamContextObject* stream)\n{\n   mStreamOutContextPool.push_back(stream);\n}\n\nvoid\nDriver::readbackStreamContext(StreamContextObject *stream, phys_addr writeAddr)\n{\n   // Transition the stream-out context to being read for a read by the transfer sytem.\n   _barrierStreamContextBuffer(mActiveCommandBuffer,\n                               stream->buffer,\n                               vk::PipelineStageFlagBits::eTransformFeedbackEXT,\n                               vk::AccessFlagBits::eTransformFeedbackCounterWriteEXT,\n                               vk::PipelineStageFlagBits::eTransfer,\n                               vk::AccessFlagBits::eTransferRead);\n\n   // Grab the memory cache object for the destination\n   auto memCache = getDataMemCache(writeAddr, 4);\n\n   // Transition the cache to being a write target, this will cause it to write-back\n   // to the GPU whenever this Pm4 Context is completed (or if its needed).\n   transitionMemCache(memCache, ResourceUsage::StreamOutCounterWrite);\n\n   // Copy the pointer from our local stream-out buffers into the user supplied region\n   vk::BufferCopy copyRegion(0, 0, 4);\n   mActiveCommandBuffer.copyBuffer(stream->buffer, memCache->buffer, { copyRegion });\n\n   // Return the stream out context to its normal feedback counter write state to\n   // enable it to continue to be used by future stream-out operations.\n   _barrierStreamContextBuffer(mActiveCommandBuffer,\n                               stream->buffer,\n                               vk::PipelineStageFlagBits::eTransfer,\n                               vk::AccessFlagBits::eTransferRead,\n                               vk::PipelineStageFlagBits::eTransformFeedbackEXT,\n                               vk::AccessFlagBits::eTransformFeedbackCounterWriteEXT);\n}\n\nStreamOutBufferDesc\nDriver::getStreamOutBufferDesc(uint32_t bufferIndex)\n{\n   // If streamout is disabled, then there is no buffer here.\n   auto vgt_strmout_en = getRegister<latte::VGT_STRMOUT_EN>(latte::Register::VGT_STRMOUT_EN);\n   if (!vgt_strmout_en.STREAMOUT()) {\n      return StreamOutBufferDesc();\n   }\n\n   StreamOutBufferDesc desc;\n\n   auto vgt_strmout_buffer_base = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_BUFFER_BASE_0 + 16 * bufferIndex);\n   auto vgt_strmout_buffer_offset = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_BUFFER_OFFSET_0 + 16 * bufferIndex);\n   auto vgt_strmout_buffer_size = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_BUFFER_SIZE_0 + 16 * bufferIndex);\n   auto vgt_strmout_vtx_stride = getRegister<uint32_t>(latte::Register::VGT_STRMOUT_VTX_STRIDE_0 + 16 * bufferIndex);\n\n   decaf_check(vgt_strmout_buffer_offset == 0);\n\n   desc.baseAddress = phys_addr(vgt_strmout_buffer_base << 8);\n   desc.size = vgt_strmout_buffer_size << 2;\n   desc.stride = vgt_strmout_vtx_stride << 2;\n\n   return desc;\n}\n\nbool\nDriver::checkCurrentStreamOut()\n{\n   if (!mCurrentDraw->streamOutEnabled) {\n      return true;\n   }\n\n   for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) {\n      auto currentDesc = getStreamOutBufferDesc(i);\n\n      if (!currentDesc.baseAddress) {\n         mCurrentDraw->streamOutBuffers[i] = nullptr;\n         continue;\n      }\n\n      // Fetch the memory cache for this buffer\n      auto memCache = getDataMemCache(currentDesc.baseAddress, currentDesc.size);\n\n      // Transition the buffer to being a stream out buffer.  This will cause it to be\n      // automatically invalidated later on.\n      transitionMemCache(memCache, ResourceUsage::StreamOutBuffer);\n\n      mCurrentDraw->streamOutBuffers[i] = memCache;\n   }\n\n   return true;\n}\n\nvoid\nDriver::bindStreamOutBuffers()\n{\n   for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) {\n      auto& buffer = mCurrentDraw->streamOutBuffers[i];\n      if (!buffer) {\n         continue;\n      }\n\n      mActiveCommandBuffer.bindTransformFeedbackBuffersEXT(i, { buffer->buffer }, { 0 }, { buffer->size }, mVkDynLoader);\n   }\n}\n\nvoid\nDriver::beginStreamOut()\n{\n   std::array<vk::Buffer, latte::MaxStreamOutBuffers> buffers = { vk::Buffer{} };\n   std::array<vk::DeviceSize, latte::MaxStreamOutBuffers> offsets = { 0 };\n   for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) {\n      auto ctxData = mCurrentDraw->streamOutContext[i];\n      if (ctxData) {\n         buffers[i] = ctxData->buffer;\n      }\n   }\n\n   mActiveCommandBuffer.beginTransformFeedbackEXT(0, buffers, offsets, mVkDynLoader);\n}\n\nvoid\nDriver::endStreamOut()\n{\n   std::array<vk::Buffer, latte::MaxStreamOutBuffers> buffers = { vk::Buffer{} };\n   std::array<vk::DeviceSize, latte::MaxStreamOutBuffers> offsets = { 0 };\n   for (auto i = 0; i < latte::MaxStreamOutBuffers; ++i) {\n      auto ctxData = mCurrentDraw->streamOutContext[i];\n      if (ctxData) {\n         buffers[i] = ctxData->buffer;\n      }\n   }\n\n   mActiveCommandBuffer.endTransformFeedbackEXT(0, buffers, offsets, mVkDynLoader);\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_surface.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"vulkan_utils.h\"\n\n#include \"latte/latte_formats.h\"\n#include <common/rangecombiner.h>\n\nnamespace vulkan\n{\n\nstatic inline std::string\n_makeSurfaceDescStr(const SurfaceDesc& info)\n{\n   static const char *DIM_NAMES[] = {\n      \"1d\", \"2d\", \"3d\", \"cube\", \"1darr\", \"2darr\", \"2daa\", \"2daaarr\" };\n\n   return fmt::format(\"{:08x}_{:x}_{}_{:x}_{:x}_{:x}_{}x{}x{}x{}#{}\",\n                      info.calcAlignedBaseAddress(),\n                      info.calcSwizzle(),\n                      DIM_NAMES[info.dim],\n                      info.format,\n                      info.tileType,\n                      info.tileMode,\n                      info.pitch,\n                      info.width,\n                      info.height,\n                      info.depth,\n                      info.samples);\n}\n\nstatic inline std::string\n_makeSurfaceName(const SurfaceDesc& info)\n{\n   static uint64_t surfImgIndex = 0;\n   return fmt::format(\"surf_{}:{}\",\n                      surfImgIndex++,\n                      _makeSurfaceDescStr(info));\n}\n\nstatic inline std::string\n_makeSurfaceViewName(const SurfaceViewDesc& info)\n{\n   static const char *COMP_NAMES[] = {\n      \"x\", \"y\", \"z\", \"w\", \"0\", \"1\", \"_\" };\n\n   static uint64_t imageViewIndex = 0;\n   return fmt::format(\"sview_{}:{}:{}{}{}{}:{}-{}\",\n                      imageViewIndex++,\n                      _makeSurfaceDescStr(info.surfaceDesc),\n                      COMP_NAMES[info.channels[0]],\n                      COMP_NAMES[info.channels[1]],\n                      COMP_NAMES[info.channels[2]],\n                      COMP_NAMES[info.channels[3]],\n                      info.sliceStart,\n                      info.sliceEnd);\n}\n\nstatic inline SectionRange\n_sliceRangeToSectionRange(const SurfaceSubRange& range)\n{\n   // Because of the way our memory cache retiler works, we can guarentee\n   // that our slices are mapped 1:1 into sections.\n   return { range.firstSlice, range.numSlices };\n}\n\nstatic inline uint32_t\n_unthickenedSliceSize(const gpu7::tiling::SurfaceInfo &info)\n{\n   return info.pitch * info.height * info.bpp / 8;\n}\n\nstatic inline std::pair<uint32_t, uint32_t>\n_sliceRangeToMemRange(SurfaceObject *surface, const SurfaceSubRange& range)\n{\n   auto sliceSize = _unthickenedSliceSize(surface->tilingInfo);\n   auto slicesOffset = range.firstSlice * sliceSize;\n   auto slicesSize = range.numSlices * sliceSize;\n\n   return { slicesOffset, slicesSize };\n}\n\nstatic inline gpu7::tiling::SurfaceDescription\n_getTilingSurfaceDesc(const SurfaceDesc &info)\n{\n   auto swizzle = info.calcSwizzle();\n   auto dataFormat = getSurfaceFormatDataFormat(info.format);\n   auto bpp = latte::getDataFormatBitsPerElement(dataFormat);\n\n   /*\n   AddrTileMode tileMode;\n   AddrFormat format;\n   uint32_t bpp;\n   uint32_t numSamples;\n   uint32_t width;\n   uint32_t height;\n   uint32_t numSlices;\n   ADDR_SURFACE_FLAGS flags;\n   uint32_t numFrags;\n   uint32_t numLevels;\n   uint32_t bankSwizzle;\n   uint32_t pipeSwizzle;\n\n   */\n\n   gpu7::tiling::SurfaceDescription tilingDesc;\n   tilingDesc.tileMode = static_cast<gpu7::tiling::TileMode>(info.tileMode);\n   tilingDesc.format = static_cast<gpu7::tiling::DataFormat>(dataFormat);\n   tilingDesc.bpp = bpp;\n   tilingDesc.numSamples = 1;\n   tilingDesc.width = info.pitch;\n   tilingDesc.height = info.height;\n   tilingDesc.numSlices = info.depth;\n   tilingDesc.numFrags = 0;\n   tilingDesc.numLevels = 1;\n   tilingDesc.pipeSwizzle = (swizzle >> 8) & 1;\n   tilingDesc.bankSwizzle = (swizzle >> 9) & 3;\n   tilingDesc.dim = static_cast<gpu7::tiling::SurfaceDim>(info.dim);\n\n   if (info.tileType == latte::SQ_TILE_TYPE::DEPTH) {\n      tilingDesc.use |= gpu7::tiling::SurfaceUse::DepthBuffer;\n   }\n   /*\n   if (dataFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && dataFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      tilingDesc.width = (tilingDesc.width + 3) / 4;\n      tilingDesc.height = (tilingDesc.height + 3) / 4;\n   }\n   */\n\n   return tilingDesc;\n}\n\nMemCacheObject *\nDriver::_getSurfaceMemCache(const SurfaceDesc &info, const gpu7::tiling::SurfaceInfo& tilingInfo)\n{\n   auto realAddr = info.calcAlignedBaseAddress();\n\n   auto sliceSize = _unthickenedSliceSize(tilingInfo);\n   auto alignedDepth = tilingInfo.depth;\n\n   // Grab a memory cache object for this image\n   return getMemCache(phys_addr(realAddr), alignedDepth, sliceSize);\n}\n\nvoid\nDriver::_copySurface(SurfaceObject *dst, SurfaceObject *src, SurfaceSubRange range)\n{\n   // TODO: Add support for copying 1D Array's here...\n   decaf_check(src->desc.dim != latte::SQ_TEX_DIM::DIM_1D_ARRAY);\n   decaf_check(dst->desc.dim != latte::SQ_TEX_DIM::DIM_1D_ARRAY);\n\n   // TODO: Add support for copying depth buffers here...\n   decaf_check(dst->desc.tileType == src->desc.tileType);\n   //decaf_check(dst->desc.tileType == latte::SQ_TILE_TYPE::DEFAULT);\n\n   auto copyWidth = std::min(dst->width, src->width);\n   auto copyHeight = std::min(dst->height, src->height);\n\n   auto srcSlices = src->arrayLayers;\n   if (src->desc.dim == latte::SQ_TEX_DIM::DIM_3D) {\n      decaf_check(src->arrayLayers == 1);\n      srcSlices = src->depth;\n   }\n\n   auto dstSlices = dst->arrayLayers;\n   if (dst->desc.dim == latte::SQ_TEX_DIM::DIM_3D) {\n      decaf_check(dst->arrayLayers == 1);\n      dstSlices = dst->depth;\n   }\n\n   auto copySlices = std::min(srcSlices, dstSlices);\n\n   if (range.firstSlice >= copySlices) {\n      // We cannot perform any work, since the requested slice start\n      // is beyond the end of one of the surfaces!\n      return;\n   }\n\n   if (range.firstSlice + range.numSlices > copySlices) {\n      // If the requested end is beyond the size of one of the surfaces,\n      // lets shrink the range to only cover the available layers.\n      range.numSlices = copySlices - range.firstSlice;\n   }\n\n   auto copyAspect = vk::ImageAspectFlags();\n   auto formatUsage = getVkSurfaceFormatUsage(src->desc.format);\n   if (src->desc.tileType != latte::SQ_TILE_TYPE::DEPTH) {\n      if (formatUsage & (SurfaceFormatUsage::TEXTURE | SurfaceFormatUsage::COLOR)) {\n         copyAspect |= vk::ImageAspectFlagBits::eColor;\n      }\n   } else {\n      if (formatUsage & SurfaceFormatUsage::DEPTH) {\n         copyAspect |= vk::ImageAspectFlagBits::eDepth;\n      }\n      if (formatUsage & SurfaceFormatUsage::STENCIL) {\n         copyAspect |= vk::ImageAspectFlagBits::eStencil;\n      }\n   }\n\n   vk::ImageCopy copyRegion;\n   if (src->desc.dim == latte::SQ_TEX_DIM::DIM_3D) {\n      copyRegion.srcOffset = vk::Offset3D { 0, 0, static_cast<int32_t>(range.firstSlice) };\n      copyRegion.srcSubresource = { copyAspect, 0, 0, 1 };\n      // range.numSlices is expressed by the extent here...\n   } else {\n      copyRegion.srcOffset = vk::Offset3D { 0, 0, 0 };\n      copyRegion.srcSubresource = { copyAspect, 0, range.firstSlice, range.numSlices };\n   }\n   if (dst->desc.dim == latte::SQ_TEX_DIM::DIM_3D) {\n      copyRegion.dstOffset = vk::Offset3D { 0, 0, static_cast<int32_t>(range.firstSlice) };\n      copyRegion.dstSubresource = { copyAspect, 0, 0, 1 };\n      // range.numSlices is expressed by the extent here...\n   } else {\n      copyRegion.dstOffset = vk::Offset3D { 0, 0, 0 };\n      copyRegion.dstSubresource = { copyAspect, 0, range.firstSlice, range.numSlices };\n\n      // range.numSlices is expressed by the extent here...\n   }\n   copyRegion.extent = vk::Extent3D { copyWidth, copyHeight, copySlices };\n\n   auto originalSrcUsage = src->activeUsage;\n\n   _barrierSurface(src, ResourceUsage::TransferSrc, vk::ImageLayout::eTransferSrcOptimal, range);\n   _barrierSurface(dst, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal, range);\n\n   mActiveCommandBuffer.copyImage(\n      src->image,\n      vk::ImageLayout::eTransferSrcOptimal,\n      dst->image,\n      vk::ImageLayout::eTransferDstOptimal,\n      { copyRegion });\n\n   // We don't know what the source was doing before, so we need to return\n   // it back to it's original usage/layout in case its in use.\n   if (originalSrcUsage != ResourceUsage::Undefined) {\n      auto originalSrcLayout = getResourceUsageMeta(originalSrcUsage).imageLayout;\n      _barrierSurface(src, originalSrcUsage, originalSrcLayout, range);\n   }\n}\n\nSurfaceGroupObject *\nDriver::_allocateSurfaceGroup(const SurfaceDesc& info)\n{\n   auto surfaceGroup = new SurfaceGroupObject();\n   surfaceGroup->desc = info;\n   return surfaceGroup;\n}\n\nvoid\nDriver::_releaseSurfaceGroup(SurfaceGroupObject *surfaceGroup)\n{\n   decaf_check(surfaceGroup->surfaces.empty());\n   delete surfaceGroup;\n}\n\nvoid\nDriver::_addSurfaceGroupSurface(SurfaceGroupObject *surfaceGroup, SurfaceObject *surface)\n{\n   surfaceGroup->surfaces.push_back(surface);\n\n   auto sliceCount = surface->slices.size();\n   while (surfaceGroup->sliceOwners.size() < sliceCount) {\n      surfaceGroup->sliceOwners.push_back(nullptr);\n   }\n}\n\nvoid\nDriver::_removeSurfaceGroupSurface(SurfaceGroupObject *surfaceGroup, SurfaceObject *surface)\n{\n   for (auto& sliceOwner : surfaceGroup->sliceOwners) {\n      if (sliceOwner == surface) {\n         sliceOwner = nullptr;\n      }\n   }\n\n   surfaceGroup->surfaces.remove(surface);\n}\n\nvoid\nDriver::_updateSurfaceGroupSlice(SurfaceGroupObject *surfaceGroup, uint32_t sliceId, SurfaceObject *surface)\n{\n   decaf_check(sliceId < surfaceGroup->sliceOwners.size());\n   surfaceGroup->sliceOwners[sliceId] = surface;\n}\n\nSurfaceObject *\nDriver::_getSurfaceGroupOwner(SurfaceGroupObject *surfaceGroup, uint32_t sliceId, uint64_t minChangeIndex)\n{\n   decaf_check(sliceId < surfaceGroup->sliceOwners.size());\n   auto& sliceOwner = surfaceGroup->sliceOwners[sliceId];\n   if (!sliceOwner) {\n      return nullptr;\n   }\n\n   if (sliceOwner->slices[sliceId].lastChangeIndex < minChangeIndex) {\n      return nullptr;\n   }\n\n   return sliceOwner;\n}\n\nSurfaceGroupObject *\nDriver::_getSurfaceGroup(const SurfaceDesc& info)\n{\n   auto &surface = mSurfaceGroups[info.hash(true)];\n   if (!surface) {\n      surface = _allocateSurfaceGroup(info);\n   }\n\n   return surface;\n}\n\nSurfaceObject *\nDriver::_allocateSurface(const SurfaceDesc& info)\n{\n   decaf_check(info.baseAddress);\n   decaf_check(info.width);\n   decaf_check(info.height);\n   decaf_check(info.depth);\n   decaf_check(info.width <= 8192);\n   decaf_check(info.height <= 8192);\n\n   auto hostFormat = getVkSurfaceFormat(info.format, info.tileType);\n   auto formatUsage = getVkSurfaceFormatUsage(info.format);\n\n   vk::ImageAspectFlags aspectFlags;\n   if (info.tileType != latte::SQ_TILE_TYPE::DEPTH) {\n      if (formatUsage & (SurfaceFormatUsage::TEXTURE | SurfaceFormatUsage::COLOR)) {\n         aspectFlags |= vk::ImageAspectFlagBits::eColor;\n      }\n   } else {\n      if (formatUsage & SurfaceFormatUsage::DEPTH) {\n         aspectFlags |= vk::ImageAspectFlagBits::eDepth;\n      }\n      if (formatUsage & SurfaceFormatUsage::STENCIL) {\n         aspectFlags |= vk::ImageAspectFlagBits::eStencil;\n      }\n   }\n\n   vk::ImageUsageFlags usageFlags;\n   usageFlags |= vk::ImageUsageFlagBits::eTransferDst;\n   usageFlags |= vk::ImageUsageFlagBits::eTransferSrc;\n   if (formatUsage & SurfaceFormatUsage::TEXTURE) {\n      usageFlags |= vk::ImageUsageFlagBits::eSampled;\n   }\n   if (info.tileType != latte::SQ_TILE_TYPE::DEPTH) {\n      if (formatUsage & SurfaceFormatUsage::COLOR) {\n         usageFlags |= vk::ImageUsageFlagBits::eColorAttachment;\n      }\n   } else {\n      if (formatUsage & (SurfaceFormatUsage::DEPTH | SurfaceFormatUsage::STENCIL)) {\n         usageFlags |= vk::ImageUsageFlagBits::eDepthStencilAttachment;\n      }\n   }\n\n   vk::ImageType imageType;\n   auto realPitch = 1u;\n   auto realWidth = 1u;\n   auto realHeight = 1u;\n   auto realDepth = 1u;\n   auto realArrayLayers = 1u;\n\n   switch (info.dim) {\n   case latte::SQ_TEX_DIM::DIM_1D:\n      imageType = vk::ImageType::e1D;\n      realPitch = info.pitch;\n      realWidth = info.width;\n      realHeight = 1;\n      realDepth = 1;\n      realArrayLayers = 1;\n      break;\n   case latte::SQ_TEX_DIM::DIM_2D:\n   case latte::SQ_TEX_DIM::DIM_2D_MSAA:\n      imageType = vk::ImageType::e2D;\n      realPitch = info.pitch;\n      realWidth = info.width;\n      realHeight = info.height;\n      realDepth = 1;\n      realArrayLayers = 1;\n      break;\n   case latte::SQ_TEX_DIM::DIM_3D:\n      imageType = vk::ImageType::e3D;\n      realPitch = info.pitch;\n      realWidth = info.width;\n      realHeight = info.height;\n      realDepth = info.depth;\n      realArrayLayers = 1;\n      break;\n   case latte::SQ_TEX_DIM::DIM_CUBEMAP:\n      imageType = vk::ImageType::e2D;\n      realPitch = info.pitch;\n      realWidth = info.width;\n      realHeight = info.height;\n      realDepth = 1;\n      realArrayLayers = info.depth;\n      break;\n   case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n      imageType = vk::ImageType::e1D;\n      realPitch = info.pitch;\n      realWidth = info.width;\n      realHeight = 1;\n      realDepth = 1;\n      realArrayLayers = info.height;\n      break;\n   case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n   case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n      imageType = vk::ImageType::e2D;\n      realPitch = info.pitch;\n      realWidth = info.width;\n      realHeight = info.height;\n      realDepth = 1;\n      realArrayLayers = info.depth;\n      break;\n   default:\n      decaf_abort(fmt::format(\"Failed to pick vulkan dim for latte dim {}\", info.dim));\n   }\n\n   auto dataFormat = getSurfaceFormatDataFormat(info.format);\n\n   auto texelPitch = info.pitch;\n   auto texelWidth = info.width;\n   auto texelHeight = info.height;\n\n   if (dataFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && dataFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      // Block compressed textures are tiled/untiled in terms of blocks\n      texelPitch = texelPitch / 4;\n      texelWidth = (texelWidth + 3) / 4;\n      texelHeight = (texelHeight + 3) / 4;\n\n      // We need to make sure to round the sizes up appropriately\n      realWidth = align_up(realWidth, 4);\n      realHeight = align_up(realHeight, 4);\n   }\n\n   vk::ImageCreateInfo createImageDesc;\n   createImageDesc.imageType = imageType;\n   createImageDesc.format = hostFormat;\n   createImageDesc.extent = vk::Extent3D(realWidth, realHeight, realDepth);\n   createImageDesc.mipLevels = 1;\n   createImageDesc.arrayLayers = realArrayLayers;\n   createImageDesc.samples = vk::SampleCountFlagBits::e1;\n   createImageDesc.tiling = vk::ImageTiling::eOptimal;\n   createImageDesc.usage = usageFlags;\n   createImageDesc.sharingMode = vk::SharingMode::eExclusive;\n   createImageDesc.initialLayout = vk::ImageLayout::eUndefined;\n   auto image = mDevice.createImage(createImageDesc);\n\n   setVkObjectName(image, _makeSurfaceName(info).c_str());\n\n   auto imageMemReqs = mDevice.getImageMemoryRequirements(image);\n\n   vk::MemoryAllocateInfo allocDesc;\n   allocDesc.allocationSize = imageMemReqs.size;\n   allocDesc.memoryTypeIndex = findMemoryType(imageMemReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal);\n   auto imageMem = mDevice.allocateMemory(allocDesc);\n\n   mDevice.bindImageMemory(image, imageMem, 0);\n\n   vk::ImageSubresourceRange subresRange;\n   subresRange.aspectMask = aspectFlags;\n   subresRange.baseMipLevel = 0;\n   subresRange.levelCount = 1;\n   subresRange.baseArrayLayer = 0;\n   subresRange.layerCount = realArrayLayers;\n\n   auto tilingDesc = _getTilingSurfaceDesc(info);\n   auto tilingInfo = gpu7::tiling::computeSurfaceInfo(tilingDesc, 0);\n\n   // Grab a reference to the memory cache that backs this surface\n   auto memCache = _getSurfaceMemCache(info, tilingInfo);\n\n   // TODO: Maybe join together the getSurfaceMemCache code and this?\n   // Generate some meta-data about how we copy in/out\n   auto alignedPitch = tilingInfo.pitch;\n   auto alignedHeight = tilingInfo.height;\n   if (dataFormat >= latte::SQ_DATA_FORMAT::FMT_BC1 && dataFormat <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n      alignedPitch *= 4;\n      alignedHeight *= 4;\n   }\n\n   vk::BufferImageCopy bufferRegion = {};\n   bufferRegion.bufferOffset = 0;\n   bufferRegion.bufferRowLength = alignedPitch;\n   bufferRegion.bufferImageHeight = alignedHeight;\n\n   bufferRegion.imageSubresource.aspectMask = subresRange.aspectMask;\n   bufferRegion.imageSubresource.mipLevel = 0;\n   bufferRegion.imageSubresource.baseArrayLayer = 0;\n   bufferRegion.imageSubresource.layerCount = realArrayLayers;\n\n   bufferRegion.imageOffset = vk::Offset3D { 0, 0, 0 };\n   bufferRegion.imageExtent = vk::Extent3D { static_cast<uint32_t>(realWidth),\n                                             static_cast<uint32_t>(realHeight),\n                                             static_cast<uint32_t>(realDepth) };\n\n   std::vector<SurfaceSlice> slices;\n\n   if (info.dim == latte::SQ_TEX_DIM::DIM_3D) {\n      // In the case of a 3D texture, we have to have a slice per DIM.  This\n      // is the only way to enable us to render to the surface appropriately.\n      decaf_check(realArrayLayers == 1);\n      for (auto i = 0u; i < info.depth; ++i) {\n         SurfaceSlice slice;\n         slice.lastChangeIndex = 0;\n         slices.push_back(slice);\n      }\n   } else {\n      for (auto i = 0u; i < realArrayLayers; ++i) {\n         SurfaceSlice slice;\n         slice.lastChangeIndex = 0;\n         slices.push_back(slice);\n      }\n   }\n\n   auto surfaceGroup = _getSurfaceGroup(info);\n\n   // Return our freshly minted surface data object\n   auto surface = new SurfaceObject();\n   surface->desc = info;\n   surface->image = image;\n   surface->imageMem = imageMem;\n   surface->pitch = realPitch;\n   surface->width = realWidth;\n   surface->height = realHeight;\n   surface->depth = realDepth;\n   surface->arrayLayers = realArrayLayers;\n   surface->tilingDesc = tilingDesc;\n   surface->tilingInfo = tilingInfo;\n   surface->slices = std::move(slices);\n   surface->memCache = memCache;\n   surface->subresRange = subresRange;\n   surface->bufferRegion = bufferRegion;\n   surface->activeUsage = ResourceUsage::Undefined;\n   surface->lastUsageIndex = mActiveBatchIndex;\n   surface->group = surfaceGroup;\n\n   _addSurfaceGroupSurface(surfaceGroup, surface);\n\n   return surface;\n}\n\nvoid\nDriver::_releaseSurface(SurfaceObject *surface)\n{\n   // TODO: Add support for releasing surface data...\n\n   delete surface;\n}\n\nvoid\nDriver::_upgradeSurface(SurfaceObject *surface, const SurfaceDesc &info)\n{\n   // Allocate the new surface\n   auto newSurface = _allocateSurface(info);\n\n   // Verify that we are not making any errors\n   // TODO: Reenable this check with support for array upgrades.\n   //decaf_check(newSurface->desc.dim == surface->desc.dim);\n   decaf_check(newSurface->desc.tileType == surface->desc.tileType);\n   decaf_check(newSurface->desc.tileMode == surface->desc.tileMode);\n   decaf_check(newSurface->desc.format == surface->desc.format);\n   decaf_check(newSurface->width == surface->width);\n   decaf_check(newSurface->height == surface->height);\n   decaf_check(newSurface->depth == surface->depth);\n   decaf_check(newSurface->arrayLayers > surface->arrayLayers);\n\n   _copySurface(newSurface, surface, { 0, surface->arrayLayers });\n\n   newSurface->lastUsageIndex = surface->lastUsageIndex;\n   for (auto i = 0u; i < surface->slices.size(); ++i) {\n      newSurface->slices[i] = surface->slices[i];\n   }\n\n   // Switch out the surfaces from the map\n   auto switchIter = mSurfaces.find(info.hash());\n   decaf_check(switchIter->second == surface);\n   std::swap(*switchIter->second, *newSurface);\n\n   // Remove the surface from the group (it was automatically added)\n   _removeSurfaceGroupSurface(surface->group, newSurface);\n\n   // Release the surface on the next frame (an earlier reference to this surface\n   // might have bound it to Vulkan, so we need to wait).\n   addRetireTask([=](){\n      _releaseSurface(newSurface);\n   });\n}\n\nvoid\nDriver::_readSurfaceData(SurfaceObject *surface, SurfaceSubRange range)\n{\n   auto& memCache = surface->memCache;\n   auto memRange = _sliceRangeToMemRange(surface, range);\n   auto sectionRange = _sliceRangeToSectionRange(range);\n\n   auto untiledOffset = memRange.first;\n   auto untiledBuffer = memCache->buffer;\n\n   auto retileInfo = gpu7::tiling::computeRetileInfo(surface->tilingInfo);\n   if (retileInfo.isTiled) {\n      // Lets just double-check everyone is in agreement...\n      // TODO: These wont match due to tile thickness needing to be aligned!\n      //decaf_check(memRange.first == retileInfo.sliceOffset);\n\n      // Calculate our retiling buffer size.\n      auto retileSize = retileInfo.thinSliceBytes * range.numSlices;\n\n      // Check that we are aligned based on our thickness.  This is critical to\n      // ensure that we correctly invalidate the regions touched by the retiler.\n      decaf_check(range.firstSlice % retileInfo.microTileThickness == 0);\n      decaf_check(range.numSlices % retileInfo.microTileThickness == 0);\n      //retileSize /= retileInfo.microTileThickness;\n\n      // Grab a staging buffer to write into before the image read\n      auto retileStaging = getStagingBuffer(retileSize, StagingBufferType::GpuToGpu);\n\n      // Calculate the real offset into our tiled data, the GPU retiler needs a buffer\n      // offset that points directly to the slice.\n      auto tiledOffset = range.firstSlice * retileInfo.thinSliceBytes;\n      auto tiledBuffer = untiledBuffer;\n\n      // Remap the untiled surface to our staging buffer\n      untiledOffset = 0;\n      untiledBuffer = retileStaging->buffer;\n\n      _barrierMemCache(surface->memCache, ResourceUsage::ComputeSsboRead, sectionRange);\n\n      dispatchGpuUntile(retileInfo,\n                        mActiveCommandBuffer,\n                        untiledBuffer, untiledOffset,\n                        tiledBuffer, tiledOffset,\n                        range.firstSlice, range.numSlices);\n\n      _barrierMemCache(surface->memCache, ResourceUsage::TransferSrc, sectionRange);\n   } else {\n      // We will directly read from the memory cache, since this is a linear surface\n      _barrierMemCache(surface->memCache, ResourceUsage::TransferSrc, sectionRange);\n   }\n\n   // Actually load the surface\n\n   auto region = surface->bufferRegion;\n   region.bufferOffset = untiledOffset;\n   if (surface->desc.dim == latte::SQ_TEX_DIM::DIM_3D) {\n      region.imageOffset.z = range.firstSlice;\n      region.imageExtent.depth = range.numSlices;\n   } else {\n      region.imageSubresource.baseArrayLayer = range.firstSlice;\n      region.imageSubresource.layerCount = range.numSlices;\n   }\n\n   _barrierSurface(surface, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal, range);\n\n   // TODO: Improve how we handle subresources everywhere.\n   bool hasDepth = !!(region.imageSubresource.aspectMask & vk::ImageAspectFlagBits::eDepth);\n   bool hasStencil = !!(region.imageSubresource.aspectMask & vk::ImageAspectFlagBits::eStencil);\n   if (!(hasDepth & hasStencil)) {\n      mActiveCommandBuffer.copyBufferToImage(\n         untiledBuffer,\n         surface->image,\n         vk::ImageLayout::eTransferDstOptimal,\n         { region });\n   } else {\n      auto region2 = region;\n      region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth;\n      region2.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil;\n\n      mActiveCommandBuffer.copyBufferToImage(\n         untiledBuffer,\n         surface->image,\n         vk::ImageLayout::eTransferDstOptimal,\n         { region, region2 });\n   }\n}\n\nvoid\nDriver::_writeSurfaceData(SurfaceObject *surface, SurfaceSubRange range)\n{\n   auto& memCache = surface->memCache;\n   auto memRange = _sliceRangeToMemRange(surface, range);\n   auto sectionRange = _sliceRangeToSectionRange(range);\n\n   auto untiledOffset = memRange.first;\n   auto untiledBuffer = memCache->buffer;\n\n   auto retileInfo = gpu7::tiling::computeRetileInfo(surface->tilingInfo);\n   if (retileInfo.isTiled) {\n      // Calculate the retiling buffer size\n      auto retileSize = retileInfo.thinSliceBytes * range.numSlices;\n\n      // Check that we are aligned based on our thickness.  This is critical to\n      // ensure that we correctly invalidate the regions touched by the retiler.\n      decaf_check(range.firstSlice % retileInfo.microTileThickness == 0);\n      decaf_check(range.numSlices % retileInfo.microTileThickness == 0);\n      //retileSize /= retileInfo.microTileThickness;\n\n      // Grab our buffer used for retiling\n      auto retileStaging = getStagingBuffer(retileSize, StagingBufferType::GpuToGpu);\n\n      untiledOffset = 0;\n      untiledBuffer = retileStaging->buffer;\n   } else {\n      // Write directly to the surface.\n      _barrierMemCache(surface->memCache, ResourceUsage::TransferDst, sectionRange);\n   }\n\n   auto region = surface->bufferRegion;\n   region.bufferOffset = untiledOffset;\n   if (surface->desc.dim == latte::SQ_TEX_DIM::DIM_3D) {\n      region.imageOffset.z = range.firstSlice;\n      region.imageExtent.depth = range.numSlices;\n   } else {\n      region.imageSubresource.baseArrayLayer = range.firstSlice;\n      region.imageSubresource.layerCount = range.numSlices;\n   }\n\n   _barrierSurface(surface, ResourceUsage::TransferSrc, vk::ImageLayout::eTransferSrcOptimal, range);\n\n   // TODO: Improve how we handle subresources everywhere.\n   bool hasDepth = !!(region.imageSubresource.aspectMask & vk::ImageAspectFlagBits::eDepth);\n   bool hasStencil = !!(region.imageSubresource.aspectMask & vk::ImageAspectFlagBits::eStencil);\n   if (!(hasDepth & hasStencil)) {\n      mActiveCommandBuffer.copyImageToBuffer(\n         surface->image,\n         vk::ImageLayout::eTransferSrcOptimal,\n         untiledBuffer,\n         { region });\n   } else {\n      auto region2 = region;\n      region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth;\n      region2.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil;\n\n      mActiveCommandBuffer.copyImageToBuffer(\n         surface->image,\n         vk::ImageLayout::eTransferSrcOptimal,\n         untiledBuffer,\n         { region, region2 });\n   }\n\n   if (retileInfo.isTiled) {\n      _barrierMemCache(surface->memCache, ResourceUsage::ComputeSsboWrite, sectionRange);\n\n      auto tiledBuffer = memCache->buffer;\n      auto tiledOffset = range.firstSlice * retileInfo.thinSliceBytes;\n\n      dispatchGpuTile(retileInfo,\n                      mActiveCommandBuffer,\n                      tiledBuffer, tiledOffset,\n                      untiledBuffer, untiledOffset,\n                      range.firstSlice, range.numSlices);\n   }\n}\n\nvoid\nDriver::_refreshSurface(SurfaceObject *surface, SurfaceSubRange range)\n{\n   auto sectionRange = _sliceRangeToSectionRange(range);\n   auto& memCache = surface->memCache;\n\n   // We manually call refresh here, as in most cases a barrier on the memory\n   // and a full data load will be unneccessary.\n   surface->memCache->lastUsageIndex = surface->lastUsageIndex;\n   _refreshMemCache_Check(memCache, sectionRange);\n\n   auto readCombiner = makeRangeCombiner<SurfaceObject*, uint32_t, uint32_t>(\n   [&](SurfaceObject* object, uint32_t start, uint32_t count){\n      _refreshMemCache_Update(memCache, { start, count });\n      _readSurfaceData(surface, { start, count });\n   });\n\n   auto blitCombiner = makeRangeCombiner<SurfaceObject*, uint32_t, uint32_t>(\n   [&](SurfaceObject* object, uint32_t start, uint32_t count){\n      _copySurface(surface, object, { start, count });\n   });\n\n   for (auto i = sectionRange.start; i < sectionRange.start + sectionRange.count; ++i) {\n      auto latestChangeIndex = memCache->sections[i].wantedChangeIndex;\n\n      if (surface->slices[i].lastChangeIndex >= latestChangeIndex) {\n         continue;\n      }\n\n      auto localOwner = _getSurfaceGroupOwner(surface->group, i, latestChangeIndex);\n      if (localOwner) {\n         decaf_check(!memCache->sections[i].needsUpload);\n         blitCombiner.push(localOwner, i, 1);\n      } else {\n         readCombiner.push(nullptr, i, 1);\n      }\n\n      surface->slices[i].lastChangeIndex = latestChangeIndex;\n   }\n\n   readCombiner.flush();\n   blitCombiner.flush();\n}\n\nvoid\nDriver::_invalidateSurface(SurfaceObject *surface, SurfaceSubRange range)\n{\n   auto& memCache = surface->memCache;\n\n   auto memRange = _sliceRangeToMemRange(surface, range);\n\n   // Mark the memory cache as delayed invalidated, and perform the write\n   // if the memory cache ends up requesting it (sometimes its cancelled).\n   invalidateMemCacheDelayed(memCache, memRange.first, memRange.second, [=](){\n      // We need to be careful to restore the surface layout after we are\n      // done, as this async download could happen mid-draw.\n      auto oldUsage = surface->activeUsage;\n\n      _writeSurfaceData(surface, range);\n\n      auto oldLayout = getResourceUsageMeta(oldUsage).imageLayout;\n      _barrierSurface(surface, oldUsage, oldLayout, range);\n   });\n\n   // Update our last change index to match the data we wrote\n   auto sectionRange = _sliceRangeToSectionRange(range);\n   for (auto i = sectionRange.start; i < sectionRange.start + sectionRange.count; ++i) {\n      surface->slices[i].lastChangeIndex = memCache->sections[i].lastChangeIndex;\n\n      // Mark the surface as the owner in this surface group\n      _updateSurfaceGroupSlice(surface->group, i, surface);\n   }\n}\n\nvoid\nDriver::_barrierSurface(SurfaceObject *surface, ResourceUsage usage, vk::ImageLayout layout, SurfaceSubRange range)\n{\n   auto srcMeta = getResourceUsageMeta(surface->activeUsage);\n   auto dstMeta = getResourceUsageMeta(usage);\n\n   // Lets make sure everyone agrees on what the layout\n   // actually needs to be for this transition.\n   decaf_check(dstMeta.imageLayout == layout);\n\n   if (surface->activeUsage == usage) {\n      return;\n   }\n\n   vk::ImageMemoryBarrier imageBarrier;\n   imageBarrier.srcAccessMask = srcMeta.accessFlags;\n   imageBarrier.dstAccessMask = dstMeta.accessFlags;\n   imageBarrier.oldLayout = srcMeta.imageLayout;\n   imageBarrier.newLayout = dstMeta.imageLayout;\n   imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n   imageBarrier.image = surface->image;\n   imageBarrier.subresourceRange = surface->subresRange;\n\n   mActiveCommandBuffer.pipelineBarrier(\n      srcMeta.stageFlags,\n      dstMeta.stageFlags,\n      vk::DependencyFlags(),\n      {},\n      {},\n      { imageBarrier });\n\n   surface->activeUsage = usage;\n}\n\nSurfaceObject *\nDriver::getSurface(const SurfaceDesc& info)\n{\n   auto& surface = mSurfaces[info.hash()];\n   if (!surface) {\n      surface = _allocateSurface(info);\n   }\n\n   if (info.pitch > surface->desc.pitch ||\n       info.width > surface->desc.width ||\n       info.height > surface->desc.height ||\n       info.depth > surface->desc.depth) {\n      _upgradeSurface(surface, info);\n   }\n\n   // Check we got the surface we wanted, not that we check height,depth\n   // as a greater-equal to easily handle the array cases.  We also skip\n   // DIM check, since it can go from 2D to 2D_ARRAY.\n   decaf_check(surface->desc.calcAlignedBaseAddress() == info.calcAlignedBaseAddress());\n   decaf_check(surface->desc.pitch == info.pitch);\n   decaf_check(surface->desc.width == info.width);\n   decaf_check(surface->desc.height >= info.height);\n   decaf_check(surface->desc.depth >= info.depth);\n   decaf_check(surface->desc.samples == info.samples);\n   decaf_check(surface->desc.tileType == info.tileType);\n   decaf_check(surface->desc.tileMode == info.tileMode);\n   decaf_check(surface->desc.format == info.format);\n\n   return surface;\n}\n\nvoid\nDriver::transitionSurface(SurfaceObject *surface, ResourceUsage usage, vk::ImageLayout layout, SurfaceSubRange range, bool skipChangeCheck)\n{\n   // We need to align our invalidation groups along a tickness boundary!\n   auto alignedRange = range;\n   auto tileThickness = gpu7::tiling::getMicroTileThickness(surface->tilingInfo.tileMode);\n   if (tileThickness > 1) {\n      auto endSlice = range.firstSlice + range.numSlices;\n      alignedRange.firstSlice = align_down(range.firstSlice, tileThickness);\n      endSlice = align_up(endSlice, tileThickness);\n      alignedRange.numSlices = endSlice - alignedRange.firstSlice;\n   }\n\n   surface->lastUsageIndex = mActiveBatchIndex;\n\n   bool forWrite = getResourceUsageMeta(usage).isWrite;\n\n   if (!skipChangeCheck) {\n      _refreshSurface(surface, alignedRange);\n\n      if (forWrite) {\n         _invalidateSurface(surface, alignedRange);\n      }\n   }\n\n   _barrierSurface(surface, usage, layout, alignedRange);\n}\n\nSurfaceViewObject *\nDriver::_allocateSurfaceView(const SurfaceViewDesc& info)\n{\n   auto adjInfo = info;\n\n   if (adjInfo.surfaceDesc.dim == latte::SQ_TEX_DIM::DIM_3D) {\n      // The source GPU allows slice selection on 3D textures.  In order\n      // to support this, we will actually have to adjust the sizing of\n      // the underlying 3D textures so that the image view can correctly\n      // see it (since we cannot view particular slices with this).\n      decaf_check(adjInfo.sliceStart == 0);\n      decaf_check(adjInfo.sliceEnd == adjInfo.surfaceDesc.depth);\n      adjInfo.sliceEnd = 1;\n   }\n\n   auto surface = getSurface(adjInfo.surfaceDesc);\n\n   auto subresRange = surface->subresRange;\n   subresRange.baseArrayLayer = adjInfo.sliceStart;\n   subresRange.layerCount = adjInfo.sliceEnd - adjInfo.sliceStart;\n\n   // We still need to support invalidating specific slices in the underlying\n   // image, in case someone renders to a specific slice of the image.\n   SurfaceSubRange range;\n   range.firstSlice = info.sliceStart;\n   range.numSlices = info.sliceEnd - info.sliceStart;\n\n   auto surfaceView = new SurfaceViewObject();\n   surfaceView->desc = info;\n   surfaceView->surfaceRange = range;\n   surfaceView->surface = surface;\n   //surfaceView->imageView = nullptr;\n   //surfaceView->boundImage = nullptr;\n   surfaceView->subresRange = subresRange;\n   return surfaceView;\n}\n\nvoid\nDriver::_releaseSurfaceView(SurfaceViewObject *surfaceView)\n{\n   // TODO: Add support for releasing surfaces...\n}\n\nSurfaceViewObject *\nDriver::getSurfaceView(const SurfaceViewDesc& info)\n{\n   auto &surfaceView = mSurfaceViews[info.hash()];\n   if (!surfaceView) {\n      surfaceView = _allocateSurfaceView(info);\n   }\n\n   decaf_check(surfaceView->desc->sliceStart == info.sliceStart);\n   decaf_check(surfaceView->desc->sliceEnd == info.sliceEnd);\n\n   return surfaceView;\n}\n\nvoid\nDriver::transitionSurfaceView(SurfaceViewObject *surfaceView, ResourceUsage usage, vk::ImageLayout layout, bool skipChangeCheck)\n{\n   transitionSurface(surfaceView->surface, usage, layout, surfaceView->surfaceRange, skipChangeCheck);\n\n   if (surfaceView->boundImage == surfaceView->surface->image) {\n      return;\n   }\n\n   auto& info = surfaceView->desc;\n   auto& surface = surfaceView->surface;\n\n   auto hostFormat = getVkSurfaceFormat(info->surfaceDesc.format, info->surfaceDesc.tileType);\n\n   vk::ImageViewType imageViewType;\n   switch (info->surfaceDesc.dim) {\n   case latte::SQ_TEX_DIM::DIM_1D:\n      imageViewType = vk::ImageViewType::e1D;\n      break;\n   case latte::SQ_TEX_DIM::DIM_2D:\n   case latte::SQ_TEX_DIM::DIM_2D_MSAA:\n      imageViewType = vk::ImageViewType::e2D;\n      break;\n   case latte::SQ_TEX_DIM::DIM_CUBEMAP:\n      imageViewType = vk::ImageViewType::e2DArray;\n      break;\n   case latte::SQ_TEX_DIM::DIM_1D_ARRAY:\n      imageViewType = vk::ImageViewType::e1DArray;\n      break;\n   case latte::SQ_TEX_DIM::DIM_2D_ARRAY:\n   case latte::SQ_TEX_DIM::DIM_2D_ARRAY_MSAA:\n      imageViewType = vk::ImageViewType::e2DArray;\n      break;\n   case latte::SQ_TEX_DIM::DIM_3D:\n      imageViewType = vk::ImageViewType::e3D;\n      break;\n   default:\n      decaf_abort(fmt::format(\"Failed to pick vulkan image view type for dim {}\", info->surfaceDesc.dim));\n   }\n\n   auto hostComponentMap = vk::ComponentMapping();\n   hostComponentMap.r = getVkComponentSwizzle(info->channels[0]);\n   hostComponentMap.g = getVkComponentSwizzle(info->channels[1]);\n   hostComponentMap.b = getVkComponentSwizzle(info->channels[2]);\n   hostComponentMap.a = getVkComponentSwizzle(info->channels[3]);\n\n   vk::ImageViewCreateInfo imageViewDesc;\n   imageViewDesc.image = surface->image;\n   imageViewDesc.viewType = imageViewType;\n   imageViewDesc.format = hostFormat;\n   imageViewDesc.components = hostComponentMap;\n   imageViewDesc.subresourceRange = surfaceView->subresRange;\n   auto imageView = mDevice.createImageView(imageViewDesc);\n\n   setVkObjectName(imageView, _makeSurfaceViewName(*info).c_str());\n\n   if (surfaceView->imageView) {\n      auto oldImageView = surfaceView->imageView;\n      addRetireTask([=](){\n         mDevice.destroyImageView(oldImageView);\n      });\n      surfaceView->imageView = vk::ImageView();\n   }\n\n   surfaceView->imageView = imageView;\n   surfaceView->boundImage = surface->image;\n}\n\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_swapchain.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\nnamespace vulkan\n{\n\nSwapChainObject *\nDriver::allocateSwapChain(const SwapChainDesc &desc)\n{\n   SurfaceDesc surfaceDesc;\n   surfaceDesc.baseAddress = desc.baseAddress.getAddress();\n   surfaceDesc.pitch = desc.width;\n   surfaceDesc.width = desc.width;\n   surfaceDesc.height = desc.height;\n   surfaceDesc.depth = 1;\n   surfaceDesc.samples = 1u;\n   surfaceDesc.dim = latte::SQ_TEX_DIM::DIM_2D;\n   surfaceDesc.format = latte::SurfaceFormat::R8G8B8A8Srgb;\n   surfaceDesc.tileType = latte::SQ_TILE_TYPE::DEFAULT;\n   surfaceDesc.tileMode = latte::SQ_TILE_MODE::LINEAR_ALIGNED;\n\n   SurfaceViewDesc surfaceViewDesc;\n   surfaceViewDesc.sliceStart = 0;\n   surfaceViewDesc.sliceEnd = 1;\n   surfaceViewDesc.surfaceDesc = surfaceDesc;\n   surfaceViewDesc.channels = {\n      latte::SQ_SEL::SEL_X,\n      latte::SQ_SEL::SEL_Y,\n      latte::SQ_SEL::SEL_Z,\n      latte::SQ_SEL::SEL_W };\n\n   // TODO: The swap buffer manager inside libgpu should not be managing the ImageView\n   // that is being used by the host application...\n   auto surfaceView = getSurfaceView(surfaceViewDesc);\n   auto surface = surfaceView->surface;\n\n   // We have to transition the view not the surface to ensure the imageView is created.\n   transitionSurfaceView(surfaceView, ResourceUsage::TransferDst, vk::ImageLayout::eTransferDstOptimal);\n\n   std::array<float, 4> clearColor = { 0.1f, 0.1f, 0.1f, 1.0f };\n   mActiveCommandBuffer.clearColorImage(surface->image, vk::ImageLayout::eTransferDstOptimal, clearColor, { surface->subresRange });\n\n   auto swapChain = new SwapChainObject();\n   swapChain->_surface = surface;\n   swapChain->desc = desc;\n   swapChain->presentable = false;\n   swapChain->imageView = surfaceView->imageView;\n   swapChain->image = surface->image;\n   swapChain->subresRange = surface->subresRange;\n   return swapChain;\n}\n\nvoid\nDriver::releaseSwapChain(SwapChainObject *swapChain)\n{\n   // TODO: Implement releasing of vulkan swap chains.\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_textures.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\nnamespace vulkan\n{\n\nSurfaceViewDesc\nDriver::getTextureDesc(ShaderStage shaderStage, uint32_t textureIdx)\n{\n   uint32_t samplerBaseIdx;\n   if (shaderStage == ShaderStage::Vertex) {\n      samplerBaseIdx = latte::SQ_RES_OFFSET::VS_TEX_RESOURCE_0;\n   } else if (shaderStage == ShaderStage::Geometry) {\n      samplerBaseIdx = latte::SQ_RES_OFFSET::GS_TEX_RESOURCE_0;\n   } else if (shaderStage == ShaderStage::Pixel) {\n      samplerBaseIdx = latte::SQ_RES_OFFSET::PS_TEX_RESOURCE_0;\n   } else {\n      decaf_abort(\"Unknown shader stage\");\n   }\n\n   auto resourceOffset = (samplerBaseIdx + textureIdx) * 7;\n   auto sq_tex_resource_word0 = getRegister<latte::SQ_TEX_RESOURCE_WORD0_N>(latte::Register::SQ_RESOURCE_WORD0_0 + 4 * resourceOffset);\n   auto sq_tex_resource_word1 = getRegister<latte::SQ_TEX_RESOURCE_WORD1_N>(latte::Register::SQ_RESOURCE_WORD1_0 + 4 * resourceOffset);\n   auto sq_tex_resource_word2 = getRegister<latte::SQ_TEX_RESOURCE_WORD2_N>(latte::Register::SQ_RESOURCE_WORD2_0 + 4 * resourceOffset);\n   auto sq_tex_resource_word3 = getRegister<latte::SQ_TEX_RESOURCE_WORD3_N>(latte::Register::SQ_RESOURCE_WORD3_0 + 4 * resourceOffset);\n   auto sq_tex_resource_word4 = getRegister<latte::SQ_TEX_RESOURCE_WORD4_N>(latte::Register::SQ_RESOURCE_WORD4_0 + 4 * resourceOffset);\n   auto sq_tex_resource_word5 = getRegister<latte::SQ_TEX_RESOURCE_WORD5_N>(latte::Register::SQ_RESOURCE_WORD5_0 + 4 * resourceOffset);\n   auto sq_tex_resource_word6 = getRegister<latte::SQ_TEX_RESOURCE_WORD6_N>(latte::Register::SQ_RESOURCE_WORD6_0 + 4 * resourceOffset);\n\n   SurfaceDesc surfaceDataDesc;\n   surfaceDataDesc.baseAddress = sq_tex_resource_word2.BASE_ADDRESS() << 8;\n   surfaceDataDesc.pitch = (sq_tex_resource_word0.PITCH() + 1) * 8;\n   surfaceDataDesc.width = sq_tex_resource_word0.TEX_WIDTH() + 1;\n   surfaceDataDesc.height = sq_tex_resource_word1.TEX_HEIGHT() + 1;\n   surfaceDataDesc.depth = sq_tex_resource_word1.TEX_DEPTH() + 1;\n   surfaceDataDesc.samples = 1u;\n   surfaceDataDesc.dim = sq_tex_resource_word0.DIM();\n   surfaceDataDesc.format = latte::getSurfaceFormat(\n      sq_tex_resource_word1.DATA_FORMAT(),\n      sq_tex_resource_word4.NUM_FORMAT_ALL(),\n      sq_tex_resource_word4.FORMAT_COMP_X(),\n      sq_tex_resource_word4.FORCE_DEGAMMA());\n   surfaceDataDesc.tileType = sq_tex_resource_word0.TILE_TYPE();\n   surfaceDataDesc.tileMode = sq_tex_resource_word0.TILE_MODE();\n\n   if (surfaceDataDesc.dim == latte::SQ_TEX_DIM::DIM_CUBEMAP) {\n      surfaceDataDesc.depth *= 6;\n   }\n\n   SurfaceViewDesc surfaceDesc;\n   surfaceDesc.surfaceDesc = surfaceDataDesc;\n   surfaceDesc.sliceStart = sq_tex_resource_word5.BASE_ARRAY();\n   surfaceDesc.sliceEnd = sq_tex_resource_word5.LAST_ARRAY() + 1;\n   surfaceDesc.channels[0] = sq_tex_resource_word4.DST_SEL_X();\n   surfaceDesc.channels[1] = sq_tex_resource_word4.DST_SEL_Y();\n   surfaceDesc.channels[2] = sq_tex_resource_word4.DST_SEL_Z();\n   surfaceDesc.channels[3] = sq_tex_resource_word4.DST_SEL_W();\n\n   return surfaceDesc;\n}\n\nvoid\nDriver::updateDrawTexture(ShaderStage shaderStage, uint32_t textureIdx)\n{\n   uint32_t shaderStageIdx = static_cast<uint32_t>(shaderStage);\n   auto &drawTexture = mCurrentDraw->textures[shaderStageIdx][textureIdx];\n\n   HashedDesc<SurfaceViewDesc> currentDesc = getTextureDesc(shaderStage, textureIdx);\n\n   if (!currentDesc->surfaceDesc.baseAddress) {\n      drawTexture = nullptr;\n      return;\n   }\n\n   if (drawTexture && drawTexture->desc == currentDesc) {\n      // We already have the correct texture set\n      return;\n   }\n\n   drawTexture = getSurfaceView(*currentDesc);\n   mCurrentDraw->textureDirty[shaderStageIdx][textureIdx] = true;\n}\n\nbool\nDriver::checkCurrentTextures()\n{\n   if (mCurrentDraw->vertexShader) {\n      for (auto textureIdx = 0u; textureIdx < latte::MaxTextures; ++textureIdx) {\n         if (mCurrentDraw->vertexShader->shader.meta.textureUsed[textureIdx]) {\n            updateDrawTexture(ShaderStage::Vertex, textureIdx);\n         } else {\n            mCurrentDraw->textures[0][textureIdx] = nullptr;\n         }\n      }\n   } else {\n      for (auto textureIdx = 0; textureIdx < latte::MaxTextures; ++textureIdx) {\n         mCurrentDraw->textures[0][textureIdx] = nullptr;\n      }\n   }\n\n   if (mCurrentDraw->geometryShader) {\n      for (auto textureIdx = 0u; textureIdx < latte::MaxTextures; ++textureIdx) {\n         if (mCurrentDraw->geometryShader->shader.meta.textureUsed[textureIdx]) {\n            updateDrawTexture(ShaderStage::Geometry, textureIdx);\n         } else {\n            mCurrentDraw->textures[1][textureIdx] = nullptr;\n         }\n      }\n   } else {\n      for (auto textureIdx = 0; textureIdx < latte::MaxTextures; ++textureIdx) {\n         mCurrentDraw->textures[1][textureIdx] = nullptr;\n      }\n   }\n\n   if (mCurrentDraw->pixelShader) {\n      for (auto textureIdx = 0u; textureIdx < latte::MaxTextures; ++textureIdx) {\n         if (mCurrentDraw->pixelShader->shader.meta.textureUsed[textureIdx]) {\n            updateDrawTexture(ShaderStage::Pixel, textureIdx);\n         } else {\n            mCurrentDraw->textures[2][textureIdx] = nullptr;\n         }\n      }\n   } else {\n      for (auto textureIdx = 0; textureIdx < latte::MaxTextures; ++textureIdx) {\n         mCurrentDraw->textures[2][textureIdx] = nullptr;\n      }\n   }\n\n   return true;\n}\n\nvoid\nDriver::prepareCurrentTextures()\n{\n   for (auto i = 0u; i < latte::MaxTextures; ++i) {\n      auto& vsSurface = mCurrentDraw->textures[0][i];\n      if (vsSurface) {\n         if (!mCurrentDraw->textureDirty[0][i]) {\n            transitionSurfaceView(vsSurface, ResourceUsage::VertexTexture, vk::ImageLayout::eShaderReadOnlyOptimal, true);\n         } else {\n            transitionSurfaceView(vsSurface, ResourceUsage::VertexTexture, vk::ImageLayout::eShaderReadOnlyOptimal);\n            mCurrentDraw->textureDirty[0][i] = false;\n         }\n      }\n\n      auto& gsSurface = mCurrentDraw->textures[1][i];\n      if (gsSurface) {\n         if (!mCurrentDraw->textureDirty[1][i]) {\n            transitionSurfaceView(gsSurface, ResourceUsage::GeometryTexture, vk::ImageLayout::eShaderReadOnlyOptimal, true);\n         } else {\n            transitionSurfaceView(gsSurface, ResourceUsage::GeometryTexture, vk::ImageLayout::eShaderReadOnlyOptimal);\n            mCurrentDraw->textureDirty[1][i] = false;\n         }\n      }\n\n      auto& psSurface = mCurrentDraw->textures[2][i];\n      if (psSurface) {\n         if (!mCurrentDraw->textureDirty[2][i]) {\n            transitionSurfaceView(psSurface, ResourceUsage::PixelTexture, vk::ImageLayout::eShaderReadOnlyOptimal, true);\n         } else {\n            transitionSurfaceView(psSurface, ResourceUsage::PixelTexture, vk::ImageLayout::eShaderReadOnlyOptimal);\n            mCurrentDraw->textureDirty[2][i] = false;\n         }\n      }\n   }\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_tiling.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\nnamespace vulkan\n{\n\nvoid\nDriver::dispatchGpuTile(const gpu7::tiling::RetileInfo& retileInfo,\n                        vk::CommandBuffer &commandBuffer,\n                        vk::Buffer dstBuffer, uint32_t dstOffset,\n                        vk::Buffer srcBuffer, uint32_t srcOffset,\n                        uint32_t firstSlice, uint32_t numSlices)\n{\n   auto handle = mGpuRetiler.tile(retileInfo,\n                                  commandBuffer,\n                                  dstBuffer, dstOffset,\n                                  srcBuffer, srcOffset,\n                                  firstSlice, numSlices);\n   mActiveSyncWaiter->retileHandles.push_back(handle);\n}\n\nvoid\nDriver::dispatchGpuUntile(const gpu7::tiling::RetileInfo& retileInfo,\n                          vk::CommandBuffer &commandBuffer,\n                          vk::Buffer dstBuffer, uint32_t dstOffset,\n                          vk::Buffer srcBuffer, uint32_t srcOffset,\n                          uint32_t firstSlice, uint32_t numSlices)\n{\n   auto handle = mGpuRetiler.untile(retileInfo,\n                                    commandBuffer,\n                                    dstBuffer, dstOffset,\n                                    srcBuffer, srcOffset,\n                                    firstSlice, numSlices);\n   mActiveSyncWaiter->retileHandles.push_back(handle);\n}\n\n}\n\n#endif // DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_utils.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_utils.h\"\n\n#include <common/decaf_assert.h>\n#include <common/log.h>\n\nnamespace vulkan\n{\n\nvk::Format\ngetVkSurfaceFormat(latte::SurfaceFormat format, latte::SQ_TILE_TYPE tileType)\n{\n   if (tileType == latte::SQ_TILE_TYPE::DEPTH) {\n      switch (format) {\n      case latte::SurfaceFormat::R16Unorm:\n         return vk::Format::eD16Unorm;\n      case latte::SurfaceFormat::R32Float:\n         return vk::Format::eD32Sfloat;\n      case latte::SurfaceFormat::D24UnormS8Uint:\n         return vk::Format::eD24UnormS8Uint;\n      case latte::SurfaceFormat::X24G8Uint:\n         return vk::Format::eD24UnormS8Uint; // Remapped?\n      case latte::SurfaceFormat::D32FloatS8UintX24:\n         return vk::Format::eD32SfloatS8Uint; // Wrong Size?\n      case latte::SurfaceFormat::D32G8UintX24:\n         return vk::Format::eD32SfloatS8Uint; // Remapped?\n      }\n\n      decaf_abort(fmt::format(\"Unexpected depth surface format {}\", format));\n   }\n\n   switch (format) {\n   case latte::SurfaceFormat::R8Unorm:\n      return vk::Format::eR8Unorm;\n   case latte::SurfaceFormat::R8Uint:\n      return vk::Format::eR8Uint;\n   case latte::SurfaceFormat::R8Snorm:\n      return vk::Format::eR8Snorm;\n   case latte::SurfaceFormat::R8Sint:\n      return vk::Format::eR8Sint;\n   case latte::SurfaceFormat::R4G4Unorm:\n      return vk::Format::eR4G4UnormPack8;\n   case latte::SurfaceFormat::R16Unorm:\n      return vk::Format::eR16Unorm;\n   case latte::SurfaceFormat::R16Uint:\n      return vk::Format::eR16Uint;\n   case latte::SurfaceFormat::R16Snorm:\n      return vk::Format::eR16Snorm;\n   case latte::SurfaceFormat::R16Sint:\n      return vk::Format::eR16Sint;\n   case latte::SurfaceFormat::R16Float:\n      return vk::Format::eR16Sfloat;\n   case latte::SurfaceFormat::R8G8Unorm:\n      return vk::Format::eR8G8Unorm;\n   case latte::SurfaceFormat::R8G8Uint:\n      return vk::Format::eR8G8Uint;\n   case latte::SurfaceFormat::R8G8Snorm:\n      return vk::Format::eR8G8Snorm;\n   case latte::SurfaceFormat::R8G8Sint:\n      return vk::Format::eR8G8Sint;\n   case latte::SurfaceFormat::R5G6B5Unorm:\n      return vk::Format::eR5G6B5UnormPack16;\n   case latte::SurfaceFormat::R5G5B5A1Unorm:\n      return vk::Format::eR5G5B5A1UnormPack16;\n   case latte::SurfaceFormat::R4G4B4A4Unorm:\n      return vk::Format::eR4G4B4A4UnormPack16;\n   case latte::SurfaceFormat::A1B5G5R5Unorm:\n      return vk::Format::eR5G5B5A1UnormPack16; // Reversed?\n   case latte::SurfaceFormat::R32Uint:\n      return vk::Format::eR32Uint;\n   case latte::SurfaceFormat::R32Sint:\n      return vk::Format::eR32Sint;\n   case latte::SurfaceFormat::R32Float:\n      return vk::Format::eR32Sfloat;\n   case latte::SurfaceFormat::R16G16Unorm:\n      return vk::Format::eR16G16Unorm;\n   case latte::SurfaceFormat::R16G16Uint:\n      return vk::Format::eR16G16Uint;\n   case latte::SurfaceFormat::R16G16Snorm:\n      return vk::Format::eR16G16Snorm;\n   case latte::SurfaceFormat::R16G16Sint:\n      return vk::Format::eR16G16Sint;\n   case latte::SurfaceFormat::R16G16Float:\n      return vk::Format::eR16G16Sfloat;\n   case latte::SurfaceFormat::D24UnormS8Uint:\n      return vk::Format::eD24UnormS8Uint;\n   case latte::SurfaceFormat::X24G8Uint:\n      return vk::Format::eD24UnormS8Uint; // Not sure if this is actually right...\n   case latte::SurfaceFormat::R11G11B10Float:\n      return vk::Format::eB10G11R11UfloatPack32; // This is the incorrect format...\n      //return vk::Format::eB10G11R11UfloatPack32; // Remapped?\n   case latte::SurfaceFormat::R10G10B10A2Unorm:\n      return vk::Format::eA2B10G10R10UnormPack32; // Remapped?\n   case latte::SurfaceFormat::R10G10B10A2Uint:\n      return vk::Format::eA2B10G10R10UnormPack32; // This is the incorrect format...\n      //return vk::Format::eA2B10G10R10UintPack32; // Remapped?\n   case latte::SurfaceFormat::R10G10B10A2Snorm:\n      return vk::Format::eA2B10G10R10UnormPack32; // This is the incorrect format...\n      //return vk::Format::eA2B10G10R10SnormPack32; // Remapped?\n   case latte::SurfaceFormat::R10G10B10A2Sint:\n      return vk::Format::eA2B10G10R10UnormPack32; // This is the incorrect format...\n      //return vk::Format::eA2B10G10R10SintPack32; // Remapped?\n   case latte::SurfaceFormat::R8G8B8A8Unorm:\n      return vk::Format::eR8G8B8A8Unorm;\n   case latte::SurfaceFormat::R8G8B8A8Uint:\n      return vk::Format::eR8G8B8A8Uint;\n   case latte::SurfaceFormat::R8G8B8A8Snorm:\n      return vk::Format::eR8G8B8A8Snorm;\n   case latte::SurfaceFormat::R8G8B8A8Sint:\n      return vk::Format::eR8G8B8A8Sint;\n   case latte::SurfaceFormat::R8G8B8A8Srgb:\n      return vk::Format::eR8G8B8A8Srgb;\n   case latte::SurfaceFormat::A2B10G10R10Unorm:\n      return vk::Format::eA2B10G10R10UnormPack32; // This is the incorrect format...\n      //return vk::Format::eA2B10G10R10UnormPack32;\n   case latte::SurfaceFormat::A2B10G10R10Uint:\n      return vk::Format::eA2B10G10R10UnormPack32; // This is the incorrect format...\n      //return vk::Format::eA2B10G10R10UintPack32;\n   case latte::SurfaceFormat::D32FloatS8UintX24:\n      return vk::Format::eD32SfloatS8Uint;\n   case latte::SurfaceFormat::D32G8UintX24:\n      return vk::Format::eD32SfloatS8Uint;\n   case latte::SurfaceFormat::R32G32Uint:\n      return vk::Format::eR32G32Uint;\n   case latte::SurfaceFormat::R32G32Sint:\n      return vk::Format::eR32G32Sint;\n   case latte::SurfaceFormat::R32G32Float:\n      return vk::Format::eR32G32Sfloat;\n   case latte::SurfaceFormat::R16G16B16A16Unorm:\n      return vk::Format::eR16G16B16A16Unorm;\n   case latte::SurfaceFormat::R16G16B16A16Uint:\n      return vk::Format::eR16G16B16A16Uint;\n   case latte::SurfaceFormat::R16G16B16A16Snorm:\n      return vk::Format::eR16G16B16A16Snorm;\n   case latte::SurfaceFormat::R16G16B16A16Sint:\n      return vk::Format::eR16G16B16A16Uint;\n   case latte::SurfaceFormat::R16G16B16A16Float:\n      return vk::Format::eR16G16B16A16Sfloat;\n   case latte::SurfaceFormat::R32G32B32A32Uint:\n      return vk::Format::eR32G32B32A32Uint;\n   case latte::SurfaceFormat::R32G32B32A32Sint:\n      return vk::Format::eR32G32B32A32Sint;\n   case latte::SurfaceFormat::R32G32B32A32Float:\n      return vk::Format::eR32G32B32A32Sfloat;\n   case latte::SurfaceFormat::BC1Unorm:\n      return vk::Format::eBc1RgbaUnormBlock;\n   case latte::SurfaceFormat::BC1Srgb:\n      return vk::Format::eBc1RgbaSrgbBlock;\n   case latte::SurfaceFormat::BC2Unorm:\n      return vk::Format::eBc2UnormBlock;\n   case latte::SurfaceFormat::BC2Srgb:\n      return vk::Format::eBc2SrgbBlock;\n   case latte::SurfaceFormat::BC3Unorm:\n      return vk::Format::eBc3UnormBlock;\n   case latte::SurfaceFormat::BC3Srgb:\n      return vk::Format::eBc3SrgbBlock;\n   case latte::SurfaceFormat::BC4Unorm:\n      return vk::Format::eBc4UnormBlock;\n   case latte::SurfaceFormat::BC4Snorm:\n      return vk::Format::eBc4SnormBlock;\n   case latte::SurfaceFormat::BC5Unorm:\n      return vk::Format::eBc5UnormBlock;\n   case latte::SurfaceFormat::BC5Snorm:\n      return vk::Format::eBc5SnormBlock;\n   //case latte::SurfaceFormat::NV12:\n      // Honestly have no clue how to support this format...\n   }\n\n   decaf_abort(fmt::format(\"Unexpected surface format {}\", format));\n}\n\nSurfaceFormatUsage\ngetVkSurfaceFormatUsage(latte::SurfaceFormat format)\n{\n   switch (format) {\n   case latte::SurfaceFormat::R8Unorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8Snorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8Sint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R4G4Unorm:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::R16Unorm:\n      return SurfaceFormatUsage::TD; // TODO: Should support TCD\n   case latte::SurfaceFormat::R16Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16Snorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16Sint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16Float:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8G8Unorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8G8Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8G8Snorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8G8Sint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R5G6B5Unorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R5G5B5A1Unorm:\n      return SurfaceFormatUsage::T; // TODO: Should support TC\n   case latte::SurfaceFormat::R4G4B4A4Unorm:\n      return SurfaceFormatUsage::T; // TODO: Should support TC\n   case latte::SurfaceFormat::A1B5G5R5Unorm:\n      return SurfaceFormatUsage::T; // TODO: Should support TC\n   case latte::SurfaceFormat::R32Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R32Sint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R32Float:\n      return SurfaceFormatUsage::TCD;\n   case latte::SurfaceFormat::R16G16Unorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16G16Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16G16Snorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16G16Sint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16G16Float:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::D24UnormS8Uint:\n      return SurfaceFormatUsage::DS;\n   case latte::SurfaceFormat::X24G8Uint:\n      return SurfaceFormatUsage::T; // TODO: Should support TC\n   case latte::SurfaceFormat::R11G11B10Float:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R10G10B10A2Unorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R10G10B10A2Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R10G10B10A2Snorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R10G10B10A2Sint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8G8B8A8Unorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8G8B8A8Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8G8B8A8Snorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8G8B8A8Sint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R8G8B8A8Srgb:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::A2B10G10R10Unorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::A2B10G10R10Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::D32FloatS8UintX24:\n      return SurfaceFormatUsage::TDS;\n   case latte::SurfaceFormat::D32G8UintX24:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::R32G32Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R32G32Sint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R32G32Float:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16G16B16A16Unorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16G16B16A16Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16G16B16A16Snorm:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16G16B16A16Sint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R16G16B16A16Float:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R32G32B32A32Uint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R32G32B32A32Sint:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::R32G32B32A32Float:\n      return SurfaceFormatUsage::TC;\n   case latte::SurfaceFormat::BC1Unorm:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::BC1Srgb:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::BC2Unorm:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::BC2Srgb:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::BC3Unorm:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::BC3Srgb:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::BC4Unorm:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::BC4Snorm:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::BC5Unorm:\n      return SurfaceFormatUsage::T;\n   case latte::SurfaceFormat::BC5Snorm:\n      return SurfaceFormatUsage::T;\n      //case latte::SurfaceFormat::NV12:\n         // Honestly have no clue how to support this format...\n   }\n\n   decaf_abort(fmt::format(\"Unexpected surface format {}\", format));\n}\n\nvk::ComponentSwizzle\ngetVkComponentSwizzle(latte::SQ_SEL sel)\n{\n   switch (sel) {\n   case latte::SQ_SEL::SEL_X:\n      return vk::ComponentSwizzle::eR;\n   case latte::SQ_SEL::SEL_Y:\n      return vk::ComponentSwizzle::eG;\n   case latte::SQ_SEL::SEL_Z:\n      return vk::ComponentSwizzle::eB;\n   case latte::SQ_SEL::SEL_W:\n      return vk::ComponentSwizzle::eA;\n   case latte::SQ_SEL::SEL_0:\n      return vk::ComponentSwizzle::eZero;\n   case latte::SQ_SEL::SEL_1:\n      return vk::ComponentSwizzle::eOne;\n   case latte::SQ_SEL::SEL_MASK:\n      return vk::ComponentSwizzle::eIdentity;\n   }\n\n   decaf_abort(fmt::format(\"Unexpected component swizzle {}\", sel));\n}\n\nvk::BorderColor\ngetVkBorderColor(latte::SQ_TEX_BORDER_COLOR color)\n{\n   switch (color) {\n   case latte::SQ_TEX_BORDER_COLOR::TRANS_BLACK:\n      return vk::BorderColor::eFloatTransparentBlack;\n   case latte::SQ_TEX_BORDER_COLOR::OPAQUE_BLACK:\n      return vk::BorderColor::eFloatOpaqueBlack;\n   case latte::SQ_TEX_BORDER_COLOR::OPAQUE_WHITE:\n      return vk::BorderColor::eFloatOpaqueWhite;\n   case latte::SQ_TEX_BORDER_COLOR::REGISTER:\n      decaf_abort(\"Unsupported register-based texture border color\");\n   default:\n      decaf_abort(\"Unexpected texture border color type\");\n   }\n}\n\nvk::Filter\ngetVkXyTextureFilter(latte::SQ_TEX_XY_FILTER filter)\n{\n   switch (filter) {\n   case latte::SQ_TEX_XY_FILTER::POINT:\n      return vk::Filter::eNearest;\n   case latte::SQ_TEX_XY_FILTER::BILINEAR:\n      return vk::Filter::eLinear;\n   case latte::SQ_TEX_XY_FILTER::BICUBIC:\n      return vk::Filter::eCubicIMG;\n   default:\n      gLog->warn(\"Unexpected texture xy filter mode\");\n      return vk::Filter::eNearest;\n      //decaf_abort(\"Unexpected texture xy filter mode\");\n   }\n}\n\nvk::SamplerMipmapMode\ngetVkZTextureFilter(latte::SQ_TEX_Z_FILTER filter)\n{\n   switch (filter) {\n   case latte::SQ_TEX_Z_FILTER::NONE:\n      return vk::SamplerMipmapMode::eNearest;\n   case latte::SQ_TEX_Z_FILTER::POINT:\n      return vk::SamplerMipmapMode::eNearest;\n   case latte::SQ_TEX_Z_FILTER::LINEAR:\n      return vk::SamplerMipmapMode::eLinear;\n   default:\n      decaf_abort(\"Unexpected texture xy filter mode\");\n   }\n}\n\nvk::SamplerAddressMode\ngetVkTextureAddressMode(latte::SQ_TEX_CLAMP clamp)\n{\n   switch (clamp) {\n   case latte::SQ_TEX_CLAMP::WRAP:\n      return vk::SamplerAddressMode::eRepeat;\n   case latte::SQ_TEX_CLAMP::MIRROR:\n      return vk::SamplerAddressMode::eMirroredRepeat;\n   case latte::SQ_TEX_CLAMP::CLAMP_LAST_TEXEL:\n      return vk::SamplerAddressMode::eClampToEdge;\n   case latte::SQ_TEX_CLAMP::MIRROR_ONCE_LAST_TEXEL:\n      return vk::SamplerAddressMode::eMirrorClampToEdge;\n   case latte::SQ_TEX_CLAMP::CLAMP_HALF_BORDER:\n      return vk::SamplerAddressMode::eClampToBorder;\n   case latte::SQ_TEX_CLAMP::MIRROR_ONCE_HALF_BORDER:\n      return vk::SamplerAddressMode::eMirrorClampToEdge;\n   case latte::SQ_TEX_CLAMP::CLAMP_BORDER:\n      return vk::SamplerAddressMode::eClampToBorder;\n   case latte::SQ_TEX_CLAMP::MIRROR_ONCE_BORDER:\n      return vk::SamplerAddressMode::eMirrorClampToEdge;\n   default:\n      decaf_abort(\"Unexpected texture clamp mode\");\n   }\n}\n\nbool\ngetVkAnisotropyEnabled(latte::SQ_TEX_ANISO aniso)\n{\n   return aniso != latte::SQ_TEX_ANISO::ANISO_1_TO_1;\n}\n\nfloat\ngetVkMaxAnisotropy(latte::SQ_TEX_ANISO aniso)\n{\n   switch (aniso) {\n   case latte::SQ_TEX_ANISO::ANISO_1_TO_1:\n      return 1.0f;\n   case latte::SQ_TEX_ANISO::ANISO_2_TO_1:\n      return 2.0f;\n   case latte::SQ_TEX_ANISO::ANISO_4_TO_1:\n      return 4.0f;\n   case latte::SQ_TEX_ANISO::ANISO_8_TO_1:\n      return 8.0f;\n   case latte::SQ_TEX_ANISO::ANISO_16_TO_1:\n      return 16.0f;\n   default:\n      decaf_abort(\"Unexpected texture anisotropy mode\");\n   }\n}\n\nbool\ngetVkCompareOpEnabled(latte::REF_FUNC refFunc)\n{\n   return refFunc != latte::REF_FUNC::NEVER;\n}\n\nvk::CompareOp\ngetVkCompareOp(latte::REF_FUNC func)\n{\n   switch (func) {\n   case latte::REF_FUNC::NEVER:\n      return vk::CompareOp::eNever;\n   case latte::REF_FUNC::LESS:\n      return vk::CompareOp::eLess;\n   case latte::REF_FUNC::EQUAL:\n      return vk::CompareOp::eEqual;\n   case latte::REF_FUNC::LESS_EQUAL:\n      return vk::CompareOp::eLessOrEqual;\n   case latte::REF_FUNC::GREATER:\n      return vk::CompareOp::eGreater;\n   case latte::REF_FUNC::NOT_EQUAL:\n      return vk::CompareOp::eNotEqual;\n   case latte::REF_FUNC::GREATER_EQUAL:\n      return vk::CompareOp::eGreaterOrEqual;\n   case latte::REF_FUNC::ALWAYS:\n      return vk::CompareOp::eAlways;\n   }\n\n   decaf_abort(fmt::format(\"Unexpected compare op {}\", func));\n}\n\nvk::StencilOp\ngetVkStencilOp(latte::DB_STENCIL_FUNC func)\n{\n   switch (func) {\n   case latte::DB_STENCIL_FUNC::KEEP:\n      return vk::StencilOp::eKeep;\n   case latte::DB_STENCIL_FUNC::ZERO:\n      return vk::StencilOp::eZero;\n   case latte::DB_STENCIL_FUNC::REPLACE:\n      return vk::StencilOp::eReplace;\n   case latte::DB_STENCIL_FUNC::INCR_CLAMP:\n      return vk::StencilOp::eIncrementAndClamp;\n   case latte::DB_STENCIL_FUNC::DECR_CLAMP:\n      return vk::StencilOp::eDecrementAndClamp;\n   case latte::DB_STENCIL_FUNC::INVERT:\n      return vk::StencilOp::eInvert;\n   case latte::DB_STENCIL_FUNC::INCR_WRAP:\n      return vk::StencilOp::eIncrementAndWrap;\n   case latte::DB_STENCIL_FUNC::DECR_WRAP:\n      return vk::StencilOp::eDecrementAndWrap;\n   }\n\n   decaf_abort(fmt::format(\"Unexpected stencil op {}\", func));\n}\n\nvk::BlendFactor\ngetVkBlendFactor(latte::CB_BLEND_FUNC func)\n{\n   switch (func) {\n   case latte::CB_BLEND_FUNC::ZERO:\n      return vk::BlendFactor::eZero;\n   case latte::CB_BLEND_FUNC::ONE:\n      return vk::BlendFactor::eOne;\n   case latte::CB_BLEND_FUNC::SRC_COLOR:\n      return vk::BlendFactor::eSrcColor;\n   case latte::CB_BLEND_FUNC::ONE_MINUS_SRC_COLOR:\n      return vk::BlendFactor::eOneMinusSrcColor;\n   case latte::CB_BLEND_FUNC::SRC_ALPHA:\n      return vk::BlendFactor::eSrcAlpha;\n   case latte::CB_BLEND_FUNC::ONE_MINUS_SRC_ALPHA:\n      return vk::BlendFactor::eOneMinusSrcAlpha;\n   case latte::CB_BLEND_FUNC::DST_ALPHA:\n      return vk::BlendFactor::eDstAlpha;\n   case latte::CB_BLEND_FUNC::ONE_MINUS_DST_ALPHA:\n      return vk::BlendFactor::eOneMinusDstAlpha;\n   case latte::CB_BLEND_FUNC::DST_COLOR:\n      return vk::BlendFactor::eDstColor;\n   case latte::CB_BLEND_FUNC::ONE_MINUS_DST_COLOR:\n      return vk::BlendFactor::eOneMinusDstColor;\n   case latte::CB_BLEND_FUNC::SRC_ALPHA_SATURATE:\n      return vk::BlendFactor::eSrcAlphaSaturate;\n   case latte::CB_BLEND_FUNC::BOTH_SRC_ALPHA:\n      decaf_abort(\"Unsupported BOTH_SRC_ALPHA blend function\");\n   case latte::CB_BLEND_FUNC::BOTH_INV_SRC_ALPHA:\n      decaf_abort(\"Unsupported BOTH_INV_SRC_ALPHA blend function\");\n   case latte::CB_BLEND_FUNC::CONSTANT_COLOR:\n      return vk::BlendFactor::eConstantColor;\n   case latte::CB_BLEND_FUNC::ONE_MINUS_CONSTANT_COLOR:\n      return vk::BlendFactor::eOneMinusConstantColor;\n   case latte::CB_BLEND_FUNC::SRC1_COLOR:\n      return vk::BlendFactor::eSrc1Color;\n   case latte::CB_BLEND_FUNC::ONE_MINUS_SRC1_COLOR:\n      return vk::BlendFactor::eOneMinusSrc1Color;\n   case latte::CB_BLEND_FUNC::SRC1_ALPHA:\n      return vk::BlendFactor::eSrc1Alpha;\n   case latte::CB_BLEND_FUNC::ONE_MINUS_SRC1_ALPHA:\n      return vk::BlendFactor::eOneMinusSrc1Alpha;\n   case latte::CB_BLEND_FUNC::CONSTANT_ALPHA:\n      return vk::BlendFactor::eConstantAlpha;\n   case latte::CB_BLEND_FUNC::ONE_MINUS_CONSTANT_ALPHA:\n      return vk::BlendFactor::eOneMinusConstantAlpha;\n   }\n\n   decaf_abort(fmt::format(\"Unexpected blend factor {}\", func));\n}\n\nvk::BlendOp\ngetVkBlendOp(latte::CB_COMB_FUNC func)\n{\n   switch (func) {\n   case latte::CB_COMB_FUNC::DST_PLUS_SRC:\n      return vk::BlendOp::eAdd;\n   case latte::CB_COMB_FUNC::SRC_MINUS_DST:\n      return vk::BlendOp::eSubtract;\n   case latte::CB_COMB_FUNC::MIN_DST_SRC:\n      return vk::BlendOp::eMin;\n   case latte::CB_COMB_FUNC::MAX_DST_SRC:\n      return vk::BlendOp::eMax;\n   case latte::CB_COMB_FUNC::DST_MINUS_SRC:\n      return vk::BlendOp::eReverseSubtract;\n   }\n\n   decaf_abort(fmt::format(\"Unexpected blend op {}\", func));\n}\n\nvk::SampleCountFlags\ngetVkSampleCount(uint32_t samples)\n{\n   switch (samples) {\n   case 1:\n      return vk::SampleCountFlagBits::e1;\n   case 2:\n      return vk::SampleCountFlagBits::e2;\n   case 4:\n      return vk::SampleCountFlagBits::e4;\n   case 8:\n      return vk::SampleCountFlagBits::e8;\n   case 16:\n      return vk::SampleCountFlagBits::e16;\n   case 32:\n      return vk::SampleCountFlagBits::e32;\n   case 64:\n      return vk::SampleCountFlagBits::e64;\n   }\n   decaf_abort(\"Unexpected surface samples value\");\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_utils.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n#include \"latte/latte_formats.h\"\n\n#include <common/vulkan_hpp.h>\n\nnamespace vulkan\n{\n\nenum SurfaceFormatUsage : uint32_t\n{\n   TEXTURE = (1 << 0),\n   COLOR = (1 << 1),\n   DEPTH = (1 << 2),\n   STENCIL = (1 << 3),\n\n   T = TEXTURE,\n   TC = TEXTURE | COLOR,\n   TD = TEXTURE | DEPTH,\n   TCD = TEXTURE | COLOR | DEPTH,\n   TDS = TEXTURE | DEPTH | STENCIL,\n   D = DEPTH,\n   DS = DEPTH | STENCIL\n};\n\nvk::Format\ngetVkSurfaceFormat(latte::SurfaceFormat format, latte::SQ_TILE_TYPE tileType);\n\nvk::ComponentSwizzle\ngetVkComponentSwizzle(latte::SQ_SEL sel);\n\nvk::BorderColor\ngetVkBorderColor(latte::SQ_TEX_BORDER_COLOR color);\n\nvk::Filter\ngetVkXyTextureFilter(latte::SQ_TEX_XY_FILTER filter);\n\nvk::SamplerMipmapMode\ngetVkZTextureFilter(latte::SQ_TEX_Z_FILTER filter);\n\nvk::SamplerAddressMode\ngetVkTextureAddressMode(latte::SQ_TEX_CLAMP clamp);\n\nbool\ngetVkAnisotropyEnabled(latte::SQ_TEX_ANISO aniso);\n\nfloat\ngetVkMaxAnisotropy(latte::SQ_TEX_ANISO aniso);\n\nbool\ngetVkCompareOpEnabled(latte::REF_FUNC func);\n\nvk::CompareOp\ngetVkCompareOp(latte::REF_FUNC func);\n\nvk::StencilOp\ngetVkStencilOp(latte::DB_STENCIL_FUNC func);\n\nvk::BlendFactor\ngetVkBlendFactor(latte::CB_BLEND_FUNC func);\n\nvk::BlendOp\ngetVkBlendOp(latte::CB_COMB_FUNC func);\n\nSurfaceFormatUsage\ngetVkSurfaceFormatUsage(latte::SurfaceFormat format);\n\nvk::SampleCountFlags\ngetVkSampleCount(uint32_t samples);\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_validate.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n#include \"vulkan_utils.h\"\n\n#include <common/log.h>\n\nnamespace vulkan\n{\n\nauto supportedColorFormats = {\n   latte::SurfaceFormat::R8Unorm,\n   latte::SurfaceFormat::R8Uint,\n   latte::SurfaceFormat::R8Snorm,\n   latte::SurfaceFormat::R8Sint,\n   latte::SurfaceFormat::R4G4Unorm,\n   latte::SurfaceFormat::R16Unorm,\n   latte::SurfaceFormat::R16Uint,\n   latte::SurfaceFormat::R16Snorm,\n   latte::SurfaceFormat::R16Sint,\n   latte::SurfaceFormat::R16Float,\n   latte::SurfaceFormat::R8G8Unorm,\n   latte::SurfaceFormat::R8G8Uint,\n   latte::SurfaceFormat::R8G8Snorm,\n   latte::SurfaceFormat::R8G8Sint,\n   latte::SurfaceFormat::R5G6B5Unorm,\n   latte::SurfaceFormat::R5G5B5A1Unorm,\n   latte::SurfaceFormat::R4G4B4A4Unorm,\n   latte::SurfaceFormat::A1B5G5R5Unorm,\n   latte::SurfaceFormat::R32Uint,\n   latte::SurfaceFormat::R32Sint,\n   latte::SurfaceFormat::R32Float,\n   latte::SurfaceFormat::R16G16Unorm,\n   latte::SurfaceFormat::R16G16Uint,\n   latte::SurfaceFormat::R16G16Snorm,\n   latte::SurfaceFormat::R16G16Sint,\n   latte::SurfaceFormat::R16G16Float,\n   latte::SurfaceFormat::D24UnormS8Uint,\n   latte::SurfaceFormat::X24G8Uint,\n   latte::SurfaceFormat::R11G11B10Float,\n   latte::SurfaceFormat::R10G10B10A2Unorm,\n   latte::SurfaceFormat::R10G10B10A2Uint,\n   latte::SurfaceFormat::R10G10B10A2Snorm,\n   latte::SurfaceFormat::R10G10B10A2Sint,\n   latte::SurfaceFormat::R8G8B8A8Unorm,\n   latte::SurfaceFormat::R8G8B8A8Uint,\n   latte::SurfaceFormat::R8G8B8A8Snorm,\n   latte::SurfaceFormat::R8G8B8A8Sint,\n   latte::SurfaceFormat::R8G8B8A8Srgb,\n   latte::SurfaceFormat::A2B10G10R10Unorm,\n   latte::SurfaceFormat::A2B10G10R10Uint,\n   latte::SurfaceFormat::D32FloatS8UintX24,\n   latte::SurfaceFormat::D32G8UintX24,\n   latte::SurfaceFormat::R32G32Uint,\n   latte::SurfaceFormat::R32G32Sint,\n   latte::SurfaceFormat::R32G32Float,\n   latte::SurfaceFormat::R16G16B16A16Unorm,\n   latte::SurfaceFormat::R16G16B16A16Uint,\n   latte::SurfaceFormat::R16G16B16A16Snorm,\n   latte::SurfaceFormat::R16G16B16A16Sint,\n   latte::SurfaceFormat::R16G16B16A16Float,\n   latte::SurfaceFormat::R32G32B32A32Uint,\n   latte::SurfaceFormat::R32G32B32A32Sint,\n   latte::SurfaceFormat::R32G32B32A32Float,\n   latte::SurfaceFormat::BC1Unorm,\n   latte::SurfaceFormat::BC1Srgb,\n   latte::SurfaceFormat::BC2Unorm,\n   latte::SurfaceFormat::BC2Srgb,\n   latte::SurfaceFormat::BC3Unorm,\n   latte::SurfaceFormat::BC3Srgb,\n   latte::SurfaceFormat::BC4Unorm,\n   latte::SurfaceFormat::BC4Snorm,\n   latte::SurfaceFormat::BC5Unorm,\n   latte::SurfaceFormat::BC5Snorm,\n   //latte::SurfaceFormat::NV12,\n};\n\nauto supportedDepthFormats = {\n   latte::SurfaceFormat::R16Unorm,\n   latte::SurfaceFormat::R32Float,\n   latte::SurfaceFormat::D24UnormS8Uint,\n   latte::SurfaceFormat::X24G8Uint,\n   latte::SurfaceFormat::D32FloatS8UintX24,\n   latte::SurfaceFormat::D32G8UintX24,\n};\n\nvoid\nDriver::validateDevice()\n{\n   // TODO: Decide what is a hard requirement and what is optional\n   auto checkFormat = [&](latte::SurfaceFormat format, latte::SQ_TILE_TYPE tileType)\n   {\n      auto hostFormat = getVkSurfaceFormat(format, tileType);\n      auto formatUsages = getVkSurfaceFormatUsage(format);\n\n      auto formatProps = mPhysDevice.getFormatProperties(hostFormat);\n\n      if (!(formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eTransferDst)) {\n         gLog->warn(\"Surface format {:03x}[{}]({}) does not support TransferDst feature\",\n                    format, tileType, vk::to_string(hostFormat));\n      }\n      if (!(formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eTransferSrc)) {\n         gLog->warn(\"Surface format {:03x}[{}]({}) does not support TransferSrc feature\",\n                    format, tileType, vk::to_string(hostFormat));\n      }\n\n      if (tileType != latte::SQ_TILE_TYPE::DEPTH) {\n         if (formatUsages & SurfaceFormatUsage::TEXTURE) {\n            if (!(formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImage)) {\n               gLog->warn(\"Surface format {:03x}[{}]({}) does not support SampledImage feature\",\n                          format, tileType, vk::to_string(hostFormat));\n            }\n         }\n         if (formatUsages & SurfaceFormatUsage::COLOR) {\n            if (!(formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eColorAttachment)) {\n               gLog->warn(\"Surface format {:03x}[{}]({}) does not support ColorAttachment feature\",\n                          format, tileType, vk::to_string(hostFormat));\n            }\n         }\n      } else {\n         if (formatUsages & (SurfaceFormatUsage::DEPTH | SurfaceFormatUsage::STENCIL)) {\n            if (!(formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment)) {\n               gLog->warn(\"Surface format {:03x}[{}]({}) does not support DepthStencilAttachement feature\",\n                          format, tileType, vk::to_string(hostFormat));\n            }\n         }\n      }\n   };\n\n   for (auto format : supportedColorFormats) {\n      checkFormat(format, latte::SQ_TILE_TYPE::DEFAULT);\n   }\n\n   for (auto format : supportedDepthFormats) {\n      checkFormat(format, latte::SQ_TILE_TYPE::DEPTH);\n   }\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/src/vulkan/vulkan_viewscissor.cpp",
    "content": "#ifdef DECAF_VULKAN\n#include \"vulkan_driver.h\"\n\nnamespace vulkan\n{\n\nbool\nDriver::checkCurrentViewportAndScissor()\n{\n   // GPU7 actually supports many viewports and many scissors, but it\n   // seems that CafeOS itself only supports a single one.\n\n   // ------------------------------------------------------------\n   // Viewport\n   // ------------------------------------------------------------\n\n   auto pa_cl_vport_xscale = getRegister<latte::PA_CL_VPORT_XSCALE_N>(latte::Register::PA_CL_VPORT_XSCALE_0);\n   auto pa_cl_vport_yscale = getRegister<latte::PA_CL_VPORT_YSCALE_N>(latte::Register::PA_CL_VPORT_YSCALE_0);\n   auto pa_cl_vport_zscale = getRegister<latte::PA_CL_VPORT_ZSCALE_N>(latte::Register::PA_CL_VPORT_ZSCALE_0);\n   auto pa_cl_vport_xoffset = getRegister<latte::PA_CL_VPORT_XOFFSET_N>(latte::Register::PA_CL_VPORT_XOFFSET_0);\n   auto pa_cl_vport_yoffset = getRegister<latte::PA_CL_VPORT_YOFFSET_N>(latte::Register::PA_CL_VPORT_YOFFSET_0);\n   auto pa_cl_vport_zoffset = getRegister<latte::PA_CL_VPORT_ZOFFSET_N>(latte::Register::PA_CL_VPORT_ZOFFSET_0);\n   auto pa_sc_vport_zmin = getRegister<latte::PA_SC_VPORT_ZMIN_N>(latte::Register::PA_SC_VPORT_ZMIN_0);\n   auto pa_sc_vport_zmax = getRegister<latte::PA_SC_VPORT_ZMAX_N>(latte::Register::PA_SC_VPORT_ZMAX_0);\n   auto pa_cl_vte_cntl = getRegister<latte::PA_CL_VTE_CNTL>(latte::Register::PA_CL_VTE_CNTL);\n   auto pa_cl_clip_cntl = getRegister<latte::PA_CL_CLIP_CNTL>(latte::Register::PA_CL_CLIP_CNTL);\n\n   auto raWidth = static_cast<float>(mCurrentDraw->framebuffer->renderArea.width);\n   auto raHeight = static_cast<float>(mCurrentDraw->framebuffer->renderArea.height);\n\n   // NOTE: Our shaders which output positions understand that if the\n   // xoffset/yoffset/xscale/yscale are disabled, that we need to\n   // pre-transform the render-area down to -1to1 on X and Y.  This\n   // means rather than using a disabled viewport here, we need to\n   // expand this back out.  The reason to do this is that there is\n   // NDC-space clipping occuring in Vulkan which would prevent this\n   // from working as it does on GPU7.\n\n   float vportOX, vportOY, vportSX, vportSY;\n   if (pa_cl_vte_cntl.VPORT_X_OFFSET_ENA()) {\n      vportOX = pa_cl_vport_xoffset.VPORT_XOFFSET();\n   } else {\n      vportOX = raWidth / 2;\n   }\n   if (pa_cl_vte_cntl.VPORT_Y_OFFSET_ENA()) {\n      vportOY  = pa_cl_vport_yoffset.VPORT_YOFFSET();\n   } else {\n      vportOY = raHeight / 2;\n   }\n   if (pa_cl_vte_cntl.VPORT_X_SCALE_ENA()) {\n      vportSX = pa_cl_vport_xscale.VPORT_XSCALE();\n   } else {\n      vportSX = raWidth / 2;\n   }\n   if (pa_cl_vte_cntl.VPORT_Y_SCALE_ENA()) {\n      vportSY = pa_cl_vport_yscale.VPORT_YSCALE();\n   } else {\n      vportSY = raHeight / 2;\n   }\n\n   vk::Viewport viewport;\n\n   viewport.x = vportOX - vportSX;\n   viewport.y = vportOY - vportSY;\n   viewport.width = vportSX * 2;\n   viewport.height = vportSY * 2;\n\n   // the view port is 'flipped by default'\n   viewport.y = viewport.height + viewport.y;\n   viewport.height = -viewport.height;\n\n   // TODO: Investigate whether we should be using ZOFFSET/ZSCALE to calculate these?\n   viewport.minDepth = pa_sc_vport_zmin.VPORT_ZMIN();\n   viewport.maxDepth = pa_sc_vport_zmax.VPORT_ZMAX();\n\n   mCurrentDraw->viewport = viewport;\n\n\n   // Set up some stuff used by the shaders\n   ShaderViewportData shaderViewport;\n\n   // These are not handled as I don't believe we actually scale/offset by the viewport...\n   //pa_cl_vte_cntl.VPORT_Z_OFFSET_ENA();\n   //pa_cl_vte_cntl.VPORT_Z_SCALE_ENA();\n\n   // TODO: Implement these\n   //pa_cl_vte_cntl.VTX_XY_FMT();\n   //pa_cl_vte_cntl.VTX_Z_FMT();\n   //pa_cl_vte_cntl.VTX_W0_FMT();\n\n   auto screenSizeX = viewport.width;\n   auto screenSizeY = -viewport.height;\n\n   if (pa_cl_vte_cntl.VPORT_X_OFFSET_ENA()) {\n      shaderViewport.xAdd = 0.0f;\n   } else {\n      shaderViewport.xAdd = -1.0f;\n   }\n   if (pa_cl_vte_cntl.VPORT_X_SCALE_ENA()) {\n      shaderViewport.xMul = 1.0f;\n   } else {\n      shaderViewport.xMul = 2.0f / screenSizeX;\n   }\n   if (pa_cl_vte_cntl.VPORT_Y_OFFSET_ENA()) {\n      shaderViewport.yAdd = 0.0f;\n   } else {\n      shaderViewport.yAdd = -1.0f;\n   }\n   if (pa_cl_vte_cntl.VPORT_Y_SCALE_ENA()) {\n      shaderViewport.yMul = 1.0f;\n   } else {\n      shaderViewport.yMul = 2.0f / screenSizeY;\n   }\n\n   if (!pa_cl_clip_cntl.DX_CLIP_SPACE_DEF()) {\n      // map gl(-1 to 1) onto vk(0 to 1)\n      shaderViewport.zAdd = 1.0f; // Add W\n      shaderViewport.zMul = 0.5f; // * 0.5\n   } else {\n      // maintain 0 to 1\n      shaderViewport.zAdd = 0.0f; // Add 0\n      shaderViewport.zMul = 1.0f; // * 1.0\n   }\n\n   mCurrentDraw->shaderViewportData = shaderViewport;\n\n\n\n   // ------------------------------------------------------------\n   // Scissoring\n   // ------------------------------------------------------------\n\n   auto pa_sc_generic_scissor_tl = getRegister<latte::PA_SC_GENERIC_SCISSOR_TL>(latte::Register::PA_SC_GENERIC_SCISSOR_TL);\n   auto pa_sc_generic_scissor_br = getRegister<latte::PA_SC_GENERIC_SCISSOR_BR>(latte::Register::PA_SC_GENERIC_SCISSOR_BR);\n\n   vk::Rect2D scissor;\n   scissor.offset.x = pa_sc_generic_scissor_tl.TL_X();\n   scissor.offset.y = pa_sc_generic_scissor_tl.TL_Y();\n   scissor.extent.width = pa_sc_generic_scissor_br.BR_X() - scissor.offset.x;\n   scissor.extent.height = pa_sc_generic_scissor_br.BR_Y() - scissor.offset.y;\n\n   mCurrentDraw->scissor = scissor;\n   return true;\n}\n\nvoid\nDriver::bindViewportAndScissor()\n{\n   mActiveCommandBuffer.setViewport(0, { mCurrentDraw->viewport });\n   mActiveCommandBuffer.setScissor(0, { mCurrentDraw->scissor });\n}\n\n} // namespace vulkan\n\n#endif // ifdef DECAF_VULKAN\n"
  },
  {
    "path": "src/libgpu/vulkan_shaders/gpu7_tiling.comp.glsl",
    "content": "#version 450\n\nlayout(constant_id = 0) const bool IsUntiling = true;\nlayout(constant_id = 1) const uint MicroTileThickness = 1;\nlayout(constant_id = 2) const uint MacroTileWidth = 1;\nlayout(constant_id = 3) const uint MacroTileHeight = 1;\nlayout(constant_id = 4) const bool IsMacro3X = false;\nlayout(constant_id = 5) const bool IsBankSwapped = false;\nlayout(constant_id = 6) const uint BitsPerElement = 8;\nlayout(constant_id = 7) const bool IsDepth = false;\n#ifndef DECAF_MVK_COMPAT\nlayout(constant_id = 8) const uint SubGroupSize = 32;\n#endif\n\n// Specify our grouping setup\nlayout(local_size_x_id = 8, local_size_y = 1, local_size_z = 1) in;\n\n// Information about the GPU tiling itself.\nconst uint MicroTileWidth = 8;\nconst uint MicroTileHeight = 8;\nconst uint NumPipes = 2;\nconst uint NumBanks = 4;\n\nconst uint PipeInterleaveBytes = 256;\nconst uint NumGroupBits = 8;\nconst uint NumPipeBits = 1;\nconst uint NumBankBits = 2;\nconst uint GroupMask = ((1 << NumGroupBits) - 1);\n\n// Setup some convenience information based on our constants\nconst bool IsMacroTiling = (MacroTileWidth > 1 || MacroTileHeight > 1);\nconst uint BytesPerElement = BitsPerElement / 8;\nconst uint MicroTileBytes = MicroTileWidth * MicroTileHeight * MicroTileThickness * BytesPerElement;\nconst uint MacroTileBytes = MacroTileWidth * MacroTileHeight * MicroTileBytes;\n\nlayout(push_constant) uniform Parameters {\n   uint firstSliceIndex;\n   uint maxTiles;\n\n   // Micro tiling parameters\n   uint numTilesPerRow;\n   uint numTilesPerSlice;\n   uint thinMicroTileBytes;\n   uint thickSliceBytes;\n\n   // Macro tiling parameters\n   uint bankSwizzle;\n   uint pipeSwizzle;\n   uint bankSwapWidth;\n} params;\n\n// Set up the shader inputs\nlayout(std430, binding = 0) buffer tiledBuffer4 { uint tiled4[]; };\nlayout(std430, binding = 1) buffer untiledBuffer4 { uint untiled4[]; };\n\nvoid copyElems4(uint untiledOffset, uint tiledOffset, uint numElems)\n{\n   if (IsUntiling) {\n       for (uint i = 0; i < numElems; ++i) {\n         untiled4[(untiledOffset / 4) + i] = tiled4[(tiledOffset / 4) + i];\n      }\n   } else {\n       for (uint i = 0; i < numElems; ++i) {\n         tiled4[(tiledOffset / 4) + i] = untiled4[(untiledOffset / 4) + i];\n      }\n   }\n}\n\n#ifndef DECAF_MVK_COMPAT\nlayout(std430, binding = 0) buffer tiledBuffer8 { uvec2 tiled8[]; };\nlayout(std430, binding = 1) buffer untiledBuffer8 { uvec2 untiled8[]; };\n\nlayout(std430, binding = 0) buffer tiledBuffer16 { uvec4 tiled16[]; };\nlayout(std430, binding = 1) buffer untiledBuffer16 { uvec4 untiled16[]; };\n\nvoid copyElems8(uint untiledOffset, uint tiledOffset, uint numElems)\n{\n   if (IsUntiling) {\n       for (uint i = 0; i < numElems; ++i) {\n         untiled8[(untiledOffset / 8) + i] = tiled8[(tiledOffset / 8) + i];\n      }\n   } else {\n       for (uint i = 0; i < numElems; ++i) {\n         tiled8[(tiledOffset / 8) + i] = untiled8[(untiledOffset / 8) + i];\n      }\n   }\n}\n\nvoid copyElems16(uint untiledOffset, uint tiledOffset, uint numElems)\n{\n   if (IsUntiling) {\n       for (uint i = 0; i < numElems; ++i) {\n         untiled16[(untiledOffset / 16)] = tiled16[(tiledOffset / 16)];\n      }\n   } else {\n       for (uint i = 0; i < numElems; ++i) {\n         tiled16[(tiledOffset / 16)] = untiled16[(untiledOffset / 16)];\n      }\n   }\n}\n#else\nvoid copyElems8(uint untiledOffset, uint tiledOffset, uint numElems)\n{\n   copyElems4(untiledOffset, tiledOffset, numElems * 2);\n}\n\nvoid copyElems16(uint untiledOffset, uint tiledOffset, uint numElems)\n{\n   copyElems4(untiledOffset, tiledOffset, numElems * 4);\n}\n#endif\n\nvoid retileMicro8(uint tiledOffset, uint untiledOffset, uint untiledStride)\n{\n   const uint tiledStride = MicroTileWidth;\n   const uint rowElems = MicroTileWidth / 8;\n\n   for (uint y = 0; y < MicroTileHeight; y += 4) {\n      const uint untiledRow0 = untiledOffset + 0 * untiledStride;\n      const uint untiledRow1 = untiledOffset + 1 * untiledStride;\n      const uint untiledRow2 = untiledOffset + 2 * untiledStride;\n      const uint untiledRow3 = untiledOffset + 3 * untiledStride;\n\n      const uint tiledRow0 = tiledOffset + 0 * tiledStride;\n      const uint tiledRow1 = tiledOffset + 1 * tiledStride;\n      const uint tiledRow2 = tiledOffset + 2 * tiledStride;\n      const uint tiledRow3 = tiledOffset + 3 * tiledStride;\n\n      copyElems8(untiledRow0, tiledRow0, rowElems);\n      copyElems8(untiledRow1, tiledRow2, rowElems);\n      copyElems8(untiledRow2, tiledRow1, rowElems);\n      copyElems8(untiledRow3, tiledRow3, rowElems);\n\n      untiledOffset += 4 * untiledStride;\n      tiledOffset += 4 * tiledStride;\n   }\n}\n\nvoid retileMicro16(uint tiledOffset, uint untiledOffset, uint untiledStride)\n{\n   const uint tiledStride = MicroTileWidth * 2;\n   const uint rowElems = MicroTileWidth * 2 / 16;\n\n   for (uint y = 0; y < MicroTileHeight; ++y) {\n      copyElems16(untiledOffset, tiledOffset, rowElems);\n\n      untiledOffset += untiledStride;\n      tiledOffset += tiledStride;\n   }\n}\n\nvoid retileMicro32(uint tiledOffset, uint untiledOffset, uint untiledStride)\n{\n   const uint tiledStride = MicroTileWidth * 4;\n   const uint groupElems = 4 * 4 / 16;\n\n   for (uint y = 0; y < MicroTileHeight; y += 2) {\n      const uint untiledRow1 = untiledOffset + 0 * untiledStride;\n      const uint untiledRow2 = untiledOffset + 1 * untiledStride;\n\n      const uint tiledRow1 = tiledOffset + 0 * tiledStride;\n      const uint tiledRow2 = tiledOffset + 1 * tiledStride;\n\n      copyElems16(untiledRow1 + 0, tiledRow1 + 0, groupElems);\n      copyElems16(untiledRow1 + 16, tiledRow2 + 0, groupElems);\n\n      copyElems16(untiledRow2 + 0, tiledRow1 + 16, groupElems);\n      copyElems16(untiledRow2 + 16, tiledRow2 + 16, groupElems);\n\n      tiledOffset += tiledStride * 2;\n      untiledOffset += untiledStride * 2;\n   }\n}\n\nvoid retileMicro64(uint tiledOffset, uint untiledOffset, uint untiledStride)\n{\n   const uint tiledStride = MicroTileWidth * 8;\n   const uint groupElems = 2 * (64 / 8) / 16;\n\n   // This will automatically DCE'd by compiler if the below ifdef is not used.\n   const uint nextGroupOffset = tiledOffset + (0x100 << (NumBankBits + NumPipeBits));\n\n   for (uint y = 0; y < MicroTileHeight; y += 2) {\n      if (IsMacroTiling && y == 4) {\n         tiledOffset = nextGroupOffset;\n      }\n\n      const uint untiledRow1 = untiledOffset + 0 * untiledStride;\n      const uint untiledRow2 = untiledOffset + 1 * untiledStride;\n\n      const uint tiledRow1 = tiledOffset + 0 * tiledStride;\n      const uint tiledRow2 = tiledOffset + 1 * tiledStride;\n\n      copyElems16(untiledRow1 + 0, tiledRow1 + 0, groupElems);\n      copyElems16(untiledRow2 + 0, tiledRow1 + 16, groupElems);\n\n      copyElems16(untiledRow1 + 16, tiledRow1 + 32, groupElems);\n      copyElems16(untiledRow2 + 16, tiledRow1 + 48, groupElems);\n\n      copyElems16(untiledRow1 + 32, tiledRow2 + 0, groupElems);\n      copyElems16(untiledRow2 + 32, tiledRow2 + 16, groupElems);\n\n      copyElems16(untiledRow1 + 48, tiledRow2 + 32, groupElems);\n      copyElems16(untiledRow2 + 48, tiledRow2 + 48, groupElems);\n\n      tiledOffset += tiledStride * 2;\n      untiledOffset += untiledStride * 2;\n   }\n}\n\nvoid retileMicro128(uint tiledOffset, uint untiledOffset, uint untiledStride)\n{\n   const uint tiledStride = MicroTileWidth * 16;\n   const uint groupBytes = 16;\n   const uint groupElems = 16 / 16;\n\n   for (uint y = 0; y < MicroTileHeight; y += 2) {\n      const uint untiledRow1 = untiledOffset + 0 * untiledStride;\n      const uint untiledRow2 = untiledOffset + 1 * untiledStride;\n\n      const uint tiledRow1 = tiledOffset + 0 * tiledStride;\n      const uint tiledRow2 = tiledOffset + 1 * tiledStride;\n\n      copyElems16(untiledRow1 + 0 * groupBytes, tiledRow1 + 0 * groupBytes, groupElems);\n      copyElems16(untiledRow1 + 1 * groupBytes, tiledRow1 + 2 * groupBytes, groupElems);\n      copyElems16(untiledRow2 + 0 * groupBytes, tiledRow1 + 1 * groupBytes, groupElems);\n      copyElems16(untiledRow2 + 1 * groupBytes, tiledRow1 + 3 * groupBytes, groupElems);\n\n      copyElems16(untiledRow1 + 2 * groupBytes, tiledRow1 + 4 * groupBytes, groupElems);\n      copyElems16(untiledRow1 + 3 * groupBytes, tiledRow1 + 6 * groupBytes, groupElems);\n      copyElems16(untiledRow2 + 2 * groupBytes, tiledRow1 + 5 * groupBytes, groupElems);\n      copyElems16(untiledRow2 + 3 * groupBytes, tiledRow1 + 7 * groupBytes, groupElems);\n\n      copyElems16(untiledRow1 + 4 * groupBytes, tiledRow2 + 0 * groupBytes, groupElems);\n      copyElems16(untiledRow1 + 5 * groupBytes, tiledRow2 + 2 * groupBytes, groupElems);\n      copyElems16(untiledRow2 + 4 * groupBytes, tiledRow2 + 1 * groupBytes, groupElems);\n      copyElems16(untiledRow2 + 5 * groupBytes, tiledRow2 + 3 * groupBytes, groupElems);\n\n      copyElems16(untiledRow1 + 6 * groupBytes, tiledRow2 + 4 * groupBytes, groupElems);\n      copyElems16(untiledRow1 + 7 * groupBytes, tiledRow2 + 6 * groupBytes, groupElems);\n      copyElems16(untiledRow2 + 6 * groupBytes, tiledRow2 + 5 * groupBytes, groupElems);\n      copyElems16(untiledRow2 + 7 * groupBytes, tiledRow2 + 7 * groupBytes, groupElems);\n\n      if (IsMacroTiling) {\n         tiledOffset += 0x100 << (NumBankBits + NumPipeBits);\n      } else {\n         tiledOffset += tiledStride * 2;\n      }\n\n      untiledOffset += untiledStride * 2;\n   }\n}\n\nvoid copyDepthXYGroup(uint tiledOffset, uint untiledOffset, uint untiledStride,\n                      uint tX, uint tY, uint uX, uint uY)\n{\n   const uint groupBytes = 2 * BytesPerElement;\n   const uint tiledStride = MicroTileWidth * BytesPerElement;\n\n   copyElems4(untiledOffset + uY * untiledStride + uX * groupBytes, tiledOffset + tY * tiledStride + tX * groupBytes, groupBytes);\n}\nvoid retileMicroDepth(uint tiledOffset, uint untiledOffset, uint untiledStride)\n{\n   for (uint y = 0; y < MicroTileHeight; y += 4) {\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 0, y + 0, 0, y + 0);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 1, y + 0, 0, y + 1);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 2, y + 0, 1, y + 0);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 3, y + 0, 1, y + 1);\n\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 0, y + 1, 0, y + 2);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 1, y + 1, 0, y + 3);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 2, y + 1, 1, y + 2);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 3, y + 1, 1, y + 3);\n\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 0, y + 2, 2, y + 0);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 1, y + 2, 2, y + 1);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 2, y + 2, 3, y + 0);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 3, y + 2, 3, y + 1);\n\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 0, y + 3, 2, y + 2);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 1, y + 3, 2, y + 3);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 2, y + 3, 3, y + 2);\n      copyDepthXYGroup(tiledOffset, untiledOffset, untiledStride, 3, y + 3, 3, y + 3);\n   }\n}\n\nvoid mainMicroTiling(uint tileIndex)\n{\n   const uint thinSliceBytes = params.thickSliceBytes / MicroTileThickness;\n   const uint untiledStride = params.numTilesPerRow * MicroTileWidth * BytesPerElement;\n   const uint thickMicroTileBytes = params.thinMicroTileBytes * MicroTileThickness;\n\n   const uint dispatchSliceIndex = tileIndex / params.numTilesPerSlice;\n   const uint sliceTileIndex = tileIndex % params.numTilesPerSlice;\n\n   // Find the global slice index we are currently at.\n   const uint srcSliceIndex = params.firstSliceIndex + dispatchSliceIndex;\n\n   // We need to identify where inside the current thick slice we are.\n   const uint localSliceIndex = srcSliceIndex % MicroTileThickness;\n\n   // Calculate the offset to our untiled data starting from the thick slice\n   const uint srcTileY = sliceTileIndex / params.numTilesPerRow;\n   const uint srcTileX = sliceTileIndex % params.numTilesPerRow;\n\n   uint untiledOffset =\n      (localSliceIndex * thinSliceBytes) +\n      (srcTileX * MicroTileWidth * BytesPerElement) +\n      (srcTileY * params.numTilesPerRow * params.thinMicroTileBytes);\n\n   // Calculate the offset to our tiled data starting from the thick slice\n   uint tiledOffset =\n      (localSliceIndex * params.thinMicroTileBytes) +\n      sliceTileIndex * thickMicroTileBytes;\n\n   // In the case that we are using thick micro tiles, we need to advance our\n     // offsets to the current thick slice boundary that we are at.\n   const uint firstThickSliceIndex = params.firstSliceIndex / MicroTileThickness;\n   const uint thickSliceIndex = srcSliceIndex / MicroTileThickness;\n   const uint thickSliceOffset = (thickSliceIndex - firstThickSliceIndex) * params.thickSliceBytes;\n   tiledOffset += thickSliceOffset;\n   untiledOffset += thickSliceOffset;\n\n   // The untiled pointers are offset forward by the local slice index already,\n   // we need to back it up since our calculations above consider it.\n   const uint firstThinSliceIndex = params.firstSliceIndex % MicroTileThickness;\n   untiledOffset -= firstThinSliceIndex * thinSliceBytes;\n\n   if (IsDepth) {\n      retileMicroDepth(tiledOffset, untiledOffset, untiledStride);\n   } else {\n      if (BitsPerElement == 8) {\n         retileMicro8(tiledOffset, untiledOffset, untiledStride);\n      } else if (BitsPerElement == 16) {\n         retileMicro16(tiledOffset, untiledOffset, untiledStride);\n      } else if (BitsPerElement == 32) {\n         retileMicro32(tiledOffset, untiledOffset, untiledStride);\n      } else if (BitsPerElement == 64) {\n         retileMicro64(tiledOffset, untiledOffset, untiledStride);\n      } else if (BitsPerElement == 128) {\n         retileMicro128(tiledOffset, untiledOffset, untiledStride);\n      }\n   }\n}\n\nvoid mainMacroTiling(uint tileIndex)\n{\n   const uint thinSliceBytes = params.thickSliceBytes / MicroTileThickness;\n   const uint untiledStride = params.numTilesPerRow * MicroTileWidth * BytesPerElement;\n\n   const uint dispatchSliceIndex = tileIndex / params.numTilesPerSlice;\n   const uint sliceTileIndex = tileIndex % params.numTilesPerSlice;\n\n   // Find the global slice index we are currently at.\n   const uint srcSliceIndex = params.firstSliceIndex + dispatchSliceIndex;\n\n   // We need to identify where inside the current thick slice we are.\n   const uint localSliceIndex = srcSliceIndex % MicroTileThickness;\n\n   // Calculate the thickSliceIndex\n   const uint thickSliceIndex = srcSliceIndex / MicroTileThickness;\n\n   // Calculate our tile positions\n   const uint microTilesPerMacro = MacroTileWidth * MacroTileHeight;\n   const uint macroTilesPerRow = params.numTilesPerRow / MacroTileWidth;\n   const uint microTilesPerMacroRow = microTilesPerMacro * macroTilesPerRow;\n\n   const uint srcMacroTileY = sliceTileIndex / microTilesPerMacroRow;\n   const uint macroRowTileIndex = sliceTileIndex % microTilesPerMacroRow;\n\n   const uint srcMacroTileX = macroRowTileIndex / microTilesPerMacro;\n   const uint microTileIndex = macroRowTileIndex % microTilesPerMacro;\n\n   const uint srcMicroTileY = microTileIndex / MacroTileWidth;\n   const uint srcMicroTileX = microTileIndex % MacroTileWidth;\n\n   const uint srcTileX = srcMacroTileX * MacroTileWidth + srcMicroTileX;\n   const uint srcTileY = srcMacroTileY * MacroTileHeight + srcMicroTileY;\n\n   // Figure out what our untiled offset shall be\n   uint untiledOffset =\n      (localSliceIndex * thinSliceBytes) +\n      (srcTileX * MicroTileWidth * BytesPerElement) +\n      (srcTileY * MicroTileHeight * untiledStride);\n\n   // Calculate the offset to our untiled data starting from the thick slice\n   const uint macroTileIndex = (srcMacroTileY * macroTilesPerRow) + srcMacroTileX;\n   const uint macroTileOffset = macroTileIndex * MacroTileBytes;\n   const uint tiledBaseOffset =\n      (macroTileOffset >> (NumBankBits + NumPipeBits)) +\n      (localSliceIndex * params.thinMicroTileBytes);\n\n   const uint offsetHigh = (tiledBaseOffset & ~GroupMask) << (NumBankBits + NumPipeBits);\n   const uint offsetLow = tiledBaseOffset & GroupMask;\n\n   // Calculate our bank/pipe/sample rotations and swaps\n   uint bankSliceRotation = 0;\n   uint pipeSliceRotation = 0;\n   if (!IsMacro3X) {\n      // 2_ format\n      bankSliceRotation = ((NumBanks >> 1) - 1)* thickSliceIndex;\n   } else {\n      // 3_ format\n      bankSliceRotation = thickSliceIndex / NumPipes;\n      pipeSliceRotation = thickSliceIndex;\n   }\n\n   uint bankSwapRotation = 0;\n   if (IsBankSwapped) {\n      const uint bankSwapOrder[] = { 0, 1, 3, 2 };\n      const uint swapIndex = ((srcMacroTileX * MicroTileWidth * MacroTileWidth) / params.bankSwapWidth);\n      bankSwapRotation = bankSwapOrder[swapIndex % NumBanks];\n   }\n\n   uint bank = 0;\n   bank |= ((srcTileX >> 0) & 1) ^ ((srcTileY >> 2) & 1);\n   bank |= (((srcTileX >> 1) & 1) ^ ((srcTileY >> 1) & 1)) << 1;\n   bank ^= (params.bankSwizzle + bankSliceRotation) & (NumBanks - 1);\n   bank ^= bankSwapRotation;\n\n   uint pipe = 0;\n   pipe |= ((srcTileX >> 0) & 1) ^ ((srcTileY >> 0) & 1);\n   pipe ^= (params.pipeSwizzle + pipeSliceRotation) & (NumPipes - 1);\n\n   uint tiledOffset =\n      (bank << (NumGroupBits + NumPipeBits)) |\n      (pipe << NumGroupBits) |\n      offsetLow | offsetHigh;\n\n   // In the case that we are using thick micro tiles, we need to advance our\n   // offsets to the current thick slice boundary that we are at.\n   const uint firstThickSliceIndex = params.firstSliceIndex / MicroTileThickness;\n   const uint thickSliceOffset = (thickSliceIndex - firstThickSliceIndex) * params.thickSliceBytes;\n   tiledOffset += thickSliceOffset;\n   untiledOffset += thickSliceOffset;\n\n   // The untiled pointers are offset forward by the local slice index already,\n   // we need to back it up since our calculations above consider it.\n   const uint firstThinSliceIndex = params.firstSliceIndex % MicroTileThickness;\n   untiledOffset -= firstThinSliceIndex * thinSliceBytes;\n\n   if (IsDepth) {\n      retileMicroDepth(tiledOffset, untiledOffset, untiledStride);\n   } else {\n      if (BitsPerElement == 8) {\n         retileMicro8(tiledOffset, untiledOffset, untiledStride);\n      } else if (BitsPerElement == 16) {\n         retileMicro16(tiledOffset, untiledOffset, untiledStride);\n      } else if (BitsPerElement == 32) {\n         retileMicro32(tiledOffset, untiledOffset, untiledStride);\n      } else if (BitsPerElement == 64) {\n         retileMicro64(tiledOffset, untiledOffset, untiledStride);\n      } else if (BitsPerElement == 128) {\n         retileMicro128(tiledOffset, untiledOffset, untiledStride);\n      }\n   }\n}\n\nvoid main()\n{\n   const uint tileIndex = gl_GlobalInvocationID.x;\n   if (tileIndex >= params.maxTiles) {\n      return;\n   }\n\n   if (IsMacroTiling) {\n      mainMacroTiling(tileIndex);\n   } else {\n      mainMicroTiling(tileIndex);\n   }\n}"
  },
  {
    "path": "tests/CMakeLists.txt",
    "content": "project(tests)\ninclude_directories(\"../src\")\n\nadd_subdirectory(\"cpu\")\nadd_subdirectory(\"gpu\")\n"
  },
  {
    "path": "tests/cpu/CMakeLists.txt",
    "content": "project(tests-cpu)\n\nadd_subdirectory(\"libcpu\")\nadd_subdirectory(\"runner-achurch\")\nadd_subdirectory(\"runner-generated\")\n"
  },
  {
    "path": "tests/cpu/fuzz-compare/fuzztests.cpp",
    "content": "#include <algorithm>\n#include <random>\n#include <string>\n#include \"fuzztests.h\"\n#include \"common/bitutils.h\"\n#include \"common/log.h\"\n#include \"libcpu/src/interpreter/interpreter.h\"\n#include \"libcpu/src/interpreter/interpreter_insreg.h\"\n#include \"libcpu/src/jit/jit.h\"\n#include \"libcpu/state.h\"\n#include \"libcpu/src/utils.h\"\n#include \"libcpu/mem.h\"\n#include \"libcpu/trace.h\"\n#include \"libcpu/espresso/espresso_instructionset.h\"\n#include \"libcpu/espresso/espresso_spr.h\"\nusing namespace espresso;\n\ntemplate<size_t SIZE, class T> inline size_t array_size(T (&arr)[SIZE]) {\n   return SIZE;\n}\n\nstruct InstructionFuzzData {\n   uint32_t baseInstr;\n   std::vector<InstructionField> allFields;\n};\n\nstatic const uint32_t instructionBase = mem::MEM2Base;\nstatic const uint32_t dataBase = instructionBase + 0x01000000;\nstd::vector<InstructionFuzzData> instructionFuzzData;\n\nbool buildFuzzData(InstructionID instrId, InstructionFuzzData &fuzzData)\n{\n   if (instrId == InstructionID::Invalid) {\n      return true;\n   }\n\n   auto data = findInstructionInfo(instrId);\n\n   // Verify JIT and Interpreter have everything registered...\n   bool hasInterpHandler = cpu::interpreter::hasInstruction(static_cast<InstructionID>(instrId));\n   bool hasJitHandler = cpu::jit::hasInstruction(static_cast<InstructionID>(instrId));\n   if ((hasInterpHandler ^ hasJitHandler) != 0) {\n      if (!hasInterpHandler) {\n         gLog->error(\"Instruction {} has a JIT handler but no Interpreter handler\", data->name);\n      }\n      if (!hasJitHandler) {\n         gLog->error(\"Instruction {} has a Interpreter handler but no JIT handler\", data->name);\n      }\n      return false;\n   }\n\n   uint32_t instr = 0x00000000;\n   uint32_t instrBits = 0x00000000;\n   for (auto &op : data->opcode) {\n      auto field = op.field;\n      auto value = op.value;\n      auto start = getInstructionFieldStart(field);\n      if (start >= 32) continue;\n      auto fieldBits = getInstructionFieldBitmask(field);\n      if (instrBits & fieldBits) {\n         gLog->error(\"Instruction {} opcode {} overwrites bits\", data->name, (uint32_t)field);\n         gLog->error(\"  {:032b} on {:032b}\", fieldBits, instrBits);\n         return false;\n      }\n      instrBits |= fieldBits;\n      instr |= value << start;\n   }\n\n   std::vector<InstructionField> allFields;\n\n   for (auto i : data->read) {\n      if (std::find(allFields.begin(), allFields.end(), i) == allFields.end()) {\n         allFields.push_back(i);\n      }\n   }\n   for (auto i : data->write) {\n      if (std::find(allFields.begin(), allFields.end(), i) == allFields.end()) {\n         allFields.push_back(i);\n      }\n   }\n   for (auto i : data->flags) {\n      if (std::find(allFields.begin(), allFields.end(), i) == allFields.end()) {\n         allFields.push_back(i);\n      }\n   }\n\n   for (auto i : allFields) {\n      if (isInstructionFieldMarker(i)) continue;\n      auto fieldBits = getInstructionFieldBitmask(i);\n      if (instrBits & fieldBits) {\n         gLog->error(\"Instruction {} field {} overwrites bits\", data->name, (uint32_t)i);\n         gLog->error(\"  {:032b} on {:032b}\", fieldBits, instrBits);\n         return false;\n      }\n      instrBits |= fieldBits;\n   }\n\n   if (instrBits != 0xFFFFFFFF) {\n      gLog->error(\"Instruction {} does not describe all its bits\", data->name);\n      gLog->error(\"  {:032b}\", instrBits);\n      return false;\n   }\n\n   fuzzData.baseInstr = instr;\n   fuzzData.allFields = std::move(allFields);\n   return true;\n}\n\nbool\nsetupFuzzData() {\n   instructionFuzzData.resize((size_t)InstructionID::InstructionCount);\n   bool res = true;\n   for (int i = 0; i < (int)InstructionID::InstructionCount; ++i) {\n      res &= buildFuzzData((InstructionID)i, instructionFuzzData[i]);\n   }\n   return res;\n}\n\nvoid setFieldValue(Instruction &instr, InstructionField field, uint32_t value) {\n   instr.value |= (value << getInstructionFieldStart(field)) & getInstructionFieldBitmask(field);\n}\n\nbool\ncompareStateField(int field, const TraceFieldValue &x, const TraceFieldValue &y)\n{\n   if (field >= StateField::FPR0 && field <= StateField::FPR31) {\n      return fabs(x.f64v0 - y.f64v0) < 0.0001 && fabs(x.f64v1 - y.f64v1) < 0.0001;\n   }\n   return x.u64v0 == y.u64v0 && x.u64v1 == y.u64v1;\n}\n\nbool\ncompareStateField(int field, const TraceFieldValue &x, const TraceFieldValue &y, const TraceFieldValue &m, bool neg = false)\n{\n   if (field >= StateField::FPR0 && field <= StateField::FPR31) {\n      uint64_t xa = x.u64v0 & m.u64v0;\n      uint64_t xb = x.u64v1 & m.u64v1;\n      uint64_t ya = y.u64v0 & m.u64v0;\n      uint64_t yb = y.u64v1 & m.u64v1;\n      return (*(double*)&xa) == (*(double*)&ya) && (*(double*)&xb) == (*(double*)&yb);\n   }\n\n   return (x.u32v0 & m.u32v0) == (y.u32v0 & m.u32v0) &&\n      (x.u32v0 & m.u32v1) == (y.u32v0 & m.u32v1) &&\n      (x.u32v0 & m.u32v2) == (y.u32v0 & m.u32v2) &&\n      (x.u32v0 & m.u32v3) == (y.u32v0 & m.u32v3);\n}\n\nbool\nexecuteInstrTest(uint32_t test_seed)\n{\n   std::mt19937 test_rand(test_seed);\n   InstructionID instrId = (InstructionID)(test_rand() % (int)InstructionID::InstructionCount);\n\n   // Special cases that we can't test easily\n   switch (instrId) {\n   case InstructionID::Invalid:\n      return true;\n   case InstructionID::lmw:\n   case InstructionID::lswi:\n   case InstructionID::lswx:\n   case InstructionID::stmw:\n   case InstructionID::stswi:\n   case InstructionID::stswx:\n      // Multi-word logic, disabled for now\n      return true;\n   case InstructionID::psq_l:\n   case InstructionID::psq_lu:\n   case InstructionID::psq_lux:\n   case InstructionID::psq_lx:\n   case InstructionID::psq_st:\n   case InstructionID::psq_stu:\n   case InstructionID::psq_stux:\n   case InstructionID::psq_stx:\n      // Quantization Registers need to be properly configured for these, disabled for now\n      return true;\n   case InstructionID::b:\n   case InstructionID::bc:\n   case InstructionID::bcctr:\n   case InstructionID::bclr:\n      // Branching cannot be fuzzed\n      return true;\n   case InstructionID::kc:\n      // Emulator Instruction\n      return true;\n   case InstructionID::sc:\n   case InstructionID::tw:\n   case InstructionID::twi:\n   case InstructionID::mfsr:\n   case InstructionID::mfsrin:\n   case InstructionID::mtsr:\n   case InstructionID::mtsrin:\n      // Supervisory Instructions\n      return true;\n   default:\n      break;\n   }\n\n   const auto data = findInstructionInfo(instrId);\n   const InstructionFuzzData *fuzzData = &instructionFuzzData[(int)instrId];\n   if (!data || !fuzzData) {\n      return false;\n   }\n\n   if (!cpu::interpreter::hasInstruction(instrId)) {\n      // No handler, skip it...\n      return true;\n   }\n\n   Instruction instr(fuzzData->baseInstr);\n\n   {\n      // TODO: Add handling for rA==0 being 0 :S\n      uint32_t gprAlloc = 0;\n      uint32_t fprAlloc = 0;\n      uint32_t gqrAlloc = 0;\n      static const uint32_t gprAllocatable[] = { 5, 6, 7, 8, 9 };\n      static const uint32_t fprAllocatable[] = { 0, 1, 2, 3 };\n      static const uint32_t gqrAllocatable[] = { 0, 1, 2, 3 };\n      auto nextGpr = [&]() {\n         assert(gprAlloc < array_size(gprAllocatable));\n         return gprAllocatable[gprAlloc++];\n      };\n      auto nextFpr = [&]() {\n         assert(fprAlloc < array_size(fprAllocatable));\n         return fprAllocatable[fprAlloc++];\n      };\n      auto nextGqr = [&]() {\n         assert(gqrAlloc < array_size(gqrAllocatable));\n         return gqrAllocatable[gqrAlloc++];\n      };\n      for (auto i : fuzzData->allFields) {\n         if (isInstructionFieldMarker(i)) {\n            continue;\n         }\n\n         switch (i) {\n         case InstructionField::rA: // gpr Targets\n         case InstructionField::rB:\n         case InstructionField::rD:\n         case InstructionField::rS:\n            setFieldValue(instr, i, nextGpr());\n            break;\n         case InstructionField::frA: // fpr Targets\n         case InstructionField::frB:\n         case InstructionField::frC:\n         case InstructionField::frD:\n         case InstructionField::frS:\n            setFieldValue(instr, i, nextFpr());\n            break;\n         case InstructionField::i: // gqr Targets\n         case InstructionField::qi:\n            setFieldValue(instr, i, test_rand() & 4);\n         case InstructionField::crbA: // crb Targets\n         case InstructionField::crbB:\n         case InstructionField::crbD:\n            setFieldValue(instr, i, test_rand());\n            break;\n         case InstructionField::crfD: // crf Targets\n         case InstructionField::crfS:\n            setFieldValue(instr, i, test_rand());\n            break;\n         case InstructionField::imm: // Random Values\n         case InstructionField::simm:\n         case InstructionField::uimm:\n         case InstructionField::rc: // Record Condition\n         case InstructionField::frc:\n         case InstructionField::oe:\n         case InstructionField::crm:\n         case InstructionField::fm:\n         case InstructionField::w:\n         case InstructionField::qw:\n         case InstructionField::sh: // Shift Registers\n         case InstructionField::mb:\n         case InstructionField::me:\n            setFieldValue(instr, i, test_rand());\n            break;\n         case InstructionField::d: // Memory Delta...\n         case InstructionField::qd:\n            setFieldValue(instr, i, test_rand());\n            break;\n         case InstructionField::spr: // Special Purpose Registers\n         {\n            SPR validSprs[] = {\n               SPR::XER,\n               SPR::CTR,\n               SPR::GQR0,\n               SPR::GQR1,\n               SPR::GQR2,\n               SPR::GQR3,\n               SPR::GQR4,\n               SPR::GQR5,\n               SPR::GQR6,\n               SPR::GQR7\n            };\n            encodeSPR(instr, validSprs[test_rand() % array_size(validSprs)]);\n            break;\n         }\n         case InstructionField::tbr: // Time Base Registers\n         {\n            SPR validTbrs[] = {\n               SPR::TBL,\n               SPR::TBU\n            };\n            encodeSPR(instr, validTbrs[test_rand() % array_size(validTbrs)]);\n            break;\n         }\n         case InstructionField::l:\n            // l always must be 0\n            instr.l = 0;\n            break;\n\n         default:\n            gLog->error(\"Instruction {} field {} is unsupported by fuzzer\", data->name, (uint32_t)i);\n            return false;\n         }\n      }\n   }\n\n   // Write an instruction\n   mem::write(instructionBase + 0, instr.value);\n\n   // Write a return for the Interpreter\n   auto bclr = encodeInstruction(InstructionID::bclr);\n   bclr.bo = 0x1f;\n   mem::write(instructionBase + 4, bclr.value);\n\n#define STATEFIELDO(x, y) (StateField::Field)((int)x + y)\n   StateField::Field randFields[] = {\n      STATEFIELDO(StateField::GPR, 0),\n      STATEFIELDO(StateField::GPR, 5),\n      STATEFIELDO(StateField::GPR, 6),\n      STATEFIELDO(StateField::GPR, 7),\n      STATEFIELDO(StateField::GPR, 8),\n      STATEFIELDO(StateField::GPR, 9),\n      STATEFIELDO(StateField::FPR, 0),\n      STATEFIELDO(StateField::FPR, 1),\n      STATEFIELDO(StateField::FPR, 2),\n      STATEFIELDO(StateField::FPR, 3),\n      STATEFIELDO(StateField::GQR, 0),\n      STATEFIELDO(StateField::GQR, 1),\n      STATEFIELDO(StateField::GQR, 2),\n      STATEFIELDO(StateField::GQR, 3),\n      StateField::CR,\n      StateField::FPSCR,\n      StateField::XER,\n      StateField::CTR\n   };\n#undef STATEFIELDO\n   size_t numRandFields = array_size(randFields);\n\n   // Build some randomized state data\n   cpu::Core iState, jState;\n   for (auto i = 0u; i < numRandFields; ++i) {\n      auto field = randFields[i];\n\n      TraceFieldValue v;\n      v.u32v0 = test_rand();\n      v.u32v1 = test_rand();\n      v.u32v2 = test_rand();\n      v.u32v3 = test_rand();\n\n      restoreStateField(&iState, field, v);\n      restoreStateField(&jState, field, v);\n   }\n\n   // Build some randomized memory data\n   const uint32_t memSize = 64;\n   uint8_t iMem[memSize], jMem[memSize];\n   for (auto i = 0u; i < memSize; ++i) {\n      auto randVal = (uint8_t)test_rand();\n      iMem[i] = randVal;\n      jMem[i] = randVal;\n   }\n\n   // Some instructions need to be forced to a certain address\n#define SETGPR(i, v) \\\n   iState.gpr[i]=v; \\\n   jState.gpr[i]=v;\n#define CONFIG_rA_rB() { \\\n      auto d = static_cast<int32_t>(test_rand()); \\\n      SETGPR(instr.rA, d); \\\n      SETGPR(instr.rB, dataBase - d); \\\n      break; }\n#define CONFIG_rA_D() { \\\n      auto d = sign_extend<16, int32_t>(instr.d); \\\n      SETGPR(instr.rA, dataBase - d); \\\n      break; }\n#define CONFIG_rA_QD() { \\\n      auto d = sign_extend<12, int32_t>(instr.qd); \\\n      SETGPR(instr.rA, dataBase - d); \\\n      break; }\n\n   switch (instrId) {\n   case InstructionID::lbz: CONFIG_rA_D();\n   case InstructionID::lbzu: CONFIG_rA_D();\n   case InstructionID::lha: CONFIG_rA_D();\n   case InstructionID::lhau: CONFIG_rA_D();\n   case InstructionID::lhz: CONFIG_rA_D();\n   case InstructionID::lhzu: CONFIG_rA_D();\n   case InstructionID::lwz: CONFIG_rA_D();\n   case InstructionID::lwzu: CONFIG_rA_D();\n   case InstructionID::lfs: CONFIG_rA_D();\n   case InstructionID::lfsu: CONFIG_rA_D();\n   case InstructionID::lfd: CONFIG_rA_D();\n   case InstructionID::lfdu: CONFIG_rA_D();\n   case InstructionID::lbzx: CONFIG_rA_rB();\n   case InstructionID::lbzux: CONFIG_rA_rB();\n   case InstructionID::lhax: CONFIG_rA_rB();\n   case InstructionID::lhaux: CONFIG_rA_rB();\n   case InstructionID::lhbrx: CONFIG_rA_rB();\n   case InstructionID::lhzx: CONFIG_rA_rB();\n   case InstructionID::lhzux: CONFIG_rA_rB();\n   case InstructionID::lwbrx: CONFIG_rA_rB();\n   case InstructionID::lwarx: CONFIG_rA_rB();\n   case InstructionID::lwzx: CONFIG_rA_rB();\n   case InstructionID::lwzux: CONFIG_rA_rB();\n   case InstructionID::lfsx: CONFIG_rA_rB();\n   case InstructionID::lfsux: CONFIG_rA_rB();\n   case InstructionID::lfdx: CONFIG_rA_rB();\n   case InstructionID::lfdux: CONFIG_rA_rB();\n\n   case InstructionID::stb: CONFIG_rA_D();\n   case InstructionID::stbu: CONFIG_rA_D();\n   case InstructionID::sth: CONFIG_rA_D();\n   case InstructionID::sthu: CONFIG_rA_D();\n   case InstructionID::stw: CONFIG_rA_D();\n   case InstructionID::stwu: CONFIG_rA_D();\n   case InstructionID::stfs: CONFIG_rA_D();\n   case InstructionID::stfsu: CONFIG_rA_D();\n   case InstructionID::stfd: CONFIG_rA_D();\n   case InstructionID::stfdu: CONFIG_rA_D();\n   case InstructionID::stbx: CONFIG_rA_rB();\n   case InstructionID::stbux: CONFIG_rA_rB();\n   case InstructionID::sthx: CONFIG_rA_rB();\n   case InstructionID::sthux: CONFIG_rA_rB();\n   case InstructionID::stwx: CONFIG_rA_rB();\n   case InstructionID::stwux: CONFIG_rA_rB();\n   case InstructionID::sthbrx: CONFIG_rA_rB();\n   case InstructionID::stwbrx: CONFIG_rA_rB();\n   case InstructionID::stwcx: CONFIG_rA_rB();\n   case InstructionID::stfsx: CONFIG_rA_rB();\n   case InstructionID::stfsux: CONFIG_rA_rB();\n   case InstructionID::stfdx: CONFIG_rA_rB();\n   case InstructionID::stfdux: CONFIG_rA_rB();\n   case InstructionID::stfiwx: CONFIG_rA_rB();\n\n   case InstructionID::psq_l: CONFIG_rA_QD();\n   case InstructionID::psq_lx: CONFIG_rA_rB();\n   case InstructionID::psq_lu: CONFIG_rA_QD();\n   case InstructionID::psq_lux: CONFIG_rA_rB();\n\n   case InstructionID::dcbz: CONFIG_rA_rB();\n   case InstructionID::dcbz_l: CONFIG_rA_rB();\n\n   default:\n      break;\n   }\n\n#undef SETGPR\n#undef CONFIG_rA_rB\n#undef CONFIG_rA_D\n#undef CONFIG_rA_QD\n\n\n   // Disable Reserveds for now\n   iState.reserve = false;\n   jState.reserve = false;\n\n   // Required to be set to this\n   iState.tracer = nullptr;\n   iState.cia = 0;\n   iState.nia = instructionBase;\n   jState.tracer = nullptr;\n   jState.cia = 0;\n   jState.nia = instructionBase;\n\n   {\n      memcpy(mem::translate(dataBase), iMem, memSize);\n      cpu::interpreter::executeSub(&iState);\n      memcpy(iMem, mem::translate(dataBase), memSize);\n   }\n\n   {\n      cpu::jit::clearCache();\n\n      memcpy(mem::translate(dataBase), jMem, memSize);\n      cpu::jit::executeSub(&jState);\n      memcpy(jMem, mem::translate(dataBase), memSize);\n   }\n\n   for (auto i = 0u; i < numRandFields; ++i) {\n      auto field = randFields[i];\n\n      TraceFieldValue iVal, jVal;\n      saveStateField(&iState, field, iVal);\n      saveStateField(&jState, field, jVal);\n\n      if (!compareStateField(field, iVal, jVal)) {\n         gLog->warn(\"{}({:08x}) :: JIT does not match Interp on {}\", data->name, test_seed, getStateFieldName(field));\n      }\n   }\n\n   return true;\n}\n\nbool\nexecuteFuzzTests(uint32_t suite_seed)\n{\n   if (!setupFuzzData()) {\n      return false;\n   }\n\n   std::mt19937 suite_rand(suite_seed);\n\n   for (auto i = 0; i < 10000; ++i) {\n      if (false) {\n         gLog->info(\"Executing test {}\", i);\n      }\n\n      executeInstrTest(suite_rand());\n   }\n\n   return true;\n}\n"
  },
  {
    "path": "tests/cpu/fuzz-compare/fuzztests.h",
    "content": "#pragma once\n#include <cstdint>\n\nbool\nexecuteFuzzTests(uint32_t suite_seed = 0x12345678);\n"
  },
  {
    "path": "tests/cpu/fuzz-compare/main.cpp",
    "content": "#include <memory>\n#include <spdlog/spdlog.h>\n#include \"fuzztests.h\"\n#include \"libcpu/mem.h\"\n\nstd::shared_ptr<spdlog::logger>\ngLog;\n\nint main(int argc, char *argv[])\n{\n   gLog = std::make_shared<spdlog::logger>(\"logger\", std::make_shared<spdlog::sinks::stdout_sink_st>());\n   gLog->set_level(spdlog::level::debug);\n\n   cpu::initialise();\n\n   auto result = executeFuzzTests() ? 0 : 1;\n\n   return result;\n}\n"
  },
  {
    "path": "tests/cpu/generator/client/code_test.s",
    "content": "# rD, rA, rB, oe, rc, simm, XER\n#\n# rA, rB, rD -> affects gpr\n# rc -> affects cr0 (cr)\n# oe -> affects overflow flag (xer)\n#\n# r3...r6\n# f1...f4\n#\n# struct TestState\n# {\n#    uint32_t xer;   // 0x00\n#    uint32_t cr;    // 0x04\n#    uint32_t ctr    // 0x08\n#    uint32_t _      // 0x0c\n#    uint32_t r3;    // 0x10\n#    uint32_t r4;    // 0x14\n#    uint32_t r5;    // 0x18\n#    uint32_t r6;    // 0x1C\n#    uint64_t fpscr  // 0x20\n#    double f1       // 0x28\n#    double f2       // 0x30\n#    double f3       // 0x38\n#    double f4       // 0x40\n# } // 0x48\n#\n# struct StackSavedState\n# {\n#    uint32_t xer;   // 0x00\n#    uint32_t cr;    // 0x04\n#    double/u64 fpscr; // 0x08\n#    uint32_t lr;    // 0x10\n# } // 0x14\n\n.global funcTest\nfuncTest:\n   li r3, 1337\n   blr\n\n# void executeCodeTest(TestState *state, void *func)\n.global executeCodeTest\nexecuteCodeTest:\n   # Create space on stack for StackSavedState\n   stwu r1, -0x20(r1)\n\n   # r10 = state\n   mr r10, r3\n\n   # Save System registers\n   mfxer r3\n   stw r3, 0x00(r1)\n\n   mfcr r3\n   stw r3, 0x04(r1)\n\n   mffs f3\n   stfd f3, 0x08(r1)\n\n   mflr r3\n   stw r3, 0x10(r1)\n\n   # Put func to call into lr\n   mtlr r4\n\n   # Load Test State\n   lwz r3, 0x00(r10)\n   mtxer r3\n\n   lwz r3, 0x04(r10)\n   mtcr r3\n\n   lwz r3, 0x08(r10)\n   mtctr r3\n\n   lwz r3, 0x10(r10)\n   lwz r4, 0x14(r10)\n   lwz r5, 0x18(r10)\n   lwz r6, 0x1C(r10)\n   li r0, 0\n\n   lfd f3, 0x20(r10)\n   mtfsf 0xFF, r3\n\n   lfd f1, 0x28(r10)\n   lfd f2, 0x30(r10)\n   lfd f3, 0x38(r10)\n   lfd f4, 0x40(r10)\n\n   # Execute test\n   blrl\n\n   # Save Test State\n   stw r3, 0x10(r10)\n   stw r4, 0x14(r10)\n   stw r5, 0x18(r10)\n   stw r6, 0x1C(r10)\n\n   mfxer r3\n   stw r3, 0x00(r10)\n\n   mfcr r3\n   stw r3, 0x04(r10)\n\n   mfctr r3\n   stw r3, 0x08(r10)\n\n   mffs f5\n   stfd f5, 0x20(r10)\n\n   stfd f1, 0x28(r10)\n   stfd f2, 0x30(r10)\n   stfd f3, 0x38(r10)\n   stfd f4, 0x40(r10)\n\n   # Restore System registers\n   lwz r3, 0x00(r1)\n   mtxer r3\n\n   lwz r3, 0x04(r1)\n   mtcr r3\n\n   lfd f3, 0x08(r1)\n   mtfsf 0xFF, f3\n\n   lwz r3, 0x10(r1)\n   mtlr r3\n\n   # Restore stack pointer and return\n   addi r1, r1, 0x20\n   blr\n"
  },
  {
    "path": "tests/cpu/generator/client/console.c",
    "content": "#include \"console.h\"\n\nvoid allocConsole(struct SystemFunctions *funcs, struct ConsoleData *console)\n{\n   unsigned i;\n   console->activeLine = 0;\n\n   for (i = 0; i < MAX_CONSOLE_LINES; ++i) {\n      console->line[i] = funcs->OSAllocFromSystem(MAX_CONSOLE_LINE_LENGTH, 4);\n      console->line[i][0] = 0;\n   }\n}\n\nvoid freeConsole(struct SystemFunctions *funcs, struct ConsoleData *console)\n{\n   unsigned i;\n\n   for (i = 0; i < MAX_CONSOLE_LINES; ++i) {\n      funcs->OSFreeToSystem(console->line[i]);\n   }\n}\n\nvoid renderConsole(struct ConsoleData *console)\n{\n   unsigned i, j;\n\n   for(i = 0; i < 2; i++)\n   {\n      fillScreen(0, 0, 0, 0);\n\n      for (j = 0; j < MAX_CONSOLE_LINES; ++j) {\n         drawString(0, j, console->line[j]);\n      }\n\n      flipBuffers();\n   }\n}\n\nchar *nextConsoleLine(struct ConsoleData *console)\n{\n   if (console->activeLine < MAX_CONSOLE_LINES) {\n      return console->line[console->activeLine++];\n   } else {\n      char *nextLine = console->line[0];\n      unsigned i;\n\n      // Shift all lines up screen 1\n      for (i = 0; i < MAX_CONSOLE_LINES - 1; ++i) {\n         console->line[i] = console->line[i + 1];\n      }\n\n      // Move 0th line to end of console, reuse for nextLine\n      console->line[MAX_CONSOLE_LINES - 1] = nextLine;\n\n      // Clear line and return it\n      nextLine[0] = 0;\n      return nextLine;\n   }\n}\n"
  },
  {
    "path": "tests/cpu/generator/client/console.h",
    "content": "#ifndef CONSOLE_H\n#define CONSOLE_H\n#include \"sysfuncs.h\"\n#include \"../../../libwiiu/src/coreinit.h\"\n#include \"../../../libwiiu/src/types.h\"\n\n#define MAX_CONSOLE_LINE_LENGTH 64\n#define MAX_CONSOLE_LINES 16\n#define LOG(c, ...) __os_snprintf(nextConsoleLine(c), MAX_CONSOLE_LINE_LENGTH, __VA_ARGS__); renderConsole(&consoleData);\n\nstruct ConsoleData\n{\n   unsigned activeLine;\n   char *line[MAX_CONSOLE_LINES];\n};\n\nvoid allocConsole(struct SystemFunctions *funcs, struct ConsoleData *console);\nvoid freeConsole(struct SystemFunctions *funcs, struct ConsoleData *console);\nvoid renderConsole(struct ConsoleData *console);\nchar *nextConsoleLine(struct ConsoleData *console);\n\n#endif\n"
  },
  {
    "path": "tests/cpu/generator/client/loader.c",
    "content": "#include \"loader.h\"\n\nvoid _start()\n{\n\t/****************************>            Fix Stack            <****************************/\n\t//Load a good stack\n\tasm(\n\t\t\"lis %r1, 0x1ab5 ;\"\n\t\t\"ori %r1, %r1, 0xd138 ;\"\n\t\t);\n\t/****************************>           Get Handles           <****************************/\n\t//Get a handle to coreinit.rpl\n\tunsigned int coreinit_handle;\n\tOSDynLoad_Acquire(\"coreinit.rpl\", &coreinit_handle);\n\t/****************************>       External Prototypes       <****************************/\n\t//OSScreen functions\n\tvoid(*OSScreenInit)();\n\tunsigned int(*OSScreenGetBufferSizeEx)(unsigned int bufferNum);\n\tunsigned int(*OSScreenSetBufferEx)(unsigned int bufferNum, void * addr);\n\t//OS Memory functions\n\tvoid*(*memset)(void * dest, uint32_t value, uint32_t bytes);\n\tvoid*(*OSAllocFromSystem)(uint32_t size, int align);\n\tvoid(*OSFreeToSystem)(void *ptr);\n\t//IM functions\n\tint(*IM_Open)();\n\tint(*IM_Close)(int fd);\n\tint(*IM_SetDeviceState)(int fd, void *mem, int state, int a, int b);\n\t/****************************>             Exports             <****************************/\n\t//OSScreen functions\n\tOSDynLoad_FindExport(coreinit_handle, 0, \"OSScreenInit\", &OSScreenInit);\n\tOSDynLoad_FindExport(coreinit_handle, 0, \"OSScreenGetBufferSizeEx\", &OSScreenGetBufferSizeEx);\n\tOSDynLoad_FindExport(coreinit_handle, 0, \"OSScreenSetBufferEx\", &OSScreenSetBufferEx);\n\t//OS Memory functions\n\tOSDynLoad_FindExport(coreinit_handle, 0, \"memset\", &memset);\n\tOSDynLoad_FindExport(coreinit_handle, 0, \"OSAllocFromSystem\", &OSAllocFromSystem);\n\tOSDynLoad_FindExport(coreinit_handle, 0, \"OSFreeToSystem\", &OSFreeToSystem);\n\t//IM functions\n\tOSDynLoad_FindExport(coreinit_handle, 0, \"IM_Open\", &IM_Open);\n\tOSDynLoad_FindExport(coreinit_handle, 0, \"IM_Close\", &IM_Close);\n\tOSDynLoad_FindExport(coreinit_handle, 0, \"IM_SetDeviceState\", &IM_SetDeviceState);\n\t/****************************>          Initial Setup          <****************************/\n\t//Restart system to get lib access\n\tint fd = IM_Open();\n\tvoid *mem = OSAllocFromSystem(0x100, 64);\n\tmemset(mem, 0, 0x100);\n\t//set restart flag to force quit browser\n\tIM_SetDeviceState(fd, mem, 3, 0, 0); \n\tIM_Close(fd);\n\tOSFreeToSystem(mem);\n\t//wait a bit for browser end\n\tunsigned int t1 = 0x1FFFFFFF;\n\twhile(t1--) ;\n\t//Call the Screen initilzation function.\n\tOSScreenInit();\n\t//Grab the buffer size for each screen (TV and gamepad)\n\tint buf0_size = OSScreenGetBufferSizeEx(0);\n\tint buf1_size = OSScreenGetBufferSizeEx(1);\n\t//Set the buffer area.\n\tOSScreenSetBufferEx(0, (void *)0xF4000000);\n\tOSScreenSetBufferEx(1, (void *)0xF4000000 + buf0_size);\n\t//Clear both framebuffers.\n\tint ii = 0;\n\tfor (ii; ii < 2; ii++)\n\t{\n\t\tfillScreen(0,0,0,0);\n\t\tflipBuffers();\n\t}\n\t//Jump to entry point.\n\t_entryPoint();\n}"
  },
  {
    "path": "tests/cpu/generator/client/loader.h",
    "content": "#ifndef LOADER_H\n#define LOADER_H\n\n#include \"../../../libwiiu/src/coreinit.h\"\n#include \"../../../libwiiu/src/vpad.h\"\n#include \"../../../libwiiu/src/types.h\"\n#include \"../../../libwiiu/src/draw.h\"\n\n\n#include \"program.h\"\n\nvoid _start();\n\nvoid _entryPoint();\n#endif /* LOADER_H */"
  },
  {
    "path": "tests/cpu/generator/client/program.c",
    "content": "#include \"program.h\"\n#include \"console.h\"\n#include \"sysfuncs.h\"\n\n#define CLIENT_VERSION 1\n\n#define ALIGN_BACKWARD(x,align) \\\n   ((typeof(x))(((unsigned int)(x)) & (~(align-1))))\n\nint sendwait(struct SystemFunctions *sysFuncs, int sock, const void *buffer, int len);\nint recvwait(struct SystemFunctions *sysFuncs, int sock, void *buffer, int len);\n\nstruct PacketHeader\n{\n   uint16_t size;\n   uint16_t command;\n};\n\nstruct VersionPacket\n{\n   struct PacketHeader header;\n   uint32_t version;\n};\n\nstruct ExecuteCodeTestPacket\n{\n   struct PacketHeader header;\n   uint32_t instr;\n   struct TestState state;\n};\n\nvoid writeInstruction(struct SystemFunctions *sysFuncs, void *func, uint32_t instr)\n{\n   // Write code\n   uint32_t *topatch = (uint32_t*)(0xA0000000 + (uint32_t)func);\n   topatch[0] = instr;        // instr\n   topatch[1] = 0x4E800020;   // blr\n\n   // Flush caches\n   unsigned int *faddr = ALIGN_BACKWARD(topatch, 32);\n   sysFuncs->DCFlushRange(faddr, 0x40);\n   sysFuncs->ICInvalidateRange(faddr, 0x40);\n}\n\nvoid _entryPoint()\n{\n   struct SystemFunctions sysFuncs;\n   struct ConsoleData consoleData;\n\n   loadSysFuncs(&sysFuncs);\n   allocConsole(&sysFuncs, &consoleData);\n\n   char *packetBuffer = (char *)sysFuncs.OSAllocFromSystem(4096, 16);\n   sysFuncs.socket_lib_init();\n\n   int error;\n   VPADData vpad_data;\n   sysFuncs.VPADRead(0, &vpad_data, 1, &error);\n\n   struct sockaddr sin;\n   sysFuncs.memset(&sin, 0, sizeof(struct sockaddr));\n   sin.sin_family = AF_INET;\n   sin.sin_port = 8008;\n   sin.sin_addr.s_addr = ((192<<24) | (168<<16) | (1<<8) | (67<<0));\n   LOG(&consoleData, \"Create socket\");\n   int sockfd = sysFuncs.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);\n   LOG(&consoleData, \"Connecting...\");\n   int status = sysFuncs.connect(sockfd, &sin, 0x10);\n   LOG(&consoleData, \"Connected.\");\n\n   bool hasKernelPermissions = false;\n   bool running = true;\n\n   if (sysFuncs.OSEffectiveToPhysical((void *)0xA0000000) != (void *)0x31000000) {\n      LOG(&consoleData, \"Running without kernel permissions\");\n      hasKernelPermissions = false;\n   } else {\n      hasKernelPermissions = true;\n   }\n\n   if (status) {\n      sockfd = 0;\n      LOG(&consoleData, \"Error connecting to server, status = %d\", status);\n   } else {\n      while (running) {\n         // Read packet header\n         if (recvwait(&sysFuncs, sockfd, packetBuffer, 4) <= 0) {\n            LOG(&consoleData, \"Error reading packet header\");\n            break;\n         }\n\n         // Read packet data\n         uint16_t packetSize = ((uint16_t*)packetBuffer)[0];\n         uint16_t packetCmd = ((uint16_t*)packetBuffer)[1];\n         uint16_t remaining = packetSize - 4;\n\n         if (remaining) {\n            if (recvwait(&sysFuncs, sockfd, packetBuffer + 4, remaining) <= 0) {\n               LOG(&consoleData, \"Error reading packet data\");\n               break;\n            }\n         }\n\n         // Process packet\n         switch(packetCmd) {\n         case 1: {\n            // Version\n            struct VersionPacket *packet = (struct VersionPacket*)packetBuffer;\n            LOG(&consoleData, \"Received server version %d\", packet->version);\n\n            // Reply with client version\n            packet->version = CLIENT_VERSION;\n            if (sendwait(&sysFuncs, sockfd, packet, packet->header.size) <= 0) {\n               LOG(&consoleData, \"Error sending version packet\");\n               running = false;\n            }\n         } break;\n         case 10: {\n            struct ExecuteCodeTestPacket *packet = (struct ExecuteCodeTestPacket*)packetBuffer;\n\n            if (hasKernelPermissions) {\n               writeInstruction(&sysFuncs, sysFuncs.PPCMtpmc4, packet->instr);\n               executeCodeTest(&packet->state, sysFuncs.PPCMtpmc4);\n            } else {\n               LOG(&consoleData, \"Skipping code test %08X (requires kernel permissions)\", packet->instr);\n            }\n\n            // Reply with test results\n            if (sendwait(&sysFuncs, sockfd, packet, packet->header.size) <= 0) {\n               LOG(&consoleData, \"Error sending test results.\");\n               running = false;\n            }\n         } break;\n         case 11: {\n            LOG(&consoleData, \"Tests finished\");\n            running = false;\n         } break;\n         }\n      }\n   }\n\nrenderLoop:\n   while(1)\n   {\n      sysFuncs.VPADRead(0, &vpad_data, 1, &error);\n\n      // Exit when HOME button pressed\n      if(vpad_data.btn_hold & BUTTON_HOME) {\n         break;\n      }\n\n      renderConsole(&consoleData);\n   }\n\n   // Safely exit\n   {\n      unsigned i;\n      sysFuncs.socket_lib_finish();\n      freeConsole(&sysFuncs, &consoleData);\n      sysFuncs.OSFreeToSystem(packetBuffer);\n\n      for(i = 0; i < 2; i++)\n      {\n         fillScreen(0,0,0,0);\n         flipBuffers();\n      }\n\n      sysFuncs._Exit();\n   }\n}\n\nint recvwait(struct SystemFunctions *sysFuncs, int sock, void *buffer, int len)\n{\n   int ret;\n   int recvd = 0;\n\n   while (len > 0) {\n      ret = sysFuncs->recv(sock, buffer, len, 0);\n\n      if (ret <= 0) {\n         return ret;\n      }\n\n      recvd += ret;\n      len -= ret;\n      buffer += ret;\n   }\n\n   return recvd;\n}\n\nint sendwait(struct SystemFunctions *sysFuncs, int sock, const void *buffer, int len)\n{\n   int ret;\n   int recvd = 0;\n\n   while (len > 0) {\n      ret = sysFuncs->send(sock, buffer, len, 0);\n\n      if (ret <= 0) {\n         return ret;\n      }\n\n      len -= ret;\n      buffer += ret;\n   }\n\n   return recvd;\n}\n"
  },
  {
    "path": "tests/cpu/generator/client/program.h",
    "content": "#ifndef PROGRAM_H\n#define PROGRAM_H\n#include \"../../../libwiiu/src/coreinit.h\"\n#include \"../../../libwiiu/src/vpad.h\"\n#include \"../../../libwiiu/src/types.h\"\n#include \"../../../libwiiu/src/draw.h\"\n#include \"../../../libwiiu/src/socket.h\"\n\nstruct TestState\n{\n   uint32_t xer;     // 0x00\n   uint32_t cr;      // 0x04\n   uint32_t ctr;     // 0x0c\n   uint32_t _;       // 0x08\n   uint32_t r3;      // 0x10\n   uint32_t r4;      // 0x14\n   uint32_t r5;      // 0x18\n   uint32_t r6;      // 0x1C\n   uint64_t fpscr;   // 0x20\n   double f1;        // 0x28\n   double f2;        // 0x30\n   double f3;        // 0x38\n   double f4;        // 0x40\n}; // 0x48\n\nextern void executeCodeTest(struct TestState *state, void *func);\n\nvoid _entryPoint();\n\n#endif /* PROGRAM_H */"
  },
  {
    "path": "tests/cpu/generator/client/sysfuncs.c",
    "content": "#include \"sysfuncs.h\"\n#include \"../../../libwiiu/src/coreinit.h\"\n\nvoid loadSysFuncs(struct SystemFunctions *sysFuncs)\n{\n   unsigned coreinit, vpad, nsysnet;\n\n   OSDynLoad_Acquire(\"coreinit.rpl\", &coreinit);\n   OSDynLoad_FindExport(coreinit, 0, \"memset\", &sysFuncs->memset);\n   OSDynLoad_FindExport(coreinit, 0, \"_Exit\", &sysFuncs->_Exit);\n   OSDynLoad_FindExport(coreinit, 0, \"ICInvalidateRange\", &sysFuncs->ICInvalidateRange);\n   OSDynLoad_FindExport(coreinit, 0, \"DCFlushRange\", &sysFuncs->DCFlushRange);\n   OSDynLoad_FindExport(coreinit, 0, \"OSAllocFromSystem\", &sysFuncs->OSAllocFromSystem);\n   OSDynLoad_FindExport(coreinit, 0, \"OSFreeToSystem\", &sysFuncs->OSFreeToSystem);\n   OSDynLoad_FindExport(coreinit, 0, \"OSEffectiveToPhysical\", &sysFuncs->OSEffectiveToPhysical);\n   OSDynLoad_FindExport(coreinit, 0, \"PPCMtpmc4\", &sysFuncs->PPCMtpmc4);\n   sysFuncs->coreinit_handle = coreinit;\n\n   OSDynLoad_Acquire(\"vpad.rpl\", &vpad);\n   OSDynLoad_FindExport(vpad, 0, \"VPADRead\", &sysFuncs->VPADRead);\n   sysFuncs->vpad_handle = vpad;\n\n   OSDynLoad_Acquire(\"nsysnet.rpl\", &nsysnet);\n   OSDynLoad_FindExport(nsysnet, 0, \"socket_lib_init\", &sysFuncs->socket_lib_init);\n   OSDynLoad_FindExport(nsysnet, 0, \"socket_lib_finish\", &sysFuncs->socket_lib_finish);\n   OSDynLoad_FindExport(nsysnet, 0, \"socket\", &sysFuncs->socket);\n   OSDynLoad_FindExport(nsysnet, 0, \"connect\", &sysFuncs->connect);\n   OSDynLoad_FindExport(nsysnet, 0, \"recv\", &sysFuncs->recv);\n   OSDynLoad_FindExport(nsysnet, 0, \"send\", &sysFuncs->send);\n   sysFuncs->nsysnet_handle = nsysnet;\n}\n"
  },
  {
    "path": "tests/cpu/generator/client/sysfuncs.h",
    "content": "#ifndef SYSFUNCS_H\n#define SYSFUNCS_H\n#include \"../../../libwiiu/src/types.h\"\n#include \"../../../libwiiu/src/vpad.h\"\n#include \"../../../libwiiu/src/socket.h\"\n\nstruct SystemFunctions\n{\n   unsigned int coreinit_handle;\n   void *(*OSAllocFromSystem)(uint32_t size, int align);\n   void (*OSFreeToSystem)(void *ptr);\n   void *(*OSEffectiveToPhysical)(const void *);\n   void (*ICInvalidateRange)(const void *, int);\n   void (*DCFlushRange)(const void *, int);\n   void (*PPCMtpmc4)();\n   void (*memset)(void *dst, char val, int bytes);\n   void (*_Exit)();\n\n   unsigned int vpad_handle;\n   int(*VPADRead)(int controller, VPADData *buffer, unsigned int num, int *error);\n\n   unsigned int nsysnet_handle;\n   void (*socket_lib_init)();\n   void (*socket_lib_finish)();\n   int (*socket)(int family, int type, int proto);\n   int (*connect)(int fd, struct sockaddr *addr, int addrlen);\n   int (*recv)(int fd, void *buffer, int len, int flags);\n   int (*send)(int fd, const void *buffer, int len, int flags);\n};\n\nvoid loadSysFuncs(struct SystemFunctions *sysFuncs);\n\n#endif\n"
  },
  {
    "path": "tests/cpu/generator/dataset/generator.cpp",
    "content": "#include <cassert>\n#include <cstdint>\n#include <fstream>\n#include <iostream>\n#include <memory>\n#include <vector>\n#include <spdlog/spdlog.h>\n#include <common/be_val.h>\n#include <common/bitutils.h>\n#include \"libcpu/state.h\"\n#include \"libcpu/espresso/espresso_instructionset.h\"\n#include \"hardware-test/hardwaretests.h\"\n#include \"generator_testlist.h\"\n#include \"generator_valuelist.h\"\n\nusing namespace espresso;\n\nstd::shared_ptr<spdlog::logger>\ngLog;\n\nstatic void\nsetCRB(hwtest::RegisterState &state, uint32_t bit, uint32_t value)\n{\n   state.cr.value = set_bit_value(state.cr.value, 31 - bit, value);\n}\n\nstatic void\ngenerateTests(InstructionInfo *data)\n{\n   std::vector<size_t> indexCur, indexMax;\n   std::vector<bool> flagSet;\n   hwtest::TestFile testFile;\n   auto complete = false;\n   auto completeIndices = false;\n\n   for (auto i = 0; i < data->read.size(); ++i) {\n      auto &field = data->read[i];\n      indexCur.push_back(0);\n\n      switch (field) {\n      case InstructionField::rA:\n      case InstructionField::rB:\n      case InstructionField::rS:\n         indexMax.push_back(gValuesGPR.size());\n         break;\n      case InstructionField::frA:\n      case InstructionField::frB:\n      case InstructionField::frC:\n      case InstructionField::frS:\n         indexMax.push_back(gValuesFPR.size());\n         break;\n      case InstructionField::crbA:\n      case InstructionField::crbB:\n         indexMax.push_back(gValuesCRB.size());\n         break;\n      case InstructionField::simm:\n         indexMax.push_back(gValuesSIMM.size());\n         break;\n      case InstructionField::sh:\n         indexMax.push_back(gValuesSH.size());\n         break;\n      case InstructionField::mb:\n         indexMax.push_back(gValuesMB.size());\n         break;\n      case InstructionField::me:\n         indexMax.push_back(gValuesME.size());\n         break;\n      case InstructionField::uimm:\n         indexMax.push_back(gValuesUIMM.size());\n         break;\n      case InstructionField::XERC:\n         indexMax.push_back(gValuesXERC.size());\n         break;\n      case InstructionField::XERSO:\n         indexMax.push_back(gValuesXERSO.size());\n         break;\n      default:\n         assert(false);\n      }\n   }\n\n   for (auto i = 0; i < data->flags.size(); ++i) {\n      flagSet.push_back(false);\n   }\n\n   while (!complete) {\n      uint32_t gpr = 0, fpr = 0, crf = 0, crb = 0;\n      hwtest::TestData test;\n      memset(&test, 0, sizeof(hwtest::TestData));\n\n      test.instr = encodeInstruction(data->id);\n\n      for (auto i = 0; i < data->read.size(); ++i) {\n         auto index = indexCur[i];\n\n         // Generate read field values\n         switch (data->read[i]) {\n         case InstructionField::rA:\n            test.instr.rA = gpr + hwtest::GPR_BASE;\n            test.input.gpr[gpr++] = gValuesGPR[index];\n            break;\n         case InstructionField::rB:\n            test.instr.rB = gpr + hwtest::GPR_BASE;\n            test.input.gpr[gpr++] = gValuesGPR[index];\n            break;\n         case InstructionField::rS:\n            test.instr.rS = gpr + hwtest::GPR_BASE;\n            test.input.gpr[gpr++] = gValuesGPR[index];\n            break;\n         case InstructionField::frA:\n            test.instr.frA = fpr + hwtest::FPR_BASE;\n            test.input.fr[fpr++] = gValuesFPR[index];\n            break;\n         case InstructionField::frB:\n            test.instr.frB = fpr + hwtest::FPR_BASE;\n            test.input.fr[fpr++] = gValuesFPR[index];\n            break;\n         case InstructionField::frC:\n            test.instr.frC = fpr + hwtest::FPR_BASE;\n            test.input.fr[fpr++] = gValuesFPR[index];\n            break;\n         case InstructionField::frS:\n            test.instr.frS = fpr + hwtest::FPR_BASE;\n            test.input.fr[fpr++] = gValuesFPR[index];\n            break;\n         case InstructionField::crbA:\n            test.instr.crbA = (crb++) + hwtest::CRB_BASE;\n            setCRB(test.input, test.instr.crbA, gValuesCRB[index]);\n            break;\n         case InstructionField::crbB:\n            test.instr.crbB = (crb++) + hwtest::CRB_BASE;\n            setCRB(test.input, test.instr.crbB, gValuesCRB[index]);\n            break;\n         case InstructionField::simm:\n            test.instr.simm = gValuesSIMM[index];\n            break;\n         case InstructionField::sh:\n            test.instr.sh = gValuesSH[index];\n            break;\n         case InstructionField::mb:\n            test.instr.mb = gValuesMB[index];\n            break;\n         case InstructionField::me:\n            test.instr.me = gValuesME[index];\n            break;\n         case InstructionField::uimm:\n            test.instr.uimm = gValuesUIMM[index];\n            break;\n         case InstructionField::XERC:\n            test.input.xer.ca = gValuesXERC[index];\n            break;\n         case InstructionField::XERSO:\n            test.input.xer.so = gValuesXERSO[index];\n            break;\n         default:\n            assert(false);\n         }\n      }\n\n      // Generate write field values\n      for (auto field : data->write) {\n         switch (field) {\n         case InstructionField::rA:\n            test.instr.rA = gpr + hwtest::GPR_BASE;\n            gpr++;\n            break;\n         case InstructionField::rD:\n            test.instr.rD = gpr + hwtest::GPR_BASE;\n            gpr++;\n            break;\n         case InstructionField::frD:\n            test.instr.frD = fpr + hwtest::FPR_BASE;\n            fpr++;\n            break;\n         case InstructionField::crfD:\n            test.instr.crfD = crf + hwtest::CRF_BASE;\n            crf++;\n            break;\n         case InstructionField::crbD:\n            test.instr.crbD = crb + hwtest::CRB_BASE;\n            crb++;\n            break;\n         case InstructionField::XERC:\n         case InstructionField::XERSO:\n         case InstructionField::FCRISI:\n         case InstructionField::FCRZDZ:\n         case InstructionField::FCRIDI:\n         case InstructionField::FCRSNAN:\n            break;\n         default:\n            assert(false);\n         }\n      }\n\n      testFile.tests.emplace_back(test);\n\n      // Increase indices\n      for (auto i = 0; i < indexCur.size(); ++i) {\n         indexCur[i]++;\n\n         if (indexCur[i] < indexMax[i]) {\n            break;\n         } else if (indexCur[i] == indexMax[i]) {\n            indexCur[i] = 0;\n\n            if (i == indexCur.size() - 1) {\n               completeIndices = true;\n            }\n         }\n      }\n\n      if (completeIndices) {\n         if (flagSet.size() == 0) {\n            complete = true;\n            break;\n         }\n\n         completeIndices = false;\n\n         // Do next flag!\n         for (auto i = 0; i < flagSet.size(); ++i) {\n            if (!flagSet[i]) {\n               flagSet[i] = true;\n               break;\n            } else {\n               flagSet[i] = false;\n\n               if (i == flagSet.size() - 1) {\n                  complete = true;\n                  break;\n               }\n            }\n         }\n      }\n   }\n\n   // Save tests to file\n   auto filename = std::string(\"tests/cpu/input/\") + data->name;\n   std::ofstream out { filename, std::ofstream::out | std::ofstream::binary };\n   cereal::BinaryOutputArchive archive(out);\n   archive(testFile);\n}\n\nint main(int argc, char **argv)\n{\n   std::vector<spdlog::sink_ptr> sinks;\n   sinks.push_back(spdlog::sinks::stdout_sink_st::instance());\n   gLog = std::make_shared<spdlog::logger>(\"decaf\", begin(sinks), end(sinks));\n\n   initialiseInstructionSet();\n\n   for (auto &group : gTestInstructions) {\n      for (auto id : group) {\n         auto data = findInstructionInfo(id);\n         generateTests(data);\n      }\n   }\n\n   return 0;\n}\n\n/*\nUnimplemented instructions:\n\n// Floating-Point Status and Control Register\nINS(mcrfs, (crfD), (crfS), (), (opcd == 63, xo1 == 64, !_9_10, !_14_15, !_16_20, !_31), \"\")\nINS(mffs, (frD), (), (rc), (opcd == 63, xo1 == 583, !_11_15, !_16_20), \"\")\nINS(mtfsf, (), (fm, frB), (rc), (opcd == 63, xo1 == 711, !_6, !_15), \"\")\nINS(mtfsfi, (crfD), (), (rc, imm), (opcd == 63, xo1 == 134, !_9_10, !_11_15, !_20), \"\")\n\n// Integer Load\nINS(lbz, (rD), (rA, d), (), (opcd == 34), \"Load Byte and Zero\")\nINS(lbzu, (rD, rA), (rA, d), (), (opcd == 35), \"Load Byte and Zero with Update\")\nINS(lbzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 87, !_31), \"Load Byte and Zero Indexed\")\nINS(lbzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 119, !_31), \"Load Byte and Zero with Update Indexed\")\nINS(lha, (rD), (rA, d), (), (opcd == 42), \"Load Half Word Algebraic\")\nINS(lhau, (rD, rA), (rA, d), (), (opcd == 43), \"Load Half Word Algebraic with Update\")\nINS(lhax, (rD), (rA, rB), (), (opcd == 31, xo1 == 343, !_31), \"Load Half Word Algebraic Indexed\")\nINS(lhaux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 375, !_31), \"Load Half Word Algebraic with Update Indexed\")\nINS(lhz, (rD), (rA, d), (), (opcd == 40), \"Load Half Word and Zero\")\nINS(lhzu, (rD, rA), (rA, d), (), (opcd == 41), \"Load Half Word and Zero with Update\")\nINS(lhzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 279, !_31), \"Load Half Word and Zero Indexed\")\nINS(lhzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 311, !_31), \"Load Half Word and Zero with Update Indexed\")\nINS(lwz, (rD), (rA, d), (), (opcd == 32), \"Load Word and Zero\")\nINS(lwzu, (rD, rA), (rA, d), (), (opcd == 33), \"Load Word and Zero with Update\")\nINS(lwzx, (rD), (rA, rB), (), (opcd == 31, xo1 == 23, !_31), \"Load Word and Zero Indexed\")\nINS(lwzux, (rD, rA), (rA, rB), (), (opcd == 31, xo1 == 55, !_31), \"Load Word and Zero with Update Indexed\")\n\n// Integer Store\nINS(stb, (), (rS, rA, d), (), (opcd == 38), \"Store Byte\")\nINS(stbu, (rA), (rS, rA, d), (), (opcd == 39), \"Store Byte with Update\")\nINS(stbx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 215, !_31), \"Store Byte Indexed\")\nINS(stbux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 247, !_31), \"Store Byte with Update Indexed\")\nINS(sth, (), (rS, rA, d), (), (opcd == 44), \"Store Half Word\")\nINS(sthu, (rA), (rS, rA, d), (), (opcd == 45), \"Store Half Word with Update\")\nINS(sthx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 407, !_31), \"Store Half Word Indexed\")\nINS(sthux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 439, !_31), \"Store Half Word with Update Indexed\")\nINS(stw, (), (rS, rA, d), (), (opcd == 36), \"Store Word\")\nINS(stwu, (rA), (rS, rA, d), (), (opcd == 37), \"Store Word with Update\")\nINS(stwx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 151, !_31), \"Store Word Indexed\")\nINS(stwux, (rA), (rS, rA, rB), (), (opcd == 31, xo1 == 183, !_31), \"Store Word with Update Indexed\")\n\n// Integer Load and Store with Byte Reverse\nINS(lhbrx, (rD), (rA, rB), (), (opcd == 31, xo1 == 790, !_31), \"Load Half Word Byte-Reverse Indexed\")\nINS(lwbrx, (rD), (rA, rB), (), (opcd == 31, xo1 == 534, !_31), \"Load Word Byte-Reverse Indexed\")\nINS(sthbrx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 918, !_31), \"Store Half Word Byte-Reverse Indexed\")\nINS(stwbrx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 662, !_31), \"Store Word Byte-Reverse Indexed\")\n\n// Integer Load and Store Multiple\nINS(lmw, (rD), (rA, d), (), (opcd == 46), \"Load Multiple Words\")\nINS(stmw, (), (rS, rA, d), (), (opcd == 47), \"Store Multiple Words\")\n\n// Integer Load and Store String\nINS(lswi, (rD), (rA, nb), (), (opcd == 31, xo1 == 597, !_31), \"Load String Word Immediate\")\nINS(lswx, (rD), (rA, rB), (), (opcd == 31, xo1 == 533, !_31), \"Load String Word Indexed\")\nINS(stswi, (), (rS, rA, nb), (), (opcd == 31, xo1 == 725, !_31), \"Store String Word Immediate\")\nINS(stswx, (), (rS, rA, rB), (), (opcd == 31, xo1 == 661, !_31), \"Store String Word Indexed\")\n\n// Memory Synchronisation\nINS(eieio, (), (), (), (opcd == 31, xo1 == 854, !_6_10, !_11_15, !_16_20, !_31), \"Enforce In-Order Execution of I/O\")\nINS(isync, (), (), (), (opcd == 19, xo1 == 150, !_6_10, !_11_15, !_16_20, !_31), \"Instruction Synchronise\")\nINS(lwarx, (rD, RSRV), (rA, rB), (), (opcd == 31, xo1 == 20, !_31), \"Load Word and Reserve Indexed\")\nINS(stwcx, (RSRV), (rS, rA, rB), (), (opcd == 31, xo1 == 150, _31 == 1), \"Store Word Conditional Indexed\")\nINS(sync, (), (), (), (opcd == 31, xo1 == 598, !_6_10, !_11_15, !_16_20, !_31), \"Synchronise\")\n\n// Floating-Point Load\nINS(lfd, (frD), (rA, d), (), (opcd == 50), \"Load Floating-Point Double\")\nINS(lfdu, (frD, rA), (rA, d), (), (opcd == 51), \"Load Floating-Point Double with Update\")\nINS(lfdx, (frD), (rA, rB), (), (opcd == 31, xo1 == 599, !_31), \"Load Floating-Point Double Indexed\")\nINS(lfdux, (frD, rA), (rA, rB), (), (opcd == 31, xo1 == 631, !_31), \"Load Floating-Point Double with Update Indexed\")\nINS(lfs, (frD), (rA, d), (), (opcd == 48), \"Load Floating-Point Single\")\nINS(lfsu, (frD, rA), (rA, d), (), (opcd == 49), \"Load Floating-Point Single with Update\")\nINS(lfsx, (frD), (rA, rB), (), (opcd == 31, xo1 == 535, !_31), \"Load Floating-Point Single Indexed\")\nINS(lfsux, (frD, rA), (rA, rB), (), (opcd == 31, xo1 == 567, !_31), \"Load Floating-Point Single with Update Indexed\")\n\n// Floating-Point Store\nINS(stfd, (), (frS, rA, d), (), (opcd == 54), \"Store Floating-Point Double\")\nINS(stfdu, (rA), (frS, rA, d), (), (opcd == 55), \"Store Floating-Point Double with Update\")\nINS(stfdx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 727, !_31), \"Store Floating-Point Double Indexed\")\nINS(stfdux, (rA), (frS, rA, rB), (), (opcd == 31, xo1 == 759, !_31), \"Store Floating-Point Double with Update Indexed\")\nINS(stfiwx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 983, !_31), \"Store Floating-Point as Integer Word Indexed\")\nINS(stfs, (), (frS, rA, d), (), (opcd == 52), \"Store Floating-Point Single\")\nINS(stfsu, (rA), (frS, rA, d), (), (opcd == 53), \"Store Floating-Point Single with Update\")\nINS(stfsx, (), (frS, rA, rB), (), (opcd == 31, xo1 == 663, !_31), \"Store Floating-Point Single Indexed\")\nINS(stfsux, (rA), (frS, rA, rB), (), (opcd == 31, xo1 == 695, !_31), \"Store Floating-Point Single with Update Indexed\")\n\n// Branch\nINS(b, (), (li), (aa, lk), (opcd == 18), \"Branch\")\nINS(bc, (bo), (bi, bd), (aa, lk), (opcd == 16), \"Branch Conditional\")\nINS(bcctr, (bo), (bi, CTR), (lk), (opcd == 19, xo1 == 528, !_16_20), \"Branch Conditional to CTR\")\nINS(bclr, (bo), (bi, LR), (lk), (opcd == 19, xo1 == 16, !_16_20), \"Branch Conditional to LR\")\n\n// System Linkage\nINS(rfi, (), (), (), (opcd == 19, xo1 == 50, !_6_10, !_11_15, !_16_20, !_31), \"\")\nINS(sc, (), (), (), (opcd == 17, !_6_10, !_11_15, !_16_29, _30 == 1, !_31), \"Syscall\")\nINS(kc, (), (kcn), (), (opcd == 1), \"krncall\")\n\n// Trap\nINS(tw, (), (to, rA, rB), (), (opcd == 31, xo1 == 4, !_31), \"\")\nINS(twi, (), (to, rA, simm), (), (opcd == 3), \"\")\n\n// Processor Control\nINS(mcrxr, (crfD), (XERO), (), (opcd == 31, xo1 == 512, !_9_10, !_11_15, !_16_20, !_31), \"Move to Condition Register from XERO\")\nINS(mfcr, (rD), (), (), (opcd == 31, xo1 == 19, !_11_15, !_16_20, !_31), \"Move from Condition Register\")\nINS(mfmsr, (rD), (), (), (opcd == 31, xo1 == 83, !_11_15, !_16_20, !_31), \"Move from Machine State Register\")\nINS(mfspr, (rD), (spr), (), (opcd == 31, xo1 == 339, !_31), \"Move from Special Purpose Register\")\nINS(mftb, (rD), (tbr), (), (opcd == 31, xo1 == 371, !_31), \"Move from Time Base Register\")\nINS(mtcrf, (crm), (rS), (), (opcd == 31, xo1 == 144, !_11, !_20, !_31), \"Move to Condition Register Fields\")\nINS(mtmsr, (), (rS), (), (opcd == 31, xo1 == 146, !_11_15, !_16_20, !_31), \"Move to Machine State Register\")\nINS(mtspr, (spr), (rS), (), (opcd == 31, xo1 == 467, !_31), \"Move to Special Purpose Register\")\n\n// Cache Management\nINS(dcbf, (), (rA, rB), (), (opcd == 31, xo1 == 86, !_6_10, !_31), \"\")\nINS(dcbi, (), (rA, rB), (), (opcd == 31, xo1 == 470, !_6_10, !_31), \"\")\nINS(dcbst, (), (rA, rB), (), (opcd == 31, xo1 == 54, !_6_10, !_31), \"\")\nINS(dcbt, (), (rA, rB), (), (opcd == 31, xo1 == 278, !_6_10, !_31), \"\")\nINS(dcbtst, (), (rA, rB), (), (opcd == 31, xo1 == 246, !_6_10, !_31), \"\")\nINS(dcbz, (), (rA, rB), (), (opcd == 31, xo1 == 1014, !_6_10, !_31), \"\")\nINS(icbi, (), (rA, rB), (), (opcd == 31, xo1 == 982, !_6_10, !_31), \"\")\nINS(dcbz_l, (), (rA, rB), (), (opcd == 4, xo1 == 1014, !_6_10, !_31), \"\")\n\n// Segment Register Manipulation\nINS(mfsr, (rD), (sr), (), (opcd == 31, xo1 == 595, !_11, !_16_20, !_31), \"Move from Segment Register\")\nINS(mfsrin, (rD), (rB), (), (opcd == 31, xo1 == 659, !_11_15, !_31), \"Move from Segment Register Indirect\")\nINS(mtsr, (), (rD, sr), (), (opcd == 31, xo1 == 210, !_11, !_16_20, !_31), \"Move to Segment Register\")\nINS(mtsrin, (), (rD, rB), (), (opcd == 31, xo1 == 242, !_11_15, !_31), \"Move to Segment Register Indirect\")\n\n// Lookaside Buffer Management\nINS(tlbie, (), (rB), (), (opcd == 31, xo1 == 306, !_6_10, !_11_15, !_31), \"\")\nINS(tlbsync, (), (), (), (opcd == 31, xo1 == 566, !_6_10, !_11_15, !_16_20, !_31), \"\")\n\n// External Control\nINS(eciwx, (rD), (rA, rB), (), (opcd == 31, xo1 == 310, !_31), \"\")\nINS(ecowx, (rD), (rA, rB), (), (opcd == 31, xo1 == 438, !_31), \"\")\n\n// Paired-Single Load and Store\nINS(psq_l, (frD), (rA, qd), (w, i), (opcd == 56), \"Paired Single Load\")\nINS(psq_lu, (frD), (rA, qd), (w, i), (opcd == 57), \"Paired Single Load with Update\")\nINS(psq_lx, (frD), (rA, rB), (qw, qi), (opcd == 4, xo3 == 6, !_31), \"Paired Single Load Indexed\")\nINS(psq_lux, (frD), (rA, rB), (qw, qi), (opcd == 4, xo3 == 38, !_31), \"Paired Single Load with Update Indexed\")\nINS(psq_st, (frD), (rA, qd), (w, i), (opcd == 60), \"Paired Single Store\")\nINS(psq_stu, (frD), (rA, qd), (w, i), (opcd == 61), \"Paired Single Store with Update\")\nINS(psq_stx, (frS), (rA, rB), (qw, qi), (opcd == 4, xo3 == 7, !_31), \"Paired Single Store Indexed\")\nINS(psq_stux, (frS), (rA, rB), (qw, qi), (opcd == 4, xo3 == 39, !_31), \"Paired Single Store with Update Indexed\")\n\n// Paired-Single Floating Point Arithmetic\nINS(ps_add, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 21, !_21_25), \"Paired Single Add\")\nINS(ps_div, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 18, !_21_25), \"Paired Single Divide\")\nINS(ps_mul, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 25, !_16_20), \"Paired Single Multiply\")\nINS(ps_sub, (frD, FPSCR), (frA, frB), (rc), (opcd == 4, xo4 == 20, !_21_25), \"Paired Single Subtract\")\nINS(ps_abs, (frD), (frB), (rc), (opcd == 4, xo1 == 264, !_11_15), \"Paired Single Absolute\")\nINS(ps_nabs, (frD), (frB), (rc), (opcd == 4, xo1 == 136, !_11_15), \"Paired Single Negate Absolute\")\nINS(ps_neg, (frD), (frB), (rc), (opcd == 4, xo1 == 40, !_11_15), \"Paired Single Negate\")\nINS(ps_sel, (frD), (frA, frB, frC), (rc), (opcd == 4, xo4 == 23), \"Paired Single Select\")\nINS(ps_res, (frD, FPSCR), (frB), (rc), (opcd == 4, xo4 == 24, !_11_15, !_21_25), \"Paired Single Reciprocal\")\nINS(ps_rsqrte, (frD, FPSCR), (frB), (rc), (opcd == 4, xo4 == 26, !_11_15, !_21_25), \"Paired Single Reciprocal Square Root Estimate\")\nINS(ps_msub, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 28), \"Paired Single Multiply and Subtract\")\nINS(ps_madd, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 29), \"Paired Single Multiply and Add\")\nINS(ps_nmsub, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 30), \"Paired Single Negate Multiply and Subtract\")\nINS(ps_nmadd, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 31), \"Paired Single Negate Multiply and Add\")\nINS(ps_mr, (frD), (frB), (rc), (opcd == 4, xo1 == 72, !_11_15), \"Paired Single Move Register\")\nINS(ps_sum0, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 10), \"Paired Single Sum High\")\nINS(ps_sum1, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 11), \"Paired Single Sum Low\")\nINS(ps_muls0, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 12, !_16_20), \"Paired Single Multiply Scalar High\")\nINS(ps_muls1, (frD, FPSCR), (frA, frC), (rc), (opcd == 4, xo4 == 13, !_16_20), \"Paired Single Multiply Scalar Low\")\nINS(ps_madds0, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 14), \"Paired Single Multiply and Add Scalar High\")\nINS(ps_madds1, (frD, FPSCR), (frA, frB, frC), (rc), (opcd == 4, xo4 == 15), \"Paired Single Multiply and Add Scalar Low\")\nINS(ps_cmpu0, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 0, !_9_10, !_31), \"Paired Single Compare Unordered High\")\nINS(ps_cmpo0, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 32, !_9_10, !_31), \"Paired Single Compare Ordered High\")\nINS(ps_cmpu1, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 64, !_9_10, !_31), \"Paired Single Compare Unordered Low\")\nINS(ps_cmpo1, (crfD, FPSCR), (frA, frB), (), (opcd == 4, xo1 == 96, !_9_10, !_31), \"Paired Single Compare Ordered Low\")\nINS(ps_merge00, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 528), \"Paired Single Merge High\")\nINS(ps_merge01, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 560), \"Paired Single Merge Direct\")\nINS(ps_merge10, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 592), \"Paired Single Merge Swapped\")\nINS(ps_merge11, (frD), (frA, frB), (rc), (opcd == 4, xo1 == 624), \"Paired Single Merge Low\")\n*/\n"
  },
  {
    "path": "tests/cpu/generator/dataset/generator_testlist.h",
    "content": "#pragma once\n#include <vector>\n#include \"libcpu/espresso/espresso_instructionid.h\"\n\nusing espresso::InstructionID;\n\nstatic const auto gIntegerArithmetic =\n{\n   InstructionID::add,\n   InstructionID::addc,\n   InstructionID::adde,\n   InstructionID::addi,\n   InstructionID::addic,\n   InstructionID::addicx,\n   InstructionID::addis,\n   InstructionID::addme,\n   InstructionID::addze,\n   InstructionID::divw,\n   InstructionID::divwu,\n   InstructionID::mulhw,\n   InstructionID::mulhwu,\n   InstructionID::mulli,\n   InstructionID::mullw,\n   InstructionID::neg,\n   InstructionID::subf,\n   InstructionID::subfc,\n   InstructionID::subfe,\n   InstructionID::subfic,\n   InstructionID::subfme,\n   InstructionID::subfze,\n};\n\nstatic const auto gIntegerLogical =\n{\n   InstructionID::and_,\n   InstructionID::andc,\n   InstructionID::andi,\n   InstructionID::andis,\n   InstructionID::cntlzw,\n   InstructionID::eqv,\n   InstructionID::extsb,\n   InstructionID::extsh,\n   InstructionID::nand,\n   InstructionID::nor,\n   InstructionID::or_,\n   InstructionID::orc,\n   InstructionID::ori,\n   InstructionID::oris,\n   InstructionID::xor_,\n   InstructionID::xori,\n   InstructionID::xoris,\n};\n\nstatic const auto gIntegerCompare =\n{\n   InstructionID::cmp,\n   InstructionID::cmpi,\n   InstructionID::cmpl,\n   InstructionID::cmpli,\n};\n\nstatic const auto gIntegerShift =\n{\n   InstructionID::slw,\n   InstructionID::sraw,\n   InstructionID::srawi,\n   InstructionID::srw,\n};\n\nstatic const auto gIntegerRotate =\n{\n   InstructionID::rlwimi,\n   InstructionID::rlwinm,\n   InstructionID::rlwnm,\n};\n\nstatic const auto gConditionRegisterLogical =\n{\n   InstructionID::crand,\n   InstructionID::crandc,\n   InstructionID::creqv,\n   InstructionID::crnand,\n   InstructionID::crnor,\n   InstructionID::cror,\n   InstructionID::crorc,\n   InstructionID::crxor,\n   //InstructionID::mcrf,\n};\n\nstatic const auto gFloatArithmetic =\n{\n   InstructionID::fadd,\n   InstructionID::fadds,\n   InstructionID::fdiv,\n   InstructionID::fdivs,\n   InstructionID::fmul,\n   InstructionID::fmuls,\n   InstructionID::fres,\n   InstructionID::fsub,\n   InstructionID::fsubs,\n   InstructionID::fsel,\n};\n\nstatic const auto gFloatArithmeticMuladd =\n{\n   InstructionID::fmadd,\n   InstructionID::fmadds,\n   InstructionID::fmsub,\n   InstructionID::fmsubs,\n   InstructionID::fnmadd,\n   InstructionID::fnmadds,\n   InstructionID::fnmsub,\n   InstructionID::fnmsubs,\n};\n\nstatic const auto gFloatRound =\n{\n   InstructionID::fctiw,\n   InstructionID::fctiwz,\n   InstructionID::frsp,\n};\n\nstatic const auto gFloatMove =\n{\n   InstructionID::fabs,\n   InstructionID::fmr,\n   InstructionID::fnabs,\n   InstructionID::fneg,\n};\n\nstatic const auto gFloatCompare =\n{\n   InstructionID::fcmpo,\n   InstructionID::fcmpu,\n};\n\nstatic const auto gTestInstructions =\n{\n   gIntegerArithmetic,\n   gIntegerLogical,\n   gIntegerCompare,\n   gIntegerShift,\n   gIntegerRotate,\n   gConditionRegisterLogical,\n   gFloatArithmetic,\n   gFloatArithmeticMuladd,\n   gFloatRound,\n   gFloatMove,\n};\n"
  },
  {
    "path": "tests/cpu/generator/dataset/generator_valuelist.h",
    "content": "#pragma once\n#include <cstdint>\n#include <vector>\n\nstatic const std::vector<uint32_t> gValuesCRB =\n{\n   0,\n   1,\n};\n\nstatic const std::vector<uint32_t> gValuesGPR =\n{\n   0,\n   1,\n   static_cast<uint32_t>(-1),\n   static_cast<uint32_t>(std::numeric_limits<int32_t>::min()),\n   static_cast<uint32_t>(std::numeric_limits<int32_t>::max()),\n   53, 0x12345678, 0x87654321\n};\n\nstatic const std::vector<int16_t> gValuesSIMM =\n{\n   0,\n   1,\n   -1,\n   std::numeric_limits<int16_t>::min(),\n   std::numeric_limits<int16_t>::max(),\n   53, 0x1234, static_cast<int16_t>(0x8765u)\n};\n\nstatic const std::vector<uint16_t> gValuesUIMM =\n{\n   0,\n   1,\n   static_cast<uint16_t>(-1),\n   static_cast<uint16_t>(std::numeric_limits<int16_t>::min()),\n   static_cast<uint16_t>(std::numeric_limits<int16_t>::max()),\n   53, 0x1234, 0x8765\n};\n\nstatic const std::vector<double> gValuesFPR =\n{\n   0.0,\n   -0.0,\n   1.0,\n   -1.0,\n   std::numeric_limits<double>::min(),\n   std::numeric_limits<double>::max(),\n   std::numeric_limits<double>::lowest(),\n   std::numeric_limits<double>::infinity(),\n   -std::numeric_limits<double>::infinity(),\n   std::numeric_limits<double>::quiet_NaN(),\n   std::numeric_limits<double>::signaling_NaN(),\n   std::numeric_limits<double>::denorm_min(),\n   std::numeric_limits<double>::epsilon(),\n   33525.78\n};\n\nstatic const std::vector<uint32_t> gValuesXERC =\n{\n   0,\n   1\n};\n\nstatic const std::vector<uint32_t> gValuesXERSO =\n{\n   0,\n   1\n};\n\nstatic const std::vector<uint32_t> gValuesSH =\n{\n   0, 15, 23, 31\n};\n\nstatic const std::vector<uint32_t> gValuesMB =\n{\n   0, 15, 23, 31\n};\n\nstatic const std::vector<uint32_t> gValuesME =\n{\n   0, 15, 23, 31\n};\n"
  },
  {
    "path": "tests/cpu/generator/server/server.cpp",
    "content": "#include <cassert>\n#include <cstdint>\n#include <iostream>\n#include <experimental/filesystem>\n#include <fstream>\n#include <memory>\n#include <vector>\n#include <ovsocket/socket.h>\n#include <ovsocket/networkthread.h>\n#include \"hardware-test/hardwaretests.h\"\n#include \"common/be_val.h\"\n\n#pragma comment(lib, \"ws2_32.lib\")\n#pragma comment(lib, \"ovsocket.lib\")\n\nusing namespace ovs;\nusing namespace std::placeholders;\nnamespace fs = std::experimental::filesystem;\n\n// Deal with it.\nstatic std::vector<hwtest::TestFile>\ngTestSet;\n\n#pragma pack(push, 1)\nstruct HWRegisterState\n{\n   be_val<uint32_t> xer;\n   be_val<uint32_t> cr;\n   be_val<uint32_t> ctr;\n   be_val<uint32_t> _;\n   be_val<uint32_t> r3;\n   be_val<uint32_t> r4;\n   be_val<uint32_t> r5;\n   be_val<uint32_t> r6;\n   be_val<uint64_t> fpscr;\n   be_val<double> f1;\n   be_val<double> f2;\n   be_val<double> f3;\n   be_val<double> f4;\n};\n\nstruct PacketHeader\n{\n   enum Commands\n   {\n      Version = 1,\n      ExecuteGeneralTest = 10,\n      ExecutePairedTest = 20,\n      TestsFinished = 50,\n   };\n\n   be_val<uint16_t> size;\n   be_val<uint16_t> command;\n};\n\nstruct VersionPacket : PacketHeader\n{\n   VersionPacket(uint32_t value)\n   {\n      size = sizeof(VersionPacket);\n      command = PacketHeader::Version;\n      version = value;\n   }\n\n   be_val<uint32_t> version;\n};\n\nstruct ExecuteGeneralTestPacket : PacketHeader\n{\n   ExecuteGeneralTestPacket()\n   {\n      size = sizeof(ExecuteGeneralTestPacket);\n      command = PacketHeader::ExecuteGeneralTest;\n      memset(&state, 0, sizeof(HWRegisterState));\n   }\n\n   be_val<uint32_t> instr;\n   HWRegisterState state;\n};\n\nstruct TestFinishedPacket : PacketHeader\n{\n   TestFinishedPacket()\n   {\n      size = sizeof(VersionPacket);\n      command = PacketHeader::TestsFinished;\n   }\n};\n#pragma pack(pop)\n\nclass TestServer\n{\n   static const uint32_t Version = 1;\n\npublic:\n   TestServer(Socket *socket) :\n      mSocket(socket)\n   {\n      mSocket->addErrorListener(std::bind(&TestServer::onSocketError, this, _1, _2));\n      mSocket->addDisconnectListener(std::bind(&TestServer::onSocketDisconnect, this, _1));\n      mSocket->addReceiveListener(std::bind(&TestServer::onSocketReceive, this, _1, _2, _3));\n\n      // Send version\n      VersionPacket version { Version };\n      mSocket->send(reinterpret_cast<const char *>(&version), sizeof(VersionPacket));\n\n      // Read first packet\n      mSocket->recvFill(sizeof(PacketHeader));\n\n      // Initialise test iterators\n      mTestFile = gTestSet.begin();\n      mTestData = mTestFile->tests.begin();\n   }\n\nprivate:\n   void saveTestFile()\n   {\n      // Save test result\n      std::ofstream out(\"tests/cpu/wiiu/\" + mTestFile->name, std::ofstream::out | std::ofstream::binary);\n      cereal::BinaryOutputArchive ar(out);\n      ar(*mTestFile);\n\n      std::cout << \"Wrote file tests/cpu/wiiu/\" << mTestFile->name << std::endl;\n   }\n\n   void sendNextTest()\n   {\n      if (mTestData == mTestFile->tests.end()) {\n         // Save current test file\n         saveTestFile();\n\n         // To the next test!\n         ++mTestFile;\n\n         if (mTestFile == gTestSet.end()) {\n            std::cout << \"Tests finished.\" << std::endl;\n\n            // Notify client tests have finished\n            TestFinishedPacket pak;\n            mSocket->send(reinterpret_cast<const char *>(&pak), sizeof(TestFinishedPacket));\n         } else {\n            // Start next test file\n            mTestData = mTestFile->tests.begin();\n         }\n      }\n\n      // Copy test input\n      ExecuteGeneralTestPacket packet;\n      packet.instr = mTestData->instr.value;\n      packet.state.xer = mTestData->input.xer.value;\n      packet.state.cr = mTestData->input.cr.value;\n      packet.state.ctr = mTestData->input.ctr;\n      packet.state.r3 = mTestData->input.gpr[0];\n      packet.state.r4 = mTestData->input.gpr[1];\n      packet.state.r5 = mTestData->input.gpr[2];\n      packet.state.r6 = mTestData->input.gpr[3];\n      packet.state.fpscr = mTestData->input.fpscr.value;\n      packet.state.f1 = mTestData->input.fr[0];\n      packet.state.f2 = mTestData->input.fr[1];\n      packet.state.f3 = mTestData->input.fr[2];\n      packet.state.f4 = mTestData->input.fr[3];\n\n      mSocket->send(reinterpret_cast<const char *>(&packet), sizeof(ExecuteGeneralTestPacket));\n   }\n\n   void handleTestResult(ExecuteGeneralTestPacket *result)\n   {\n      // Sanity check\n      assert(mTestData->instr.value == result->instr.value());\n\n      // Copy the output\n      mTestData->output.xer.value = result->state.xer;\n      mTestData->output.cr.value = result->state.cr;\n      mTestData->output.ctr = result->state.ctr;\n      mTestData->output.gpr[0] = result->state.r3;\n      mTestData->output.gpr[1] = result->state.r4;\n      mTestData->output.gpr[2] = result->state.r5;\n      mTestData->output.gpr[3] = result->state.r6;\n      mTestData->output.fpscr.value = static_cast<uint32_t>(result->state.fpscr);\n      mTestData->output.fr[0] = result->state.f1;\n      mTestData->output.fr[1] = result->state.f2;\n      mTestData->output.fr[2] = result->state.f3;\n      mTestData->output.fr[3] = result->state.f4;\n\n      // Start next test\n      mTestData++;\n      sendNextTest();\n   }\n\n   void onReceivePacket(PacketHeader *packet)\n   {\n      VersionPacket *version;\n      ExecuteGeneralTestPacket *result;\n\n      switch (packet->command) {\n      case PacketHeader::Version:\n         // Receive version, begin tests\n         version = reinterpret_cast<VersionPacket *>(packet);\n         std::cout << \"Server Version: \" << Version << \", Client Version: \" << version->version << std::endl;\n         std::cout << \"Running tests...\" << std::endl;\n         sendNextTest();\n         break;\n      case PacketHeader::ExecuteGeneralTest:\n         // Receive test result\n         result = reinterpret_cast<ExecuteGeneralTestPacket *>(packet);\n         handleTestResult(result);\n         break;\n      }\n   }\n\n   void onSocketError(Socket *socket, int code)\n   {\n      assert(mSocket == socket);\n      std::cout << \"Socket error: \" << code << std::endl;\n   }\n\n   void onSocketDisconnect(Socket *socket)\n   {\n      assert(mSocket == socket);\n      std::cout << \"Socket Disconnected\" << std::endl;\n   }\n\n   void onSocketReceive(Socket *socket, const char *buffer, size_t size)\n   {\n      PacketHeader *header;\n      assert(mSocket == socket);\n\n      if (mCurrentPacket.size() == 0) {\n         assert(size == sizeof(PacketHeader));\n\n         // Copy packet to buffer\n         mCurrentPacket.resize(size);\n         header = reinterpret_cast<PacketHeader *>(mCurrentPacket.data());\n         std::memcpy(header, buffer, size);\n\n         // Read rest of packet\n         auto read = header->size - size;\n         socket->recvFill(read);\n      } else {\n         // Check we have read rest of packet\n         header = reinterpret_cast<PacketHeader *>(mCurrentPacket.data());\n         auto totalSize = size + sizeof(PacketHeader);\n         assert(totalSize == header->size);\n\n         // Read rest of packet\n         mCurrentPacket.resize(totalSize);\n         header = reinterpret_cast<PacketHeader *>(mCurrentPacket.data());\n         std::memcpy(mCurrentPacket.data() + sizeof(PacketHeader), buffer, size);\n\n         onReceivePacket(header);\n\n         // Read next packet\n         mCurrentPacket.clear();\n         socket->recvFill(sizeof(PacketHeader));\n      }\n   }\n\nprivate:\n   Socket *mSocket;\n   std::vector<char> mCurrentPacket;\n   std::vector<hwtest::TestFile>::iterator mTestFile;\n   std::vector<hwtest::TestData>::iterator mTestData;\n};\n\nstatic std::vector<std::unique_ptr<TestServer>>\ngTestServers;\n\nstatic void\nloadTests()\n{\n   fs::create_directories(\"tests/cpu/wiiu\");\n\n   // Read all tests\n   for (auto &entry : fs::directory_iterator(\"tests/cpu/input\")) {\n      std::ifstream file { entry.path().string(), std::ifstream::in | std::ifstream::binary };\n      gTestSet.emplace_back();\n      auto &test = gTestSet.back();\n      test.name = entry.path().filename().string();\n\n      // Parse cereal data\n      cereal::BinaryInputArchive input(file);\n      input(test);\n   }\n}\n\nint main(int argc, char **argv)\n{\n   WSADATA wsaData;\n   WSAStartup(MAKEWORD(2, 2), &wsaData);\n\n   loadTests();\n\n   NetworkThread thread;\n   auto socket = new Socket();\n   auto ip = \"0.0.0.0\";\n   auto port = \"8008\";\n\n   // On socket error\n   socket->addErrorListener([](Socket *socket, int code) {\n      std::cout << \"Listen Socket Error: \" << code << std::endl;\n   });\n\n   socket->addDisconnectListener([](Socket *socket) {\n      std::cout << \"Listen Socket Disconnected\" << std::endl;\n   });\n\n   // On socket connected, accept pls\n   socket->addAcceptListener([&thread](Socket *socket) {\n      auto newSock = socket->accept();\n\n      if (!newSock) {\n         std::cout << \"Failed to accept new connection\" << std::endl;\n         return;\n      } else {\n         std::cout << \"New Connection Accepted\" << std::endl;\n      }\n\n      gTestServers.emplace_back(new TestServer(socket));\n      thread.addSocket(newSock);\n   });\n\n   // Start server\n   if (!socket->listen(ip, port)) {\n      std::cout << \"Error starting connect!\" << std::endl;\n      return 0;\n   }\n\n   // Run network thread in main thread\n   std::cout << \"Listening on \" << ip << \":\" << port << std::endl;\n   thread.addSocket(socket);\n   thread.start();\n\n   WSACleanup();\n   return 0;\n}\n"
  },
  {
    "path": "tests/cpu/libcpu/CMakeLists.txt",
    "content": "include_directories(\".\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nadd_executable(test-libcpu ${SOURCE_FILES} ${HEADER_FILES})\nset_target_properties(test-libcpu PROPERTIES FOLDER tests)\n\ntarget_link_libraries(test-libcpu\n    catch2\n    common\n    libcpu)\n\nadd_test(NAME tests_libcpu\n         WORKING_DIRECTORY \"${PROJECT_SOURCE_DIR}\"\n         COMMAND test-libcpu)\n"
  },
  {
    "path": "tests/cpu/libcpu/main.cpp",
    "content": "#define CATCH_CONFIG_MAIN\n#include <catch.hpp>\n\n#include <libcpu/be2_struct.h>\n\nTEST_CASE(\"be_* address of returns virt_ptr\")\n{\n   // \"virt_addrof be_val<uint32_t>\" returns \"virt_ptr<uint32_t>\"\n   REQUIRE((std::is_same<virt_ptr<uint32_t>, decltype(virt_addrof(std::declval<be2_val<uint32_t>>()))>::value));\n\n   // \"virt_addrof be_struct<SomeStructure>\" returns \"virt_ptr<SomeStructure>\"\n   struct SomeStructure { };\n   REQUIRE((std::is_same<virt_ptr<SomeStructure>, decltype(virt_addrof(std::declval<be2_struct<SomeStructure>>()))>::value));\n\n   // \"virt_addrof be_array<char, 100>\" returns \"vitr_ptr<char>\"\n   REQUIRE((std::is_same<virt_ptr<char>, decltype(virt_addrof(std::declval<be2_array<char, 100>>()))>::value));\n}\n\nTEST_CASE(\"virt_ptr dereference\")\n{\n   // Dereferencing \"virt_ptr<uint32_t>\" returns \"be2_val<uint32_t> &\"\n   REQUIRE((std::is_same<be2_val<uint32_t> &, decltype(*std::declval<virt_ptr<uint32_t>>())>::value));\n\n   // Dereferencing \"virt_ptr<const uint32_t>\" returns \"const be_val<const uint32_t> &\"\n   REQUIRE((std::is_same<const be2_val<const uint32_t> &, decltype(*std::declval<virt_ptr<const uint32_t>>())>::value));\n}\n\nTEST_CASE(\"virt_ptr cast\")\n{\n   // virt_ptr<T> can be cast to virt_ptr<void>\n   REQUIRE((std::is_constructible<virt_ptr<void>, virt_ptr<uint32_t>>::value));\n\n   // virt_ptr<X> can not be cast to virt_ptr<Y>\n   REQUIRE((!std::is_constructible<virt_ptr<uint32_t>, virt_ptr<void>>::value));\n   REQUIRE((!std::is_constructible<virt_ptr<uint64_t>, virt_ptr<uint32_t>>::value));\n\n   // Assign virt_ptr<uint32_t> to be2_ptr<uint32_t>\n   REQUIRE((std::is_assignable<be2_ptr<uint32_t>, virt_ptr<uint32_t>>::value));\n\n   // Can construct virt_ptr<const void> from virt_ptr<void>\n   REQUIRE((std::is_constructible<virt_ptr<const void>, virt_ptr<void>>::value));\n}\n\nTEST_CASE(\"be_val values\")\n{\n   // Assign uint32_t to be2_val<uint32_t>\n   REQUIRE((std::is_assignable<be2_val<uint32_t>, uint32_t>::value));\n\n   // Array access of be_array<uint32_t, N> returns a be_val<uint32_t> &\n   REQUIRE((std::is_same<be2_val<uint32_t> &, decltype(std::declval<be2_array<uint32_t, 100>>()[1])>::value));\n}\n"
  },
  {
    "path": "tests/cpu/runner-achurch/CMakeLists.txt",
    "content": "include_directories(\".\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nadd_executable(runner-achurch ${SOURCE_FILES} ${HEADER_FILES})\nset_target_properties(runner-achurch PROPERTIES FOLDER tests)\n\ntarget_link_libraries(runner-achurch\n    common\n    libcpu)\n\nadd_test(NAME tests_cpu_achurch\n         WORKING_DIRECTORY \"${PROJECT_SOURCE_DIR}\"\n         COMMAND runner-achurch)\n"
  },
  {
    "path": "tests/cpu/runner-achurch/main.cpp",
    "content": "#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <fstream>\n#include <libcpu/cpu.h>\n#include <libcpu/cpu_config.h>\n#include <libcpu/espresso/espresso_disassembler.h>\n#include <libcpu/mem.h>\n#include <memory>\n#include <spdlog/spdlog.h>\n#include <spdlog/sinks/stdout_sinks.h>\n\nint\nrunTests()\n{\n   // Built From http://achurch.org/cpu-tests/ppc750cl.s\n   // powerpc-eabi-as ppc750cl.S -o ppc750cl.o --defsym TEST_SC=0 --defsym HAVE_UGQR=1 --defsym TEST_TRAP=0\n   // powerpc-eabi-ld ppc750cl.o -o ppc750cl --oformat=binary\n   std::ifstream file { \"data/achurch.bin\", std::ifstream::in | std::ifstream::binary };\n\n   if (!file.is_open()) {\n      gLog->error(\"Could not open data/achurch.bin\");\n      return -1;\n   }\n\n   // Calculate the total size of the file\n   file.seekg(0, std::ios::end);\n   auto file_size = file.tellg();\n   file.seekg(0, std::ios::beg);\n\n   // Allocate code memory\n   auto baseCodeAddress = cpu::VirtualAddress { 0x01000000u };\n   auto baseCodePhysicalAddress = cpu::PhysicalAddress { 0x50000000u };\n   auto codeSize = align_up(static_cast<uint32_t>(file_size), cpu::PageSize);\n   cpu::allocateVirtualAddress(baseCodeAddress, codeSize);\n   cpu::mapMemory(baseCodeAddress, baseCodePhysicalAddress, codeSize, cpu::MapPermission::ReadWrite);\n\n   // Allocate data memory\n   auto baseDataAddress = cpu::VirtualAddress { 0x03000000u };\n   auto baseDataPhysicalAddress = cpu::PhysicalAddress { 0x52000000u };\n   auto dataSize = 0x02000000u;\n   cpu::allocateVirtualAddress(baseDataAddress, dataSize);\n   cpu::mapMemory(baseDataAddress, baseDataPhysicalAddress, dataSize, cpu::MapPermission::ReadWrite);\n\n   // Read the file directly into PPC memory\n   file.read(mem::translate<char>(baseCodeAddress.getAddress()), file_size);\n\n   auto core = cpu::this_core::state();\n   auto scratchMemAddr = baseDataAddress.getAddress() + (dataSize / 2);\n   auto failResultsAddr = baseDataAddress.getAddress();\n   std::memset(mem::translate<void>(scratchMemAddr), 0, 32 * 1024u);\n\n   core->nia = baseCodeAddress.getAddress();\n   core->gpr[3] = 0;\n   core->gpr[4] = scratchMemAddr;\n   core->gpr[5] = failResultsAddr;\n   core->fpr[1].paired0 = 1.0;\n   cpu::this_core::executeSub();\n\n   auto failedTests = core->gpr[3];\n\n   if (failedTests) {\n      for (uint32_t i = 0; i < failedTests; ++i) {\n         uint32_t failedInstr = mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 0);\n         uint32_t failedAddr = mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 4);\n         uint32_t failedAux[] = {\n            mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 8),\n            mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 12),\n            mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 16),\n            mem::read<uint32_t>(failResultsAddr + (i * 4 * 8) + 20)\n         };\n\n         espresso::Disassembly disassembly;\n         espresso::disassemble(failedInstr, disassembly, failedAddr);\n         gLog->warn(\" {:08x} = {:08x} failed - {}\", failedAddr, failedInstr, espresso::disassemblyToText(disassembly));\n         gLog->warn(\"   {:08x} {:08x} {:08x} {:08x}\", failedAux[0], failedAux[1], failedAux[2], failedAux[3]);\n      }\n\n      gLog->error(\"Failed {} tests.\", failedTests);\n      return static_cast<int>(failedTests);\n   }\n\n   return 0;\n}\n\nint main(int argc, char *argv[])\n{\n   int runResult;\n   auto logger = std::make_shared<spdlog::logger>(\"logger\", std::make_shared<spdlog::sinks::stdout_sink_st>());\n   logger->set_level(spdlog::level::debug);\n   gLog = logger;\n\n   auto cpuConfig = cpu::Settings { };\n   cpuConfig.jit.enabled = true;\n   cpu::setConfig(cpuConfig);\n   cpu::initialise();\n\n   // We need to run the tests on a core.\n   cpu::setCoreEntrypointHandler(\n      [&runResult](cpu::Core *core) {\n         if (cpu::this_core::id() == 1) {\n            // Run the tests on only a single core.\n            runResult = runTests();\n         }\n      });\n\n   cpu::start();\n   cpu::join();\n   return runResult;\n}\n"
  },
  {
    "path": "tests/cpu/runner-generated/CMakeLists.txt",
    "content": "include_directories(\".\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nadd_executable(runner-generated ${SOURCE_FILES} ${HEADER_FILES})\nset_target_properties(runner-generated PROPERTIES FOLDER tests)\n\ntarget_link_libraries(runner-generated\n    common\n    libcpu\n    libdecaf\n    cereal)\n\nadd_test(NAME tests_cpu_generated\n         WORKING_DIRECTORY \"${PROJECT_SOURCE_DIR}\"\n         COMMAND runner-generated)\n"
  },
  {
    "path": "tests/cpu/runner-generated/hardwaretests.cpp",
    "content": "#include \"hardwaretests.h\"\n\n#include <cassert>\n#include <cfenv>\n#include <common/bit_cast.h>\n#include <common/floatutils.h>\n#include <common/log.h>\n#include <common/strutils.h>\n#include <filesystem>\n#include <fstream>\n#include <libcpu/cpu.h>\n#include <libcpu/mem.h>\n#include <libcpu/espresso/espresso_disassembler.h>\n#include <libcpu/espresso/espresso_instructionset.h>\n#include <libcpu/espresso/espresso_registerformats.h>\n\nusing namespace espresso;\n\nstatic const auto TEST_FPSCR = false;\nstatic const auto TEST_FPSCR_FR = false;\nstatic const auto TEST_FPSCR_UX = false;\nstatic const auto TEST_FMADDSUB = false;\n\nnamespace hwtest\n{\n\nstatic void\nprintTestField(InstructionField field, Instruction instr, RegisterState *input, RegisterState *output, cpu::CoreRegs *state)\n{\n   auto printGPR = [&](uint32_t reg) {\n      assert(reg >= GPR_BASE);\n\n      gLog->debug(\"r{:02d}    =         {:08X}         {:08X}         {:08X}\", reg,\n                  input->gpr[reg - GPR_BASE],\n                  output->gpr[reg - GPR_BASE],\n                  state->gpr[reg]);\n   };\n\n   auto printFPR = [&](uint32_t reg) {\n      assert(reg >= FPR_BASE);\n\n      gLog->debug(\"f{:02d}    = {:16e} {:16e} {:16e}\", reg,\n                  input->fr[reg - FPR_BASE],\n                  output->fr[reg - FPR_BASE],\n                  state->fpr[reg].value);\n\n      gLog->debug(\"         {:16X} {:16X} {:16X}\",\n                  bit_cast<uint64_t>(input->fr[reg - FPR_BASE]),\n                  bit_cast<uint64_t>(output->fr[reg - FPR_BASE]),\n                  bit_cast<uint64_t>(state->fpr[reg].value));\n   };\n\n   switch (field) {\n   case InstructionField::rA:\n      printGPR(instr.rA);\n      break;\n   case InstructionField::rB:\n      printGPR(instr.rB);\n      break;\n   case InstructionField::rD:\n      printGPR(instr.rS);\n      break;\n   case InstructionField::rS:\n      printGPR(instr.rS);\n      break;\n   case InstructionField::frA:\n      printFPR(instr.frA);\n      break;\n   case InstructionField::frB:\n      printFPR(instr.frB);\n      break;\n   case InstructionField::frC:\n      printFPR(instr.frC);\n      break;\n   case InstructionField::frD:\n      printFPR(instr.frD);\n      break;\n   case InstructionField::frS:\n      printFPR(instr.frS);\n      break;\n   case InstructionField::XERC:\n      gLog->debug(\"xer.ca =         {:08X}         {:08X}         {:08X}\", input->xer.ca, output->xer.ca, state->xer.ca);\n      break;\n   case InstructionField::XERSO:\n      gLog->debug(\"xer.so =         {:08X}         {:08X}         {:08X}\", input->xer.so, output->xer.so, state->xer.so);\n      break;\n   case InstructionField::FPSCR:\n      gLog->debug(\"fpscr =          {:08X}         {:08x}         {:08X}\", input->fpscr.value, output->fpscr.value, state->fpscr.value);\n      break;\n   default:\n      break;\n   }\n}\n\n#define CompareFPSCRField(field) \\\n   if (result.field != expected.field) { \\\n      gLog->debug(\"fpscr.\" #field \" = input {} expected {} found {}\", input.field, expected.field, result.field); \\\n      failed = true; \\\n   }\n\n// Compare all individual fields in fpscr\nstatic bool\ncompareFPSCR(FloatingPointStatusAndControlRegister input,\n             FloatingPointStatusAndControlRegister expected,\n             FloatingPointStatusAndControlRegister result)\n{\n   auto failed = false;\n   CompareFPSCRField(rn);\n   CompareFPSCRField(ni);\n   CompareFPSCRField(xe);\n   CompareFPSCRField(ze);\n   CompareFPSCRField(ue);\n   CompareFPSCRField(oe);\n   CompareFPSCRField(ve);\n   CompareFPSCRField(vxcvi);\n   CompareFPSCRField(vxsqrt);\n   CompareFPSCRField(vxsoft);\n   CompareFPSCRField(fprf);\n   CompareFPSCRField(fi);\n   CompareFPSCRField(fr);\n   CompareFPSCRField(vxvc);\n   CompareFPSCRField(vximz);\n   CompareFPSCRField(vxzdz);\n   CompareFPSCRField(vxidi);\n   CompareFPSCRField(vxisi);\n   CompareFPSCRField(vxsnan);\n   CompareFPSCRField(xx);\n   CompareFPSCRField(zx);\n   CompareFPSCRField(ux);\n   CompareFPSCRField(ox);\n   CompareFPSCRField(vx);\n   CompareFPSCRField(fex);\n   CompareFPSCRField(fx);\n   return failed;\n}\n\nint runTests(const std::string &path)\n{\n   uint32_t testsFailed = 0, testsPassed = 0;\n   auto baseAddress = cpu::VirtualAddress { 0x02000000u };\n   auto basePhysicalAddress = cpu::PhysicalAddress { 0x50000000u };\n   auto codeSize = 2048u;\n\n   cpu::allocateVirtualAddress(baseAddress, codeSize);\n   cpu::mapMemory(baseAddress, basePhysicalAddress, codeSize, cpu::MapPermission::ReadWrite);\n\n   Instruction bclr = encodeInstruction(InstructionID::bclr);\n   bclr.bo = 0x1f;\n   mem::write(baseAddress.getAddress() + 4, bclr.value);\n\n   auto ec = std::error_code { };\n   for (auto itr = std::filesystem::directory_iterator { \"/tests\", ec }; itr != end(itr); ++itr) {\n      std::ifstream file(itr->path().string(), std::ifstream::in | std::ifstream::binary);\n      cereal::BinaryInputArchive cerealInput(file);\n      TestFile testFile;\n\n      // Parse test file with cereal\n      testFile.name = itr->path().filename().string();\n      cerealInput(testFile);\n\n      // Run tests\n      for (auto &test : testFile.tests) {\n         bool failed = false;\n\n         if (!TEST_FMADDSUB) {\n            auto data = espresso::decodeInstruction(test.instr);\n            switch (data->id) {\n            case InstructionID::fmadd:\n            case InstructionID::fmadds:\n            case InstructionID::fmsub:\n            case InstructionID::fmsubs:\n            case InstructionID::fnmadd:\n            case InstructionID::fnmadds:\n            case InstructionID::fnmsub:\n            case InstructionID::fnmsubs:\n               failed = true;\n               break;\n            }\n            if (failed) {\n               continue;\n            }\n         }\n\n         // Setup core state from test input\n         cpu::CoreRegs *state = cpu::this_core::state();\n         memset(state, 0, sizeof(cpu::CoreRegs));\n         state->cia = 0;\n         state->nia = baseAddress.getAddress();\n         state->xer = test.input.xer;\n         state->cr = test.input.cr;\n         state->fpscr = test.input.fpscr;\n         state->ctr = test.input.ctr;\n\n         for (auto i = 0; i < 4; ++i) {\n            state->gpr[i + 3] = test.input.gpr[i];\n            state->fpr[i + 1].paired0 = test.input.fr[i];\n         }\n\n         // Execute test\n         mem::write(baseAddress.getAddress(), test.instr.value);\n         cpu::clearInstructionCache();\n         cpu::this_core::executeSub();\n\n         // Check XER (all bits)\n         if (state->xer.value != test.output.xer.value) {\n            gLog->error(\"Test failed, xer expected {:08X} found {:08X}\", test.output.xer.value, state->xer.value);\n            failed = true;\n         }\n\n         // Check Condition Register (all bits)\n         if (state->cr.value != test.output.cr.value) {\n            gLog->error(\"Test failed, cr expected {:08X} found {:08X}\", test.output.cr.value, state->cr.value);\n            failed = true;\n         }\n\n         // Check FPSCR\n         if (TEST_FPSCR) {\n            if (!TEST_FPSCR_FR) {\n               state->fpscr.fr = 0;\n               test.output.fpscr.fr = 0;\n            }\n\n            if (!TEST_FPSCR_UX) {\n               state->fpscr.ux = 0;\n               test.output.fpscr.ux = 0;\n            }\n\n            auto state_fpscr = state->fpscr.value;\n            auto test_fpscr = test.output.fpscr.value;\n\n            if (state_fpscr != test_fpscr) {\n               gLog->error(\"Test failed, fpscr {:08X} found {:08X}\", test.output.fpscr.value, state->fpscr.value);\n               compareFPSCR(test.input.fpscr, state->fpscr, test.output.fpscr);\n               failed = true;\n            }\n         }\n\n         // Check CTR\n         if (state->ctr != test.output.ctr) {\n            gLog->error(\"Test failed, ctr expected {:08X} found {:08X}\", test.output.ctr, state->ctr);\n            failed = true;\n         }\n\n         // Check all GPR\n         for (auto i = 0; i < 4; ++i) {\n            auto reg = i + hwtest::GPR_BASE;\n            auto value = state->gpr[reg];\n            auto expected = test.output.gpr[i];\n\n            if (value != expected) {\n               gLog->error(\"Test failed, r{} expected {:08X} found {:08X}\", reg, expected, value);\n               failed = true;\n            }\n         }\n\n         // Check all FPR\n         for (auto i = 0; i < 4; ++i) {\n            auto reg = i + hwtest::FPR_BASE;\n            auto value = state->fpr[reg].value;\n            auto expected = test.output.fr[i];\n\n            if (!is_nan(value) && !is_nan(expected) && !is_infinity(value) && !is_infinity(expected)) {\n               double dval = value / expected;\n\n               if (dval < 0.999 || dval > 1.001) {\n                  gLog->error(\"Test failed, f{} expected {:16f} found {:16f}\", reg, expected, value);\n                  failed = true;\n               }\n            } else {\n               if (is_nan(value) && is_nan(expected)) {\n                  auto bits = get_float_bits(value);\n                  bits.sign = get_float_bits(expected).sign;\n                  value = bits.v;\n               }\n\n               if (bit_cast<uint64_t>(value) != bit_cast<uint64_t>(expected)) {\n                  gLog->error(\"Test failed, f{} expected {:16X} found {:16X}\", reg, bit_cast<uint64_t>(expected), bit_cast<uint64_t>(value));\n                  failed = true;\n               }\n            }\n         }\n\n         if (failed) {\n            Disassembly dis;\n\n            // Print disassembly\n            disassemble(test.instr, dis, baseAddress.getAddress());\n            gLog->debug(disassemblyToText(dis));\n\n            // Print all test fields\n            gLog->debug(\"{:08x}            Input         Hardware           Interp\", test.instr.value);\n\n            for (auto field : dis.instruction->read) {\n               printTestField(field, test.instr, &test.input, &test.output, state);\n            }\n\n            for (auto field : dis.instruction->write) {\n               printTestField(field, test.instr, &test.input, &test.output, state);\n            }\n\n            for (auto field : dis.instruction->flags) {\n               printTestField(field, test.instr, &test.input, &test.output, state);\n            }\n\n            gLog->debug(\"\");\n            ++testsFailed;\n         } else {\n            ++testsPassed;\n         }\n      }\n   }\n\n   if (testsFailed) {\n      gLog->error(\"Failed {} of {} tests.\", testsFailed, testsFailed + testsPassed);\n   }\n\n   return testsFailed;\n}\n\n} // namespace hwtest\n"
  },
  {
    "path": "tests/cpu/runner-generated/hardwaretests.h",
    "content": "#pragma once\n#include <cereal/types/vector.hpp>\n#include <cereal/archives/binary.hpp>\n#include <vector>\n#include <libcpu/state.h>\n#include <libcpu/espresso/espresso_instruction.h>\n#include <libcpu/espresso/espresso_registerformats.h>\n\nnamespace hwtest\n{\n\nstatic const auto GPR_BASE = 3;\nstatic const auto FPR_BASE = 1;\nstatic const auto CRF_BASE = 2;\nstatic const auto CRB_BASE = 8;\n\nstruct RegisterState\n{\n   espresso::FixedPointExceptionRegister xer;\n   espresso::ConditionRegister cr;\n   espresso::FloatingPointStatusAndControlRegister fpscr;\n   uint32_t ctr;\n   uint32_t gpr[4];\n   double fr[4];\n\n   template <class Archive>\n   void serialize(Archive & ar)\n   {\n      ar(xer.value);\n      ar(cr.value);\n      ar(fpscr.value);\n      ar(ctr);\n\n      for (auto i = 0; i < 4; ++i) {\n         ar(gpr[i]);\n      }\n\n      for (auto i = 0; i < 4; ++i) {\n         ar(fr[i]);\n      }\n   }\n};\n\nstruct TestData\n{\n   espresso::Instruction instr;\n   RegisterState input;\n   RegisterState output;\n\n   template <class Archive>\n   void serialize(Archive & ar)\n   {\n      ar(instr.value);\n      ar(input);\n      ar(output);\n   }\n};\n\nstruct TestFile\n{\n   std::string name;\n   std::vector<TestData> tests;\n\n   template <class Archive>\n   void serialize(Archive & ar)\n   {\n      ar(tests);\n   }\n};\n\nint runTests(const std::string &path);\n\n} // namespace hwtest\n"
  },
  {
    "path": "tests/cpu/runner-generated/main.cpp",
    "content": "#include \"hardwaretests.h\"\n\n#include <common/decaf_assert.h>\n#include <common/log.h>\n#include <libcpu/cpu.h>\n#include <libcpu/cpu_config.h>\n#include <libcpu/mem.h>\n#include <memory>\n#include <spdlog/spdlog.h>\n#include <spdlog/sinks/stdout_sinks.h>\n\nstatic int runResult;\n\nint main(int argc, char *argv[])\n{\n   auto logger = std::make_shared<spdlog::logger>(\"logger\", std::make_shared<spdlog::sinks::stdout_sink_st>());\n   logger->set_level(spdlog::level::debug);\n   gLog = logger;\n\n   auto cpuConfig = cpu::Settings { };\n   cpuConfig.jit.enabled = true;\n   cpu::setConfig(cpuConfig);\n   cpu::initialise();\n\n   // We need to run the tests on a core.\n   cpu::setCoreEntrypointHandler(\n      [](cpu::Core *core) {\n         if (cpu::this_core::id() == 1) {\n            // Run the tests on only a single core.\n            runResult = hwtest::runTests(\"data/wiiu\");\n         }\n      });\n\n   cpu::start();\n   cpu::join();\n   return runResult;\n}\n"
  },
  {
    "path": "tests/gpu/CMakeLists.txt",
    "content": "project(tests-gpu)\n\nadd_subdirectory(\"tiling\")\n"
  },
  {
    "path": "tests/gpu/tiling/CMakeLists.txt",
    "content": "include_directories(\".\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nadd_executable(test-gpu-tiling ${SOURCE_FILES} ${HEADER_FILES})\nset_target_properties(test-gpu-tiling PROPERTIES FOLDER tests)\n\ntarget_link_libraries(test-gpu-tiling\n    addrlib\n    catch2\n    common\n    libcpu\n    libgpu)\n\nif(DECAF_VULKAN)\n    target_link_libraries(test-gpu-tiling\n        vulkan\n        SPIRV)\nendif()\n\nadd_test(NAME gpu-tiling\n         WORKING_DIRECTORY \"${PROJECT_SOURCE_DIR}\"\n         COMMAND test-gpu-tiling)\n"
  },
  {
    "path": "tests/gpu/tiling/addrlib_helpers.h",
    "content": "#pragma once\n\n#include <addrlib/addrinterface.h>\n#include <catch.hpp>\n#include <cstring>\n#include <libgpu/gpu7_tiling.h>\n\nclass AddrLib\n{\npublic:\n   AddrLib()\n   {\n      ADDR_CREATE_INPUT input;\n      ADDR_CREATE_OUTPUT output;\n      std::memset(&input, 0, sizeof(input));\n      std::memset(&output, 0, sizeof(output));\n\n      input.size = sizeof(ADDR_CREATE_INPUT);\n      output.size = sizeof(ADDR_CREATE_OUTPUT);\n\n      input.chipEngine = CIASICIDGFXENGINE_R600;\n      input.chipFamily = 0x51;\n      input.chipRevision = 71;\n      input.createFlags.fillSizeFields = 1;\n      input.regValue.gbAddrConfig = 0x44902;\n\n      input.callbacks.allocSysMem = &addrLibAlloc;\n      input.callbacks.freeSysMem = &addrLibFree;\n\n      REQUIRE(AddrCreate(&input, &output) == ADDR_OK);\n      mHandle = output.hLib;\n   }\n\n   ~AddrLib()\n   {\n      if (mHandle) {\n         AddrDestroy(mHandle);\n      }\n   }\n\n   void\n   untileSlices(const gpu7::tiling::SurfaceDescription &desc,\n                uint32_t mipLevel,\n                const uint8_t *src,\n                uint8_t *dst,\n                uint32_t firstSlice,\n                uint32_t numSlices)\n   {\n      for (uint32_t sample = 0; sample < desc.numSamples; ++sample) {\n         for (uint32_t slice = firstSlice; slice < firstSlice + numSlices; ++slice) {\n            const auto info = computeSurfaceInfo(desc, slice, mipLevel);\n            auto srcAddrInput = getTiledAddrFromCoordInput(desc, info);\n            srcAddrInput.sample = sample;\n            srcAddrInput.slice = slice;\n\n            auto dstAddrInput = getUntiledAddrFromCoordInput(desc, info);\n            dstAddrInput.sample = sample;\n            dstAddrInput.slice = slice;\n\n            copySurfacePixels(src, srcAddrInput, dst, dstAddrInput);\n         }\n      }\n   }\n\n   void\n   tileSlices(const gpu7::tiling::SurfaceDescription &desc,\n              uint32_t mipLevel,\n              uint8_t *src,\n              uint8_t *dst,\n              uint32_t firstSlice,\n              uint32_t numSlices)\n   {\n      for (uint32_t sample = 0; sample < desc.numSamples; ++sample) {\n         for (uint32_t slice = firstSlice; slice < firstSlice + numSlices; ++slice) {\n            const auto info = computeSurfaceInfo(desc, 0, mipLevel);\n\n            auto srcAddrInput = getUntiledAddrFromCoordInput(desc, info);\n            srcAddrInput.sample = sample;\n            srcAddrInput.slice = slice;\n\n            auto dstAddrInput = getTiledAddrFromCoordInput(desc, info);\n            dstAddrInput.sample = sample;\n            dstAddrInput.slice = slice;\n\n            copySurfacePixels(src, srcAddrInput, dst, dstAddrInput);\n         }\n      }\n   }\n\n   ADDR_COMPUTE_SURFACE_INFO_OUTPUT\n   computeSurfaceInfo(const gpu7::tiling::SurfaceDescription &surface,\n                      uint32_t slice,\n                      uint32_t mipLevel)\n   {\n      auto output = ADDR_COMPUTE_SURFACE_INFO_OUTPUT { };\n      output.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_OUTPUT);\n\n      auto input = ADDR_COMPUTE_SURFACE_INFO_INPUT { };\n      input.size = sizeof(ADDR_COMPUTE_SURFACE_INFO_INPUT);\n      input.tileMode = static_cast<AddrTileMode > (surface.tileMode);\n      input.format = static_cast<AddrFormat>(surface.format);\n      input.bpp = surface.bpp;\n      input.numSamples = surface.numSamples;\n      input.numFrags = surface.numFrags;\n      input.mipLevel = mipLevel;\n      input.slice = slice;\n      input.numSlices = surface.numSlices;\n\n      input.width = std::max(surface.width >> mipLevel, 1u);\n      input.height = std::max(surface.height >> mipLevel, 1u);\n      input.flags.inputBaseMap = mipLevel == 0 ? 1 : 0;\n\n      if (surface.use & gpu7::tiling::SurfaceUse::ScanBuffer) {\n         input.flags.display = 1;\n      }\n\n      if (surface.use & gpu7::tiling::SurfaceUse::DepthBuffer) {\n         input.flags.depth = 1;\n      }\n\n      if (surface.dim == gpu7::tiling::SurfaceDim::Texture3D) {\n         input.flags.volume = 1;\n         input.numSlices = std::max(surface.numSlices >> mipLevel, 1u);\n      }\n\n      if (surface.dim == gpu7::tiling::SurfaceDim::TextureCube) {\n         input.flags.cube = 1;\n      }\n\n      REQUIRE(AddrComputeSurfaceInfo(mHandle, &input, &output) == ADDR_OK);\n      return output;\n   }\n\nprivate:\n   static void *\n   addrLibAlloc(const ADDR_ALLOCSYSMEM_INPUT *pInput)\n   {\n      return std::malloc(pInput->sizeInBytes);\n   }\n\n   static ADDR_E_RETURNCODE\n   addrLibFree(const ADDR_FREESYSMEM_INPUT *pInput)\n   {\n      std::free(pInput->pVirtAddr);\n      return ADDR_OK;\n   }\n\n   void\n   copySurfacePixels(const uint8_t *src,\n                     ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &srcAddrInput,\n                     uint8_t *dst,\n                     ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT &dstAddrInput)\n   {\n      auto srcAddrOutput = ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT { };\n      auto dstAddrOutput = ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT { };\n      srcAddrOutput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT);\n      dstAddrOutput.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT);\n\n      assert(srcAddrInput.bpp == dstAddrInput.bpp);\n      assert(srcAddrInput.pitch == dstAddrInput.pitch);\n      assert(srcAddrInput.height == dstAddrInput.height);\n      auto bytesPerElem = dstAddrInput.bpp / 8;\n\n      for (auto y = 0u; y < dstAddrInput.height; ++y) {\n         for (auto x = 0u; x < dstAddrInput.pitch; ++x) {\n            dstAddrInput.x = x;\n            dstAddrInput.y = y;\n            AddrComputeSurfaceAddrFromCoord(mHandle, &dstAddrInput, &dstAddrOutput);\n\n            srcAddrInput.x = x;\n            srcAddrInput.y = y;\n            AddrComputeSurfaceAddrFromCoord(mHandle, &srcAddrInput, &srcAddrOutput);\n\n            std::memcpy(dst + dstAddrOutput.addr,\n                        src + srcAddrOutput.addr,\n                        bytesPerElem);\n         }\n      }\n   }\n\n   ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT\n   getUntiledAddrFromCoordInput(const gpu7::tiling::SurfaceDescription &desc,\n                                 const ADDR_COMPUTE_SURFACE_INFO_OUTPUT &info)\n   {\n      auto input = getTiledAddrFromCoordInput(desc, info);\n      input.tileMode = AddrTileMode::ADDR_TM_LINEAR_GENERAL;\n      input.bankSwizzle = 0;\n      input.pipeSwizzle = 0;\n      return input;\n   }\n\n   ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT\n   getTiledAddrFromCoordInput(const gpu7::tiling::SurfaceDescription &desc,\n                              const ADDR_COMPUTE_SURFACE_INFO_OUTPUT &info)\n   {\n      auto input = ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT { };\n      input.size = sizeof(ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT);\n      input.bpp = info.bpp;\n      input.pitch = info.pitch;\n      input.height = info.height;\n      input.numSlices = info.depth;\n      input.numSamples = desc.numSamples;\n      input.tileMode = info.tileMode;\n      input.isDepth = !!(desc.use & gpu7::tiling::SurfaceUse::DepthBuffer);\n      input.tileBase = 0;\n      input.compBits = 0;\n      input.numFrags = desc.numFrags;\n      input.bankSwizzle = desc.bankSwizzle;\n      input.pipeSwizzle = desc.pipeSwizzle;\n      return input;\n   }\n\nprivate:\n   ADDR_HANDLE mHandle = nullptr;\n};\n"
  },
  {
    "path": "tests/gpu/tiling/cpu_tiling_test.cpp",
    "content": "#include \"tiling_tests.h\"\n#include \"addrlib_helpers.h\"\n#include \"test_helpers.h\"\n\n#include <common/align.h>\n#include <libgpu/gpu7_tiling_cpu.h>\n\nstatic inline void\ncompareTilingToAddrLib(const gpu7::tiling::SurfaceDescription& desc,\n                       std::vector<uint8_t>& input,\n                       uint32_t firstSlice,\n                       uint32_t numSlices)\n{\n   auto addrLib = AddrLib { };\n\n   auto alibUntiled = std::vector<uint8_t> { };\n   auto gpu7Untiled = std::vector<uint8_t> { };\n   auto alibTiled = std::vector<uint8_t> { };\n   auto gpu7Tiled = std::vector<uint8_t> { };\n\n   // Compute surface info\n   auto alibInfo = addrLib.computeSurfaceInfo(desc, 0, 0);\n   auto gpu7Info = gpu7::tiling::computeSurfaceInfo(desc, 0);\n\n   REQUIRE(gpu7Info.surfSize == alibInfo.surfSize);\n   REQUIRE(input.size() >= gpu7Info.surfSize);\n\n   alibUntiled.resize(alibInfo.surfSize);\n   alibTiled.resize(alibInfo.surfSize);\n   gpu7Untiled.resize(gpu7Info.surfSize);\n   gpu7Tiled.resize(gpu7Info.surfSize);\n\n   // AddrLib\n   {\n      addrLib.untileSlices(desc, 0, input.data(), alibUntiled.data(), firstSlice, numSlices);\n      addrLib.tileSlices(desc, 0, alibUntiled.data(), alibTiled.data(), firstSlice, numSlices);\n   }\n\n   // GPU7\n   {\n      auto retileInfo = gpu7::tiling::computeRetileInfo(gpu7Info);\n\n      auto tiledFirstSliceIndex = align_down(firstSlice, retileInfo.microTileThickness);\n      auto tiledSliceOffset = tiledFirstSliceIndex * retileInfo.thinSliceBytes;\n      auto untiledSliceOffset = firstSlice * retileInfo.thinSliceBytes;\n\n      gpu7::tiling::cpu::untile(retileInfo,\n                                gpu7Untiled.data() + untiledSliceOffset,\n                                input.data() + tiledSliceOffset,\n                                firstSlice, numSlices);\n\n      gpu7::tiling::cpu::tile(retileInfo,\n                              gpu7Untiled.data() + untiledSliceOffset,\n                              gpu7Tiled.data() + tiledSliceOffset,\n                              firstSlice, numSlices);\n   }\n\n   CHECK(compareImages(gpu7Untiled, alibUntiled));\n   CHECK(compareImages(gpu7Tiled, alibTiled));\n}\n\nTEST_CASE(\"cpuTiling\")\n{\n   for (auto& layout : sTestLayout) {\n      SECTION(fmt::format(\"{}x{}x{} s{}n{}\",\n                          layout.width, layout.height, layout.depth,\n                          layout.testFirstSlice, layout.testNumSlices))\n      {\n         for (auto& mode : sTestTilingMode) {\n            SECTION(fmt::format(\"{}\", tileModeToString(mode.tileMode)))\n            {\n               for (auto& format : sTestFormats) {\n                  SECTION(fmt::format(\"{}bpp{}\", format.bpp, format.depth ? \" depth\" : \"\"))\n                  {\n                     auto surface = gpu7::tiling::SurfaceDescription { };\n                     surface.tileMode = mode.tileMode;\n                     surface.format = format.format;\n                     surface.bpp = format.bpp;\n                     surface.width = layout.width;\n                     surface.height = layout.height;\n                     surface.numSlices = layout.depth;\n                     surface.numSamples = 1u;\n                     surface.numLevels = 1u;\n                     surface.bankSwizzle = 0u;\n                     surface.pipeSwizzle = 0u;\n                     surface.dim = gpu7::tiling::SurfaceDim::Texture2DArray;\n                     surface.use = format.depth ?\n                        gpu7::tiling::SurfaceUse::DepthBuffer :\n                        gpu7::tiling::SurfaceUse::None;\n\n                     compareTilingToAddrLib(surface,\n                                            sRandomData,\n                                            layout.testFirstSlice,\n                                            layout.testNumSlices);\n                  }\n               }\n            }\n         }\n      }\n   }\n}\n\nstruct ALibPendingCpuPerfEntry\n{\n   gpu7::tiling::SurfaceDescription desc;\n   uint32_t firstSlice;\n   uint32_t numSlices;\n\n   ADDR_COMPUTE_SURFACE_INFO_OUTPUT info;\n};\n\nTEST_CASE(\"alibTilingPerf\", \"[!benchmark]\")\n{\n   // Set up AddrLib to generate data to test against\n   auto addrLib = AddrLib { };\n\n   // Get some random data to use\n   auto& untiled = sRandomData;\n\n   // Some place to store pending tests\n   std::vector<ALibPendingCpuPerfEntry> pendingTests;\n\n   // Generate all the test cases to run\n   auto& layout = sPerfTestLayout;\n   for (auto& mode : sTestTilingMode) {\n      for (auto& format : sTestFormats) {\n         auto surface = gpu7::tiling::SurfaceDescription {};\n         surface.tileMode = mode.tileMode;\n         surface.format = format.format;\n         surface.bpp = format.bpp;\n         surface.width = layout.width;\n         surface.height = layout.height;\n         surface.numSlices = layout.depth;\n         surface.numSamples = 1u;\n         surface.numLevels = 1u;\n         surface.bankSwizzle = 0u;\n         surface.pipeSwizzle = 0u;\n         surface.dim = gpu7::tiling::SurfaceDim::Texture2DArray;\n         surface.use = format.depth ?\n            gpu7::tiling::SurfaceUse::DepthBuffer :\n            gpu7::tiling::SurfaceUse::None;\n\n         auto info = addrLib.computeSurfaceInfo(surface, 0, 0);\n\n         ALibPendingCpuPerfEntry test;\n         test.desc = surface;\n         test.info = info;\n         test.firstSlice = layout.testFirstSlice;\n         test.numSlices = layout.testNumSlices;\n         pendingTests.push_back(test);\n      }\n   }\n\n   auto addrLibImage = std::vector<uint8_t> { };\n   addrLibImage.resize(untiled.size());\n\n   BENCHMARK(fmt::format(\"processing ({} retiles)\", pendingTests.size()))\n   {\n      for (auto &test : pendingTests) {\n         // Compare image\n         addrLib.untileSlices(test.desc, 0,\n            untiled.data(), addrLibImage.data(),\n            test.firstSlice, test.numSlices);\n      }\n   };\n}\n\nstruct PendingCpuPerfEntry\n{\n   gpu7::tiling::SurfaceDescription desc;\n   uint32_t firstSlice;\n   uint32_t numSlices;\n\n   gpu7::tiling::SurfaceInfo info;\n};\n\nTEST_CASE(\"cpuTilingPerf\", \"[!benchmark]\")\n{\n   // Set up AddrLib to generate data to test against\n   auto addrLib = AddrLib { };\n\n   // Get some random data to use\n   auto& untiled = sRandomData;\n\n   // Some place to store pending tests\n   std::vector<PendingCpuPerfEntry> pendingTests;\n\n   // Generate all the test cases to run\n   auto& layout = sPerfTestLayout;\n   for (auto& mode : sTestTilingMode) {\n      for (auto& format : sTestFormats) {\n         auto surface = gpu7::tiling::SurfaceDescription {};\n         surface.tileMode = mode.tileMode;\n         surface.format = format.format;\n         surface.bpp = format.bpp;\n         surface.width = layout.width;\n         surface.height = layout.height;\n         surface.numSlices = layout.depth;\n         surface.numSamples = 1u;\n         surface.numLevels = 1u;\n         surface.bankSwizzle = 0u;\n         surface.pipeSwizzle = 0u;\n         surface.dim = gpu7::tiling::SurfaceDim::Texture2DArray;\n         surface.use = format.depth ?\n            gpu7::tiling::SurfaceUse::DepthBuffer :\n            gpu7::tiling::SurfaceUse::None;\n\n         auto info = gpu7::tiling::computeSurfaceInfo(surface, 0);\n\n         PendingCpuPerfEntry test;\n         test.desc = surface;\n         test.info = info;\n         test.firstSlice = layout.testFirstSlice;\n         test.numSlices = layout.testNumSlices;\n         pendingTests.push_back(test);\n      }\n   }\n\n   auto tiledImage = std::vector<uint8_t> { };\n   tiledImage.resize(untiled.size());\n\n   static constexpr auto TestIterMulti = 10;\n\n   BENCHMARK(fmt::format(\"processing ({} retiles)\", pendingTests.size() * TestIterMulti))\n   {\n      for (auto i = 0; i < TestIterMulti; ++i) {\n         for (auto& test : pendingTests) {\n\n            auto retileInfo = gpu7::tiling::computeRetileInfo(test.info);\n\n            auto tiledFirstSliceIndex = align_down(test.firstSlice, retileInfo.microTileThickness);\n            auto tiledSliceOffset = tiledFirstSliceIndex * retileInfo.thinSliceBytes;\n            auto untiledSliceOffset = test.firstSlice * retileInfo.thinSliceBytes;\n\n            gpu7::tiling::cpu::untile(retileInfo,\n                                      untiled.data() + untiledSliceOffset,\n                                      tiledImage.data() + tiledSliceOffset,\n                                      test.firstSlice,\n                                      test.numSlices);\n         }\n      }\n   };\n}\n\n"
  },
  {
    "path": "tests/gpu/tiling/test_helpers.h",
    "content": "#pragma once\n\n#include <catch.hpp>\n#include <fmt/core.h>\n#include <random>\n#include <vector>\n\nstatic inline std::vector<uint8_t>\ngenerateRandomData(size_t size)\n{\n   std::mt19937 eng { 0x0DECAF10 };\n   std::uniform_int_distribution<uint32_t> urd { 0, 255 };\n   std::vector<uint8_t> result;\n   result.resize(size);\n\n   for (auto i = 0; i < size; ++i) {\n      result[i] = static_cast<uint8_t>(urd(eng));\n   }\n\n   return result;\n}\n\nstatic inline bool\ncompareImages(const std::vector<uint8_t> &data,\n              const std::vector<uint8_t> &reference)\n{\n   REQUIRE(data.size() == reference.size());\n\n   for (auto i = 0u; i < data.size(); ++i) {\n      if (data[i] != reference[i]) {\n         WARN(fmt::format(\"Difference at offset {}, 0x{:02X} != 0x{:02X}\", i, data[i], reference[i]));\n         return false;\n      }\n   }\n\n   return true;\n}\n"
  },
  {
    "path": "tests/gpu/tiling/tiling_test.cpp",
    "content": "#define CATCH_CONFIG_RUNNER\n#include <catch.hpp>\n\n#include \"test_helpers.h\"\n#include <spdlog/spdlog.h>\n\n#ifdef DECAF_VULKAN\nbool vulkanBeforeStart();\nbool vulkanAfterComplete();\n#else\nbool vulkanBeforeStart() { return true;  }\nbool vulkanAfterComplete() { return true; }\n#endif\n\nstd::vector<uint8_t>\nsRandomData = generateRandomData(32 * 1024 * 1024);\n\nint main(int argc, char* argv[])\n{\n   // Set up a Vulkan instance\n   if (!vulkanBeforeStart()) {\n      printf(\"Could not initialize Vulkan\\n\");\n      return -1;\n   }\n\n   // Run the test session\n   int result = Catch::Session().run(argc, argv);\n\n   // Shut down our Vulkan instance\n   if (!vulkanAfterComplete()) {\n      printf(\"Failed to shut down Vulkan\\n\");\n   }\n\n   return result;\n}\n"
  },
  {
    "path": "tests/gpu/tiling/tiling_tests.h",
    "content": "#pragma once\n\n#include <addrlib/addrinterface.h>\n#include <catch.hpp>\n#include <random>\n#include <spdlog/spdlog.h>\n#include <vector>\n#include <libgpu/gpu7_tiling.h>\n\nextern std::vector<uint8_t> sRandomData;\n\nstruct TestLayout\n{\n   uint32_t width;\n   uint32_t height;\n   uint32_t depth;\n   uint32_t testFirstSlice;\n   uint32_t testNumSlices;\n};\n\nstruct TestFormat\n{\n   gpu7::tiling::DataFormat format;\n   uint32_t bpp;\n   bool depth;\n};\n\nstruct TestTilingMode\n{\n   gpu7::tiling::TileMode tileMode;\n};\n\nstatic constexpr TestLayout sTestLayout[] = {\n   { 1u, 1u, 1u, 0u, 1u },\n   { 1u, 1u, 11u, 5u, 5u },\n   { 338u, 309u, 1u, 0u, 1u },\n   { 338u, 309u, 11u, 5u, 5u },\n\n   // The variants above cover these, but they are useful for\n   // debugging various errors in the algorithms.  The matrix\n   // is already huge though, so disabling by default.\n   //{ 338u, 309u, 4u, 0u, 4u },\n};\n\nstatic constexpr TestLayout sPerfTestLayout = { 338u, 309u, 8u, 0u, 8u };\n\nstatic constexpr TestTilingMode sTestTilingMode[] = {\n   { gpu7::tiling::TileMode::Micro1DTiledThin1 },\n   { gpu7::tiling::TileMode::Micro1DTiledThick },\n   { gpu7::tiling::TileMode::Macro2DTiledThin1 },\n   { gpu7::tiling::TileMode::Macro2DTiledThin2 },\n   { gpu7::tiling::TileMode::Macro2DTiledThin4 },\n   { gpu7::tiling::TileMode::Macro2DTiledThick },\n   { gpu7::tiling::TileMode::Macro2BTiledThin1 },\n   { gpu7::tiling::TileMode::Macro2BTiledThin2 },\n   { gpu7::tiling::TileMode::Macro2BTiledThin4 },\n   { gpu7::tiling::TileMode::Macro2BTiledThick },\n   { gpu7::tiling::TileMode::Macro3DTiledThin1 },\n   { gpu7::tiling::TileMode::Macro3DTiledThick },\n   { gpu7::tiling::TileMode::Macro3BTiledThin1 },\n   { gpu7::tiling::TileMode::Macro3BTiledThick },\n};\n\nstatic constexpr TestFormat sTestFormats[] = {\n   { gpu7::tiling::DataFormat::FMT_8, 8u, false },\n   { gpu7::tiling::DataFormat::FMT_8_8, 16u, false },\n   { gpu7::tiling::DataFormat::FMT_8_8_8_8, 32u, false },\n   { gpu7::tiling::DataFormat::FMT_32_32, 64u, false },\n   { gpu7::tiling::DataFormat::FMT_32_32_32_32, 128u, false },\n   //{ gpu7::tiling::DataFormat::FMT_16, 16u, true },\n   //{ gpu7::tiling::DataFormat::FMT_32, 32u, true },\n   //{ gpu7::tiling::DataFormat::FMT_X24_8_32_FLOAT, 64u, true },\n};\n\nstatic const char*\ntileModeToString(gpu7::tiling::TileMode mode)\n{\n   switch (mode) {\n   case gpu7::tiling::TileMode::LinearGeneral:\n      return \"LinearGeneral\";\n   case gpu7::tiling::TileMode::LinearAligned:\n      return \"LinearAligned\";\n   case gpu7::tiling::TileMode::Micro1DTiledThin1:\n      return \"Tiled1DThin1\";\n   case gpu7::tiling::TileMode::Micro1DTiledThick:\n      return \"Tiled1DThick\";\n   case gpu7::tiling::TileMode::Macro2DTiledThin1:\n      return \"Tiled2DThin1\";\n   case gpu7::tiling::TileMode::Macro2DTiledThin2:\n      return \"Tiled2DThin2\";\n   case gpu7::tiling::TileMode::Macro2DTiledThin4:\n      return \"Tiled2DThin4\";\n   case gpu7::tiling::TileMode::Macro2DTiledThick:\n      return \"Tiled2DThick\";\n   case gpu7::tiling::TileMode::Macro2BTiledThin1:\n      return \"Tiled2BThin1\";\n   case gpu7::tiling::TileMode::Macro2BTiledThin2:\n      return \"Tiled2BThin2\";\n   case gpu7::tiling::TileMode::Macro2BTiledThin4:\n      return \"Tiled2BThin4\";\n   case gpu7::tiling::TileMode::Macro2BTiledThick:\n      return \"Tiled2BThick\";\n   case gpu7::tiling::TileMode::Macro3DTiledThin1:\n      return \"Tiled3DThin1\";\n   case gpu7::tiling::TileMode::Macro3DTiledThick:\n      return \"Tiled3DThick\";\n   case gpu7::tiling::TileMode::Macro3BTiledThin1:\n      return \"Tiled3BThin1\";\n   case gpu7::tiling::TileMode::Macro3BTiledThick:\n      return \"Tiled3BThick\";\n   default:\n      FAIL(fmt::format(\"Unknown tiling mode {}\", static_cast<int>(mode)));\n      return \"Unknown\";\n   }\n}\n"
  },
  {
    "path": "tests/gpu/tiling/vulkan_helpers.cpp",
    "content": "#ifdef DECAF_VULKAN\n\n#include \"vulkan_helpers.h\"\n\nstatic constexpr bool ENABLE_VALIDATION = false;\n\nvk::Instance gVulkan = {};\nvk::PhysicalDevice gPhysDevice = {};\nvk::Device gDevice = {};\nvk::Queue gQueue = {};\nuint32_t gQueueFamilyIndex = static_cast<uint32_t>(-1);\nvk::CommandPool gCommandPool = {};\n\nstatic VKAPI_ATTR VkBool32 VKAPI_CALL\ndebugMessageCallback(VkDebugReportFlagsEXT flags,\n                     VkDebugReportObjectTypeEXT objectType,\n                     uint64_t object,\n                     size_t location,\n                     int32_t messageCode,\n                     const char* pLayerPrefix,\n                     const char* pMessage,\n                     void* pUserData)\n{\n   platform::debugLog(\n      fmt::format(\"Vulkan Debug Report: {}, {}, {}, {}, {}, {}, {}\\n\",\n                  vk::to_string(vk::DebugReportFlagsEXT(flags)),\n                  vk::to_string(vk::DebugReportObjectTypeEXT(objectType)),\n                  object,\n                  location,\n                  messageCode,\n                  pLayerPrefix,\n                  pMessage));\n\n   if (flags == VK_DEBUG_REPORT_WARNING_BIT_EXT || flags == VK_DEBUG_REPORT_ERROR_BIT_EXT) {\n      platform::debugBreak();\n   }\n\n   return VK_FALSE;\n}\n\nbool\ninitialiseVulkan()\n{\n   // Create our instance\n   std::vector<const char *> instanceLayers = { };\n   std::vector<const char *> instanceExtensions = { };\n\n   if (ENABLE_VALIDATION) {\n      instanceLayers.push_back(\"VK_LAYER_KHRONOS_validation\");\n      instanceExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);\n   }\n\n   vk::ApplicationInfo appDesc = {};\n   appDesc.pApplicationName = \"gpu-tile-perf\";\n   appDesc.applicationVersion = 0;\n   appDesc.pEngineName = \"\";\n   appDesc.engineVersion = 0;\n   appDesc.apiVersion = VK_API_VERSION_1_0;;\n\n   vk::InstanceCreateInfo instanceDesc = {};\n   instanceDesc.pApplicationInfo = &appDesc;\n   instanceDesc.enabledLayerCount = static_cast<uint32_t>(instanceLayers.size());\n   instanceDesc.ppEnabledLayerNames = instanceLayers.data();\n   instanceDesc.enabledExtensionCount = static_cast<uint32_t>(instanceExtensions.size());\n   instanceDesc.ppEnabledExtensionNames = instanceExtensions.data();\n\n   gVulkan = vk::createInstance(instanceDesc);\n\n   // Get our Physical Device\n   auto physDevices = gVulkan.enumeratePhysicalDevices();\n   gPhysDevice = physDevices[0];\n\n   std::vector<const char*> deviceLayers = { };\n   std::vector<const char*> deviceExtensions = { };\n\n   if (ENABLE_VALIDATION) {\n      deviceLayers.push_back(\"VK_LAYER_KHRONOS_validation\");\n   }\n\n   // Find an appropriate queue\n   auto queueFamilyProps = gPhysDevice.getQueueFamilyProperties();\n   uint32_t queueFamilyIndex = 0;\n   for (; queueFamilyIndex < queueFamilyProps.size(); ++queueFamilyIndex) {\n      auto &qfp = queueFamilyProps[queueFamilyIndex];\n\n      if (!(qfp.queueFlags & (vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eTransfer | vk::QueueFlagBits::eCompute))) {\n         continue;\n      }\n\n      break;\n   }\n\n   if (queueFamilyIndex >= queueFamilyProps.size()) {\n      printf(\"Failed to find a suitable Vulkan queue to use.\\n\");\n      return false;\n   }\n\n   std::array<float, 1> queuePriorities = { 0.0f };\n   vk::DeviceQueueCreateInfo deviceQueueCreateInfo(\n      vk::DeviceQueueCreateFlags(),\n      queueFamilyIndex,\n      static_cast<uint32_t>(queuePriorities.size()),\n      queuePriorities.data());\n\n   vk::PhysicalDeviceFeatures deviceFeatures;\n\n   vk::DeviceCreateInfo deviceDesc = { };\n   deviceDesc.queueCreateInfoCount = 1;\n   deviceDesc.pQueueCreateInfos = &deviceQueueCreateInfo;\n   deviceDesc.enabledLayerCount = static_cast<uint32_t>(deviceLayers.size());\n   deviceDesc.ppEnabledLayerNames = deviceLayers.data();\n   deviceDesc.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());\n   deviceDesc.ppEnabledExtensionNames = deviceExtensions.data();\n   deviceDesc.pEnabledFeatures = &deviceFeatures;\n   deviceDesc.pNext = nullptr;\n   gDevice = gPhysDevice.createDevice(deviceDesc);\n\n   // Pick our queue\n   gQueue = gDevice.getQueue(queueFamilyIndex, 0);\n   gQueueFamilyIndex = queueFamilyIndex;\n\n   // Grab a command pool\n   gCommandPool = gDevice.createCommandPool(\n      vk::CommandPoolCreateInfo(\n         vk::CommandPoolCreateFlagBits::eTransient,\n         gQueueFamilyIndex));\n\n   // Set up our debug reporting callback\n   vk::DispatchLoaderDynamic vkDynLoader;\n   vkDynLoader.init(gVulkan, ::vkGetInstanceProcAddr);\n   if (vkDynLoader.vkCreateDebugReportCallbackEXT) {\n      vk::DebugReportCallbackCreateInfoEXT dbgReportDesc;\n      dbgReportDesc.flags =\n         vk::DebugReportFlagBitsEXT::eDebug |\n         vk::DebugReportFlagBitsEXT::eWarning |\n         vk::DebugReportFlagBitsEXT::eError |\n         vk::DebugReportFlagBitsEXT::ePerformanceWarning;\n      dbgReportDesc.pfnCallback = debugMessageCallback;\n      dbgReportDesc.pUserData = nullptr;\n      gVulkan.createDebugReportCallbackEXT(dbgReportDesc, nullptr, vkDynLoader);\n   }\n\n   return true;\n}\n\nbool\nshutdownVulkan()\n{\n   return true;\n}\n\nSsboBuffer\nallocateSsboBuffer(uint32_t size, SsboBufferUsage usage)\n{\n   auto findMemoryType = [&](uint32_t typeFilter, vk::MemoryPropertyFlags props)\n   {\n      auto memProps = gPhysDevice.getMemoryProperties();\n\n      for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {\n         if ((typeFilter & (1 << i)) && (memProps.memoryTypes[i].propertyFlags & props) == props) {\n            return i;\n         }\n      }\n\n      printf(\"Failed to find suitable Vulkan memory type.\\n\");\n      throw std::runtime_error(\"invalid memory type\");\n   };\n\n   vk::BufferCreateInfo bufferDesc;\n   bufferDesc.size = size;\n   bufferDesc.usage =\n      vk::BufferUsageFlagBits::eStorageBuffer |\n      vk::BufferUsageFlagBits::eTransferSrc |\n      vk::BufferUsageFlagBits::eTransferDst;\n   bufferDesc.sharingMode = vk::SharingMode::eExclusive;\n   bufferDesc.queueFamilyIndexCount = 1;\n   bufferDesc.pQueueFamilyIndices = &gQueueFamilyIndex;\n   auto buffer = gDevice.createBuffer(bufferDesc);\n\n   auto bufferMemReqs = gDevice.getBufferMemoryRequirements(buffer);\n\n   // These memory properties are stolen from VMA\n   vk::MemoryPropertyFlags memoryProps;\n   if (usage == SsboBufferUsage::Gpu) {\n      memoryProps |= vk::MemoryPropertyFlagBits::eDeviceLocal;\n   } else if (usage == SsboBufferUsage::CpuToGpu) {\n      memoryProps |= vk::MemoryPropertyFlagBits::eHostVisible;\n   } else if (usage == SsboBufferUsage::GpuToCpu) {\n      memoryProps |= vk::MemoryPropertyFlagBits::eHostVisible;\n      memoryProps |= vk::MemoryPropertyFlagBits::eHostCoherent;\n      memoryProps |= vk::MemoryPropertyFlagBits::eHostCached;\n   }\n\n   vk::MemoryAllocateInfo allocDesc;\n   allocDesc.allocationSize = bufferMemReqs.size;\n   allocDesc.memoryTypeIndex = findMemoryType(bufferMemReqs.memoryTypeBits, memoryProps);\n   auto bufferMem = gDevice.allocateMemory(allocDesc);\n\n   gDevice.bindBufferMemory(buffer, bufferMem, 0);\n\n   return SsboBuffer {\n      buffer,\n      bufferMem\n   };\n}\n\nvoid\nfreeSsboBuffer(SsboBuffer buffer)\n{\n   gDevice.destroyBuffer(buffer.buffer);\n   gDevice.freeMemory(buffer.memory);\n}\n\nvoid\nuploadSsboBuffer(SsboBuffer buffer, void *data, uint32_t size)\n{\n   auto mappedPtr = gDevice.mapMemory(buffer.memory, 0, size);\n   memcpy(mappedPtr, data, size);\n   gDevice.flushMappedMemoryRanges({ vk::MappedMemoryRange{ buffer.memory, 0, size } });\n   gDevice.unmapMemory(buffer.memory);\n}\n\nvoid\ndownloadSsboBuffer(SsboBuffer buffer, void *data, uint32_t size)\n{\n   auto mappedPtr = gDevice.mapMemory(buffer.memory, 0, size);\n   gDevice.invalidateMappedMemoryRanges({ vk::MappedMemoryRange{ buffer.memory, 0, size } });\n   memcpy(data, mappedPtr, size);\n   gDevice.unmapMemory(buffer.memory);\n}\n\nSyncCmdBuffer\nallocSyncCmdBuffer()\n{\n   // Allocate a command buffer to use\n   vk::CommandBufferAllocateInfo cmdBufferDesc = { };\n   cmdBufferDesc.commandPool = gCommandPool;\n   cmdBufferDesc.level = vk::CommandBufferLevel::ePrimary;\n   cmdBufferDesc.commandBufferCount = 1;\n   auto cmdBuffer = gDevice.allocateCommandBuffers(cmdBufferDesc)[0];\n\n   // Preallocate a fence for executing\n   auto waitFence = gDevice.createFence(vk::FenceCreateInfo {});\n\n   // Return our object\n   SyncCmdBuffer syncCmdBuffer;\n   syncCmdBuffer.cmds = cmdBuffer;\n   syncCmdBuffer.fence = waitFence;\n   return syncCmdBuffer;\n}\n\nvoid\nfreeSyncCmdBuffer(SyncCmdBuffer cmdBuffer)\n{\n   // Free our temporary fence\n   gDevice.destroyFence(cmdBuffer.fence);\n\n   // Free our command buffer\n   gDevice.freeCommandBuffers(gCommandPool, { cmdBuffer.cmds });\n}\n\nvoid\nbeginSyncCmdBuffer(SyncCmdBuffer cmdBuffer)\n{\n   // Start recording our command buffer\n   cmdBuffer.cmds.begin(\n      vk::CommandBufferBeginInfo(\n         vk::CommandBufferUsageFlagBits::eOneTimeSubmit));\n}\n\nvoid\nendSyncCmdBuffer(SyncCmdBuffer cmdBuffer)\n{\n   // End recording our command buffer\n   cmdBuffer.cmds.end();\n}\n\nvoid\nexecSyncCmdBuffer(SyncCmdBuffer cmdBuffer)\n{\n   // Submit this command buffer and wait for completion\n   vk::SubmitInfo submitDesc;\n   submitDesc.commandBufferCount = 1;\n   submitDesc.pCommandBuffers = &cmdBuffer.cmds;\n   gQueue.submit(submitDesc, cmdBuffer.fence);\n\n   // Wait for the command buffer to complete\n   gDevice.waitForFences({ cmdBuffer.fence }, true, -1);\n}\n\nvoid\nglobalVkMemoryBarrier(vk::CommandBuffer cmdBuffer, vk::AccessFlags srcAccessMask, vk::AccessFlags dstAccessMask)\n{\n   // Barrier our host writes the transfer reads\n   cmdBuffer.pipelineBarrier(\n      vk::PipelineStageFlagBits::eAllCommands,\n      vk::PipelineStageFlagBits::eAllCommands,\n      vk::DependencyFlags(),\n      { vk::MemoryBarrier(srcAccessMask, dstAccessMask) },\n      {}, {}, {}\n   );\n}\n\n#endif"
  },
  {
    "path": "tests/gpu/tiling/vulkan_helpers.h",
    "content": "#pragma once\n#ifdef DECAF_VULKAN\n\n#include <common/platform.h>\n#include <common/platform_debug.h>\n#include <common/vulkan_hpp.h>\n#include <fmt/core.h>\n\nextern vk::Instance gVulkan;\nextern vk::PhysicalDevice gPhysDevice;\nextern vk::Device gDevice;\nextern vk::Queue gQueue;\nextern uint32_t gQueueFamilyIndex;\nextern vk::CommandPool gCommandPool;\n\nenum class SsboBufferUsage\n{\n   Gpu,\n   CpuToGpu,\n   GpuToCpu\n};\n\nstruct SsboBuffer\n{\n   vk::Buffer buffer;\n   vk::DeviceMemory memory;\n};\n\nstruct SyncCmdBuffer\n{\n   vk::CommandBuffer cmds;\n   vk::Fence fence;\n};\n\nbool initialiseVulkan();\nbool shutdownVulkan();\n\nSsboBuffer allocateSsboBuffer(uint32_t size, SsboBufferUsage usage);\nvoid freeSsboBuffer(SsboBuffer buffer);\nvoid uploadSsboBuffer(SsboBuffer buffer, void *data, uint32_t size);\nvoid downloadSsboBuffer(SsboBuffer buffer, void *data, uint32_t size);\n\nSyncCmdBuffer allocSyncCmdBuffer();\nvoid freeSyncCmdBuffer(SyncCmdBuffer cmdBuffer);\nvoid beginSyncCmdBuffer(SyncCmdBuffer cmdBuffer);\nvoid endSyncCmdBuffer(SyncCmdBuffer cmdBuffer);\nvoid execSyncCmdBuffer(SyncCmdBuffer cmdBuffer);\n\nvoid globalVkMemoryBarrier(vk::CommandBuffer cmdBuffer, vk::AccessFlags srcAccessMask, vk::AccessFlags dstAccessMask);\n\n#endif // DECAF_VULKAN\n"
  },
  {
    "path": "tests/gpu/tiling/vulkan_tiling_test.cpp",
    "content": "#ifdef DECAF_VULKAN\n\n#include \"tiling_tests.h\"\n#include \"addrlib_helpers.h\"\n#include \"test_helpers.h\"\n\n#include <common/align.h>\n#include <libgpu/gpu7_tiling_vulkan.h>\n#include <vector>\n\n#include \"vulkan_helpers.h\"\n\nstatic gpu7::tiling::vulkan::Retiler gVkRetiler;\n\nstatic inline void\ncompareTilingToAddrLib(const gpu7::tiling::SurfaceDescription& desc,\n                       std::vector<uint8_t>& input,\n                       uint32_t firstSlice, uint32_t numSlices)\n{\n   // Set up AddrLib to generate data to test against\n   auto addrLib = AddrLib { };\n\n   auto alibUntiled = std::vector<uint8_t> { };\n   auto gpu7Untiled = std::vector<uint8_t> { };\n   auto alibTiled = std::vector<uint8_t> { };\n   auto gpu7Tiled = std::vector<uint8_t> { };\n\n   // Compute surface info\n   auto alibInfo = addrLib.computeSurfaceInfo(desc, 0, 0);\n   auto gpu7Info = gpu7::tiling::computeSurfaceInfo(desc, 0);\n\n   REQUIRE(gpu7Info.surfSize == alibInfo.surfSize);\n   REQUIRE(input.size() >= gpu7Info.surfSize);\n\n   alibUntiled.resize(alibInfo.surfSize);\n   alibTiled.resize(alibInfo.surfSize);\n   gpu7Untiled.resize(gpu7Info.surfSize);\n   gpu7Tiled.resize(gpu7Info.surfSize);\n\n   // AddrLib\n   {\n      addrLib.untileSlices(desc, 0, input.data(), alibUntiled.data(), firstSlice, numSlices);\n      addrLib.tileSlices(desc, 0, alibUntiled.data(), alibTiled.data(), firstSlice, numSlices);\n   }\n\n   // Get the sizes we will use for our buffers.  We oversize the work\n   // buffers to avoid errors causing buffer overruns which crash my GPU.\n   auto surfSize = gpu7Info.surfSize;\n   auto uploadSize = gpu7Info.surfSize;\n   auto workSize = gpu7Info.surfSize * 2;\n\n   // Create input/output buffers\n   auto uploadBuffer = allocateSsboBuffer(uploadSize, SsboBufferUsage::CpuToGpu);\n   auto inputBuffer = allocateSsboBuffer(workSize, SsboBufferUsage::Gpu);\n   auto untiledOutputBuffer = allocateSsboBuffer(workSize, SsboBufferUsage::Gpu);\n   auto tiledOutputBuffer = allocateSsboBuffer(workSize, SsboBufferUsage::Gpu);\n   auto untiledDownloadBuffer = allocateSsboBuffer(surfSize, SsboBufferUsage::CpuToGpu);\n   auto tiledDownloadBuffer = allocateSsboBuffer(surfSize, SsboBufferUsage::CpuToGpu);\n\n   // Upload to the upload buffer\n   uploadSsboBuffer(uploadBuffer, input.data(), uploadSize);\n\n   {\n      // Allocate a command buffer and fence\n      auto cmdBuffer = allocSyncCmdBuffer();\n      beginSyncCmdBuffer(cmdBuffer);\n\n      // Barrier our host writes the transfer reads\n      globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eHostWrite, vk::AccessFlagBits::eTransferRead);\n\n      // Clear the input/output buffers to a known value so its obvious if something goes wrong\n      cmdBuffer.cmds.fillBuffer(inputBuffer.buffer, 0, surfSize, 0xffffffff);\n      cmdBuffer.cmds.fillBuffer(inputBuffer.buffer, surfSize, workSize - surfSize, 0xfefefefe);\n      cmdBuffer.cmds.fillBuffer(untiledOutputBuffer.buffer, 0, surfSize, 0x00000000);\n      cmdBuffer.cmds.fillBuffer(untiledOutputBuffer.buffer, surfSize, workSize - surfSize, 0x01010101);\n      cmdBuffer.cmds.fillBuffer(tiledOutputBuffer.buffer, 0, surfSize, 0x00000000);\n      cmdBuffer.cmds.fillBuffer(tiledOutputBuffer.buffer, surfSize, workSize - surfSize, 0x01010101);\n\n      // Copy the data\n      cmdBuffer.cmds.copyBuffer(uploadBuffer.buffer, inputBuffer.buffer, { vk::BufferCopy(0, 0, uploadSize) });\n\n      // Barrier our transfers to the shader reads\n      globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eShaderRead);\n\n      // Dispatch the actual retile\n      auto retileInfo = gpu7::tiling::computeRetileInfo(gpu7Info);\n\n      auto tiledFirstSliceIndex = align_down(firstSlice, retileInfo.microTileThickness);\n      auto tiledSliceOffset = tiledFirstSliceIndex * retileInfo.thinSliceBytes;\n      auto untiledSliceOffset = firstSlice * retileInfo.thinSliceBytes;\n\n      auto untileHandle = gVkRetiler.untile(retileInfo,\n                                            cmdBuffer.cmds,\n                                            untiledOutputBuffer.buffer, untiledSliceOffset,\n                                            inputBuffer.buffer, tiledSliceOffset,\n                                            firstSlice, numSlices);\n\n      // Barrier between these to force the pipeline flush\n      globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eShaderRead);\n\n      auto tileHandle = gVkRetiler.tile(retileInfo,\n                                        cmdBuffer.cmds,\n                                        tiledOutputBuffer.buffer, tiledSliceOffset,\n                                        untiledOutputBuffer.buffer, untiledSliceOffset,\n                                        firstSlice, numSlices);\n\n      // Put a barrier from the shader writes to the transfers\n      globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eTransferRead);\n\n      // Copy the output buffer to the download buffer\n      cmdBuffer.cmds.copyBuffer(untiledOutputBuffer.buffer, untiledDownloadBuffer.buffer, { vk::BufferCopy(0, 0, surfSize) });\n      cmdBuffer.cmds.copyBuffer(tiledOutputBuffer.buffer, tiledDownloadBuffer.buffer, { vk::BufferCopy(0, 0, surfSize) });\n\n      // Put a barrier from the transfer writes to the host reads\n      globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eHostRead);\n\n      // End, execute and free this buffer\n      endSyncCmdBuffer(cmdBuffer);\n      execSyncCmdBuffer(cmdBuffer);\n      freeSyncCmdBuffer(cmdBuffer);\n\n      // Free the retiler resources we used\n      gVkRetiler.releaseHandle(untileHandle);\n      gVkRetiler.releaseHandle(tileHandle);\n   }\n\n   // Capture the retiled data from the GPU\n   downloadSsboBuffer(untiledDownloadBuffer, gpu7Untiled.data(), surfSize);\n   downloadSsboBuffer(tiledDownloadBuffer, gpu7Tiled.data(), surfSize);\n\n   // Free the buffers associated with this\n   freeSsboBuffer(uploadBuffer);\n   freeSsboBuffer(inputBuffer);\n   freeSsboBuffer(untiledOutputBuffer);\n   freeSsboBuffer(tiledOutputBuffer);\n   freeSsboBuffer(untiledDownloadBuffer);\n   freeSsboBuffer(tiledDownloadBuffer);\n\n   // Compare that the images match\n   CHECK(compareImages(gpu7Untiled, alibUntiled));\n   CHECK(compareImages(gpu7Tiled, alibTiled));\n}\n\nTEST_CASE(\"vkTiling\")\n{\n   for (auto& layout : sTestLayout) {\n      SECTION(fmt::format(\"{}x{}x{} s{}n{}\",\n                          layout.width, layout.height, layout.depth,\n                          layout.testFirstSlice, layout.testNumSlices))\n      {\n         for (auto& mode : sTestTilingMode) {\n            SECTION(fmt::format(\"{}\", tileModeToString(mode.tileMode)))\n            {\n               for (auto& format : sTestFormats) {\n                  SECTION(fmt::format(\"{}bpp{}\", format.bpp, format.depth ? \" depth\" : \"\"))\n                  {\n                     auto surface = gpu7::tiling::SurfaceDescription { };\n                     surface.tileMode = mode.tileMode;\n                     surface.format = format.format;\n                     surface.bpp = format.bpp;\n                     surface.width = layout.width;\n                     surface.height = layout.height;\n                     surface.numSlices = layout.depth;\n                     surface.numSamples = 1u;\n                     surface.numLevels = 1u;\n                     surface.bankSwizzle = 0u;\n                     surface.pipeSwizzle = 0u;\n                     surface.dim = gpu7::tiling::SurfaceDim::Texture2DArray;\n                     surface.use = format.depth ?\n                        gpu7::tiling::SurfaceUse::DepthBuffer :\n                        gpu7::tiling::SurfaceUse::None;\n\n                     compareTilingToAddrLib(surface,\n                                            sRandomData,\n                                            layout.testFirstSlice,\n                                            layout.testNumSlices);\n                  }\n               }\n            }\n         }\n      }\n   }\n}\n\nstruct PendingVkPerfEntry\n{\n   gpu7::tiling::SurfaceDescription desc;\n   uint32_t firstSlice;\n   uint32_t numSlices;\n\n   gpu7::tiling::SurfaceInfo info;\n   SsboBuffer uploadBuffer;\n   SsboBuffer inputBuffer;\n   SsboBuffer outputBuffer;\n\n   gpu7::tiling::vulkan::RetileHandle handle;\n};\n\nTEST_CASE(\"vkTilingPerf\", \"[!benchmark]\")\n{\n   // Set up AddrLib to generate data to test against\n   auto addrLib = AddrLib { };\n\n   // Get some random data to use\n   auto& untiled = sRandomData;\n\n   // Some place to store pending tests\n   std::vector<PendingVkPerfEntry> pendingTests;\n\n   // Generate all the test cases to run\n   auto& layout = sPerfTestLayout;\n   for (auto& mode : sTestTilingMode) {\n      for (auto& format : sTestFormats) {\n         auto surface = gpu7::tiling::SurfaceDescription { };\n         surface.tileMode = mode.tileMode;\n         surface.format = format.format;\n         surface.bpp = format.bpp;\n         surface.width = layout.width;\n         surface.height = layout.height;\n         surface.numSlices = layout.depth;\n         surface.numSamples = 1u;\n         surface.numLevels = 1u;\n         surface.bankSwizzle = 0u;\n         surface.pipeSwizzle = 0u;\n         surface.dim = gpu7::tiling::SurfaceDim::Texture2DArray;\n         surface.use = format.depth ?\n            gpu7::tiling::SurfaceUse::DepthBuffer :\n            gpu7::tiling::SurfaceUse::None;\n\n         PendingVkPerfEntry test;\n         test.desc = surface;\n         test.firstSlice = layout.testFirstSlice;\n         test.numSlices = layout.testNumSlices;\n         pendingTests.push_back(test);\n      }\n   }\n\n   // Set up all the tests\n   for (auto& test : pendingTests) {\n      // Compute some needed surface information\n      auto info = gpu7::tiling::computeSurfaceInfo(test.desc, 0);\n      auto surfSize = static_cast<uint32_t>(info.surfSize);\n\n      // Make sure our test data is big enough\n      REQUIRE(untiled.size() >= info.surfSize);\n\n      // Create input/output buffers\n      auto uploadBuffer = allocateSsboBuffer(surfSize, SsboBufferUsage::CpuToGpu);\n      auto inputBuffer = allocateSsboBuffer(surfSize, SsboBufferUsage::Gpu);\n      auto outputBuffer = allocateSsboBuffer(surfSize, SsboBufferUsage::Gpu);\n\n      // Upload to the upload buffer\n      uploadSsboBuffer(uploadBuffer, untiled.data(), surfSize);\n\n      // Store the state between setup loops\n      test.info = info;\n      test.uploadBuffer = uploadBuffer;\n      test.inputBuffer = inputBuffer;\n      test.outputBuffer = outputBuffer;\n   }\n\n   // Copy our uploaded data to the input buffers\n   {\n      auto cmdBuffer = allocSyncCmdBuffer();\n      beginSyncCmdBuffer(cmdBuffer);\n\n      for (auto& test : pendingTests) {\n         auto surfSize = static_cast<uint32_t>(test.info.surfSize);\n\n         // Barrier our host writes the transfer reads\n         globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eHostWrite, vk::AccessFlagBits::eTransferRead);\n\n         // Set up the input/output buffers on the GPU\n         cmdBuffer.cmds.copyBuffer(test.uploadBuffer.buffer,\n                                   test.inputBuffer.buffer,\n                                   { vk::BufferCopy(0, 0, surfSize) });\n\n         // Barrier our transfers to the shader reads\n         globalVkMemoryBarrier(cmdBuffer.cmds, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eShaderRead);\n      }\n\n      endSyncCmdBuffer(cmdBuffer);\n      execSyncCmdBuffer(cmdBuffer);\n      freeSyncCmdBuffer(cmdBuffer);\n   }\n\n   // Run the retiles\n   {\n      auto cmdBuffer = allocSyncCmdBuffer();\n      beginSyncCmdBuffer(cmdBuffer);\n\n      for (auto& test : pendingTests) {\n         // Calculate data on how to retile\n         auto retileInfo = gpu7::tiling::computeRetileInfo(test.info);\n\n         auto tiledFirstSliceIndex = align_down(test.firstSlice, retileInfo.microTileThickness);\n         auto tiledSliceOffset = tiledFirstSliceIndex * retileInfo.thinSliceBytes;\n         auto untiledSliceOffset = test.firstSlice * retileInfo.thinSliceBytes;\n\n         // Dispatch the actual retile\n         auto handle = gVkRetiler.untile(retileInfo,\n                                         cmdBuffer.cmds,\n                                         test.outputBuffer.buffer, untiledSliceOffset,\n                                         test.inputBuffer.buffer, tiledSliceOffset,\n                                         test.firstSlice, test.numSlices);\n\n         // Save some information for freeing later\n         test.handle = handle;\n      }\n\n      endSyncCmdBuffer(cmdBuffer);\n\n      BENCHMARK(fmt::format(\"processing ({} retiles)\", pendingTests.size()))\n      {\n         execSyncCmdBuffer(cmdBuffer);\n      };\n\n      freeSyncCmdBuffer(cmdBuffer);\n   }\n\n   // Clean up all our resources used...\n   for (auto& test : pendingTests) {\n      // Free the retiler resources we used\n      gVkRetiler.releaseHandle(test.handle);\n\n      // Free the buffers we allocated\n      freeSsboBuffer(test.uploadBuffer);\n      freeSsboBuffer(test.inputBuffer);\n      freeSsboBuffer(test.outputBuffer);\n   }\n}\n\nbool vulkanBeforeStart()\n{\n   if (!initialiseVulkan()) {\n      return false;\n   }\n\n   // Initialize our retiler\n   gVkRetiler.initialise(gDevice);\n   return true;\n}\n\nbool vulkanAfterComplete()\n{\n   return shutdownVulkan();\n}\n\n#endif // DECAF_VULKAN\n"
  },
  {
    "path": "tools/CMakeLists.txt",
    "content": "project(tools)\ninclude(ExternalProject)\ninclude_directories(\".\")\ninclude_directories(\"../src\")\n\nadd_subdirectory(gfd-tool)\nadd_subdirectory(latte-assembler)\n\nif(DECAF_GL)\n   add_subdirectory(pm4-replay)\n\n   if(DECAF_QT)\n       # add_subdirectory(pm4-replay-qt)\n   endif()\nendif()\n\nif(DEVKITPPC AND WUT_ROOT)\n   externalproject_add(wiiu-rpc\n      SOURCE_DIR \"${PROJECT_SOURCE_DIR}/wiiu-rpc\"\n      INSTALL_COMMAND \"\"\n      CMAKE_GENERATOR \"Unix Makefiles\"\n      CMAKE_CACHE_ARGS\n         -DDEVKITPPC:string=${DEVKITPPC}\n         -DWUT_ROOT:string=${WUT_ROOT}\n         -DCMAKE_TOOLCHAIN_FILE:string=${WUT_ROOT}/share/wut.toolchain.cmake)\n   set_target_properties(wiiu-rpc PROPERTIES FOLDER tools)\n\n   externalproject_add_step(wiiu-rpc forcebuild\n      COMMAND ${CMAKE_COMMAND} -E echo \"Force build of wiiu-rpc\"\n      DEPENDEES \"configure\"\n      DEPENDERS \"build\"\n      ALWAYS 1)\nendif()\n"
  },
  {
    "path": "tools/gfd-tool/CMakeLists.txt",
    "content": "project(gfd-tool)\n\ninclude_directories(\".\")\ninclude_directories(\"../../src/libdecaf/src\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nadd_executable(gfd-tool ${SOURCE_FILES} ${HEADER_FILES})\nset_target_properties(gfd-tool PROPERTIES FOLDER tools)\n\ntarget_link_libraries(gfd-tool\n    common\n    libdecaf\n    libgfd\n    excmd)\n\ninstall(TARGETS gfd-tool RUNTIME DESTINATION \"${DECAF_INSTALL_BINDIR}\")\n"
  },
  {
    "path": "tools/gfd-tool/gfdtool.cpp",
    "content": "#include <libgfd/gfd.h>\n\n#include <cassert>\n#include <common/teenyheap.h>\n#include <excmd.h>\n#include <fmt/core.h>\n#include <fstream>\n#include <gsl/gsl-lite.hpp>\n#include <iostream>\n#include <iterator>\n#include <libcpu/cpu.h>\n#include <libgfd/gfd.h>\n#include <libgpu/gpu7_tiling_cpu.h>\n#include <libgpu/latte/latte_disassembler.h>\n#include <libgpu/latte/latte_formats.h>\n#include <libdecaf/src/cafe/libraries/gx2/gx2_debug_dds.h>\n#include <libdecaf/src/cafe/libraries/gx2/gx2_enum_string.h>\n#include <libdecaf/src/cafe/libraries/gx2/gx2_internal_gfd.h>\n#include <spdlog/spdlog.h>\n\nstruct OutputState\n{\n   fmt::memory_buffer writer;\n   std::string indent;\n};\n\nstatic void\nincreaseIndent(OutputState &out)\n{\n   out.indent += \"  \";\n}\n\nstatic void\ndecreaseIndent(OutputState &out)\n{\n   if (out.indent.size() >= 2) {\n      out.indent.resize(out.indent.size() - 2);\n   }\n}\n\nstatic void\nstartGroup(OutputState &out, const std::string &group)\n{\n   fmt::format_to(std::back_inserter(out.writer), \"{}{}\\n\", out.indent, group);\n   increaseIndent(out);\n}\n\nstatic void\nendGroup(OutputState &out)\n{\n   decreaseIndent(out);\n}\n\ntemplate<typename Type>\nstatic void\nwriteField(OutputState &out, const std::string &field, const Type &value)\n{\n   fmt::format_to(std::back_inserter(out.writer), \"{}{:<30} = {}\\n\", out.indent, field, value);\n}\n\nstatic bool\nprintInfo(const std::string &filename)\n{\n   OutputState out;\n   gfd::GFDFile file;\n\n   try {\n      if (!gfd::readFile(file, filename)) {\n         return false;\n      }\n   } catch (gfd::GFDReadException ex) {\n      std::cerr << fmt::format(\"Error reading gfd: {}\", ex.what()) << std::endl;\n      return false;\n   }\n\n   for (auto &shader : file.vertexShaders) {\n      startGroup(out, \"VertexShaderHeader\");\n      {\n         writeField(out, \"size\", shader.data.size());\n         writeField(out, \"mode\", cafe::gx2::to_string(shader.mode));\n\n         writeField(out, \"uniformBlockCount\", shader.uniformBlocks.size());\n\n         for (auto i = 0u; i < shader.uniformBlocks.size(); ++i) {\n            startGroup(out, fmt::format(\"uniformBlocks[{}]\", i));\n            {\n               auto &var = shader.uniformBlocks[i];\n               writeField(out, \"name\", var.name);\n               writeField(out, \"offset\", var.offset);\n               writeField(out, \"size\", var.size);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"uniformVarCount\", shader.uniformVars.size());\n\n         for (auto i = 0u; i < shader.uniformVars.size(); ++i) {\n            startGroup(out, fmt::format(\"uniformVars[{}]\", i));\n            {\n               auto &var = shader.uniformVars[i];\n               writeField(out, \"name\", var.name);\n               writeField(out, \"type\", cafe::gx2::to_string(var.type));\n               writeField(out, \"count\", var.count);\n               writeField(out, \"offset\", var.offset);\n               writeField(out, \"block\", var.block);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"initialValueCount\", shader.initialValues.size());\n\n         for (auto i = 0u; i < shader.initialValues.size(); ++i) {\n            startGroup(out, fmt::format(\"initialValues[{}]\", i));\n            {\n               auto &var = shader.initialValues[i];\n               writeField(out, \"value.x\", var.value[0]);\n               writeField(out, \"value.y\", var.value[1]);\n               writeField(out, \"value.z\", var.value[2]);\n               writeField(out, \"value.w\", var.value[3]);\n               writeField(out, \"offset\", var.offset);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"loopVarCount\", shader.loopVars.size());\n\n         for (auto i = 0u; i < shader.loopVars.size(); ++i) {\n            startGroup(out, fmt::format(\"loopVars[{}]\", i));\n            {\n               auto &var = shader.loopVars[i];\n               writeField(out, \"offset\", var.offset);\n               writeField(out, \"value\", var.value);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"samplerVarCount\", shader.samplerVars.size());\n\n         for (auto i = 0u; i < shader.samplerVars.size(); ++i) {\n            startGroup(out, fmt::format(\"samplerVars[{}]\", i));\n            {\n               auto &var = shader.samplerVars[i];\n               writeField(out, \"name\", var.name);\n               writeField(out, \"type\", cafe::gx2::to_string(var.type));\n               writeField(out, \"location\", var.location);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"attribVarCount\", shader.attribVars.size());\n\n         for (auto i = 0u; i < shader.attribVars.size(); ++i) {\n            startGroup(out, fmt::format(\"attribVars[{}]\", i));\n            {\n               auto &var = shader.attribVars[i];\n               writeField(out, \"name\", var.name);\n               writeField(out, \"type\", cafe::gx2::to_string(var.type));\n               writeField(out, \"count\", var.count);\n               writeField(out, \"location\", var.location);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"ringItemsize\", shader.ringItemSize);\n         writeField(out, \"hasStreamOut\", shader.hasStreamOut);\n\n         startGroup(out, \"SQ_PGM_RESOURCES_VS\");\n         {\n            auto sq_pgm_resources_vs = shader.regs.sq_pgm_resources_vs;\n            writeField(out, \"NUM_GPRS\", sq_pgm_resources_vs.NUM_GPRS());\n            writeField(out, \"STACK_SIZE\", sq_pgm_resources_vs.STACK_SIZE());\n            writeField(out, \"DX10_CLAMP\", sq_pgm_resources_vs.DX10_CLAMP());\n            writeField(out, \"PRIME_CACHE_PGM_EN\", sq_pgm_resources_vs.PRIME_CACHE_PGM_EN());\n            writeField(out, \"PRIME_CACHE_ON_DRAW\", sq_pgm_resources_vs.PRIME_CACHE_ON_DRAW());\n            writeField(out, \"FETCH_CACHE_LINES\", sq_pgm_resources_vs.FETCH_CACHE_LINES());\n            writeField(out, \"UNCACHED_FIRST_INST\", sq_pgm_resources_vs.UNCACHED_FIRST_INST());\n            writeField(out, \"PRIME_CACHE_ENABLE\", sq_pgm_resources_vs.PRIME_CACHE_ENABLE());\n            writeField(out, \"PRIME_CACHE_ON_CONST\", sq_pgm_resources_vs.PRIME_CACHE_ON_CONST());\n         }\n         endGroup(out);\n\n         startGroup(out, \"VGT_PRIMITIVEID_EN\");\n         {\n            auto vgt_primitiveid_en = shader.regs.vgt_primitiveid_en;\n            writeField(out, \"PRIMITIVEID_EN\", vgt_primitiveid_en.PRIMITIVEID_EN());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SPI_VS_OUT_CONFIG\");\n         {\n            auto spi_vs_out_config = shader.regs.spi_vs_out_config;\n            writeField(out, \"VS_PER_COMPONENT\", spi_vs_out_config.VS_PER_COMPONENT());\n            writeField(out, \"VS_EXPORT_COUNT\", spi_vs_out_config.VS_EXPORT_COUNT());\n            writeField(out, \"VS_EXPORTS_FOG\", spi_vs_out_config.VS_EXPORTS_FOG());\n            writeField(out, \"VS_OUT_FOG_VEC_ADDR\", spi_vs_out_config.VS_OUT_FOG_VEC_ADDR());\n         }\n         endGroup(out);\n\n         auto num_spi_vs_out_id = shader.regs.num_spi_vs_out_id;\n         writeField(out, \"NUM_SPI_VS_OUT_ID\", num_spi_vs_out_id);\n\n         auto spi_vs_out_id = shader.regs.spi_vs_out_id;\n\n         for (auto i = 0u; i < std::min<size_t>(num_spi_vs_out_id, spi_vs_out_id.size()); ++i) {\n            startGroup(out, fmt::format(\"SPI_VS_OUT_ID[{}]\", i));\n            {\n               writeField(out, \"SEMANTIC_0\", spi_vs_out_id[i].SEMANTIC_0());\n               writeField(out, \"SEMANTIC_1\", spi_vs_out_id[i].SEMANTIC_1());\n               writeField(out, \"SEMANTIC_2\", spi_vs_out_id[i].SEMANTIC_2());\n               writeField(out, \"SEMANTIC_3\", spi_vs_out_id[i].SEMANTIC_3());\n            }\n            endGroup(out);\n         }\n\n         startGroup(out, \"PA_CL_VS_OUT_CNTL\");\n         {\n            auto pa_cl_vs_out_cntl = shader.regs.pa_cl_vs_out_cntl;\n            writeField(out, \"CLIP_DIST_ENA_0\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_0());\n            writeField(out, \"CLIP_DIST_ENA_1\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_1());\n            writeField(out, \"CLIP_DIST_ENA_2\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_2());\n            writeField(out, \"CLIP_DIST_ENA_3\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_3());\n            writeField(out, \"CLIP_DIST_ENA_4\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_4());\n            writeField(out, \"CLIP_DIST_ENA_5\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_5());\n            writeField(out, \"CLIP_DIST_ENA_6\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_6());\n            writeField(out, \"CLIP_DIST_ENA_7\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_7());\n            writeField(out, \"CULL_DIST_ENA_0\", pa_cl_vs_out_cntl.CULL_DIST_ENA_0());\n            writeField(out, \"CULL_DIST_ENA_1\", pa_cl_vs_out_cntl.CULL_DIST_ENA_1());\n            writeField(out, \"CULL_DIST_ENA_2\", pa_cl_vs_out_cntl.CULL_DIST_ENA_2());\n            writeField(out, \"CULL_DIST_ENA_3\", pa_cl_vs_out_cntl.CULL_DIST_ENA_3());\n            writeField(out, \"CULL_DIST_ENA_4\", pa_cl_vs_out_cntl.CULL_DIST_ENA_4());\n            writeField(out, \"CULL_DIST_ENA_5\", pa_cl_vs_out_cntl.CULL_DIST_ENA_5());\n            writeField(out, \"CULL_DIST_ENA_6\", pa_cl_vs_out_cntl.CULL_DIST_ENA_6());\n            writeField(out, \"CULL_DIST_ENA_7\", pa_cl_vs_out_cntl.CULL_DIST_ENA_7());\n            writeField(out, \"USE_VTX_POINT_SIZE\", pa_cl_vs_out_cntl.USE_VTX_POINT_SIZE());\n            writeField(out, \"USE_VTX_EDGE_FLAG\", pa_cl_vs_out_cntl.USE_VTX_EDGE_FLAG());\n            writeField(out, \"USE_VTX_RENDER_TARGET_INDX\", pa_cl_vs_out_cntl.USE_VTX_RENDER_TARGET_INDX());\n            writeField(out, \"USE_VTX_VIEWPORT_INDX\", pa_cl_vs_out_cntl.USE_VTX_VIEWPORT_INDX());\n            writeField(out, \"USE_VTX_KILL_FLAG\", pa_cl_vs_out_cntl.USE_VTX_KILL_FLAG());\n            writeField(out, \"VS_OUT_MISC_VEC_ENA\", pa_cl_vs_out_cntl.VS_OUT_MISC_VEC_ENA());\n            writeField(out, \"VS_OUT_CCDIST0_VEC_ENA\", pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA());\n            writeField(out, \"VS_OUT_CCDIST1_VEC_ENA\", pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA());\n            writeField(out, \"VS_OUT_MISC_SIDE_BUS_ENA\", pa_cl_vs_out_cntl.VS_OUT_MISC_SIDE_BUS_ENA());\n            writeField(out, \"USE_VTX_GS_CUT_FLAG\", pa_cl_vs_out_cntl.USE_VTX_GS_CUT_FLAG());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SQ_VTX_SEMANTIC_CLEAR\");\n         {\n            auto sq_vtx_semantic_clear = shader.regs.sq_vtx_semantic_clear;\n            writeField(out, \"CLEAR\", sq_vtx_semantic_clear.CLEAR());\n         }\n         endGroup(out);\n\n         auto num_sq_vtx_semantic = shader.regs.num_sq_vtx_semantic;\n         writeField(out, \"NUM_SQ_VTX_SEMANTIC\", num_sq_vtx_semantic);\n\n         auto sq_vtx_semantic = shader.regs.sq_vtx_semantic;\n         for (auto i = 0u; i < std::min<size_t>(num_sq_vtx_semantic, sq_vtx_semantic.size()); ++i) {\n            startGroup(out, fmt::format(\"SQ_VTX_SEMANTIC[{}]\", i));\n            {\n               writeField(out, \"SEMANTIC_ID\", sq_vtx_semantic[i].SEMANTIC_ID());\n            }\n            endGroup(out);\n         }\n\n         startGroup(out, \"VGT_STRMOUT_BUFFER_EN\");\n         {\n            auto vgt_strmout_buffer_en = shader.regs.vgt_strmout_buffer_en;\n            writeField(out, \"BUFFER_0_EN\", vgt_strmout_buffer_en.BUFFER_0_EN());\n            writeField(out, \"BUFFER_1_EN\", vgt_strmout_buffer_en.BUFFER_1_EN());\n            writeField(out, \"BUFFER_2_EN\", vgt_strmout_buffer_en.BUFFER_2_EN());\n            writeField(out, \"BUFFER_3_EN\", vgt_strmout_buffer_en.BUFFER_3_EN());\n         }\n         endGroup(out);\n\n         startGroup(out, \"VGT_VERTEX_REUSE_BLOCK_CNTL\");\n         {\n            auto vgt_vertex_reuse_block_cntl = shader.regs.vgt_vertex_reuse_block_cntl;\n            writeField(out, \"VTX_REUSE_DEPTH\", vgt_vertex_reuse_block_cntl.VTX_REUSE_DEPTH());\n         }\n         endGroup(out);\n\n         startGroup(out, \"VGT_HOS_REUSE_DEPTH\");\n         {\n            auto vgt_hos_reuse_depth = shader.regs.vgt_hos_reuse_depth;\n            writeField(out, \"REUSE_DEPTH\", vgt_hos_reuse_depth.REUSE_DEPTH());\n         }\n         endGroup(out);\n      }\n      endGroup(out);\n\n      startGroup(out, \"VertexShaderProgram\");\n      {\n         writeField(out, \"size\", shader.data.size());\n\n         std::string disassembly;\n         disassembly = latte::disassemble(gsl::make_span(shader.data));\n         fmt::format_to(std::back_inserter(out.writer), \"\\n{}\", disassembly);\n      }\n      endGroup(out);\n   }\n\n   for (auto &shader : file.pixelShaders) {\n      startGroup(out, \"PixelShaderHeader\");\n      {\n         writeField(out, \"size\", shader.data.size());\n         writeField(out, \"mode\", cafe::gx2::to_string(shader.mode));\n\n         writeField(out, \"uniformBlockCount\", shader.uniformBlocks.size());\n\n         for (auto i = 0u; i < shader.uniformBlocks.size(); ++i) {\n            startGroup(out, fmt::format(\"uniformBlocks[{}]\", i));\n            {\n               auto &var = shader.uniformBlocks[i];\n               writeField(out, \"name\", var.name);\n               writeField(out, \"offset\", var.offset);\n               writeField(out, \"size\", var.size);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"uniformVarCount\", shader.uniformVars.size());\n\n         for (auto i = 0u; i < shader.uniformVars.size(); ++i) {\n            startGroup(out, fmt::format(\"uniformVars[{}]\", i));\n            {\n               auto &var = shader.uniformVars[i];\n               writeField(out, \"name\", var.name);\n               writeField(out, \"type\", cafe::gx2::to_string(var.type));\n               writeField(out, \"count\", var.count);\n               writeField(out, \"offset\", var.offset);\n               writeField(out, \"block\", var.block);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"initialValueCount\", shader.initialValues.size());\n\n         for (auto i = 0u; i < shader.initialValues.size(); ++i) {\n            startGroup(out, fmt::format(\"initialValues[{}]\", i));\n            {\n               auto &var = shader.initialValues[i];\n               writeField(out, \"value.x\", var.value[0]);\n               writeField(out, \"value.y\", var.value[1]);\n               writeField(out, \"value.z\", var.value[2]);\n               writeField(out, \"value.w\", var.value[3]);\n               writeField(out, \"offset\", var.offset);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"loopVarCount\", shader.loopVars.size());\n\n         for (auto i = 0u; i < shader.loopVars.size(); ++i) {\n            startGroup(out, fmt::format(\"loopVars[{}]\", i));\n            {\n               auto &var = shader.loopVars[i];\n               writeField(out, \"offset\", var.offset);\n               writeField(out, \"value\", var.value);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"samplerVarCount\", shader.samplerVars.size());\n\n         for (auto i = 0u; i < shader.samplerVars.size(); ++i) {\n            startGroup(out, fmt::format(\"samplerVars[{}]\", i));\n            {\n               auto &var = shader.samplerVars[i];\n               writeField(out, \"name\", var.name);\n               writeField(out, \"type\", cafe::gx2::to_string(var.type));\n               writeField(out, \"location\", var.location);\n            }\n            endGroup(out);\n         }\n\n         startGroup(out, \"SQ_PGM_RESOURCES_PS\");\n         {\n            auto sq_pgm_resources_ps = shader.regs.sq_pgm_resources_ps;\n            writeField(out, \"NUM_GPRS\", sq_pgm_resources_ps.NUM_GPRS());\n            writeField(out, \"STACK_SIZE\", sq_pgm_resources_ps.STACK_SIZE());\n            writeField(out, \"DX10_CLAMP\", sq_pgm_resources_ps.DX10_CLAMP());\n            writeField(out, \"PRIME_CACHE_PGM_EN\", sq_pgm_resources_ps.PRIME_CACHE_PGM_EN());\n            writeField(out, \"PRIME_CACHE_ON_DRAW\", sq_pgm_resources_ps.PRIME_CACHE_ON_DRAW());\n            writeField(out, \"FETCH_CACHE_LINES\", sq_pgm_resources_ps.FETCH_CACHE_LINES());\n            writeField(out, \"UNCACHED_FIRST_INST\", sq_pgm_resources_ps.UNCACHED_FIRST_INST());\n            writeField(out, \"PRIME_CACHE_ENABLE\", sq_pgm_resources_ps.PRIME_CACHE_ENABLE());\n            writeField(out, \"PRIME_CACHE_ON_CONST\", sq_pgm_resources_ps.PRIME_CACHE_ON_CONST());\n            writeField(out, \"CLAMP_CONSTS\", sq_pgm_resources_ps.CLAMP_CONSTS());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SQ_PGM_EXPORTS_PS\");\n         {\n            auto sq_pgm_exports_ps = shader.regs.sq_pgm_exports_ps;\n            writeField(out, \"EXPORT_MODE\", sq_pgm_exports_ps.EXPORT_MODE());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SPI_PS_IN_CONTROL_0\");\n         {\n            auto spi_ps_in_control_0 = shader.regs.spi_ps_in_control_0;\n            writeField(out, \"NUM_INTERP\", spi_ps_in_control_0.NUM_INTERP());\n            writeField(out, \"POSITION_ENA\", spi_ps_in_control_0.POSITION_ENA());\n            writeField(out, \"POSITION_CENTROID\", spi_ps_in_control_0.POSITION_CENTROID());\n            writeField(out, \"POSITION_ADDR\", spi_ps_in_control_0.POSITION_ADDR());\n            writeField(out, \"PARAM_GEN\", spi_ps_in_control_0.PARAM_GEN());\n            writeField(out, \"PARAM_GEN_ADDR\", spi_ps_in_control_0.PARAM_GEN_ADDR());\n            writeField(out, \"BARYC_SAMPLE_CNTL\", spi_ps_in_control_0.BARYC_SAMPLE_CNTL());\n            writeField(out, \"PERSP_GRADIENT_ENA\", spi_ps_in_control_0.PERSP_GRADIENT_ENA());\n            writeField(out, \"LINEAR_GRADIENT_ENA\", spi_ps_in_control_0.LINEAR_GRADIENT_ENA());\n            writeField(out, \"POSITION_SAMPLE\", spi_ps_in_control_0.POSITION_SAMPLE());\n            writeField(out, \"BARYC_AT_SAMPLE_ENA\", spi_ps_in_control_0.BARYC_AT_SAMPLE_ENA());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SPI_PS_IN_CONTROL_1\");\n         {\n            auto spi_ps_in_control_1 = shader.regs.spi_ps_in_control_1;\n            writeField(out, \"GEN_INDEX_PIX\", spi_ps_in_control_1.GEN_INDEX_PIX());\n            writeField(out, \"GEN_INDEX_PIX_ADDR\", spi_ps_in_control_1.GEN_INDEX_PIX_ADDR());\n            writeField(out, \"FRONT_FACE_ENA\", spi_ps_in_control_1.FRONT_FACE_ENA());\n            writeField(out, \"FRONT_FACE_CHAN\", spi_ps_in_control_1.FRONT_FACE_CHAN());\n            writeField(out, \"FRONT_FACE_ALL_BITS\", spi_ps_in_control_1.FRONT_FACE_ALL_BITS());\n            writeField(out, \"FRONT_FACE_ADDR\", spi_ps_in_control_1.FRONT_FACE_ADDR());\n            writeField(out, \"FOG_ADDR\", spi_ps_in_control_1.FOG_ADDR());\n            writeField(out, \"FIXED_PT_POSITION_ENA\", spi_ps_in_control_1.FIXED_PT_POSITION_ENA());\n            writeField(out, \"FIXED_PT_POSITION_ADDR\", spi_ps_in_control_1.FIXED_PT_POSITION_ADDR());\n            writeField(out, \"POSITION_ULC\", spi_ps_in_control_1.POSITION_ULC());\n         }\n         endGroup(out);\n\n         auto num_spi_ps_input_cntl = shader.regs.num_spi_ps_input_cntl;\n         auto spi_ps_input_cntls = shader.regs.spi_ps_input_cntls;\n         writeField(out, \"NUM_SPI_PS_INPUT_CNTL\", num_spi_ps_input_cntl);\n\n         for (auto i = 0u; i < std::min<size_t>(num_spi_ps_input_cntl, spi_ps_input_cntls.size()); ++i) {\n            startGroup(out, fmt::format(\"SPI_PS_INPUT_CNTL[{}]\", i));\n            {\n               writeField(out, \"SEMANTIC\", spi_ps_input_cntls[i].SEMANTIC());\n               writeField(out, \"DEFAULT_VAL\", spi_ps_input_cntls[i].DEFAULT_VAL());\n               writeField(out, \"FLAT_SHADE\", spi_ps_input_cntls[i].FLAT_SHADE());\n               writeField(out, \"SEL_CENTROID\", spi_ps_input_cntls[i].SEL_CENTROID());\n               writeField(out, \"SEL_LINEAR\", spi_ps_input_cntls[i].SEL_LINEAR());\n               writeField(out, \"CYL_WRAP\", spi_ps_input_cntls[i].CYL_WRAP());\n               writeField(out, \"PT_SPRITE_TEX\", spi_ps_input_cntls[i].PT_SPRITE_TEX());\n               writeField(out, \"SEL_SAMPLE\", spi_ps_input_cntls[i].SEL_SAMPLE());\n            }\n            endGroup(out);\n         }\n\n         startGroup(out, \"CB_SHADER_MASK\");\n         {\n            auto cb_shader_mask = shader.regs.cb_shader_mask;\n            writeField(out, \"OUTPUT0_ENABLE\", cb_shader_mask.OUTPUT0_ENABLE());\n            writeField(out, \"OUTPUT1_ENABLE\", cb_shader_mask.OUTPUT1_ENABLE());\n            writeField(out, \"OUTPUT2_ENABLE\", cb_shader_mask.OUTPUT2_ENABLE());\n            writeField(out, \"OUTPUT3_ENABLE\", cb_shader_mask.OUTPUT3_ENABLE());\n            writeField(out, \"OUTPUT4_ENABLE\", cb_shader_mask.OUTPUT4_ENABLE());\n            writeField(out, \"OUTPUT5_ENABLE\", cb_shader_mask.OUTPUT5_ENABLE());\n            writeField(out, \"OUTPUT6_ENABLE\", cb_shader_mask.OUTPUT6_ENABLE());\n            writeField(out, \"OUTPUT7_ENABLE\", cb_shader_mask.OUTPUT7_ENABLE());\n         }\n         endGroup(out);\n\n         startGroup(out, \"CB_SHADER_CONTROL\");\n         {\n            auto cb_shader_control = shader.regs.cb_shader_control;\n            writeField(out, \"RT0_ENABLE\", cb_shader_control.RT0_ENABLE());\n            writeField(out, \"RT1_ENABLE\", cb_shader_control.RT1_ENABLE());\n            writeField(out, \"RT2_ENABLE\", cb_shader_control.RT2_ENABLE());\n            writeField(out, \"RT3_ENABLE\", cb_shader_control.RT3_ENABLE());\n            writeField(out, \"RT4_ENABLE\", cb_shader_control.RT4_ENABLE());\n            writeField(out, \"RT5_ENABLE\", cb_shader_control.RT5_ENABLE());\n            writeField(out, \"RT6_ENABLE\", cb_shader_control.RT6_ENABLE());\n            writeField(out, \"RT7_ENABLE\", cb_shader_control.RT7_ENABLE());\n         }\n         endGroup(out);\n\n         startGroup(out, \"DB_SHADER_CONTROL\");\n         {\n            auto db_shader_control = shader.regs.db_shader_control;\n            writeField(out, \"Z_EXPORT_ENABLE\", db_shader_control.Z_EXPORT_ENABLE());\n            writeField(out, \"STENCIL_REF_EXPORT_ENABLE\", db_shader_control.STENCIL_REF_EXPORT_ENABLE());\n            writeField(out, \"Z_ORDER\", db_shader_control.Z_ORDER());\n            writeField(out, \"KILL_ENABLE\", db_shader_control.KILL_ENABLE());\n            writeField(out, \"COVERAGE_TO_MASK_ENABLE\", db_shader_control.COVERAGE_TO_MASK_ENABLE());\n            writeField(out, \"MASK_EXPORT_ENABLE\", db_shader_control.MASK_EXPORT_ENABLE());\n            writeField(out, \"DUAL_EXPORT_ENABLE\", db_shader_control.DUAL_EXPORT_ENABLE());\n            writeField(out, \"EXEC_ON_HIER_FAIL\", db_shader_control.EXEC_ON_HIER_FAIL());\n            writeField(out, \"EXEC_ON_NOOP\", db_shader_control.EXEC_ON_NOOP());\n            writeField(out, \"ALPHA_TO_MASK_DISABLE\", db_shader_control.ALPHA_TO_MASK_DISABLE());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SPI_INPUT_Z\");\n         {\n            auto spi_input_z = shader.regs.spi_input_z;\n            writeField(out, \"PROVIDE_Z_TO_SPI\", spi_input_z.PROVIDE_Z_TO_SPI());\n         }\n         endGroup(out);\n      }\n      endGroup(out);\n\n      startGroup(out, \"PixelShaderProgram\");\n      {\n         writeField(out, \"size\", shader.data.size());\n\n         std::string disassembly;\n         disassembly = latte::disassemble(gsl::make_span(shader.data));\n         fmt::format_to(std::back_inserter(out.writer), \"\\n{}\", disassembly);\n      }\n      endGroup(out);\n   }\n\n   for (auto &shader : file.geometryShaders) {\n      startGroup(out, \"GeometryShaderHeader\");\n      {\n         writeField(out, \"size\", shader.data.size());\n         writeField(out, \"vshSize\", shader.vertexShaderData.size());\n         writeField(out, \"mode\", cafe::gx2::to_string(shader.mode));\n\n         writeField(out, \"uniformBlockCount\", shader.uniformBlocks.size());\n\n         for (auto i = 0u; i < shader.uniformBlocks.size(); ++i) {\n            startGroup(out, fmt::format(\"uniformBlocks[{}]\", i));\n            {\n               auto &var = shader.uniformBlocks[i];\n               writeField(out, \"name\", var.name);\n               writeField(out, \"offset\", var.offset);\n               writeField(out, \"size\", var.size);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"uniformVarCount\", shader.uniformVars.size());\n\n         for (auto i = 0u; i < shader.uniformVars.size(); ++i) {\n            startGroup(out, fmt::format(\"uniformVars[{}]\", i));\n            {\n               auto &var = shader.uniformVars[i];\n               writeField(out, \"name\", var.name);\n               writeField(out, \"type\", cafe::gx2::to_string(var.type));\n               writeField(out, \"count\", var.count);\n               writeField(out, \"offset\", var.offset);\n               writeField(out, \"block\", var.block);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"initialValueCount\", shader.initialValues.size());\n\n         for (auto i = 0u; i < shader.initialValues.size(); ++i) {\n            startGroup(out, fmt::format(\"initialValues[{}]\", i));\n            {\n               auto &var = shader.initialValues[i];\n               writeField(out, \"value.x\", var.value[0]);\n               writeField(out, \"value.y\", var.value[1]);\n               writeField(out, \"value.z\", var.value[2]);\n               writeField(out, \"value.w\", var.value[3]);\n               writeField(out, \"offset\", var.offset);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"loopVarCount\", shader.loopVars.size());\n\n         for (auto i = 0u; i < shader.loopVars.size(); ++i) {\n            startGroup(out, fmt::format(\"loopVars[{}]\", i));\n            {\n               auto &var = shader.loopVars[i];\n               writeField(out, \"offset\", var.offset);\n               writeField(out, \"value\", var.value);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"samplerVarCount\", shader.samplerVars.size());\n\n         for (auto i = 0u; i < shader.samplerVars.size(); ++i) {\n            startGroup(out, fmt::format(\"samplerVars[{}]\", i));\n            {\n               auto &var = shader.samplerVars[i];\n               writeField(out, \"name\", var.name);\n               writeField(out, \"type\", cafe::gx2::to_string(var.type));\n               writeField(out, \"location\", var.location);\n            }\n            endGroup(out);\n         }\n\n         writeField(out, \"ringItemSize\", shader.ringItemSize);\n         writeField(out, \"hasStreamOut\", shader.hasStreamOut);\n\n         for (auto i = 0u; i < shader.streamOutStride.size(); ++i) {\n            writeField(out, fmt::format(\"streamOutStride[{}]\", i), shader.streamOutStride[i]);\n         }\n\n         startGroup(out, \"SQ_PGM_RESOURCES_GS\");\n         {\n            auto sq_pgm_resources_gs = shader.regs.sq_pgm_resources_gs;\n            writeField(out, \"NUM_GPRS\", sq_pgm_resources_gs.NUM_GPRS());\n            writeField(out, \"STACK_SIZE\", sq_pgm_resources_gs.STACK_SIZE());\n            writeField(out, \"DX10_CLAMP\", sq_pgm_resources_gs.DX10_CLAMP());\n            writeField(out, \"PRIME_CACHE_PGM_EN\", sq_pgm_resources_gs.PRIME_CACHE_PGM_EN());\n            writeField(out, \"PRIME_CACHE_ON_DRAW\", sq_pgm_resources_gs.PRIME_CACHE_ON_DRAW());\n            writeField(out, \"FETCH_CACHE_LINES\", sq_pgm_resources_gs.FETCH_CACHE_LINES());\n            writeField(out, \"UNCACHED_FIRST_INST\", sq_pgm_resources_gs.UNCACHED_FIRST_INST());\n            writeField(out, \"PRIME_CACHE_ENABLE\", sq_pgm_resources_gs.PRIME_CACHE_ENABLE());\n            writeField(out, \"PRIME_CACHE_ON_CONST\", sq_pgm_resources_gs.PRIME_CACHE_ON_CONST());\n         }\n         endGroup(out);\n\n         startGroup(out, \"VGT_GS_OUT_PRIM_TYPE\");\n         {\n            auto vgt_gs_out_prim_type = shader.regs.vgt_gs_out_prim_type;\n            writeField(out, \"PRIM_TYPE\", vgt_gs_out_prim_type.PRIM_TYPE());\n         }\n         endGroup(out);\n\n         startGroup(out, \"VGT_GS_MODE\");\n         {\n            auto vgt_gs_mode = shader.regs.vgt_gs_mode;\n            writeField(out, \"MODE\", vgt_gs_mode.MODE());\n            writeField(out, \"ES_PASSTHRU\", vgt_gs_mode.ES_PASSTHRU());\n            writeField(out, \"CUT_MODE\", vgt_gs_mode.CUT_MODE());\n            writeField(out, \"MODE_HI\", vgt_gs_mode.MODE_HI());\n            writeField(out, \"GS_C_PACK_EN\", vgt_gs_mode.GS_C_PACK_EN());\n            writeField(out, \"COMPUTE_MODE\", vgt_gs_mode.COMPUTE_MODE());\n            writeField(out, \"FAST_COMPUTE_MODE\", vgt_gs_mode.FAST_COMPUTE_MODE());\n            writeField(out, \"ELEMENT_INFO_EN\", vgt_gs_mode.ELEMENT_INFO_EN());\n            writeField(out, \"PARTIAL_THD_AT_EOI\", vgt_gs_mode.PARTIAL_THD_AT_EOI());\n         }\n         endGroup(out);\n\n         startGroup(out, \"PA_CL_VS_OUT_CNTL\");\n         {\n            auto pa_cl_vs_out_cntl = shader.regs.pa_cl_vs_out_cntl;\n            writeField(out, \"CLIP_DIST_ENA_0\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_0());\n            writeField(out, \"CLIP_DIST_ENA_1\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_1());\n            writeField(out, \"CLIP_DIST_ENA_2\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_2());\n            writeField(out, \"CLIP_DIST_ENA_3\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_3());\n            writeField(out, \"CLIP_DIST_ENA_4\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_4());\n            writeField(out, \"CLIP_DIST_ENA_5\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_5());\n            writeField(out, \"CLIP_DIST_ENA_6\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_6());\n            writeField(out, \"CLIP_DIST_ENA_7\", pa_cl_vs_out_cntl.CLIP_DIST_ENA_7());\n            writeField(out, \"CULL_DIST_ENA_0\", pa_cl_vs_out_cntl.CULL_DIST_ENA_0());\n            writeField(out, \"CULL_DIST_ENA_1\", pa_cl_vs_out_cntl.CULL_DIST_ENA_1());\n            writeField(out, \"CULL_DIST_ENA_2\", pa_cl_vs_out_cntl.CULL_DIST_ENA_2());\n            writeField(out, \"CULL_DIST_ENA_3\", pa_cl_vs_out_cntl.CULL_DIST_ENA_3());\n            writeField(out, \"CULL_DIST_ENA_4\", pa_cl_vs_out_cntl.CULL_DIST_ENA_4());\n            writeField(out, \"CULL_DIST_ENA_5\", pa_cl_vs_out_cntl.CULL_DIST_ENA_5());\n            writeField(out, \"CULL_DIST_ENA_6\", pa_cl_vs_out_cntl.CULL_DIST_ENA_6());\n            writeField(out, \"CULL_DIST_ENA_7\", pa_cl_vs_out_cntl.CULL_DIST_ENA_7());\n            writeField(out, \"USE_VTX_POINT_SIZE\", pa_cl_vs_out_cntl.USE_VTX_POINT_SIZE());\n            writeField(out, \"USE_VTX_EDGE_FLAG\", pa_cl_vs_out_cntl.USE_VTX_EDGE_FLAG());\n            writeField(out, \"USE_VTX_RENDER_TARGET_INDX\", pa_cl_vs_out_cntl.USE_VTX_RENDER_TARGET_INDX());\n            writeField(out, \"USE_VTX_VIEWPORT_INDX\", pa_cl_vs_out_cntl.USE_VTX_VIEWPORT_INDX());\n            writeField(out, \"USE_VTX_KILL_FLAG\", pa_cl_vs_out_cntl.USE_VTX_KILL_FLAG());\n            writeField(out, \"VS_OUT_MISC_VEC_ENA\", pa_cl_vs_out_cntl.VS_OUT_MISC_VEC_ENA());\n            writeField(out, \"VS_OUT_CCDIST0_VEC_ENA\", pa_cl_vs_out_cntl.VS_OUT_CCDIST0_VEC_ENA());\n            writeField(out, \"VS_OUT_CCDIST1_VEC_ENA\", pa_cl_vs_out_cntl.VS_OUT_CCDIST1_VEC_ENA());\n            writeField(out, \"VS_OUT_MISC_SIDE_BUS_ENA\", pa_cl_vs_out_cntl.VS_OUT_MISC_SIDE_BUS_ENA());\n            writeField(out, \"USE_VTX_GS_CUT_FLAG\", pa_cl_vs_out_cntl.USE_VTX_GS_CUT_FLAG());\n         }\n         endGroup(out);\n\n         auto num_spi_vs_out_id = shader.regs.num_spi_vs_out_id;\n         writeField(out, \"NUM_SPI_VS_OUT_ID\", num_spi_vs_out_id);\n\n         auto spi_vs_out_id = shader.regs.spi_vs_out_id;\n\n         for (auto i = 0u; i < std::min<size_t>(num_spi_vs_out_id, spi_vs_out_id.size()); ++i) {\n            startGroup(out, fmt::format(\"SPI_VS_OUT_ID[{}]\", i));\n            {\n               writeField(out, \"SEMANTIC_0\", spi_vs_out_id[i].SEMANTIC_0());\n               writeField(out, \"SEMANTIC_1\", spi_vs_out_id[i].SEMANTIC_1());\n               writeField(out, \"SEMANTIC_2\", spi_vs_out_id[i].SEMANTIC_2());\n               writeField(out, \"SEMANTIC_3\", spi_vs_out_id[i].SEMANTIC_3());\n            }\n            endGroup(out);\n         }\n\n         startGroup(out, \"SQ_PGM_RESOURCES_VS\");\n         {\n            auto sq_pgm_resources_vs = shader.regs.sq_pgm_resources_vs;\n            writeField(out, \"NUM_GPRS\", sq_pgm_resources_vs.NUM_GPRS());\n            writeField(out, \"STACK_SIZE\", sq_pgm_resources_vs.STACK_SIZE());\n            writeField(out, \"DX10_CLAMP\", sq_pgm_resources_vs.DX10_CLAMP());\n            writeField(out, \"PRIME_CACHE_PGM_EN\", sq_pgm_resources_vs.PRIME_CACHE_PGM_EN());\n            writeField(out, \"PRIME_CACHE_ON_DRAW\", sq_pgm_resources_vs.PRIME_CACHE_ON_DRAW());\n            writeField(out, \"FETCH_CACHE_LINES\", sq_pgm_resources_vs.FETCH_CACHE_LINES());\n            writeField(out, \"UNCACHED_FIRST_INST\", sq_pgm_resources_vs.UNCACHED_FIRST_INST());\n            writeField(out, \"PRIME_CACHE_ENABLE\", sq_pgm_resources_vs.PRIME_CACHE_ENABLE());\n            writeField(out, \"PRIME_CACHE_ON_CONST\", sq_pgm_resources_vs.PRIME_CACHE_ON_CONST());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SQ_GS_VERT_ITEMSIZE\");\n         {\n            auto sq_gs_vert_itemsize = shader.regs.sq_gs_vert_itemsize;\n            writeField(out, \"ITEMSIZE\", sq_gs_vert_itemsize.ITEMSIZE());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SPI_VS_OUT_CONFIG\");\n         {\n            auto spi_vs_out_config = shader.regs.spi_vs_out_config;\n            writeField(out, \"VS_PER_COMPONENT\", spi_vs_out_config.VS_PER_COMPONENT());\n            writeField(out, \"VS_EXPORT_COUNT\", spi_vs_out_config.VS_EXPORT_COUNT());\n            writeField(out, \"VS_EXPORTS_FOG\", spi_vs_out_config.VS_EXPORTS_FOG());\n            writeField(out, \"VS_OUT_FOG_VEC_ADDR\", spi_vs_out_config.VS_OUT_FOG_VEC_ADDR());\n         }\n         endGroup(out);\n\n         startGroup(out, \"VGT_STRMOUT_BUFFER_EN\");\n         {\n            auto vgt_strmout_buffer_en = shader.regs.vgt_strmout_buffer_en;\n            writeField(out, \"BUFFER_0_EN\", vgt_strmout_buffer_en.BUFFER_0_EN());\n            writeField(out, \"BUFFER_1_EN\", vgt_strmout_buffer_en.BUFFER_1_EN());\n            writeField(out, \"BUFFER_2_EN\", vgt_strmout_buffer_en.BUFFER_2_EN());\n            writeField(out, \"BUFFER_3_EN\", vgt_strmout_buffer_en.BUFFER_3_EN());\n         }\n         endGroup(out);\n      }\n      endGroup(out);\n\n      startGroup(out, \"GeometryShaderProgram\");\n      {\n         writeField(out, \"size\", shader.data.size());\n\n         std::string disassembly;\n         disassembly = latte::disassemble(gsl::make_span(shader.data));\n         fmt::format_to(std::back_inserter(out.writer), \"\\n{}\", disassembly);\n      }\n      endGroup(out);\n\n      startGroup(out, \"GeometryShaderCopyProgram\");\n      {\n         writeField(out, \"size\", shader.vertexShaderData.size());\n\n         std::string disassembly;\n         disassembly = latte::disassemble(gsl::make_span(shader.vertexShaderData));\n         fmt::format_to(std::back_inserter(out.writer), \"\\n{}\", disassembly);\n      }\n      endGroup(out);\n   }\n\n   for (auto &tex : file.textures) {\n\n      startGroup(out, \"TextureHeader\");\n      {\n         writeField(out, \"dim\", cafe::gx2::to_string(tex.surface.dim));\n         writeField(out, \"width\", tex.surface.width);\n         writeField(out, \"height\", tex.surface.height);\n         writeField(out, \"depth\", tex.surface.depth);\n         writeField(out, \"mipLevels\", tex.surface.mipLevels);\n         writeField(out, \"format\", cafe::gx2::to_string(tex.surface.format));\n         writeField(out, \"aa\", cafe::gx2::to_string(tex.surface.aa));\n         writeField(out, \"use\", cafe::gx2::to_string(tex.surface.use));\n         writeField(out, \"imageSize\", tex.surface.image.size());\n         writeField(out, \"mipmapSize\", tex.surface.mipmap.size());\n         writeField(out, \"tileMode\", cafe::gx2::to_string(tex.surface.tileMode));\n         writeField(out, \"swizzle\", tex.surface.swizzle);\n         writeField(out, \"alignment\", tex.surface.alignment);\n         writeField(out, \"pitch\", tex.surface.pitch);\n\n         for (auto i = 0u; i < tex.surface.mipLevelOffset.size(); ++i) {\n            writeField(out, fmt::format(\"mipLevelOffset[{}]\", i), tex.surface.mipLevelOffset[i]);\n         }\n\n         writeField(out, \"viewFirstMip\", tex.viewFirstMip);\n         writeField(out, \"viewNumMips\", tex.viewNumMips);\n         writeField(out, \"viewFirstSlice\", tex.viewFirstSlice);\n         writeField(out, \"viewNumSlices\", tex.viewNumSlices);\n         writeField(out, \"compMap\", tex.compMap);\n\n         startGroup(out, \"SQ_TEX_RESOURCE_WORD0_0\");\n         {\n            auto word0 = tex.regs.word0;\n            writeField(out, \"DIM\", word0.DIM());\n            writeField(out, \"TILE_MODE\", word0.TILE_MODE());\n            writeField(out, \"TILE_TYPE\", word0.TILE_TYPE());\n            writeField(out, \"PITCH\", word0.PITCH());\n            writeField(out, \"TEX_WIDTH\", word0.TEX_WIDTH());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SQ_TEX_RESOURCE_WORD0_1\");\n         {\n            auto word1 = tex.regs.word1;\n            writeField(out, \"TEX_HEIGHT\", word1.TEX_HEIGHT());\n            writeField(out, \"TEX_DEPTH\", word1.TEX_DEPTH());\n            writeField(out, \"DATA_FORMAT\", word1.DATA_FORMAT());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SQ_TEX_RESOURCE_WORD0_4\");\n         {\n            auto word4 = tex.regs.word4;\n            writeField(out, \"FORMAT_COMP_X\", word4.FORMAT_COMP_X());\n            writeField(out, \"FORMAT_COMP_Y\", word4.FORMAT_COMP_Y());\n            writeField(out, \"FORMAT_COMP_Z\", word4.FORMAT_COMP_Z());\n            writeField(out, \"FORMAT_COMP_W\", word4.FORMAT_COMP_W());\n            writeField(out, \"NUM_FORMAT_ALL\", word4.NUM_FORMAT_ALL());\n            writeField(out, \"SRF_MODE_ALL\", word4.SRF_MODE_ALL());\n            writeField(out, \"FORCE_DEGAMMA\", word4.FORCE_DEGAMMA());\n            writeField(out, \"ENDIAN_SWAP\", word4.ENDIAN_SWAP());\n            writeField(out, \"REQUEST_SIZE\", word4.REQUEST_SIZE());\n            writeField(out, \"DST_SEL_X\", word4.DST_SEL_X());\n            writeField(out, \"DST_SEL_Y\", word4.DST_SEL_Y());\n            writeField(out, \"DST_SEL_Z\", word4.DST_SEL_Z());\n            writeField(out, \"DST_SEL_W\", word4.DST_SEL_W());\n            writeField(out, \"BASE_LEVEL\", word4.BASE_LEVEL());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SQ_TEX_RESOURCE_WORD0_5\");\n         {\n            auto word5 = tex.regs.word5;\n            writeField(out, \"LAST_LEVEL\", word5.LAST_LEVEL());\n            writeField(out, \"BASE_ARRAY\", word5.BASE_ARRAY());\n            writeField(out, \"LAST_ARRAY\", word5.LAST_ARRAY());\n            writeField(out, \"YUV_CONV\", word5.YUV_CONV());\n         }\n         endGroup(out);\n\n         startGroup(out, \"SQ_TEX_RESOURCE_WORD0_6\");\n         {\n            auto word6 = tex.regs.word6;\n            writeField(out, \"MPEG_CLAMP\", word6.MPEG_CLAMP());\n            writeField(out, \"MAX_ANISO_RATIO\", word6.MAX_ANISO_RATIO());\n            writeField(out, \"PERF_MODULATION\", word6.PERF_MODULATION());\n            writeField(out, \"INTERLACED\", word6.INTERLACED());\n            writeField(out, \"ADVIS_FAULT_LOD\", word6.ADVIS_FAULT_LOD());\n            writeField(out, \"ADVIS_CLAMP_LOD\", word6.ADVIS_CLAMP_LOD());\n            writeField(out, \"TYPE\", word6.TYPE());\n         }\n         endGroup(out);\n      }\n      endGroup(out);\n\n      if (tex.surface.image.size()) {\n         startGroup(out, \"TextureImage\");\n         writeField(out, \"size\", tex.surface.image.size());\n         endGroup(out);\n      }\n\n      if (tex.surface.mipmap.size()) {\n         startGroup(out, \"TextureMipmap\");\n         writeField(out, \"size\", tex.surface.mipmap.size());\n         endGroup(out);\n      }\n   }\n\n   out.writer.push_back('\\0');\n   std::cout << out.writer.data();\n   return true;\n}\n\nstatic std::string\ngetFilename(const std::string &path)\n{\n   auto startBS = path.find_last_of('\\\\');\n   auto startFS = path.find_last_of('/');\n   auto start = startBS;\n\n   if (startBS == std::string::npos) {\n      start = startFS;\n   }\n\n   if (start == std::string::npos) {\n      start = 0;\n   } else {\n      start = start + 1;\n   }\n\n   auto filename = path.substr(start);\n   return filename;\n}\n\nstatic std::string\ngetFileBasename(const std::string &filename)\n{\n   auto start = filename.find_last_of('.');\n\n   if (start == std::string::npos) {\n      return filename;\n   } else {\n      return filename.substr(0, start);\n   }\n}\n\nstatic bool\nconvertTexture(const std::string &path)\n{\n   OutputState out;\n   gfd::GFDFile file;\n\n   try {\n      if (!gfd::readFile(file, path)) {\n         return false;\n      }\n   } catch (gfd::GFDReadException ex) {\n      std::cerr << fmt::format(\"Error reading gfd: {}\", ex.what()) << std::endl;\n      return false;\n   }\n\n   auto filename = getFilename(path);\n   auto basename = getFileBasename(path);\n   auto index = 0u;\n\n   for (auto &tex : file.textures) {\n      auto format = static_cast<latte::SQ_DATA_FORMAT>(tex.surface.format & 0x3f);\n      auto bpp = latte::getDataFormatBitsPerElement(format);\n\n      // Fill out tiling surface information\n      auto surface = gpu7::tiling::SurfaceDescription { };\n      surface.tileMode = static_cast<gpu7::tiling::TileMode>(tex.surface.tileMode);\n      surface.format = static_cast<gpu7::tiling::DataFormat>(format);\n      surface.bpp = bpp;\n      surface.numSamples = 1u << static_cast<int>(tex.surface.aa);\n      surface.width = tex.surface.width;\n      surface.height = tex.surface.height;\n      surface.numSlices = tex.surface.depth;\n      surface.use  = static_cast<gpu7::tiling::SurfaceUse>(tex.surface.use);\n      surface.dim = static_cast<gpu7::tiling::SurfaceDim>(tex.surface.dim);\n      surface.numFrags = 0;\n      surface.numLevels = tex.surface.mipLevels;\n\n      /* Not sure if needed or not.*/\n      if (format >= latte::SQ_DATA_FORMAT::FMT_BC1 &&\n          format <= latte::SQ_DATA_FORMAT::FMT_BC5) {\n         surface.width = (surface.width + 3) / 4;\n         surface.height = (surface.height + 3) / 4;\n      }\n\n      surface.pipeSwizzle = (tex.surface.swizzle >> 8) & 1;\n      surface.bankSwizzle = (tex.surface.swizzle >> 9) & 3;\n\n      std::vector<uint8_t> untiled;\n      std::vector<uint8_t> imageData;\n      std::vector<uint8_t> mipMapData;\n\n      // Untile image\n      untiled.resize(tex.surface.image.size());\n\n      auto surfaceInfo = gpu7::tiling::computeSurfaceInfo(surface, 0);\n      auto retileInfo = gpu7::tiling::computeRetileInfo(surfaceInfo);\n      gpu7::tiling::cpu::untile(retileInfo, untiled.data(),\n                                tex.surface.image.data(),\n                                0, surface.numSlices);\n\n      // Unpitch image\n      imageData.resize(gpu7::tiling::computeUnpitchedImageSize(surface));\n      gpu7::tiling::unpitchImage(surface, untiled.data(), imageData.data());\n\n      // Untile mipmaps\n      auto mipOffset = 0u;\n      untiled.resize(tex.surface.mipmap.size());\n\n      for (auto i = 1u; i < surface.numLevels; ++i) {\n         auto mipSurfaceInfo = gpu7::tiling::computeSurfaceInfo(surface, i);\n         auto mipRetileInfo = gpu7::tiling::computeRetileInfo(mipSurfaceInfo);\n         mipOffset = align_up(mipOffset, mipSurfaceInfo.baseAlign);\n         gpu7::tiling::cpu::untile(mipRetileInfo, untiled.data() + mipOffset,\n                                   tex.surface.mipmap.data() + mipOffset,\n                                   0, surface.numSlices);\n         mipOffset += mipSurfaceInfo.surfSize;\n      }\n\n      // Unpitch mipmaps\n      mipMapData.resize(gpu7::tiling::computeUnpitchedMipMapSize(surface));\n      gpu7::tiling::unpitchMipMap(surface, untiled.data(), mipMapData.data());\n\n      // Save to DDS file\n      std::string outname;\n      if (file.textures.size() > 1) {\n         outname = fmt::format(\"{}.gtx.{}.dds\", basename, index++);\n      } else {\n         outname = fmt::format(\"{}.gtx.dds\", basename);\n      }\n\n      cafe::gx2::GX2Surface gx2surface;\n      cafe::gx2::internal::gfdToGX2Surface(tex.surface, &gx2surface);\n      gx2surface.tileMode = cafe::gx2::GX2TileMode::LinearSpecial;\n      gx2surface.pitch = gx2surface.width;\n      gx2surface.imageSize = static_cast<uint32_t>(imageData.size());\n      gx2surface.mipLevels = tex.surface.mipLevels;\n      gx2surface.mipmapSize = static_cast<uint32_t>(mipMapData.size());\n      cafe::gx2::debug::saveDDS(outname, &gx2surface, imageData.data(), mipMapData.data());\n   }\n\n   return true;\n}\n\nstatic bool\ndisassembleShaderBinary(const std::string &path)\n{\n   std::vector<uint8_t> binary;\n   std::ifstream file;\n   file.open(path, std::fstream::binary);\n   if (!file.is_open()) {\n      return false;\n   }\n\n   file.seekg(0, std::fstream::end);\n   binary.resize(file.tellg());\n   file.seekg(0, std::fstream::beg);\n   file.read(reinterpret_cast<char *>(binary.data()), binary.size());\n   file.close();\n\n   std::cout << latte::disassemble(gsl::make_span(binary)) << std::endl;\n   return true;\n}\n\nint main(int argc, char **argv)\n{\n   int result = -1;\n   excmd::parser parser;\n   excmd::option_state options;\n\n   // Setup command line options\n   parser.global_options()\n      .add_option(\"h,help\", excmd::description { \"Show the help.\" });\n\n   parser.add_command(\"help\")\n      .add_argument(\"command\", excmd::value<std::string> { });\n\n   parser.add_command(\"info\")\n      .add_argument(\"file in\", excmd::value<std::string> { });\n\n   parser.add_command(\"convert\")\n      .add_argument(\"src\", excmd::value<std::string> { });\n\n   parser.add_command(\"disassemble\")\n      .add_argument(\"shader\", excmd::value<std::string> { });\n\n   // Parse command line\n   try {\n      options = parser.parse(argc, argv);\n   } catch (excmd::exception ex) {\n      std::cout << \"Error parsing command line: \" << ex.what() << std::endl;\n      std::exit(-1);\n   }\n\n   // Print help\n   if (argc == 1 || options.has(\"help\")) {\n      if (options.has(\"command\")) {\n         std::cout << parser.format_help(\"gfdtool\", options.get<std::string>(\"command\")) << std::endl;\n      } else {\n         std::cout << parser.format_help(\"gfdtool\") << std::endl;\n      }\n\n      std::exit(0);\n   }\n\n   if (options.has(\"info\")) {\n      auto in = options.get<std::string>(\"file in\");\n      result = printInfo(in) ? 0 : -1;\n   } else if (options.has(\"convert\")) {\n      auto src = options.get<std::string>(\"src\");\n      result = convertTexture(src) ? 0 : -1;\n   } else if (options.has(\"disassemble\")) {\n      auto src = options.get<std::string>(\"shader\");\n      result = disassembleShaderBinary(src) ? 0 : -1;\n   }\n\n   return result;\n}\n"
  },
  {
    "path": "tools/latte-assembler/CMakeLists.txt",
    "content": "project(latte-assembler)\n\ninclude_directories(\".\")\ninclude_directories(${PROJECT_BINARY_DIR})\n\nfile(GLOB_RECURSE SOURCE_FILES src/*.cpp)\nfile(GLOB_RECURSE HEADER_FILES src/*.h)\n\n# Generates a grammar.h with the contents of grammar.txt in a C++ string\nset(GRAMMAR_FILE ${PROJECT_SOURCE_DIR}/resources/grammar.txt)\nset(GRAMMAR_CMAKE ${PROJECT_BINARY_DIR}/generate_grammar.cmake)\nset(GRAMMAR_HEADER ${PROJECT_BINARY_DIR}/grammar.h)\n\nfile(WRITE ${GRAMMAR_CMAKE} \"FILE(WRITE grammar.h \\\"const char *LatteGrammar = R\\\\\\\"(\\\\n\\\")\\n\")\nfile(APPEND ${GRAMMAR_CMAKE} \"FILE(READ \")\nfile(APPEND ${GRAMMAR_CMAKE} ${GRAMMAR_FILE})\nfile(APPEND ${GRAMMAR_CMAKE} \" CONTENTS)\\n\")\nfile(APPEND ${GRAMMAR_CMAKE} \"FILE(APPEND grammar.h \\${CONTENTS})\\n\")\nfile(APPEND ${GRAMMAR_CMAKE} \"FILE(APPEND grammar.h \\\")\\\\\\\";\\\\n\\\")\\n\")\n\nadd_custom_command(COMMAND ${CMAKE_COMMAND} -P ${GRAMMAR_CMAKE}\n                   DEPENDS ${GRAMMAR_FILE}\n                   OUTPUT ${GRAMMAR_HEADER}\n                   COMMENT \"Generating grammar.h\")\n\n# Build latte-assembler\nadd_executable(latte-assembler ${SOURCE_FILES} ${HEADER_FILES} ${GRAMMAR_HEADER})\n\ntarget_link_libraries(latte-assembler\n    common\n    libgfd\n    excmd\n    peglib\n    SPIRV)\n\nset_target_properties(latte-assembler PROPERTIES FOLDER tools)\nGroupSources(\"Source Files\" src)\nGroupSources(\"Resource Files\" resources)\n\ninstall(TARGETS latte-assembler RUNTIME DESTINATION \"${DECAF_INSTALL_BINDIR}\")\n"
  },
  {
    "path": "tools/latte-assembler/resources/example_shader.psh",
    "content": "; $MODE = \"UniformRegister\"\n; $SQ_PGM_RESOURCES_PS.NUM_GPRS = 1\n; $SPI_PS_IN_CONTROL_0.NUM_INTERP = 1\n; $NUM_SPI_PS_INPUT_CNTL = 1\n; $SPI_PS_INPUT_CNTL[0].SEMANTIC = 0\n; $SPI_PS_INPUT_CNTL[0].DEFAULT_VAL = 1\n\n00 EXP_DONE: PIX0, R0.xyzw\nEND_OF_PROGRAM\n"
  },
  {
    "path": "tools/latte-assembler/resources/example_shader.vsh",
    "content": "; $MODE = \"UniformRegister\"\n; $ATTRIB_VARS[0].name = \"aColour\"\n; $ATTRIB_VARS[0].type = \"Float4\"\n; $ATTRIB_VARS[0].location = 0\n; $ATTRIB_VARS[1].name = \"aPosition\"\n; $ATTRIB_VARS[1].type = \"Float4\"\n; $ATTRIB_VARS[1].location = 1\n; $SQ_PGM_RESOURCES_VS.NUM_GPRS = 3\n; $SQ_PGM_RESOURCES_VS.STACK_SIZE = 1\n; $NUM_SPI_VS_OUT_ID              = 1\n; $SPI_VS_OUT_ID[0].SEMANTIC_0 = 0\n; $SQ_VTX_SEMANTIC_CLEAR.CLEAR = 0xFFFFFFFC\n; $NUM_SQ_VTX_SEMANTIC = 2\n; $SQ_VTX_SEMANTIC[0].SEMANTIC_ID = 0\n; $SQ_VTX_SEMANTIC[1].SEMANTIC_ID = 1\n; $VGT_VERTEX_REUSE_BLOCK_CNTL.VTX_REUSE_DEPTH = 14\n; $VGT_HOS_REUSE_DEPTH.REUSE_DEPTH = 16\n\n00 CALL_FS NO_BARRIER\n01 EXP_DONE: POS0, R2.xyzw\n02 EXP_DONE: PARAM0, R1.xyzw NO_BARRIER\nEND_OF_PROGRAM\n"
  },
  {
    "path": "tools/latte-assembler/src/assembler_alu.cpp",
    "content": "#include \"shader_assembler.h\"\n#include \"assembler_instructions.h\"\n\n#include <fmt/core.h>\n\nstatic std::string\ndecodeOpcodeAlias(const std::string &op)\n{\n   if (op == \"SQRT_e\") {\n      return \"SQRT_IEEE\";\n   } else if (op == \"EXP_e\") {\n      return \"EXP_IEEE\";\n   } else if (op == \"LOG_e\") {\n      return \"LOG_IEEE\";\n   } else if (op == \"RSQ_e\") {\n      return \"RECIPSQRT_IEEE\";\n   } else if (op == \"RCP_e\") {\n      return \"RECIP_IEEE\";\n   } else if (op == \"LOG_sat\") {\n      return \"LOG_CLAMPED\";\n   } else if (op == \"MUL_e\") {\n      return \"MUL_IEEE\";\n   } else if (op == \"DOT4_e\") {\n      return \"DOT4_IEEE\";\n   } else if (op == \"MULADD_e\") {\n      return \"MULADD_IEEE\";\n   } else {\n      return op;\n   }\n}\n\nstatic void\nassembleAluInst(Shader &shader, AluGroup &group, peg::Ast &node, unsigned numSrcs)\n{\n   auto inst = latte::AluInst {};\n   auto srcIndex = 0;\n   auto aluUnit = latte::SQ_CHAN {};\n   std::memset(&inst, 0, sizeof(latte::AluInst));\n\n   inst.op2 = inst.op2\n      .WRITE_MASK(true);\n\n   for (auto &child : node.nodes) {\n      if (child->name == \"AluUnit\") {\n         aluUnit = parseChan(*child);\n\n         if (aluUnit != latte::SQ_CHAN::T) {\n            inst.word1 = inst.word1\n               .DST_CHAN(aluUnit);\n         }\n      } else if (child->name == \"AluOpcode0\" || child->name == \"AluOpcode1\" || child->name == \"AluOpcode2\") {\n         const auto name = decodeOpcodeAlias(child->token);\n         const auto opcode = getAluOp2InstructionByName(name);\n\n         if (opcode == latte::SQ_OP2_INST_INVALID) {\n            throw invalid_alu_op2_inst_exception { *child };\n         }\n\n         inst.word1 = inst.word1\n            .ENCODING(latte::SQ_ALU_ENCODING::OP2);\n         inst.op2 = inst.op2\n            .ALU_INST(opcode);\n      } else if (child->name == \"AluOpcode3\") {\n         const auto name = decodeOpcodeAlias(child->token);\n         const auto opcode = getAluOp3InstructionByName(name);\n\n         if (opcode == latte::SQ_OP3_INST_INVALID) {\n            throw invalid_alu_op3_inst_exception { *child };\n         }\n\n         inst.op3 = inst.op3\n            .ALU_INST(opcode);\n      } else if (child->name == \"AluOutputModifier\") {\n         inst.op2 = inst.op2\n            .OMOD(parseOutputModifier(*child));\n      } else if (child->name == \"AluDst\") {\n         for (auto &dst : child->nodes) {\n            if (dst->name == \"WriteMask\") {\n               if (inst.word1.ENCODING() != latte::SQ_ALU_ENCODING::OP2) {\n                  throw node_parse_exception { *dst, fmt::format(\"Write mask ____ is only valid on an OP2 instruction\") };\n               }\n\n               inst.op2 = inst.op2\n                  .WRITE_MASK(false);\n            } else if (dst->name == \"Gpr\") {\n               inst.word1 = inst.word1\n                  .DST_GPR(parseNumber(*dst));\n               markGprWritten(shader, inst.word1.DST_GPR());\n            } else if (dst->name == \"AluRel\") {\n               inst.word0 = inst.word0\n                  .INDEX_MODE(parseAluDstRelIndexMode(*dst));\n\n               inst.word1 = inst.word1\n                  .DST_REL(latte::SQ_REL::REL);\n            } else if (dst->name == \"OneCompSwizzle\") {\n               inst.word1 = inst.word1\n                  .DST_CHAN(parseChan(*dst));\n            } else {\n               throw unhandled_node_exception { *dst };\n            }\n         }\n      } else if (child->name == \"AluSrc\") {\n         auto negate = false;\n         auto srcAbs = false;\n         auto rel = latte::SQ_REL::ABS;\n         auto chan = inst.word1.DST_CHAN();\n         auto sel = latte::SQ_ALU_SRC::REGISTER_FIRST;\n\n         for (auto src : child->nodes) {\n            if (src->name == \"Negate\") {\n               negate = true;\n            } else if (src->name == \"AluRel\") {\n               rel = latte::SQ_REL::REL;\n               inst.word0 = inst.word0\n                  .INDEX_MODE(parseAluDstRelIndexMode(*src));\n            } else if (src->name == \"OneCompSwizzle\") {\n               chan = parseChan(*src);\n            } else if (src->name == \"AluAbsSrcValue\" || src->name == \"AluSrcValue\") {\n               auto srcType = src->nodes[0];\n\n               if (src->name == \"AluAbsSrcValue\") {\n                  srcAbs = true;\n                  srcType = src->nodes[0]->nodes[0];\n               }\n\n               if (srcType->name == \"Gpr\") {\n                  sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::REGISTER_FIRST + parseNumber(*srcType));\n               } else if (srcType->name == \"ConstantCache0\") {\n                  sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::KCACHE_BANK0_FIRST + parseNumber(*srcType));\n               } else if (srcType->name == \"ConstantCache1\") {\n                  sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::KCACHE_BANK1_FIRST + parseNumber(*srcType));\n               } else if (srcType->name == \"ConstantFile\") {\n                  sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::CONST_FILE_FIRST + parseNumber(*srcType));\n               } else if (srcType->name == \"PreviousScalar\") {\n                  sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::PS);\n               } else if (srcType->name == \"PreviousVector\") {\n                  sel = static_cast<latte::SQ_ALU_SRC>(latte::SQ_ALU_SRC::PV);\n               } else if (srcType->name == \"Literal\") {\n                  auto literal = parseLiteral(*srcType);\n                  sel = latte::SQ_ALU_SRC::LITERAL;\n\n                  if (literal.flags & LiteralValue::ReadFloat) {\n                     if (literal.floatValue == 0.0f) {\n                        if ((literal.flags & LiteralValue::ReadHex) == 0 || literal.hexValue == 0) {\n                           sel = latte::SQ_ALU_SRC::IMM_0;\n                        }\n                     }\n                     /*\n                     AMD ShaderAnalyzer only seems to use IMM_0\n                     else if (literal.floatValue == 1.0f) {\n                        sel = latte::SQ_ALU_SRC::IMM_1;\n                     } else if (literal.floatValue == 0.5f) {\n                        sel = latte::SQ_ALU_SRC::IMM_0_5;\n                     }\n                     */\n                  }\n\n                  if (sel == latte::SQ_ALU_SRC::LITERAL) {\n                     chan = static_cast<latte::SQ_CHAN>(group.literals.size());\n                     group.literals.push_back(literal);\n                  }\n               } else {\n                  throw unhandled_node_exception { *srcType };\n               }\n            } else {\n               throw unhandled_node_exception { *src };\n            }\n         }\n\n         if (srcIndex == 0) {\n            inst.word0 = inst.word0\n               .SRC0_CHAN(chan)\n               .SRC0_NEG(negate)\n               .SRC0_SEL(sel)\n               .SRC0_REL(rel);\n\n            if (srcAbs) {\n               inst.op2 = inst.op2\n                  .SRC0_ABS(true);\n            }\n         } else if (srcIndex == 1) {\n            inst.word0 = inst.word0\n               .SRC1_CHAN(chan)\n               .SRC1_NEG(negate)\n               .SRC1_SEL(sel)\n               .SRC1_REL(rel);\n\n            if (srcAbs) {\n               inst.op2 = inst.op2\n                  .SRC1_ABS(true);\n            }\n         } else if (srcIndex == 2) {\n            inst.op3 = inst.op3\n               .SRC2_CHAN(chan)\n               .SRC2_NEG(negate)\n               .SRC2_SEL(sel)\n               .SRC2_REL(rel);\n         }\n\n         markSrcRead(shader, sel);\n         srcIndex++;\n      } else if (child->name == \"AluProperties\") {\n         for (auto &prop : child->nodes) {\n            if (prop->name == \"BANK_SWIZZLE\") {\n               inst.word1 = inst.word1\n                  .BANK_SWIZZLE(parseAluBankSwizzle(*prop));\n            } else if (prop->name == \"UPDATE_EXEC_MASK\") {\n               inst.op2 = inst.op2\n                  .UPDATE_EXECUTE_MASK(true);\n            } else if (prop->name == \"UPDATE_PRED\") {\n               inst.op2 = inst.op2\n                  .UPDATE_PRED(true);\n            } else if (prop->name == \"PRED_SEL\") {\n               inst.word0 = inst.word0\n                  .PRED_SEL(parsePredSel(*prop));\n            } else if (prop->name == \"CLAMP\") {\n               inst.word1 = inst.word1\n                  .CLAMP(true);\n            } else {\n               throw invalid_alu_property_exception { *prop };\n            }\n         }\n      } else {\n         throw unhandled_node_exception { *child };\n      }\n   }\n\n   group.insts.push_back(inst);\n}\n\nstatic void\nassembleAluGroup(Shader &shader, AluClause &clause, peg::Ast &node)\n{\n   auto group = AluGroup {};\n\n   for (auto &child : node.nodes) {\n      if (child->name == \"InstCount\") {\n         group.clausePC = parseNumber(*child);\n\n         if (group.clausePC != shader.clausePC) {\n            throw incorrect_clause_pc_exception { *child, group.clausePC, shader.clausePC };\n         }\n      } else if (child->name == \"AluScalar0\") {\n         assembleAluInst(shader, group, *child, 0);\n      } else if (child->name == \"AluScalar1\") {\n         assembleAluInst(shader, group, *child, 1);\n      } else if (child->name == \"AluScalar2\") {\n         assembleAluInst(shader, group, *child, 2);\n      } else if (child->name == \"AluScalar3\") {\n         assembleAluInst(shader, group, *child, 3);\n      } else {\n         throw unhandled_node_exception { *child };\n      }\n   }\n\n   if (group.insts.size()) {\n      auto &lastInst = group.insts.back();\n      lastInst.word0 = lastInst.word0\n         .LAST(true);\n   }\n\n   shader.clausePC++;\n   clause.groups.push_back(std::move(group));\n}\n\nvoid\nassembleAluClause(Shader &shader,\n                 peg::Ast &node)\n{\n   auto cfInst = latte::ControlFlowInst { };\n   auto clause = AluClause {};\n\n   cfInst.alu.word1 = cfInst.alu.word1\n      .BARRIER(true);\n\n   for (auto &child : node.nodes) {\n      if (child->name == \"InstCount\") {\n         clause.cfPC = parseNumber(*child);\n\n         if (clause.cfPC != shader.cfInsts.size()) {\n            throw incorrect_cf_pc_exception { *child, clause.cfPC, shader.cfInsts.size() };\n         }\n      } else if (child->name == \"AluClauseInstType\") {\n         auto &name = child->token;\n         auto opcode = getCfAluInstructionByName(name);\n\n         if (opcode == latte::SQ_CF_ALU_INST_INVALID) {\n            throw invalid_cf_alu_inst_exception { *child };\n         }\n\n         cfInst.alu.word1 = cfInst.alu.word1\n            .CF_INST(opcode);\n      } else if (child->name == \"AluClauseProperties\") {\n         for (auto &prop : child->nodes) {\n            if (prop->name == \"ADDR\") {\n               clause.addrNode = prop;\n               cfInst.alu.word0 = cfInst.alu.word0\n                  .ADDR(parseNumber(*prop));\n            } else if (prop->name == \"CNT\") {\n               clause.countNode = prop;\n               cfInst.alu.word1 = cfInst.alu.word1\n                  .COUNT(parseNumber(*prop) - 1);\n            } else if (prop->name == \"KCACHE0\") {\n               assert(prop->nodes.size() == 3);\n               auto bank = parseNumber(*prop->nodes[0]);\n               auto start = parseNumber(*prop->nodes[1]);\n               auto end = parseNumber(*prop->nodes[2]);\n               auto mode = latte::SQ_CF_KCACHE_MODE::NOP;\n\n               if (end - start == 15) {\n                  mode = latte::SQ_CF_KCACHE_MODE::LOCK_1;\n               } else if (end - start == 31) {\n                  mode = latte::SQ_CF_KCACHE_MODE::LOCK_2;\n               }\n\n               cfInst.alu.word0 = cfInst.alu.word0\n                  .KCACHE_BANK0(bank)\n                  .KCACHE_MODE0(mode);\n\n               cfInst.alu.word1 = cfInst.alu.word1\n                  .KCACHE_ADDR0(start / 16);\n            } else if (prop->name == \"KCACHE1\") {\n               assert(prop->nodes.size() == 3);\n               auto bank = parseNumber(*prop->nodes[0]);\n               auto start = parseNumber(*prop->nodes[1]);\n               auto end = parseNumber(*prop->nodes[2]);\n               auto mode = latte::SQ_CF_KCACHE_MODE::NOP;\n\n               if (end - start == 15) {\n                  mode = latte::SQ_CF_KCACHE_MODE::LOCK_1;\n               } else if (end - start == 31) {\n                  mode = latte::SQ_CF_KCACHE_MODE::LOCK_2;\n               }\n\n               cfInst.alu.word0 = cfInst.alu.word0\n                  .KCACHE_BANK1(bank);\n\n               cfInst.alu.word1 = cfInst.alu.word1\n                  .KCACHE_MODE1(mode)\n                  .KCACHE_ADDR1(start / 16);\n            } else if (prop->name == \"NO_BARRIER\") {\n               cfInst.alu.word1 = cfInst.alu.word1\n                  .BARRIER(false);\n            } else if (prop->name == \"WHOLE_QUAD_MODE\") {\n               cfInst.alu.word1 = cfInst.alu.word1\n                  .WHOLE_QUAD_MODE(true);\n            } else if (prop->name == \"USES_WATERFALL\") {\n               cfInst.alu.word1 = cfInst.alu.word1\n                  .ALT_CONST(true);\n            } else {\n               throw invalid_cf_alu_property_exception { *prop };\n            }\n         }\n      } else if (child->name == \"AluGroup\") {\n         assembleAluGroup(shader, clause, *child);\n      } else {\n         throw unhandled_node_exception { *child };\n      }\n   }\n\n   shader.aluClauses.push_back(std::move(clause));\n   shader.cfInsts.push_back(cfInst);\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/assembler_cf.cpp",
    "content": "#include \"shader_assembler.h\"\n#include \"assembler_instructions.h\"\n\n#include <common/align.h>\n#include <common/bit_cast.h>\n#include <fmt/core.h>\n\nstatic const size_t\nAluClauseAlign = 256;\n\nstatic const size_t\nTexClauseAlign = 128;\n\nstatic void\nassembleCfInst(Shader &shader, peg::Ast &node)\n{\n   auto inst = latte::ControlFlowInst { };\n\n   inst.word1 = inst.word1\n      .CF_INST_TYPE(latte::SQ_CF_INST_TYPE_NORMAL)\n      .BARRIER(true);\n\n   for (auto &child : node.nodes) {\n      if (child->name == \"InstCount\") {\n         auto cfPC = parseNumber(*child);\n\n         if (cfPC != shader.cfInsts.size()) {\n            throw incorrect_cf_pc_exception { *child, cfPC, shader.cfInsts.size() };\n         }\n      } else if (child->name == \"CfOpcode\") {\n         auto &name = child->token;\n         auto opcode = getCfInstructionByName(name);\n\n         if (opcode == latte::SQ_CF_INST_INVALID) {\n            throw invalid_cf_inst_exception { *child };\n         }\n\n         inst.word1 = inst.word1.CF_INST(opcode);\n      } else if (child->name == \"CfInstProperties\") {\n         for (auto &prop : child->nodes) {\n            if (prop->name == \"NO_BARRIER\") {\n               inst.word1 = inst.word1.BARRIER(false);\n            } else if (prop->name == \"WHOLE_QUAD_MODE\") {\n               inst.word1 = inst.word1.WHOLE_QUAD_MODE(true);\n            } else if (prop->name == \"VALID_PIX\") {\n               inst.word1 = inst.word1.VALID_PIXEL_MODE(true);\n            } else if (prop->name == \"CF_CONST\") {\n               inst.word1 = inst.word1.CF_CONST(parseNumber(*prop));\n            } else if (prop->name == \"POP_CNT\") {\n               inst.word1 = inst.word1.POP_COUNT(parseNumber(*prop));\n            } else if (prop->name == \"ADDR\" || prop->name == \"PASS_JUMP_ADDR\" || prop->name == \"FAIL_JUMP_ADDR\") {\n               inst.word0 = inst.word0.ADDR(parseNumber(*prop));\n            } else {\n               throw invalid_cf_property_exception { *prop };\n            }\n         }\n      } else {\n         throw unhandled_node_exception { *child };\n      }\n   }\n\n   shader.cfInsts.push_back(inst);\n}\n\nstatic void\nassembleInstruction(Shader &shader, peg::Ast &node)\n{\n   if (node.name == \"CfInst\") {\n      assembleCfInst(shader, node);\n   } else if (node.name == \"CfExpInst\") {\n      assembleExpInst(shader, node);\n   } else if (node.name == \"AluClause\") {\n      assembleAluClause(shader, node);\n   } else if (node.name == \"TexClause\") {\n      assembleTexClause(shader, node);\n   } else {\n      throw unhandled_node_exception { node };\n   }\n}\n\nstatic void\nassembleClauses(Shader &shader)\n{\n   auto aluClauseData = std::vector<uint32_t> {};\n   auto texClauseData = std::vector<uint32_t> {};\n   auto aluClauseBaseAddress = align_up(shader.cfInsts.size(), AluClauseAlign / 8);\n\n   for (auto &clause : shader.aluClauses) {\n      auto &cfInst = shader.cfInsts[clause.cfPC];\n      auto offset = aluClauseData.size();\n\n      for (auto &group : clause.groups) {\n         for (auto inst : group.insts) {\n            aluClauseData.push_back(inst.word0.value);\n            aluClauseData.push_back(inst.word1.value);\n         }\n\n         for (auto literal : group.literals) {\n            if (literal.flags & LiteralValue::ReadHex) {\n               aluClauseData.push_back(literal.hexValue);\n            } else {\n               aluClauseData.push_back(bit_cast<uint32_t>(literal.floatValue));\n            }\n         }\n\n         if (group.literals.size() % 2) {\n            // Must pad to 64 bit\n            aluClauseData.push_back(0);\n         }\n      }\n\n      auto addr = aluClauseBaseAddress + (offset / 2);\n      auto count = (aluClauseData.size() - offset) / 2;\n\n      if (clause.addrNode) {\n         if (cfInst.alu.word0.ADDR() != addr) {\n            throw incorrect_clause_addr_exception { *clause.countNode, cfInst.alu.word0.ADDR(), addr };\n         }\n      } else {\n         cfInst.alu.word0 = cfInst.alu.word0\n            .ADDR(static_cast<uint32_t>(addr));\n      }\n\n      if (clause.countNode) {\n         auto parsedCount = cfInst.alu.word1.COUNT() + 1;\n\n         if (parsedCount != count) {\n            throw incorrect_clause_count_exception { *clause.countNode, parsedCount, count };\n         }\n      } else {\n         cfInst.alu.word1 = cfInst.alu.word1\n            .COUNT(static_cast<uint32_t>(count - 1));\n      }\n   }\n\n   if (aluClauseData.size()) {\n      shader.aluClauseBaseAddress = static_cast<uint32_t>(aluClauseBaseAddress);\n      shader.aluClauseData = std::move(aluClauseData);\n   } else {\n      shader.aluClauseBaseAddress = 0;\n   }\n\n   auto texClauseBaseAddress = align_up(aluClauseBaseAddress + shader.aluClauseData.size() / 2, TexClauseAlign / 8);\n\n   for (auto &clause : shader.texClauses) {\n      auto &cfInst = shader.cfInsts[clause.cfPC];\n      auto offset = texClauseData.size();\n\n      for (auto &inst : clause.insts) {\n         texClauseData.push_back(inst.word0.value);\n         texClauseData.push_back(inst.word1.value);\n         texClauseData.push_back(inst.word2.value);\n         texClauseData.push_back(inst.padding);\n      }\n\n      auto addr = texClauseBaseAddress + (offset / 2);\n      auto count = (texClauseData.size() - offset) / 4;\n\n      if (clause.addrNode) {\n         if (cfInst.word0.ADDR() != addr) {\n            throw incorrect_clause_addr_exception { *clause.countNode, cfInst.word0.ADDR(), addr };\n         }\n      } else {\n         cfInst.word0 = cfInst.word0\n            .ADDR(static_cast<uint32_t>(addr));\n      }\n\n      if (clause.countNode) {\n         auto parsedCount = (cfInst.word1.COUNT() | (cfInst.word1.COUNT_3() << 3)) + 1;\n\n         if (parsedCount != count) {\n            throw incorrect_clause_count_exception { *clause.countNode, parsedCount, count };\n         }\n      } else {\n         cfInst.word1 = cfInst.word1\n            .COUNT(static_cast<uint32_t>((count - 1) & 0b111))\n            .COUNT_3(static_cast<uint32_t>((count - 1) >> 3));\n      }\n   }\n\n   if (texClauseData.size()) {\n      shader.texClauseBaseAddress = static_cast<uint32_t>(texClauseBaseAddress);\n      shader.texClauseData = std::move(texClauseData);\n   } else {\n      shader.texClauseBaseAddress = 0;\n   }\n\n   // TODO: Same for VTX clauses\n}\n\nstatic void\nassembleEndOfProgram(Shader &shader)\n{\n   if (shader.cfInsts.size()) {\n      auto &last = shader.cfInsts.back();\n      if (last.word1.CF_INST_TYPE() == latte::SQ_CF_INST_TYPE_NORMAL\n       || last.word1.CF_INST_TYPE() == latte::SQ_CF_INST_TYPE_EXPORT) {\n         last.word1 = last.word1\n            .END_OF_PROGRAM(true);\n         return;\n      }\n   }\n\n   auto inst = latte::ControlFlowInst { };\n   inst.word1 = inst.word1\n      .CF_INST(latte::SQ_CF_INST_NOP)\n      .CF_INST_TYPE(latte::SQ_CF_INST_TYPE_NORMAL)\n      .END_OF_PROGRAM(true);\n}\n\nvoid\nassembleAST(Shader &shader,\n           std::shared_ptr<peg::Ast> ast)\n{\n   auto foundEOP = false;\n\n   if (ast->name != \"Program\") {\n      throw node_parse_exception { *ast, fmt::format(\"Expected root node to be Program, not {}\", ast->name) };\n   }\n\n   for (auto &node : ast->nodes) {\n      if (node->name == \"Instruction\") {\n         assert(node->nodes.size() == 1);\n         assembleInstruction(shader, *node->nodes[0]);\n      } else if (node->name == \"EndOfProgram\") {\n         assembleEndOfProgram(shader);\n         foundEOP = true;\n      } else if (node->name == \"Comment\") {\n         if (node->token.size()) {\n            shader.comments.push_back(node->token);\n         }\n      } else {\n         throw unhandled_node_exception { *node };\n      }\n   }\n\n   // If the user did not add a END_OF_PROGRAM, we should do it automatically\n   if (!foundEOP) {\n      assembleEndOfProgram(shader);\n   }\n\n   assembleClauses(shader);\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/assembler_common.cpp",
    "content": "#include \"shader_assembler.h\"\n\nunsigned long\nparseNumber(peg::Ast &node)\n{\n   assert(node.is_token);\n   return std::stoul(node.token);\n}\n\nfloat\nparseFloat(peg::Ast &node)\n{\n   return std::stof(node.token);\n}\n\nuint32_t\nparseHexNumber(peg::Ast &node)\n{\n   return static_cast<uint32_t>(std::stoul(node.token, 0, 0));\n}\n\nLiteralValue\nparseLiteral(peg::Ast &node)\n{\n   auto literal = LiteralValue { };\n\n   for (auto child : node.nodes) {\n      if (child->name == \"HexNumber\") {\n         literal.flags |= LiteralValue::ReadHex;\n         literal.hexValue = parseHexNumber(*child);\n      } else if (child->name == \"Float\") {\n         literal.flags |= LiteralValue::ReadFloat;\n         literal.floatValue = parseFloat(*child);\n      }\n   }\n\n   return literal;\n}\n\nvoid\nmarkGprRead(Shader &shader,\n            uint32_t gpr)\n{\n   shader.gprRead[gpr] = true;\n}\n\nvoid\nmarkGprWritten(Shader &shader,\n               uint32_t gpr)\n{\n   shader.gprWritten[gpr] = true;\n}\n\nvoid\nmarkSrcRead(Shader &shader,\n            latte::SQ_ALU_SRC src)\n{\n   if (src >= latte::SQ_ALU_SRC::REGISTER_FIRST && src <= latte::SQ_ALU_SRC::REGISTER_LAST) {\n      markGprRead(shader, src - latte::SQ_ALU_SRC::REGISTER_FIRST);\n   } else if (src >= latte::SQ_ALU_SRC::KCACHE_BANK0_FIRST && src <= latte::SQ_ALU_SRC::KCACHE_BANK0_LAST) {\n      shader.uniformBlocksUsed = true;\n   } else if (src >= latte::SQ_ALU_SRC::KCACHE_BANK1_FIRST && src <= latte::SQ_ALU_SRC::KCACHE_BANK1_LAST) {\n      shader.uniformBlocksUsed = true;\n   } else if (src >= latte::SQ_ALU_SRC::CONST_FILE_FIRST && src <= latte::SQ_ALU_SRC::CONST_FILE_LAST) {\n      shader.uniformRegistersUsed = true;\n   }\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/assembler_exp.cpp",
    "content": "#include \"shader_assembler.h\"\n#include \"assembler_instructions.h\"\n\nvoid\nassembleExpInst(Shader &shader, peg::Ast &node)\n{\n   auto inst = latte::ControlFlowInst { };\n\n   inst.word1 = inst.word1\n      .CF_INST_TYPE(latte::SQ_CF_INST_TYPE_EXPORT)\n      .BARRIER(true);\n\n   for (auto &child : node.nodes) {\n      if (child->name == \"InstCount\") {\n         auto cfPC = parseNumber(*child);\n\n         if (cfPC != shader.cfInsts.size()) {\n            throw incorrect_cf_pc_exception { *child, cfPC, shader.cfInsts.size() };\n         }\n      } else if (child->name == \"ExpOpcode\") {\n         auto &name = child->token;\n         auto opcode = getCfExpInstructionByName(name);\n\n         if (opcode == latte::SQ_CF_EXP_INST_INVALID) {\n            throw invalid_exp_inst_exception { *child };\n         }\n\n         if (opcode != latte::SQ_CF_INST_EXP && opcode != latte::SQ_CF_INST_EXP_DONE) {\n            // TODO: Only EXP and EXP_DONE are supported for now.\n            throw invalid_exp_inst_exception { *child };\n         }\n\n         inst.exp.word1 = inst.exp.word1.CF_INST(opcode);\n      } else if (child->name == \"ExpParamTarget\") {\n         auto index = parseNumber(*child);\n         shader.maxParamExport = std::max(shader.maxParamExport, index);\n\n         inst.exp.word0 = inst.exp.word0\n            .TYPE(latte::SQ_EXPORT_TYPE::PARAM)\n            .ARRAY_BASE(index);\n      } else if (child->name == \"ExpPixTarget\") {\n         auto index = parseNumber(*child);\n         shader.maxPixelExport = std::max(shader.maxPixelExport, index);\n\n         inst.exp.word0 = inst.exp.word0\n            .TYPE(latte::SQ_EXPORT_TYPE::PIXEL)\n            .ARRAY_BASE(index);\n      } else if (child->name == \"ExpPosTarget\") {\n         auto index = parseNumber(*child);\n         shader.maxPosExport = std::max(shader.maxPosExport, index);\n\n         inst.exp.word0 = inst.exp.word0\n            .TYPE(latte::SQ_EXPORT_TYPE::POS)\n            .ARRAY_BASE(index + 60);\n      } else if (child->name == \"ExpSrc\") {\n         for (auto &src : child->nodes) {\n            if (src->name == \"Gpr\") {\n               inst.exp.word0 = inst.exp.word0\n                  .RW_GPR(parseNumber(*src))\n                  .RW_REL(latte::SQ_REL::ABS);\n\n               // Set default GPR swizzle\n               inst.exp.swiz = inst.exp.swiz\n                  .SEL_X(latte::SQ_SEL::SEL_X)\n                  .SEL_Y(latte::SQ_SEL::SEL_Y)\n                  .SEL_Z(latte::SQ_SEL::SEL_Z)\n                  .SEL_W(latte::SQ_SEL::SEL_W);\n\n               markGprRead(shader, inst.exp.word0.RW_GPR());\n            } else if (src->name == \"GprRel\") {\n               inst.exp.word0 = inst.exp.word0\n                  .RW_GPR(parseNumber(*src))\n                  .RW_REL(latte::SQ_REL::REL);\n\n               // Set default GPR swizzle\n               inst.exp.swiz = inst.exp.swiz\n                  .SEL_X(latte::SQ_SEL::SEL_X)\n                  .SEL_Y(latte::SQ_SEL::SEL_Y)\n                  .SEL_Z(latte::SQ_SEL::SEL_Z)\n                  .SEL_W(latte::SQ_SEL::SEL_W);\n\n               markGprRead(shader, inst.exp.word0.RW_GPR());\n            } else if (src->name == \"FourCompSwizzle\") {\n               auto selX = latte::SQ_SEL::SEL_0;\n               auto selY = latte::SQ_SEL::SEL_0;\n               auto selZ = latte::SQ_SEL::SEL_0;\n               auto selW = latte::SQ_SEL::SEL_1;\n\n               parseFourCompSwizzle(*src, selX, selY, selZ, selW);\n               inst.exp.swiz = inst.exp.swiz\n                  .SEL_X(selX)\n                  .SEL_Y(selY)\n                  .SEL_Z(selZ)\n                  .SEL_W(selW);\n            }\n         }\n      } else if (child->name == \"CfInstProperties\") {\n         for (auto &prop : child->nodes) {\n            if (prop->name == \"NO_BARRIER\") {\n               inst.word1 = inst.word1.BARRIER(false);\n            } else if (prop->name == \"WHOLE_QUAD_MODE\") {\n               inst.exp.word1 = inst.exp.word1.WHOLE_QUAD_MODE(true);\n            } else if (prop->name == \"VALID_PIX\") {\n               inst.exp.word1 = inst.exp.word1.VALID_PIXEL_MODE(true);\n            } else if (prop->name == \"ELEM_SIZE\") {\n               inst.exp.word0 = inst.exp.word0.ELEM_SIZE(parseNumber(*prop));\n            } else if (prop->name == \"BURSTCNT\") {\n               inst.exp.word1 = inst.exp.word1.BURST_COUNT(parseNumber(*prop));\n            } else {\n               throw invalid_exp_property_exception { *prop };\n            }\n         }\n      } else {\n         throw unhandled_node_exception { *child };\n      }\n   }\n\n   shader.cfInsts.push_back(inst);\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/assembler_instructions.cpp",
    "content": "#include \"assembler_instructions.h\"\n\nlatte::SQ_CF_INST getCfInstructionByName(const std::string &findName)\n{\n#define CF_INST(name, value) if (findName == #name) { return latte::SQ_CF_INST_##name; }\n#include <libgpu/latte/latte_instructions_def.inl>\n#undef CF_INST\n   return latte::SQ_CF_INST_INVALID;\n}\n\nlatte::SQ_CF_EXP_INST getCfExpInstructionByName(const std::string &findName)\n{\n#define EXP_INST(name, value) if (findName == #name) { return latte::SQ_CF_INST_##name; }\n#include <libgpu/latte/latte_instructions_def.inl>\n#undef EXP_INST\n   return latte::SQ_CF_EXP_INST_INVALID;\n}\n\nlatte::SQ_CF_ALU_INST getCfAluInstructionByName(const std::string &findName)\n{\n#define ALU_INST(name, value) if (findName == #name) { return latte::SQ_CF_INST_##name; }\n#include <libgpu/latte/latte_instructions_def.inl>\n#undef ALU_INST\n   return latte::SQ_CF_ALU_INST_INVALID;\n}\n\nlatte::SQ_OP2_INST getAluOp2InstructionByName(const std::string &findName)\n{\n#define ALU_OP2(name, value, srcs, flags) if (findName == #name) { return latte::SQ_OP2_INST_##name; }\n#include <libgpu/latte/latte_instructions_def.inl>\n#undef ALU_OP2\n   return latte::SQ_OP2_INST_INVALID;\n}\n\nlatte::SQ_OP3_INST getAluOp3InstructionByName(const std::string &findName)\n{\n#define ALU_OP3(name, value, srcs, flags) if (findName == #name) { return latte::SQ_OP3_INST_##name; }\n#include <libgpu/latte/latte_instructions_def.inl>\n#undef ALU_OP3\n   return latte::SQ_OP3_INST_INVALID;\n}\n\nlatte::SQ_TEX_INST getTexInstructionByName(const std::string &findName)\n{\n#define TEX_INST(name, value) if (findName == #name) { return latte::SQ_TEX_INST_##name; }\n#include <libgpu/latte/latte_instructions_def.inl>\n#undef TEX_INST\n   return latte::SQ_TEX_INST_INVALID;\n}\n\nlatte::SQ_VTX_INST getVtxInstructionByName(const std::string &findName)\n{\n#define VTX_INST(name, value) if (findName == #name) { return latte::SQ_VTX_INST_##name; }\n#include <libgpu/latte/latte_instructions_def.inl>\n#undef VTX_INST\n   return latte::SQ_VTX_INST_INVALID;\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/assembler_instructions.h",
    "content": "#pragma once\n#include <libgpu/latte/latte_instructions.h>\n#include <string>\n\nlatte::SQ_CF_INST getCfInstructionByName(const std::string &name);\nlatte::SQ_CF_EXP_INST getCfExpInstructionByName(const std::string &name);\nlatte::SQ_CF_ALU_INST getCfAluInstructionByName(const std::string &name);\nlatte::SQ_OP2_INST getAluOp2InstructionByName(const std::string &name);\nlatte::SQ_OP3_INST getAluOp3InstructionByName(const std::string &name);\nlatte::SQ_TEX_INST getTexInstructionByName(const std::string &name);\nlatte::SQ_VTX_INST getVtxInstructionByName(const std::string &name);\n"
  },
  {
    "path": "tools/latte-assembler/src/assembler_latte.cpp",
    "content": "#include \"shader_assembler.h\"\n#include <fmt/core.h>\n\nlatte::SQ_ALU_VEC_BANK_SWIZZLE\nparseAluBankSwizzle(peg::Ast &node)\n{\n   if (node.token == \"SCL_210\") {\n      return static_cast<latte::SQ_ALU_VEC_BANK_SWIZZLE>(latte::SQ_ALU_SCL_BANK_SWIZZLE::SCL_210);\n   } else if (node.token == \"SCL_122\") {\n      return static_cast<latte::SQ_ALU_VEC_BANK_SWIZZLE>(latte::SQ_ALU_SCL_BANK_SWIZZLE::SCL_122);\n   } else if (node.token == \"SCL_212\") {\n      return static_cast<latte::SQ_ALU_VEC_BANK_SWIZZLE>(latte::SQ_ALU_SCL_BANK_SWIZZLE::SCL_212);\n   } else if (node.token == \"SCL_221\") {\n      return static_cast<latte::SQ_ALU_VEC_BANK_SWIZZLE>(latte::SQ_ALU_SCL_BANK_SWIZZLE::SCL_221);\n   } else if (node.token == \"VEC_012\") {\n      return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_012;\n   } else if (node.token == \"VEC_021\") {\n      return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_021;\n   } else if (node.token == \"VEC_120\") {\n      return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_120;\n   } else if (node.token == \"VEC_102\") {\n      return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_102;\n   } else if (node.token == \"VEC_201\") {\n      return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_201;\n   } else if (node.token == \"VEC_210\") {\n      return latte::SQ_ALU_VEC_BANK_SWIZZLE::VEC_210;\n   } else {\n      throw node_parse_exception { node, fmt::format(\"Invalid SQ_ALU_VEC_BANK_SWIZZLE {}\", node.token) };\n   }\n}\n\nlatte::SQ_INDEX_MODE\nparseAluDstRelIndexMode(peg::Ast &node)\n{\n   if (node.token == \"[AR.x]\") {\n      return latte::SQ_INDEX_MODE::AR_X;\n   } else if (node.token == \"[AR.y]\") {\n      return latte::SQ_INDEX_MODE::AR_Y;\n   } else if (node.token == \"[AR.z]\") {\n      return latte::SQ_INDEX_MODE::AR_Z;\n   } else if (node.token == \"[AR.w]\") {\n      return latte::SQ_INDEX_MODE::AR_W;\n   } else if (node.token == \"[AL]\") {\n      return latte::SQ_INDEX_MODE::LOOP;\n   } else {\n      throw node_parse_exception { node, fmt::format(\"Invalid SQ_INDEX_MODE {}\", node.token) };\n   }\n}\n\nlatte::SQ_CF_COND\nparseCfCond(peg::Ast &node)\n{\n   if (node.token == \"ACTIVE\") {\n      return latte::SQ_CF_COND::ACTIVE;\n   } else if (node.token == \"ALWAYS_FALSE\") {\n      return latte::SQ_CF_COND::ALWAYS_FALSE;\n   } else if (node.token == \"CF_BOOL\") {\n      return latte::SQ_CF_COND::CF_BOOL;\n   } else if (node.token == \"CF_NOT_BOOL\") {\n      return latte::SQ_CF_COND::CF_NOT_BOOL;\n   } else {\n      throw node_parse_exception { node, fmt::format(\"Invalid SQ_CF_COND {}\", node.token) };\n   }\n}\n\nlatte::SQ_CHAN\nparseChan(peg::Ast &node)\n{\n   switch (node.token[0]) {\n   case 'x':\n   case 'X':\n      return latte::SQ_CHAN::X;\n   case 'y':\n   case 'Y':\n      return latte::SQ_CHAN::Y;\n   case 'z':\n   case 'Z':\n      return latte::SQ_CHAN::Z;\n   case 'w':\n   case 'W':\n      return latte::SQ_CHAN::W;\n   case 't':\n   case 'T':\n      return latte::SQ_CHAN::T;\n   default:\n      throw node_parse_exception { node, fmt::format(\"Invalid SQ_CHAN {}\",  node.token[0]) };\n   }\n}\n\nsize_t\nparseFourCompSwizzle(peg::Ast &node,\n                     latte::SQ_SEL &selX,\n                     latte::SQ_SEL &selY,\n                     latte::SQ_SEL &selZ,\n                     latte::SQ_SEL &selW)\n{\n   assert(node.is_token);\n   size_t numSel = 0;\n\n   if (node.token.size() > 0) {\n      selX = parseSel(node, 0);\n      numSel++;\n   }\n\n   if (node.token.size() > 1) {\n      selY = parseSel(node, 1);\n      numSel++;\n   }\n\n   if (node.token.size() > 2) {\n      selZ = parseSel(node, 2);\n      numSel++;\n   }\n\n   if (node.token.size() > 3) {\n      selW = parseSel(node, 3);\n      numSel++;\n   }\n\n   return numSel;\n}\n\nlatte::SQ_ALU_OMOD\nparseOutputModifier(peg::Ast &node)\n{\n   if (node.token == \"/2\") {\n      return latte::SQ_ALU_OMOD::D2;\n   } else if (node.token == \"*2\") {\n      return latte::SQ_ALU_OMOD::M2;\n   } else if (node.token == \"*4\") {\n      return latte::SQ_ALU_OMOD::M4;\n   } else {\n      throw node_parse_exception { node, fmt::format(\"Invalid SQ_ALU_OMOD {}\", node.token) };\n   }\n}\n\nlatte::SQ_PRED_SEL\nparsePredSel(peg::Ast &node)\n{\n   if (node.token == \"PRED_SEL_OFF\") {\n      return latte::SQ_PRED_SEL::OFF;\n   } else if (node.token == \"PRED_SEL_ZERO\") {\n      return latte::SQ_PRED_SEL::ZERO;\n   } else if (node.token == \"PRED_SEL_ONE\") {\n      return latte::SQ_PRED_SEL::ONE;\n   } else {\n      throw node_parse_exception { node, fmt::format(\"Invalid SQ_PRED_SEL {}\", node.token) };\n   }\n}\n\nlatte::SQ_SEL\nparseSel(peg::Ast &node,\n         unsigned index)\n{\n   switch (node.token[index]) {\n   case 'x':\n   case 'X':\n      return latte::SQ_SEL::SEL_X;\n   case 'y':\n   case 'Y':\n      return latte::SQ_SEL::SEL_Y;\n   case 'z':\n   case 'Z':\n      return latte::SQ_SEL::SEL_Z;\n   case 'w':\n   case 'W':\n      return latte::SQ_SEL::SEL_W;\n   case '0':\n      return latte::SQ_SEL::SEL_0;\n   case '1':\n      return latte::SQ_SEL::SEL_1;\n   case '_':\n      return latte::SQ_SEL::SEL_MASK;\n   default:\n      throw node_parse_exception { node, fmt::format(\"Invalid SQ_SEL {}\", node.token[index]) };\n   }\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/assembler_parse.cpp",
    "content": "#include \"shader_assembler.h\"\n#include \"grammar.h\"\n\nbool\nassembleShaderCode(Shader &shader,\n                   std::string_view code)\n{\n   peg::parser parser;\n   parser.log = [&](size_t ln, size_t col, const std::string &msg) {\n      std::cout << \"Error parsing grammar:\" << ln << \":\" << col << \": \" << msg << std::endl;\n   };\n\n   if (!parser.load_grammar(LatteGrammar)) {\n      return false;\n   }\n\n   parser.log = [&](size_t ln, size_t col, const std::string &msg) {\n      std::cout << shader.path << \":\" << ln << \":\" << col << \": \" << msg << std::endl;\n   };\n\n   parser.enable_ast();\n\n   std::shared_ptr<peg::Ast> ast;\n   if (!parser.parse_n(code.data(), code.size(), ast)) {\n      return false;\n   }\n\n   ast = peg::AstOptimizer(false).optimize(ast);\n   assembleAST(shader, ast);\n   return true;\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/assembler_tex.cpp",
    "content": "#include \"shader_assembler.h\"\n#include \"assembler_instructions.h\"\n\nstatic void\nassembleTexInst(Shader &shader,\n               TexClause &clause,\n               peg::Ast &node)\n{\n\n   auto inst = latte::TextureFetchInst { };\n   std::memset(&inst, 0, sizeof(latte::TextureFetchInst));\n\n   inst.word0 = inst.word0\n      .SRC_REL(latte::SQ_REL::ABS);\n\n   inst.word1 = inst.word1\n      .DST_REL(latte::SQ_REL::ABS)\n      .DST_SEL_X(latte::SQ_SEL::SEL_X)\n      .DST_SEL_Y(latte::SQ_SEL::SEL_Y)\n      .DST_SEL_Z(latte::SQ_SEL::SEL_Z)\n      .DST_SEL_W(latte::SQ_SEL::SEL_W);\n\n   inst.word2 = inst.word2\n      .SRC_SEL_X(latte::SQ_SEL::SEL_X)\n      .SRC_SEL_Y(latte::SQ_SEL::SEL_Y)\n      .SRC_SEL_Z(latte::SQ_SEL::SEL_Z)\n      .SRC_SEL_W(latte::SQ_SEL::SEL_W);\n\n   inst.word1 = inst.word1\n      .COORD_TYPE_X(latte::SQ_TEX_COORD_TYPE::NORMALIZED)\n      .COORD_TYPE_Y(latte::SQ_TEX_COORD_TYPE::NORMALIZED)\n      .COORD_TYPE_Z(latte::SQ_TEX_COORD_TYPE::NORMALIZED)\n      .COORD_TYPE_W(latte::SQ_TEX_COORD_TYPE::NORMALIZED);\n\n   for (auto &child : node.nodes) {\n      if (child->name == \"InstCount\") {\n         auto clausePC = parseNumber(*child);\n\n         if (clausePC != shader.clausePC) {\n            throw incorrect_clause_pc_exception { *child, clausePC, shader.clausePC };\n         }\n\n         shader.clausePC++;\n      } else if (child->name == \"TexOpcode\") {\n         auto &name = child->token;\n         auto opcode = getTexInstructionByName(name);\n\n         if (opcode == latte::SQ_TEX_INST_INVALID) {\n            throw invalid_tex_inst_exception { *child };\n         }\n\n         inst.word0 = inst.word0\n            .TEX_INST(opcode);\n      } else if (child->name == \"TexDst\") {\n         for (auto &dst : child->nodes) {\n            if (dst->name == \"WriteMask\") {\n               inst.word1 = inst.word1\n                  .DST_SEL_X(latte::SQ_SEL::SEL_MASK)\n                  .DST_SEL_Y(latte::SQ_SEL::SEL_MASK)\n                  .DST_SEL_Z(latte::SQ_SEL::SEL_MASK)\n                  .DST_SEL_W(latte::SQ_SEL::SEL_MASK);\n            } else if (dst->name == \"Gpr\") {\n               inst.word1 = inst.word1\n                  .DST_GPR(parseNumber(*dst));\n               markGprWritten(shader, inst.word1.DST_GPR());\n            } else if (dst->name == \"TexRel\") {\n               inst.word1 = inst.word1\n                  .DST_REL(latte::SQ_REL::REL);\n            } else if (dst->name == \"FourCompSwizzle\") {\n               auto selX = latte::SQ_SEL::SEL_X;\n               auto selY = latte::SQ_SEL::SEL_Y;\n               auto selZ = latte::SQ_SEL::SEL_Z;\n               auto selW = latte::SQ_SEL::SEL_W;\n\n               parseFourCompSwizzle(*dst, selX, selY, selZ, selW);\n               inst.word1 = inst.word1\n                  .DST_SEL_X(selX)\n                  .DST_SEL_Y(selY)\n                  .DST_SEL_Z(selZ)\n                  .DST_SEL_W(selW);\n            } else {\n               throw unhandled_node_exception { *dst };\n            }\n         }\n      } else if (child->name == \"TexSrc\") {\n         for (auto &src : child->nodes) {\n            if (src->name == \"Gpr\") {\n               inst.word0 = inst.word0\n                  .SRC_GPR(parseNumber(*src));\n               markGprRead(shader, inst.word0.SRC_GPR());\n            } else if (src->name == \"TexRel\") {\n               inst.word0 = inst.word0\n                  .SRC_REL(latte::SQ_REL::REL);\n            } else if (src->name == \"FourCompSwizzle\") {\n               auto selX = latte::SQ_SEL::SEL_X;\n               auto selY = latte::SQ_SEL::SEL_Y;\n               auto selZ = latte::SQ_SEL::SEL_Z;\n               auto selW = latte::SQ_SEL::SEL_W;\n\n               parseFourCompSwizzle(*src, selX, selY, selZ, selW);\n               inst.word2 = inst.word2\n                  .SRC_SEL_X(selX)\n                  .SRC_SEL_Y(selY)\n                  .SRC_SEL_Z(selZ)\n                  .SRC_SEL_W(selW);\n            } else {\n               throw unhandled_node_exception { *src };\n            }\n         }\n      } else if (child->name == \"TexResourceId\") {\n         inst.word0 = inst.word0\n            .RESOURCE_ID(parseNumber(*child));\n      } else if (child->name == \"TexSamplerId\") {\n         inst.word2 = inst.word2\n            .SAMPLER_ID(parseNumber(*child));\n      } else if (child->name == \"TexProperties\") {\n         for (auto &prop : child->nodes) {\n            if (prop->name == \"ALT_CONST\") {\n               inst.word0 = inst.word0\n                  .ALT_CONST(true);\n            } else if (prop->name == \"BC_FRAC_MODE\") {\n               inst.word0 = inst.word0\n                  .BC_FRAC_MODE(true);\n            } else if (prop->name == \"DENORM\") {\n               if (prop->token.find_first_of('x') != std::string::npos) {\n                  inst.word1 = inst.word1\n                     .COORD_TYPE_X(latte::SQ_TEX_COORD_TYPE::UNNORMALIZED);\n               }\n\n               if (prop->token.find_first_of('y') != std::string::npos) {\n                  inst.word1 = inst.word1\n                     .COORD_TYPE_Y(latte::SQ_TEX_COORD_TYPE::UNNORMALIZED);\n               }\n\n               if (prop->token.find_first_of('z') != std::string::npos) {\n                  inst.word1 = inst.word1\n                     .COORD_TYPE_Z(latte::SQ_TEX_COORD_TYPE::UNNORMALIZED);\n               }\n\n               if (prop->token.find_first_of('w') != std::string::npos) {\n                  inst.word1 = inst.word1\n                     .COORD_TYPE_W(latte::SQ_TEX_COORD_TYPE::UNNORMALIZED);\n               }\n            } else if (prop->name == \"NORM\") {\n               if (prop->token.find_first_of('x') != std::string::npos) {\n                  inst.word1 = inst.word1\n                     .COORD_TYPE_X(latte::SQ_TEX_COORD_TYPE::NORMALIZED);\n               }\n\n               if (prop->token.find_first_of('y') != std::string::npos) {\n                  inst.word1 = inst.word1\n                     .COORD_TYPE_Y(latte::SQ_TEX_COORD_TYPE::NORMALIZED);\n               }\n\n               if (prop->token.find_first_of('z') != std::string::npos) {\n                  inst.word1 = inst.word1\n                     .COORD_TYPE_Z(latte::SQ_TEX_COORD_TYPE::NORMALIZED);\n               }\n\n               if (prop->token.find_first_of('w') != std::string::npos) {\n                  inst.word1 = inst.word1\n                     .COORD_TYPE_W(latte::SQ_TEX_COORD_TYPE::NORMALIZED);\n               }\n            } else if (prop->name == \"LOD\") {\n               inst.word1 = inst.word1\n                  .LOD_BIAS(sfixed_1_3_3_t { parseFloat(*prop) });\n            } else if (prop->name == \"WHOLE_QUAD_MODE\") {\n               inst.word0 = inst.word0\n                  .FETCH_WHOLE_QUAD(true);\n            } else if (prop->name == \"XOFFSET\") {\n               inst.word2 = inst.word2\n                  .OFFSET_X(sfixed_1_3_1_t { parseFloat(*prop) });\n            } else if (prop->name == \"YOFFSET\") {\n               inst.word2 = inst.word2\n                  .OFFSET_Y(sfixed_1_3_1_t { parseFloat(*prop) });\n            } else if (prop->name == \"ZOFFSET\") {\n               inst.word2 = inst.word2\n                  .OFFSET_Z(sfixed_1_3_1_t { parseFloat(*prop) });\n            } else {\n               throw invalid_tex_property_exception { *prop };\n            }\n         }\n      } else {\n         throw unhandled_node_exception { *child };\n      }\n   }\n\n   clause.insts.push_back(inst);\n}\n\nvoid\nassembleTexClause(Shader &shader,\n                 peg::Ast &node)\n{\n   auto cfInst = latte::ControlFlowInst { };\n   auto clause = TexClause {};\n   clause.clausePC = shader.clausePC;\n\n   cfInst.word1 = cfInst.word1\n      .BARRIER(true);\n\n   for (auto &child : node.nodes) {\n      if (child->name == \"InstCount\") {\n         clause.cfPC = parseNumber(*child);\n\n         if (clause.cfPC != shader.cfInsts.size()) {\n            throw incorrect_cf_pc_exception { *child, clause.cfPC, shader.cfInsts.size() };\n         }\n      } else if (child->name == \"TexClauseInstType\") {\n         auto &name = child->token;\n         auto opcode = getCfInstructionByName(name);\n\n         if (opcode == latte::SQ_CF_INST_INVALID) {\n            throw invalid_cf_tex_inst_exception { *child };\n         }\n\n         cfInst.word1 = cfInst.word1\n            .CF_INST_TYPE(latte::SQ_CF_INST_TYPE_NORMAL)\n            .CF_INST(opcode);\n      } else if (child->name == \"TexClauseProperties\") {\n         for (auto &prop : child->nodes) {\n            if (prop->name == \"ADDR\") {\n               clause.addrNode = prop;\n               cfInst.word0 = cfInst.word0\n                  .ADDR(parseNumber(*prop));\n            } else if (prop->name == \"CNT\") {\n               auto count = parseNumber(*prop) - 1;\n               clause.countNode = prop;\n               cfInst.word1 = cfInst.word1\n                  .COUNT_3(count >> 3)\n                  .COUNT(count & 0b111);\n            } else if (prop->name == \"CND\") {\n               cfInst.word1 = cfInst.word1\n                  .COND(parseCfCond(*prop));\n            } else if (prop->name == \"CF_CONST\") {\n               cfInst.word1 = cfInst.word1\n                  .CF_CONST(parseNumber(*prop));\n            } else if (prop->name == \"NO_BARRIER\") {\n               cfInst.word1 = cfInst.word1\n                  .BARRIER(false);\n            } else if (prop->name == \"WHOLE_QUAD_MODE\") {\n               cfInst.word1 = cfInst.word1\n                  .WHOLE_QUAD_MODE(true);\n            } else if (prop->name == \"VALID_PIX\") {\n               cfInst.word1 = cfInst.word1\n                  .VALID_PIXEL_MODE(true);\n            } else {\n               throw invalid_cf_tex_property_exception { *prop };\n            }\n         }\n      } else if (child->name == \"TexInst\") {\n         assembleTexInst(shader, clause, *child);\n      } else {\n         throw unhandled_node_exception { *child };\n      }\n   }\n\n   shader.texClauses.push_back(std::move(clause));\n   shader.cfInsts.push_back(cfInst);\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/gfd.cpp",
    "content": "#include \"gfd_comment_parser.h\"\n#include <libgpu/latte/latte_constants.h>\n\n#include <cstring>\n#include <fmt/core.h>\n#include <libgfd/gfd.h>\n#include <regex>\n#include <vector>\n\nusing namespace cafe::gx2;\n\n/*\nMatches:\n; $Something = true\n; $attribVars[1].type = \"Float4\"\n; $VGT_HOS_REUSE_DEPTH.REUSE_DEPTH = 16\n; $SQ_VTX_SEMANTIC_CLEAR.CLEAR = 0xFFFFFFFC\n*/\nstatic std::regex\nsCommentKeyValueRegex\n{\n   \";[[:space:]]*\\\\$([_[:alnum:]]+)(?:\\\\[([[:digit:]]+)\\\\])?(?:\\\\.([_[:alnum:]]+))?[[:space:]]*=[[:space:]]*(\\\"[^\\\"]+\\\"|[0-9]+|0x[0-9a-fA-F]+|true|false|TRUE|FALSE)\"\n};\n\nstatic std::regex\nsCommentKeyValueStartRegex\n{\n   \";[[:space:]]*\\\\$\"\n};\n\nbool\nparseComment(const std::string &comment,\n             CommentKeyValue &out)\n{\n   std::smatch match;\n   if (!std::regex_match(comment, match, sCommentKeyValueRegex)) {\n      if (std::regex_match(comment, match, sCommentKeyValueStartRegex)) {\n         throw gfd_header_parse_exception {\n            fmt::format(\"Syntax error in comment {}\", comment)\n         };\n      }\n\n      return false;\n   }\n\n   out.obj = match[1];\n   out.index = match[2];\n   out.member = match[3];\n   out.value = match[4];\n\n   if (out.value.size() >= 2 && out.value[0] == '\"') {\n      // Erase quotes from string value\n      out.value.erase(out.value.begin());\n      out.value.erase(out.value.end() - 1);\n   }\n\n   return true;\n}\n\nvoid\nensureArrayOfObjects(const CommentKeyValue &kv)\n{\n   if (!kv.isArrayOfObjects()) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"{} is an array of objects\", kv.obj)\n      };\n   }\n}\n\nvoid\nensureArrayOfValues(const CommentKeyValue &kv)\n{\n   if (!kv.isArrayOfValues()) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"{} is an array of values\", kv.obj)\n      };\n   }\n}\n\nvoid\nensureObject(const CommentKeyValue &kv)\n{\n   if (!kv.isObject()) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"{} is an object\", kv.obj)\n      };\n   }\n}\n\nvoid\nensureValue(const CommentKeyValue &kv)\n{\n   if (!kv.isValue()) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"{} is a value\", kv.obj)\n      };\n   }\n}\n\nGX2ShaderVarType\nparseShaderVarType(const std::string &v)\n{\n   auto value = v;\n   std::transform(value.begin(), value.end(),\n                  value.begin(),\n                  [](char x) { return static_cast<char>(::tolower(x)); });\n\n   if (value == \"void\") {\n      return GX2ShaderVarType::Void;\n   } else if (value == \"bool\") {\n      return GX2ShaderVarType::Bool;\n   } else if (value == \"int\") {\n      return GX2ShaderVarType::Int;\n   } else if (value == \"uint\") {\n      return GX2ShaderVarType::Uint;\n   } else if (value == \"float\") {\n      return GX2ShaderVarType::Float;\n   } else if (value == \"double\") {\n      return GX2ShaderVarType::Double;\n   } else if (value == \"dvec2\") {\n      return GX2ShaderVarType::Double2;\n   } else if (value == \"dvec3\") {\n      return GX2ShaderVarType::Double3;\n   } else if (value == \"dvec4\") {\n      return GX2ShaderVarType::Double4;\n   } else if (value == \"vec2\") {\n      return GX2ShaderVarType::Float2;\n   } else if (value == \"vec3\") {\n      return GX2ShaderVarType::Float3;\n   } else if (value == \"vec4\") {\n      return GX2ShaderVarType::Float4;\n   } else if (value == \"bvec2\") {\n      return GX2ShaderVarType::Bool2;\n   } else if (value == \"bvec3\") {\n      return GX2ShaderVarType::Bool3;\n   } else if (value == \"bvec4\") {\n      return GX2ShaderVarType::Bool4;\n   } else if (value == \"ivec2\") {\n      return GX2ShaderVarType::Int2;\n   } else if (value == \"ivec3\") {\n      return GX2ShaderVarType::Int3;\n   } else if (value == \"ivec4\") {\n      return GX2ShaderVarType::Int4;\n   } else if (value == \"uvec2\") {\n      return GX2ShaderVarType::Uint2;\n   } else if (value == \"uvec3\") {\n      return GX2ShaderVarType::Uint3;\n   } else if (value == \"uvec4\") {\n      return GX2ShaderVarType::Uint4;\n   } else if (value == \"mat2x2\" || value == \"mat2\") {\n      return GX2ShaderVarType::Float2x2;\n   } else if (value == \"mat2x3\") {\n      return GX2ShaderVarType::Float2x3;\n   } else if (value == \"mat2x4\") {\n      return GX2ShaderVarType::Float2x4;\n   } else if (value == \"mat3x2\") {\n      return GX2ShaderVarType::Float3x2;\n   } else if (value == \"mat3x3\" || value == \"mat3\") {\n      return GX2ShaderVarType::Float3x3;\n   } else if (value == \"mat3x4\") {\n      return GX2ShaderVarType::Float3x4;\n   } else if (value == \"mat4x2\") {\n      return GX2ShaderVarType::Float4x2;\n   } else if (value == \"mat4x3\") {\n      return GX2ShaderVarType::Float4x3;\n   } else if (value == \"mat4x4\" || value == \"mat4\") {\n      return GX2ShaderVarType::Float4x4;\n   } else if (value == \"dmat2x2\" || value == \"dmat2\") {\n      return GX2ShaderVarType::Double2x2;\n   } else if (value == \"dmat2x3\") {\n      return GX2ShaderVarType::Double2x3;\n   } else if (value == \"dmat2x4\") {\n      return GX2ShaderVarType::Double2x4;\n   } else if (value == \"dmat3x2\") {\n      return GX2ShaderVarType::Double3x2;\n   } else if (value == \"dmat3x3\" || value == \"dmat3\") {\n      return GX2ShaderVarType::Double3x3;\n   } else if (value == \"dmat3x4\") {\n      return GX2ShaderVarType::Double3x4;\n   } else if (value == \"dmat4x2\") {\n      return GX2ShaderVarType::Double4x2;\n   } else if (value == \"dmat4x3\") {\n      return GX2ShaderVarType::Double4x3;\n   } else if (value == \"dmat4x4\" || value == \"dmat4\") {\n      return GX2ShaderVarType::Double4x4;\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Invalid GX2ShaderVarType {}\", value)\n      };\n   }\n}\n\nGX2SamplerVarType\nparseSamplerVarType(const std::string &v)\n{\n   auto value = v;\n   std::transform(value.begin(), value.end(), value.begin(), ::toupper);\n\n   if (value == \"SAMPLER1D\") {\n      return GX2SamplerVarType::Sampler1D;\n   } else if (value == \"SAMPLER2D\") {\n      return GX2SamplerVarType::Sampler2D;\n   } else if (value == \"SAMPLER3D\") {\n      return GX2SamplerVarType::Sampler3D;\n   } else if (value == \"SAMPLERCUBE\") {\n      return GX2SamplerVarType::SamplerCube;\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Invalid GX2SamplerVarType {}\", value)\n      };\n   }\n}\n\nGX2ShaderMode\nparseShaderMode(const std::string &v)\n{\n   auto value = v;\n   std::transform(value.begin(), value.end(), value.begin(), ::toupper);\n\n   if (value == \"UNIFORMREGISTER\") {\n      return GX2ShaderMode::UniformRegister;\n   } else if (value == \"UNIFORMBLOCK\") {\n      return GX2ShaderMode::UniformBlock;\n   } else if (value == \"GEOMETRYSHADER\") {\n      return GX2ShaderMode::GeometryShader;\n   } else if (value == \"COMPUTERSHADER\") {\n      return GX2ShaderMode::ComputeShader;\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Invalid GX2ShaderMode {}\", value)\n      };\n   }\n}\n\nvoid\nparseUniformBlocks(std::vector<gfd::GFDUniformBlock> &UniformBlocks,\n                   uint32_t index,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (index >= latte::MaxUniformBlocks) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"UNIFORM_BLOCKS[{}] invalid index, max: {}\",\n                     index, latte::MaxUniformBlocks)\n      };\n   }\n\n   if (index >= UniformBlocks.size()) {\n      UniformBlocks.resize(index + 1);\n      UniformBlocks[index].offset = index + 1;\n      UniformBlocks[index].size = 16;\n   }\n\n   if (member == \"NAME\") {\n      UniformBlocks[index].name = value;\n   } else if (member == \"OFFSET\") {\n      UniformBlocks[index].offset = parseValueNumber(value);\n   } else if (member == \"SIZE\") {\n      UniformBlocks[index].size = parseValueNumber(value);\n      if (UniformBlocks[index].size >= latte::MaxUniformBlockSize) {\n         throw gfd_header_parse_exception {\n            fmt::format(\"UNIFORM_BLOCKS[{}] invalid index, max: {}\",\n                        index, latte::MaxUniformBlocks)\n         };\n      }\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"UNIFORM_BLOCKS[{}] does not have member {}\",\n                     index, member)\n      };\n   }\n}\n\nvoid\nparseUniformVars(std::vector<gfd::GFDUniformVar> &uniformVars,\n                 uint32_t index,\n                 const std::string &member,\n                 const std::string &value)\n{\n   if (index >= latte::MaxUniformRegisters) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"UNIFORM_VARS[{}] invalid index, max: {}\",\n                     index, latte::MaxUniformRegisters)\n      };\n   }\n\n   if (index >= uniformVars.size()) {\n      uniformVars.resize(index + 1);\n      uniformVars[index].type = GX2ShaderVarType::Float4;\n      uniformVars[index].count = 1;\n      uniformVars[index].block = -1;\n   }\n\n   if (member == \"NAME\") {\n      uniformVars[index].name = value;\n   } else if (member == \"BLOCK\") {\n      uniformVars[index].block = parseValueNumber(value);\n   } else if (member == \"COUNT\") {\n      uniformVars[index].count = parseValueNumber(value);\n   } else if (member == \"OFFSET\") {\n      uniformVars[index].offset = parseValueNumber(value);\n   } else if (member == \"TYPE\") {\n      uniformVars[index].type = parseShaderVarType(value);\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"UNIFORM_VARS[{}] does not have member {}\", index, member)\n      };\n   }\n}\n\nvoid\nparseInitialValues(std::vector<gfd::GFDUniformInitialValue> &initialValues,\n                   uint32_t index,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (index >= latte::MaxUniformRegisters) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"INITIAL_VALUES[{}] invalid index, max: {}\",\n                     index, latte::MaxUniformRegisters)\n      };\n   }\n\n   if (index >= initialValues.size()) {\n      initialValues.resize(index + 1);\n   }\n\n   if (member == \"OFFSET\") {\n      initialValues[index].offset = parseValueNumber(value);\n   } else if (member == \"VALUE[0]\") {\n      initialValues[index].value[0] = parseValueFloat(value);\n   } else if (member == \"VALUE[1]\") {\n      initialValues[index].value[1] = parseValueFloat(value);\n   } else if (member == \"VALUE[2]\") {\n      initialValues[index].value[2] = parseValueFloat(value);\n   } else if (member == \"VALUE[3]\") {\n      initialValues[index].value[3] = parseValueFloat(value);\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"INITIAL_VALUES[{}] does not have member {}\",\n                     index, member)\n      };\n   }\n}\n\nvoid\nparseLoopVars(std::vector<gfd::GFDLoopVar> &loopVars,\n              uint32_t index,\n              const std::string &member,\n              const std::string &value)\n{\n   if (index >= loopVars.size()) {\n      loopVars.resize(index + 1);\n      loopVars[index].offset = index;\n   }\n\n   if (member == \"OFFSET\") {\n      loopVars[index].offset = parseValueNumber(value);\n   } else if (member == \"VALUE\") {\n      loopVars[index].value = parseValueNumber(value);\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"LOOP_VARS[{}] does not have member {}\", index, member)\n      };\n   }\n}\n\nvoid\nparseSamplerVars(std::vector<gfd::GFDSamplerVar> &samplerVars,\n                 uint32_t index,\n                 const std::string &member,\n                 const std::string &value)\n{\n   if (index >= latte::MaxSamplers) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SAMPLER_VARS[{}] invalid index, max: {}\",\n                     index, latte::MaxSamplers)\n      };\n   }\n\n   if (index >= samplerVars.size()) {\n      samplerVars.resize(index + 1);\n      samplerVars[index].type = GX2SamplerVarType::Sampler2D;\n      samplerVars[index].location = index;\n   }\n\n   if (member == \"NAME\") {\n      samplerVars[index].name = value;\n   } else if (member == \"LOCATION\") {\n      samplerVars[index].location = parseValueNumber(value);\n   } else if (member == \"TYPE\") {\n      samplerVars[index].type = parseSamplerVarType(value);\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SAMPLER_VARS[{}] does not have member {}\",\n                     index, member)\n      };\n   }\n}\n\nbool\nparseValueBool(const std::string &v)\n{\n   auto value = v;\n   std::transform(value.begin(), value.end(), value.begin(), ::toupper);\n\n   if (value == \"TRUE\") {\n      return true;\n   } else if (value == \"FALSE\") {\n      return false;\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Expected boolean value, found {}\", value)\n      };\n   }\n}\n\nuint32_t\nparseValueNumber(const std::string &v)\n{\n   return static_cast<uint32_t>(std::stoul(v, 0, 0));\n}\n\nfloat\nparseValueFloat(const std::string &v)\n{\n   return static_cast<float>(std::stof(v));\n}\n\nstatic std::vector<uint8_t>\ngetShaderBinary(Shader &shader)\n{\n   std::vector<uint8_t> binary;\n   auto cfStart = size_t { 0 };\n   auto cfSize = shader.cfInsts.size() * sizeof(shader.cfInsts[0]);\n   auto cfEnd = cfStart + cfSize;\n\n   auto aluStart = shader.aluClauseBaseAddress * 8;\n   auto aluSize = shader.aluClauseData.size() * sizeof(shader.aluClauseData[0]);\n   auto aluEnd = aluStart + aluSize;\n\n   auto texStart = shader.texClauseBaseAddress * 8;\n   auto texSize = shader.texClauseData.size() * sizeof(shader.texClauseData[0]);\n   auto texEnd = texStart + texSize;\n\n   binary.resize(std::max({ cfEnd, aluEnd, texEnd }), 0);\n\n   std::memcpy(binary.data() + cfStart, shader.cfInsts.data(), cfSize);\n   std::memcpy(binary.data() + aluStart, shader.aluClauseData.data(), aluSize);\n   std::memcpy(binary.data() + texStart, shader.texClauseData.data(), texSize);\n   return std::move(binary);\n}\n\nstatic uint32_t\ncountNumGpr(Shader &shader)\n{\n   auto highestRead = uint32_t { 0 };\n   auto highestWritten = uint32_t { 0 };\n\n   for (auto i = 0u; i < shader.gprRead.size(); ++i) {\n      if (!shader.gprRead[i]) {\n         continue;\n      }\n\n      if (i >= (latte::SQ_ALU_SRC::REGISTER_TEMP_FIRST - latte::SQ_ALU_SRC::REGISTER_FIRST)) {\n         // Ignore temporary registers\n         continue;\n      }\n\n      highestRead = std::max<uint32_t>(highestRead, i);\n   }\n\n   for (auto i = 0u; i < shader.gprWritten.size(); ++i) {\n      if (!shader.gprWritten[i]) {\n         continue;\n      }\n\n      if (i >= (latte::SQ_ALU_SRC::REGISTER_TEMP_FIRST - latte::SQ_ALU_SRC::REGISTER_FIRST)) {\n         // Ignore temporary registers\n         continue;\n      }\n\n      highestWritten = std::max<uint32_t>(highestWritten, i);\n   }\n\n   return std::max(highestRead, highestWritten) + 1;\n}\n\nbool\ngfdAddVertexShader(gfd::GFDFile &file,\n                   Shader &shader)\n{\n   auto out = gfd::GFDVertexShader {};\n   auto numGpr = countNumGpr(shader);\n\n   // Initialise some default values\n   out.ringItemSize = 0;\n   out.hasStreamOut = false;\n   out.streamOutStride.fill(0);\n   out.gx2rData.elemCount = 0;\n   out.gx2rData.elemSize = 0;\n   out.gx2rData.flags = static_cast<GX2RResourceFlags>(0);\n\n   if (shader.uniformBlocksUsed) {\n      out.mode = GX2ShaderMode::UniformBlock;\n   } else {\n      out.mode = GX2ShaderMode::UniformRegister;\n   }\n\n   std::memset(&out.regs, 0, sizeof(out.regs));\n   out.regs.spi_vs_out_id.fill(latte::SPI_VS_OUT_ID_N::get(0xFFFFFFFF));\n\n   out.regs.sq_pgm_resources_vs = out.regs.sq_pgm_resources_vs\n      .NUM_GPRS(numGpr)\n      .STACK_SIZE(1);\n\n   out.regs.vgt_hos_reuse_depth = out.regs.vgt_hos_reuse_depth\n      .REUSE_DEPTH(16);\n\n   out.regs.vgt_vertex_reuse_block_cntl = out.regs.vgt_vertex_reuse_block_cntl\n      .VTX_REUSE_DEPTH(14);\n\n   // Create binary\n   out.data = getShaderBinary(shader);\n\n   // Parse shader comments\n   parseShaderComments(out, shader.comments);\n\n   // Set out pa_cl_vs_out_cntl properly\n   if (out.regs.pa_cl_vs_out_cntl.USE_VTX_POINT_SIZE() ||\n       out.regs.pa_cl_vs_out_cntl.USE_VTX_EDGE_FLAG() ||\n       out.regs.pa_cl_vs_out_cntl.USE_VTX_RENDER_TARGET_INDX() ||\n       out.regs.pa_cl_vs_out_cntl.USE_VTX_VIEWPORT_INDX() ||\n       out.regs.pa_cl_vs_out_cntl.USE_VTX_KILL_FLAG() ||\n       out.regs.pa_cl_vs_out_cntl.USE_VTX_GS_CUT_FLAG()) {\n      out.regs.pa_cl_vs_out_cntl = out.regs.pa_cl_vs_out_cntl.\n         VS_OUT_MISC_VEC_ENA(true).\n         VS_OUT_MISC_SIDE_BUS_ENA(true);\n   }\n\n   if (out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_0() ||\n       out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_1() ||\n       out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_2() ||\n       out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_3() ||\n       out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_4() ||\n       out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_5() ||\n       out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_6() ||\n       out.regs.pa_cl_vs_out_cntl.CLIP_DIST_ENA_7()) {\n      out.regs.pa_cl_vs_out_cntl = out.regs.pa_cl_vs_out_cntl.\n         VS_OUT_CCDIST0_VEC_ENA(true);\n   }\n\n   if (out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_0() ||\n       out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_1() ||\n       out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_2() ||\n       out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_3() ||\n       out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_4() ||\n       out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_5() ||\n       out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_6() ||\n       out.regs.pa_cl_vs_out_cntl.CULL_DIST_ENA_7()) {\n      out.regs.pa_cl_vs_out_cntl = out.regs.pa_cl_vs_out_cntl.\n         VS_OUT_CCDIST1_VEC_ENA(true);\n   }\n\n   // NUM_GPRS should be the number of GPRs used in the shader\n   if (out.regs.sq_pgm_resources_vs.NUM_GPRS() != numGpr) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Invalid SQ_PGM_RESOURCES_VS.NUM_GPRS {}, expected {}\",\n                     out.regs.sq_pgm_resources_vs.NUM_GPRS(), numGpr)\n      };\n   }\n\n   // NUM_SQ_VTX_SEMANTIC should reflect the size of ATTRIB_VARS array\n   if (out.regs.num_sq_vtx_semantic == 0) {\n      out.regs.num_sq_vtx_semantic = static_cast<uint32_t>(out.attribVars.size());\n\n      for (auto i = 0u; i < out.regs.num_sq_vtx_semantic; ++i) {\n         out.regs.sq_vtx_semantic[i] = out.regs.sq_vtx_semantic[i]\n            .SEMANTIC_ID(out.attribVars[i].location);\n      }\n   } else if (out.regs.num_sq_vtx_semantic != out.attribVars.size()) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Invalid NUM_SQ_VTX_SEMANTIC {}, expected {}\",\n                     out.regs.num_sq_vtx_semantic, out.attribVars.size())\n      };\n   }\n\n   for (auto i = out.regs.num_sq_vtx_semantic; i < out.regs.sq_vtx_semantic.size(); ++i) {\n      out.regs.sq_vtx_semantic[i] = out.regs.sq_vtx_semantic[i]\n         .SEMANTIC_ID(0xFF);\n   }\n\n   // SQ_VTX_SEMANTIC_CLEAR.CLEAR should reflect the value of NUM_SQ_VTX_SEMANTIC\n   auto semanticClear = static_cast<uint32_t>(~((1 << out.regs.num_sq_vtx_semantic) - 1));\n   if (out.regs.sq_vtx_semantic_clear.CLEAR() == 0) {\n      out.regs.sq_vtx_semantic_clear = out.regs.sq_vtx_semantic_clear\n         .CLEAR(semanticClear);\n   } else if (out.regs.sq_vtx_semantic_clear.CLEAR() != semanticClear) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Invalid SQ_VTX_SEMANTIC_CLEAR {:#x}, expected {:#x}\",\n                     out.regs.sq_vtx_semantic_clear.CLEAR(), semanticClear)\n      };\n   }\n\n   file.vertexShaders.push_back(out);\n   return true;\n}\n\nbool\ngfdAddPixelShader(gfd::GFDFile &file,\n                  Shader &shader)\n{\n   auto out = gfd::GFDPixelShader {};\n   auto numGpr = countNumGpr(shader);\n\n   // Initialise some default values\n   out.gx2rData.elemCount = 0;\n   out.gx2rData.elemSize = 0;\n   out.gx2rData.flags = static_cast<GX2RResourceFlags>(0);\n\n   if (shader.uniformBlocksUsed) {\n      out.mode = GX2ShaderMode::UniformBlock;\n   } else {\n      out.mode = GX2ShaderMode::UniformRegister;\n   }\n\n   std::memset(&out.regs, 0, sizeof(out.regs));\n   out.regs.cb_shader_mask = out.regs.cb_shader_mask\n      .OUTPUT0_ENABLE(0b1111);\n\n   out.regs.cb_shader_control = out.regs.cb_shader_control\n      .RT0_ENABLE(true);\n\n   out.regs.db_shader_control = out.regs.db_shader_control\n      .Z_ORDER(latte::DB_Z_ORDER::EARLY_Z_THEN_LATE_Z);\n\n   out.regs.spi_ps_in_control_0 = out.regs.spi_ps_in_control_0\n      .BARYC_SAMPLE_CNTL(latte::SPI_BARYC_CNTL::CENTERS_ONLY)\n      .PERSP_GRADIENT_ENA(true);\n\n   out.regs.sq_pgm_exports_ps = out.regs.sq_pgm_exports_ps\n      .EXPORT_MODE(2);\n\n   out.regs.sq_pgm_resources_ps = out.regs.sq_pgm_resources_ps\n      .NUM_GPRS(numGpr)\n      .STACK_SIZE(static_cast<uint32_t>(out.loopVars.size() * 2));\n\n   // Create binary\n   out.data = getShaderBinary(shader);\n\n   // Parse shader comments\n   parseShaderComments(out, shader.comments);\n\n   // NUM_GPRS should be the number of GPRs used in the shader\n   if (out.regs.sq_pgm_resources_ps.NUM_GPRS() != numGpr) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Invalid SQ_PGM_RESOURCES_PS.NUM_GPRS {}, expected {}\",\n                     out.regs.sq_pgm_resources_ps.NUM_GPRS(), numGpr)\n      };\n   }\n\n   if (out.regs.spi_ps_in_control_0.NUM_INTERP() == 0) {\n      out.regs.spi_ps_in_control_0 = out.regs.spi_ps_in_control_0\n         .NUM_INTERP(out.regs.num_spi_ps_input_cntl);\n   } else if (out.regs.spi_ps_in_control_0.NUM_INTERP() != out.regs.num_spi_ps_input_cntl) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Expected SPI_PS_IN_CONTROL_0.NUM_INTERP {} to equal NUM_SPI_PS_INPUT_CNTL {}\",\n                     out.regs.spi_ps_in_control_0.NUM_INTERP(),\n                     out.regs.num_spi_ps_input_cntl)\n      };\n   }\n\n   file.pixelShaders.push_back(out);\n   return true;\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/gfd_comment_parser.h",
    "content": "#pragma once\n#include \"shader.h\"\n#include <cstdint>\n#include <libgfd/gfd.h>\n#include <vector>\n#include <string>\n\nclass gfd_header_parse_exception : public std::runtime_error\n{\npublic:\n   gfd_header_parse_exception(const std::string &m) :\n      std::runtime_error(m)\n   {\n   }\n\nprivate:\n   std::string mMessage;\n};\n\nstruct CommentKeyValue\n{\n   bool isValue() const\n   {\n      return member.empty() && index.empty();\n   }\n\n   bool isObject() const\n   {\n      return !member.empty() && index.empty();\n   }\n\n   bool isArrayOfValues() const\n   {\n      return !index.empty() && member.empty();\n   }\n\n   bool isArrayOfObjects() const\n   {\n      return !member.empty() && !index.empty();\n   }\n\n   std::string obj;\n   std::string index;\n   std::string member;\n   std::string value;\n};\n\nvoid\nensureArrayOfObjects(const CommentKeyValue &kv);\n\nvoid\nensureArrayOfValues(const CommentKeyValue &kv);\n\nvoid\nensureObject(const CommentKeyValue &kv);\n\nvoid\nensureValue(const CommentKeyValue &kv);\n\nbool\nparseComment(const std::string &comment,\n             CommentKeyValue &out);\n\nbool\nparseValueBool(const std::string &value);\n\nuint32_t\nparseValueNumber(const std::string &value);\n\nfloat\nparseValueFloat(const std::string &v);\n\nbool\ngfdAddVertexShader(gfd::GFDFile &file,\n                   Shader &shader);\n\nbool\ngfdAddPixelShader(gfd::GFDFile &file,\n                  Shader &shader);\n\ncafe::gx2::GX2ShaderVarType\nparseShaderVarType(const std::string &v);\n\ncafe::gx2::GX2SamplerVarType\nparseSamplerVarType(const std::string &v);\n\ncafe::gx2::GX2ShaderMode\nparseShaderMode(const std::string &v);\n\nvoid\nparseUniformBlocks(std::vector<gfd::GFDUniformBlock> &UniformBlocks,\n                   uint32_t index,\n                   const std::string &member,\n                   const std::string &value);\n\nvoid\nparseUniformVars(std::vector<gfd::GFDUniformVar> &uniformVars,\n                 uint32_t index,\n                 const std::string &member,\n                 const std::string &value);\nvoid\nparseInitialValues(std::vector<gfd::GFDUniformInitialValue> &initialValues,\n                   uint32_t index,\n                   const std::string &member,\n                   const std::string &value);\nvoid\nparseLoopVars(std::vector<gfd::GFDLoopVar> &loopVars,\n              uint32_t index,\n              const std::string &member,\n              const std::string &value);\n\nvoid\nparseSamplerVars(std::vector<gfd::GFDSamplerVar> &samplerVars,\n                 uint32_t index,\n                 const std::string &member,\n                 const std::string &value);\n\nbool\nparseShaderComments(gfd::GFDVertexShader &shader,\n                    std::vector<std::string> &comments);\n\nbool\nparseShaderComments(gfd::GFDPixelShader &shader,\n                    std::vector<std::string> &comments);\n"
  },
  {
    "path": "tools/latte-assembler/src/gfd_psh_comment_parser.cpp",
    "content": "#include \"gfd_comment_parser.h\"\n#include <fmt/core.h>\n\nstatic void\nparseRegisterValue(latte::SQ_PGM_RESOURCES_PS &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"NUM_GPRS\") {\n      reg = reg\n         .NUM_GPRS(parseValueNumber(value));\n   } else if (member == \"STACK_SIZE\") {\n      reg = reg\n         .STACK_SIZE(parseValueNumber(value));\n   } else if (member == \"DX10_CLAMP\") {\n      reg = reg\n         .DX10_CLAMP(parseValueBool(value));\n   } else if (member == \"PRIME_CACHE_PGM_EN\") {\n      reg = reg\n         .PRIME_CACHE_PGM_EN(parseValueBool(value));\n   } else if (member == \"PRIME_CACHE_ON_DRAW\") {\n      reg = reg\n         .PRIME_CACHE_ON_DRAW(parseValueBool(value));\n   } else if (member == \"FETCH_CACHE_LINES\") {\n      reg = reg\n         .FETCH_CACHE_LINES(parseValueNumber(value));\n   } else if (member == \"UNCACHED_FIRST_INST\") {\n      reg = reg\n         .UNCACHED_FIRST_INST(parseValueBool(value));\n   } else if (member == \"PRIME_CACHE_ENABLE\") {\n      reg = reg\n         .PRIME_CACHE_ENABLE(parseValueBool(value));\n   } else if (member == \"PRIME_CACHE_ON_CONST\") {\n      reg = reg\n         .PRIME_CACHE_ON_CONST(parseValueBool(value));\n   } else if (member == \"CLAMP_CONSTS\") {\n      reg = reg\n         .CLAMP_CONSTS(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SQ_PGM_RESOURCES_PS does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::SQ_PGM_EXPORTS_PS &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"EXPORT_MODE\") {\n      reg = reg\n         .EXPORT_MODE(parseValueNumber(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SQ_PGM_EXPORTS_PS does not have member {}\", member)\n      };\n   }\n}\n\nstatic latte::SPI_BARYC_CNTL\nparseSpiBarycCntl(const std::string &v)\n{\n   auto value = v;\n   std::transform(value.begin(), value.end(), value.begin(), ::toupper);\n\n   if (value == \"CENTROIDS_ONLY\") {\n      return latte::SPI_BARYC_CNTL::CENTROIDS_ONLY;\n   } else if (value == \"CENTERS_ONLY\") {\n      return latte::SPI_BARYC_CNTL::CENTERS_ONLY;\n   } else if (value == \"CENTROIDS_AND_CENTERS\") {\n      return latte::SPI_BARYC_CNTL::CENTROIDS_AND_CENTERS;\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Invalid SPI_BARYC_CNTL {}\", value)\n      };\n   }\n}\n\nstatic latte::DB_Z_ORDER\nparseDbZOrder(const std::string &v)\n{\n   auto value = v;\n   std::transform(value.begin(), value.end(), value.begin(), ::toupper);\n\n   if (value == \"LATE_Z\") {\n      return latte::DB_Z_ORDER::LATE_Z;\n   } else if (value == \"EARLY_Z_THEN_LATE_Z\") {\n      return latte::DB_Z_ORDER::EARLY_Z_THEN_LATE_Z;\n   } else if (value == \"RE_Z\") {\n      return latte::DB_Z_ORDER::RE_Z;\n   } else if (value == \"EARLY_Z_THEN_RE_Z\") {\n      return latte::DB_Z_ORDER::EARLY_Z_THEN_RE_Z;\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"Invalid DB_Z_ORDER {}\", value)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::SPI_PS_IN_CONTROL_0 &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"NUM_INTERP\") {\n      reg = reg\n         .NUM_INTERP(parseValueNumber(value));\n   } else if (member == \"POSITION_ENA\") {\n      reg = reg\n         .POSITION_ENA(parseValueBool(value));\n   } else if (member == \"POSITION_CENTROID\") {\n      reg = reg\n         .POSITION_CENTROID(parseValueBool(value));\n   } else if (member == \"POSITION_ADDR\") {\n      reg = reg\n         .POSITION_ADDR(parseValueNumber(value));\n   } else if (member == \"PARAM_GEN\") {\n      reg = reg\n         .PARAM_GEN(parseValueNumber(value));\n   } else if (member == \"PARAM_GEN_ADDR\") {\n      reg = reg\n         .PARAM_GEN_ADDR(parseValueNumber(value));\n   } else if (member == \"BARYC_SAMPLE_CNTL\") {\n      reg = reg\n         .BARYC_SAMPLE_CNTL(parseSpiBarycCntl(value));\n   } else if (member == \"PERSP_GRADIENT_ENA\") {\n      reg = reg\n         .PERSP_GRADIENT_ENA(parseValueBool(value));\n   } else if (member == \"LINEAR_GRADIENT_ENA\") {\n      reg = reg\n         .LINEAR_GRADIENT_ENA(parseValueBool(value));\n   } else if (member == \"POSITION_SAMPLE\") {\n      reg = reg\n         .POSITION_SAMPLE(parseValueBool(value));\n   } else if (member == \"BARYC_AT_SAMPLE_ENA\") {\n      reg = reg\n         .BARYC_AT_SAMPLE_ENA(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SPI_PS_IN_CONTROL_0 does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::SPI_PS_IN_CONTROL_1 &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"GEN_INDEX_PIX\") {\n      reg = reg\n         .GEN_INDEX_PIX(parseValueBool(value));\n   } else if (member == \"GEN_INDEX_PIX_ADDR\") {\n      reg = reg\n         .GEN_INDEX_PIX_ADDR(parseValueNumber(value));\n   } else if (member == \"FRONT_FACE_ENA\") {\n      reg = reg\n         .FRONT_FACE_ENA(parseValueBool(value));\n   } else if (member == \"FRONT_FACE_CHAN\") {\n      reg = reg\n         .FRONT_FACE_CHAN(parseValueNumber(value));\n   } else if (member == \"FRONT_FACE_ALL_BITS\") {\n      reg = reg\n         .FRONT_FACE_ALL_BITS(parseValueBool(value));\n   } else if (member == \"FRONT_FACE_ADDR\") {\n      reg = reg\n         .FRONT_FACE_ADDR(parseValueNumber(value));\n   } else if (member == \"FOG_ADDR\") {\n      reg = reg\n         .FOG_ADDR(parseValueNumber(value));\n   } else if (member == \"FIXED_PT_POSITION_ENA\") {\n      reg = reg\n         .FIXED_PT_POSITION_ENA(parseValueBool(value));\n   } else if (member == \"FIXED_PT_POSITION_ADDR\") {\n      reg = reg\n         .FIXED_PT_POSITION_ADDR(parseValueNumber(value));\n   } else if (member == \"POSITION_ULC\") {\n      reg = reg\n         .POSITION_ULC(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SPI_PS_IN_CONTROL_1 does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(std::array<latte::SPI_PS_INPUT_CNTL_N, 32> &spi_ps_input_cntls,\n                   uint32_t index,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (index >= spi_ps_input_cntls.size()) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SPI_PS_INPUT_CNTL[{}] invalid index, max: {}\",\n                     index, spi_ps_input_cntls.size())\n      };\n   }\n\n   if (member == \"SEMANTIC\") {\n      spi_ps_input_cntls[index] = spi_ps_input_cntls[index]\n         .SEMANTIC(static_cast<uint8_t>(parseValueNumber(value)));\n   } else if (member == \"DEFAULT_VAL\") {\n      spi_ps_input_cntls[index] = spi_ps_input_cntls[index]\n         .DEFAULT_VAL(parseValueNumber(value));\n   } else if (member == \"FLAT_SHADE\") {\n      spi_ps_input_cntls[index] = spi_ps_input_cntls[index]\n         .FLAT_SHADE(parseValueBool(value));\n   } else if (member == \"SEL_CENTROID\") {\n      spi_ps_input_cntls[index] = spi_ps_input_cntls[index]\n         .SEL_CENTROID(parseValueBool(value));\n   } else if (member == \"SEL_LINEAR\") {\n      spi_ps_input_cntls[index] = spi_ps_input_cntls[index]\n         .SEL_LINEAR(parseValueBool(value));\n   } else if (member == \"CYL_WRAP\") {\n      spi_ps_input_cntls[index] = spi_ps_input_cntls[index]\n         .CYL_WRAP(parseValueNumber(value));\n   } else if (member == \"PT_SPRITE_TEX\") {\n      spi_ps_input_cntls[index] = spi_ps_input_cntls[index]\n         .PT_SPRITE_TEX(parseValueBool(value));\n   } else if (member == \"SEL_SAMPLE\") {\n      spi_ps_input_cntls[index] = spi_ps_input_cntls[index]\n         .SEL_SAMPLE(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SPI_PS_INPUT_CNTL[{}] does not have member {}\",\n                     index, member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::CB_SHADER_MASK &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"OUTPUT0_ENABLE\") {\n      reg = reg\n         .OUTPUT0_ENABLE(parseValueBool(value));\n   } else if (member == \"OUTPUT1_ENABLE\") {\n      reg = reg\n         .OUTPUT1_ENABLE(parseValueBool(value));\n   } else if (member == \"OUTPUT2_ENABLE\") {\n      reg = reg\n         .OUTPUT2_ENABLE(parseValueBool(value));\n   } else if (member == \"OUTPUT3_ENABLE\") {\n      reg = reg\n         .OUTPUT3_ENABLE(parseValueBool(value));\n   } else if (member == \"OUTPUT4_ENABLE\") {\n      reg = reg\n         .OUTPUT4_ENABLE(parseValueBool(value));\n   } else if (member == \"OUTPUT5_ENABLE\") {\n      reg = reg\n         .OUTPUT5_ENABLE(parseValueBool(value));\n   } else if (member == \"OUTPUT6_ENABLE\") {\n      reg = reg\n         .OUTPUT6_ENABLE(parseValueBool(value));\n   } else if (member == \"OUTPUT7_ENABLE\") {\n      reg = reg\n         .OUTPUT7_ENABLE(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"CB_SHADER_MASK does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::CB_SHADER_CONTROL &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"RT0_ENABLE\") {\n      reg = reg\n         .RT0_ENABLE(parseValueBool(value));\n   } else if (member == \"RT1_ENABLE\") {\n      reg = reg\n         .RT1_ENABLE(parseValueBool(value));\n   } else if (member == \"RT2_ENABLE\") {\n      reg = reg\n         .RT2_ENABLE(parseValueBool(value));\n   } else if (member == \"RT3_ENABLE\") {\n      reg = reg\n         .RT3_ENABLE(parseValueBool(value));\n   } else if (member == \"RT4_ENABLE\") {\n      reg = reg\n         .RT4_ENABLE(parseValueBool(value));\n   } else if (member == \"RT5_ENABLE\") {\n      reg = reg\n         .RT5_ENABLE(parseValueBool(value));\n   } else if (member == \"RT6_ENABLE\") {\n      reg = reg\n         .RT6_ENABLE(parseValueBool(value));\n   } else if (member == \"RT7_ENABLE\") {\n      reg = reg\n         .RT7_ENABLE(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"CB_SHADER_CONTROL does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::DB_SHADER_CONTROL &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"Z_EXPORT_ENABLE\") {\n      reg = reg\n         .Z_EXPORT_ENABLE(parseValueBool(value));\n   } else if (member == \"STENCIL_REF_EXPORT_ENABLE\") {\n      reg = reg\n         .STENCIL_REF_EXPORT_ENABLE(parseValueBool(value));\n   } else if (member == \"Z_ORDER\") {\n      reg = reg\n         .Z_ORDER(parseDbZOrder(value));\n   } else if (member == \"KILL_ENABLE\") {\n      reg = reg\n         .KILL_ENABLE(parseValueBool(value));\n   } else if (member == \"COVERAGE_TO_MASK_ENABLE\") {\n      reg = reg\n         .COVERAGE_TO_MASK_ENABLE(parseValueBool(value));\n   } else if (member == \"MASK_EXPORT_ENABLE\") {\n      reg = reg\n         .MASK_EXPORT_ENABLE(parseValueBool(value));\n   } else if (member == \"DUAL_EXPORT_ENABLE\") {\n      reg = reg\n         .DUAL_EXPORT_ENABLE(parseValueBool(value));\n   } else if (member == \"EXEC_ON_HIER_FAIL\") {\n      reg = reg\n         .EXEC_ON_HIER_FAIL(parseValueBool(value));\n   } else if (member == \"EXEC_ON_NOOP\") {\n      reg = reg\n         .EXEC_ON_NOOP(parseValueBool(value));\n   } else if (member == \"ALPHA_TO_MASK_DISABLE\") {\n      reg = reg\n         .ALPHA_TO_MASK_DISABLE(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"DB_SHADER_CONTROL does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::SPI_INPUT_Z &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"PROVIDE_Z_TO_SPI\") {\n      reg = reg\n         .PROVIDE_Z_TO_SPI(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SPI_INPUT_Z does not have member {}\", member)\n      };\n   }\n}\n\nbool\nparseShaderComments(gfd::GFDPixelShader &shader,\n                    std::vector<std::string> &comments)\n{\n   for (auto &comment : comments) {\n      CommentKeyValue kv;\n\n      if (!parseComment(comment, kv)) {\n         continue;\n      }\n\n      std::transform(kv.obj.begin(), kv.obj.end(), kv.obj.begin(), ::toupper);\n      std::transform(kv.member.begin(), kv.member.end(), kv.member.begin(), ::toupper);\n\n      if (kv.obj == \"SQ_PGM_RESOURCES_PS\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.sq_pgm_resources_ps, kv.member, kv.value);\n      } else if (kv.obj == \"SQ_PGM_EXPORTS_PS\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.sq_pgm_exports_ps, kv.member, kv.value);\n      } else if (kv.obj == \"SPI_PS_IN_CONTROL_0\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.spi_ps_in_control_0, kv.member, kv.value);\n      } else if (kv.obj == \"SPI_PS_IN_CONTROL_1\") {\n         ensureValue(kv);\n         parseRegisterValue(shader.regs.spi_ps_in_control_1, kv.member, kv.value);\n      } else if (kv.obj == \"NUM_SPI_PS_INPUT_CNTL\") {\n         ensureValue(kv);\n         shader.regs.num_spi_ps_input_cntl = parseValueNumber(kv.value);\n      } else if (kv.obj == \"SPI_PS_INPUT_CNTL\") {\n         ensureArrayOfObjects(kv);\n         parseRegisterValue(shader.regs.spi_ps_input_cntls, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"CB_SHADER_MASK\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.cb_shader_mask, kv.member, kv.value);\n      } else if (kv.obj == \"CB_SHADER_CONTROL\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.cb_shader_control, kv.member, kv.value);\n      } else if (kv.obj == \"DB_SHADER_CONTROL\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.db_shader_control, kv.member, kv.value);\n      } else if (kv.obj == \"SPI_INPUT_Z\") {\n         ensureArrayOfObjects(kv);\n         parseRegisterValue(shader.regs.spi_input_z, kv.member, kv.value);\n      } else if (kv.obj == \"UNIFORM_BLOCKS\") {\n         ensureArrayOfObjects(kv);\n         parseUniformBlocks(shader.uniformBlocks, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"UNIFORM_VARS\") {\n         ensureArrayOfObjects(kv);\n         parseUniformVars(shader.uniformVars, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"INITIAL_VALUES\") {\n         ensureArrayOfObjects(kv);\n         parseInitialValues(shader.initialValues, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"LOOP_VARS\") {\n         ensureArrayOfObjects(kv);\n         parseLoopVars(shader.loopVars, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"SAMPLER_VARS\") {\n         ensureArrayOfObjects(kv);\n         parseSamplerVars(shader.samplerVars, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"MODE\") {\n         ensureValue(kv);\n         shader.mode = parseShaderMode(kv.value);\n      } else {\n         throw gfd_header_parse_exception { fmt::format(\"Unknown key {}\", kv.obj) };\n      }\n\n      /*\n      TODO:\n      GFDRBuffer gx2rData;\n      */\n   }\n\n   return true;\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/gfd_vsh_comment_parser.cpp",
    "content": "#include \"gfd_comment_parser.h\"\n#include <fmt/core.h>\n#include <libgpu/latte/latte_constants.h>\n\nstatic void\nparseRegisterValue(latte::SQ_PGM_RESOURCES_VS &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"NUM_GPRS\") {\n      reg = reg\n         .NUM_GPRS(parseValueNumber(value));\n   } else if (member == \"STACK_SIZE\") {\n      reg = reg\n         .STACK_SIZE(parseValueNumber(value));\n   } else if (member == \"DX10_CLAMP\") {\n      reg = reg\n         .DX10_CLAMP(parseValueBool(value));\n   } else if (member == \"PRIME_CACHE_PGM_EN\") {\n      reg = reg\n         .PRIME_CACHE_PGM_EN(parseValueBool(value));\n   } else if (member == \"PRIME_CACHE_ON_DRAW\") {\n      reg = reg\n         .PRIME_CACHE_ON_DRAW(parseValueBool(value));\n   } else if (member == \"FETCH_CACHE_LINES\") {\n      reg = reg\n         .FETCH_CACHE_LINES(parseValueNumber(value));\n   } else if (member == \"UNCACHED_FIRST_INST\") {\n      reg = reg\n         .UNCACHED_FIRST_INST(parseValueBool(value));\n   } else if (member == \"PRIME_CACHE_ENABLE\") {\n      reg = reg\n         .PRIME_CACHE_ENABLE(parseValueBool(value));\n   } else if (member == \"PRIME_CACHE_ON_CONST\") {\n      reg = reg\n         .PRIME_CACHE_ON_CONST(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SQ_PGM_RESOURCES_VS does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::VGT_PRIMITIVEID_EN &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"PRIMITIVEID_EN\") {\n      reg = reg\n         .PRIMITIVEID_EN(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"VGT_PRIMITIVEID_EN does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::SPI_VS_OUT_CONFIG &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"VS_PER_COMPONENT\") {\n      reg = reg\n         .VS_PER_COMPONENT(parseValueBool(value));\n   } else if (member == \"VS_EXPORT_COUNT\") {\n      reg = reg\n         .VS_EXPORT_COUNT(parseValueNumber(value));\n   } else if (member == \"VS_EXPORTS_FOG\") {\n      reg = reg\n         .VS_EXPORTS_FOG(parseValueBool(value));\n   } else if (member == \"VS_OUT_FOG_VEC_ADDR\") {\n      reg = reg\n         .VS_OUT_FOG_VEC_ADDR(parseValueNumber(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SPI_VS_OUT_CONFIG does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(std::array<latte::SPI_VS_OUT_ID_N, 10> &spi_vs_out_id,\n                   uint32_t index,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (index >= spi_vs_out_id.size()) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SQ_VTX_SEMANTIC[{}] invalid index, max: {}\",\n                     index, spi_vs_out_id.size())\n      };\n   }\n\n   if (member == \"SEMANTIC_0\") {\n      spi_vs_out_id[index] = spi_vs_out_id[index]\n         .SEMANTIC_0(static_cast<uint8_t>(parseValueNumber(value)));\n   } else if (member == \"SEMANTIC_1\") {\n      spi_vs_out_id[index] = spi_vs_out_id[index]\n         .SEMANTIC_1(static_cast<uint8_t>(parseValueNumber(value)));\n   } else if (member == \"SEMANTIC_2\") {\n      spi_vs_out_id[index] = spi_vs_out_id[index]\n         .SEMANTIC_2(static_cast<uint8_t>(parseValueNumber(value)));\n   } else if (member == \"SEMANTIC_3\") {\n      spi_vs_out_id[index] = spi_vs_out_id[index]\n         .SEMANTIC_3(static_cast<uint8_t>(parseValueNumber(value)));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SPI_VS_OUT_ID[{}] does not have member {}\",\n                     index, member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::PA_CL_VS_OUT_CNTL &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"CLIP_DIST_ENA_0\") {\n      reg = reg\n         .CLIP_DIST_ENA_0(parseValueBool(value));\n   } else if (member == \"CLIP_DIST_ENA_1\") {\n      reg = reg\n         .CLIP_DIST_ENA_1(parseValueBool(value));\n   } else if (member == \"CLIP_DIST_ENA_2\") {\n      reg = reg\n         .CLIP_DIST_ENA_2(parseValueBool(value));\n   } else if (member == \"CLIP_DIST_ENA_3\") {\n      reg = reg\n         .CLIP_DIST_ENA_3(parseValueBool(value));\n   } else if (member == \"CLIP_DIST_ENA_4\") {\n      reg = reg\n         .CLIP_DIST_ENA_4(parseValueBool(value));\n   } else if (member == \"CLIP_DIST_ENA_5\") {\n      reg = reg\n         .CLIP_DIST_ENA_5(parseValueBool(value));\n   } else if (member == \"CLIP_DIST_ENA_6\") {\n      reg = reg\n         .CLIP_DIST_ENA_6(parseValueBool(value));\n   } else if (member == \"CLIP_DIST_ENA_7\") {\n      reg = reg\n         .CLIP_DIST_ENA_7(parseValueBool(value));\n   } else if (member == \"CULL_DIST_ENA_0\") {\n      reg = reg\n         .CULL_DIST_ENA_0(parseValueBool(value));\n   } else if (member == \"CULL_DIST_ENA_1\") {\n      reg = reg\n         .CULL_DIST_ENA_1(parseValueBool(value));\n   } else if (member == \"CULL_DIST_ENA_2\") {\n      reg = reg\n         .CULL_DIST_ENA_2(parseValueBool(value));\n   } else if (member == \"CULL_DIST_ENA_3\") {\n      reg = reg\n         .CULL_DIST_ENA_3(parseValueBool(value));\n   } else if (member == \"CULL_DIST_ENA_4\") {\n      reg = reg\n         .CULL_DIST_ENA_4(parseValueBool(value));\n   } else if (member == \"CULL_DIST_ENA_5\") {\n      reg = reg\n         .CULL_DIST_ENA_5(parseValueBool(value));\n   } else if (member == \"CULL_DIST_ENA_6\") {\n      reg = reg\n         .CULL_DIST_ENA_6(parseValueBool(value));\n   } else if (member == \"CULL_DIST_ENA_7\") {\n      reg = reg\n         .CULL_DIST_ENA_7(parseValueBool(value));\n   } else if (member == \"USE_VTX_POINT_SIZE\") {\n      reg = reg\n         .USE_VTX_POINT_SIZE(parseValueBool(value));\n   } else if (member == \"USE_VTX_EDGE_FLAG\") {\n      reg = reg\n         .USE_VTX_EDGE_FLAG(parseValueBool(value));\n   } else if (member == \"USE_VTX_RENDER_TARGET_INDX\") {\n      reg = reg\n         .USE_VTX_RENDER_TARGET_INDX(parseValueBool(value));\n   } else if (member == \"USE_VTX_VIEWPORT_INDX\") {\n      reg = reg\n         .USE_VTX_VIEWPORT_INDX(parseValueBool(value));\n   } else if (member == \"USE_VTX_KILL_FLAG\") {\n      reg = reg\n         .USE_VTX_KILL_FLAG(parseValueBool(value));\n   } else if (member == \"VS_OUT_MISC_VEC_ENA\") {\n      reg = reg\n         .VS_OUT_MISC_VEC_ENA(parseValueBool(value));\n   } else if (member == \"VS_OUT_CCDIST0_VEC_ENA\") {\n      reg = reg\n         .VS_OUT_CCDIST0_VEC_ENA(parseValueBool(value));\n   } else if (member == \"VS_OUT_CCDIST1_VEC_ENA\") {\n      reg = reg\n         .VS_OUT_CCDIST1_VEC_ENA(parseValueBool(value));\n   } else if (member == \"VS_OUT_MISC_SIDE_BUS_ENA\") {\n      reg = reg\n         .VS_OUT_MISC_SIDE_BUS_ENA(parseValueBool(value));\n   } else if (member == \"USE_VTX_GS_CUT_FLAG\") {\n      reg = reg\n         .USE_VTX_GS_CUT_FLAG(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SPI_VS_OUT_CONFIG does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(std::array<latte::SQ_VTX_SEMANTIC_N, 32> &sq_vtx_semantic,\n                   uint32_t index,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (index >= sq_vtx_semantic.size()) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SQ_VTX_SEMANTIC[{}] invalid index, max: {}\",\n                     index, sq_vtx_semantic.size())\n      };\n   }\n\n   if (member == \"SEMANTIC_ID\") {\n      sq_vtx_semantic[index] = sq_vtx_semantic[index]\n         .SEMANTIC_ID(parseValueNumber(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"SQ_VTX_SEMANTIC[{}] does not have member {}\",\n                     index, member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::VGT_STRMOUT_BUFFER_EN &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"BUFFER_0_EN\") {\n      reg = reg\n         .BUFFER_0_EN(parseValueBool(value));\n   } else if(member == \"BUFFER_1_EN\") {\n      reg = reg\n         .BUFFER_1_EN(parseValueBool(value));\n   } else if (member == \"BUFFER_2_EN\") {\n      reg = reg\n         .BUFFER_2_EN(parseValueBool(value));\n   } else if (member == \"BUFFER_3_EN\") {\n      reg = reg\n         .BUFFER_3_EN(parseValueBool(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"VGT_STRMOUT_BUFFER_EN does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::VGT_VERTEX_REUSE_BLOCK_CNTL &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"VTX_REUSE_DEPTH\") {\n      reg = reg\n         .VTX_REUSE_DEPTH(parseValueNumber(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"VGT_VERTEX_REUSE_BLOCK_CNTL does not have member {}\",\n                     member)\n      };\n   }\n}\n\nstatic void\nparseRegisterValue(latte::VGT_HOS_REUSE_DEPTH &reg,\n                   const std::string &member,\n                   const std::string &value)\n{\n   if (member == \"REUSE_DEPTH\") {\n      reg = reg\n         .REUSE_DEPTH(parseValueNumber(value));\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"VGT_HOS_REUSE_DEPTH does not have member {}\", member)\n      };\n   }\n}\n\nstatic void\nparseAttribVars(std::vector<gfd::GFDAttribVar> &attribVars,\n                uint32_t index,\n                const std::string &member,\n                const std::string &value)\n{\n   if (index >= latte::MaxAttribBuffers) {\n      throw gfd_header_parse_exception {\n         fmt::format(\"ATTRIB_VARS[{}] invalid index, max: {}\",\n                     index, latte::MaxAttribBuffers)\n      };\n   }\n\n   if (index >= attribVars.size()) {\n      attribVars.resize(index + 1);\n      attribVars[index].type = cafe::gx2::GX2ShaderVarType::Float4;\n      attribVars[index].count = 0;\n      attribVars[index].location = index;\n   }\n\n   if (member == \"NAME\") {\n      attribVars[index].name = value;\n   } else if (member == \"TYPE\") {\n      attribVars[index].type = parseShaderVarType(value);\n   } else if (member == \"COUNT\") {\n      attribVars[index].count = parseValueNumber(value);\n   } else if (member == \"LOCATION\") {\n      attribVars[index].location = parseValueNumber(value);\n   } else {\n      throw gfd_header_parse_exception {\n         fmt::format(\"ATTRIB_VARS[{}] does not have member {}\", index, member)\n      };\n   }\n}\n\nbool\nparseShaderComments(gfd::GFDVertexShader &shader,\n                    std::vector<std::string> &comments)\n{\n   for (auto &comment : comments) {\n      CommentKeyValue kv;\n\n      if (!parseComment(comment, kv)) {\n         continue;\n      }\n\n      std::transform(kv.obj.begin(), kv.obj.end(), kv.obj.begin(), ::toupper);\n      std::transform(kv.member.begin(), kv.member.end(), kv.member.begin(), ::toupper);\n\n      if (kv.obj == \"SQ_PGM_RESOURCES_VS\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.sq_pgm_resources_vs, kv.member, kv.value);\n      } else if (kv.obj == \"VGT_PRIMITIVEID_EN\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.vgt_primitiveid_en, kv.member, kv.value);\n      } else if (kv.obj == \"SPI_VS_OUT_CONFIG\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.spi_vs_out_config, kv.member, kv.value);\n      } else if (kv.obj == \"NUM_SPI_VS_OUT_ID\") {\n         ensureValue(kv);\n         shader.regs.num_spi_vs_out_id = parseValueNumber(kv.value);\n      } else if (kv.obj == \"SPI_VS_OUT_ID\") {\n         ensureArrayOfObjects(kv);\n         parseRegisterValue(shader.regs.spi_vs_out_id, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"PA_CL_VS_OUT_CNTL\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.pa_cl_vs_out_cntl, kv.member, kv.value);\n      } else if (kv.obj == \"SQ_VTX_SEMANTIC_CLEAR\") {\n         ensureValue(kv);\n         shader.regs.sq_vtx_semantic_clear = shader.regs.sq_vtx_semantic_clear\n            .CLEAR(parseValueNumber(kv.value));\n      } else if (kv.obj == \"NUM_SQ_VTX_SEMANTIC\") {\n         ensureValue(kv);\n         shader.regs.num_sq_vtx_semantic = parseValueNumber(kv.value);\n      } else if (kv.obj == \"SQ_VTX_SEMANTIC\") {\n         ensureArrayOfObjects(kv);\n         parseRegisterValue(shader.regs.sq_vtx_semantic, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"VGT_STRMOUT_BUFFER_EN\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.vgt_strmout_buffer_en, kv.member, kv.value);\n      } else if (kv.obj == \"VGT_VERTEX_REUSE_BLOCK_CNTL\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.vgt_vertex_reuse_block_cntl, kv.member, kv.value);\n      } else if (kv.obj == \"VGT_HOS_REUSE_DEPTH\") {\n         ensureObject(kv);\n         parseRegisterValue(shader.regs.vgt_hos_reuse_depth, kv.member, kv.value);\n      } else if (kv.obj == \"ATTRIB_VARS\") {\n         ensureArrayOfObjects(kv);\n         parseAttribVars(shader.attribVars, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"UNIFORM_BLOCKS\") {\n         ensureArrayOfObjects(kv);\n         parseUniformBlocks(shader.uniformBlocks, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"UNIFORM_VARS\") {\n         ensureArrayOfObjects(kv);\n         parseUniformVars(shader.uniformVars, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"INITIAL_VALUES\") {\n         ensureArrayOfObjects(kv);\n         parseInitialValues(shader.initialValues, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"LOOP_VARS\") {\n         ensureArrayOfObjects(kv);\n         parseLoopVars(shader.loopVars, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"SAMPLER_VARS\") {\n         ensureArrayOfObjects(kv);\n         parseSamplerVars(shader.samplerVars, std::stoul(kv.index), kv.member, kv.value);\n      } else if (kv.obj == \"MODE\") {\n         ensureValue(kv);\n         shader.mode = parseShaderMode(kv.value);\n      } else if (kv.obj == \"RING_ITEM_SIZE\") {\n         ensureValue(kv);\n         shader.ringItemSize = parseValueNumber(kv.value);\n      } else if (kv.obj == \"HAS_STREAM_OUT\") {\n         ensureValue(kv);\n         shader.hasStreamOut = parseValueBool(kv.value);\n      } else if (kv.obj == \"STREAM_OUT_STRIDE\") {\n         ensureArrayOfValues(kv);\n         auto index = std::stoul(kv.index);\n\n         if (index >= shader.streamOutStride.size()) {\n            throw gfd_header_parse_exception {\n               fmt::format(\"STREAM_OUT_STRIDE[{}] invalid index, max: {}\",\n                           index, shader.streamOutStride.size())\n            };\n         }\n\n         shader.streamOutStride[index] = parseValueNumber(kv.value);\n      } else {\n         throw gfd_header_parse_exception {\n            fmt::format(\"Unknown key {}\", kv.obj)\n         };\n      }\n\n      /*\n      TODO:\n      GFDRBuffer gx2rData;\n      */\n   }\n\n   return true;\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/glsl_compiler.cpp",
    "content": "#include \"shader_assembler.h\"\n\n#include <common/align.h>\n#include <common/platform.h>\n#include <common/platform_winapi_string.h>\n#include <fmt/format.h>\n#include <fstream>\n#include <glslang/Include/Types.h>\n#include <glslang/Public/ShaderLang.h>\n#include <glslang/MachineIndependent/localintermediate.h>\n#include <iterator>\n#include <iostream>\n#include <memory>\n#include <string>\n#include <vector>\n\n#ifdef PLATFORM_WINDOWS\n#include <Windows.h>\n#endif\n\nstatic bool\nreadFile(const std::string &path,\n         std::string &buff)\n{\n   std::ifstream ifs { path, std::ios::in | std::ios::binary };\n   if (ifs.fail()) {\n      return false;\n   }\n\n   buff.resize(static_cast<unsigned int>(ifs.seekg(0, std::ios::end).tellg()));\n   if (!buff.empty()) {\n      ifs.seekg(0, std::ios::beg).read(&buff[0], static_cast<std::streamsize>(buff.size()));\n   }\n\n   return true;\n}\n\nnamespace glslang {\nconst TBuiltInResource DefaultTBuiltInResource = {\n   /* .MaxLights = */ 32,\n   /* .MaxClipPlanes = */ 6,\n   /* .MaxTextureUnits = */ 32,\n   /* .MaxTextureCoords = */ 32,\n   /* .MaxVertexAttribs = */ 64,\n   /* .MaxVertexUniformComponents = */ 4096,\n   /* .MaxVaryingFloats = */ 64,\n   /* .MaxVertexTextureImageUnits = */ 32,\n   /* .MaxCombinedTextureImageUnits = */ 80,\n   /* .MaxTextureImageUnits = */ 32,\n   /* .MaxFragmentUniformComponents = */ 4096,\n   /* .MaxDrawBuffers = */ 32,\n   /* .MaxVertexUniformVectors = */ 128,\n   /* .MaxVaryingVectors = */ 8,\n   /* .MaxFragmentUniformVectors = */ 16,\n   /* .MaxVertexOutputVectors = */ 16,\n   /* .MaxFragmentInputVectors = */ 15,\n   /* .MinProgramTexelOffset = */ -8,\n   /* .MaxProgramTexelOffset = */ 7,\n   /* .MaxClipDistances = */ 8,\n   /* .MaxComputeWorkGroupCountX = */ 65535,\n   /* .MaxComputeWorkGroupCountY = */ 65535,\n   /* .MaxComputeWorkGroupCountZ = */ 65535,\n   /* .MaxComputeWorkGroupSizeX = */ 1024,\n   /* .MaxComputeWorkGroupSizeY = */ 1024,\n   /* .MaxComputeWorkGroupSizeZ = */ 64,\n   /* .MaxComputeUniformComponents = */ 1024,\n   /* .MaxComputeTextureImageUnits = */ 16,\n   /* .MaxComputeImageUniforms = */ 8,\n   /* .MaxComputeAtomicCounters = */ 8,\n   /* .MaxComputeAtomicCounterBuffers = */ 1,\n   /* .MaxVaryingComponents = */ 60,\n   /* .MaxVertexOutputComponents = */ 64,\n   /* .MaxGeometryInputComponents = */ 64,\n   /* .MaxGeometryOutputComponents = */ 128,\n   /* .MaxFragmentInputComponents = */ 128,\n   /* .MaxImageUnits = */ 8,\n   /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8,\n   /* .MaxCombinedShaderOutputResources = */ 8,\n   /* .MaxImageSamples = */ 0,\n   /* .MaxVertexImageUniforms = */ 0,\n   /* .MaxTessControlImageUniforms = */ 0,\n   /* .MaxTessEvaluationImageUniforms = */ 0,\n   /* .MaxGeometryImageUniforms = */ 0,\n   /* .MaxFragmentImageUniforms = */ 8,\n   /* .MaxCombinedImageUniforms = */ 8,\n   /* .MaxGeometryTextureImageUnits = */ 16,\n   /* .MaxGeometryOutputVertices = */ 256,\n   /* .MaxGeometryTotalOutputComponents = */ 1024,\n   /* .MaxGeometryUniformComponents = */ 1024,\n   /* .MaxGeometryVaryingComponents = */ 64,\n   /* .MaxTessControlInputComponents = */ 128,\n   /* .MaxTessControlOutputComponents = */ 128,\n   /* .MaxTessControlTextureImageUnits = */ 16,\n   /* .MaxTessControlUniformComponents = */ 1024,\n   /* .MaxTessControlTotalOutputComponents = */ 4096,\n   /* .MaxTessEvaluationInputComponents = */ 128,\n   /* .MaxTessEvaluationOutputComponents = */ 128,\n   /* .MaxTessEvaluationTextureImageUnits = */ 16,\n   /* .MaxTessEvaluationUniformComponents = */ 1024,\n   /* .MaxTessPatchComponents = */ 120,\n   /* .MaxPatchVertices = */ 32,\n   /* .MaxTessGenLevel = */ 64,\n   /* .MaxViewports = */ 16,\n   /* .MaxVertexAtomicCounters = */ 0,\n   /* .MaxTessControlAtomicCounters = */ 0,\n   /* .MaxTessEvaluationAtomicCounters = */ 0,\n   /* .MaxGeometryAtomicCounters = */ 0,\n   /* .MaxFragmentAtomicCounters = */ 8,\n   /* .MaxCombinedAtomicCounters = */ 8,\n   /* .MaxAtomicCounterBindings = */ 1,\n   /* .MaxVertexAtomicCounterBuffers = */ 0,\n   /* .MaxTessControlAtomicCounterBuffers = */ 0,\n   /* .MaxTessEvaluationAtomicCounterBuffers = */ 0,\n   /* .MaxGeometryAtomicCounterBuffers = */ 0,\n   /* .MaxFragmentAtomicCounterBuffers = */ 1,\n   /* .MaxCombinedAtomicCounterBuffers = */ 1,\n   /* .MaxAtomicCounterBufferSize = */ 16384,\n   /* .MaxTransformFeedbackBuffers = */ 4,\n   /* .MaxTransformFeedbackInterleavedComponents = */ 64,\n   /* .MaxCullDistances = */ 8,\n   /* .MaxCombinedClipAndCullDistances = */ 8,\n   /* .MaxSamples = */ 4,\n   /* .maxMeshOutputVerticesNV = */ 256,\n   /* .maxMeshOutputPrimitivesNV = */ 512,\n   /* .maxMeshWorkGroupSizeX_NV = */ 32,\n   /* .maxMeshWorkGroupSizeY_NV = */ 1,\n   /* .maxMeshWorkGroupSizeZ_NV = */ 1,\n   /* .maxTaskWorkGroupSizeX_NV = */ 32,\n   /* .maxTaskWorkGroupSizeY_NV = */ 1,\n   /* .maxTaskWorkGroupSizeZ_NV = */ 1,\n   /* .maxMeshViewCountNV = */ 4,\n   /* .maxDualSourceDrawBuffersEXT */ 1,\n\n   /* .limits = */ {\n   /* .nonInductiveForLoops = */ true,\n   /* .whileLoops = */ true,\n   /* .doWhileLoops = */ true,\n   /* .generalUniformIndexing = */ true,\n   /* .generalAttributeMatrixVectorIndexing = */ true,\n   /* .generalVaryingIndexing = */ true,\n   /* .generalSamplerIndexing = */ true,\n   /* .generalVariableIndexing = */ true,\n   /* .generalConstantMatrixVectorIndexing = */ true,\n} };\n\n} // namespace glslang\n\nstatic int\ngetTypeBytes(const glslang::TType *type)\n{\n   auto size = 0;\n\n   switch (type->getBasicType()) {\n   case glslang::EbtInt8:\n   case glslang::EbtUint8:\n      size = 1;\n      break;\n   case glslang::EbtInt16:\n   case glslang::EbtUint16:\n   case glslang::EbtFloat16:\n      size = 2;\n      break;\n   case glslang::EbtInt:\n   case glslang::EbtUint:\n   case glslang::EbtFloat:\n      size = 4;\n      break;\n   case glslang::EbtInt64:\n   case glslang::EbtUint64:\n   case glslang::EbtDouble:\n      size = 8;\n      break;\n   default:\n      return 0;\n   }\n\n   if (type->isMatrix()) {\n      size *= type->getMatrixCols();\n      size *= type->getMatrixRows();\n   }\n\n   if (type->isArray()) {\n      size *= type->getCumulativeArraySize();\n   }\n\n   if (type->isVector()) {\n      size *= type->getVectorSize();\n   }\n\n   return size;\n}\n\nstatic std::string\ngetTypeString(const glslang::TType *type)\n{\n   if (type->isVector()) {\n      auto result = std::string { };\n\n      switch (type->getBasicType()) {\n      case glslang::EbtFloat:\n         break;\n      case glslang::EbtBool:\n         result = \"b\";\n         break;\n      case glslang::EbtInt:\n         result = \"i\";\n         break;\n      case glslang::EbtUint:\n         result = \"u\";\n         break;\n      case glslang::EbtDouble:\n         result = \"d\";\n         break;\n      default:\n         return {}; // Invalid\n      }\n\n      result += \"vec\";\n      result += std::to_string(type->getVectorSize());\n      return result;\n   }\n\n   if (type->isMatrix()) {\n      auto result = std::string { };\n\n      if (type->getBasicType() == glslang::EbtDouble) {\n         result += \"d\";\n      } else if (type->getBasicType() != glslang::EbtFloat) {\n         return {};\n      }\n\n      auto cols = type->getMatrixCols();\n      auto rows = type->getMatrixRows();\n      if (cols < 2 || cols > 4 || rows < 2 || rows > 4) {\n         return {};\n      }\n\n      result += \"mat\";\n      result += std::to_string(cols);\n\n      if (rows != cols) {\n         result += \"x\";\n         result += std::to_string(rows);\n      }\n\n      return result;\n   }\n\n   switch (type->getBasicType()) {\n   case glslang::EbtVoid:\n      return \"void\";\n   case glslang::EbtBool:\n      return \"bool\";\n   case glslang::EbtInt:\n      return \"int\";\n   case glslang::EbtUint:\n      return \"uint\";\n   case glslang::EbtFloat:\n      return \"float\";\n   case glslang::EbtDouble:\n      return \"double\";\n   default:\n      return {}; // Invalid\n   }\n}\n\nstatic std::unique_ptr<glslang::TShader>\nparseShader(EShLanguage stage, std::string path)\n{\n   auto source = std::string { };\n   if (!readFile(path, source)) {\n      std::cout << \"Could not read \" << path << std::endl;\n      return nullptr;\n   }\n\n   /*std::string header = \"#version 410\\n#extension GL_ARB_separate_shader_objects : enable\\n\";\n   source.insert(source.begin(), std::begin(header), std::end(header));\n   source.push_back('\\0');*/\n\n   const char *texts[1] = { source.c_str() };\n   const char *paths[1] = { path.c_str() };\n   auto shader = std::make_unique<glslang::TShader>(stage);\n   shader->setStringsWithLengthsAndNames(texts, nullptr, paths, 1);\n   shader->setUniformLocationBase(0);\n   shader->setEntryPoint(\"main\");\n\n   auto resources = glslang::DefaultTBuiltInResource;\n   auto messages = EShMessages::EShMsgDefault;\n   if (!shader->parse(&resources, 120, false, messages)) {\n      std::cout << \"glslang failed to parse shader\" << std::endl;\n      std::cout << shader->getInfoLog() << std::endl;\n      std::cout << shader->getInfoDebugLog() << std::endl;\n      return nullptr;\n   }\n\n   shader->getIntermediate()->addRequestedExtension(\"GL_ARB_separate_shader_objects\");\n   return shader;\n}\n\nstatic std::string\nparseGlslFileToHeader(glslang::TShader &shader)\n{\n   auto messages = EShMessages::EShMsgDefault;\n   auto program = glslang::TProgram { };\n   program.addShader(&shader);\n\n   if (!program.link(messages)) {\n      std::cout << \"glslang failed to link shader for shader\" << std::endl;\n      std::cout << program.getInfoLog() << std::endl;\n      std::cout << program.getInfoDebugLog() << std::endl;\n      return {};\n   }\n\n   if (!program.buildReflection(EShReflectionDefault | EShReflectionAllBlockVariables | EShReflectionIntermediateIO)) {\n      std::cout << \"glslang failed to build reflection for shader\" << std::endl;\n      std::cout << program.getInfoLog() << std::endl;\n      std::cout << program.getInfoDebugLog() << std::endl;\n      return {};\n   }\n\n   /*\n   auto mapper = new glslang::TGlslIoMapper { };\n   auto resolver = new glslang::TDefaultGlslIoResolver { *program.getIntermediate(EShLanguage::EShLangVertex) };\n   if (!program.mapIO(resolver, mapper)) {\n      std::cout << \"glslang failed to map io\" << std::endl;\n      std::cout << vertexShader->getInfoLog() << std::endl;\n      std::cout << vertexShader->getInfoDebugLog() << std::endl;\n      return -1;\n   }\n      */\n\n   // AMD ShaderAnalyzer puts the in / out / uniforms in alphabetical order\n   std::vector<glslang::TObjectReflection> inputs;\n   std::vector<glslang::TObjectReflection> outputs;\n   std::vector<glslang::TObjectReflection> uniformVars;\n   std::vector<glslang::TObjectReflection> uniformBlocks;\n\n   for (auto i = 0; i < program.getNumPipeInputs(); ++i) {\n      const auto &input = program.getPipeInput(i);\n      inputs.insert(std::upper_bound(inputs.begin(), inputs.end(), input,\n         [](const auto &lhs, const auto &rhs) {\n            return lhs.name < rhs.name;\n         }), input);\n   }\n\n   for (auto i = 0; i < program.getNumPipeOutputs(); ++i) {\n      const auto &output = program.getPipeOutput(i);\n      outputs.insert(std::upper_bound(outputs.begin(), outputs.end(), output,\n         [](const auto &lhs, const auto &rhs) {\n            return lhs.name < rhs.name;\n         }), output);\n   }\n\n   for (auto i = 0; i < program.getNumUniformVariables(); ++i) {\n      const auto &uniform = program.getUniform(i);\n      uniformVars.insert(std::upper_bound(uniformVars.begin(), uniformVars.end(), uniform,\n         [](const auto &lhs, const auto &rhs) {\n            return lhs.name < rhs.name;\n         }), uniform);\n   }\n\n   for (auto i = 0; i < program.getNumUniformBlocks(); ++i) {\n      const auto &uniform = program.getUniformBlock(i);\n      uniformBlocks.insert(std::upper_bound(uniformBlocks.begin(), uniformBlocks.end(), uniform,\n         [](const auto &lhs, const auto &rhs) {\n            return lhs.name < rhs.name;\n         }), uniform);\n   }\n\n   // Generate assembly annotation\n   auto outVsh = fmt::memory_buffer { };\n   auto outPsh = fmt::memory_buffer{ };\n   fmt::format_to(std::back_inserter(outVsh), \"\\n\");\n   fmt::format_to(std::back_inserter(outPsh), \"\\n\");\n\n   // Process inputs for fragment shader\n   auto pixelInputCount = 0;\n   for (auto i = 0u; i < inputs.size(); ++i) {\n      if (inputs[i].stages & EShLanguageMask::EShLangFragmentMask) {\n         ++pixelInputCount;\n      }\n   }\n\n   if (pixelInputCount) {\n      fmt::format_to(std::back_inserter(outPsh), \"; $NUM_SPI_PS_INPUT_CNTL = {}\\n\", pixelInputCount);\n      for (auto i = 0; i < pixelInputCount; ++i) {\n         fmt::format_to(std::back_inserter(outPsh), \"; $SPI_PS_INPUT_CNTL[{}].SEMANTIC = {}\\n\", i, i);\n      }\n   }\n\n   // Process inputs for vertex shader\n   for (auto i = 0u; i < inputs.size(); ++i) {\n      const auto &input = inputs[i];\n      auto type = input.getType();\n      auto typeName = getTypeString(type);\n      auto typeBytes = getTypeBytes(type);\n      if (typeName.empty() || typeBytes == 0) {\n         std::cout << \"Invalid type for input \" << input.name << std::endl;\n         return {};\n      }\n\n      if (input.stages & EShLanguageMask::EShLangVertexMask) {\n         fmt::format_to(std::back_inserter(outVsh), \"; $ATTRIB_VARS[{}].name = \\\"{}\\\"\\n\", i, input.name);\n         fmt::format_to(std::back_inserter(outVsh), \"; $ATTRIB_VARS[{}].type = \\\"{}\\\"\\n\", i, typeName);\n         fmt::format_to(std::back_inserter(outVsh), \"; $ATTRIB_VARS[{}].location = {}\\n\", i, i);\n\n         if (type->isArray()) {\n            fmt::format_to(std::back_inserter(outVsh), \"; $ATTRIB_VARS[{}].count = {}\\n\", i, type->getCumulativeArraySize());\n         }\n\n         fmt::format_to(std::back_inserter(outVsh), \"\\n\");\n      }\n   }\n\n   // Process uniform vars for both shaders\n   auto uniformVarsOffset = 0;\n   for (auto i = 0u; i < uniformVars.size(); ++i) {\n      const auto &uniform = uniformVars[i];\n      auto type = uniform.getType();\n      auto typeName = getTypeString(type);\n      auto typeBytes = getTypeBytes(type);\n      if (typeName.empty() || typeBytes == 0) {\n         std::cout << \"Invalid type for uniform \" << uniform.name << std::endl;\n         return {};\n      }\n\n      auto out = fmt::memory_buffer { };\n      fmt::format_to(std::back_inserter(out),\n                     \"; $UNIFORM_VARS[{}].name = \\\"{}\\\"\\n\",\n                     i, uniform.name);\n      fmt::format_to(std::back_inserter(out),\n                     \"; $UNIFORM_VARS[{}].type = \\\"{}\\\"\\n\",\n                     i, typeName);\n      fmt::format_to(std::back_inserter(out),\n                     \"; $UNIFORM_VARS[{}].offset = {}\\n\",\n                     i, uniformVarsOffset / 4);\n      uniformVarsOffset += align_up(typeBytes, 4);\n\n      if (type->isArray()) {\n         fmt::format_to(std::back_inserter(out),\n                        \"; $UNIFORM_VARS[{}].count = {}\\n\",\n                        i, type->getCumulativeArraySize());\n      }\n\n      fmt::format_to(std::back_inserter(out), \"\\n\");\n\n      if (uniform.stages & EShLanguageMask::EShLangVertexMask) {\n         outVsh.append(out.begin(), out.end());\n      }\n\n      if (uniform.stages & EShLanguageMask::EShLangFragmentMask) {\n         outPsh.append(out.begin(), out.end());\n      }\n   }\n\n   if (!uniformBlocks.empty()) {\n      std::cout << \"Unimplemented uniform blocks\" << std::endl;\n      return {};\n   }\n\n   // Process output for vertex shader\n   auto vertexOutputCount = 0;\n   for (auto i = 0u; i < outputs.size(); ++i) {\n      const auto &output = outputs[i];\n      if (output.stages & EShLanguageMask::EShLangVertexMask) {\n         auto qualifier = output.getType()->getQualifier();\n         if (qualifier.storage == glslang::EvqVaryingOut) {\n            ++vertexOutputCount;\n         } else if (qualifier.builtIn == glslang::EbvPointSize) {\n            fmt::format_to(std::back_inserter(outVsh), \"; $PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE = true\\n\");\n         }\n      }\n   }\n\n   if (vertexOutputCount) {\n      fmt::format_to(std::back_inserter(outVsh), \"; $NUM_SPI_VS_OUT_ID = {}\\n\", (vertexOutputCount + 3) / 4);\n      for (auto i = 0; i < vertexOutputCount; ++i) {\n         fmt::format_to(std::back_inserter(outVsh), \"; $SPI_VS_OUT_ID[{}].SEMANTIC_{} = {}\\n\", i / 4, i % 4, i);\n      }\n   }\n\n   outVsh.push_back('\\0');\n   outPsh.push_back('\\0');\n\n   if (shader.getStage() == EShLanguage::EShLangVertex) {\n      return std::string { outVsh.data() };\n   } else {\n      return std::string { outPsh.data() };\n   }\n}\n\nstatic std::string\nrunAmdShaderAnalyzer(std::string shaderAnalyzerPath,\n                     std::string shaderPath,\n                     ShaderType shaderType)\n{\n   std::string args;\n   args += \"\\\"\" + shaderAnalyzerPath + \"\\\"\";\n   args += \" \\\"\" + shaderPath + \"\\\"\";\n   args += \" -ASIC RV730\";\n\n   if (shaderType == ShaderType::VertexShader) {\n      args += \" -P glsl_vs\";\n   } else if (shaderType == ShaderType::PixelShader) {\n      args += \" -P glsl_fs\";\n   }\n\n   args += \" -I tmp.txt\";\n\n#ifdef PLATFORM_WINDOWS\n   SECURITY_ATTRIBUTES security_attributes;\n   security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);\n   security_attributes.bInheritHandle = TRUE;\n   security_attributes.lpSecurityDescriptor = nullptr;\n\n   STARTUPINFOA si;\n   PROCESS_INFORMATION pi;\n   DWORD exitCode;\n   ZeroMemory(&si, sizeof(STARTUPINFOA));\n   ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));\n   si.cb = sizeof(si);\n   if (!CreateProcessA(nullptr, args.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi)) {\n      std::cout << \"Failed to create AMD ShaderAnalyzer process\" << std::endl;\n      return {};\n   }\n   CloseHandle(pi.hThread);\n\n   WaitForSingleObject(pi.hProcess, INFINITE);\n   if (!GetExitCodeProcess(pi.hProcess, &exitCode)) {\n      exitCode = static_cast<DWORD>(-1);\n   }\n\n   if (exitCode != 0) {\n      std::cout << \"AMD ShaderAnalyzer returned \" << exitCode << std::endl;\n      return {};\n   }\n#else\n   std::cout << \"runAmdShaderAnalyzer unimplemented on this platform\" << std::endl;\n   std::cout << \"Consider using wine ? ShaderAnalyzer is Windows only anyway :D\" << std::endl;\n   return {};\n#endif\n\n   std::string assembly;\n   if (!readFile(\"tmp.txt\", assembly)) {\n      std::cout << \"Could not read AMD ShaderAnalyzer output\" << std::endl;\n      return {};\n   }\n\n   if (std::strncmp(assembly.c_str(), \"; --------  Disassembly --------------------\", strlen(\"; --------  Disassembly --------------------\"))) {\n      std::cout << \"Unexpected AMD ShaderAnalyzer output\" << std::endl;\n      return {};\n   }\n\n   return assembly;\n}\n\nstd::string\ncompileShader(std::string shaderAnalyzerPath,\n              std::string shaderPath,\n              ShaderType shaderType)\n{\n   EShLanguage language;\n   if (shaderType == ShaderType::VertexShader) {\n      language = EShLangVertex;\n   } else if (shaderType == ShaderType::PixelShader) {\n      language = EShLangFragment;\n   } else {\n      std::cout << \"Invalid shader type\" << std::endl;\n      return {};\n   }\n\n   auto shader = parseShader(language, shaderPath);\n   if (!shader) {\n      std::cout << \"Failed to parse shader \" << shaderPath << std::endl;\n      return {};\n   }\n\n   auto shaderHeader = parseGlslFileToHeader(*shader);\n   if (shaderHeader.empty()) {\n      std::cout << \"Failed to generate shader header for \" << shaderPath << std::endl;\n      return {};\n   }\n\n   auto shaderCode = runAmdShaderAnalyzer(shaderAnalyzerPath, shaderPath, shaderType);\n   if (shaderCode.empty()) {\n      std::cout << \"Failed to generate shader code for \" << shaderPath << std::endl;\n      return {};\n   }\n\n   return shaderHeader + shaderCode;\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/glsl_compiler.h",
    "content": "#pragma once\n#include \"shader.h\"\n#include <string>\n\nstd::string\ncompileShader(std::string shaderAnalyzerPath,\n              std::string shaderPath,\n              ShaderType shaderType);\n"
  },
  {
    "path": "tools/latte-assembler/src/main.cpp",
    "content": "#include \"shader_assembler.h\"\n#include \"gfd_comment_parser.h\"\n#include \"glsl_compiler.h\"\n\n#include <excmd.h>\n#include <fmt/core.h>\n#include <fstream>\n#include <glslang/Public/ShaderLang.h>\n#include <memory>\n#include <optional>\n#include <string>\n#include <spdlog/spdlog.h>\n\nstatic bool\nreadFile(const std::string &path,\n         std::string &buff)\n{\n   std::ifstream ifs { path, std::ios::in | std::ios::binary };\n   if (ifs.fail()) {\n      return false;\n   }\n\n   buff.resize(static_cast<unsigned int>(ifs.seekg(0, std::ios::end).tellg()));\n   if (!buff.empty()) {\n      ifs.seekg(0, std::ios::beg).read(&buff[0], static_cast<std::streamsize>(buff.size()));\n   }\n   return true;\n}\n\nstatic bool\nassembleFile(Shader &shader,\n             const std::string &path)\n{\n   std::string src;\n\n   if (!readFile(path, src)) {\n      return false;\n   }\n\n   return assembleShaderCode(shader, src);\n}\n\nint main(int argc, char **argv)\n{\n   excmd::parser parser;\n   excmd::option_state options;\n\n   // Setup command line options\n   parser.global_options()\n      .add_option(\"h,help\", excmd::description { \"Show the help.\" })\n      .add_option(\"vsh\",\n                  excmd::description { \"Vertex shader input.\" },\n                  excmd::value<std::string> {})\n      .add_option(\"psh\",\n                  excmd::description { \"Pixel shader input.\" },\n                  excmd::value<std::string> {})\n      .add_option(\"align\",\n                  excmd::description { \"Align data in gsh file.\" })\n      .add_option(\"amd-shader-analyzer\",\n                  excmd::description{ \"Path to AMD Shader Analyzer exe.\" },\n                  excmd::value<std::string> {});\n\n   parser.add_command(\"help\")\n      .add_argument(\"command\", excmd::value<std::string> { });\n\n   parser.add_command(\"assemble\")\n      .add_argument(\"gsh\", excmd::value<std::string> { });\n\n   parser.add_command(\"compile\")\n      .add_argument(\"gsh\", excmd::value<std::string> { });\n\n   // Parse command line\n   try {\n      options = parser.parse(argc, argv);\n   } catch (excmd::exception ex) {\n      std::cout << \"Error parsing command line: \" << ex.what() << std::endl;\n      return -1;\n   }\n\n   // Print help\n   if (argc == 1 || options.has(\"help\")) {\n      if (options.has(\"command\")) {\n         std::cout << parser.format_help(\"gfdtool\", options.get<std::string>(\"command\")) << std::endl;\n      } else {\n         std::cout << parser.format_help(\"gfdtool\") << std::endl;\n      }\n\n      return 0;\n   }\n\n\n   try {\n      if (options.has(\"compile\")) {\n         if (!options.has(\"amd-shader-analyzer\")) {\n            std::cout << \"Compile requires amd-shader-analyzer\" << std::endl;\n            return -1;\n         }\n\n         if (!options.has(\"vsh\") || !options.has(\"psh\")) {\n            std::cout << \"Compile requires vsh and psh\" << std::endl;\n            return -1;\n         }\n\n         auto vertexShaderPath = options.get<std::string>(\"vsh\");\n         auto pixelShaderPath = options.get<std::string>(\"psh\");\n         auto shaderAnalyzerPath = options.get<std::string>(\"amd-shader-analyzer\");\n         auto outGshPath = options.get<std::string>(\"gsh\");\n         glslang::InitializeProcess();\n\n         auto vertexShaderAssembly = compileShader(shaderAnalyzerPath, vertexShaderPath, ShaderType::VertexShader);\n         if (vertexShaderAssembly.empty()) {\n            std::cout << \"Failed to compile vertex shader \" << vertexShaderPath << std::endl;\n            return -1;\n         }\n\n         auto pixelShaderAssembly = compileShader(shaderAnalyzerPath, pixelShaderPath, ShaderType::PixelShader);\n         if (pixelShaderAssembly.empty()) {\n            std::cout << \"Failed to compile pixel shader \" << pixelShaderPath << std::endl;\n            return -1;\n         }\n\n         auto vertexShader = Shader { };\n         vertexShader.path = vertexShaderPath;\n         vertexShader.type = ShaderType::VertexShader;\n         if (!assembleShaderCode(vertexShader, vertexShaderAssembly)) {\n            std::cout << \"Failed to assemble vertex shader\" << std::endl;\n            return -1;\n         }\n\n         auto pixelShader = Shader { };\n         pixelShader.path = pixelShaderPath;\n         pixelShader.type = ShaderType::PixelShader;\n         if (!assembleShaderCode(pixelShader, pixelShaderAssembly)) {\n            std::cout << \"Failed to assemble pixel shader\" << std::endl;\n            return -1;\n         }\n\n         auto gfd = gfd::GFDFile { };\n         if (!gfdAddVertexShader(gfd, vertexShader)) {\n            std::cout << \"Failed to add vertex shader to gfd\" << std::endl;\n            return -1;\n         }\n\n         if (!gfdAddPixelShader(gfd, pixelShader)) {\n            std::cout << \"Failed to add pixel shader to gfd\" << std::endl;\n            return -1;\n         }\n\n         if (!gfd::writeFile(gfd, outGshPath, options.has(\"align\"))) {\n            std::cout << \"Failed to add write gfd\" << std::endl;\n            return -1;\n         }\n\n         glslang::FinalizeProcess();\n      } else if (options.has(\"assemble\")) {\n         auto dst = options.get<std::string>(\"gsh\");\n         gfd::GFDFile gfd;\n\n         if (options.has(\"vsh\")) {\n            auto src = options.get<std::string>(\"vsh\");\n            Shader shader;\n            shader.path = src;\n            shader.type = ShaderType::VertexShader;\n\n            if (!assembleFile(shader, src)) {\n               return -1;\n            }\n\n            if (!gfdAddVertexShader(gfd, shader)) {\n               return -1;\n            }\n         }\n\n         if (options.has(\"psh\")) {\n            auto src = options.get<std::string>(\"psh\");\n            Shader shader;\n            shader.path = src;\n            shader.type = ShaderType::PixelShader;\n\n            if (!assembleFile(shader, src)) {\n               return -1;\n            }\n\n            if (!gfdAddPixelShader(gfd, shader)) {\n               return -1;\n            }\n         }\n\n         if (!gfd::writeFile(gfd, dst, options.has(\"align\"))) {\n            return -1;\n         }\n      } else {\n         return -1;\n      }\n   } catch (parse_exception e) {\n      std::cout << \"Parse exception: \" << e.what() << std::endl;\n      return -1;\n   } catch (gfd_header_parse_exception e) {\n      std::cout << \"GFD header parse exception: \" << e.what() << std::endl;\n      return -1;\n   }\n\n   return 0;\n}\n"
  },
  {
    "path": "tools/latte-assembler/src/shader.h",
    "content": "#pragma once\n#include <array>\n#include <cstdint>\n#include <libgpu/latte/latte_instructions.h>\n#include <peglib.h>\n#include <vector>\n#include <string>\n\nenum class ShaderType\n{\n   Invalid,\n   PixelShader,\n   VertexShader,\n};\n\nstruct LiteralValue\n{\n   enum Flags\n   {\n      ReadHex = 1 << 0,\n      ReadFloat = 1 << 1,\n   };\n\n   unsigned flags;\n   uint32_t hexValue = 0;\n   float floatValue = 0.0f;\n};\n\nstruct AluGroup\n{\n   uint32_t clausePC = 0;\n   std::vector<latte::AluInst> insts;\n   std::vector<LiteralValue> literals;\n};\n\nstruct AluClause\n{\n   uint32_t cfPC = 0;\n   std::shared_ptr<peg::Ast> addrNode;\n   std::shared_ptr<peg::Ast> countNode;\n   std::vector<AluGroup> groups;\n};\n\nstruct TexClause\n{\n   uint32_t cfPC = 0;\n   uint32_t clausePC = 0;\n   std::shared_ptr<peg::Ast> addrNode;\n   std::shared_ptr<peg::Ast> countNode;\n   std::vector<latte::TextureFetchInst> insts;\n};\n\nstruct Shader\n{\n   Shader()\n   {\n      gprRead.fill(false);\n      gprWritten.fill(false);\n   }\n\n   ShaderType type = ShaderType::Invalid;\n   std::string path;\n\n   uint32_t clausePC = 0;\n   std::vector<latte::ControlFlowInst> cfInsts;\n\n   std::vector<AluClause> aluClauses;\n   uint32_t aluClauseBaseAddress;\n   std::vector<uint32_t> aluClauseData;\n\n   std::vector<TexClause> texClauses;\n   uint32_t texClauseBaseAddress;\n   std::vector<uint32_t> texClauseData;\n\n   std::vector<std::string> comments;\n\n   bool uniformBlocksUsed = false;\n   bool uniformRegistersUsed = false;\n\n   std::array<bool, 128> gprRead;\n   std::array<bool, 128> gprWritten;\n\n   unsigned long maxGPR = 0;\n   unsigned long maxStack = 0;\n   unsigned long maxPixelExport = 0;\n   unsigned long maxParamExport = 0;\n   unsigned long maxPosExport = 0;\n};\n"
  },
  {
    "path": "tools/latte-assembler/src/shader_assembler.h",
    "content": "#pragma once\n#include \"shader.h\"\n\n#include <fmt/core.h>\n#include <peglib.h>\n#include <stdexcept>\n#include <string>\n#include <string_view>\n#include <vector>\n\nclass parse_exception : public std::runtime_error\n{\npublic:\n   parse_exception(const std::string &m) :\n      std::runtime_error { m }\n   {\n   }\n};\n\nclass node_parse_exception : public parse_exception\n{\npublic:\n   node_parse_exception(peg::Ast &node, const std::string &m) :\n      parse_exception { fmt::format(\"{}:{} {}\", node.line, node.column, m) }\n   {\n   }\n};\n\nclass unhandled_node_exception : public node_parse_exception\n{\npublic:\n   unhandled_node_exception(peg::Ast &node) :\n      node_parse_exception { node, fmt::format(\"Unxpected node {}\", node.name) }\n   {\n   }\n};\n\nclass invalid_inst_exception : public node_parse_exception\n{\npublic:\n   invalid_inst_exception(peg::Ast &node, const std::string &instType) :\n      node_parse_exception { node, fmt::format(\"Invalid {} instruction {}\", instType, node.token) }\n   {\n   }\n};\n\nclass invalid_alu_op2_inst_exception : public invalid_inst_exception\n{\npublic:\n   invalid_alu_op2_inst_exception(peg::Ast &node) :\n      invalid_inst_exception { node, \"ALU OP2\" }\n   {\n   }\n};\n\nclass invalid_alu_op3_inst_exception : public invalid_inst_exception\n{\npublic:\n   invalid_alu_op3_inst_exception(peg::Ast &node) :\n      invalid_inst_exception { node, \"ALU OP3\" }\n   {\n   }\n};\n\nclass invalid_cf_inst_exception : public invalid_inst_exception\n{\npublic:\n   invalid_cf_inst_exception(peg::Ast &node) :\n      invalid_inst_exception { node, \"CF\" }\n   {\n   }\n};\n\nclass invalid_cf_alu_inst_exception : public invalid_inst_exception\n{\npublic:\n   invalid_cf_alu_inst_exception(peg::Ast &node) :\n      invalid_inst_exception { node, \"CF ALU\" }\n   {\n   }\n};\n\nclass invalid_cf_tex_inst_exception : public invalid_inst_exception\n{\npublic:\n   invalid_cf_tex_inst_exception(peg::Ast &node) :\n      invalid_inst_exception { node, \"CF TEX\" }\n   {\n   }\n};\n\nclass invalid_exp_inst_exception : public invalid_inst_exception\n{\npublic:\n   invalid_exp_inst_exception(peg::Ast &node) :\n      invalid_inst_exception { node, \"EXP\" }\n   {\n   }\n};\n\nclass invalid_tex_inst_exception : public invalid_inst_exception\n{\npublic:\n   invalid_tex_inst_exception(peg::Ast &node) :\n      invalid_inst_exception { node, \"TEX\" }\n   {\n   }\n};\n\nclass invalid_inst_property_exception : public node_parse_exception\n{\npublic:\n   invalid_inst_property_exception(peg::Ast &node, const std::string &instType) :\n      node_parse_exception { node, fmt::format(\"Invalid property {} for {} instruction\", node.name, instType) }\n   {\n   }\n};\n\nclass invalid_alu_property_exception : public invalid_inst_property_exception\n{\npublic:\n   invalid_alu_property_exception(peg::Ast &node) :\n      invalid_inst_property_exception { node, \"ALU\" }\n   {\n   }\n};\n\nclass invalid_cf_property_exception : public invalid_inst_property_exception\n{\npublic:\n   invalid_cf_property_exception(peg::Ast &node) :\n      invalid_inst_property_exception { node, \"CF\" }\n   {\n   }\n};\n\nclass invalid_cf_alu_property_exception : public invalid_inst_property_exception\n{\npublic:\n   invalid_cf_alu_property_exception(peg::Ast &node) :\n      invalid_inst_property_exception { node, \"CF ALU\" }\n   {\n   }\n};\n\nclass invalid_cf_tex_property_exception : public invalid_inst_property_exception\n{\npublic:\n   invalid_cf_tex_property_exception(peg::Ast &node) :\n      invalid_inst_property_exception { node, \"CF TEX\" }\n   {\n   }\n};\n\nclass invalid_exp_property_exception : public invalid_inst_property_exception\n{\npublic:\n   invalid_exp_property_exception(peg::Ast &node) :\n      invalid_inst_property_exception { node, \"EXP\" }\n   {\n   }\n};\n\nclass invalid_tex_property_exception : public invalid_inst_property_exception\n{\npublic:\n   invalid_tex_property_exception(peg::Ast &node) :\n      invalid_inst_property_exception { node, \"TEX\" }\n   {\n   }\n};\n\nclass incorrect_cf_pc_exception : public node_parse_exception\n{\npublic:\n   incorrect_cf_pc_exception(peg::Ast &node, size_t found, size_t expected) :\n      node_parse_exception { node, fmt::format(\"Incorrect CF PC {}, expected {}\", found, expected) }\n   {\n   }\n};\n\nclass incorrect_clause_pc_exception : public node_parse_exception\n{\npublic:\n   incorrect_clause_pc_exception(peg::Ast &node, size_t found, size_t expected) :\n      node_parse_exception { node, fmt::format(\"Incorrect clause PC {}, expected {}\", found, expected) }\n   {\n   }\n};\n\nclass incorrect_clause_addr_exception : public node_parse_exception\n{\npublic:\n   incorrect_clause_addr_exception(peg::Ast &node, size_t found, size_t expected) :\n      node_parse_exception { node, fmt::format(\"Incorrect clause addr {}, expected {}\", found, expected) }\n   {\n   }\n};\n\nclass incorrect_clause_count_exception : public node_parse_exception\n{\npublic:\n   incorrect_clause_count_exception(peg::Ast &node, size_t found, size_t expected) :\n      node_parse_exception { node, fmt::format(\"Incorrect clause count {}, expected {}\", found, expected) }\n   {\n   }\n};\n\n// assembler_parse\nbool\nassembleShaderCode(Shader &shader,\n                   std::string_view code);\n\n// assembler_cf\nvoid\nassembleAST(Shader &shader,\n           std::shared_ptr<peg::Ast> ast);\n\n// assembler_alu\nvoid\nassembleAluClause(Shader &shader,\n                  peg::Ast &node);\n\n// assembler_exp\nvoid\nassembleExpInst(Shader &shader,\n                peg::Ast &node);\n\n// assembler_tex\nvoid\nassembleTexClause(Shader &shader,\n                  peg::Ast &node);\n\n// assembler_latte\nlatte::SQ_ALU_VEC_BANK_SWIZZLE\nparseAluBankSwizzle(peg::Ast &node);\n\nlatte::SQ_INDEX_MODE\nparseAluDstRelIndexMode(peg::Ast &node);\n\nlatte::SQ_CF_COND\nparseCfCond(peg::Ast &node);\n\nlatte::SQ_CHAN\nparseChan(peg::Ast &node);\n\nsize_t\nparseFourCompSwizzle(peg::Ast &node,\n                     latte::SQ_SEL &selX,\n                     latte::SQ_SEL &selY,\n                     latte::SQ_SEL &selZ,\n                     latte::SQ_SEL &selW);\n\nlatte::SQ_ALU_OMOD\nparseOutputModifier(peg::Ast &node);\n\nlatte::SQ_PRED_SEL\nparsePredSel(peg::Ast &node);\n\nlatte::SQ_SEL\nparseSel(peg::Ast &node,\n         unsigned index);\n\n// assembler_common\nfloat\nparseFloat(peg::Ast &node);\n\nuint32_t\nparseHexNumber(peg::Ast &node);\n\nunsigned long\nparseNumber(peg::Ast &node);\n\nLiteralValue\nparseLiteral(peg::Ast &node);\n\nvoid\nmarkGprRead(Shader &shader,\n            uint32_t gpr);\n\nvoid\nmarkSrcRead(Shader &shader,\n            latte::SQ_ALU_SRC src);\n\nvoid\nmarkGprWritten(Shader &shader,\n               uint32_t gpr);\n"
  },
  {
    "path": "tools/pm4-replay/CMakeLists.txt",
    "content": "project(pm4-replay)\n\ninclude_directories(\".\")\ninclude_directories(\"../../src/libdecaf/src\")\ninclude_directories(\"../../src/libgpu\")\ninclude_directories(\"../../src/libgpu/src\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\n\nadd_executable(pm4-replay ${SOURCE_FILES} ${HEADER_FILES})\nset_target_properties(pm4-replay PROPERTIES FOLDER tools)\n\ntarget_include_directories(pm4-replay PRIVATE\n    ${SDL2_INCLUDE_DIRS})\ntarget_link_libraries(pm4-replay\n    common\n    libconfig\n    libdecaf\n    excmd\n    ${SDL2_LIBRARIES})\n\nif(MSVC)\n   target_link_libraries(pm4-replay\n      Setupapi)\nendif()\n\nif(${CMAKE_SYSTEM_NAME} MATCHES \"Linux\")\n    target_link_libraries(pm4-replay X11)\nendif()\n\ninstall(TARGETS pm4-replay RUNTIME DESTINATION \"${DECAF_INSTALL_BINDIR}\")\n"
  },
  {
    "path": "tools/pm4-replay/clilog.h",
    "content": "#pragma once\n#include <spdlog/spdlog.h>\n\nextern std::shared_ptr<spdlog::logger>\ngCliLog;\n"
  },
  {
    "path": "tools/pm4-replay/config.h",
    "content": "#pragma once\n#include <string>\n\nnamespace config\n{\n\nextern bool dump_drc_frames;\nextern bool dump_tv_frames;\nextern std::string dump_frames_dir;\n\n} // namespace config\n"
  },
  {
    "path": "tools/pm4-replay/main.cpp",
    "content": "#include \"config.h\"\n#include \"sdl_window.h\"\n\n#include <common/log.h>\n#include <excmd.h>\n#include <iostream>\n#include <libcpu/cpu.h>\n#include <libcpu/mem.h>\n#include <libdecaf/decaf.h>\n#include <libdecaf/decaf_config.h>\n#include <libdecaf/decaf_log.h>\n#include <libgpu/gpu_config.h>\n#include <spdlog/spdlog.h>\n\nnamespace config\n{\n\nbool dump_drc_frames = false;\nbool dump_tv_frames = false;\nstd::string dump_frames_dir = \"frames\";\nstd::string renderer = \"vulkan\";\n\n} // namespace config\n\n\nstd::shared_ptr<spdlog::logger>\ngCliLog;\n\nstatic excmd::parser\ngetCommandLineParser()\n{\n   excmd::parser parser;\n   using excmd::description;\n   using excmd::optional;\n   using excmd::default_value;\n   using excmd::allowed;\n   using excmd::value;\n   using excmd::make_default_value;\n\n   parser.global_options()\n      .add_option(\"v,version\",\n                  description { \"Show version.\" })\n      .add_option(\"h,help\",\n                  description { \"Show help.\" });\n\n   auto replayOptions = parser.add_option_group(\"Replay Options\")\n      .add_option(\"dump-drc-frames\",\n                  description { \"Dump rendered DRC frames to file.\" })\n      .add_option(\"dump-tv-frames\",\n                  description { \"Dump rendered TV frames to file.\" })\n      .add_option(\"dump-frames-dir\",\n                  description { \"Folder to place dumped frames in\" },\n                  make_default_value(config::dump_frames_dir))\n      .add_option(\"renderer\",\n                  description { \"Which graphics renderer to use.\" },\n                  make_default_value(config::renderer));\n\n   parser.add_command(\"help\")\n      .add_argument(\"help-command\",\n                    optional {},\n                    value<std::string> {});\n\n   parser.add_command(\"replay\")\n      .add_argument(\"trace file\",\n                    value<std::string> {})\n      .add_option_group(replayOptions);\n\n   return parser;\n}\n\nstatic int\nreplay(const std::string &path)\n{\n   SDLWindow sdl;\n\n   if (!sdl.initCore()) {\n      gCliLog->error(\"Failed to initialise SDL\");\n      return -1;\n   }\n\n   if (!sdl.initGraphics()) {\n      gCliLog->error(\"Failed to initialise graphics backend.\");\n      return -1;\n   }\n\n   return sdl.run(path);\n}\n\nint\nstart(excmd::parser &parser,\n      excmd::option_state &options)\n{\n   // Print version\n   if (options.has(\"version\")) {\n      // TODO: print git hash\n      std::cout << \"Decaf PM4 Replay tool version 0.0.1\" << std::endl;\n      std::exit(0);\n   }\n\n   // Print help\n   if (options.empty() || options.has(\"help\")) {\n      if (options.has(\"help-command\")) {\n         std::cout << parser.format_help(\"decaf-pm4-replay\", options.get<std::string>(\"help-command\")) << std::endl;\n      } else {\n         std::cout << parser.format_help(\"decaf-pm4-replay\") << std::endl;\n      }\n\n      std::exit(0);\n   }\n\n   if (!options.has(\"replay\")) {\n      return 0;\n   }\n\n   if (options.has(\"dump-drc-frames\")) {\n      config::dump_drc_frames = true;\n   }\n\n   if (options.has(\"dump-tv-frames\")) {\n      config::dump_tv_frames = true;\n   }\n\n   if (options.has(\"dump-frames-dir\")) {\n      config::dump_frames_dir = options.get<std::string>(\"dump-frames-dir\");\n   }\n\n   if (options.has(\"renderer\")) {\n      config::renderer = options.get<std::string>(\"renderer\");\n   }\n\n   auto traceFile = options.get<std::string>(\"trace file\");\n\n   // Initialise libdecaf logger\n   auto decafSettings = decaf::Settings { };\n   decafSettings.log.to_file = true;\n   decafSettings.log.to_stdout = true;\n   decafSettings.log.level = \"debug\";\n   decaf::setConfig(decafSettings);\n   decaf::initialiseLogging(\"pm4-replay.txt\");\n\n   auto gpuSettings = gpu::Settings { };\n   gpuSettings.debug.debug_enabled = true;\n   gpu::setConfig(gpuSettings);\n\n   gCliLog = decaf::makeLogger(\"decaf-pm4-replay\");\n   gCliLog->set_pattern(\"[%l] %v\");\n   gCliLog->info(\"Trace path {}\", traceFile);\n\n   // Initialise CPU to setup physical memory\n   cpu::initialise();\n\n   return replay(traceFile);\n}\n\nint main(int argc, char **argv)\n{\n   auto parser = getCommandLineParser();\n   excmd::option_state options;\n\n   try {\n      options = parser.parse(argc, argv);\n   } catch (excmd::exception ex) {\n      std::cout << \"Error parsing options: \" << ex.what() << std::endl;\n      std::exit(-1);\n   }\n\n   return start(parser, options);\n}\n"
  },
  {
    "path": "tools/pm4-replay/replay_parser.h",
    "content": "#pragma once\n#include <string_view>\n\nclass ReplayParser\n{\npublic:\n   virtual ~ReplayParser() = default;\n\n   virtual bool runUntilTimestamp(uint64_t timestamp) = 0;\n};\n"
  },
  {
    "path": "tools/pm4-replay/replay_parser_pm4.cpp",
    "content": "#include \"replay_parser_pm4.h\"\n\n#include <libdecaf/src/cafe/cafe_tinyheap.h>\n#include <libgpu/latte/latte_pm4_commands.h>\n\nusing namespace latte::pm4;\n\nstd::unique_ptr<ReplayParser>\nReplayParserPM4::Create(gpu::GraphicsDriver *driver,\n                        RingBuffer *ringBuffer,\n                        phys_ptr<cafe::TinyHeapPhysical> heap,\n                        const std::string &path)\n{\n   // Try open file\n   std::ifstream file;\n   file.open(path, std::ifstream::binary);\n   if (!file.is_open()) {\n      return {};\n   }\n\n   // Check magic header\n   std::array<char, 4> magic;\n   file.read(magic.data(), 4);\n   if (magic != decaf::pm4::CaptureMagic) {\n      return {};\n   }\n\n   // Allocate register storage\n   auto allocPtr = phys_ptr<void> { nullptr };\n   cafe::TinyHeap_Alloc(heap, 0x10000 * 4, 0x100, &allocPtr);\n   if (!allocPtr) {\n      return { };\n   }\n\n   auto self = new ReplayParserPM4 { };\n   self->mGraphicsDriver = driver;\n   self->mRingBuffer = ringBuffer;\n   self->mHeap = heap;\n   self->mRegisterStorage = phys_cast<uint32_t *>(allocPtr);\n   self->mFile = std::move(file);\n\n   return std::unique_ptr<ReplayParser> { self };\n}\n\nbool\nReplayParserPM4::runUntilTimestamp(uint64_t timestamp)\n{\n   std::vector<char> buffer;\n   bool reachedTimestamp = false;\n   mFile.clear();\n   mFile.seekg(4, std::fstream::beg);\n\n   while (true) {\n      decaf::pm4::CapturePacket packet;\n      mFile.read(reinterpret_cast<char *>(&packet), sizeof(decaf::pm4::CapturePacket));\n      if (!mFile) {\n         return false;\n      }\n\n      switch (packet.type) {\n      case decaf::pm4::CapturePacket::CommandBuffer:\n      {\n         buffer.resize(packet.size);\n         mFile.read(buffer.data(), buffer.size());\n\n         if (!mFile) {\n            return false;\n         }\n\n         handleCommandBuffer(buffer.data(), packet.size);\n         break;\n      }\n      case decaf::pm4::CapturePacket::RegisterSnapshot:\n      {\n         decaf_check((packet.size % 4) == 0);\n         auto numRegisters = packet.size / 4;\n         mFile.read(reinterpret_cast<char *>(mRegisterStorage.getRawPointer()), packet.size);\n\n         // Swap it into big endian, so we can write LOAD_ commands\n         for (auto i = 0u; i < numRegisters; ++i) {\n            mRegisterStorage[i] = byte_swap(mRegisterStorage[i]);\n         }\n\n         handleRegisterSnapshot(mRegisterStorage, numRegisters);\n         mRingBuffer->waitTimestamp(mRingBuffer->flushCommandBuffer());\n         break;\n      }\n      case decaf::pm4::CapturePacket::SetBuffer:\n      {\n         decaf::pm4::CaptureSetBuffer setBuffer;\n         mFile.read(reinterpret_cast<char *>(&setBuffer), sizeof(decaf::pm4::CaptureSetBuffer));\n\n         handleSetBuffer(setBuffer);\n         mRingBuffer->waitTimestamp(mRingBuffer->flushCommandBuffer());\n         break;\n      }\n      case decaf::pm4::CapturePacket::MemoryLoad:\n      {\n         decaf::pm4::CaptureMemoryLoad load;\n         mFile.read(reinterpret_cast<char *>(&load), sizeof(decaf::pm4::CaptureMemoryLoad));\n\n         if (!mFile) {\n            return false;\n         }\n\n         buffer.resize(packet.size - sizeof(decaf::pm4::CaptureMemoryLoad));\n         mFile.read(buffer.data(), buffer.size());\n\n         if (!mFile) {\n            return false;\n         }\n\n         handleMemoryLoad(load, buffer);\n         break;\n      }\n      default:\n         mFile.seekg(packet.size, std::ifstream::cur);\n      }\n\n      if (packet.timestamp >= timestamp) {\n         reachedTimestamp = true;\n         break;\n      }\n   }\n\n   // Flush ringbuffer to gpu\n   mRingBuffer->waitTimestamp(mRingBuffer->flushCommandBuffer());\n   return reachedTimestamp;\n}\n\nbool\nReplayParserPM4::handleCommandBuffer(void *buffer, uint32_t sizeBytes)\n{\n   auto numWords = sizeBytes / 4;\n   mRingBuffer->writeBuffer(buffer, numWords);\n   return scanCommandBuffer(buffer, numWords);\n}\n\nvoid\nReplayParserPM4::handleSetBuffer(decaf::pm4::CaptureSetBuffer &setBuffer)\n{\n   auto isTv = (setBuffer.type == decaf::pm4::CaptureSetBuffer::TvBuffer) ? 1u : 0u;\n\n   mRingBuffer->writePM4(DecafSetBuffer {\n      isTv ? latte::pm4::ScanTarget::TV : latte::pm4::ScanTarget::DRC,\n      setBuffer.address,\n      setBuffer.bufferingMode,\n      setBuffer.width,\n      setBuffer.height\n   });\n}\n\nvoid\nReplayParserPM4::handleRegisterSnapshot(phys_ptr<uint32_t> registers, uint32_t count)\n{\n   // Enable loading of registers\n   auto LOAD_CONTROL = latte::CONTEXT_CONTROL_ENABLE::get(0)\n      .ENABLE_CONFIG_REG(true)\n      .ENABLE_CONTEXT_REG(true)\n      .ENABLE_ALU_CONST(true)\n      .ENABLE_BOOL_CONST(true)\n      .ENABLE_LOOP_CONST(true)\n      .ENABLE_RESOURCE(true)\n      .ENABLE_SAMPLER(true)\n      .ENABLE_CTL_CONST(true)\n      .ENABLE_ORDINAL(true);\n\n   auto SHADOW_ENABLE = latte::CONTEXT_CONTROL_ENABLE::get(0);\n\n   mRingBuffer->writePM4(ContextControl {\n      LOAD_CONTROL,\n      SHADOW_ENABLE\n   });\n\n   // Write all the register load packets!\n   static std::pair<uint32_t, uint32_t>\n   LoadConfigRange[] = { { 0, (latte::Register::ConfigRegisterEnd - latte::Register::ConfigRegisterBase) / 4 }, };\n\n   mRingBuffer->writePM4(LoadConfigReg {\n      phys_cast<phys_addr>(registers + (latte::Register::ConfigRegisterBase / 4)),\n      gsl::make_span(LoadConfigRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadContextRange[] = { { 0, (latte::Register::ContextRegisterEnd - latte::Register::ContextRegisterBase) / 4 }, };\n\n   mRingBuffer->writePM4(LoadContextReg {\n      phys_cast<phys_addr>(registers + (latte::Register::ContextRegisterBase / 4)),\n      gsl::make_span(LoadContextRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadAluConstRange[] = { { 0, (latte::Register::AluConstRegisterEnd - latte::Register::AluConstRegisterBase) / 4 }, };\n\n   mRingBuffer->writePM4(LoadAluConst {\n      phys_cast<phys_addr>(registers + (latte::Register::AluConstRegisterBase / 4)),\n      gsl::make_span(LoadAluConstRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadResourceRange[] = { { 0, (latte::Register::ResourceRegisterEnd - latte::Register::ResourceRegisterBase) / 4 }, };\n\n   mRingBuffer->writePM4(latte::pm4::LoadResource {\n      phys_cast<phys_addr>(registers + (latte::Register::ResourceRegisterBase / 4)),\n      gsl::make_span(LoadResourceRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadSamplerRange[] = { { 0, (latte::Register::SamplerRegisterEnd - latte::Register::SamplerRegisterBase) / 4 }, };\n\n   mRingBuffer->writePM4(LoadSampler {\n      phys_cast<phys_addr>(registers + (latte::Register::SamplerRegisterBase / 4)),\n      gsl::make_span(LoadSamplerRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadControlRange[] = { { 0, (latte::Register::ControlRegisterEnd - latte::Register::ControlRegisterBase) / 4 }, };\n\n   mRingBuffer->writePM4(LoadControlConst {\n      phys_cast<phys_addr>(registers + (latte::Register::ControlRegisterBase / 4)),\n      gsl::make_span(LoadControlRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadLoopRange[] = { { 0, (latte::Register::LoopConstRegisterEnd - latte::Register::LoopConstRegisterBase) / 4 }, };\n\n   mRingBuffer->writePM4(LoadLoopConst {\n      phys_cast<phys_addr>(registers + (latte::Register::LoopConstRegisterBase / 4)),\n      gsl::make_span(LoadLoopRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadBoolRange[] = { { 0, (latte::Register::BoolConstRegisterEnd - latte::Register::BoolConstRegisterBase) / 4 }, };\n\n   mRingBuffer->writePM4(LoadLoopConst {\n      phys_cast<phys_addr>(registers + (latte::Register::BoolConstRegisterBase / 4)),\n      gsl::make_span(LoadBoolRange)\n   });\n}\n\nvoid\nReplayParserPM4::handleMemoryLoad(decaf::pm4::CaptureMemoryLoad &load,\n                                  std::vector<char> &data)\n{\n   std::memcpy(phys_cast<void *>(load.address).getRawPointer(),\n               data.data(), data.size());\n\n   mGraphicsDriver->notifyCpuFlush(load.address,\n                                    static_cast<uint32_t>(data.size()));\n}\n\nbool\nReplayParserPM4::scanType0(HeaderType0 header,\n                           const gsl::span<be2_val<uint32_t>> &data)\n{\n   return false;\n}\n\nbool\nReplayParserPM4::scanType3(HeaderType3 header,\n                           const gsl::span<be2_val<uint32_t>> &data)\n{\n   if (header.opcode() == IT_OPCODE::DECAF_SWAP_BUFFERS) {\n      return true;\n   }\n\n   if (header.opcode() == IT_OPCODE::INDIRECT_BUFFER ||\n         header.opcode() == IT_OPCODE::INDIRECT_BUFFER_PRIV) {\n      return scanCommandBuffer(phys_cast<void *>(phys_addr { data[0].value() }).getRawPointer(),\n                                 data[2]);\n   }\n\n   return false;\n}\n\nbool\nReplayParserPM4::scanCommandBuffer(void *words, uint32_t numWords)\n{\n   auto buffer = reinterpret_cast<be2_val<uint32_t> *>(words);\n   auto foundSwap = false;\n\n   for (auto pos = size_t { 0u }; pos < numWords; ) {\n      auto header = Header::get(buffer[pos]);\n      auto size = size_t { 0u };\n\n      switch (header.type()) {\n      case PacketType::Type0:\n      {\n         auto header0 = HeaderType0::get(header.value);\n         size = header0.count() + 1;\n\n         decaf_check(pos + size < numWords);\n         foundSwap |= scanType0(header0, gsl::make_span(buffer + pos + 1, size));\n         break;\n      }\n      case PacketType::Type3:\n      {\n         auto header3 = HeaderType3::get(header.value);\n         size = header3.size() + 1;\n\n         decaf_check(pos + size < numWords);\n         foundSwap |= scanType3(header3, gsl::make_span(buffer + pos + 1, size));\n         break;\n      }\n      case PacketType::Type2:\n      {\n         // This is a filler packet, like a \"nop\", ignore it\n         break;\n      }\n      case PacketType::Type1:\n      default:\n         size = numWords;\n         break;\n      }\n\n      pos += size + 1;\n   }\n\n   return foundSwap;\n}\n"
  },
  {
    "path": "tools/pm4-replay/replay_parser_pm4.h",
    "content": "#pragma once\n#include \"replay_parser.h\"\n#include \"replay_ringbuffer.h\"\n\n#include <libcpu/be2_struct.h>\n#include <libdecaf/decaf_pm4replay.h>\n#include <libdecaf/src/cafe/cafe_tinyheap.h>\n#include <libgpu/gpu_graphicsdriver.h>\n#include <libgpu/latte/latte_pm4.h>\n\n#include <cstdint>\n#include <fstream>\n#include <memory>\n#include <string>\n\nclass ReplayParserPM4 : public ReplayParser\n{\n   ReplayParserPM4() = default;\n   ReplayParserPM4(const ReplayParserPM4 &) = delete;\n   ReplayParserPM4(ReplayParserPM4 &&) = delete;\n\n   ReplayParserPM4 &operator=(const ReplayParserPM4 &) = delete;\n   ReplayParserPM4 &operator=(ReplayParserPM4 &&) = delete;\n\npublic:\n   virtual ~ReplayParserPM4() = default;\n   bool runUntilTimestamp(uint64_t timestamp) override;\n\n   static std::unique_ptr<ReplayParser>\n   Create(gpu::GraphicsDriver *driver,\n          RingBuffer *ringBuffer,\n          phys_ptr<cafe::TinyHeapPhysical> heap,\n          const std::string &path);\n\nprivate:\n   bool handleCommandBuffer(void *buffer, uint32_t sizeBytes);\n   void handleSetBuffer(decaf::pm4::CaptureSetBuffer &setBuffer);\n   void handleRegisterSnapshot(phys_ptr<uint32_t> registers, uint32_t count);\n   void handleMemoryLoad(decaf::pm4::CaptureMemoryLoad &load, std::vector<char> &data);\n   bool scanType0(latte::pm4::HeaderType0 header, const gsl::span<be2_val<uint32_t>> &data);\n   bool scanType3(latte::pm4::HeaderType3 header, const gsl::span<be2_val<uint32_t>> &data);\n   bool scanCommandBuffer(void *words, uint32_t numWords);\n\nprivate:\n   gpu::GraphicsDriver *mGraphicsDriver = nullptr;\n   RingBuffer *mRingBuffer = nullptr;\n   std::ifstream mFile;\n\n   phys_ptr<cafe::TinyHeapPhysical> mHeap = nullptr;\n   phys_ptr<uint32_t> mRegisterStorage = nullptr;\n};\n"
  },
  {
    "path": "tools/pm4-replay/replay_ringbuffer.h",
    "content": "#pragma once\n#include <libdecaf/src/cafe/cafe_tinyheap.h>\n#include <libgpu/gpu_ringbuffer.h>\n#include <libgpu/gpu_ih.h>\n#include <libgpu/latte/latte_pm4_commands.h>\n#include <libgpu/latte/latte_pm4_sizer.h>\n#include <libgpu/latte/latte_pm4_writer.h>\n\n#include <atomic>\n#include <cstring>\n#include <mutex>\n#include <condition_variable>\n\nclass RingBuffer\n{\n   static constexpr auto BufferSize = 0x1000000u;\n\npublic:\n   RingBuffer(phys_ptr<cafe::TinyHeapPhysical> replayHeap) :\n      mReplayHeap(replayHeap)\n   {\n      auto allocPtr = phys_ptr<void> { nullptr };\n\n      cafe::TinyHeap_Alloc(replayHeap, 8, 0x100, &allocPtr);\n      mRetireTimestampMemory = phys_cast<uint64_t *>(allocPtr);\n      *mRetireTimestampMemory = 0ull;\n      mRetireTimestamp = 0ull;\n      mSubmitTimestamp = 0ull;\n   }\n\n   ~RingBuffer()\n   {\n      if (mRetireTimestampMemory) {\n         cafe::TinyHeap_Free(mReplayHeap, mRetireTimestampMemory);\n      }\n   }\n\n   uint64_t\n   insertRetiredTimestamp()\n   {\n      auto submitTimestamp = ++mSubmitTimestamp;\n      auto eventType = latte::VGT_EVENT_TYPE::CACHE_FLUSH_TS;\n      writePM4(\n         latte::pm4::EventWriteEOP {\n            latte::VGT_EVENT_INITIATOR::get(0)\n               .EVENT_TYPE(eventType)\n               .EVENT_INDEX(latte::VGT_EVENT_INDEX::TS),\n            latte::pm4::EW_ADDR_LO::get(0)\n               .ADDR_LO(static_cast<uint32_t>(phys_cast<phys_addr>(mRetireTimestampMemory)) >> 2)\n               .ENDIAN_SWAP(latte::CB_ENDIAN::SWAP_8IN64),\n            latte::pm4::EWP_ADDR_HI::get(0)\n               .DATA_SEL(latte::pm4::EWP_DATA_64)\n               .INT_SEL(latte::pm4::EWP_INT_WRITE_CONFIRM),\n            static_cast<uint32_t>(submitTimestamp & 0xFFFFFFFF),\n            static_cast<uint32_t>(submitTimestamp >> 32)\n         });\n      return submitTimestamp;\n   }\n\n   uint64_t\n   flushCommandBuffer()\n   {\n      auto timestamp = insertRetiredTimestamp();\n      gpu::ringbuffer::write(mBuffer);\n      mBuffer.clear();\n      return timestamp;\n   }\n\n   void\n   waitTimestamp(uint64_t timestamp)\n   {\n      std::unique_lock<std::mutex> lock{ mRetireMutex };\n      mRetireCV.wait(lock, [this]() {\n         return mRetireTimestamp >= mSubmitTimestamp.load();\n      });\n   }\n\n   void\n   onGpuInterrupt()\n   {\n      std::unique_lock<std::mutex> lock { mRetireMutex };\n      mRetireTimestamp = *mRetireTimestampMemory;\n      mRetireCV.notify_all();\n   }\n\n   template<typename Type>\n   void\n   writePM4(const Type &value)\n   {\n      auto &ncValue = const_cast<Type &>(value);\n\n      // Calculate the total size this object will be\n      latte::pm4::PacketSizer sizer;\n      ncValue.serialise(sizer);\n      auto totalSize = sizer.getSize() + 1;\n\n      // Resize buffer\n      auto writePosition = static_cast<uint32_t>(mBuffer.size());\n      mBuffer.resize(mBuffer.size() + totalSize);\n\n      // Serialize the packet to the active command buffer\n      auto writer = latte::pm4::PacketWriter {\n         mBuffer.data(),\n         writePosition,\n         Type::Opcode,\n         totalSize\n      };\n      ncValue.serialise(writer);\n      decaf_check(writePosition == mBuffer.size());\n   }\n\n   void\n   writeBuffer(const void *buffer, uint32_t numWords)\n   {\n      auto offset = mBuffer.size();\n      mBuffer.resize(mBuffer.size() + numWords);\n      std::memcpy(mBuffer.data() + offset, buffer, numWords * 4);\n   }\n\nprivate:\n   std::vector<uint32_t> mBuffer;\n\n   phys_ptr<cafe::TinyHeapPhysical> mReplayHeap = nullptr;\n   phys_ptr<uint64_t> mRetireTimestampMemory = nullptr;\n\n   std::atomic<uint64_t> mSubmitTimestamp = 0ull;\n   std::atomic<uint64_t> mRetireTimestamp = 0ull;\n\n   std::mutex mRetireMutex;\n   std::condition_variable mRetireCV;\n};\n"
  },
  {
    "path": "tools/pm4-replay/sdl_window.cpp",
    "content": "#include \"sdl_window.h\"\n#include \"clilog.h\"\n#include \"config.h\"\n\n#include \"replay_ringbuffer.h\"\n#include \"replay_parser_pm4.h\"\n\n#include <array>\n#include <atomic>\n#include <fstream>\n#include <common/log.h>\n#include <common/platform_dir.h>\n#include <future>\n#include <libgpu/gpu_config.h>\n#include <SDL_syswm.h>\n\nusing namespace latte::pm4;\n\nRingBuffer *sRingBuffer = nullptr; // eww is global because of onGpuInterrupt\n\nvoid initialiseRegisters(RingBuffer *ringBuffer);\n\nSDLWindow::~SDLWindow()\n{\n}\n\nbool\nSDLWindow::initCore()\n{\n   if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0) {\n      gCliLog->error(\"Failed to initialize SDL: {}\", SDL_GetError());\n      return false;\n   }\n\n   return true;\n}\n\nbool\nSDLWindow::initGraphics()\n{\n   auto videoInitialised = false;\n\n#ifdef SDL_VIDEO_DRIVER_X11\n   if (!videoInitialised) {\n      videoInitialised = SDL_VideoInit(\"x11\") == 0;\n      if (!videoInitialised) {\n         gCliLog->error(\"Failed to initialize SDL Video with x11: {}\", SDL_GetError());\n      }\n   }\n#endif\n\n#ifdef SDL_VIDEO_DRIVER_WAYLAND\n   if (!videoInitialised) {\n      videoInitialised = SDL_VideoInit(\"wayland\") == 0;\n      if (!videoInitialised) {\n         gCliLog->error(\"Failed to initialize SDL Video with wayland: {}\", SDL_GetError());\n      }\n   }\n#endif\n\n   if (!videoInitialised) {\n      if (SDL_VideoInit(NULL) != 0) {\n         gCliLog->error(\"Failed to initialize SDL Video: {}\", SDL_GetError());\n         return false;\n      }\n   }\n\n   gCliLog->info(\"Using SDL video driver {}\", SDL_GetCurrentVideoDriver());\n\n   mGraphicsDriver = gpu::createGraphicsDriver();\n   if (!mGraphicsDriver) {\n      return false;\n   }\n\n   switch (mGraphicsDriver->type()) {\n   case gpu::GraphicsDriverType::Vulkan:\n      mRendererName = \"Vulkan\";\n      break;\n   case gpu::GraphicsDriverType::Null:\n      mRendererName = \"Null\";\n      break;\n   default:\n      mRendererName = \"Unknown\";\n   }\n\n   return true;\n}\n\nstatic void\nonGpuInterrupt()\n{\n   auto entries = gpu::ih::read();\n   sRingBuffer->onGpuInterrupt();\n}\n\nbool\nSDLWindow::run(const std::string &tracePath)\n{\n   std::atomic_bool shouldQuit = false;\n\n   // Setup some basic window stuff\n   mWindow =\n      SDL_CreateWindow(\"pm4-replay\",\n                       SDL_WINDOWPOS_UNDEFINED,\n                       SDL_WINDOWPOS_UNDEFINED,\n                       WindowWidth, WindowHeight,\n                       SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE);\n\n   if (gpu::config()->display.screenMode == gpu::DisplaySettings::Fullscreen) {\n      SDL_SetWindowFullscreen(mWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);\n   }\n\n   // Setup graphics driver\n   auto wsi = gpu::WindowSystemInfo { };\n   auto sysWmInfo = SDL_SysWMinfo { };\n   SDL_VERSION(&sysWmInfo.version);\n   if (!SDL_GetWindowWMInfo(mWindow, &sysWmInfo)) {\n      gCliLog->error(\"SDL_GetWindowWMInfo failed: {}\", SDL_GetError());\n   }\n\n   switch (sysWmInfo.subsystem) {\n#ifdef SDL_VIDEO_DRIVER_WINDOWS\n   case SDL_SYSWM_WINDOWS:\n      wsi.type = gpu::WindowSystemType::Windows;\n      wsi.renderSurface = static_cast<void *>(sysWmInfo.info.win.window);\n      break;\n#endif\n#ifdef SDL_VIDEO_DRIVER_X11\n   case SDL_SYSWM_X11:\n      wsi.type = gpu::WindowSystemType::X11;\n      wsi.renderSurface = reinterpret_cast<void *>(sysWmInfo.info.x11.window);\n      wsi.displayConnection = static_cast<void *>(sysWmInfo.info.x11.display);\n      break;\n#endif\n#ifdef SDL_VIDEO_DRIVER_COCOA\n   case SDL_SYSWM_COCOA:\n      wsi.type = gpu::WindowSystemType::Cocoa;\n      wsi.renderSurface = static_cast<void *>(sysWmInfo.info.cocoa.window);\n      break;\n#endif\n#ifdef SDL_VIDEO_DRIVER_WAYLAND\n   case SDL_SYSWM_WAYLAND:\n      wsi.type = gpu::WindowSystemType::Wayland;\n      wsi.renderSurface = static_cast<void *>(sysWmInfo.info.wl.surface);\n      wsi.displayConnection = static_cast<void *>(sysWmInfo.info.wl.display);\n      break;\n#endif\n   default:\n      decaf_abort(fmt::format(\"Unsupported SDL window subsystem {}\", sysWmInfo.subsystem));\n   }\n\n   mGraphicsDriver->setWindowSystemInfo(wsi);\n\n   // Setup replay parser\n   auto replayHeap = phys_cast<cafe::TinyHeapPhysical *>(phys_addr { 0x34000000 });\n   cafe::TinyHeap_Setup(replayHeap,\n                        0x430,\n                        phys_cast<void *>(phys_addr { 0x34000000 + 0x430 }),\n                        0x1C000000 - 0x430);\n   auto ringBuffer = std::make_unique<RingBuffer>(replayHeap);\n   sRingBuffer = ringBuffer.get();\n\n   initialiseRegisters(ringBuffer.get());\n\n   auto parser = ReplayParserPM4::Create(mGraphicsDriver, ringBuffer.get(),\n                                         replayHeap, tracePath);\n   if (!parser) {\n      return false;\n   }\n\n   gpu::ih::enable(latte::CP_INT_CNTL::get(0xFFFFFFFF));\n   gpu::ih::setInterruptCallback(onGpuInterrupt);\n\n   auto loopReplay = true;\n   auto replayThread = std::thread {\n      [&]() {\n         do {\n            parser->runUntilTimestamp(0xFFFFFFFFFFFFFFFFull);\n         } while (loopReplay && !shouldQuit);\n      } };\n\n   auto graphicsThread = std::thread {\n      [&]() {\n         mGraphicsDriver->run();\n      } };\n\n   while (!shouldQuit) {\n      auto event = SDL_Event { };\n\n      while (SDL_PollEvent(&event)) {\n         switch (event.type) {\n         case SDL_WINDOWEVENT:\n            if (event.window.event == SDL_WINDOWEVENT_CLOSE) {\n               shouldQuit = true;\n            }\n\n            break;\n         case SDL_KEYUP:\n            if (event.key.keysym.sym == SDLK_ESCAPE) {\n               shouldQuit = true;\n            }\n            break;\n         case SDL_QUIT:\n            shouldQuit = true;\n            break;\n         }\n      }\n\n      std::this_thread::sleep_for(std::chrono::milliseconds(100));\n   }\n\n   // We have to wait until replay is finished...\n   replayThread.join();\n\n   mGraphicsDriver->stop();\n   graphicsThread.join();\n   return true;\n}\n\nvoid initialiseRegisters(RingBuffer *ringBuffer)\n{\n   // Straight copied from gx2\n\n   std::array<uint32_t, 24> zeroes;\n   zeroes.fill(0);\n\n   uint32_t values28030_28034[] = {\n      latte::PA_SC_SCREEN_SCISSOR_TL::get(0).value,\n      latte::PA_SC_SCREEN_SCISSOR_BR::get(0)\n         .BR_X(8192)\n         .BR_Y(8192).value\n   };\n\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::PA_SC_SCREEN_SCISSOR_TL,\n      gsl::make_span(values28030_28034)\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::PA_SC_LINE_CNTL,\n      latte::PA_SC_LINE_CNTL::get(0)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::PA_SU_VTX_CNTL,\n      latte::PA_SU_VTX_CNTL::get(0)\n         .PIX_CENTER(latte::PA_SU_VTX_CNTL_PIX_CENTER::OGL)\n         .ROUND_MODE(latte::PA_SU_VTX_CNTL_ROUND_MODE::TRUNCATE)\n         .QUANT_MODE(latte::PA_SU_VTX_CNTL_QUANT_MODE::QUANT_1_256TH)\n         .value\n                         });\n\n   // PA_CL_POINT_X_RAD, PA_CL_POINT_Y_RAD, PA_CL_POINT_POINT_SIZE, PA_CL_POINT_POINT_CULL_RAD\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::PA_CL_POINT_X_RAD,\n      gsl::make_span(zeroes.data(), 4)\n                         });\n\n   // PA_CL_UCP_0_X ... PA_CL_UCP_5_W\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::PA_CL_UCP_0_X,\n      gsl::make_span(zeroes.data(), 24)\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::PA_CL_VTE_CNTL,\n      latte::PA_CL_VTE_CNTL::get(0)\n         .VPORT_X_SCALE_ENA(true)\n         .VPORT_X_OFFSET_ENA(true)\n         .VPORT_Y_SCALE_ENA(true)\n         .VPORT_Y_OFFSET_ENA(true)\n         .VPORT_Z_SCALE_ENA(true)\n         .VPORT_Z_OFFSET_ENA(true)\n         .VTX_W0_FMT(true)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::PA_CL_NANINF_CNTL,\n      latte::PA_CL_NANINF_CNTL::get(0)\n         .value\n                         });\n\n   uint32_t values28200_28208[] = {\n      0,\n      latte::PA_SC_WINDOW_SCISSOR_TL::get(0)\n         .WINDOW_OFFSET_DISABLE(true)\n         .value,\n      latte::PA_SC_WINDOW_SCISSOR_BR::get(0)\n         .BR_X(8192)\n         .BR_Y(8192)\n         .value,\n   };\n\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::PA_SC_WINDOW_OFFSET,\n      gsl::make_span(values28200_28208)\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::PA_SC_LINE_STIPPLE,\n      latte::PA_SC_LINE_STIPPLE::get(0)\n      .value\n                         });\n\n   uint32_t values28A0C_28A10[] = {\n      latte::PA_SC_MPASS_PS_CNTL::get(0)\n         .value,\n      latte::PA_SC_MODE_CNTL::get(0)\n         .MSAA_ENABLE(true)\n         .FORCE_EOV_CNTDWN_ENABLE(true)\n         .FORCE_EOV_REZ_ENABLE(true)\n         .value\n   };\n\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::PA_SC_LINE_STIPPLE,\n      gsl::make_span(values28A0C_28A10)\n                         });\n\n   uint32_t values28250_28254[] = {\n      latte::PA_SC_VPORT_SCISSOR_0_TL::get(0)\n         .WINDOW_OFFSET_DISABLE(true)\n         .value,\n      latte::PA_SC_VPORT_SCISSOR_0_BR::get(0)\n         .BR_X(8192)\n         .BR_Y(8192)\n         .value,\n   };\n\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::PA_SC_VPORT_SCISSOR_0_TL,\n      gsl::make_span(values28250_28254)\n                         });\n\n   // TODO: Register 0x8B24 unknown\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      static_cast<latte::Register>(0x8B24),\n      0xFF3FFF\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::PA_SC_CLIPRECT_RULE,\n      latte::PA_SC_CLIPRECT_RULE::get(0)\n         .CLIP_RULE(0xFFFF)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      latte::Register::VGT_GS_VERTEX_REUSE,\n      latte::VGT_GS_VERTEX_REUSE::get(0)\n         .VERT_REUSE(16)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_OUTPUT_PATH_CNTL,\n      latte::VGT_OUTPUT_PATH_CNTL::get(0)\n         .PATH_SELECT(latte::VGT_OUTPUT_PATH_SELECT::TESS_EN)\n         .value\n                         });\n\n   // TODO: This is an unknown value 16 * 0xb14(r31) * 0xb18(r31)\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      latte::Register::VGT_ES_PER_GS,\n      latte::VGT_ES_PER_GS::get(0)\n         .ES_PER_GS(16 * 1 * 1)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      latte::Register::VGT_GS_PER_ES,\n      latte::VGT_GS_PER_ES::get(0)\n         .GS_PER_ES(256)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      latte::Register::VGT_GS_PER_VS,\n      latte::VGT_GS_PER_VS::get(0)\n         .GS_PER_VS(4)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_INDX_OFFSET,\n      latte::VGT_INDX_OFFSET::get(0)\n         .INDX_OFFSET(0)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_REUSE_OFF,\n      latte::VGT_REUSE_OFF::get(0)\n         .REUSE_OFF(false)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_MULTI_PRIM_IB_RESET_EN,\n      latte::VGT_MULTI_PRIM_IB_RESET_EN::get(0)\n         .RESET_EN(true)\n         .value\n                         });\n\n   uint32_t values28C58_28C5C[] = {\n      latte::VGT_VERTEX_REUSE_BLOCK_CNTL::get(0)\n         .VTX_REUSE_DEPTH(14)\n         .value,\n      latte::VGT_OUT_DEALLOC_CNTL::get(0)\n         .DEALLOC_DIST(16)\n         .value,\n   };\n\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::VGT_VERTEX_REUSE_BLOCK_CNTL,\n      gsl::make_span(values28C58_28C5C)\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_HOS_REUSE_DEPTH,\n      latte::VGT_HOS_REUSE_DEPTH::get(0)\n         .REUSE_DEPTH(16)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_STRMOUT_DRAW_OPAQUE_OFFSET,\n      latte::VGT_STRMOUT_DRAW_OPAQUE_OFFSET::get(0)\n         .OFFSET(0)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::VGT_VTX_CNT_EN,\n      latte::VGT_VTX_CNT_EN::get(0)\n         .VTX_CNT_EN(false)\n         .value\n                         });\n\n   uint32_t values28400_28404[] = {\n      latte::VGT_MAX_VTX_INDX::get(0)\n         .MAX_INDX(-1)\n         .value,\n      latte::VGT_MIN_VTX_INDX::get(0)\n         .MIN_INDX(0)\n         .value\n   };\n\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::VGT_MAX_VTX_INDX,\n      gsl::make_span(values28400_28404)\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      latte::Register::TA_CNTL_AUX,\n      latte::TA_CNTL_AUX::get(0)\n         .UNK0(true)\n         .SYNC_GRADIENT(true)\n         .SYNC_WALKER(true)\n         .SYNC_ALIGNER(true)\n         .value\n                         });\n\n   // TODO: Register 0x9714 unknown\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      static_cast<latte::Register>(0x9714),\n      1\n                         });\n\n   // TODO: Register 0x8D8C unknown\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      static_cast<latte::Register>(0x8D8C),\n      0x4000\n                         });\n\n   // SQ_ESTMP_RING_BASE ... SQ_REDUC_RING_SIZE\n   ringBuffer->writePM4(latte::pm4::SetConfigRegs {\n      latte::Register::SQ_ESTMP_RING_BASE,\n      gsl::make_span(zeroes.data(), 12)\n                         });\n\n   // SQ_ESTMP_RING_ITEMSIZE ... SQ_REDUC_RING_ITEMSIZE\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::SQ_ESTMP_RING_ITEMSIZE,\n      gsl::make_span(zeroes.data(), 6)\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetControlConstant {\n      latte::Register::SQ_VTX_START_INST_LOC,\n      latte::SQ_VTX_START_INST_LOC::get(0)\n         .OFFSET(0)\n         .value\n                         });\n\n   // SPI_FOG_CNTL ... SPI_FOG_FUNC_BIAS\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::SPI_FOG_CNTL,\n      gsl::make_span(zeroes.data(), 3)\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::SPI_INTERP_CONTROL_0,\n      latte::SPI_INTERP_CONTROL_0::get(0)\n         .FLAT_SHADE_ENA(true)\n         .PNT_SPRITE_ENA(false)\n         .PNT_SPRITE_OVRD_X(latte::SPI_PNT_SPRITE_SEL::SEL_S)\n         .PNT_SPRITE_OVRD_Y(latte::SPI_PNT_SPRITE_SEL::SEL_T)\n         .PNT_SPRITE_OVRD_Z(latte::SPI_PNT_SPRITE_SEL::SEL_0)\n         .PNT_SPRITE_OVRD_W(latte::SPI_PNT_SPRITE_SEL::SEL_1)\n         .PNT_SPRITE_TOP_1(true)\n         .value\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      latte::Register::SPI_CONFIG_CNTL_1,\n      latte::SPI_CONFIG_CNTL_1::get(0)\n         .value\n                         });\n\n   // TODO: Register 0x286C8 unknown\n   ringBuffer->writePM4(latte::pm4::SetAllContextsReg {\n      static_cast<latte::Register>(0x286C8),\n      1\n                         });\n\n   // TODO: Register 0x28354 unknown\n   auto unkValue = 0u; // 0x143C(r31)\n\n   if (unkValue > 0x5270) {\n      ringBuffer->writePM4(latte::pm4::SetContextReg {\n         static_cast<latte::Register>(0x28354),\n         0xFF\n                            });\n   } else {\n      ringBuffer->writePM4(latte::pm4::SetContextReg {\n         static_cast<latte::Register>(0x28354),\n         0x1FF\n                            });\n   }\n\n   uint32_t values28D28_28D2C[] = {\n      latte::DB_SRESULTS_COMPARE_STATE0::get(0)\n         .value,\n      latte::DB_SRESULTS_COMPARE_STATE1::get(0)\n         .value\n   };\n\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::DB_SRESULTS_COMPARE_STATE0,\n      gsl::make_span(values28D28_28D2C)\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::DB_RENDER_OVERRIDE,\n      latte::DB_RENDER_OVERRIDE::get(0)\n         .value\n                         });\n\n   // TODO: Register 0x9830 unknown\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      static_cast<latte::Register>(0x9830),\n      0\n                         });\n\n   // TODO: Register 0x983C unknown\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      static_cast<latte::Register>(0x983C),\n      0x1000000\n                         });\n\n   uint32_t values28C30_28C3C[] = {\n      latte::CB_CLRCMP_CONTROL::get(0)\n         .CLRCMP_FCN_SEL(latte::CB_CLRCMP_SEL::SRC)\n         .value,\n      latte::CB_CLRCMP_SRC::get(0)\n         .CLRCMP_SRC(0)\n         .value,\n      latte::CB_CLRCMP_DST::get(0)\n         .CLRCMP_DST(0)\n         .value,\n      latte::CB_CLRCMP_MSK::get(0)\n         .CLRCMP_MSK(0xFFFFFFFF)\n         .value\n   };\n\n   ringBuffer->writePM4(latte::pm4::SetContextRegs {\n      latte::Register::CB_CLRCMP_CONTROL,\n      gsl::make_span(values28C30_28C3C)\n                         });\n\n   // TODO: Register 0x9A1C unknown\n   ringBuffer->writePM4(latte::pm4::SetConfigReg {\n      static_cast<latte::Register>(0x9A1C),\n      0\n                         });\n\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      latte::Register::PA_SC_AA_MASK,\n      latte::PA_SC_AA_MASK::get(0)\n         .AA_MASK_ULC(0xFF)\n         .AA_MASK_URC(0xFF)\n         .AA_MASK_LLC(0xFF)\n         .AA_MASK_LRC(0xFF)\n         .value\n                         });\n\n   // TODO: Register 0x28230 unknown\n   ringBuffer->writePM4(latte::pm4::SetContextReg {\n      static_cast<latte::Register>(0x28230),\n      0xAAAAAAAA\n                         });\n}\n"
  },
  {
    "path": "tools/pm4-replay/sdl_window.h",
    "content": "#pragma once\n#include <SDL.h>\n#include <libgpu/gpu_graphicsdriver.h>\n\n#include <string>\n#include <thread>\n\nclass SDLWindow\n{\n   static const auto WindowWidth = 1420;\n   static const auto WindowHeight = 768;\n\npublic:\n   ~SDLWindow();\n\n   bool initCore();\n   bool initGraphics();\n\n   bool run(const std::string &tracePath);\n\nprivate:\n   SDL_Window *mWindow = nullptr;\n   gpu::GraphicsDriver *mGraphicsDriver = nullptr;\n   std::string mRendererName;\n   bool mToggleDRC = false;\n};\n"
  },
  {
    "path": "tools/pm4-replay-qt/CMakeLists.txt",
    "content": "project(pm4-replay-qt)\nset(CMAKE_AUTOMOC ON)\nset(CMAKE_INCLUDE_CURRENT_DIR ON)\n\ninclude_directories(\".\")\ninclude_directories(\"../../src/libdecaf/src\")\ninclude_directories(\"../../src/libgpu\")\ninclude_directories(\"../../src/libgpu/src\")\n\nfile(GLOB_RECURSE SOURCE_FILES *.cpp)\nfile(GLOB_RECURSE HEADER_FILES *.h)\nfile(GLOB_RECURSE UI_FILES *.ui)\n\nqt5_wrap_ui(UIS_HDRS ${UI_FILES})\n\nadd_executable(pm4-replay-qt ${SOURCE_FILES} ${HEADER_FILES} ${UIS_HDRS})\nset_target_properties(pm4-replay-qt PROPERTIES FOLDER tools)\n\ntarget_link_libraries(pm4-replay-qt\n    common\n    libdecaf\n    excmd)\n\ntarget_link_libraries(pm4-replay-qt Qt5::Widgets)\n\ninstall(TARGETS pm4-replay-qt RUNTIME DESTINATION \"${DECAF_INSTALL_BINDIR}\")\n"
  },
  {
    "path": "tools/pm4-replay-qt/decaf.cpp",
    "content": "#include \"decaf.h\"\n#include <QEventLoop>\n\n#include <libdecaf/decaf.h>\n#include <libdecaf/src/kernel/kernel_memory.h>\n#include <libdecaf/src/modules/gx2/gx2_internal_cbpool.h>\n#include <libdecaf/src/modules/gx2/gx2_state.h>\n#include <libcpu/cpu.h>\n#include <libcpu/be2_struct.h>\n#include <libgpu/gpu_config.h>\n\nvoid Decaf::start()\n{\n   // Initialise libdecaf logger\n   decaf::config::log::to_file = true;\n   decaf::config::log::to_stdout = true;\n   decaf::config::log::level = \"debug\";\n   decaf::initialiseLogging(\"pm4-replay-qt\");\n\n   gpu::config::debug = true;\n\n   // We need to run the trace on a core.\n   cpu::initialise();\n   cpu::setCoreEntrypointHandler(\n      [runner = this](cpu::Core *core) {\n         if (core->id == 1) {\n            runner->mainCoreEntry();\n         }\n      });\n\n   cpu::start();\n}\n\nvoid Decaf::mainCoreEntry()\n{\n   mThread = QThread::currentThread();\n   mGraphicsDriver = reinterpret_cast<gpu::OpenGLDriver *>(gpu::createGLDriver());\n\n   // Setup decaf\n   kernel::initialiseVirtualMemory();\n   kernel::initialiseAppMemory(0x10000, 0, 0);\n   auto systemHeapBounds = kernel::getVirtualRange(kernel::VirtualRegion::CafeOS);\n   mHeap = new TeenyHeap { virt_cast<void *>(static_cast<virt_addr>(systemHeapBounds.start)).getRawPointer(), systemHeapBounds.size };\n\n   // Setup pm4 command buffer pool\n   auto cbPoolSize = 0x2000;\n   auto cbPoolBase = mHeap->alloc(cbPoolSize, 0x100);\n   gx2::internal::setMainCore();\n   gx2::internal::initCommandBufferPool(reinterpret_cast<uint32_t *>(cbPoolBase), cbPoolSize / 4);\n\n   // Inform listeners we are ready to go!\n   emit started();\n\n   // Run a Qt event loop which we can schedule tasks on\n   QEventLoop loop;\n   loop.exec();\n\n   // Exit!\n   mThread = nullptr;\n}\n"
  },
  {
    "path": "tools/pm4-replay-qt/decaf.h",
    "content": "#pragma once\n#include <libgpu/gpu_opengldriver.h>\n#include <QThread>\n#include <QObject>\n#include <QOpenGLContext>\n#include <QOffscreenSurface>\n#include <QTimer>\n#include <common/teenyheap.h>\n\nclass Decaf : public QObject\n{\n   Q_OBJECT\n\npublic:\n   void start();\n   void mainCoreEntry();\n\n   QThread *thread()\n   {\n      return mThread;\n   }\n\n   QOpenGLContext *context()\n   {\n      return mContext;\n   }\n\n   QOffscreenSurface *surface()\n   {\n      return mSurface;\n   }\n\n   bool makeCurrent()\n   {\n      return mContext->makeCurrent(mSurface);\n   }\n\n   void doneCurrent()\n   {\n      mContext->doneCurrent();\n   }\n\n   void setContext(QOffscreenSurface *surface, QOpenGLContext *context)\n   {\n      mSurface = surface;\n      mContext = context;\n   }\n\n   TeenyHeap *heap()\n   {\n      return mHeap;\n   }\n\n   gpu::OpenGLDriver *graphicsDriver()\n   {\n      return mGraphicsDriver;\n   }\n\nQ_SIGNALS:\n   void started();\n\nprivate:\n   QThread *mThread = nullptr;\n   TeenyHeap *mHeap = nullptr;\n   QOffscreenSurface *mSurface = nullptr;\n   QOpenGLContext *mContext = nullptr;\n   gpu::OpenGLDriver *mGraphicsDriver;\n};\n"
  },
  {
    "path": "tools/pm4-replay-qt/main.cpp",
    "content": "#include \"mainwindow.h\"\n#include <QApplication>\n\nint main(int argc, char **argv)\n{\n   QApplication app { argc, argv };\n   MainWindow mainWin;\n   mainWin.show();\n   return app.exec();\n}\n"
  },
  {
    "path": "tools/pm4-replay-qt/mainwindow.cpp",
    "content": "#include \"replayrunner.h\"\n#include \"mainwindow.h\"\n#include \"replaycommandsmodel.h\"\n\n#include <QFileDialog>\n#include <QOpenGLContext>\n#include <QOffscreenSurface>\n\nMainWindow::MainWindow(QWidget *parent) :\n   QMainWindow(parent),\n   mErrorMessage(this)\n{\n   ui.setupUi(this);\n\n   connect(&mDecaf, &Decaf::started, this, &MainWindow::onDecafStarted);\n   mDecaf.start();\n}\n\nvoid MainWindow::onFileOpen()\n{\n   auto fileName = QFileDialog::getOpenFileName(this,\n                                                tr(\"Open PM4 Replay\"), \"\",\n                                                tr(\"PM4 Replay (*.pm4);;All Files (*)\"));\n\n   if (fileName.isEmpty()) {\n      return;\n   }\n\n   mReplay = openReplay(fileName.toStdString());\n\n   if (!mReplay) {\n      mErrorMessage.showMessage(QString { \"%1 is not a valid pm4 replay file.\" }.arg(fileName));\n      return;\n   }\n\n   // TODO: Thread buildReplayIndex...\n   buildReplayIndex(mReplay);\n\n   auto model = new ReplayCommandModel { mReplay, nullptr };\n   ui.tableView->setModel(model);\n\n   auto runner = new ReplayRunner { &mDecaf, mReplay };\n   connect(runner, &ReplayRunner::frameFinished, ui.openGLWidget, &ReplayRenderWidget::displayFrame);\n   runner->moveToThread(mDecaf.thread());\n   QMetaObject::invokeMethod(runner, \"initialise\");\n   QMetaObject::invokeMethod(runner, \"runFrame\");\n\n   QTimer *timer = new QTimer(this);\n   connect(timer, &QTimer::timeout, runner, &ReplayRunner::runFrame);\n   timer->start(100);\n}\n\nvoid MainWindow::startReplay()\n{\n}\n\nvoid MainWindow::onDecafStarted()\n{\n   ui.openGLWidget->doneCurrent();\n\n   auto surface = new QOffscreenSurface { };\n   surface->setFormat(ui.openGLWidget->context()->format());\n   surface->create();\n\n   auto context = new QOpenGLContext { };\n   context->setFormat(surface->format());\n   context->setShareContext(ui.openGLWidget->context());\n   context->create();\n\n   surface->moveToThread(mDecaf.thread());\n   context->moveToThread(mDecaf.thread());\n   mDecaf.setContext(surface, context);\n\n   ui.openGLWidget->makeCurrent();\n}\n"
  },
  {
    "path": "tools/pm4-replay-qt/mainwindow.h",
    "content": "#pragma once\n#include \"decaf.h\"\n#include \"replay.h\"\n#include \"replayrenderwidget.h\"\n\n#include \"ui_mainwindow.h\"\n#include <QErrorMessage>\n\nclass MainWindow : public QMainWindow\n{\n   Q_OBJECT\n\npublic:\n   explicit MainWindow(QWidget *parent = 0);\n\n   void startReplay();\n\nprotected slots:\n   void onFileOpen();\n   void onDecafStarted();\n\nprivate:\n   Ui_MainWindow ui;\n   std::shared_ptr<ReplayFile> mReplay;\n   QErrorMessage mErrorMessage;\n   Decaf mDecaf;\n};\n"
  },
  {
    "path": "tools/pm4-replay-qt/replay.cpp",
    "content": "#include \"replay.h\"\n#include <libgpu/latte/latte_enum_as_string.h>\n\nstd::shared_ptr<ReplayFile>\nopenReplay(const std::string &path)\n{\n   auto fileSize = size_t { 0 };\n   auto fileHandle = platform::openMemoryMappedFile(path, platform::ProtectFlags::ReadOnly, &fileSize);\n   if (fileHandle == platform::InvalidMapFileHandle) {\n      return nullptr;\n   }\n\n   // Map filew\n   auto fileView = platform::mapViewOfFile(fileHandle, platform::ProtectFlags::ReadOnly, 0, fileSize);\n   if (!fileView) {\n      platform::closeMemoryMappedFile(fileHandle);\n      return nullptr;\n   }\n\n   // Sanity check the magic header\n   auto magic = *reinterpret_cast<std::array<char, 4> *>(fileView);\n   if (magic != decaf::pm4::CaptureMagic) {\n      platform::unmapViewOfFile(fileView, fileSize);\n      platform::closeMemoryMappedFile(fileHandle);\n      return nullptr;\n   }\n\n   auto replay = std::make_shared<ReplayFile>();\n   replay->handle = fileHandle;\n   replay->view = reinterpret_cast<uint8_t *>(fileView);\n   replay->size = fileSize;\n   return replay;\n}\n\nstatic bool\nbuildIndexCommandBuffer(std::shared_ptr<ReplayFile> replay,\n                        size_t filePos,\n                        size_t numWords)\n{\n   auto buffer = reinterpret_cast<be2_val<uint32_t> *>(replay->view + filePos);\n\n   for (auto pos = size_t { 0u }; pos < numWords; ) {\n      auto header = Header::get(buffer[pos]);\n      auto size = size_t { 0u };\n\n      switch (header.type()) {\n      case PacketType::Type0:\n      {\n         auto header0 = HeaderType0::get(header.value);\n         size = header0.count() + 1;\n         break;\n      }\n      case PacketType::Type3:\n      {\n         auto header3 = HeaderType3::get(header.value);\n         size = header3.size() + 1;\n\n         if (header3.opcode() == IT_OPCODE::DECAF_SWAP_BUFFERS) {\n            replay->index.frames.push_back({ ReplayPosition { replay->index.packets.size(), replay->index.commands.size() } });\n         }\n\n         break;\n      }\n      case PacketType::Type2:\n      {\n         // This is a filler packet, like a \"nop\", ignore it\n         break;\n      }\n      case PacketType::Type1:\n      default:\n         size = numWords;\n         break;\n      }\n\n      replay->index.commands.push_back({ header, buffer + pos });\n      pos += size + 1;\n   }\n\n   return true;\n}\n\nbool\nbuildReplayIndex(std::shared_ptr<ReplayFile> replay)\n{\n   size_t pos = 4;\n   replay->index.frames.push_back({ ReplayPosition { 0, 0 } });\n\n   while (pos + sizeof(decaf::pm4::CapturePacket) <= replay->size) {\n      auto packet = reinterpret_cast<decaf::pm4::CapturePacket *>(replay->view + pos);\n      pos += sizeof(decaf::pm4::CapturePacket);\n\n      if (pos + packet->size > replay->size) {\n         break;\n      }\n\n      switch (packet->type) {\n      case decaf::pm4::CapturePacket::CommandBuffer:\n      {\n         buildIndexCommandBuffer(replay, pos, packet->size / 4);\n         break;\n      }\n      case decaf::pm4::CapturePacket::RegisterSnapshot:\n      {\n         break;\n      }\n      case decaf::pm4::CapturePacket::SetBuffer:\n      {\n         break;\n      }\n      case decaf::pm4::CapturePacket::MemoryLoad:\n      {\n         break;\n      }\n      default:\n         break;\n      }\n\n      replay->index.packets.push_back({ packet->type, packet->size, replay->view + pos });\n      pos += packet->size;\n   }\n\n   return true;\n}\n\nstd::string\ngetCommandName(ReplayIndex::Command &command)\n{\n   switch (command.header.type()) {\n   case PacketType::Type0:\n      return \"PM4::Type0 Unknown\";\n   case PacketType::Type1:\n      return \"PM4::Type1 Unknown\";\n   case PacketType::Type2:\n      return \"PM4::Type2 Unknown\";\n   case PacketType::Type3:\n   {\n      auto header3 = HeaderType3::get(command.header.value);\n      return latte::pm4::to_string(header3.opcode());\n   }\n   default:\n      return \"Unknown\";\n   }\n}\n"
  },
  {
    "path": "tools/pm4-replay-qt/replay.h",
    "content": "#pragma once\n#include <array>\n#include <libdecaf/decaf_pm4replay.h>\n#include <libgpu/latte/latte_pm4.h>\n#include <libgpu/latte/latte_pm4_commands.h>\n#include <libgpu/latte/latte_pm4_reader.h>\n#include <libgpu/latte/latte_pm4_writer.h>\n#include <string>\n#include <vector>\n#include <memory>\n#include <common/platform_memory.h>\n\nusing namespace latte::pm4;\n\nstruct ReplayPosition\n{\n   size_t packetIndex;\n   size_t commandIndex;\n};\n\nstruct ReplayIndex\n{\n   struct Frame\n   {\n      ReplayPosition pos;\n   };\n\n   struct Packet\n   {\n      decaf::pm4::CapturePacket::Type type;\n      uint32_t size;\n      uint8_t *data;\n   };\n\n   struct Command\n   {\n      latte::pm4::Header header;\n      void *command;\n   };\n\n   std::vector<Frame> frames;\n   std::vector<Packet> packets;\n   std::vector<Command> commands;\n};\n\nstruct ReplayFile\n{\n   ~ReplayFile()\n   {\n      if (view) {\n         platform::unmapViewOfFile(view, size);\n         view = nullptr;\n      }\n\n      if (handle != platform::InvalidMapFileHandle) {\n         platform::closeMemoryMappedFile(handle);\n         handle = platform::InvalidMapFileHandle;\n      }\n\n      size = 0;\n   }\n\n   platform::MapFileHandle handle = platform::InvalidMapFileHandle;\n   uint8_t *view = nullptr;\n   size_t size = 0;\n   ReplayIndex index;\n};\n\nstd::shared_ptr<ReplayFile>\nopenReplay(const std::string &path);\n\nstd::string\ngetCommandName(ReplayIndex::Command &command);\n\nbool\nbuildReplayIndex(std::shared_ptr<ReplayFile> replay);\n"
  },
  {
    "path": "tools/pm4-replay-qt/replaycommandsmodel.cpp",
    "content": "#include \"replaycommandsmodel.h\"\n\nReplayCommandModel::ReplayCommandModel(std::shared_ptr<ReplayFile> replay, QObject *parent) :\n   QAbstractTableModel(parent),\n   mReplay(replay)\n{\n}\n\nint ReplayCommandModel::rowCount(const QModelIndex &parent) const\n{\n   return static_cast<int>(mReplay->index.commands.size());\n}\n\nint ReplayCommandModel::columnCount(const QModelIndex &parent) const\n{\n   return 2;\n}\n\nQVariant ReplayCommandModel::data(const QModelIndex &index, int role) const\n{\n   if (role == Qt::DisplayRole) {\n      if (index.column() == 0) {\n         return QString { \"%1\" }.arg(index.row() + 1);\n      } else if (index.column() == 1) {\n         return QString::fromStdString(getCommandName(mReplay->index.commands[index.row()]));\n      }\n   }\n\n   return QVariant {};\n}\n"
  },
  {
    "path": "tools/pm4-replay-qt/replaycommandsmodel.h",
    "content": "#pragma once\n#include <QAbstractTableModel>\n#include \"replay.h\"\n\nclass ReplayCommandModel : public QAbstractTableModel\n{\n   Q_OBJECT\npublic:\n   ReplayCommandModel(std::shared_ptr<ReplayFile> replay, QObject *parent = nullptr);\n\n   int rowCount(const QModelIndex &parent = QModelIndex()) const;\n   int columnCount(const QModelIndex &parent = QModelIndex()) const;\n   QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;\n\nprivate:\n   std::shared_ptr<ReplayFile> mReplay;\n};\n"
  },
  {
    "path": "tools/pm4-replay-qt/replayrenderwidget.cpp",
    "content": "#include \"replayrenderwidget.h\"\n\nReplayRenderWidget::ReplayRenderWidget(QWidget *parent) :\n   QOpenGLWidget(parent)\n{\n   QSurfaceFormat format;\n   format.setDepthBufferSize(24);\n   format.setStencilBufferSize(8);\n   format.setVersion(4, 5);\n   format.setProfile(QSurfaceFormat::CompatibilityProfile);\n   setFormat(format);\n}\n\nvoid ReplayRenderWidget::initializeGL()\n{\n   static auto vertexCode = R\"(\n      #version 420 core\n      in vec2 fs_position;\n      in vec2 fs_texCoord;\n      out vec2 vs_texCoord;\n\n      out gl_PerVertex {\n         vec4 gl_Position;\n      };\n\n      void main()\n      {\n         vs_texCoord = fs_texCoord;\n         gl_Position = vec4(fs_position, 0.0, 1.0);\n      })\";\n\n   static auto pixelCode = R\"(\n      #version 420 core\n      in vec2 vs_texCoord;\n      out vec4 ps_color;\n      uniform sampler2D sampler_0;\n\n      void main()\n      {\n         ps_color = texture(sampler_0, vs_texCoord);\n      })\";\n\n   initializeOpenGLFunctions();\n\n   // Create vertex program\n   mVertexProgram = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertexCode);\n\n   // Create pixel program\n   mPixelProgram = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &pixelCode);\n   glBindFragDataLocation(mPixelProgram, 0, \"ps_color\");\n\n   // Create pipeline\n   glGenProgramPipelines(1, &mPipeline);\n   glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertexProgram);\n   glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mPixelProgram);\n\n   // (TL, TR, BR)    (BR, BL, TL)\n   // Create vertex buffer\n   static const GLfloat vertices[] = {\n      -1.0f,  -1.0f,   0.0f, 1.0f,\n      1.0f,  -1.0f,   1.0f, 1.0f,\n      1.0f, 1.0f,   1.0f, 0.0f,\n\n      1.0f, 1.0f,   1.0f, 0.0f,\n      -1.0f, 1.0f,   0.0f, 0.0f,\n      -1.0f,  -1.0f,   0.0f, 1.0f,\n   };\n\n   glCreateBuffers(1, &mVertBuffer);\n   glNamedBufferData(mVertBuffer, sizeof(vertices), vertices, GL_STATIC_DRAW);\n\n   // Create vertex array\n   glCreateVertexArrays(1, &mVertArray);\n\n   auto fs_position = glGetAttribLocation(mVertexProgram, \"fs_position\");\n   glEnableVertexArrayAttrib(mVertArray, fs_position);\n   glVertexArrayAttribFormat(mVertArray, fs_position, 2, GL_FLOAT, GL_FALSE, 0);\n   glVertexArrayAttribBinding(mVertArray, fs_position, 0);\n\n   auto fs_texCoord = glGetAttribLocation(mVertexProgram, \"fs_texCoord\");\n   glEnableVertexArrayAttrib(mVertArray, fs_texCoord);\n   glVertexArrayAttribFormat(mVertArray, fs_texCoord, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat));\n   glVertexArrayAttribBinding(mVertArray, fs_texCoord, 0);\n\n   // Create texture sampler\n   glGenSamplers(1, &mSampler);\n\n   glSamplerParameteri(mSampler, GL_TEXTURE_WRAP_S, static_cast<int>(GL_CLAMP_TO_EDGE));\n   glSamplerParameteri(mSampler, GL_TEXTURE_WRAP_T, static_cast<int>(GL_CLAMP_TO_EDGE));\n   glSamplerParameteri(mSampler, GL_TEXTURE_MIN_FILTER, static_cast<int>(GL_LINEAR));\n   glSamplerParameteri(mSampler, GL_TEXTURE_MAG_FILTER, static_cast<int>(GL_LINEAR));\n}\n\nvoid ReplayRenderWidget::paintGL()\n{\n   // Set up some needed GL state\n   glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);\n   glDisablei(GL_BLEND, 0);\n   glDisable(GL_DEPTH_TEST);\n   glDisable(GL_STENCIL_TEST);\n   glDisable(GL_SCISSOR_TEST);\n   glDisable(GL_CULL_FACE);\n\n   // Clear screen\n   glClearColor(0.6f, 0.2f, 0.2f, 1.0f);\n   glClear(GL_COLOR_BUFFER_BIT);\n\n   // Draw displays\n\n   // Setup screen draw shader\n   glBindVertexArray(mVertArray);\n   glBindVertexBuffer(0, mVertBuffer, 0, 4 * sizeof(GLfloat));\n   glBindProgramPipeline(mPipeline);\n\n   // Draw screen quad\n   glBindSampler(0, mSampler);\n   glBindTextureUnit(0, mTvBuffer);\n\n   glDrawArrays(GL_TRIANGLES, 0, 6);\n}\n\nvoid ReplayRenderWidget::displayFrame(unsigned int tv, unsigned int drc)\n{\n   mTvBuffer = tv;\n   mDrcBuffer = drc;\n   update();\n}\n"
  },
  {
    "path": "tools/pm4-replay-qt/replayrenderwidget.h",
    "content": "#pragma once\n#include <QOpenGLWidget>\n#include <QOpenGLFunctions_4_5_Core>\n\nclass ReplayRenderWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core\n{\n   Q_OBJECT\n\npublic:\n   ReplayRenderWidget(QWidget *parent = 0);\n\n   void initializeGL() override;\n   void paintGL() override;\n\npublic Q_SLOTS:\n   void displayFrame(unsigned int tv, unsigned int drc);\n\nprivate:\n   GLuint mTvBuffer;\n   GLuint mDrcBuffer;\n\n   GLuint mVertexProgram;\n   GLuint mPixelProgram;\n   GLuint mPipeline;\n   GLuint mVertArray;\n   GLuint mVertBuffer;\n   GLuint mSampler;\n};\n"
  },
  {
    "path": "tools/pm4-replay-qt/replayrunner.cpp",
    "content": "\n#include <glbinding/Binding.h>\n#include <glbinding/Meta.h>\n\n#include <common/log.h>\n#include <common/teenyheap.h>\n#include <libdecaf/decaf.h>\n#include <libdecaf/src/kernel/kernel_memory.h>\n#include <libdecaf/src/modules/gx2/gx2_internal_cbpool.h>\n#include <libdecaf/src/modules/gx2/gx2_state.h>\n#include <libcpu/cpu.h>\n#include <libcpu/pointer.h>\n#include <libcpu/be2_struct.h>\n#include <libgpu/gpu_config.h>\n#include <libgpu/gpu_opengldriver.h>\n#include <spdlog/spdlog.h>\n\n#include <condition_variable>\n#include <queue>\n\n#include \"replay.h\"\n#include \"replayrunner.h\"\n\nusing namespace latte::pm4;\nstatic std::string\ngetGlDebugSource(gl::GLenum source)\n{\n   switch (source) {\n   case GL_DEBUG_SOURCE_API:\n      return \"API\";\n   case GL_DEBUG_SOURCE_WINDOW_SYSTEM:\n      return \"WINSYS\";\n   case GL_DEBUG_SOURCE_SHADER_COMPILER:\n      return \"COMPILER\";\n   case GL_DEBUG_SOURCE_THIRD_PARTY:\n      return \"EXTERNAL\";\n   case GL_DEBUG_SOURCE_APPLICATION:\n      return \"APP\";\n   case GL_DEBUG_SOURCE_OTHER:\n      return \"OTHER\";\n   default:\n      return glbinding::Meta::getString(source);\n   }\n}\n\nstatic std::string\ngetGlDebugType(gl::GLenum severity)\n{\n   switch (severity) {\n   case GL_DEBUG_TYPE_ERROR:\n      return \"ERROR\";\n   case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:\n      return \"DEPRECATED_BEHAVIOR\";\n   case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:\n      return \"UNDEFINED_BEHAVIOR\";\n   case GL_DEBUG_TYPE_PORTABILITY:\n      return \"PORTABILITY\";\n   case GL_DEBUG_TYPE_PERFORMANCE:\n      return \"PERFORMANCE\";\n   case GL_DEBUG_TYPE_MARKER:\n      return \"MARKER\";\n   case GL_DEBUG_TYPE_PUSH_GROUP:\n      return \"PUSH_GROUP\";\n   case GL_DEBUG_TYPE_POP_GROUP:\n      return \"POP_GROUP\";\n   case GL_DEBUG_TYPE_OTHER:\n      return \"OTHER\";\n   default:\n      return glbinding::Meta::getString(severity);\n   }\n}\n\nstatic std::string\ngetGlDebugSeverity(gl::GLenum severity)\n{\n   switch (severity) {\n   case GL_DEBUG_SEVERITY_HIGH:\n      return \"HIGH\";\n   case GL_DEBUG_SEVERITY_MEDIUM:\n      return \"MED\";\n   case GL_DEBUG_SEVERITY_LOW:\n      return \"LOW\";\n   case GL_DEBUG_SEVERITY_NOTIFICATION:\n      return \"NOTIF\";\n   default:\n      return glbinding::Meta::getString(severity);\n   }\n}\n\nstatic void GL_APIENTRY\ndebugMessageCallback(gl::GLenum source, gl::GLenum type, gl::GLuint id, gl::GLenum severity,\n                     gl::GLsizei length, const gl::GLchar* message, const void *userParam)\n{\n   for (auto filterID : gpu::config::debug_filters) {\n      if (filterID == id) {\n         return;\n      }\n   }\n\n   auto outputStr = fmt::format(\"GL Message ({}, {}, {}, {}) {}\", id,\n                                getGlDebugSource(source),\n                                getGlDebugType(type),\n                                getGlDebugSeverity(severity),\n                                message);\n\n   if (severity == GL_DEBUG_SEVERITY_HIGH) {\n      gLog->warn(outputStr);\n   } else if (severity == GL_DEBUG_SEVERITY_MEDIUM) {\n      gLog->debug(outputStr);\n   } else if (severity == GL_DEBUG_SEVERITY_LOW) {\n      gLog->trace(outputStr);\n   } else if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) {\n      gLog->info(outputStr);\n   } else {\n      gLog->info(outputStr);\n   }\n}\n\nvoid ReplayRunner::initialise()\n{\n   mDecaf->context()->makeCurrent(mDecaf->surface());\n   glbinding::Binding::initialize();\n\n   if (gpu::config::debug) {\n      glbinding::setCallbackMaskExcept(glbinding::CallbackMask::After | glbinding::CallbackMask::ParametersAndReturnValue, { \"glGetError\" });\n      glbinding::setAfterCallback([](const glbinding::FunctionCall &call) {\n         auto error = glbinding::Binding::GetError.directCall();\n\n         if (error != GL_NO_ERROR) {\n            fmt::MemoryWriter writer;\n            writer << call.function->name() << \"(\";\n\n            for (unsigned i = 0; i < call.parameters.size(); ++i) {\n               writer << call.parameters[i]->asString();\n               if (i < call.parameters.size() - 1)\n                  writer << \", \";\n            }\n\n            writer << \")\";\n\n            if (call.returnValue) {\n               writer << \" -> \" << call.returnValue->asString();\n            }\n\n            gLog->error(\"OpenGL error: {} with {}\", glbinding::Meta::getString(error), writer.str());\n         }\n      });\n\n      gl::glDebugMessageCallback(&debugMessageCallback, nullptr);\n      gl::glEnable(static_cast<gl::GLenum>(GL_DEBUG_OUTPUT));\n      gl::glEnable(static_cast<gl::GLenum>(GL_DEBUG_OUTPUT_SYNCHRONOUS));\n   }\n}\n\nvoid ReplayRunner::runGpu()\n{\n   mDecaf->context()->makeCurrent(mDecaf->surface());\n   mDecaf->graphicsDriver()->syncPoll([&](unsigned int tvBuffer, unsigned int drcBuffer) {\n      emit frameFinished(tvBuffer, drcBuffer);\n   });\n}\n\nvoid ReplayRunner::runFrame()\n{\n   auto foundFrameTerminator = false;\n\n   while (mRunning && mPosition.packetIndex < mReplay->index.packets.size()) {\n      foundFrameTerminator = runPacket(mReplay->index.packets[mPosition.packetIndex]);\n\n      if (foundFrameTerminator) {\n         break;\n      }\n\n      mPosition.packetIndex++;\n   }\n\n   runGpu();\n\n   if (!foundFrameTerminator) {\n      emit replayFinished();\n   }\n}\n\nbool ReplayRunner::runPacket(ReplayIndex::Packet &packet)\n{\n   auto foundFrameTerminator = false;\n\n   switch (packet.type) {\n   case decaf::pm4::CapturePacket::CommandBuffer:\n   {\n      auto commandIndex = 0;\n      auto packetEnd = packet.data + packet.size;\n\n      while (mRunning && !foundFrameTerminator) {\n         auto &command = mReplay->index.commands[mPosition.commandIndex];\n\n         if (command.command >= packetEnd) {\n            break;\n         }\n\n         foundFrameTerminator = runCommand(command);\n         mPosition.commandIndex++;\n      }\n\n      gx2::internal::flushCommandBuffer(0x100);\n      break;\n   }\n   case decaf::pm4::CapturePacket::MemoryLoad:\n   {\n      auto loadPacket = reinterpret_cast<decaf::pm4::CaptureMemoryLoad *>(packet.data);\n      auto loadData = packet.data + sizeof(decaf::pm4::CaptureMemoryLoad);\n      auto dst = virt_cast<void *>(static_cast<virt_addr>(loadPacket->address));\n      std::memcpy(dst.getRawPointer(), loadData, packet.size - sizeof(decaf::pm4::CaptureMemoryLoad));\n      break;\n   }\n   case decaf::pm4::CapturePacket::RegisterSnapshot:\n   {\n      auto registers = reinterpret_cast<uint32_t *>(packet.data);\n      auto count = packet.size / sizeof(uint32_t);\n\n      for (auto i = 0u; i < count; ++i) {\n         mRegisterStorage[i] = registers[i];\n      }\n\n      runRegisterSnapshot(mRegisterStorage, count);\n      gx2::internal::flushCommandBuffer(0x100);\n      break;\n   }\n   case decaf::pm4::CapturePacket::SetBuffer:\n   {\n      auto setBufferPacket = reinterpret_cast<decaf::pm4::CaptureSetBuffer *>(packet.data);\n      auto isTv = (setBufferPacket->type == decaf::pm4::CaptureSetBuffer::TvBuffer) ? 1u : 0u;\n\n      gx2::internal::writePM4(DecafSetBuffer {\n         isTv,\n         setBufferPacket->bufferingMode,\n         setBufferPacket->width,\n         setBufferPacket->height\n      });\n      gx2::internal::flushCommandBuffer(0x100);\n      break;\n   }\n   }\n\n   return foundFrameTerminator;\n}\n\nvoid ReplayRunner::runRegisterSnapshot(be_val<uint32_t> *registers, uint32_t count)\n{\n   // Enable loading of registers\n   auto LOAD_CONTROL = latte::CONTEXT_CONTROL_ENABLE::get(0)\n      .ENABLE_CONFIG_REG(true)\n      .ENABLE_CONTEXT_REG(true)\n      .ENABLE_ALU_CONST(true)\n      .ENABLE_BOOL_CONST(true)\n      .ENABLE_LOOP_CONST(true)\n      .ENABLE_RESOURCE(true)\n      .ENABLE_SAMPLER(true)\n      .ENABLE_CTL_CONST(true)\n      .ENABLE_ORDINAL(true);\n\n   auto SHADOW_ENABLE = latte::CONTEXT_CONTROL_ENABLE::get(0);\n\n   gx2::internal::writePM4(ContextControl {\n      LOAD_CONTROL,\n      SHADOW_ENABLE\n   });\n\n   // Write all the register load packets!\n   static std::pair<uint32_t, uint32_t>\n   LoadConfigRange[] = { { 0, (latte::Register::ConfigRegisterEnd - latte::Register::ConfigRegisterBase) / 4 }, };\n\n   gx2::internal::writePM4(LoadConfigReg {\n      reinterpret_cast<be_val<uint32_t> *>(&registers[latte::Register::ConfigRegisterBase / 4]),\n      gsl::make_span(LoadConfigRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadContextRange[] = { { 0, (latte::Register::ContextRegisterEnd - latte::Register::ContextRegisterBase) / 4 }, };\n\n   gx2::internal::writePM4(LoadContextReg {\n      reinterpret_cast<be_val<uint32_t> *>(&registers[latte::Register::ContextRegisterBase / 4]),\n      gsl::make_span(LoadContextRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadAluConstRange[] = { { 0, (latte::Register::AluConstRegisterEnd - latte::Register::AluConstRegisterBase) / 4 }, };\n\n   gx2::internal::writePM4(LoadAluConst {\n      reinterpret_cast<be_val<uint32_t> *>(&registers[latte::Register::AluConstRegisterBase / 4]),\n      gsl::make_span(LoadAluConstRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadResourceRange[] = { { 0, (latte::Register::ResourceRegisterEnd - latte::Register::ResourceRegisterBase) / 4 }, };\n\n   gx2::internal::writePM4(latte::pm4::LoadResource {\n      reinterpret_cast<be_val<uint32_t> *>(&registers[latte::Register::ResourceRegisterBase / 4]),\n      gsl::make_span(LoadResourceRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadSamplerRange[] = { { 0, (latte::Register::SamplerRegisterEnd - latte::Register::SamplerRegisterBase) / 4 }, };\n\n   gx2::internal::writePM4(LoadSampler {\n      reinterpret_cast<be_val<uint32_t> *>(&registers[latte::Register::SamplerRegisterBase / 4]),\n      gsl::make_span(LoadSamplerRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadControlRange[] = { { 0, (latte::Register::ControlRegisterEnd - latte::Register::ControlRegisterBase) / 4 }, };\n\n   gx2::internal::writePM4(LoadControlConst {\n      reinterpret_cast<be_val<uint32_t> *>(&registers[latte::Register::ControlRegisterBase / 4]),\n      gsl::make_span(LoadControlRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadLoopRange[] = { { 0, (latte::Register::LoopConstRegisterEnd - latte::Register::LoopConstRegisterBase) / 4 }, };\n\n   gx2::internal::writePM4(LoadLoopConst {\n      reinterpret_cast<be_val<uint32_t> *>(&registers[latte::Register::LoopConstRegisterBase / 4]),\n      gsl::make_span(LoadLoopRange)\n   });\n\n   static std::pair<uint32_t, uint32_t>\n   LoadBoolRange[] = { { 0, (latte::Register::BoolConstRegisterEnd - latte::Register::BoolConstRegisterBase) / 4 }, };\n\n   gx2::internal::writePM4(LoadLoopConst {\n      reinterpret_cast<be_val<uint32_t> *>(&registers[latte::Register::BoolConstRegisterBase / 4]),\n      gsl::make_span(LoadBoolRange)\n   });\n}\n\nbool ReplayRunner::runCommand(ReplayIndex::Command &command)\n{\n   auto foundFrameTerminator = false;\n\n   switch (command.header.type()) {\n   case PacketType::Type3:\n   {\n      auto header3 = HeaderType3::get(command.header.value);\n      auto size = header3.size() + 1;\n\n      if (header3.opcode() == IT_OPCODE::DECAF_SWAP_BUFFERS) {\n         foundFrameTerminator = true;\n      }\n\n      if (header3.opcode() == IT_OPCODE::INDIRECT_BUFFER_PRIV) {\n         // Should we iterate through commands in an indirect buffer??\n      }\n\n      decaf::pm4::injectCommandBuffer(command.command, (size + 1) * 4);\n      break;\n   }\n   case PacketType::Type0:\n   {\n      auto header0 = HeaderType0::get(command.header.value);\n      auto size = header0.count() + 1;\n      decaf::pm4::injectCommandBuffer(command.command, (size + 1) * 4);\n      break;\n   }\n   default:\n      // FUCK\n      break;\n   }\n\n   return foundFrameTerminator;\n}\n"
  },
  {
    "path": "tools/pm4-replay-qt/replayrunner.h",
    "content": "#pragma once\n#include \"decaf.h\"\n#include \"replay.h\"\n#include <QObject>\n\nclass ReplayRunner : public QObject\n{\n   Q_OBJECT\n\npublic:\n   ReplayRunner(Decaf *decaf, std::shared_ptr<ReplayFile> replay) :\n      mDecaf(decaf),\n      mReplay(replay)\n   {\n      mRegisterStorage = reinterpret_cast<be_val<uint32_t> *>(mDecaf->heap()->alloc(0x10000 * 4, 0x100));\n   }\n\npublic Q_SLOTS:\n   void initialise();\n   void runFrame();\n\nQ_SIGNALS:\n   void replayFinished();\n   void frameFinished(unsigned int tv, unsigned int drc);\n\nprivate:\n   bool runPacket(ReplayIndex::Packet &packet);\n   bool runCommand(ReplayIndex::Command &command);\n   void runRegisterSnapshot(be_val<uint32_t> *registers, uint32_t count);\n   void runGpu();\n\nprivate:\n   Decaf *mDecaf = nullptr;\n   size_t mPacketIndex = 0;\n   size_t mCommandIndex = 0;\n   size_t mIndirectCommandIndex = 0;\n   std::shared_ptr<ReplayFile> mReplay;\n   be_val<uint32_t> *mRegisterStorage = nullptr;\n   bool mRunning = true;\n   ReplayPosition mPosition = { 0 , 0 };\n};\n"
  },
  {
    "path": "tools/pm4-replay-qt/resources/mainwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>MainWindow</class>\n <widget class=\"QMainWindow\" name=\"MainWindow\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>850</width>\n    <height>693</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>PM4 Replay Tool</string>\n  </property>\n  <widget class=\"QWidget\" name=\"centralwidget\">\n   <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n    <property name=\"spacing\">\n     <number>0</number>\n    </property>\n    <property name=\"leftMargin\">\n     <number>0</number>\n    </property>\n    <property name=\"topMargin\">\n     <number>0</number>\n    </property>\n    <property name=\"rightMargin\">\n     <number>0</number>\n    </property>\n    <property name=\"bottomMargin\">\n     <number>0</number>\n    </property>\n    <item>\n     <widget class=\"ReplayRenderWidget\" name=\"openGLWidget\"/>\n    </item>\n   </layout>\n  </widget>\n  <widget class=\"QMenuBar\" name=\"menubar\">\n   <property name=\"geometry\">\n    <rect>\n     <x>0</x>\n     <y>0</y>\n     <width>850</width>\n     <height>21</height>\n    </rect>\n   </property>\n   <widget class=\"QMenu\" name=\"menuFile\">\n    <property name=\"title\">\n     <string>&amp;File</string>\n    </property>\n    <addaction name=\"actionOpen\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionExit\"/>\n   </widget>\n   <addaction name=\"menuFile\"/>\n  </widget>\n  <widget class=\"QStatusBar\" name=\"statusbar\"/>\n  <widget class=\"QDockWidget\" name=\"dockWidget\">\n   <attribute name=\"dockWidgetArea\">\n    <number>1</number>\n   </attribute>\n   <widget class=\"QWidget\" name=\"dockWidgetContents\">\n    <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n     <property name=\"leftMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>0</number>\n     </property>\n     <item>\n      <widget class=\"QTableView\" name=\"tableView\"/>\n     </item>\n    </layout>\n   </widget>\n  </widget>\n  <action name=\"actionOpen\">\n   <property name=\"text\">\n    <string>&amp;Open</string>\n   </property>\n  </action>\n  <action name=\"actionExit\">\n   <property name=\"text\">\n    <string>E&amp;xit</string>\n   </property>\n  </action>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>ReplayRenderWidget</class>\n   <extends>QOpenGLWidget</extends>\n   <header>replayrenderwidget.h</header>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections>\n  <connection>\n   <sender>actionOpen</sender>\n   <signal>triggered()</signal>\n   <receiver>MainWindow</receiver>\n   <slot>onFileOpen()</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>-1</x>\n     <y>-1</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>424</x>\n     <y>346</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n <slots>\n  <slot>onFileOpen()</slot>\n </slots>\n</ui>\n"
  },
  {
    "path": "tools/wiiu-rpc/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.2)\nset(CMAKE_TOOLCHAIN_FILE \"$ENV{WUT_ROOT}/share/wut.toolchain.cmake\")\nproject(wiiu-rpc)\ninclude(\"${WUT_ROOT}/share/wut.cmake\" REQUIRED)\n\nset(CMAKE_C_STANDARD 99)\n\nadd_executable(wiiu-rpc\n    src/main.c\n    src/console.c\n    src/server.c\n    src/packet.c)\n\ntarget_link_libraries(wiiu-rpc\n    whb\n    coreinit\n    sysapp\n    nsysnet\n    nn_ac\n    proc_ui)\n\nwut_enable_newlib(wiiu-rpc)\nwut_default_malloc(wiiu-rpc)\nwut_create_rpx(wiiu-rpc.rpx wiiu-rpc)\n"
  },
  {
    "path": "tools/wiiu-rpc/client.py",
    "content": "import socket, struct\nimport binascii\nfrom functools import partial\n\nCMD_DYNLOAD_ACQUIRE = 1000\nCMD_DYNLOAD_RELEASE = 1001\nCMD_DYNLOAD_FINDEXPORT = 1002\n\nCMD_READ_MEMORY = 2000\nCMD_WRITE_MEMORY = 2001\nCMD_CALL_FUNCTION = 2002\n\nCMD_IOS_OPEN = 3000\nCMD_IOS_CLOSE = 3001\nCMD_IOS_IOCTL = 3002\nCMD_IOS_IOCTLV = 3003\n\nARG_TYPE_INT32 = 0\nARG_TYPE_INT64 = 1\nARG_TYPE_STRING = 2\nARG_TYPE_DATA_IN = 3\nARG_TYPE_DATA_OUT = 4\nARG_TYPE_DATA_IN_OUT = 5\n\nclass Packet:\n   def __init__(self, command):\n      self.command = command;\n      self.data = \"\"\n      self.readPos = 0\n\n   def writeUint8(self, value):\n      self.data += struct.pack(\">B\", value)\n\n   def writeUint32(self, value):\n      self.data += struct.pack(\">I\", value)\n\n   def writeUint64(self, value):\n      self.data += struct.pack(\">Q\", value)\n\n   def writeInt32(self, value):\n      self.data += struct.pack(\">i\", value)\n\n   def writeInt64(self, value):\n      self.data += struct.pack(\">q\", value)\n\n   def writePointer(self, value):\n      self.writeUint32(value)\n\n   def writeString(self, value):\n      self.writeUint32(len(value) + 1)\n      self.data += value\n      self.writeUint8(0)\n\n   def writeBinary(self, value):\n      self.writeUint32(len(value))\n      self.data += value\n\n   def readUint8(self):\n      [value] = struct.unpack(\">B\", self.data[self.readPos : self.readPos + 1])\n      self.readPos += 1\n      return value\n\n   def readInt32(self):\n      [value] = struct.unpack(\">i\", self.data[self.readPos : self.readPos + 4])\n      self.readPos += 4\n      return value\n\n   def readUint32(self):\n      [value] = struct.unpack(\">I\", self.data[self.readPos : self.readPos + 4])\n      self.readPos += 4\n      return value\n\n   def readPointer(self):\n      return self.readUint32()\n\n   def readInt64(self):\n      [value] = struct.unpack(\">q\", self.data[self.readPos : self.readPos + 8])\n      self.readPos += 8\n      return value\n\n   def readUint64(self):\n      [value] = struct.unpack(\">Q\", self.data[self.readPos : self.readPos + 8])\n      self.readPos += 8\n      return value\n\n   def readString(self):\n      length = self.readUint32()\n      value = self.data[self.readPos : self.readPos + length - 1]\n      self.readUint8()\n      self.readPos += length\n      return value\n\n   def readBinary(self):\n      length = self.readUint32()\n      value = self.data[self.readPos : self.readPos + length]\n      self.readPos += length\n      return value\n\nclass InOutData:\n   def __init__(self, size):\n      self.size = size\n      self.data = \"\"\n\nclass OutData:\n   def __init__(self, size):\n      self.size = size\n      self.data = \"\"\n\nclass Client:\n   def __init__(self):\n      self.fd = None\n\n   def connect(self, ip, port):\n      self.fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n      self.fd.settimeout(5)\n      self.fd.connect((ip, port))\n\n   def disconnect(self):\n      self.fd.close()\n      self.fd = None\n\n   def send(self, packet):\n      header = struct.pack(\">II\", len(packet.data) + 8, packet.command)\n      self.fd.sendall(header + packet.data)\n\n   def recv(self):\n      [length] = struct.unpack(\">I\", self.fd.recv(4))\n      [command] = struct.unpack(\">I\", self.fd.recv(4))\n      dataLength = length - 8\n\n      packet = Packet(command)\n      packet.readPos = 0\n      packet.data = \"\"\n\n      while len(packet.data) < dataLength:\n         remaining = dataLength - len(packet.data)\n         packet.data += self.fd.recv(remaining)\n\n      return packet\n\n   def OSDynLoad_Acquire(self, name):\n      packet = Packet(CMD_DYNLOAD_ACQUIRE)\n      packet.writeString(name)\n      self.send(packet)\n\n      reply = self.recv()\n      result = reply.readInt32()\n      handle = reply.readPointer()\n      return [result, handle]\n\n   def OSDynLoad_Release(self, handle):\n      packet = Packet(CMD_DYNLOAD_RELEASE)\n      packet.writePointer(handle)\n      self.send(packet)\n      self.recv()\n\n   def OSDynLoad_FindExport(self, handle, isData, name):\n      packet = Packet(CMD_DYNLOAD_FINDEXPORT)\n      packet.writePointer(handle)\n      packet.writeInt32(isData)\n      packet.writeString(name)\n      self.send(packet)\n\n      reply = self.recv()\n      result = reply.readInt32()\n      handle = reply.readPointer()\n      return [result, handle]\n\n   def readMemory(self, addr, size):\n      packet = Packet(CMD_READ_MEMORY)\n      packet.writePointer(addr)\n      packet.writeUint32(size)\n      self.send(packet)\n\n      reply = self.recv()\n      data = reply.readBinary()\n      return data\n\n   def writeMemory(self, addr, data):\n      packet = Packet(CMD_WRITE_MEMORY)\n      packet.writePointer(addr)\n      packet.writeBinary(data)\n      self.send(packet)\n\n      reply = self.recv()\n\n   def callFunction(self, addr, *args):\n      packet = Packet(CMD_CALL_FUNCTION)\n      packet.writePointer(addr)\n      packet.writeUint32(len(args))\n\n      for arg in args:\n         if type(arg) is int:\n            packet.writeUint32(ARG_TYPE_INT32)\n            packet.writeUint32(arg)\n         elif type(arg) is long:\n            packet.writeUint32(ARG_TYPE_INT64)\n            packet.writeInt64(arg)\n         elif type(arg) is str:\n            packet.writeUint32(ARG_TYPE_STRING)\n            packet.writeString(arg)\n         elif isinstance(arg, OutData):\n            packet.writeUint32(ARG_TYPE_DATA_OUT)\n            packet.writeUint32(arg.size)\n         elif isinstance(arg, InOutData):\n            packet.writeUint32(ARG_TYPE_DATA_IN_OUT)\n            packet.writeBinary(arg.data)\n\n      self.send(packet)\n      reply = self.recv()\n      result = reply.readUint32()\n      tmpDataOutNum = reply.readUint32()\n\n      for i in range(0, tmpDataOutNum):\n         argNum = reply.readUint32()\n         data = reply.readBinary()\n         args[argNum].data = data\n\n      return result\n\n   def createFunction(self, addr):\n      return partial(self.callFunction, addr)\n\n   def acquireLibrary(self, name):\n      [result, handle] = self.OSDynLoad_Acquire(name)\n      if result != 0:\n         raise Exception(\"Module %s not found\" % name)\n      return handle\n\n   def releaseLibrary(self, handle):\n      self.OSDynLoad_Release(handle)\n\n   def loadFunction(self, handle, name):\n      [result, addr] = self.OSDynLoad_FindExport(handle, 0, name)\n      if result != 0:\n         raise Exception(\"Function %s not found\" % name)\n\n      return self.createFunction(addr)\n\n   def iosIoctlv(self, handle, request, vecsIn, vecsOut):\n      packet = Packet(CMD_IOS_IOCTLV)\n      packet.writeUint32(handle)\n      packet.writeUint32(request)\n      packet.writeUint32(len(vecsIn))\n      packet.writeUint32(len(vecsOut))\n\n      for vec in vecsIn:\n         packet.writeBinary(vec)\n\n      for vec in vecsOut:\n         packet.writeUint32(vec.size)\n\n      self.send(packet)\n      reply = self.recv()\n      result = reply.readUint32()\n\n      for vec in vecsOut:\n         vec.data = reply.readBinary()\n\n      return result\n\nprint \"Connecting...\"\nclient = Client()\nclient.connect(\"192.168.1.132\", 1337)\n\n# Example usage!\nprint \"Connected\"\ncoreinit = client.acquireLibrary(\"coreinit.rpl\")\n\nMCP_Open = client.loadFunction(coreinit, \"MCP_Open\")\nMCP_TitleCount = client.loadFunction(coreinit, \"MCP_TitleCount\")\nMCP_TitleList = client.loadFunction(coreinit, \"MCP_TitleList\")\nMCP_Close = client.loadFunction(coreinit, \"MCP_Close\")\n\nhandle = MCP_Open()\ntitleCount = MCP_TitleCount(handle)\n\nprint \"Title count: %d\" % titleCount\nif titleCount > 0:\n   titleList = OutData(titleCount * 0x61)\n   actualTitleCount = OutData(4)\n\n   result = MCP_TitleList(handle, actualTitleCount, titleList, titleCount * 0x61)\n\n   if result == 0:\n      totalTitleCount = int(binascii.hexlify(actualTitleCount.data), 16)\n      print \"Total number of titles: %d\" % totalTitleCount\n      for i in range(0, totalTitleCount):\n         print \"Title \" + str(i) + \" ID: \" + binascii.hexlify(titleList.data[i*0x61:i*0x61+8])\n\nMCP_Close(handle)\n\nclient.releaseLibrary(coreinit)\nclient.disconnect()\n"
  },
  {
    "path": "tools/wiiu-rpc/src/console.c",
    "content": "#include \"console.h\"\n\n#include <coreinit/cache.h>\n#include <coreinit/memheap.h>\n#include <coreinit/memexpheap.h>\n#include <coreinit/memfrmheap.h>\n#include <coreinit/screen.h>\n\n#include <string.h>\n#include <whb/log.h>\n\n#define NUM_LINES (16)\n#define LINE_LENGTH (128)\n#define FRAME_HEAP_TAG (0x000DECAF)\n\nstatic char sConsoleBuffer[NUM_LINES][LINE_LENGTH];\nstatic int sLineNum = 0;\nstatic void *sBufferTV, *sBufferDRC;\nstatic uint32_t sBufferSizeTV, sBufferSizeDRC;\n\nBOOL\nconsoleInit()\n{\n   MEMHeapHandle heap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM1);\n   MEMRecordStateForFrmHeap(heap, FRAME_HEAP_TAG);\n\n   OSScreenInit();\n   sBufferSizeTV = OSScreenGetBufferSizeEx(SCREEN_TV);\n   sBufferSizeDRC = OSScreenGetBufferSizeEx(SCREEN_DRC);\n\n   sBufferTV = MEMAllocFromFrmHeapEx(heap, sBufferSizeTV, 4);\n   if (!sBufferTV) {\n      WHBLogPrintf(\"sBufferTV = MEMAllocFromFrmHeapEx(heap, 0x%X, 4) returned NULL\", sBufferSizeTV);\n      return FALSE;\n   }\n\n   sBufferDRC = MEMAllocFromFrmHeapEx(heap, sBufferSizeDRC, 4);\n   if (!sBufferDRC) {\n      WHBLogPrintf(\"sBufferDRC = MEMAllocFromFrmHeapEx(heap, 0x%X, 4) returned NULL\", sBufferSizeDRC);\n      return FALSE;\n   }\n\n   OSScreenSetBufferEx(SCREEN_TV, sBufferTV);\n   OSScreenSetBufferEx(SCREEN_DRC, sBufferDRC);\n\n   OSScreenEnableEx(SCREEN_TV, 1);\n   OSScreenEnableEx(SCREEN_DRC, 1);\n   WHBAddLogHandler(consoleAddLine);\n   return TRUE;\n}\n\nvoid\nconsoleFree()\n{\n   MEMHeapHandle heap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM1);\n   OSScreenShutdown();\n   MEMFreeByStateToFrmHeap(heap, FRAME_HEAP_TAG);\n}\n\nvoid\nconsoleDraw()\n{\n   OSScreenClearBufferEx(SCREEN_TV, 0x993333FF);\n   OSScreenClearBufferEx(SCREEN_DRC, 0x993333FF);\n\n   for (int y = 0; y < NUM_LINES; ++y) {\n      OSScreenPutFontEx(SCREEN_TV, 0, y, sConsoleBuffer[y]);\n      OSScreenPutFontEx(SCREEN_DRC, 0, y, sConsoleBuffer[y]);\n   }\n\n   DCFlushRange(sBufferTV, sBufferSizeTV);\n   DCFlushRange(sBufferDRC, sBufferSizeDRC);\n   OSScreenFlipBuffersEx(SCREEN_TV);\n   OSScreenFlipBuffersEx(SCREEN_DRC);\n}\n\nvoid\nconsoleAddLine(const char *line)\n{\n   int length = strlen(line);\n\n   if (length > LINE_LENGTH) {\n      length = LINE_LENGTH - 1;\n   }\n\n   if (sLineNum == NUM_LINES) {\n      for (int i = 0; i < NUM_LINES - 1; ++i) {\n         memcpy(sConsoleBuffer[i], sConsoleBuffer[i + 1], LINE_LENGTH);\n      }\n\n      memcpy(sConsoleBuffer[sLineNum - 1], line, length);\n      sConsoleBuffer[sLineNum - 1][length] = 0;\n   } else {\n      memcpy(sConsoleBuffer[sLineNum], line, length);\n      sConsoleBuffer[sLineNum][length] = 0;\n      ++sLineNum;\n   }\n}\n"
  },
  {
    "path": "tools/wiiu-rpc/src/console.h",
    "content": "#ifndef CONSOLE_H\n#define CONSOLE_H\n\n#include <coreinit/internal.h>\n\nBOOL\nconsoleInit();\n\nvoid\nconsoleFree();\n\nvoid\nconsoleDraw();\n\nvoid\nconsoleAddLine(const char *buffer);\n\n#endif // CONSOLE_H\n"
  },
  {
    "path": "tools/wiiu-rpc/src/main.c",
    "content": "#include \"console.h\"\n#include \"server.h\"\n#include \"packet.h\"\n\n#include <coreinit/core.h>\n#include <coreinit/dynload.h>\n#include <coreinit/filesystem.h>\n#include <coreinit/foreground.h>\n#include <coreinit/ios.h>\n#include <coreinit/memexpheap.h>\n#include <coreinit/memheap.h>\n#include <coreinit/screen.h>\n#include <coreinit/systeminfo.h>\n#include <coreinit/thread.h>\n#include <coreinit/time.h>\n#include <nn/ac/ac_c.h>\n#include <nsysnet/socket.h>\n#include <sysapp/launch.h>\n#include <whb/crash.h>\n#include <whb/file.h>\n#include <whb/gfx.h>\n#include <whb/log.h>\n#include <whb/log_udp.h>\n#include <whb/proc.h>\n\n#include <string.h>\n#include <malloc.h>\n\ntypedef enum PacketCommand\n{\n   CMD_DYNLOAD_ACQUIRE = 1000,\n   CMD_DYNLOAD_RELEASE = 1001,\n   CMD_DYNLOAD_FINDEXPORT = 1002,\n   CMD_READ_MEMORY = 2000,\n   CMD_WRITE_MEMORY = 2001,\n   CMD_CALL_FUNCTION = 2002,\n   CMD_IOS_OPEN = 3000,\n   CMD_IOS_CLOSE = 3001,\n   CMD_IOS_IOCTL = 3002,\n   CMD_IOS_IOCTLV = 3003,\n} PacketCommand;\n\ntypedef enum ArgTypes\n{\n   ARG_TYPE_INT32 = 0,\n   ARG_TYPE_INT64 = 1,\n   ARG_TYPE_STRING = 2,\n   ARG_TYPE_DATA_IN = 3,\n   ARG_TYPE_DATA_OUT = 4,\n   ARG_TYPE_DATA_IN_OUT = 5,\n} ArgTypes;\n\n#define MAX_NUM_ARGS 5\n\nstatic uint32_t\ncallFuncArgs0(void *func)\n{\n   WHBLogPrintf(\"%p()\", func);\n   return ((uint32_t (*)())func)();\n}\n\nstatic uint32_t\ncallFuncArgs1(void *func, uint32_t *args)\n{\n   WHBLogPrintf(\"%p(%x)\", func, args[0]);\n   return ((uint32_t (*)(uint32_t))func)(args[0]);\n}\n\nstatic uint32_t\ncallFuncArgs2(void *func, uint32_t *args)\n{\n   WHBLogPrintf(\"%p(%x, %x)\", func, args[0], args[1]);\n   return ((uint32_t (*)(uint32_t, uint32_t))func)(args[0], args[1]);\n}\n\nstatic uint32_t\ncallFuncArgs3(void *func, uint32_t *args)\n{\n   WHBLogPrintf(\"%p(%x, %x, %x)\", func, args[0], args[1], args[2]);\n   return ((uint32_t (*)(uint32_t, uint32_t, uint32_t))func)(args[0], args[1], args[2]);\n}\n\nstatic uint32_t\ncallFuncArgs4(void *func, uint32_t *args)\n{\n   WHBLogPrintf(\"%p(%x, %x, %x, %x)\", func, args[0], args[1], args[2], args[3]);\n   return ((uint32_t (*)(uint32_t, uint32_t, uint32_t, uint32_t))func)(args[0], args[1], args[2], args[3]);\n}\n\nstatic uint32_t\ncallFuncArgs5(void *func, uint32_t *args)\n{\n   WHBLogPrintf(\"%p(%x, %x, %x, %x, %x)\", func, args[0], args[1], args[2], args[3], args[4]);\n   return ((uint32_t (*)(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t))func)(args[0], args[1], args[2], args[3], args[4]);\n}\n\nint\npacketHandler(Server *server, PacketReader *packet)\n{\n   PacketWriter out;\n   MEMHeapHandle mem2 = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM2);\n\n   switch (packet->command) {\n   case CMD_DYNLOAD_ACQUIRE:\n   {\n      const char *name = pakReadString(packet);\n      OSDynLoad_Module module = NULL;\n      int result;\n\n      WHBLogPrintf(\"OSDynLoad_Acquire(\\\"%s\\\")\", name);\n      result = OSDynLoad_Acquire(name, &module);\n\n      pakWriteAlloc(&out, CMD_DYNLOAD_ACQUIRE);\n      pakWriteInt32(&out, result);\n      pakWritePointer(&out, module);\n      serverSendPacket(server, &out);\n      pakWriteFree(&out);\n      break;\n   }\n   case CMD_DYNLOAD_RELEASE:\n   {\n      OSDynLoad_Module *module = (OSDynLoad_Module *)pakReadPointer(packet);\n\n      WHBLogPrintf(\"OSDynLoad_Release(%p)\", module);\n      OSDynLoad_Release(module);\n\n      pakWriteAlloc(&out, CMD_DYNLOAD_RELEASE);\n      serverSendPacket(server, &out);\n      pakWriteFree(&out);\n      break;\n   }\n   case CMD_DYNLOAD_FINDEXPORT:\n   {\n      OSDynLoad_Module *module = (OSDynLoad_Module *)pakReadPointer(packet);\n      int32_t isData = pakReadInt32(packet);\n      const char *name = pakReadString(packet);\n      void *addr = NULL;\n      int result = 0;\n\n      WHBLogPrintf(\"OSDynLoad_FindExport(%p, %d, \\\"%s\\\")\", module, isData, name);\n      result = OSDynLoad_FindExport(module, isData, name, &addr);\n\n      pakWriteAlloc(&out, CMD_DYNLOAD_FINDEXPORT);\n      pakWriteInt32(&out, result);\n      pakWritePointer(&out, addr);\n      serverSendPacket(server, &out);\n      pakWriteFree(&out);\n      break;\n   }\n   case CMD_READ_MEMORY:\n   {\n      void *src = pakReadPointer(packet);\n      uint32_t size = pakReadUint32(packet);\n\n      pakWriteAlloc(&out, CMD_DYNLOAD_FINDEXPORT);\n      pakWriteData(&out, src, size);\n      serverSendPacket(server, &out);\n      pakWriteFree(&out);\n      break;\n   }\n   case CMD_WRITE_MEMORY:\n   {\n      void *dst = pakReadPointer(packet);\n      uint32_t size;\n      const uint8_t *data = pakReadData(packet, &size);\n\n      memcpy(dst, data, size);\n\n      pakWriteAlloc(&out, CMD_WRITE_MEMORY);\n      serverSendPacket(server, &out);\n      pakWriteFree(&out);\n      break;\n   }\n   case CMD_CALL_FUNCTION:\n   {\n      void *func = pakReadPointer(packet);\n      uint32_t numArgs = pakReadUint32(packet);\n      uint32_t argsArray[MAX_NUM_ARGS * 2] = { 0 };\n      uint8_t *argsTmpData[MAX_NUM_ARGS] = { 0 };\n      uint32_t argsTmpDataSize[MAX_NUM_ARGS] = { 0 };\n      uint32_t numCallArgs = 0;\n      uint32_t numTmpData = 0;\n      uint32_t result = 0;\n\n      WHBLogPrintf(\"Call function %p\", func);\n\n      for (int i = 0; i < numArgs; i++) {\n         uint32_t argType = pakReadUint32(packet);\n\n         if (argType == ARG_TYPE_INT32) {\n            argsArray[numCallArgs++] = pakReadUint32(packet);\n         } else if (argType == ARG_TYPE_INT64) {\n            uint64_t value = pakReadUint64(packet);\n            argsArray[numCallArgs++] = (uint32_t)((value >> 32) & 0xFFFFFFFF);\n            argsArray[numCallArgs++] = (uint32_t)(value & 0xFFFFFFFF);\n         } else if (argType == ARG_TYPE_STRING) {\n            const char *str = pakReadString(packet);\n            argsArray[numCallArgs++] = (uint32_t)str;\n         } else if (argType == ARG_TYPE_DATA_IN) {\n            uint32_t size;\n            const uint8_t *data = pakReadData(packet, &size);\n            argsArray[numCallArgs++] = (uint32_t)data;\n         } else if (argType == ARG_TYPE_DATA_OUT) {\n            argsTmpDataSize[i] = pakReadUint32(packet);\n            argsTmpData[i] = MEMAllocFromExpHeapEx(mem2, argsTmpDataSize[i], 0x100);\n            argsArray[numCallArgs++] = (uint32_t)argsTmpData[i];\n            numTmpData++;\n         } else if (argType == ARG_TYPE_DATA_IN_OUT) {\n            uint32_t size;\n            const uint8_t *data = pakReadData(packet, &size);\n            argsTmpDataSize[i] = size;\n            argsTmpData[i] = MEMAllocFromExpHeapEx(mem2, argsTmpDataSize[i], 0x100);\n            memcpy(argsTmpData[i], data, argsTmpDataSize[i]);\n            argsArray[numCallArgs++] = (uint32_t)argsTmpData[i];\n            numTmpData++;\n         }\n      }\n\n      if (numCallArgs == 0) {\n         result = callFuncArgs0(func);\n      } else if (numCallArgs == 1) {\n         result = callFuncArgs1(func, argsArray);\n      } else if (numCallArgs == 2) {\n         result = callFuncArgs2(func, argsArray);\n      } else if (numCallArgs == 3) {\n         result = callFuncArgs3(func, argsArray);\n      } else if (numCallArgs == 4) {\n         result = callFuncArgs4(func, argsArray);\n      } else if (numCallArgs == 5) {\n         result = callFuncArgs5(func, argsArray);\n      }\n\n      pakWriteAlloc(&out, CMD_CALL_FUNCTION);\n      pakWriteInt32(&out, result);\n      pakWriteUint32(&out, numTmpData);\n\n      for (int i = 0; i < numArgs; ++i) {\n         if (argsTmpData[i]) {\n            pakWriteUint32(&out, i);\n            pakWriteData(&out, argsTmpData[i], argsTmpDataSize[i]);\n            MEMFreeToExpHeap(mem2, argsTmpData[i]);\n         }\n      }\n\n      serverSendPacket(server, &out);\n      pakWriteFree(&out);\n      break;\n   }\n   case CMD_IOS_IOCTLV:\n   {\n      IOSHandle handle = (IOSHandle)pakReadUint32(packet);\n      uint32_t request = pakReadUint32(packet);\n      uint32_t vecIn = pakReadUint32(packet);\n      uint32_t vecOut = pakReadUint32(packet);\n      IOSVec *vecs = MEMAllocFromExpHeapEx(mem2, sizeof(IOSVec) * (vecIn + vecOut), 0x100);\n      void **vecsPtrs = malloc(sizeof(void *) * (vecIn + vecOut));\n      uint32_t *vecsLen = malloc(sizeof(uint32_t) * (vecIn + vecOut));\n      IOSError result;\n\n      for (int i = 0; i < vecIn; ++i) {\n         const uint8_t *data = pakReadData(packet, &vecsLen[i]);\n         vecsPtrs[i] = MEMAllocFromExpHeapEx(mem2, vecsLen[i], 0x100);\n\n         vecs[i].len = vecsLen[i];\n         vecs[i].paddr = vecsPtrs[i];\n         memcpy(vecsPtrs[i], data, vecsLen[i]);\n      }\n\n      for (int i = vecIn; i < vecIn + vecOut; ++i) {\n         vecsLen[i] = pakReadUint32(packet);\n         vecsPtrs[i] = MEMAllocFromExpHeapEx(mem2, vecsLen[i], 0x100);\n         vecs[i].len = vecsLen[i];\n         vecs[i].paddr = vecsPtrs[i];\n         memset(vecsPtrs[i], 0, vecsLen[i]);\n      }\n\n      result = IOS_Ioctlv(handle, request, vecIn, vecOut, vecs);\n\n      pakWriteAlloc(&out, CMD_IOS_IOCTLV);\n      pakWriteInt32(&out, (int32_t)result);\n\n      for (int i = vecIn; i < vecIn + vecOut; ++i) {\n         pakWriteData(&out, vecsPtrs[i], vecsLen[i]);\n      }\n\n      serverSendPacket(server, &out);\n      pakWriteFree(&out);\n\n      for (int i = 0; i < vecIn + vecOut; ++i) {\n         MEMFreeToExpHeap(mem2, vecsPtrs[i]);\n      }\n\n      MEMFreeToExpHeap(mem2, vecs);\n      free(vecsPtrs);\n      free(vecsLen);\n      break;\n   }\n   default:\n      WHBLogPrintf(\"Unknown packet command: %d\", packet->command);\n      return -1;\n   }\n\n   return 0;\n}\n\nint\nmain(int argc, char **argv)\n{\n   Server server;\n   int result = 0;\n\n   WHBProcInit(TRUE);\n   WHBLogUdpInit();\n   WHBInitCrashHandler();\n\n   if (!consoleInit()) {\n      result = -1;\n      goto exit;\n   }\n\n   socket_lib_init();\n   ACInitialize();\n\n   if (serverStart(&server, 1337) == 0) {\n      while(WHBProcIsRunning()) {\n         consoleDraw();\n         serverProcess(&server, &packetHandler);\n         OSSleepTicks(OSMillisecondsToTicks(30));\n      }\n   } else {\n      WHBLogPrintf(\"Failed to start server.\");\n   }\n\nexit:\n   WHBLogPrintf(\"Exiting...\");\n   serverClose(&server);\n   socket_lib_finish();\n   ACFinalize();\n   consoleFree();\n   WHBProcShutdown();\n   return result;\n}\n"
  },
  {
    "path": "tools/wiiu-rpc/src/packet.c",
    "content": "#include \"packet.h\"\n#include \"console.h\"\n\n#include <string.h>\n#include <malloc.h>\n#include <whb/log.h>\n\n#define INITIAL_WRITE_PACKET_SIZE 1024\n\nuint32_t\npakReadUint32(PacketReader *packet)\n{\n   uint32_t value;\n   memcpy(&value, packet->data + packet->pos, sizeof(uint32_t));\n   packet->pos += sizeof(uint32_t);\n\n   if (packet->pos > packet->dataLength) {\n      WHBLogPrintf(\"Read past end of packet! pos: %d length: %d\", packet->pos, packet->dataLength);\n   }\n\n   return value;\n}\n\nuint64_t\npakReadUint64(PacketReader *packet)\n{\n   uint64_t value;\n   memcpy(&value, packet->data + packet->pos, sizeof(uint64_t));\n   packet->pos += sizeof(uint64_t);\n\n   if (packet->pos > packet->dataLength) {\n      WHBLogPrintf(\"Read past end of packet! pos: %d length: %d\", packet->pos, packet->dataLength);\n   }\n\n   return value;\n}\n\nint32_t\npakReadInt32(PacketReader *packet)\n{\n   return (int32_t)pakReadUint32(packet);\n}\n\nint64_t\npakReadInt64(PacketReader *packet)\n{\n   return (int64_t)pakReadUint32(packet);\n}\n\nvoid *\npakReadPointer(PacketReader *packet)\n{\n   return (void *)pakReadUint32(packet);\n}\n\nconst uint8_t *\npakReadData(PacketReader *packet, uint32_t *length)\n{\n   const uint8_t *data;\n   *length = pakReadUint32(packet);\n   data = packet->data + packet->pos;\n   packet->pos += *length;\n\n   if (packet->pos > packet->dataLength) {\n      WHBLogPrintf(\"Read past end of packet! pos: %d length: %d\", packet->pos, packet->dataLength);\n   }\n\n   return data;\n}\n\nconst char *\npakReadString(PacketReader *packet)\n{\n   uint32_t length = pakReadUint32(packet);\n   const char *str = packet->data + packet->pos;\n   packet->pos += length;\n\n   if (packet->pos > packet->dataLength) {\n      WHBLogPrintf(\"Read past end of packet! pos: %d length: %d\", packet->pos, packet->dataLength);\n   }\n\n   return str;\n}\n\nvoid\npakWriteAlloc(PacketWriter *packet, uint32_t command)\n{\n   packet->command = command;\n   packet->pos = 0;\n   packet->bufferSize = INITIAL_WRITE_PACKET_SIZE;\n   packet->buffer = malloc(packet->bufferSize);\n\n   pakWriteUint32(packet, 8);\n   pakWriteUint32(packet, command);\n}\n\nvoid\npakWriteFree(PacketWriter *packet)\n{\n   free(packet->buffer);\n   packet->buffer = NULL;\n}\n\nstatic void\npakWriteIncreaseSize(PacketWriter *packet, uint32_t size)\n{\n   uint32_t newSize = packet->pos + size;\n\n   if (newSize > packet->bufferSize) {\n      uint32_t newBufferSize = packet->bufferSize;\n\n      while (newSize > newBufferSize) {\n         newBufferSize += INITIAL_WRITE_PACKET_SIZE;\n      }\n\n      char *newBuffer = malloc(newBufferSize);\n      memcpy(newBuffer, packet->buffer, packet->bufferSize);\n      free(packet->buffer);\n      packet->buffer = newBuffer;\n      packet->bufferSize = newBufferSize;\n   }\n\n   // Update size at word 0\n   memcpy(packet->buffer, &newSize, sizeof(uint32_t));\n}\n\nvoid\npakWriteUint32(PacketWriter *packet, uint32_t value)\n{\n   char *dst;\n   pakWriteIncreaseSize(packet, sizeof(uint32_t));\n\n   dst = packet->buffer + packet->pos;\n   memcpy(dst, &value, sizeof(uint32_t));\n   packet->pos += sizeof(uint32_t);\n}\n\nvoid\npakWriteUint64(PacketWriter *packet, uint64_t value)\n{\n   char *dst;\n   pakWriteIncreaseSize(packet, sizeof(uint64_t));\n\n   dst = packet->buffer + packet->pos;\n   memcpy(dst, &value, sizeof(uint64_t));\n   packet->pos += sizeof(uint64_t);\n}\n\nvoid\npakWriteInt32(PacketWriter *packet, int32_t value)\n{\n   pakWriteUint32(packet, (uint32_t)value);\n}\n\nvoid\npakWriteInt64(PacketWriter *packet, int64_t value)\n{\n   pakWriteUint64(packet, (uint64_t)value);\n}\n\nvoid\npakWritePointer(PacketWriter *packet, void *value)\n{\n   pakWriteUint32(packet, (uint32_t)value);\n}\n\nvoid\npakWriteString(PacketWriter *packet, const char *str)\n{\n   uint32_t length = str ? strlen(str) : 0;\n   pakWriteData(packet, (const uint8_t *)str, length + 1);\n}\n\nvoid\npakWriteData(PacketWriter *packet, const uint8_t *data, uint32_t length)\n{\n   char *dst;\n   pakWriteUint32(packet, length);\n\n   if (data && length > 0) {\n      pakWriteIncreaseSize(packet, length);\n\n      dst = packet->buffer + packet->pos;\n      memcpy(dst, data, length);\n      packet->pos += length;\n   }\n}\n"
  },
  {
    "path": "tools/wiiu-rpc/src/packet.h",
    "content": "#ifndef PACKET_H\n#define PACKET_H\n\n#include <wut_types.h>\n\ntypedef struct PacketReader\n{\n   uint32_t command;\n   uint8_t *data;\n   uint32_t dataLength;\n   uint32_t pos;\n} PacketReader;\n\ntypedef struct PacketWriter\n{\n   uint32_t command;\n   uint32_t pos;\n   void *buffer;\n   uint32_t bufferSize;\n} PacketWriter;\n\nuint32_t\npakReadUint32(PacketReader *packet);\n\nuint64_t\npakReadUint64(PacketReader *packet);\n\nint32_t\npakReadInt32(PacketReader *packet);\n\nint64_t\npakReadInt64(PacketReader *packet);\n\nvoid *\npakReadPointer(PacketReader *packet);\n\nconst uint8_t *\npakReadData(PacketReader *packet, uint32_t *length);\n\nconst char *\npakReadString(PacketReader *packet);\n\nvoid\npakWriteAlloc(PacketWriter *packet, uint32_t command);\n\nvoid\npakWriteFree(PacketWriter *packet);\n\nvoid\npakWriteUint32(PacketWriter *packet, uint32_t value);\n\nvoid\npakWriteUint64(PacketWriter *packet, uint64_t value);\n\nvoid\npakWriteInt32(PacketWriter *packet, int32_t value);\n\nvoid\npakWriteInt64(PacketWriter *packet, int64_t value);\n\nvoid\npakWritePointer(PacketWriter *packet, void *value);\n\nvoid\npakWriteString(PacketWriter *packet, const char *str);\n\nvoid\npakWriteData(PacketWriter *packet, const uint8_t *data, uint32_t length);\n\n#endif // PACKET_H\n"
  },
  {
    "path": "tools/wiiu-rpc/src/server.c",
    "content": "#include \"server.h\"\n#include \"console.h\"\n#include \"packet.h\"\n\n#include <coreinit/memexpheap.h>\n#include <coreinit/memheap.h>\n#include <coreinit/systeminfo.h>\n#include <coreinit/thread.h>\n#include <coreinit/time.h>\n#include <nn/ac/ac_c.h>\n#include <nsysnet/socket.h>\n#include <stdbool.h>\n#include <stdbool.h>\n#include <string.h>\n#include <whb/log.h>\n\n#define SEND_MAX_BYTES (1024)\n\nint\nserverStart(Server *server, int port)\n{\n   int fd, opt;\n   struct sockaddr_in addr;\n   struct in_addr ip_addr;\n   uint32_t ipAddress;\n   ACConfigId startupId;\n   MEMHeapHandle mem2 = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM2);\n\n   server->fd = -1;\n   server->client = -1;\n   server->readPos = 0;\n   server->readLength = 0;\n   server->readMax = 1024 * 1024;\n   server->readBuffer = (uint8_t *)MEMAllocFromExpHeapEx(mem2, server->readMax, 0x100);\n\n   fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);\n\n   if (fd < 0) {\n      WHBLogPrintf(\"Error creating socket: %d\", socketlasterr());\n      return -1;\n   }\n\n   setsockopt(fd, SOL_SOCKET, SO_NBIO, NULL, 0);\n\n   opt = 1;\n   setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));\n\n   memset(&addr, 0, sizeof(addr));\n   addr.sin_family = AF_INET;\n   addr.sin_port = htons(port);\n   addr.sin_addr.s_addr = htonl(INADDR_ANY);\n\n   if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {\n      socketclose(fd);\n      WHBLogPrintf(\"Error binding socket: %d\", socketlasterr());\n      return -1;\n   }\n\n   if (listen(fd, 3) < 0) {\n      socketclose(fd);\n      WHBLogPrintf(\"Error listening on socket: %d\", socketlasterr());\n      return -1;\n   }\n\n   ACGetStartupId(&startupId);\n   ACConnectWithConfigId(startupId);\n   ACGetAssignedAddress(&ipAddress);\n\n   ip_addr.s_addr = ipAddress;\n   WHBLogPrintf(\"Started server on %s:%d\", inet_ntoa(ip_addr), port);\n   server->fd = fd;\n   return 0;\n}\n\nint serverClose(Server *server)\n{\n   MEMHeapHandle mem2 = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM2);\n   serverCloseClient(server);\n\n   if (server->fd != -1) {\n      socketclose(server->fd);\n      server->fd = -1;\n   }\n\n   server->readMax = 0;\n   MEMFreeToExpHeap(mem2, server->readBuffer);\n   return 0;\n}\n\nvoid\nserverCloseClient(Server *server)\n{\n   if (server->client != -1) {\n      socketclose(server->client);\n      server->client = -1;\n   }\n}\n\nstatic int\nserverAccept(Server *server)\n{\n   struct sockaddr_in addr;\n   socklen_t addrlen = sizeof(addr);\n   int clientFd;\n\n   while (true) {\n      clientFd = accept(server->fd, (struct sockaddr *)&addr, &addrlen);\n\n      if (clientFd < 0) {\n         int error = socketlasterr();\n\n         if (error != NSN_EWOULDBLOCK) {\n            WHBLogPrintf(\"Error, accept returned %d\", clientFd);\n            return error;\n         }\n\n         return 0;\n      }\n\n      if (server->client != -1) {\n         WHBLogPrintf(\"Max clients reached, reject %s\", inet_ntoa(addr.sin_addr));\n         socketclose(clientFd);\n         return 0;\n      }\n\n      WHBLogPrintf(\"Accepted connection from %s\", inet_ntoa(addr.sin_addr));\n      server->client = clientFd;\n   }\n\n   return 0;\n}\n\nint serverSendPacket(Server *server, PacketWriter *packet)\n{\n   int sent = 0;\n   while (sent < packet->pos) {\n      int sendBytes = packet->pos - sent;\n      int result = 0;\n      if (sendBytes > SEND_MAX_BYTES) {\n         sendBytes = SEND_MAX_BYTES;\n      }\n\n      result = send(server->client, (uint8_t *)packet->buffer + sent, sendBytes, 0);\n      if (result > 0) {\n         sent += sendBytes;\n      }\n\n      if (result != sendBytes) {\n         int error = socketlasterr();\n         if (error == NSN_EWOULDBLOCK) {\n            OSSleepTicks(OSMillisecondsToTicks(1));\n            continue;\n         }\n\n         WHBLogPrintf(\"send returned %d instead of %d, error: %d\", result, sendBytes, error);\n         break;\n      }\n   }\n\n   return sent;\n}\n\nstatic int\nserverHandlePacket(Server *server, PacketHandlerFn fn)\n{\n   PacketReader reader;\n   reader.dataLength = *(uint32_t *)(server->readBuffer + 0) - 8;\n   reader.command = *(uint32_t *)(server->readBuffer + 4);\n   reader.data = server->readBuffer + 8;\n   reader.pos = 0;\n\n   return fn(server, &reader);\n}\n\nstatic int\nserverRead(Server *server, PacketHandlerFn fn)\n{\n   while (server->client != -1) {\n      int read;\n      int length;\n\n      if (server->readLength == 0) {\n         length = 4;\n      } else {\n         length = server->readLength;\n      }\n\n      read = recv(server->client, server->readBuffer + server->readPos, length, 0);\n      if (read < 0) {\n         int error = socketlasterr();\n\n         if (error != NSN_EWOULDBLOCK) {\n            WHBLogPrintf(\"Error receiving from socket: %d\", error);\n            serverCloseClient(server);\n            return error;\n         }\n\n         return 0;\n      } else if (read == 0) {\n         WHBLogPrintf(\"Client disconnected gracefully.\");\n         serverCloseClient(server);\n         return read;\n      }\n\n      server->readPos += read;\n\n      if (server->readLength == 0 && server->readPos >= 2) {\n         server->readLength = *(uint32_t *)server->readBuffer;\n      }\n\n      if (server->readPos >= server->readLength) {\n         serverHandlePacket(server, fn);\n         server->readPos = 0;\n         server->readLength = 0;\n      }\n   }\n\n   return 0;\n}\n\nint\nserverProcess(Server *server, PacketHandlerFn fn)\n{\n   int error = serverAccept(server);\n\n   if (error) {\n      return error;\n   }\n\n   return serverRead(server, fn);\n}\n"
  },
  {
    "path": "tools/wiiu-rpc/src/server.h",
    "content": "#ifndef SERVER_H\n#define SERVER_H\n\n#include <wut_types.h>\n\ntypedef struct Server\n{\n   int fd;\n   int client;\n   int readPos;\n   int readLength;\n   int readMax;\n   uint8_t *readBuffer;\n} Server;\n\ntypedef struct PacketReader PacketReader;\ntypedef struct PacketWriter PacketWriter;\n\ntypedef int (*PacketHandlerFn)(Server *server, PacketReader *reader);\n\nint serverStart(Server *server, int port);\nint serverClose(Server *server);\nvoid serverCloseClient(Server *server);\nint serverProcess(Server *server, PacketHandlerFn fn);\n\nint serverSendPacket(Server *server, PacketWriter *packet);\n\n#endif // SERVER_H\n"
  },
  {
    "path": "vcpkg.json",
    "content": "{\n  \"name\": \"decaf-emu\",\n  \"version-string\": \"0.2.0\",\n  \"builtin-baseline\": \"06c79a9afa6f99f02f44d20df9e0848b2a56bf1b\",\n  \"dependencies\": [\n    {\n      \"name\": \"ffmpeg\",\n      \"default-features\": false,\n      \"features\": [\"avcodec\", \"avfilter\", \"swscale\"]\n    },\n    \"c-ares\",\n    \"curl\",\n    \"libuv\",\n    \"openssl\",\n    \"sdl2\",\n    \"zlib\"\n  ]\n}\n"
  }
]